用法
const [count, setCount] = useState(0)
const handleClick = () => {
setCount((count) => count + 1)
}
实现:
- 要返回一个数组,第一个是当前的 state,第二个是修改 state 的方法
const useState = (inital) => {
const stateHook = {
state: inital
}
const setState = (action) => {
stateHook.state = action(stateHook.state)
}
return [stateHook.state, setState]
}
- 更新页面使用直接的 update 里的逻辑修改 wipRoot
let currentFiber = wipFiber
const setState = (callback) => {
stateHook.state = callback(stateHook.state)
wipRoot = {
...currentFiber,
alternate: currentFiber
}
nextWorkOfUnit = wipRoot
}
- 因为每次我们更新页面我们的state 的值都会重置所以我们需要存储我们的 state 的值,存在我们之前的 fiber 上
const useState = (state) => {
let currentFiber = wipFiber
const oldHook = currentFiber.alternate?.stateHook
const stateHook = {
state: oldHook ? oldHook.state : state
}
currentFiber.stateHook = stateHook
const setState = (callback) => {
stateHook.state = callback(stateHook.state)
wipRoot = {
...currentFiber,
alternate: currentFiber
}
nextWorkOfUnit = wipRoot
}
return [stateHook.state, setState]
}
- 实现多个 useState
上面的 useState 如果我们页面里有多个后面的会把前面的覆盖
- demo
const Foo = () => {
console.log('foo render')
const [count, setCount] = React.useState(0)
const [age, setAge] = React.useState(1)
const handleClick = () => {
setCount((count) => count + 1)
setAge((age) => age + 2)
}
return (
<div>
<h1>foo</h1>
{count}
{age}
<button onClick={handleClick}>click</button>
</div>
)
}
点击都会每次 +2
解决方式:使用一个数组和索引来对应关系
每次updateFunctionComponent 的时候初始化数组和索引值
let stateHooks
let stateHooksIndex
const updateFunctionComponent = (fiber) => {
+ stateHooks = []
+ stateHooksIndex = 0
wipFiber = fiber
const children = [fiber.type(fiber.props)]
initChildren(fiber, children)
}
const useState = (inital) => {
let currentFiber = wipFiber
const oldHook = currentFiber.alternate?.stateHooks[stateHooksIndex]
const stateHook = {
state: oldHook ? oldHook.state : inital
}
stateHooks.push(stateHook)
currentFiber.stateHooks = stateHooks
const setState = (action) => {
stateHook.state = action(stateHook.state)
wipRoot = {
...currentFiber,
alternate: currentFiber
}
nextWorkOfUnit = wipRoot
}
stateHooksIndex++
return [stateHook.state, setState]
}
- 批量处理 action
const useState = (inital) => {
let currentFiber = wipFiber
const oldHook = currentFiber.alternate?.stateHooks[stateHooksIndex]
const stateHook = {
state: oldHook ? oldHook.state : inital,
queue: oldHook? oldHook.queue : []
}
stateHook.queue.forEach((action) => stateHook.state = action(stateHook.state))
stateHook.queue = []
stateHooks.push(stateHook)
currentFiber.stateHooks = stateHooks
const setState = (action) => {
stateHook.queue.push(action)
wipRoot = {
...currentFiber,
alternate: currentFiber
}
nextWorkOfUnit = wipRoot
}
stateHooksIndex++
return [stateHook.state, setState]
}
每次steState 不会直接修改,是在dom重新渲染后才修改
- 支持直接传入值
setAge(4)
const setState = (action) => {
+ stateHook.queue.push(typeof action === 'function' ? action : () => action)
...
}
- 减少不必要的更新
我们上面的代码不管我们后面修改的 state和之前一不一样都不重新 render
const setState = (action) => {
const eagerState = typeof action === 'function' ? action(stateHook.state) : action
if (eagerState === stateHook.state) {
return
}
....
}