跳至主要內容

hook

h7mlreactreacthook大约 2 分钟约 696 字

react hook 原理

讲 hooks 之前提出一个问题:为什么 hooks 不能写在条件语句之中?

我们在初始化 hooks 的时候,fiber 的结构是长什么样的呢?

function App() {
  const [num, updateNum] = useState(0);
  const [name, setName] = useState('alvin');
  return null
}
// fiber 结构:
{
  // memoizedState:hooks 链表结构
  memoizedState: {
    queue: { pending: null },
    memoizedState: 1,
    next: { queue: { pending: null}, memoizedState: 'alvin', next: null }
  },
  stateNode: [Function: App]
  // 其他属性...
}

当我们执行 updateNum 怎么去更新我们的应用呢?

如上,创建一个 hooks 链表结构,存储在 fibermemoizedState 属性上,next 指针指向下一个 hooks

创建更新对象

const update = { action, next: null };

对于 App 来说,点击 p 标签产生的 updateactionnum => num + 1

如果我们改写下 ApponClick

// 之前
return <p onClick={() => updateNum((num) => num + 1)}>{num}</p>;

// 之后
return (
  <p
    onClick={() => {
      updateNum((num) => num + 1);
      updateNum((num) => num + 1);
      updateNum((num) => num + 1);
    }}
  >
    {num}
  </p>
);

那么点击 p 标签会产生三个 update

合并更新

这些 update 是如何组合在一起呢?

答案是:他们会形成环状单向链表

function dispatchAction(queue, action) {
  // 创建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;

  // 模拟React开始调度更新
  schedule();
}

环状链表操作不太容易理解,这里我们详细讲解下。

当产生第一个update(我们叫他u0),此时queue.pending === null

update.next = update;u0.next = u0,他会和自己首尾相连形成单向环状链表

然后queue.pending = update;queue.pending = u0

queue.pending = u0 ---> u0
                ^       |
                |       |
                ---------

当产生第二个update(我们叫他u1),update.next = queue.pending.next;,此时queue.pending.next === u0,即u1.next = u0

queue.pending.next = update;,即u0.next = u1

然后queue.pending = update;queue.pending = u1

queue.pending = u1 ---> u0
                ^       |
                |       |
                ---------

你可以照着这个例子模拟插入多个update的情况,会发现queue.pending始终指向最后一个插入的update

这样做的好处是,当我们要遍历update时,queue.pending.next指向第一个插入的update

简单实现

详情略...

let workInProgressHook;
let isMount = true;

// App组件对应的fiber对象
const fiber = {
  // 保存该FunctionComponent对应的Hooks链表
  memoizedState: null,
  // 指向App函数
  stateNode: App,
};

function schedule() {
  workInProgressHook = fiber.memoizedState;
  const app = fiber.stateNode();
  isMount = false;
  return app;
}

function dispatchAction(queue, action) {
  // 创建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;

  // 模拟React开始调度更新
  schedule();
}

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 = hook;
  } else {
    hook = workInProgressHook;
    workInProgressHook = workInProgressHook.next;
  }

  let baseState = hook.memoizedState;
  if (hook.queue.pending) {
    let firstUpdate = hook.queue.pending.next;

    do {
      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)];
}

function App() {
  const [num, updateNum] = useState(0);

  console.log(`${isMount ? 'mount' : 'update'} num: `, num);
  return {
    onClick() {
      updateNum((num) => num + 1);
    },
  };
}

const app = schedule();
app.onClick();