https://medium.com/the-guild/under-the-hood-of-reacts-hooks-system-eb59638c9dba
首先,让我们进入需要确保hooks在React的作用域调用的机制,因为你现在可能知道如果在没有正确的上下文调用钩子是没有意义的:
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。我想向您介绍一个新概念:
-
它的初始状态在首次渲染时被创建。 -
她的状态可以即时更新。 -
React会在之后的渲染中记住hook的状态 -
React会根据调用顺序为您提供正确的状态 -
React会知道这个hook属于哪个Fiber。
{
foo: 'foo',
bar: 'bar',
baz: 'baz',
}
{
memoizedState: 'foo',
next: {
memoizedState: 'bar',
next: {
memoizedState: 'bar',
next: null
}
}
}
· 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开始:
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的属性:
- 它们是在渲染时创建的,但它们在绘制后运行。
- 它们将在下一次绘制之前被销毁。
- 它们按照已经被定义的顺序执行。
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} />
)