RN作为H5的下一代跨平台方案,风头正劲。用UC震惊部的话来说:没有研究过RN,都不好意思说自己做过客户端。现在还不了解学习React-Native,你就要快老了!
好了,抛开标题的噱头。写下这篇文章的主要目的,还是作为自己的一个学习记录与汇总,同时对RN的踩坑与学习的经验进行分享,总结ES 6的常用语法,页面的生命周期与相关的一些学习资料。让其他客户端开发者能快速的对React-Native进行接入与学习。文章包含以下内容:
- 为什么使用RN
- RN的简单用例
- ES 6语法
- RN的生命周期
- 坑点汇总
为什么选择RN
其实在一开始我也曾思考过这个问题。既然现在已经有成熟的H5方案了,为什么还要选择RN?
最根源的想法还是来自于H5的痛点。页面的交互性差,数据缓存,回传回调困难。在使用过程中也难以满足用户所需的多元化功能与快速响应的速度。面对这种情况,以往我们只能委屈求全,摘掉一些耗时长,操作复杂的功能。亦或是舍弃使用H5,最后改由客户端实现,已达到最好的用户体验。其次,也是作为一个技术的自我学习心态的驱使。React-Native作为编写Hybrid APP的新思路,不管项目需要接入与否,我们都有必要去学习与了解其中的原理和使用。即作为一个知识储备,也能更好的学习里面的新思想。作为一个客户端开发者,Redux的设计给我的思考与收获也是很大的。
总结下来,RN具有以下优势:
- 运行性能更好,调用原生组件。
- 采用了css,flexbox的布局模式,方便开发。
- 比起 Hybird 扩展性更强,自由度更高,交互体验更好。
- 支持应用热更新与远程调试。
- 展示复杂的高阶动画,能通过原生平台编写,更少卡顿。
- 大部分代码都可跨平台,易于维护。
- 和weex相比,社区环境更优秀,也有更多成熟的应用使用RN方案。
RN的简单用例
以下是RN最简单的样例,分为三部分组成:
1.对象、组件的导入
2.样式的声明
3.组件的使用与样式引入
4.注册入口类。将与Native中代码的设置对应(这里为'rn'
)。
// 1
import React, { Component } from 'react';
import {
AppRegistry,
Text,
Button,
View,
TouchableHighlight,
Image,
StyleSheet,
} from 'react-native';
// 2
const style = StyleSheet.create({
centerSelf:{
alignSelf: 'center',
},
centerJustify:{
justifyContent: 'center',
},
centerItems:{
alignItems: 'center',
},
red:{
backgroundColor: 'red',
},
});
// 3
class HelloWorldApp extends Component {
componentDidMount(){ // 页面加载后
// do something...
}
render() {
return (
Hello , !
);
}
}
// 4 注意,这里用引号括起来的'rn'必须和你init创建的项目名一致
AppRegistry.registerComponent('rn', () => HelloWorldApp);
可以看出其实RN的组件与样式的使用与H5的标签的使用非常相似。只要在给style传入样式,就可以实现布局了。上述代码的效果也很简单,只有 View 与 Text 两个组件。
外层 View 大小随外层大小变化并填充满(flex:1
)。同时 View 的背景为红色(backgroundColor: 'red'
)。他的子组件将会水平居中(alignItems: 'center'
)与垂直居中(justifyContent: 'center'
)。严格说来应该是主轴与次轴居中,RN中组件可以设置竖直或水平方向为主轴。
内层为 Text 组件内容显示为 粗体,黄色 的 Hello , !
。
从上不难看出RN的样式其实就是传入配置json。使其使用起来和H5类似。因此,当多个样式同时使用时需要用数组传入。
style = {style.red} // 传入样式对象
style = {{fontSize: 20}} // 编写传入对象
style = {[style.red, style.centerItems]} // 传入多个样式则通过数组配置
RN的页面生命周期也与客户端类似,有各种加载与渲染的过程,如// 3 中的componentDidMount,与iOS的 ViewDidLoad 用法类似。下面就让我们来认识一下React-Native组件的生命周期。
RN 的组件生命周期
主要分为 Mounting(加载)
, Updating(更新)
, Unmounting(卸载)
过程。Mounting
操作在组件实例化的时候将被调用,Updating
会在组件 props 或 state 变化的时候调用,而Unmounting
则负责页面销毁。
Mounting
组件实例化时以下函数会被调用或插入 Dom 树。加载过程如上述流程图的最上侧框图。
- constructor()
- componentWillMount()
- render()
- componentDidMount()
Updating
一般当属性或组件状态变化的时候会触发,以下方法会在组件重新渲染的时候调用。更新调用链如上述流程图左下侧框图。
- componentWillReceiveProps()
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
Unmounting
在组件 Dom 树删除时调用。卸载调用链如上述流程图右下侧框图。
- componentWillUnmount()
ES6 常用语法
定义组件
ES 6与ES 5定义组件的代码有很大变化,ES 6终于有了类的声明,也让我们看顺眼多了。为了代码的维护和使用,已经不再建议使用ES 5的语法了。
// ES5 的用法,不建议
var View = React.createClass({
render(){
// 输出变量
return (Hello,{this.props.name}! );
}
});
// ES6
class View extends React.Component{
render(){
// 跨组件传值
return ( );
}
}
属性初始化与校验
ES 6类属性的初始化建议在构造函数中constructor
实现。其中也提供了一个属性propTypes
,用以校验某个变量是否必要。使用如下:
class MyView extends React.Component{
constructor(props) {
super(props);
this.state = {
color: props.initialColor
};
}
// 默认值(组件不传递值过来的时候)
static defaultProps = {
name: "bilibili",
nick: "2333",
}
// propTypes用于验证转入的props,当向 props 传入无效数据时,JavaScript 控制台会抛出警告
static propTypes = {
name: React.PropTypes.string.isRequired,
nick: React.PropTypes.number.isRequired,
}
state = {
city: this.props.city,
index:this.props.index,
}
}
导入与导出
ES 6 另一个让我觉得便利的地方就是,新的导出方式。不再像以前一样,需要用类似module.exports={xxx}
这样一点也不美观的方式导出。并且别名与通配符的使用也很好的防止了变量的冲突与导出。
//ES5
//接收对象
var o = require('../xx/xx.js'); // 模块导入
module.exports={ // 模块导出
add:add,
sub:sub
}
//ES6
// 按照变量名导入
import {* as all , userInfo,userToken as uToken,userXXX} form 'test.js';
// 默认导入
import xxx form 'test.js'; // 导入用 default 修饰的默认对象
export {
info as userInfo, // 别名
token as userToken,
xxx as userXXX
};
export default { abcde }; // 默认导出
块级变量let
在这里提出的主要原因是他和我们客户端常见所理解的let不大一样,并不是代表常量的意思(swift,kotelin)。而是代表块级变量(js里常量是const),生命周期只在代码块里有效。虽然块级变量在很多语言中都已很常见,但原js中并没有提供块级变量的使用。而var是全局可用的。这里提出避免误用。
// var 容易造成的问题
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10,如果把 i 声明为 let,则输出为6
其他
以下是一些曾令我疑惑的语法,不一定属于ES 6的新特性。但同属js的语法,因此放在ES 6常见特性的最后。
// 重点在于 state=...this.props.route,相当于把所有命名一致的变量传入
// 类似的
const {name,type} = this.items;
//等价于下面
const name = this.items.name;
const name = this.items.type;
坑点汇总
坑点的汇总主要分为两各部分,框架踩坑与语言特性踩坑。
框架踩坑
1.接入坑点
因为RN的迭代速度有点快,官方文档往往更不上代码的变动速度。Stack Overflow里面相对的解决方案也比较少,且适用性随版本变化。因此,很多坑不能在网络资源中获得答案,而是要在GitHub里的issue,在对应问题中一般都能找到前人的填坑记录。
其中,最常见的问题便是
。但在不同版本中,但原可能不同。
0.45版本
RN 需要引入 BatchedBridge
,不然运行会出现类似报错。而虽然 0.40版本
下也会出现这个问题,但原因是 CocoaPod 需要升级到最新版,把1.1版本升到1.2.1就可以解决了。(其他版本未测试过)
#根据实际路径修改下面的`:path`
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'DevSupport', # 如果RN版本 >= 0.43,则需要加入此行才能开启开发者菜单
'RCTText',
'RCTNetwork',
'RCTWebSocket',
'BatchedBridge', # 0.45 需要添加这行
'RCTImage',
]
# 如果你的RN版本 >= 0.42.0,请加入下面这行
pod "Yoga", :path => "../node_modules/react-native/ReactCommon/yoga"
2.第三方库使用
类似的,在使用第三方库的时候,第三方库不一定会随着RN的跟新做出变化。
React-Native link
这个方便的命令有时候用起来就会莫名的恼火。可以的话,第三方库还是通过 CocoaPod 手动引入的好。React-Native link
容易遇到的问题一般是找不到
。 遇到这类问题,一些文章会建议把第三方尖括号not find!
引用改成引号"React/xxx.h"
。但这样改动太大,且不便于操作。在我研究对比RN
0.39
和0.40
的配置区别后,发现主要是xcode 项目配置Alaways Search User Paths
导致的。因此,在第三方库的Header Search Paths
加入CocoaPod的Pod目录Public里的React文件夹
作为搜索路径就好了(0.45
在Development文件夹
里)。同时需要把第三方库的Alaways Search User Paths
设成 YES 。
语言特性踩坑
1.没有宏定义
全局变量无法当做宏定义使用,没有预编译。全局变量需要运行后才有值,无法在 import 里面当路径宏使用。
2.引入的资源必须用静态路径
Require 图片资源时必须为静态路径,因为require是在编译时期执行,而非运行时期执行。这个动作会发生在运行之前。如果你使用了变量,打包的时候变量并不会有值。RN会给你报错。
3.编译检错
只有语法补全的插件,即使语法错误,运行后才能得知运行结果。
例如:
if(...) A(); else (error code)...;
的代码。测试时,如果不走else的判断,连语法错误都不会检查。如果是夹杂多个的判断,进行语法检错也是需要全部走完。
4.弱类型语言要注意语境
js为弱类型语言且空类型多(如下方第5点所示)。js 判断时会出现强类型语言考虑之外的情况,编写的代码在类型转换的思考上要更为周全。也会出现一些感官上觉得奇怪的代码,如下所示:
return !!b; // 两次强转,保证变量返回是 Boolean 类型`
return a==1?a:0 // 没有写成a==1?1:0,可能业务上结果会返回字符串和数字类型,不能确定
5.类型较多对比时容易弄混
容易弄混的有0、-0、null、""、false、undefined或者NaN。
其中:
- undefined:未定义或未赋值的变量
- null :特殊的object类型,为空对象,和swift 的拆包的空盒模型类似。
- NaN:特殊的number,类似盒子模型拆盒后为空,而不为0。
以下为容易混淆的对比点:
6.this的作用域问题
this的指向场景会根据使用情况变化。而对于不熟悉语言的客户端开发者来说,会经常对指向对象错误的操作。this的指向场景分别四类:
- 有对象就指向调用对象
- 没调用对象就指向全局对象
- 用new构造就指向新对象
- 通过 apply 或 call 或 bind 来改变 this 的所指。
这个问题的解决,最主要还是了解其中的机制。可以参考这篇文章《作用域与闭包:this,var,(function () {})》。
附录
数值的常见用法和结果。
用 法 结 果
Number(false) 0
Number(true) 1
Number(undefined) NaN
Number(null) 0
Number( "5.5 ") 5.5
Number( "56 ") 56
Number( "5.6.7 ") NaN
Number(new Object()) NaN
Number(100) 100
parseInt("AF", 16); //returns 175
parseInt("10", 2); //returns 2
parseInt("10", 8); //returns 8
parseInt("10", 10); //returns 10
Boolean(""); //false – empty string
Boolean("hi"); //true – non-empty string
Boolean(100); //true – non-zero number
Boolean(null); //false - null
Boolean(0); //false - zero
Boolean(new Object()); //true – object
参考资料:
[1] React-Native官方文档
[2] React Native 中文网
[3] GitHub: react-native
[4] GitHub: react-redux
[5] React Native 中组件的生命周期
[6]《作用域与闭包:this,var,(function () {})》