# 七天快速学完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对象赋值给currentFiber的stateHook属性。接下来定义了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
上一节我们写的方法,其实是每次触发useState的action的时候,都会更新一下视图,这样是不太好的,会造成性能上的浪费,所以,这一节我们来实现一下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的学习