目录

七天快速学完mini-react ,再也不担心不会原理了(第七天)

# 七天快速学完mini-react ,再也不担心不会原理了(第一天)

# 七天快速学完mini-react ,再也不担心不会原理了(第二天)

# 七天快速学完mini-react ,再也不担心不会原理了(第三天)

# 七天快速学完mini-react ,再也不担心不会原理了(第四天)

# 七天快速学完mini-react ,再也不担心不会原理了(第五天)

# 七天快速学完mini-react ,再也不担心不会原理了(第六天)

第七天:搞定 useEffect

实现 useEffect

我们先来看看怎么使用

js 复制代码
// useEffect
// 调用时机是在 React 完成对 DOM 的渲染之后,并且在浏览器完成绘制之前
useEffect(() => {
    console.log("init")
}, [])

useEffect(() => {
    console.log("init")
}, [count])

useEffect 接收两个参数,一个callback,和一个deps,当deps是空的时候,相当于初始化,如果有依赖项,会在依赖项发生变化的时候再次调用一次

接下来我们先试试怎么实现

jsx 复制代码
import React from "./core/React.js"

// useEffect
// 调用时机是在 React 完成对 DOM 的渲染之后,并且在浏览器完成绘制之前

function Foo() {
  const [count, setCount] = React.useState(10)
  const [bar, setBar] = React.useState("bar")
  function handleClick() {
    setCount(c => c + 1)
    setBar(() => "bar")
  }

  React.useEffect(() => {
    console.log("init")
  }, [])

  return (
    <div>
      <h1>Foo : {count}</h1>
      <div>{bar}</div>
      <button onClick={handleClick}>click</button>
    </div>
  )
}
function App() {
  return (
    <div>
      <h1>App</h1>
      <Foo></Foo>
    </div>
  )
}

export default App

我们来创建一个useEffect函数,并导出

这里的话,我们还是跟useState一样,我们定义一个effectHook,把它存在我们的Fiber节点中

js 复制代码
function useEffect(callback, deps) {
  const effectHook = {
    callback,
    deps,
  }

  wipFiber.effectHook = effectHook
}

const React = {
  update,
  render,
  createElement,
  useState,
  useEffect,
}

然后我们应该在那去调用呢,看看调用时机,时机应该在 React 完成对 DOM 的渲染之后

所以我们应该在commitWork调用完再去调用,我们写一个方法commitEffectHook,然后调用它,这里因为需要处理子节点和兄弟节点,所以我们需要递归去调用它

js 复制代码
function commitRoot() {
  deletions.forEach(commitDeletion)
  commitWork(wipRoot.child)
  commitEffectHook()
  currentRoot = wipRoot
  wipRoot = null
  deletions = []
}
function commitEffectHook() {
  function run(fiber) {
    if (!fiber) return
    fiber.effectHook?.callback()
    run(fiber.child)
    run(fiber.sibling)
  }
  run(wipRoot)
}

运行我们看一下

可以看到,确实执行了,接下来我们加上依赖项

js 复制代码
  React.useEffect(() => {
    console.log("init")
  }, [count])

这里我们先判断是不是初始化还是update,可以通过之前的alternate字段来判断,有值的话就是update,在更新的时候,我们需要判断deps有没有更新,有更新的话,我们才去执行callback

js 复制代码
function commitEffectHook() {
  function run(fiber) {
    if (!fiber) return
    if (!fiber.alternate) {
      // 初始化
      fiber.effectHook?.callback()
    } else {
      // update  需要去检测deps有没有更新
      const oldEffectHook = fiber.alternate?.effectHook

      const needUpdate = oldEffectHook?.deps.some((oldDep, index) => {
        return oldDep !== fiber.effectHook?.deps[index]
      })

      if (needUpdate) {
        fiber.effectHook?.callback()
      }
    }
    run(fiber.child)
    run(fiber.sibling)
  }
  run(wipRoot)
}

我们来试试效果

确实可以正常执行了,那如果有多个useEffect怎么处理呢

jsx 复制代码
  React.useEffect(() => {
    console.log("init")
  }, [])

  React.useEffect(() => {
    console.log("update", count)
  }, [count])

先看看实现,定义一个effectHooks去存多个useEffect,然后放到effectHooks这个属性上,初始化的时候,应该是在初始化functionComponent上的,所以我们也加一下;然后就是处理内部了,循环effectHooks去执行里面的callback,这个流程跟useState的处理很类似

js 复制代码
let effectHooks
function useEffect(callback, deps) {
  const effectHook = {
    callback,
    deps,
  }
  effectHooks.push(effectHook)
  wipFiber.effectHooks = effectHooks
}

function updateFunctionComponent(fiber) {
  stateHooks = []
  effectHooks = []
  stateHookIndex = 0
  wipFiber = fiber
  const children = [fiber.type(fiber.props)]

  reconcileChildren(fiber, children)
}

function commitEffectHook() {
  function run(fiber) {
    if (!fiber) return
    if (!fiber.alternate) {
      // 初始化
      fiber.effectHooks?.forEach(hook => hook?.callback())
    } else {
      // update  需要去检测deps有没有更新

      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)
}

之后我们试试效果怎么样?

可以看到,点击的时候只触发了updatecallback

最终代码,我们就加了个判断,当deps不为空的时候再去执行比较

js 复制代码
function commitEffectHook() {
  function run(fiber) {
    if (!fiber) return
    if (!fiber.alternate) {
      // 初始化
      fiber.effectHooks?.forEach(hook => hook?.callback())
    } else {
      // update  需要去检测deps有没有更新

      fiber.effectHooks?.forEach((newHook, index) => {
        if (newHook.deps.length > 0) {
          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)
}

实现 cleanup

首先我们来了解一下cleanUp的机制

cleanUp 函数会在组件卸载的时候执行 在调用useEffect之前进行调用 ,当deps 为空的时候不会调用返回的cleanUp

我写了一个demo文件,我们可以看看它应该如何打印呢

  1. deps为空的时候,它的cleanUp是不会调用的
  2. deps不为空的时候,执行下一次的useEffect的时候之前会先执行一下cleanUp函数
jsx 复制代码
import React from "./core/React.js"

// useEffect
// 调用时机是在 React 完成对 DOM 的渲染之后,并且在浏览器完成绘制之前
// cleanUp 函数会在组件卸载的时候执行 在调用useEffect之前进行调用 ,当deps 为空的时候不会调用返回的cleanup

function Foo() {
  const [count, setCount] = React.useState(10)
  const [bar, setBar] = React.useState("bar")
  function handleClick() {
    setCount(c => c + 1)
    setBar(() => "bar")
  }
  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>
      <h1>Foo : {count}</h1>
      <div>{bar}</div>
      <button onClick={handleClick}>click</button>
    </div>
  )
}
function App() {
  return (
    <div>
      <h1>App</h1>
      <Foo></Foo>
    </div>
  )
}

export default App

实现:

首先我们存一个cleanUp属性,然后我们去执行hookcallback的时候,需要把结果放在hookcleanUp属性上,接下来我们就可以去执行了;

我们先创建一个方法,跟run类似,我们叫做runCleanUp吧,注意我们这里只需要当depslength大于0的时候才去执行

js 复制代码
function useEffect(callback, deps) {
  const effectHook = {
    callback,
    deps,
    cleanUp: undefined,
  }
  effectHooks.push(effectHook)
  wipFiber.effectHooks = effectHooks
}

function commitEffectHook() {
  function run(fiber) {
    if (!fiber) return
    if (!fiber.alternate) {
      // 初始化
      fiber.effectHooks?.forEach(hook => {
        hook.cleanUp = hook?.callback()
      })
    } else {
      // update  需要去检测deps有没有更新

      fiber.effectHooks?.forEach((newHook, index) => {
        if (newHook.deps.length > 0) {
          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)
}

我们来看看页面效果

可以看到,deps为空的时候不会调用cleanUp函数了,到目前为止,我们就已经完成所有的React任务,后面的就是用我们写的React源码去实战一个todoList

等待下次更新吧,xdm~~~

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
—Qeyser32 分钟前
用 Deepseek 写的uniapp血型遗传查询工具
前端·javascript·ai·chatgpt·uni-app·deepseek
codingandsleeping33 分钟前
HTTP1.0、1.1、2.0 的区别
前端·网络协议·http
小满blue35 分钟前
uniapp实现目录树效果,异步加载数据
前端·uni-app
喜樂的CC2 小时前
[react]Next.js之自适应布局和高清屏幕适配解决方案
javascript·react.js·postcss
天天扭码2 小时前
零基础 | 入门前端必备技巧——使用 DOM 操作插入 HTML 元素
前端·javascript·dom
咖啡虫3 小时前
css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
前端·css·3d
拉不动的猪3 小时前
设计模式之------策略模式
前端·javascript·面试
旭久3 小时前
react+Tesseract.js实现前端拍照获取/选择文件等文字识别OCR
前端·javascript·react.js
独行soc3 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf
uhakadotcom3 小时前
Google Earth Engine 机器学习入门:基础知识与实用示例详解
前端·javascript·面试