react 学习(五) 完善 setState 及实现 ref

2022-04-12 21:45:42 浏览数 (2)

上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二参数作为回调函数。我们先把上一节的工作收个尾再介绍 ref 的实现。

完善 setState

立即执行

我们在代码中实现如下逻辑,传入函数,我们需要修改一下 getState 方法

代码语言:txt复制
this.setState((state) => ({ number: state.number   1 }));
this.setState((state) => ({ number: state.number   1 }));


// src/Component.js
getState() {
  ...
  pendingStates.forEach(nextState => {
    // 对参数进行判断
    if (typeof nextState === 'function') {
      nextState = nextState(state)// 这里把旧的 state 传入,立即执行,这样我们在代码中看到的就类似同步执行,函数返回新的 state 状态
    }
    state = {
      ...state,
      ...nextState
    }
  })
}

相信看完上面代码,大家应该能够理解为什么传入函数就可以同步执行。因为每次都会立即计算获取当前最新的状态。

实现回调函数

setState 的第二个参数会在页面更新后执行,获取到最新的状态返回值。所以我们能想到开始先把回调函数收集,在组件更新完成之后再依次执行,代码如下:

代码语言:txt复制
// src/Component.js Updater类
初始化需要定义回调函数栈
this.callbacks = []

在 addState 添加状态的同时收集回调函数
addState(partialState, callback) {
  this.pendingStates.push(partialState)
  callback && this.callbacks.push(callback)
  ...
}


在组件完成更新 updateComponent 方法的最后执行回调

updateComponent() {
  const {classInstance, pendingStates, callbacks} = this
  if (pendingStates.length) {
    ...
  }
  // 这里咱们使用个微任务执行,因为 js 的单线程机制,可以保证微任务一定在也买呢渲染后执行
  queueMicrotask(() => {
    callbacks.forEach(cb => cb.call(this))
    this.callbacks.length = [] // 执行完成后清空
  })
}

相信大家看过之前几节的基础上,能够很快的理解上面的代码,如果大家更这实现也基本能实现自己的 react-demo 了。

实现 createRef

我们通常用 ref 来获取真实 dom 节点然后进行操作,上一小节我们知道我们已经把真实的 dom 绑定在了 vdom 上,类组件的实例也能够拿到,所以我们只需加一层判断即可。

首先获取 ref 属性,入口文件中实现如下 dom 结构:

代码语言:txt复制
// src/index.js

constructor(props) {
  super(props);
  this.a = React.createRef(); // {current: null}
  this.b = React.createRef();
  this.result = React.createRef();
}
  
...
<>
  <input ref={this.a} type="text" />   <input ref={this.b} type="text" />{" "}
  <button onClick={this.handleClick}>=</button>{" "}
  <input ref={this.result} type="text" />
</>

修改 react.js 文件

代码语言:txt复制
第一步返回值新增字段
const React = {
  createElement,
  Component,
  createRef,
};
// 一行代码就完事了
function createRef() { return {current: null} }

我们还需要修改下返回的虚拟dom对象,新增 ref 属性

function createElement(type, config, chidlren) {
  let ref 
  if (config) {
    ref = config.ref
    delete config.ref
    ...
  }
  ...
  return {
     ...
     ref
  } 
}

我们注意 ref 对于 dom 来说一般用在类组件和正常的 dom 上,所以我们只需要修改 createDOMmountClassComponent 方法

代码语言:txt复制
// src/react-dom.js
function createDOM(vdom) {
  // 拿到 ref
  const {type, props, ref} = vdom
  ...
  if (ref) ref.current = dom
  ...
}

function mountClassComponent(vdom) {
  // 同理
  const {type, props, ref} = vdom
  ...
  if (ref) ref.current = classInstance
  ...
}

本小节我们完善了 setState 方法的使用,了解了函数可以同步执行的原因;我们也知道了 ref 的实现原理,只需要对其 current 属性进行绑定即可。下一小节我们学习下 react 的生命周期。如果有不对,欢迎指正!

0 人点赞