【React】709- React Hook 的底层实现原理
共 7742字,需浏览 16分钟
·
2020-09-08 09:38
原文链接:
https://medium.com/the-guild/under-the-hood-of-reacts-hooks-system-eb59638c9dba
首先,让我们进入需要确保hooks在React的作用域调用的机制,因为你现在可能知道如果在没有正确的上下文调用钩子是没有意义的:
The dispatcher
let currentDispatcher
const dispatcherWithoutHooks = { /* ... */ }
const dispatcherWithHooks = { /* ... */ }
function resolveDispatcher() {
if (currentDispatcher) return currentDispatcher
throw Error("Hooks can't be called")
}
function useXXX(...args) {
const dispatcher = resolveDispatcher()
return dispatcher.useXXX(...args)
}
function renderRoot() {
currentDispatcher = enableHooks ? dispatcherWithHooks : dispatcherWithoutHooks
performWork()
currentDispatcher = null
}
到此为止既然我们已经看过了这种简单的封装机制,我希望我们转到本文的核心 - Hooks。我想向您介绍一个新概念:
The hooks queue
它的初始状态在首次渲染时被创建。 她的状态可以即时更新。 React会在之后的渲染中记住hook的状态 React会根据调用顺序为您提供正确的状态 React会知道这个hook属于哪个Fiber。
{
foo: 'foo',
bar: 'bar',
baz: 'baz',
}
{
memoizedState: 'foo',
next: {
memoizedState: 'bar',
next: {
memoizedState: 'bar',
next: null
}
}
}
· baseState - 将给予reducer的状态对象。
· baseUpdate- 最近的创建了最新baseState的调度操作。
· queue - 调度操作的队列,等待进入reducer。
let currentlyRenderingFiber
let workInProgressQueue
let currentHook
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:123
function prepareHooks(recentFiber) {
currentlyRenderingFiber = workInProgressFiber
currentHook = recentFiber.memoizedState
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:148
function finishHooks() {
currentlyRenderingFiber.memoizedState = workInProgressHook
currentlyRenderingFiber = null
workInProgressHook = null
currentHook = null
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:115
function resolveCurrentlyRenderingFiber() {
if (currentlyRenderingFiber) return currentlyRenderingFiber
throw Error("Hooks can't be called")
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:267
function createWorkInProgressHook() {
workInProgressHook = currentHook ? cloneHook(currentHook) : createNewHook()
currentHook = currentHook.next
workInProgressHook
}
function useXXX() {
const fiber = resolveCurrentlyRenderingFiber()
const hook = createWorkInProgressHook()
// ...
}
function updateFunctionComponent(recentFiber, workInProgressFiber, Component, props) {
prepareHooks(recentFiber, workInProgressFiber)
Component(props)
finishHooks()
}
const ChildComponent = () => {
useState('foo')
useState('bar')
useState('baz')
return null
}
const ParentComponent = () => {
const childFiberRef = useRef()
useEffect(() => {
let hookNode = childFiberRef.current.memoizedState
assert(hookNode.memoizedState, 'foo')
hookNode = hooksNode.next
assert(hookNode.memoizedState, 'bar')
hookNode = hooksNode.next
assert(hookNode.memoizedState, 'baz')
})
return (
<ChildComponent ref={childFiberRef} />
)
}
让我们更具体一点,谈谈各个hooks,从最常见的state hook开始:
State hooks
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
const ParentComponent = () => {
const [name, setName] = useState()
return (
<ChildComponent toUpperCase={setName} />
)
}
const ChildComponent = (props) => {
useEffect(() => {
props.toUpperCase((state) => state.toUpperCase())
}, [true])
return null
}
最后,effect hooks - 它对组件的生命周期及其工作方式产生了重大影响:
Effect hooks
Effect hooks 的行为略有不同,并且有一个额外的逻辑层,我接下来会解释。同样,在我深入了解实现之前,我希望你能记住effect hooks的属性:
它们是在渲染时创建的,但它们在绘制后运行。
它们将在下一次绘制之前被销毁。
它们按照已经被定义的顺序执行。
执行所有生命周期和ref回调。生命周期作为单独的过程发生,因此整个树中的所有放置,更新和删除都已经被调用。此过程还会触发任何特定渲染的初始effects。
由useEffect() hook 安排的effects - 基于实现也被称为“passive effects” (也许我们应该在React社区中开始使用这个术语?!)。
destroy- 从create()返回的回调应该在初始渲染之前运行。
inputs - 一组值,用于确定是否应销毁和重新创建effe
next - 函数组件中定义的下一个effect的引用。
const NoEffect = /* */ 0b00000000;
const UnmountSnapshot = /* */ 0b00000010;
const UnmountMutation = /* */ 0b00000100;
const MountMutation = /* */ 0b00001000;
const UnmountLayout = /* */ 0b00010000;
const MountLayout = /* */ 0b00100000;
const MountPassive = /* */ 0b01000000;
const UnmountPassive = /* */ 0b10000000;
Default effect — UnmountPassive | MountPassive.
Mutation effect — UnmountSnapshot | MountMutation.
Layout effect — UnmountMutation | MountLayout.
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
}
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
}
因此,基于我们刚刚学到的关于effect hooks的内容,我们实际上可以在外部向某个fiber注入effect:
function injectEffect(fiber) {
const lastEffect = fiber.updateQueue.lastEffect
const destroyEffect = () => {
console.log('on destroy')
}
const createEffect = () => {
console.log('on create')
return destroy
}
const injectedEffect = {
tag: 0b11000000,
next: lastEffect.next,
create: createEffect,
destroy: destroyEffect,
inputs: [createEffect],
}
lastEffect.next = injectedEffect
}
const ParentComponent = (
<ChildComponent ref={injectEffect} />
)
回复“加群”与大佬们一起交流学习~
点击“阅读原文”查看70+篇原创文章