实现useEffect

1. 实现useEffect

(1)首先,需要了解useEffect的执行时机,他执行在React对Dom渲染之后,并且浏览器渲染之前。useEffect接收两个参数,第一个为callback函数,第二个为数组。数组里可以定义绑定依赖的值。

(2)定义一个effectHook对象,将useEffect接收的属性都绑定在对象上,然后将其对象绑定到当前的fiber上。由于它的调用时机是在对dom渲染之后、浏览器渲染之前,所以应该在commitWork后也就是渲染视图之前进行统一的链表更新之后执行。

(3)定义commitEffectHooks方法,在内部定义run方法,将wipRoot也就是Dom树根节点进行传入,采用递归的形式对整个dom进行处理。首先判断初始化还是更新,当fiber的alternate不存在时就是初始化的情况,我们直接执行初始化操作fiber.effectHook.callback()即可。更新:我们则需要通过alternate获取旧节点上的oldEffectHook,通过对其内部的deps进行遍历,通过索引值判断新旧节点的deps里的依赖值是否发生更新,如果为true则执行newHook的callback函数。

(4)当出现多个调用useEffect时,我们就需要创建一个队列effectHooks,用于将effectHook进行存储,这个逻辑跟实现useState的逻辑非常相似。在处理functionComponent时对effectHooks进行初始化。那么commitEffectHooks方法的逻辑也要随之更改一些,获取队列effectHooks,对队列进行遍历,通过索引确定每一项,将其与旧节点的deps里的依赖进行对比,进行更新。

(5)代码如下:

function commitEffectHooks() {
  function run(fiber) {
    if(!fiber) return
    if(!fiber.alternate) {
      // init
      fiber.effectHooks?.forEach(hook => {
        hook.callback()
      })
    } else {
      // update
      fiber.effectHooks?.forEach((newHook,index) => {
        const oldEffectHook = fiber.alternate?.effectHooks[index]
        const needUpdate = oldEffectHook?.deps.some((oldDep,i) => {
          return oldDep !== newHook.deps[i]
        })
        needUpdate && newHook.callback()
      })      
    }

    run(fiber.child)
    run(fiber.sibling)
  }
  run(wipRoot)
}
let effectHooks 
function useEffect(callback,deps) {
  const effectHook = {
    callback,
    deps
  }
  effectHooks.push(effectHook)
  wipFiber.effectHooks = effectHooks
}

2. 实现cleanup

(1)为了清空副作用。调用时机:在调用useEffect之前进行调用的回调函数,当 deps 为空的时候不会调用返回的cleanup。并且初始化不会cleanup逻辑。

(2)首先,将cleanup添加到对象中,其次就是在调用callback函数的时候,去给cleanup进行赋值。这样就把callback返回的函数进行存储下来。

(3)由于它是在调用useEffect之前进行调用,所以我们需要在执行run方法之前进行对cleanup的处理。它的行为依然是从跟节点进行遍历查找。如果fiber存在,我们需要取出之前的effectHook的cleanup进行调用。并在最后通过递归处理链表上其fiber的child以及兄弟节点。

(4)代码如下:

function commitEffectHooks() {
  function run(fiber) {
    if(!fiber) return
    if(!fiber.alternate) {
      // init
      fiber.effectHooks?.forEach(hook => {
        hook.cleanup = hook.callback()
      })
    } else {
      // update
      fiber.effectHooks?.forEach((newHook,index) => {
        const oldEffectHook = fiber.alternate?.effectHooks[index]
        const needUpdate = oldEffectHook?.deps.some((oldDep,i) => {
          return oldDep !== newHook.deps[i]
        })
        needUpdate && (newHook.cleanup = newHook.callback())
      })      
    }

    run(fiber.child)
    run(fiber.sibling)
  }
  function runCleanup(fiber) {
    if(!fiber) return
    fiber.alternate?.effectHooks?.forEach(hook => {
      if(hook.deps.length > 0) {
        hook.cleanup && hook.cleanup()
      }
    })
    runCleanup(fiber.child)
    runCleanup(fiber.sibling)
  }
  runCleanup(wipRoot)
  run(wipRoot)
}
function Counter() {
    const [count,setCount] = React.useState(20)
    const [bar,setBar] = React.useState('bar')
    function handClick() {
        setCount((c) => {
            return c+1
        })
        setBar("barbar")
    }
    // useEffect:调用时机在React完成对Dom渲染之后,并且浏览器完成绘制之前
    // cleanup: 在调用useEffect之前进行调用,当 deps 为空的时候不会调用返回的cleanup
    React.useEffect(() => {
        console.log('init');
        return () => {
            console.log('cleanup 0');
        }
    },[])
    React.useEffect(() => {
        console.log('update',count);
        return () => {
            console.log('cleanup 1');
        }
    },[count])
    React.useEffect(() => {
        console.log('update',count);
        return () => {
            console.log('cleanup 2');
        }
    },[count])
    return (
        <div>
            count:{count}
            <div>
                {bar}
            </div>
            <button onClick={ handClick }>点击</button>
        </div>
    )
}

3. 迷你react的实现版就到此结束了,大家可以尝试自己写一个属于自己的mini-react,整个过程强调的还是多学习一些代码编写的思想,我觉得很关键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值