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

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

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

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

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

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

第六天:搞定 useState

实现 useState

我们先写一个demo

jsx 复制代码
import React from "./core/React.js"
function Foo() {
  const [count, setCount] = React.useState(10)
  function handleClick() {
    setCount(pre => pre + 2)
  }
  return (
    <div>
      <h1>Foo : {count}</h1>
      <button onClick={handleClick}>click</button>
    </div>
  )
}
function App() {
  return (
    <div>
      <h1>App</h1>
      <Foo></Foo>
    </div>
  )
}

export default App

这里的话,我们先实现通过函数去实现数据更新

js 复制代码
function useState(initial) {
  let currentFiber = wipFiber
  let oldHook = currentFiber.alternate?.stateHook

  const stateHook = {
    state: oldHook ? oldHook.state : initial,
  }

  currentFiber.stateHook = stateHook

  function setState(action) {

    stateHook.state = action(stateHook.state)

    wipRoot = {
      ...currentFiber,
      alternate: currentFiber,
    }

    nextWorkOfUnit = wipRoot
  }

  return [stateHook.state, setState]
}

在函数内部,首先获取当前的Fiber节点currentFiber,然后尝试获取之前的钩子状态oldHook,如果存在的话。接着创建一个stateHook对象,其中的state属性被初始化为之前的状态或者初始值initial

然后将stateHook对象赋值给currentFiberstateHook属性。接下来定义了setState函数,它接受一个action作为参数,这个action是一个函数,用于根据当前状态计算新的状态。在setState函数内部,就是之前的update函数了。

最后,useState函数返回一个数组,其中第一个元素是状态的当前值,第二个元素是setState函数,用于更新状态。

我们可以看到,确实更新了

但是呢,如果我们写了多个useState,就会出现问题,因为我们的oldHook是一个变量,所以我们需要用数组来存储

jsx 复制代码
import React from "./core/React.js"
function Foo() {
  const [count, setCount] = React.useState(10)
  const [bar, setBar] = React.useState("bar")
  function handleClick() {
    setCount(pre => pre + 2)
    setBar(pre => pre + "bar")
  }
  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
js 复制代码
let stateHooks
let stateHookIndex
function useState(initial) {
  let currentFiber = wipFiber
  let oldHook = currentFiber.alternate?.stateHooks[stateHookIndex]

  const stateHook = {
    state: oldHook ? oldHook.state : initial,
  }
  stateHookIndex++
  stateHooks.push(stateHook)
  currentFiber.stateHooks = stateHooks

  function setState(action) {
    stateHook.state = action(stateHook.state)

    wipRoot = {
      ...currentFiber,
      alternate: currentFiber,
    }

    nextWorkOfUnit = wipRoot
  }

  return [stateHook.state, setState]
}

我们这里通过设置stateHooks变量去存储stateHook,并且设置stateHookIndex索引来获取老的值,这样就不会影响下次更新了,这也是为什么useState必须写在顶层,不能用if语句去包裹的原因,

这里需要注意的是,每次更新后,需要把值清空

js 复制代码
function updateFunctionComponent(fiber) {
  stateHooks = []
  stateHookIndex = 0
  wipFiber = fiber
  const children = [fiber.type(fiber.props)]

  reconcileChildren(fiber, children)
}

这样一来我们就已经完成了useState

批量执行 action

上一节我们写的方法,其实是每次触发useStateaction的时候,都会更新一下视图,这样是不太好的,会造成性能上的浪费,所以,这一节我们来实现一下useState的批处理

js 复制代码
let stateHooks
let stateHookIndex
function useState(initial) {
  let currentFiber = wipFiber
  let oldHook = currentFiber.alternate?.stateHooks[stateHookIndex]

  const stateHook = {
    state: oldHook ? oldHook.state : initial,
    queue: oldHook ? oldHook.queue : [],
  }
  // 调用action
  stateHook.queue.forEach(action => {
    stateHook.state = action(stateHook.state)
  })
  stateHook.queue = []

  stateHookIndex++
  stateHooks.push(stateHook)
  currentFiber.stateHooks = stateHooks

  function setState(action) {
    stateHook.queue.push(typeof action === "function" ? action : () => action)
    // stateHook.state = action(stateHook.state)

    wipRoot = {
      ...currentFiber,
      alternate: currentFiber,
    }

    nextWorkOfUnit = wipRoot
  }

  return [stateHook.state, setState]
}

这里我们加入一个queue来存储action,并循环去执行action,这样就实现了把多次action的操作,转化成一次去执行。

我们还去判断了一下action的类型,如果不是函数,那么我们就包装成一个函数,这样我们就实现了直接输入值的情况。

提前检测-减少不必要的更新

当值没有发生改变的时候,我们应该不需要去更新组件

js 复制代码
import React from "./core/React.js"
function Foo() {
  const [count, setCount] = React.useState(10)
  const [bar, setBar] = React.useState("bar")
  function handleClick() {
    setBar(pre => "bar")
  }
  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

我们只需要去判断一下值是否相等就行了!!!

js 复制代码
let stateHooks
let stateHookIndex
function useState(initial) {
  let currentFiber = wipFiber
  let oldHook = currentFiber.alternate?.stateHooks[stateHookIndex]

  const stateHook = {
    state: oldHook ? oldHook.state : initial,
    queue: oldHook ? oldHook.queue : [],
  }
  // 调用action
  stateHook.queue.forEach(action => {
    stateHook.state = action(stateHook.state)
  })
  stateHook.queue = []

  stateHookIndex++
  stateHooks.push(stateHook)
  currentFiber.stateHooks = stateHooks

  function setState(action) {
    // 处理值一样的情况
    const eagerState = typeof action === "function" ? action(stateHook.state) : action
    if (eagerState === stateHook.state) return

    stateHook.queue.push(typeof action === "function" ? action : () => action)
    // stateHook.state = action(stateHook.state)

    wipRoot = {
      ...currentFiber,
      alternate: currentFiber,
    }

    nextWorkOfUnit = wipRoot
  }

  return [stateHook.state, setState]
}

到目前为止,我们已经完成了useState的方法了,下一期将进入useEffect的学习

相关推荐
飘尘12 分钟前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆21 分钟前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
YFF菲菲兔1 小时前
调度系统和调和系统的桥梁
react.js
浏览器工程师1 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆1 小时前
VSCode自动格式化三要素
前端
爱勇宝2 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen3 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518135 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode5 小时前
Redis 在生产项目的使用
前端·后端
LiaCode5 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端