mini-react之hook

240 阅读3分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

大家好!我是前端爬楼工程师,是一名热爱学习的野生程序员,希望结识更多朋友一起进步.

本篇讲的内容是函数组件的hooks,同步代码在这里4.0分支

1. hook的结构

首先我们看看hook是什么样的?

hook = {
    memorizedState: null, 
    next: null
}

照代码上看,hook是一个链表结构, 函数组件使用多个hook的时候, 他们的结构应该是用next一直往后拼接的.

2. hook如何存储的

众所周知,react虚拟dom是fiber结构,hooks的状态最好记录的方式就是存储到fiber上.

我们在渲染函数组件的时候,同时调用了renderWithHook()方法来初始化hook, 初始化了以下几个变量

// 当前渲染的fiber
let currentRenderingFiber = null
// 当前处理的hook
let workInProgressHook = null

function renderWithHook(fiber){
    currentRenderingFiber = fiber
    currentRenderingFiber.memorizedState = null // 当前fiber状态
    workInProgressHook = null
}

3. useReducer的实现

以useReducer为例,我们看一下react在初始化和更新的时候是如何创建和更新hook的.

首先,useReducer接收的参数是一个改变状态的reducer和一个初始状态, 返回的是最新的状态,修改状态的函数

接着,考虑fiber是初始化还是更新阶段:

  • 初始化的时候,hook的状态直接取初始状态
  • 更新阶段,hook的状态取reducer执行后的状态

最后,我们调用scheduleUpdateOnFiber(fiber)更新当前fiber,开启任务调度.

export function useReducer(reducer, initialState){
    const hook = updateWorkInProgressHook()
    // 如果fiber没有老节点 认为是初始化
    if(!currentRenderingFiber.alternate){
        hook.memorizedState = initialState
    }
     // 这里需要绑定作用域 在渲染函数组件的时候, hook和currentRenderingFiber做关联
    const dispatch = dispatchReducerAction.bind(null, currentRenderingFiber, hook, reducer )
    return [hook.memorizedState, dispatch]
}

function dispatchReducerAction(fiber, hook, reducer){
    hook.memorizedState = reducer(hook.memorizedState)
    fiber.alternate = {...fiber}
    fiber.sibling = null // 这里只提交自己不需要提交兄弟 所以把兄弟置空
    scheduleUpdateOnFiber(fiber) // 更新fiber
}

4. updateWorkInProgressHook的实现

updateWorkInProgressHook的主要工作是处理函数组件的所有hook,将他们组成链表结构.

组成链表的处理也同样需要考虑初始化和更新阶段:

  • 初始化阶段:
    • 考虑是否有当前工作的workInProgressHook, 如果没有创建头节点
    hook = {
        memorizedState: null,
        next: null
    }
    
    • 然后将hook放到当前渲染的fiber上
    workInProgressHook = currentRenderingFiber.memorizedState = hook
    
    • 接着就是next一个一个拼接
    workInProgressHook = workInProgressHook.next = hook
    
  • 更新阶段: 更新阶段考虑的方式和初始化是一样的. 不过更新阶段hook的头节点是老节点状态currentRenderingFiber.memorizedState 接着就是取workInProgressHook.next的
// 当前渲染的fiber
let currentRenderingFiber = null
// 当前处理的hook
let workInProgressHook = null

function updateWorkInProgressHook(){
    let hook; 
    const current = currentRenderingFiber.alternate 
    
    if(current){
        // 更新阶段
        currentlyRenderingFiber.memorizedState = current.memorizedState
        if(workInProgressHook){
            //workInProgressHook存在说明hook有头节点
            workInProgressHook = hook = workInProgressHook.next
        }else{
            workInProgressHook = hook = currentRenderingFiber.memorizedState
        }
    }else{
        // hook 0
        hook = {
                memorizedState: null,
                next: null
            }
        // 初次渲染
        if(workInProgressHook){
            //workInProgressHook存在说明hook有头节点
            workInProgressHook = workInProgressHook.next = hook
        }else{
            
            workInProgressHook = currentRenderingFiber.memorizedState = hook
        }
    }
}

5 其他处理

createFiber()方法里fiber需要增加一个属性alternate老节点

reconcilerChildren()需要协调老节点

function reconcilerChildren(wip, children) {
  if (isStringOrNumber(children)) return
  const newChildren = isArray(children) ? children : [children]
  let previousNewFiber = null // 记录上一次的fiber
  let oldFiber = wip.alternate?.child
  for (let i = 0; i < newChildren.length; i++) {
    const newChild = newChildren[i];
    const newFiber = createFiber(newChild, wip)
    const same = sameNode(newFiber, oldFiber)
    if(same) {
      Object.assign(newFiber, {
        stateNode: oldFiber.stateNode,
        alternate: oldFiber,
        flags: Update
      })
    }
    if(oldFiber){
      oldFiber = oldFiber.sibling;
    }
    if (i == 0) {
      wip.child = newFiber
    } else {
      previousNewFiber.sibling = newFiber
    }
    previousNewFiber = newFiber
  }
}


function sameNode(a, b) {
  return a && b && a.type == b.type && a.key == b.key
}

updateNode(node, preVal, nextVal)里需要对点击事件做一个简单的事件监听.

commitWorker()里需要增加对于flagsUpdatefiber 更新处理.

useState 已更新到 4.0 分支

下一篇: 我们看看min-react 删除更新的实现