Bootstrap

ReactNative踩坑及技术方案总结(2022 持续更新中)

一、写在前面

1. 前言

  经过将近一个月的学习和开发,也做出了第一个由RN开发的 Android 应用。该文章旨在将近期学习的 React Native 所踩的坑、技术方案进行总结,为之后的开发留下可参考的财富,减少重复造轮子、踩坑的时间。(你所深陷的困境,可能别人早已轻而易举地解决,不要把脑力花在这上面!除非你想自己造轮子。)

2. 总结路线

  1. 环境、框架搭建相关
  2. 基础组件
  3. 第三方组件
  4. 样式
  5. 代码实现
  6. 常见报错

3. 输出内容

  1. 环境搭建:傻瓜式可操作步骤(参考「菜谱」)
  2. 组件:最简单用法 -> 效果 -> 扩展用法(参考「UI库官方文档」)
  3. 样式:希望实现的效果 -> 写法
  4. 代码实现:希望实现的功能 -> 写法 -> 注意点
  5. 报错:报错内容 -> 报错原因 -> 解决办法

二、环境、框架搭建

1. 在电脑上安装多个JDK版本

要让 RN 跑起来,JDK 环境是必须的,网上已有非常多的 JDK 安装教程,找一篇跟着操作即可

  这里要解决的问题是:假设我电脑上已原有 JDK8 版本,而官方文档要求如果 RN 版本 >= 0.67,则需要 JDK11 的版本。那么此时我该如何 **同时配置两个JDK环境?**操作步骤如下:

  1. 先正常安装 JDK8 和 JDK11 两个版本
  2. 安装完成后,按照下图步骤,配置两个环境变量,分别是 JAVA8_HOMEJAVA11_HOME

image.png

  1. 若在当前电脑需要切换到 JDK8 的环境,则在Path里配置如下(JDK11同理),并点击确定

image.png

  1. Path里将要使用的JDK版本放在最前面,即可完成切换

image.png

  1. 最后需打开 的cmd窗口,输入java -version验证当前版本是否已切换成功

如下图,即代表当前环境为 JDK11

image.png

2. 真机进行网络调试

方式一:react-native-debugger

方式二:reactotron【推荐】

Reactotron 官方链接:https://github.com/infinitered/reactotron/blob/master/docs/quick-start-react-native.md

其使用非常简单,根据官方文档进行操作即可,大体分为两个步骤:

  1. 安装本地客户端
  2. 在项目代码中配置后,重新编译即可生效,并在客户端中看到效果如图

image.png


三、基础组件

1. TouchableOpacity

  有点击样式的 块元素

可以理解为:加了 hover 样式的 div。与之不同的是,在RN中,<View> 无法添加 onPress 来触发点击事件,因此需要在 <View> 外层套一个 <TouchableOpacity onPress={...}>

import { TouchableOpacity, Text  } from 'react-native'

const handlePress = () => { ... }

const Index = () => {
  <TouchableOpacity onPress={handlePress} activeOpacity={0.5}>
    <View></View>
  </TouchableOpacity>
}

常用属性:

  • activeOpacity:控制手指按下时,元素的透明度变化值(类似css的 div:hover { opacity: 0.5 }

四、样式

1. 响应式

UI给出的设计稿的屏幕宽度通常都是 375,也就是以 iPhone6 作为标准来设计,而实际开发中,为了适配各种机型的显示,我们需要声明如下函数,并在样式中使用它

  1. 声明
// src/utils/stylesKits.js
import {Dimensions} from 'react-native';

// 手机中元素的宽度 = 手机屏幕 * 元素宽度 / 设计稿宽度(以375为例)
export const screenWidth = Dimensions.get('window').width;
export const screenHeight = Dimensions.get('window').height;

export const pxToDp = elePx => (screenWidth * elePx) / 375;
  1. 使用
// xxx.js 组件中编写styles时引用
import {pxToDp} from '../../utils/stylesKits';

const styles = StyleSheet.create({
  image: {
    width: pxToDp(150),
    height: pxToDp(150),
  }
});

2. 沉浸式导航栏

使用的路由和导航栏组件是 @react-navigation/native-stack

参考文档:https://reactnavigation.org/docs/native-stack-navigator

当希望实现类似「沉浸式导航栏」时,我们需要把顶部的导航栏隐藏,实现如下:

方法一:【不推荐,会占位】将导航栏的背景色和字体色调整成和页面背景色一致即可

<Stack.Navigator initialRouteName="Login">
  <Stack.Screen
    name="Login"
    component={Login}
    options={{
      headerStyle: {
        backgroundColor: '#fff',
      },
      // 隐藏标头上的高程阴影 (Android) 或底部边框 (iOS)。
      headerShadowVisible: false,
      headerTintColor: '#fff',
    }}
    />
</Stack.Navigator>

方法二:【推荐】将 header 设置成 null

header 实际用途是:自定义标题栏,此时返回 null 相当于自定义了一个没有任何结构的导航栏,达到了隐藏原生导航栏的目的

<Stack.Navigator initialRouteName="Login">
  <Stack.Screen
    name="Login"
    component={Login}
    options={{
          header: () => {
            return null
          },
        }}
    />
</Stack.Navigator>

3. 设置渐变色

RN 无法像 CSS 一样,直接通过 linear-gradient 就可以设置渐变色

需要引入第三方插件 https://www.npmjs.com/package/react-native-linear-gradient

  1. 安装
yarn add react-native-linear-gradient
  1. 简单使用
<LinearGradinet
  start={{x: 0, y: 0}}
  end={{x: 1, y: 0}}
  colors={['#9b63cd', '#e0708c']}
  style={{width: 200, height: 200}}
/>

五、写法

1. useEffect 中使用异步函数

  当我尝试中 useEffect 中用如下代码写「异步函数」时,报错如下

  useEffect must not return anything besides a function, which is used for clean-up.

// 错误写法
useEffect(async () => {
  ...
}, []);

  正确写法如下

// 正确写法
useEffect(() => {
  (async () => {
    ...
  })();
}, []);

2. 缓存

原生自带的 AsyncStorage 已被官方废弃,其推荐使用社区的包

  因此,我们使用 @react-native-async-storage/async-storage(社区的其他包也可以,该包较为容易上手),地址:https://react-native-async-storage.github.io/async-storage/docs/install。简单使用如下:

  1. 安装
yarn add @react-native-async-storage/async-storage
  1. 在需要使用的地方导入
import AsyncStorage from '@react-native-async-storage/async-storage';
  1. 写入缓存

① 写入的是如「字符串」之类的值类型

const storeData = async (value) => {
  try {
    await AsyncStorage.setItem('@storage_Key', value)
  } catch (e) {
    // saving error
  }
}

② 写入的是如「对象、数组」之类的引用类型(需要先将对象转换成字符串)

const storeData = async (value) => {
  try {
    const jsonValue = JSON.stringify(value)
    await AsyncStorage.setItem('@storage_Key', jsonValue)
  } catch (e) {
    // saving error
  }
}
  1. 读取缓存

① 读取「字符串」之类的值类型

const getData = async () => {
  try {
    const value = await AsyncStorage.getItem('@storage_Key')
    if(value !== null) {
      // value previously stored
    }
  } catch(e) {
    // error reading value
  }
}

② 读取「对象、数组」之类的引用类型

const getData = async () => {
  try {
    const jsonValue = await AsyncStorage.getItem('@storage_Key')
    return jsonValue != null ? JSON.parse(jsonValue) : null;
  } catch(e) {
    // error reading value
  }
}
;