2023-09-09
ReactNative
00
请注意,本文编写于 449 天前,最后修改于 306 天前,其中某些信息可能已经过时。

目录

样式
怎样知道哪些组件有哪些样式呢?
Flex布局
与Web端样式差异
组件
Image组件
点按组件
点按事件
TextInput
对比RN组件与Web标签
列表组件
ScrollView
FlatList / SectionList
ReyclerListView
FlashList
特定平台代码
事件&交互
DOM操作
JS运行环境

本文从一个Web开发者的角度简单梳理RN开发的基础知识,对比web开发可能让web同学更快的上手RN开发。

样式

  1. 大部分RN提供的框架组件都有style属性
  • 没有className, 使用styleSheet.create({})style={{}},推荐前者性能更好
  • 属性必须小驼峰
  1. React 组件在概念上被设计为强隔离性的 样式不被继承
    • 也有特例 仅限于文本标签的子树 有一小部分样式可以继承(如fontWeight

怎样知道哪些组件有哪些样式呢?

  1. 一方面 通过TypeScript声明文件,编辑器会提醒你某个组件有哪些样式
  2. 另一方面 RN的组件样式是有规则的,先把高频样式用会,低频样式查查文档

组件样式是有继承关系,分三层

image.png

  • 通用样式 盒模型相关属性+ flex布局 + transform + shadow
  • View 组件样式 继承通用样式, 自身有 backgroundColorborder相关、opacity
  • 大部分组件(如TextImage)都继承了View组件样式。因此View组件的私有样式其实也可以算作通用样式
  • Text/Image组件的私有样式,就不能相互通用了。

Flex布局

优点: 跨平台、高性能、易上手

  1. Flex是跨平台的 Web/Android/IOS平台都在用, RN的布局引擎YogaAndroid/iOS通用的。
    • 组件上写的Flex布局代码,最终都会被Yoga引擎计算为精准的坐标系,然后渲染到屏幕上
  2. 与Web端的差异
  • flexDirection默认值变为column
  • flex没有了auto none快捷值

与Web端样式差异

RN的样式远没有web强大,不过RN组件非常丰富,弥补了这方面的缺点。

  • 支持盒模型属性 但是display只有noneflex
  • 支持postion相关
    • 不支持sticky 需要借助 <scrollview>stickyHeaderIndices属性 来实现
    • 不支持fixed
  • 背景图片请使用 ImageBackground
  • 选择器没有了,伪类选择器都没有了
  • 基本没有复合样式 如 backgroundbordermargin
  • 单位
    • RN中只有百分比或不使用单位(逻辑像素)
    • 使用逻辑像素会带来的问题,相同物理屏幕尺寸的手机分辨高的手机字体小
    • 如果想实现rem的效果,需提供一个工具方法
  • transfrom 写法有变化 transform: [{translateX:100}]
  • 首行锁进用字符实现,没有text-indent

推荐使用StyleSheet

  • 元素结构和样式分离,可维护性更好;
  • 样式对象可以复用,能减少重复代码;
  • 样式对象只创建一次,也减少性能的损耗。
ts
// width: 750rpx = 100% export const radio = (function(logicWidth: number) { return logicWidth / 750 })(logicWidth);
  • 刘海处理
    1. 使用<SafeAreaView>包裹页面,使用 <StatusBar>设置样式
    2. 获取状态栏高度 android StatusBar.currentHeight, ios 设置默认值 20
    3. npm包 react-native-device-info

组件

因为RN的样式比较少,但是RN提供了大量的组件弥补这方面的缺陷

Image组件

Image组件支持4种加载图片的方法

  1. 静态图片资源
  2. 网络图片
  3. 宿主应用图片
  4. Base64图片

静态图片资源

  • 通过require的方式引入,require的入参,必须是字面常量

静态图片资源的加载原理

  1. 编译 图片内容不混入代码,代码中只有图片的引用,编译后能获得图片的宽高格式等信息
  2. 构建 编译后的Bundle和静态图片资源,会在构建时编译到APP中。
  3. 运行 由于前面编译时已经获取到图片的基本信息,所以渲染时不会出现页面抖动。

网络图片

  • RN对网络图片做了缓存和预加载机制
  • Android 用的是 Fresco 第三方图片加载组件的缓存机制
  • iOS 用的是 NSURLCache 系统提供的缓存机制。它遵循的是 HTTP 的 Cache-Control 缓存策略
  • Android 和 iOS 的缓存设置方式和实现原理虽然有所不同,但整体上采用了内存和磁盘的综合缓存机制
  • React Native 也提供了非常方便的图片预加载接口 Image.prefetch(url)

宿主应用图片

  • 宿主应用图片指的是 React Native 使用 Android/iOS 宿主应用的图片进行加载的方式.
html
// Android drawable 文件目录 // iOS asset 文件目录 <Image source={{ uri: 'app_icon' }} /> // Android asset 文件目录 <Image source={{ uri: 'asset:/app_icon.png' }} />

Base64 图片

  • 由于 Base64 图片是嵌套在 Bundle 文件中的,所以 Base64 图片的优点是无需额外的网络请求展示快,
  • 缺点是它会增大 Bundle 的体积。
  • 只适合用在体积小的图片或关键的图片上

image.png

点按组件

RN的点按组件经历了三代

  1. Touchable组件
  2. Button组件
  3. Pressable组件

第一代 Touchable 组件

  • 思路: 提供多种原生平台的反馈风格给开发者自己选择。框架提供了 1 个基类和 4 个扩展类
  • TouchableWithoutFeedback:用于响应用户的点按操作,但不给出任何点按反馈效果。反馈效果由 4 个扩展类实现;
  • TouchableNativeFeedback:给出当前原生平台的点按反馈效果,在 Android 中是涟漪(ripple)效果,就是从点击处散水波纹的效果
  • TouchableOpacity:短暂地改变组件的透明度;
  • TouchableHighlight:短暂地加深组件的背景色;
  • TouchableBounce:有 bounce 回弹动画的响应效果。

第二代 Button

  • Touchable 组件进行封装,别让开发者纠结选啥组件了
  • 在 Android 上是 TouchableNativeFeedback 组件,在 iOS 上是 TouchableOpacity 组件

第三代 Pressable 组件

  • 它是一个全新重构的点按组件,它的反馈效果可由开发者自行配置。
  • 它支持固定样式和动态样式
jsx
// 固定的基础样式 const baseStyle = { width: 50, height: 50, backgroundColor: 'red'} <Pressable onPress={handlePress} style={baseStyle} > <Text>按钮</Text> </Pressable> // 固定的基础样式 const baseStyle = { width: 50, height: 50, backgroundColor: 'red'} <Pressable onPress={handlePress} style={({ pressed }) => [ /* 动态样式 */ baseStyle, { opacity: pressed ? 0.5 : 1} ]} > <Text>按钮</Text> </Pressable>

点按事件

  • onPressIn: 开始响应事件,用户手指接触屏幕,且该手势被当前组件锁定后触发;
  • onPressOut:结束响应事件,用户手指离开屏幕时触发。
  • onPress: 在onPressOut中,如果距离onPressIn < 500ms则触发 onPress
  • 如果 >= 500ms 则属于长按 onLongPress

ref 的值不会因为组件刷新而重新声明,它是专门用来存储组件级别的信息的。

TextInput

TextInput是输入框组件,相对于web,它的能力更强,可以定制键盘

一些与键盘优化相关的属性

  • enablesReturnKeyAutomatically iOS 独有的属性,默认是false
  • returnKeyType
  • 跳转下一个输入框 iOS textContentType android autoComplete
  • keyboardType可以控制键盘类型

对比RN组件与Web标签

  • 文本必须使用文本标签<Text>包裹;
  • image网络图片必须给定宽高;
    • src --> source 更灵活
    • android下支持gif或webp格式的图片,要修改gradle配置
    • 在主线程外解码图片(这是web端页面掉帧的一大因素)
  • 滚动必须用<ScrollView> 没有overflow:scroll
    • 还集成了 触摸锁定响应者系统
    • 可以模拟web开发中的UI组件 swipe
    • 滚动到指定位置 通过ref拿到原生组件,调用方法scrollTo(x, y, animated)
  • 长列表 FlatList / SectionList 后者支持分组,比如城市列表、联系人列表

列表组件

ScrollView

  • RN中没有overflow:scroll 想要滚动就需要 <ScrollView>
  • 可以模拟web开发中的UI组件 swipe
  • 滚动到指定位置 通过ref拿到原生组件,调用方法scrollTo(x, y, animated)

FlatList / SectionList

  • <ScrollView> 会全部渲染元素,对于长列表就有性能问题
  • <FlatList><SectionList>会渲染可视区及附近的元素,滚动时销毁远离可视区的元素并创建即将进入可视区的元素。
  • 区别是后者支持分组(比如城市列表、联系人列表),为了简化理解,后面只针对<FlatList>做说明
  • <FlatList> 的底层实现是 <VirtualizedList><VirtualizedList> 的底层又是<ScrollView> 所以<FlatList> 可以使用<ScrollView><VirtualizedList>的大部分属性
  • 建议尽量填一下getItemLayout属性,如果不把列表项高度告知<FlatList>,<FlatList>会通过onLayout的布局回调动态计算,用户是会感觉到卡顿的

ReyclerListView

官方的<FlatList>方案性能确实有的大幅提升,但性能还不够理想,社区提出了<ReyclerListView>方案, 它是改改在用(变更离开可视区的元素为即将进入可视区的元素)所以它的性能更好。

<ReyclerListView> 有一定学习成本,先来看一下使用

它有三个必填属性

  • 列表数据:dataProvider
  • 列表项的布局方法:layoutProvider
  • 列表项的渲染函数:rowRenderer

示例

jsx
function MyList() { let dp = new DataProvider((r1, r2) => { return r1 !== r2; }); useEffect(() => { fetch('XXX') .then(res => res.json()) .then(arr => { dp = dp.cloneWithRows(arr); }) }) const _layoutProvider = new LayoutProvider( index => { if (index % 3 === 0) { return ViewTypes.FULL; } else { return ViewTypes.HALF_RIGHT; } }, (type, dimension) => { switch (type) { case ViewTypes.HALF_RIGHT: dimension.width = width / 2; dimension.height = 160; break; case ViewTypes.FULL: dimension.width = width; dimension.height = 140; break; } }, ); const _rowRenderer(type, data) { //You can return any view here, CellContainer has no special significance switch (type) { case ViewTypes.HALF_RIGHT: return ( <CellContainer style={styles.containerGridRight}> <Text>Data: {data}</Text> </CellContainer> ); case ViewTypes.FULL: return ( <CellContainer style={styles.container}> <Text>Data: {data}</Text> </CellContainer> ); default: return null; } } return ( <RecyclerListView onEndReached={fetchNextPage} dataProvider={dp} layoutProvider={layoutProvider} rowRenderer={RowRenderer} /> ) }

分析

  • 一个列表中可能存在多种类型组件 所以设计了两个参数
  • layoutProvider获取元素尺寸和类型,
  • rowRenderer 根据类型渲染元素
  • dataProvider 除了数据还要提供比对方面,提高性能

image.png

image.png

因为RecyclerListView 用的是 position:absolute 的绝对定位布局。所以要指定组件的宽高

如果宽高大致确定吗也是可以用的,可以开启forceNonDeterministicRendering 小幅修正布局位置

FlashList

<RecyclerListView>貌似很完美,但不支持双列

解决方案1: 需要修改<RecyclerListView> 源码,如这个案例

解决方案2 就是使用<FlashList> 它是对<RecyclerListView>的二次封装

官网

最后建议,如果有长列表瀑布流需求, 一开始就用<RecyclerListView><FlashList> , 不然随着业务迭代改造成本很大。

特定平台代码

  • 样式使用Platform.select({ios, android})
  • 给文件增加特定平台的扩展名
  • 条件代码 根据平台Platform.OS执行不同逻辑

事件&交互

web的e.target.value => RNe.nativeEvent.text

导航 react-navigation / react-native-navigation / 使用原生导航管理RN视图

动画 使用Animated

  • 并行/串行/延迟/集合运算 等等可以随意组合, 比web端的css动画更灵活
  • 跟踪手势, 比如配合ScrollView实现WebSwipe组件的脚标动画 Animated.event
  • 使用原生动画驱动, 避免通信损耗

DOM操作

  • 通过ref获取到原生组件 调用 setNativeProps 方法
  • 埋点实现 曝光 使用原生组件方法测量位置(measure measureLayout)+ 监听滚动 可实现类似Web端的IntersectionObserver的功能
    • 并非所有标签都能使用measure方法
  • layout 方法在渲染前可以拿到文本元素尺寸(web需要渲染后才知道)

JS运行环境

  • 浏览器下的JS 包含浏览器APIBOMECMA规范两部份组成 由JS引擎(chrome V8 / safari JSCore )提供。
  • RN >=7.0使用 Hermes引擎 <7.0使用 JSCore
  • 网络方面:实现了与web端几乎一致的 XMLHttpRequest / fetch / WebSocket 没有跨域问题
  • 内置了Babel 做了语法转换 内置了APIPolyfills
  • require / console
  • 定时器 setTimeoutsetIntervalrequestAnimationFramesetImmediate
  • __DEV__

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!