React Native开发初探

环境安装

参见官网:

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;

结构&样式

原生组件:

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

  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 —— 只能有一个子节点,多个子节点需要包裹

  • 投影

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 Android postMessage 不工作

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

疑难杂症

  • 如何同时运行模拟器和真机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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容