环境安装

参见官网:

https://reactnative.dev/docs/environment-setup

https://reactnative.cn/docs/environment-setup

Notes

项目创建

npx react-native@latest init rnProject

Notes:项目名称只支持驼峰,不支持连字符。

项目运行

npm run android

  • 往模拟器或真机安装APK:包运行所需要的资源。
  • 引用原生依赖,需要重新运行该命令。
  • --mode=release临时打包,输出路径android\app\build\outputs\apk\release

npm run start

  • 运行Metro打包JS代码,启动热更新,在模拟器或真机实时查看改动。
  • --reset-cache清除Metro缓存,重新编译JS代码。

清除运行缓存

  • "./gradlew" clean

    • 切换到android目录下,在命令行执行该命令,可清除gradle缓存。
  • npx react-native start --reset-cache

    • 清除Metro缓存,重新编译JS代码
    • 应用场景:环境报错、开发者工具出现问题

页面适配

方案一:

// src/utils/px2dp.js
import { Dimensions, PixelRatio, StyleSheet } from 'react-native';
const windowWidth = Dimensions.get('window').width;

const px2dp = function (px) {
  if (!isNaN(px)) {
    return (windowWidth / 1080) * px / PixelRatio.get()
  } else {
    return 0
  }
}
export default px2dp
// entry.js
import px2dp from '../utils/px2dp';
StyleSheet.create({
  pageTitle: {
    fontSize: px2dp(64),
    lineHeight: px2dp(85),
    paddingLeft: px2dp(100),
    paddingRight: px2dp(100),
    marginTop: px2dp(80),
  },
})

方案二:


import React from 'react';
import {
  StyleSheet,
  Text,
  Dimensions,
  PixelRatio,
  View,
} from 'react-native';
const { width: layoutWidth, height: layoutHeight } = Dimensions.get('window') // 获取到设备pd
const ratio = PixelRatio.get() // 像素密度
const pixelWidth = PixelRatio.getPixelSizeForLayoutSize(layoutWidth) // pd转px
const pixelHeight = PixelRatio.getPixelSizeForLayoutSize(layoutHeight) // pd转px
const designWidth = 950 // 设计图尺寸

/**
 * 设备以px展示,以1/ratio缩放,通过translateX、translateY重置transform原点
 * | 设计图 | 设备 |
 * | 20px | x |
 * | 950px | pixelWidth |
 * 同等占比:x = 20 * pixelWidth / 950
 * ============================================
 * 结合缩放比例:
 * scale = pixelWidth / (designWidth * ratio)
 * 其余组件可按设计图设置像素即可
*/
const styles = StyleSheet.create({
  adapter: {
    width: pixelWidth,
    height: pixelHeight,
    transform: [
      {
        translateX: -pixelWidth * 0.5,
      },
      {
        translateY: -pixelHeight * 0.5
      },
      {
        scale: pixelWidth / (ratio * designWidth)
      },
      {
        translateX: pixelWidth * 0.5,
      },
      {
        translateY: pixelHeight * 0.5
      }
    ]
  },
  fullscreen: {
    width: designWidth,
    height:90,
    backgroundColor: 'red'
  },
  halfscreen: {
    width: designWidth / 2,
    height:90,
    backgroundColor: 'green'
  },
  quaterscreen: {
    width: designWidth / 4,
    height:90,
    backgroundColor: 'blue'
  }
});

function App(): JSX.Element {
  return (
    <View style={styles.adapter}>
      <View style={styles.fullscreen}>
        <Text>1</Text>
      </View>
      <View style={styles.halfscreen}>
        <Text>2</Text>
      </View>
      <View style={styles.quaterscreen}>
        <Text>3</Text>
      </View>
    </View>
  );
}

export default App;

方案二优化:

方案二虽然能展示正常,但在android Studio中看到的布局并不是可视宽度,现将容器宽高设置为对应的设计图尺寸即可。
const designWidth = 1080;

function useAdaptationStyle() {
  const {
    width: layoutWidth,
    scale: ratio,
    height: layoutHeight,
  } = useWindowDimensions();
  const {height: screenHeight} = Dimensions.get('screen');
  const designHeight = (screenHeight * designWidth) / layoutWidth;
  const pixelWidth = PixelRatio.getPixelSizeForLayoutSize(layoutWidth);
  const pixelHeight = PixelRatio.getPixelSizeForLayoutSize(screenHeight);
  return StyleSheet.create({
    container: {
      backgroundColor: 'blue',
      width: designWidth,
      height: designHeight,
      transform: [
        {
          translateX: -designWidth * 0.5,
        },
        {
          translateY: -designHeight * 0.5,
        },
        {
          scale: pixelWidth / (ratio * designWidth),
        },
        {
          translateX: designWidth * 0.5,
        },
        {
          translateY: designHeight * 0.5,
        },
      ],
    },
  });
}

结构&样式

原生组件:

https://reactnative.dev/docs/components-and-apis
  • 只有特定的组件才有交互样式与事件,如ButtonTouchableHighlightTouchableOpacity,其余组件无法绑定onPress事件

    • 一般不选用Button,而使用自定义Button组件,因为原生Button样式不好调节
  • 不支持svg,需要借助第三方库,e.g.react-native-svgreact-native-svg-transformer
  • 结构搭建:将HTML的标签用法完全忘记,重新根据文档学习使用方法。

样式:

https://reactnative.dev/docs/image-style-props
  • 只能使用组件规范的样式,否则不起作用
  • lineHeight不能使文字居中,请使用justifyContent
  • 针对Text组件,需要单独定义相关样式,不会继承父级非Text组件Text样式,e.g.不会从父级继承color
  • 不支持渐变色、投影等效果,需要借助第三方库,e.g.react-native-linear-gradientreact-native-shadow-2

Notes:第三方库的安装,需要重新启动项目,否则,会报模块找不到。

嵌套ScrollView

  • flatlist嵌套在scrollView无法滚动,父子级都需要设置nestedScrollEnabled属性
  • scrollView必须设定一个高度,否则会使用默认高度,并非由内容撑开。

    • 注意stylecontentContainerStyle的区别
    • scrollView高度设定:专设View组件包裹,scrollView高度设置为flex:1

代码调试

react-native-debugger的使用

  • 连接react-native-debugger,需要应用程序开启Debug模式

    • 真机:摇一摇手机,出现操作面板,选择Debug

开启Debug模式

  • 模拟器:模拟器聚焦后,使用Ctrl + m打开操作面板

    • 双击rreload
  • 使用react-native-debugger时,如果发出网络请求,可能会在Network面板发现没有request发起,请求(成功/失败)回调没有执行。

    • react-native-debugger非Chrome控制台面板中右键,开启Enable Network Inspect

      开启网络审查

  • 使用react-native-debugger时,如果发现Components面板始终空白,使用npx react-native start --reset-cache清除缓存启动项目
  • react-native-debugger的使用,需要同版本react-devtoolsreact-devtools-core作为开发依赖。

当前实例获取

类同Chrome开发者工具,在RN任意调试工具的Console面板:

  • 通过$reactNative等同于import $reactNative from "react-native",可以使用react-native库中的方法。

image.png

  • 通过$r可以获取当前选中节点实例

image.png

多种调试工具切换

在模拟器中开启debug后,调试每次都会自动默认打开debugger-ui页面,如何关闭:

  1. 在启动项目npm run start的命令行中使用shift + d切换到模拟器并打开调试面板,此时点击debug面板选项不再自动调起debugger-ui页面。
  2. 通过取消选中小勾号来解决它Maintain Priority
  3. 避免直接在模拟器中通过Ctrl + M唤起面板,选择debug,这样会默认调起debugger-ui

UI审查

开启Android Studio:

  • 通过右下角的Layout Inspector,选择模拟器或真机。

UI审查:选择设备

  • 点击结构,可以查看组件属性
  • 可以通过该面板的右上角设置,切换单位。

UI审查

  • 可以通过给组件赋值testIDaria-label,进行组件识别。

日志查看

开启Android Studio:

  • 通过下方的logcat可以设备日志,可用于分析应用程序崩溃原因。
  • 可进行日志筛选,e.g.package:com.awesomeproject level:error

日志审查

#筛选条件列表#

外部字体

https://www.jianshu.com/p/6000eb97d53b

  • 不像H5一样,针对不同的font-weight设置@`font-face`
@font-face { font-family: "Sans"; font-weight: 100; src: url("../assets/fonts/Thin.otf");}
@font-face { font-family: "Sans"; font-weight: 200; src: url("../assets/fonts/Light.otf");}
@font-face { font-family: "Sans"; font-weight: 400; src: url("../assets/fonts/Normal.otf");}

只能通过字体文件名设置不同的fontFamily

// font-weight:100
f100: {
  fontFamily: 'Thin' // 字体文件名
}
// font-weight:200
f200: {
  fontFamily: 'Light' // 字体文件名
}
// font-weight:300
f300: {
  fontFamily: 'Normal' // 字体文件名
}

动画

  1. toValue只能定义整型,如果动画需要其他类型的值,需要使用interpolate
  2. 无法对svg中的circle使用transform定义动画,只能通过View或其他元素包裹,实现旋转动画
  3. 如果页面内有大量运算,==Animated会被阻塞,因为Animated是在JS线程运行,而非UI线程(https://stackoverflow.com/questions/56980044/how-to-prevent-setinterval-in-react-native-from-blocking-running-animation)—— 使用react-native-reanimated库可以解决该问题,react-native-reanimated提供的hook在UI线程处理动画
  const animationRef = useRef({
    strokeDashoffset: new Animated.Value(240),
    rotateZ: new Animated.Value(1)
  })
  useEffect(() => {
    const animationDef = () => {
      Animated.loop(
        Animated.sequence([
          Animated.parallel([
            Animated.timing(
              animationRef.current.strokeDashoffset,
              {
                toValue: 240,
                useNativeDriver: true,
                duration: 900
              }
            ),
            Animated.timing(
              animationRef.current.rotateZ,
              {
                toValue: 2,
                useNativeDriver: true,
                duration: 900
              }
            ),
          ]),
          Animated.parallel([
            Animated.timing(
              animationRef.current.strokeDashoffset,
              {
                toValue: 200,
                useNativeDriver: true,
                duration: 100
              }
            ),
            Animated.timing(
              animationRef.current.rotateZ,
              {
                toValue: 3,
                useNativeDriver: true,
                duration: 100
              }
            ),
          ]),
          Animated.parallel([
            Animated.timing(
              animationRef.current.strokeDashoffset,
              {
                toValue: 200,
                useNativeDriver: true,
                duration: 900
              }
            ),
            Animated.timing(
              animationRef.current.rotateZ,
              {
                toValue: 4,
                useNativeDriver: true,
                duration: 900
              }
            ),
          ]),
          Animated.parallel([
            Animated.timing(
              animationRef.current.strokeDashoffset,
              {
                toValue: 240,
                useNativeDriver: true,
                duration: 100
              }
            ),
            Animated.timing(
              animationRef.current.rotateZ,
              {
                toValue: 5,
                useNativeDriver: true,
                duration: 100
              }
            ),
          ])
        ])
      ).start()
    }
    animationDef()
  }, [animationRef])
  const rotateZ = animationRef.current.rotateZ.interpolate({
    inputRange: [1, 2, 3, 4, 5],
    outputRange: ['-100deg', '-424deg', '-500deg', '-784deg', '-820deg'],
  })

常用第三方库

项目需要重启才可以,否则会报模块找不到
  • 渐变色

    • import LinearGradient from 'react-native-linear-gradient';
  • 支持svg

    • "react-native-svg": "^13.9.0",
    • "react-native-svg-transformer": "^1.0.0",
  • 全局样式变量

"react-native-extended-stylesheet": "^0.12.0",

  • 毛玻璃

@react-native-community/blur

<BlurView .../>标签本身不能包含子元素:

  • 若添加子元素,子元素不模糊,但会堆叠在一起
  • <BlurView .../>标签产生模糊的范围是根据元素顺序来的 —— 该标签之前的所有节点是非模糊的,之后的所有节点都将被视为它的子级进行模糊。
  • 投影

yarn add react-native-shadow-2 —— 包裹唯一子节点不能使用margin,会导致Shadow不对齐,如果需要设置margin,使用View包裹Shadow元素

第三方库使用带来的问题

UseEffect vs. UseFocusEffect

使用react-native-navigation在同一路由栈中切换页面,页面没有销毁,所以,useEffect的清除回调不会被触发,需要考虑使用[useFocusEffect](https://reactnavigation.org/docs/bottom-tab-navigator)替代useEffect

环境变量设置

使用react-native-dotenv或其他第三方库设置环境变量,会出现报错:Property left of AssignmentExpression expected node to be of a type ["LVal"] but instead got "StringLiteral"

需要修改rn-nodeify自动生成的shim.js文件,给process.env换一种赋值方式:

const isDev = typeof __DEV__ === 'boolean' && __DEV__
const env = process.env || {}
env['NODE_ENV'] = isDev ? 'development' : 'production'
process.env = env
if (typeof localStorage !== 'undefined') {
  localStorage.debug = isDev ? '*' : ''
}

报错:ReferenceError: Property 'TextEncoder' doesn't exist, js engine: hermes

https://github.com/hapijs/joi/issues/2141

android.support.annotation包不存在的错误

jetifier可一键解决
  1. 错误发生在你将你的Android应用程序迁移到使用androidx 库时
  2. import 当你使用androidx ,你需要更新你的Android源代码,用androidx.annotation 包替换所有android.support.annotation 包的语句。

https://www.qiniu.com/qfans/qnso-40380519【推荐】

react-native-webview错误: 无法将类 FileProvider中的构造器 FileProvider应用到给定类型;

https://github.com/react-native-webview/react-native-webview/issues/2978
将版本由12.2降级到12.1即可

react-native-webview Android postMessage 不工作

在安卓环境下,通过postmessage通信,需要使用document监听,ios下通过window监听。
https://github.com/react-native-webview/react-native-webview/issues/356

疑难杂症

  • 嵌套路由中,onPress不起作用
    TouchableWithoutFeedback在嵌套路由下不工作,可用<TouchableOpacity activeOpacity={1} />替代。
  • 如何同时运行模拟器和真机how to run react-native app on simulator and real device at the same time

https://stackoverflow.com/questions/51336123/how-can-i-run-two-react-native-applications

  • 调试工具flipperReact Devtools面板始终无法连接到应用程序

进行一下端口映射即可:adb reverse tcp:8097 tcp:8097

  • Error: Unable to resolve module 'stream'
    针对于rn-nodeify兼容的库,如果无法解析的话,需要在metro.config.js中进行模块配置extraNodeModules

    resolver: {
      ...resolver,
      assetExts: resolver.assetExts.filter(ext => ext !== 'svg'),
      sourceExts: [...resolver.sourceExts, 'svg'],
      extraNodeModules: {
        stream: require.resolve('readable-stream'),
      },
    },

米花儿团儿
1.3k 声望75 粉丝