本文主要从前端的角度介绍 ReactNative 的用法,主要介绍 ReactNative 的基本用法(布局,样式,动画,手势,特殊API) 帮助大家快速掌握 ReactNaitve。而对于 ReactNative 封装的各种组件就不会过多深入了。(阅读建议:掌握 react 用法,熟悉前端)
视图层
ReactNative 本质上开发和前端没有任何区别,仅仅是用了 ReactNative 专用的标签(组件)和一套精简过的 css 来完成的绘制。常用的类比如下:
<div>
-><View>
<span>
-><Text>
<input>, <textarea>
-><TextInput>
<img>
-><Image>
- 其他封装的组件
而,CSS 不再以 .css
文件的形式存在,而是变成一个通过 StyleSheet
创建的对象。引用的时候不再有 className='xxx'
的写法,而统一为:style={xxx}
举个例子:
|
|
效果如下:
似不似很简单。
Style 样式
ReactNative 使用的是阉割版的 css,需要用 StyleSheet.create 来创建样式。没有样式文件一说,统统 js。大部分,常用的 css 样式你都可以在 ReactNative 中找到(注意写成驼峰形式)。这里列举几个和 css 表现不太一致的样式:
与 css 对比
差异如下:
- fontWeight:去掉了
bolder
属性; - textAlignVertical:center 取代了 middle,并阉割了 baseline, sub 等值
- textDecorationLine:阉割了 overline, blink 取值
- textDecorationStyle: 阉割了 wavy 取值
- position:阉割了 static, fixed 取值 (注意:在 ReactNative 里面,absolute 定位不再相对于父辈非 static 的元素定位,变简单了,直接相对于父元素定位。)
- magrin: 只能定义一个参数,用以表示上、右、下、左4个方位的外补白
- marginHorizontal:CSS中没有对应的属性,相当于同时设置marginRight和marginLeft
- marginVertical:CSS中没有对应的属性,相当于同时设置marginTop和marginBottom
- padding 同 margin
- borderStyle:阉割了 none, hidden, double, groove, ridge, inset, outset 取值,且无方向分拆属性
- shadowColor:对应 CSS 中的 box-shadow 属性中的颜色定义
- shadowOffset:取值 {width: number, height: number} ,对应 CSS 中的 box-shadow 属性中的阴影偏移定义
- shadowRadius:在 CSS 中,阴影的圆角大小取决于元素的圆角定义,不需要额外定义
- shadowOpacity:对应 CSS 中的 box-shadow 属性中的阴影透明度定义
- transform: 写法变成数组,例如平移x,y [{translateX: number}, {translateY: number}]
- Flex 系列后面单独说
- overflow:阉割了 scroll, auto 取值
- elevation:
Android
特有,css 无 - resizeMode:CSS 中没有对应的属性,可以参考 background-size 属性
- tintColor:
iOS
特有 CSS中没有对应的属性,iOS 图像上特殊的色彩,改变不透明像素的颜色 - 其他各段特有属性,不再一一列举
取值:
在 ReactNative 中,没有 px
单位,统一为数字 width: 10
,长度单位为 dp。
这样,就省略了有前端在移动端适配这一环节,当然如果你想用 fixedWidth 方式适配也不是不行,ReactNative 提供了获取相应的计算值方案,这里不深入讲解,有兴趣的可参考下面资料:
同时,width,height 支持百分比单位 (低版本,并不支持)。
还有一点要注意的就是,ReactNative 并不支持样式继承。
对于样式覆盖,我们可以给 style=[{}, {}] 一个数组,倒是可以。
flex 布局
在 ReactNative 不再有所谓的 display
属性。除了 position
,布局上采用 flexbox
布局。熟悉前端的大家一定不陌生。但是在 ReactNative 中,默认的 flexDirection
方向为 column
纵向。且相应的属性仍有不少删减:
常用组件差别
我们来看下,标签(组件)的差别。
在 ReactNative 中,文本必须被包裹在 <Text>
中(而在前端这并不是强制的,但是我推荐文本至少要包裹在 span
之类的内联元素中,而不是暴露在 div
中,当初了拖拽选蓝的时候,你会发现这个规则非常的有帮助)
前端常用的 input, textarea
处理输入,到了 ReactNative 统一为 TextInput
组件。
TextInput
默认为单行输入框,这就和 input
是一样的了, 大部分属性和前端一致。
列举几个不一样的常用属性:
- editable:如果为false,文本框是不可编辑的。默认值为true。
- keyboardType:决定弹出何种软键盘类型,譬如numeric(纯数字键盘)
- placeholderTextColor:占位字符串(placeholder)颜色
- secureTextEntry:密码输入框效果
- selection:取值 {start: number,end: number},设置选中文字的范围(指定首尾的索引值)。如果首尾为同一索引位置,则相当于指定光标的位置
- selectTextOnFocus:如果为true,当获得焦点的时候,所有的文字都会被选中
- autoFocus:如果为true,在componentDidMount后会获得焦点。默认值为false
可以看出,ReactNative 上文本输入框能力的强大,许多焦点设置都有对应的属性来实现,同时还额外提供了输入修正等能力。有兴趣的可以看文档。
那么,如果我想要多行文本输入 textarea
呢?
我们可以通过设置:multiline = true
来实现,同时我们还可以指定输入框最大行数:numberOfLines
。另外对于多行文本输入,还提供了根据输入内容,自动转换成可点击URL的设定:dataDetectorTypes
。
有了这些,我们还需要获得和设置文本框的值。设置我们可以通过 value
属性。但是获取就没有那么容易了,在 ReactNative 中,TextInput
最终会被转换成 Native 原生节点。并不是DOM节点,所以我们无法通过 ref
手段来像前端那样直接获取节点属性。所以我们必须采用受控组件的方式,来通过 state
来获取值。同样我们也可以监听 onChangeText
事件,来实时得到文本变化的内容 (这也是唯一的获取手段)。
TextInput 支持事件主要有:
- onChange:当文本框内容变化时调用此回调函数。回调参数为{ nativeEvent: { eventCount, target, text} }
- onChangeText:onChange 的简化版,只有 text
- onEndEditing:当文本输入结束后调用此回调函数
- onKeyPress:当一个键被按下的时候调用此回调。传递给回调函数的参数为{ nativeEvent: { key: keyValue } },其中keyValue即为被按下的键。会在onChange之前调用。注意:在Android上只有软键盘会触发此事件,物理键盘不会触发。
- onSubmitEditing:此回调函数当软键盘的确定/提交按钮被按下的时候调用此函数,所传参数为{nativeEvent: {text, eventCount, target}}。如果multiline={true},此属性不可用。
同样还有一些和焦点,选蓝有关的事件,也不一一介绍了。总之很全面,嘿嘿。
在前端,我们还会关注一个问题就是,键盘弹起遮挡当前内容。这个可以通过使用 KeyboardAvoidingView
组件来解决。
当然了,熟悉 Android 的,我们也可以在 native 端设置 android:windowSoftInputMode="adjustPan"
来解决哦。
键盘遮挡问题参考:
Image
基本格式:
|
|
和前端不同,在 ReactNative 中,如果使用静态图片地址,地址必须是静态字符串,不能包含变量。如下:
|
|
这主要是因为,require 时在编译时期执行,而非运行使其执行。
如果加载网络图片,格式如下:
|
|
注意,这里必须给图片尺寸(静态资源不需要)。同样 uri 后面可以跟,base64 的资源。
ReactNative 还提供了对网络图片,更加精细的加载请求(设置HTTP头部等),不过多介绍。
另外,对于混合资源等细节,请参阅文档。
思考一个问题,如果我们想加载一个背景图片呢?ReactNative 提供了 ImageBackground
组件来解决这一问题。
|
|
想了想,常用的视图有关就这么多了呢。剩下那些扩展组件,需要使用哪儿个查哪儿个就好了。
改变视图
有了布局等内容,我们现在思考如何改变某一部分样式。在前端改变 css 或者 style 即可。而在 ReactNative 中,第一种修改方式就是讲组件样式作为 state
来处理。
|
|
但是这里有个问题,我们知道触发 setState
会导致树 re-render。频繁的改变 setState
就会引起页面卡顿。这里我们就需要一种像直接操作 DOM 的方案,来改变一个组件的样式。
setNativeProp
在 React Native 中,setNativeProps就是等价于直接操作 DOM 节点的方法。setNativeProps 一般来说只是用来创建连续的动画,同时避免渲染组件结构和同步太多视图变化所带来的大量开销。
通过 ref 获得组件的引用,然后就可以直接使用了。距离如下:
|
|
这里要注意,setNativeProps 只能直接作用在 RN 组件上,也就是说不能作用在自定义组件上面(这时候,请考虑 React.forwardRef 来转发 ref)。
setNativeProps 还可以用来直接改变 TextInput
的值:
|
|
获取节点样式信息
在前端有时候我们需要获取元素的宽高,在屏幕中的位置信息等。这个要怎么计算呢?
屏幕宽高 Dimensions
获取屏幕宽高,React Native 提供了直接的获取方法:
|
|
同样,如果我们想监听屏幕变化,Dimensions.addEventListener('change', callback)
即可。
组件位置和大小
1.onLayout 事件属性
|
|
当组件重新渲染时,该方法就能重新获取到元素的宽高和位置信息,但是有时组件并没有重新render那么就获取不到正确的值,例如页面滚动,但是state没有发生变化,组件也就没有重新渲染。
2.元素自带measure方法
|
|
然后需要注意的是需要在componentDidMount方法里面添加一个定时器,定时器里面再进行测量,否则拿到的数据都为0.
|
|
注意不能用在自定义组件上面哦,但是下面这个方法可以用在自定义组件上面。
3.使用UIManager measure方法
首先引入:
|
|
再加入:
|
|
最后测量一下即可:
|
|
同样的,这个也不能在 componentDidMount
后立即计算。
Animated 动画
在动画方面,ReactNative 使用 Animated 来做动画。Animated仅封装了四个可以动画化的组件:View、Text、Image和ScrollView。(Animated.createAnimatedComponent() 可以封装自定义组件),举个例子:
|
|
Animated 主要是以声明的形式来定义动画的输入与输出 (Animated.Value 变量),在其中建立一个可配置的变化函数,然后使用简单的 start/stop
方法来控制动画按顺序执行。
Animated 提供的动画类型:
- Animated.decay()以指定的初始速度开始变化,然后变化速度越来越慢直至停下。
- Animated.spring()提供了一个简单的弹簧物理模型.
- Animated.timing()使用easing 函数让数值随时间动起来。
同时还提供了组合动画的方式:
- Animated.delay(time) 在给定延迟后开始动画。
- Animated.parallel(animations, config?) 同时启动多个动画。
- Animated.sequence(animations) 按顺序启动动画,等待每一个动画完成后再开始下一个动画。
- Animated.stagger(time, animations) 按照给定的延时间隔,顺序并行的启动动画。
- Animated.loop(animation, config?) 无限循环一个指定的动画
例如:
|
|
同事,对于 Aniamted.Value 还提供了加减乘除以及取余运算,插值器等,需要的自己看吧。
动画有点卡,试试 useNativeDriver
我在模拟器上,改变动画确实卡的要命。这个 RN 同样提供了一个方案:启动原生动化。在动画中启用原生驱动非常简单。只需在开始动画之前,在动画配置中加入一行useNativeDriver: true,如下所示:
|
|
Animated的 API 是可序列化的(即可转化为字符串表达以便通信或存储)。通过启用原生驱动,我们在启动动画前就把其所有配置信息都发送到原生端,利用原生代码在 UI 线程执行动画,而不用每一帧都在两端间来回沟通。如此一来,动画一开始就完全脱离了 JS 线程,因此此时即便 JS 线程被卡住,也不会影响到动画了。
然而,并不是所有的属性都可以启用原生动画,只有 non-layout
类型的属性才可以。那这就很尴尬了,我想要实现一个折叠面板,也卡的我难受。咋弄呢?
LayoutAnimation
它常用来更新 flexbox 布局,因为它可以无需测量或者计算特定属性就能直接产生动画。(感觉和 Android 的 <LayoutAnimation>
有关呢)。使用也十分的简单:
|
|
只需要在改动之前,调用一下 LayoutAnimation 即可。同样,LayoutAnimation 也提供了其他动画方式(easeInEaseOut, linear, spring),和动画的基础配置。
这里有一点要注意的是,如果在 Android 需要加入如下内容:
|
|
除了这些,ReactNative 还提供了滚动,滑动等事件值映射到动画值上面。Animated.Event
这样就可以将动画和手势结合起来。
总结
其实 RN 的动画效果并不是很好,我们看见只有动画脱离了 JS (useNativeDriver) 才好点,这其中和 js 到 native 的频繁通信有很大的关系。脱离通信就好多了。
联系到,如果我们想做一些,复杂的交互动画效果,同样会产生问题。频繁的通信,序列化与反序列化。
这里推荐一篇文章,其解决思路可以参考: