相关文章推荐
export  function Demo() {
  const [count, updateCount] = useState(1)
  const [name, updateName] = useState('a')
  const handleClick = () => {
    updateCount(count => count +1)
    updateCount(count => count +1)
  const handleChange = () => {
    updateName(name => name +'a')
    updateName(name => name +'b')
  return (<>
  <p>useState简易实现</p>
  <button onClick={handleClick}>ADD</button>
  <button onClick={handleChange}>Change</button>

通过定义useState这个函数,useState的返回值是 [count, updateCount],count永远保存的最新的值,updateCount是用来更新这个count的方法。updateCount的改变会触发组件重新render。对name的更新同理。所以react hooks工作流程大致如下:

1、通过一些方式产生更新,并且更新会使得组件重新render(函数组件Demo1重新执行)

2、组件render时useState返回的是更新后的结果

其中,更新分为mount和update两个阶段:

1、mount时赋予初始值(initialValue),产生mount的更新

2、通过点击button让值加1,产生update的更新

开始实现一个setState

react hooks采用单向链表的形式进行链接,所以需要一个存储hooks链表的地方,这个地方就是fiber对象,定义一个简单fiber对象如下:

let fiber = {
   memoizedState: null,  //保存组件对应的hooks链表 ,每个链表节点是一个hook对象
   stateNode: Demo   //指向函数组件

让fiber的stateNode执行函数组件,这样就可以通过调用fiber.stateNode()让函数组件重新执行;通过fiber.memoizedState以链表的形式记录第一个hook到最后一个hook的信息

既然有链表,就得有指针,记录当前工作流下执行的是哪个hook,所以定义一个变量workInProgressHook作为指针。

定义一个变量isMount区分是在挂载阶段还是更新阶段

let isMount = true //用来区分是挂载还是更新
let workInProgressHook = null //

然后定义一个调度器schedule来执行组件rerender,并且每次重新执行,需要将workInProgressHook 复位指向第一个hook (这其实也是为什么每次更新,都是按顺序执行一遍hooks)

function schedule(){
  workInProgressHook = fiber.memoizedState;
  fiber.stateNode()

这时,让我们简单的写下useState代码,大概逻辑如下:

function useState(initialState) { // 当前useState使用的hook会被赋值该该变量 let hook; if (isMount) { // ...mount时需要生成hook对象 } else { // ...update时从workInProgressHook中取出该useState对应的hook //处理更新,并且return [state,setStateAction],返回当前状态,和设置状态的方法

定义hook对象,并且初始化让fiber.memoizedState指向第一个useState,然后第二个hook进来(执行第二个useState),让第一个指向第二个

代码如下:

function useState(initialState){
  let hook
  if (isMount) {
    // ...mount时需要生成hook对象
    hook = {
      memoizedState:initialState,
      next:null
    if(!fiber.memoizedState){
      fiber.memoizedState = hook
    }else{
      workInProgressHook.next = hook
    workInProgressHook = hook
  } else {
    // ...update时从workInProgressHook中取出该useState对应的hook
  let baseState = hook.memoizedState;
  //TODO: 处理更新
  //...
  return [baseSate,dispatchAction]

既然react hook是通过每次更新是组件rerender,那么我们就需要定义一个更新对象,用来存储每次更新的信息,由于更新的时候可能是同时更新了多次,如下所示调用了三次setCount:

 const handleClick = () => {
    updateCount(count => count +1)
    updateCount(count => count +1)
    updateCount(count => count +1)

模拟react对更新操作的处理,会将这些更新同样的用连起来,通过环状单向链表的形式,使用环状的目的是,react中对更新操作有优先级的概念,环状链表可以方便定位到任何一次高优先级的更新位置去执行,而暂时摒弃低优先级的更新。定义更新的结构如下:

const update = {
  action,// 更新执行的函数 count => count +1
  next: null// 与同一个Hook的其他更新形成链表

问: 何时进行更新对象的链接?更新的信息存在哪?

答: 将更新的信息存储在对应的hook对象中,在hook对象中添加一个属性queue,queue的pending指针就指向更新的链表。在调用dispatchAction时将这些更新链接起来。

所以我们更新下hook对象结构,更新后的hook对象结构:

 hook = {
      queue: {
        pending: null
      memoizedState: initialState,
      next: null

环状单向链表示意图:

定义dispatchAction 方法如下:

function dispatchAction (queue,action) { //调用hook的更新方法,实现更新(queue是存放update信息的地方,通过改变queue。pending和其next指针完成链接)
  // 创建update
  const update = {
    action,
    next: null
  // 环状单向链表操作
  if (queue.pending === null) {
    update.next = update;
  } else {
    update.next = queue.pending.next;
    queue.pending.next = update;
  queue.pending = update;
  schedule(); // 模拟React开始调度更新

环状链表串联示意图:

每调用一次dispatchAction让这些更新对象串联起来,通过schedule使组件重新执行,然后在useState里执行挂载在hook的更新

function useState(initialState) {
  let hook;
  if (isMount) {
    hook = {
      queue: {
        pending: null
      memoizedState: initialState,
      next: null
    if (!fiber.memoizedState) {
      fiber.memoizedState = hook;
    } else {
      workInProgressHook.next = hook;
    // 移动workInProgressHook指针
    workInProgressHook = hook;
  } else {
    // update时找到对应hook
    hook = workInProgressHook;
    // 移动workInProgressHook指针
    workInProgressHook = workInProgressHook.next;
  let baseState = hook.memoizedState;
  if (hook.queue.pending) {
    let firstUpdate = hook.queue.pending.next;
      const action = firstUpdate.action;
      baseState = action(baseState);
      firstUpdate = firstUpdate.next;
    } while (firstUpdate !== hook.queue.pending)
    hook.queue.pending = null;
  hook.memoizedState = baseState;
  return [baseState, dispatchAction.bind(null, hook.queue)]

通过链表的形式连接hooks对象,将链表的信息存储在fiber对象的memoizedState中以供更新使用,每次调用dispatchAction时将更新操作连接,更新信息存在对应hook对象的queue中,然后schedule重新执行函数组件,进入useState,查看对应的hook的queue,执行。最后将结果返回。

clone一份react源码,找到ReactFiberHooks.old.js,搜索useState,可以看到useState存在多个HooksDispatcher里,找到对应mountState、updateState方法的实现,可以看到与简易的useState实现思路是一致的。

对比与React的区别:

1、React Hooks没有使用isMount变量,而是在不同时机使用不同的dispatcher。(react 源码里搜:HooksDispatcher)

2、React Hooks有中途跳过更新的优化手段。

3、React Hooks有批量更新batchedUpdates,当在click中触发三次updateNum,精简React会触发三次更新,而React只会触发一次。

4、React Hooks的update有优先级概念,可以跳过不高优先的update,简易实现虽然模拟react有单向环状链表,但是其实并没有利用到环状结构的优势。

参考文章: @魔法师卡颂 www.yuque.com/liangxincha… react.iamkasong.com/process/fib…

 
推荐文章