为什么需要合成事件
- 兼容浏览器监听写法
- 避免大量节点绑定事件占用内存,将事件委托到
document
上,有统一的事件处理函数,等事件冒泡到document
上时,react
从target
节点往上遍历父元素,判断有没有元素绑定对应事件,有则触发该事件。 - 事件回调函数处理完成后需要清空state队列中的更新,重新渲染视图。
事件持久化
event.persist
,每当事件回调函数被触发后,合成事件对象都会被清空,保证其垃圾回收。如果需要异步处理事件,则需要执行event.persist
,需要的事件对象会被保留,清空操作的对象则是一个新创建的对象。SyntheticEvent
就是合成事件对象,这个对象会被重复使用,避免对象的重复创建开销。
setState
当执行setState
时,先将当前要更新的state
存储在Updater
组件实例上,然后判断当前是否处于批量更新状态
,如果是,则将当前Updater
实例存储下来,等到非批量更新
的时候一起更新。
在处理事件的时候,因为可能在回调函数中的动作会引起多个组件的更新,为了性能优化,react
会进入批量更新状态
。在代码中就是在统一的事件处理函数中将状态设置为批量更新状态。等当前事件执行结束后(遍历完所有的父节点,执行完所有的回调函数)将状态设置为false
。并且执行batchUpdate
批量执行更新逻辑。
state={count: 0}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
// 0
console.log(this.state.count)
this.setState((prevState) => ({
count: prevState.count + 1
}))
// 0
console.log(this.state.count)
setTimeout(() => {
this.setState((prevState) => ({
count: prevState.count + 1
}))
// 3
console.log(this.state.count)
this.setState((prevState) => ({
count: prevState.count + 1
}))
// 4
console.log(this.state.count)
}, 0)
}
执行更新逻辑时,如果render
函数返回的是函数组件,前后对比两次函数执行结果,进行dom-diff
,执行需要的节点更新逻辑。如果返回的都是类组件,并不是直接调用render方法,将前后结果进行比对,而是调用返回组件实例上的updater. emitUpdate
方法,这样的话,就会执行子组件中的componentShouldUpdate
方法判断此次是否需要更新,如果需要则执行forceUpdate
方法再次执行dom-diff
逻辑判断,这个时候子组件返回的就是普通的原生节点,按原有逻辑进行比对即可。
在构造函数被new
之后,componentWillMount
被调用,调用实例的render
函数,会递归解析返回的元素,解析子组件,实例化子组件,子组件componentWillMount
被调用,调用子组件render
函数,componentDidMount
完成所有的子元素解析后,执行父组件的componentDidMount
后完毕。当父组件调用setState
,生命周期执行顺序为
- 父组件
shouldComponentUpdate
- 父组件
componentWillUpdate
- 子组件
componentWillReceiveProps
- 子组件
shouldComponentUpdate
- 子组件
componentWillUpdate
- 子组件
componentDidUpdate
- 父组件
componentDidUpdate
为什么不能将index作为key
因为react在dom-diff对比时,会将老节点列表中的key相同的元素保留作为新节点列表中对应key的元素。
dom-diff规则
- 对树来说,如果出现组件移动的情况,不处理,直接销毁组件重新生成
- 如果组件/节点类型发生变化则直接替换
- 同一层级的dom变更:增加、删除、移动(删除+插入 )
dom-diff
算法
- 将老节点信息存储在一个对象中,键名为
key
,键值为虚拟dom
对象 - 遍历新子元素数组,先去对象中查找有没有相同
key
的虚拟dom
,如果有,则复用该虚拟dom
并更新节点属性,再判断一下虚拟dom
在原节点层次中的位置,如果比lastIndex
小,则移动(删除当前节点,将虚拟dom拷贝到新位置),否则就不动(lastIndex
会在遍历时变大)。相当于节点会不断往右移动。lastIndex用于判断可复用的节点是否需要移动。