数据驱动视图是什么?(单向数据流)
传统的jquery开发是通过操作dom来实现页面的渲染和交互,而React采用的是数据驱动视图的方式:view是基于数据来渲染的,数据一旦变化,view就会自动更新。因此我们在开发时,只需要关注数据即可,不用直接操作dom。
注意:React不是响应式设计,因为它需要通过this.setState、useState等方式手动触发数据变化。
一、数据驱动视图原理
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {val: 0};
}
render() {
return(
<div className="container">
<button onClick={ () => {
this.setState({val: 1})
}}>Submit!</button>
</div>
)
}
}
React通过setState实现数据驱动视图,通过setState来引发一次组件的更新过程从而实现页面的重新渲染(除非shouldComponentUpdate返回false)。假设我们点击button触发了onClick事件,然后它对应的监听函数调用了this.setState()来改变数据,之后的操作过程为:
1. setState:dirtyComponent => 批量更新
pending:当前所有等待更新的state队列。isBatchingUpdates:React中用于标识当前是否处理批量更新状态,默认false。dirtyComponent:当前所有待更新state的组件队列。- 将
pending队列中的state进行合并,得到最终要更新的state,并将pending队列置为空。(该方法的执行时机不确定,应该在事务结束后?)
处理过程:
setState()首先将接收的第一个参数state存储在pending队列中;(state)- 判断当前
React是否处于批量更新状态,是的话就将需要更新state的组件添加到dirtyComponents中;(组件) - 不是的话,它会遍历
dirtyComponents的所有组件,调用updateComponent方法更新每个dirty组件(开启批量更新事务),每个组件有:
2. 执行更新阶段的生命周期 => dirty组件的更新
调用setState会默认调用组件更新阶段的5个生命周期,依次是:
- 执行生命周期
getDerivedStateFromProps - 执行生命周期
shouldComponentUpdate,根据返回值判断是否要继续更新。 - 执行生命周期
render:执行真正的更新。 - 执行生命周期
getSnapshotBeforeUpdate - 执行生命周期
componentDidUpdate
3. render:重新构建虚拟dom树,执行 diff 并更新到真实dom => view更新
我们知道在React的生命周期里,无论是挂载还是更新阶段,在render之前的生命周期函数都不会更新this.state和props,直到render执行完成后,数据才会更新。(只有shouldComponentUpdate返回false时例外,此时会中断更新过程,但依然会更新this.state)
3.1 虚拟dom树:dirty组件的组件树
jsx通过babel转换成React.createElement;createElement()函数返回了一个了ReactElement函数;ReactElement函数返回的对象就是虚拟dom(Fiber 链表结构);Fiber节点通过return、child、sibling属性连接成Fiber Tree。
当state或props改变时,会再次调用render生成一个虚拟dom树。
3.2 diff:批量更新,使虚拟dom和真实dom保持同步
比较新旧两个虚拟dom树,生成一个补丁,最后批量把补丁更新到真实dom上。
它会把收集到的多个补丁集暂存到队列中,最终实现集中的dom批量更新。
协调Reconciliation过程是将虚拟dom和真实dom保持同步,它包含diff算法,但它加了一些启发规则。