本文实现包含:
✅ 可工作的useState
/useEffect
✅ 依赖收集系统
✅ 批量更新机制
✅ 闭包陷阱深度分析
一、前置概念:Hooks的运作基石
1.1 函数组件的执行特点
function MyComponent() {
const [count, setCount] = useState(0) // 如何保持状态?
// 每次渲染都是全新执行上下文
}
1.2 核心设计约束
- 调用顺序稳定性:Hooks必须按固定顺序执行
- 状态隔离性:多个组件实例互不影响
- 副作用调度:精准控制DOM操作时机
二、极简Hooks实现(完整可运行)
2.1 全局调度器(模拟React Reconciler)
// 当前渲染的组件实例
let currentInstance = null
// 状态存储结构
const instances = new Map()
class Dispatcher {
constructor() {
this.hookIndex = 0
this.effectQueue = []
}
// 组件挂载入口
render(Component) {
const instanceId = Symbol('instance')
currentInstance = {
id: instanceId,
hooks: [],
dispatcher: this
}
instances.set(instanceId, currentInstance)
const vdom = Component()
this.flushEffects()
currentInstance = null
this.hookIndex = 0
return vdom
}
flushEffects() {
this.effectQueue.forEach(effect => effect())
this.effectQueue = []
}
}
2.2 实现useState
function createUseState(initialState) {
return () => {
const dispatcher = currentInstance.dispatcher
const index = dispatcher.hookIndex++
const hooks = currentInstance.hooks
if (index >= hooks.length) {
hooks.push({
state: typeof initialState === 'function'
? initialState()
: initialState,
queue: []
})
}
const hook = hooks[index]
// 批量更新处理
if (hook.queue.length > 0) {
hook.state = hook.queue.reduce((state, action) => {
return typeof action === 'function'
? action(state)
: action
}, hook.state)
hook.queue = []
}
const setState = (action) => {
hook.queue.push(action)
// 触发重新渲染
dispatcher.render(currentInstance.Component)
}
return [hook.state, setState]
}
}
const useState = createUseState()
2.3 实现useEffect
function useEffect(effectFn, deps) {
const dispatcher = currentInstance.dispatcher
const index = dispatcher.hookIndex++
const hooks = currentInstance.hooks
const hook = hooks[index] || { deps: undefined }
const hasChanged = !deps ||
!hook.deps ||
deps.some((dep, i) => !Object.is(dep, hook.deps[i]))
if (hasChanged) {
hook.cleanup?.()
hook.cleanup = effectFn()
hook.deps = deps
}
dispatcher.effectQueue.push(hook.cleanup)
dispatcher.hookIndex++
}
三、关键性能陷阱解析
3.1 闭包陷阱(Stale Closure)
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
console.log(count) // 永远打印初始值
}, 1000)
return () => clearInterval(timer)
}, []) // 空依赖
return <button onClick={() => setCount(c => c+1)}>
{count}
</button>
}
解决方案:
// 正确写法:使用函数式更新
setCount(prev => prev + 1)
// 或者添加count到依赖数组
useEffect(() => {...}, [count])
3.2 无效渲染(Zombie Children)
function Parent() {
const [show, setShow] = useState(true)
return (
<div>
<button onClick={() => setShow(false)}>
Remove
</button>
{show && <Child />}
</div>
)
}
function Child() {
const [data, setData] = useState(null)
useEffect(() => {
fetchData().then(res => {
setData(res) // 组件卸载后仍可能执行
})
}, [])
return <div>{data}</div>
}
解决方案:
useEffect(() => {
let isMounted = true
fetchData().then(res => {
if (isMounted) setData(res)
})
return () => { isMounted = false }
}, [])
3.3 依赖数组误用
const [data, setData] = useState([])
// 反模式:缺少data依赖
useEffect(() => {
localStorage.setItem('data', JSON.stringify(data))
}, [])
// 正确写法:
useEffect(() => {
localStorage.setItem('data', JSON.stringify(data))
}, [data])
四、性能优化方案
4.1 批量更新优化
// 原生React行为
const onClick = () => {
setCount(1)
setFlag(true)
// 仅触发一次渲染
}
// 我们的实现需添加批处理:
function batchedUpdates(fn) {
isBatchingUpdates = true
fn()
isBatchingUpdates = false
if (!isBatchingUpdates) {
dispatcher.render(currentInstance.Component)
}
}
4.2 状态冻结技术
function useImmutableState(initialState) {
const [state, setState] = useState(
Object.freeze(initialState)
)
const updateState = (newState) => {
setState(prev =>
Object.freeze(
typeof newState === 'function'
? newState(prev)
: newState
)
)
}
return [state, updateState]
}
五、与React官方实现的差异
特性 | 本文实现 | React官方 |
---|---|---|
状态存储 | 数组+闭包 | 链表结构 |
更新调度 | 同步渲染 | 优先级调度 |
生命周期 | 简单Effect队列 | 完整调度阶段 |
DevTools支持 | 无 | 完整调试信息 |