作者:京东批发 郑炳懿
前言
React Hooks
是React
16.8 引入的一个新个性,它容许函数组件中应用state
和其余 React 个性,而不用应用类组件。Hooks
是一个十分重要的概念,因为它们提供了更简略、更易于了解的React
开发体验。
React Hooks
的外围源码次要包含两个局部:React
外部的Hook
管理器和一系列预置的Hook
函数。
首先,让咱们看一下React
外部的Hook
管理器。这个管理器是React
外部的一个重要机制,它负责管理组件中的所有Hook
,并确保它们在组件渲染期间以正确的顺序调用。
外部Hook管理器
示例:
const Hook = { queue: [], current: null,};function useState(initialState) { const state = Hook.current[Hook.queue.length]; if (!state) { Hook.queue.push({ state: typeof initialState === 'function' ? initialState() : initialState, setState(value) { this.state = value; render(); }, }); } return [state.state, state.setState.bind(state)];}function useHook(callback) { Hook.current = { __proto__: Hook.current, }; try { callback(); } finally { Hook.current = Hook.current.__proto__; }}function render() { useHook(() => { const [count, setCount] = useState(0); console.log('count:', count); setTimeout(() => { setCount(count + 1); }, 1000); });}render();
在这个示例中,Hook
对象有两个重要属性:queue
和current
。queue
存储组件中所有Hook
的状态和更新函数,current
存储以后正在渲染的组件的Hook
链表。useState
和useHook
函数则别离负责创立新的Hook
状态和在组件中应用Hook
。
预置 Hook 函数
useState Hook
以下是useState Hook
的实现示例:
function useState(initialState) { const hook = updateWorkInProgressHook(); if (!hook.memoizedState) { hook.memoizedState = [ typeof initialState === 'function' ? initialState() : initialState, action => { hook.queue.pending = true; hook.queue.dispatch = action; scheduleWork(); }, ]; } return hook.memoizedState;}
上述代码实现了useState Hook
,其次要作用是返回一个state
和更新函数的数组,state 初始值为initialState
。
在这个实现中,updateWorkInProgressHook()
函数用来获取以后正在执行的函数组件的 fiber 对象并判断是否存在对应的hook
。它的实现如下:
function updateWorkInProgressHook() { const fiber = getWorkInProgressFiber(); let hook = fiber.memoizedState; if (hook) { fiber.memoizedState = hook.next; hook.next = null; } else { hook = { memoizedState: null, queue: { pending: null, dispatch: null, last: null, }, next: null, }; } workInProgressHook = hook; return hook;}
getWorkInProgressFiber()
函数用来获取以后正在执行的函数组件的fiber
对象,workInProgressHook
则用来存储以后正在执行的hook
对象。在函数组件中,每一个useState
调用都会创立一个新的 hook 对象,并将其增加到fiber
对象的hooks
链表中。这个hooks
链表是通过fiber
对象的memoizedState
属性来保护的。
咱们还须要留神到在useState Hook
的实现中,每一个hook
对象都蕴含了一个queue
对象,用来存储待更新的状态以及更新函数。scheduleWork()
函数则用来告诉React
调度器有工作须要执行。
在React
的源码中,useState
函数实际上是一个叫做useStateImpl
的外部函数。
上面是useStateImpl
的源码:
function useStateImpl<S>(initialState: (() => S) | S): [S, Dispatch<SetStateAction<S>>] { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState);}
能够看到,useStateImpl
函数的作用就是获取以后的dispatcher
并调用它的useState
办法,返回一个数组,第一个元素是状态的值,第二个元素是一个dispatch
函数,用来更新状态。这里的resolveDispatcher
函数用来获取以后的dispatcher
,其实现如下:
function resolveDispatcher(): Dispatcher { const dispatcher = currentlyRenderingFiber?.dispatcher; if (dispatcher === undefined) { throw new Error('Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)'); } return dispatcher;}
resolveDispatcher
函数首先尝试获取以后正在渲染的fiber
对象的dispatcher
属性,如果获取不到则说
明以后不在组件的渲染过程中,就会抛出一个谬误。
最初,咱们来看一下useState
办法在具体的dispatcher
实现中是如何实现的。咱们以useReducer
的
dispatcher
为例,它的实现如下:
export function useReducer<S, A>( reducer: (prevState: S, action: A) => S, initialState: S, initialAction?: A,): [S, Dispatch<A>] { const [dispatch, currentState] = updateReducer<S, A>( reducer, // $FlowFixMe: Flow doesn't like mixed types [initialState, initialAction], // $FlowFixMe: Flow doesn't like mixed types reducer === basicStateReducer ? basicStateReducer : updateStateReducer, ); return [currentState, dispatch];}
能够看到,useReducer
办法实际上是调用了一个叫做updateReducer
的函数,返回了一个蕴含以后状态和dispatch
函数的数组。updateReducer
的实现比较复杂,波及到了很多细节,这里不再开展介绍。
useEffect Hook
useEffect
是React
中罕用的一个Hook
函数,用于在组件中执行副作用操作,例如拜访近程数据、增加/移除事件监听器、手动操作DOM
等等。useEffect
的外围性能是在组件的渲染过程完结之后异步执行回调函数,它的实现形式波及到 React 中的异步渲染机制。
以下是useEffect Hook的实现示例:
function useEffect(callback, dependencies) { // 通过调用 useLayoutEffect 或者 useEffect 办法来获取以后的渲染批次 const batch = useContext(BatchContext); // 依据以后的渲染批次判断是否须要执行回调函数 if (shouldFireEffect(batch, dependencies)) { callback(); } // 在组件被卸载时革除以后 effect 的状态信息 return () => clearEffect(batch);}
在这个示例中,useEffect
接管两个参数:回调函数和依赖项数组。当依赖项数组中的任何一个值发生变化时,
React
会在下一次渲染时从新执行useEffect
中传入的回调函数。
useEffect
函数的实现形式次要依赖于React
中的异步渲染机制。当一个组件须要从新渲染时,React
会将所有的state
更新操作退出到一个队列中,在以后渲染批次完结之后再异步执行这些更新操作,从而防止在同一个渲染批次中间断执行屡次更新操作。
在useEffect
函数中,咱们通过调用useContext(BatchContext)
办法来获取以后的渲染批次,并依据shouldFireEffect
办法判断是否须要执行回调函数。在回调函数执行结束后,咱们须要通过clearEffect
办法来革除以后effect
的状态信息,防止对后续的渲染批次产生影响。
总结
总的来说,React Hooks
的实现原理并不简单,它次要依赖于React
外部的fiber
数据结构和调度零碎,通过这些机制来实现对组件状态的治理和更新。Hooks
可能让咱们在函数组件中应用状态和其余React
个性,使得函数组件的性能能够和类组件媲美。
除了useState
、useEffect
等hook
,React
还有useContext
等罕用的Hook
。它们的实现原理也根本类似,都是利用fiber
架构来实现状态治理和生命周期钩子等性能。
以上是hook
简略实现示例,它们并不是React
中理论应用的代码,然而能够帮忙咱们更好地了解hook
的外围实现形式。