ZF_react 类组件状态的使用 实现组件的更新,合成事件,批量更新
Posted lin-fighting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZF_react 类组件状态的使用 实现组件的更新,合成事件,批量更新相关的知识,希望对你有一定的参考价值。
类组件状态的使用
类组件的数据来源有两个地方,父组件传过来的属性以及自己内部的状态。
属性和状态发生变化后组件都会更新。
state的更新可能是异步,出于性能考虑可能会把多个setState合并到一起使用。
连续两个this.setState({})在第二个state不能拿到最新的值。他们会被合并。
可以使用this.setState(pre=>({…pre}))传入函数的形式来返回一个新值,这样下一个setState就能拿到最新的值。
这些setState还是会异步执行。怎样同步执行呢?在react管不到的地方,比如setTimeout里面执行的状态更新就都是同步的。即setState之后在同个函数可以拿到最新的修改完的State。
- 凡是react可以管控的地方,就是setState批量异步执行。
- 而不能管控的地方就是非批量同步执行。 setTimeout,原生dom事件等等。
- 本质就是批量与非批量,与js的同步异步无关。setState第二个回调参数也是有批量与非批量之分。
react事件采用小驼峰,如onClick。传的是一个函数的引用地址,真实的函数定义。
而原生dom事件传的是函数名的字符串。
简单实现:
let isBatchingUpdate = false;
let queue = [];
let state = { number: 1 };
function setState(newState) {
if (isBatchingUpdate) {
//批量更新
queue.push(newState);
} else {
state = { ...state, ...newState };
}
}
const handleClick = () => {
//表示批量更新 react内部操作
isBatchingUpdate = true;
//自己的逻辑 我们自己书写的逻辑
setState({ number: state.number + 1 });
console.log(state);
setState({ number: state.number + 1 });
console.log(state);
//结束
//react内部操作, 计算
state = queue.reduce((newState, currentVal) => {
console.log('newState', newState);
console.log('currentVal', currentVal);
return { ...newState, ...currentVal };
}, state);
};
handleClick();
console.log(state);
如上,我们定义了一个变量控制是否批量更新。如果批量更新则先放入数组,否则立即更新。
在批量更新之后,会执行一个遍历操作。将最新的state与老的state喝冰。
这是打印结果,可以看到最后的state的number是2,他执行了两次为什么还是2?因为currentVal里面的state指向的是老的state。即使第一次更新了number变为2,但是第二次更新的时候state还是1,执行了加一操作后变成2,再与第一次执行的状态进行合并,u最后得到2。
组件状态的实现
现在我们要基于自己之前实现的源码继续进行学习。
- 首先,要在React.Component这个类动手脚。
在其原型上定义一个setState的方法。每个类组件的实例都有一个updater更新器。每次一setState就会触发更新器的addState方法。
所以重点看下这个更新器:先实现同步的更新
Updater这个类的属性有类的实例,以及将要更新的队列和一个回调函数队列。
addState方法
这个方法用于收集更新任务放入队列。执行emitUpdate方法。
这个方法暂时实现先调用updateComponent方法。
这个方法用于调用shouldUpdate方法,并将根据队列计算所得的新的state传入
这个方法会直接修改类实例的state。调用类的forceUpdate方法。
getState方法的实现,用于遍历队列中的更新计算最新的state然后返回。
看效果:
到此为止类的state就已经更新了。接着实现组件的更新,首先先暂时不使用diff算法。
我们需要在更新组件的时候获取新老vdom,并将其差异渲染在真实dom上。主要实现findDom,和compareTwoVdom两个方法。
在我们首次创建dom的时候,比如类组件本身就是一个react元素,然后调用render又返回一个react元素。那么就让类组件的vdom上的oldRenderVdom指向这个内层的返回的react元素,函数组件也是同样的道理。
到达内层组件的时候,
在我们之前创建dom的时候就有把真实dom挂载到vdom上。所以就可以通过类组件的vdom获取内层的vdom,在获取真实的dom。
findDom的实现
通过虚拟dom获取真实dom,如果类型是组件,就应该继续调用,传入内层的vdom。如果不是组件,就表示是内层的vdom了,直接返回dom即可。
compareTwoVdom的简易实现
直接更改。到此,类组件状态的同步更新就完成了。
效果:
我们刚才实现的时候同步的更新,也就是未批量的更新,现在实现异步(批量更新)
批量更新的实现
批量更新的实现需要依赖一个全局变量,updateQueue,他可以控制是否批量更新,并且存放着更新的队列,和一个遍历队列触发更新的函数。
如
然后在每次收集更新依赖的时候
如果开启了批量更新的开关,就将其存起来,等时机一到再一起更新。
目前我们已经定义了一个全局变量用来控制批量更新。我们还需要当更新发生的时候,就是onclick事件触发的时候开启批量更新的开关。
合成事件的完成
react通过将事件委托在document上,(17之后是委托在root组件)
在处理事件的时候需要修改一下,实现addEvent函数,该函数接受三个参数,当前的dom,事件名,事件函数。
这个函数做的事情也很简单,主要是给dom上加了一个store属性,用来存放该dom的事件。
然后将该事件委托给了document。当事件触发的时候触发dispatchEvent函数。
dispatchEvent函数,通过event,拿到target和type。然后在这里开启批量更新,因为事件已经发生了,对应的事件函数也会触发,
这个函数就会被调用,因为开启了批量更新,所以被收集到updateQueue中。
然后模范了下事件冒泡,比如a的伏组件是b,a发生了点击事件后,通过a.parentNode拿到b,如果b有点击事件也会触发。最后结束的时候直接调用全局变量updateQueue执行batchUpdate函数,遍历执行里面的每一个updater的updateComponent方法。达到批量更新的目的
结果:
可以看到,setTimeout之外的更新,两个state此时都是numebr为0,表示了这时候已经是批量更新了。然后接着调用setTimeout内的函数,
- 因为setTimeout是宏任务,会等同步任务执行完后再执行,所以当执行setTimeout里面的函数的时候,事件处理已经过去了,而批量更新的开关也已经关闭,所以只能是非批量(同步更新),所以可以看到两个打印出来的值是实时的。
- 这也是在开发中有时候遇到两个setState的时候,会有一个触发过后,另一个拿到的state的值还是老的的原因,因为他们是批量更新的,执行的时候拿到的state还是老的。解决办法有通过setTimeout(()=>{},0)让一个跳出批量更新,这样另一个拿到的state就永远是最新的了。
以上是关于ZF_react 类组件状态的使用 实现组件的更新,合成事件,批量更新的主要内容,如果未能解决你的问题,请参考以下文章
ZF_react hooks useState的实现 useCallback useMemo useReducer useContext
ZF_react 高阶组件 多个context的实现 render props Purcomponent的实现 React.memo的实现
ZF_react redux 中间件thunk promise logger applyMiddleware 中间件级联实现
ZF_react react-router prompt lazy的实现
React 表格内自定义组件不进行实时更新,采用更新key方式实时更新组件,以及直接变更开关 defaultChecked 为 checked 状态实现表格组件更新