hook解析

58 阅读2分钟

useState

上节讨论了fiber, 今天我们继续来看hook

function App() {
  const [num, updateNum] = useState(0);
  
  function increment() {
    setTimeout(() => {
      updateNum(num + 1);
    }, 1000);
  }
  
  return <p onClick={increment}>{num}</p>;

首先提问: useState怎么保存状态的呢, 然后有时怎么更新状态的呢

带着问题我们继续往下思考

我们都知道useState改变会触发组件重新渲染, useRef值不会触发

多个hooks的连接指向

A-> B-> C

const hookA = {
  // hook保存的数据
  memoizedState: null,
  // 指向下一个hook
  next: hookB
  // ...省略其他字段
  // 本次更新以baseState为基础计算新的state
  baseState: null,
  // 本次更新开始时已有的update队列
  baseQueue: null,
  // 本次更新需要增加的update队列
  queue: null,
};

hookB.next = hookC;

currentlyRenderingFiber.memoizedState = hookA;
currentlyRenderingFiber.memoizedState = hookA;

其中memoizedState存储这当前的值得状态

对于更新:

useState存在第二个参数: setXX

每次调用的时候都会产生一个update对象:

updateA -> updateB -> updateC

const update = {
  // 更新的数据
  action: action,
  // 指向下一个更新
  next: null
};

那么这个链表存储在哪里呢, 由上图可以知道存在 queue,

顺序依次为 queue挂载在 baseQueue 然后与 baseState进行比对更新, 最后将更新结果赋予 memoizeState

再回头来看: , 多次点击的num+1, 由于还未来的及更新此时baseState依旧为0 , 所以最终渲染依旧是同一个值1

传函数与传值的区别

在计算state时,会将queue的环状链表剪开挂载在baseQueue最后面,

baseQueue基于baseState计算新的state。


let newState = baseState;
let firstUpdate = hook.baseQueue.next;
let update = firstUpdate;

// 遍历baseQueue中的每一个update
do {
  if (typeof update.action === 'function') {
    newState = update.action(newState);
  } else {
    newState = action;
  }
} while (update !== firstUpdate)

如果其中setTimeout 的时间改的很小, 比如直接不设置

 function increment() {
    setTimeout(() => {
      updateNum(num + 1);
    });
  }

如果会发生改变么?

那如果我们使用useReducer 呢????