React 的理念
React 的主要思想是通过构建可复用组件来构建用户界面。所谓组件,其实就是有限状态机(FSM),通过状态渲染对应的界面,且每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段改变和执行。
简单的来说,如下图:
比较装逼的来说,有限状态机,表示有限个状态以及在这些状态之间的转移和动作等行为的模型。一般通过状态、事件、转换和动作来描述有限状态机。下图 是描述组合锁状态机的模型图,包括 5 个状态、5 个状态自转换、6 个状态间转换和 1 个复位 RESET 转换到状态 s1。状态机能够记住目前所处的状态,可以根据当前的状态做出相应的决策,并且可以在进入不同的状态时做不同的操作。状态机将复杂的关系简单化,利用这种自然而直观的方式可以让代码更容易理解。
React 正是利用这一概念,通过管理状态来实现对组件的管理。例如,某个组件有显示和隐藏两个状态,通常会设计两个方法 show() 和 hide() 来实现切换,而 React 只需要设置状态 setState({ showed: true/false }) 即可实现。同时,React 还引入了组件的生命周期这个概念。通过它,就可以实现组件的状态机控制,从而达到“生命周期→状态→组件”的和谐画面。
虽然组件、状态机、生命周期这三者都不是 React 独创的,但 Web Components 标准与其中的自定义组件的生命周期的概念相似。就目前而言,React 是将这几种概念结合得相对清晰、流畅的 View 实现。
UI = ƒ(count) =
div(
span('Count ' + count),
button('Add +1')
)
在 React 中,数据是自顶向下单向流动的,即从父组件到子组件。这条原则让组件之间的关系变得简单且可预测。
state 与 props 是 React 组件中最重要的概念。如果顶层组件初始化 props,那么 React 会向下遍历整棵组件树,重新尝试渲染所有相关的子组件。而 state 只关心每个组件自己内部的状态,这些状态只能在组件内改变。把组件看成一个函数,那么它接受了 props 作为参数,内部由 state 作为函数的内部参数,返回一个 Virtual DOM 的实现。
在使用 React 之前,常见的 MVC 框架也非常容易实现交互界面的状态管理,比如 Backbone。它们将 View 中与界面交互的状态解耦,一般将状态放在 Model 中管理。但在 React 没有结合 Flux 或 Redux 框架前,它自身也同样可以管理组件的内部状态。在 React 中,把这类状态统一称为 state。
当组件内部使用库内置的 setState 方法时,最大的表现行为就是该组件会尝试重新渲染。这很好理解,因为我们改变了内部状态,组件需要更新了。
render(){
return (
<div>
<span>
Count:<b>{this.state.count}</b>
</span>
<button onClick={() => ???}>
Add +1
</button>
</div>
)
}
所以??? 就是 this.setState({count:this.state.count + 1})
数据更新过程
因为View的展示和View的事件响应分属于不同的端,展示部分的描述在JS端,响应事件的监听和描述都在Native端,通过Native转发给JS端。Native开发里,什么时候会执行代码?只在有事件触发的时候,这个事件可以是启动事件,触摸事件,timer事件,系统事件,回调事件。而在React Native里,这些事件发生时OC都会调用JS相应的模块方法去处理,处理完这些事件后再执行JS想让OC执行的方法,而没有事件发生的时候,是不会执行任何代码的,这跟Native开发里事件响应机制是一致的。 会在下一节具体说明
就拿一个简单的点击按钮,更新 text 计数来说,点击以后, Native会分发如下事件:
[_bridge enqueueJSCall:@"EventEmitter.receiveTouches" args:@[
@"end",
@{@"x": @42, @"y": @106}]];
经过 bridge转换后,就变成
call('EventEmitter', 'receiveTouches', [{x: 42, y: 106}])
如果组件自身的 state 更新了,那么会依次执行 shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
class App extends Component {
componentWillReceiveProps(nextProps) {
// this.setState({})
}
shouldComponentUpdate(nextProps, nextState) {
// return true;
}
componentWillUpdate(nextProps, nextState) {
// ...
}
componentDidUpdate(prevProps, prevState) {
// ...
}
}
shouldComponentUpdate 是一个特别的方法,它接收需要更新的 props 和 state,让开发者增加必要的条件判断,让其在需要时更新,不需要时不更新。因此,当方法返回 false 的时候,组件不再向下执行生命周期方法。
shouldComponentUpdate 的本质是用来进行正确的组件渲染。怎么理解呢?我们需要先从初始化组件的过程开始说起,假设有如图所示的组件关系,它呈三级的树状结构,其中空心圆表示已经渲染的节点。
当父节点 props 改变的时候,在理想情况下,只需渲染在一条链路上有相关 props 改变的节点即可
而默认情况下,React 会渲染所有的节点,因为 shouldComponentUpdate 默认返回 true。正确的组件渲染从另一个意义上说,也是性能优化的手段之一。
回到 RN 来说,RN框架会根据传递进来的信息,计算出应该哪个节点响应事件,并把该组件的 ID 作为参数传入。如果shouldComponentUpdate返回 true需要渲染,则让 Native 进行更新渲染。
var UIManager = require('NativeModules').UIManager;
UIManager.update(18, {text: '43'});
通过MessageQueue发送相应的数据给 Native 处理
NativeModules.UIManager = {
...
update: function(viewID, attributes) {
MessageQueue.push(
['UIManager', 'update', [viewID, attributes]]
);
}
...
};
转换成的 Native 代码,markAsDirty标记此控件需要更新,等待 VSync 事件更新
[UIManager updateView:18 props:@{@"text": @"43"}]
addUIBlock:^() {
UILabel *label = viewRegistry[18];
label.text = @"43";
[label markAsDirty];
}
简要的流程图