一. useCallback
方法介绍
useCallback
方法接收两个参数,第一个参数是cache function
,第二个参数是依赖deps
,首次渲染调用会直接返回cache function
,更新渲染会比对deps
是否相同,不相同返回新的cache function
function App() {
const handleClick = useCallback(() => {}, [])
return <h1 onClick={handleClick}>click</h1>
}
二. 实现useCallback
2.1 定义Hook
对象原型
每次调用React Hook
方法都会创建一个Hook
对象,多个Hook
对象之间通过next
指针进行索引,构成单链表数据结构
function Hook() {
this.memoizedState = null // 记录Hook数据
this.next = null // 记录下一个Hook对象
}
2.2 定义函数组件方法调用装饰器
在构建虚拟DOM
树阶段,每次调用函数组件方法(例如App Component Function
)时会执行renderWithHooks
方法,记录新FiberNode
节点,在调用React Hook
方法时会用到。
// 记录新FiberNode节点
let currentlyRenderingFiber = null
// 记录旧Hook对象
let currentHook = null
// 记录新Hook对象
let workInProgressHook = null
/**
* @param {*} workInProgress 新FiberNode节点
* @param {*} Component 函数组件方法
* @param {*} props 函数组件方法入参属性
*/
export function renderWithHooks(workInProgress, Component, props) {
// 记录新FiberNode节点
currentlyRenderingFiber = workInProgress
// 将FiberNode节点的updateQueue属性赋值为null,重新收集useEffect、useLayoutEffect、useInsertionEffect数据
workInProgress.updateQueue = null
// 调用组件方法获取child ReactElement
const children = Component(props)
currentlyRenderingFiber = null
currentHook = null
workInProgressHook = null
return children
}
2.3 首次调用useCallback
当首次执行组件方法调用useCallback
方法时,执行mountCallback
方法逻辑
- 创建
Hook
对象,构建Hook
链表 - 将
callback
和deps
保存到Hook
对象的memoizedState
属性 - 返回
callback
function mountWorkInProgressHook() {
// 创建Hook对象
const hook = new Hook()
// 构建Hook链表
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook
} else {
workInProgressHook = workInProgressHook.next = hook
}
return hook
}
function mountCallback(callback, deps) {
// 创建Hook对象,构建Hook单链表
const hook = mountWorkInProgressHook()
// 将callback和deps保存到Hook对象的memoizedState属性
hook.memoizedState = [callback, deps]
return callback
}
2.4 更新调用useCallback
当触发更新渲染重新执行组件方法调用useCallback
方法时,执行updateCallback
方法逻辑
- 创建
Hook
对象,复制旧Hook
对象属性值,构建Hook
链表 - 获取旧函数和依赖
deps
,比对新旧deps
是否相同,相同则返回旧函数,不相同则返回新函数 - 将新函数和
deps
保存到Hook
对象的memoizedState
属性
function updateWorkInProgressHook() {
// 获取旧Hook对象
if (currentHook === null) {
currentHook = currentlyRenderingFiber.alternate.memoizedState
} else {
currentHook = currentHook.next
}
// 创建新Hook对象,复制旧Hook对象属性值
const hook = new Hook()
hook.memoizedState = currentHook.memoizedState
// 构建Hook链表
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook
} else {
workInProgressHook = workInProgressHook.next = hook
}
return hook
}
// 通过Object.is方法比对deps属性值是否变更
function areHookInputsEqual(nextDeps, prevDeps) {
for (let i = 0; i < nextDeps.length; i++) {
if (!Object.is(nextDeps[i], prevDeps[i])) {
return false
}
}
return true
}
function updateCallback(callback, deps) {
// 创建Hook对象,复制旧Hook对象属性值,构建Hook链表
const hook = updateWorkInProgressHook()
// 获取旧函数和依赖deps
const prevState = hook.memoizedState
// 比对新旧deps是否相同,相同则直接返回旧值
if (deps !== null && areHookInputsEqual(deps, prevState[1])) {
return prevState[0]
}
// 将新函数和deps保存到Hook对象的memoizedState属性
hook.memoizedState = [callback, deps]
return callback
}
2.5 定义useCallback
方法
如果新节点不存在旧FiberNode
节点,说明是首次调用函数组件方法,则调用mountCallback
方法,否则调用updateCallback
方法
function useCallback(callback, deps = null) {
const current = currentlyRenderingFiber.alternate
if (current === null) {
return mountCallback(callback, deps)
} else {
return updateCallback(callback, deps)
}
}