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 中的父类更新我的子组件状态?

React 表格内自定义组件不进行实时更新,采用更新key方式实时更新组件,以及直接变更开关 defaultChecked 为 checked 状态实现表格组件更新