useState 参照源码简单实现

254 阅读2分钟

最近在看 React Hooks 源码,并参照大神卡颂的B站视频,依照源码实现了简单的 useState 的实现,直接上代码。

let isMount = true
let workInProgressHook = null

const fiber = {
  stateNode: App,
  memoizedState: null // 单向链表存储每一个 useXXX 的 hook
}

function useState (initialState) {
  let hook
  if (isMount) {
    hook = {
      memoizedState: initialState,
      next: null,
      // 存储 setXXX 时的队列,pending是待执行的循环链表
      queue: {
        pending: null
      }
    }
    if (!fiber.memoizedState) {
      fiber.memoizedState = hook
    } else {
      workInProgressHook.next = hook
    }
    // 上一行代码,workInProgressHook 可以视为上一次的 hook
    // 将上一次的 hook 的 next 指向当前的 hook
    // 并将当前的 hook 的引用赋给 workInProgressHook
    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 dispatchAction (queue, action) {
  // 环状链表,链表中的每一个 action 可能执行的优先级不同
  // 因为优先级不同,可能会跳过一些节点执行优先级高的节点
  // 环状链表可以完成被跳过节点的执行
  // 而单向链表需要找到第一个节点再继续执行
  const update = {
    action,
    next: null
  }

  // 生成环状链表
  if (queue.pending === null) {
    // u0 -> u0
    update.next = update
  } else {
    // u1 -> u0 -> u1
    // queue.pending 为环状链表的最后一个节点
    // queue.pending.next 即为环状链表的第一个节点

    // u0 -> u1 -> u2 -> u0, 添加 u3 后,环状为 u0 -> u1 -> u2 -> u3 -> u0
    // 其中 u3: update, u2: queue.pending, u0: queue.pending.next
    // u3.next = u0, u2.next = u3
    update.next = queue.pending.next
    queue.pending.next = update
  }

  queue.pending = update

  schedule()
}

function schedule () {
  // 渲染完成后将 workInProgressHook 设置回链表的第一个位置
  workInProgressHook = fiber.memoizedState
  const app = fiber.stateNode()
  isMount = false
  return app
}


function App () {
  const [num, updateNum] = useState(1)
  const [num1, updateNum1] = useState(10)

  console.log('isMount?', isMount)
  console.log('num:', num)
  console.log('num1:', num1)
  return {
    onClick () {
      updateNum(num => num + 1)
      updateNum1(num => num + 10)
    }
  }
}

window.app = schedule()

代码中的一些注释是根据自己的理解写进去的,不一定完全正确,欢迎路过的大佬指正。

附上 卡颂老师的B站主页

卡颂老师视频分享里面会更加详细的介绍迷你版 useState 的实现过程,并有源码解读。感谢卡颂老师的分享。