原文連接:前端
https://medium.com/the-guild/under-the-hood-of-reacts-hooks-system-eb59638c9dbanode
首先,讓咱們進入須要確保hooks在React的做用域調用的機制,由於你如今可能知道若是在沒有正確的上下文調用鉤子是沒有意義的:react

The dispatchergit
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。我想向您介紹一個新概念:github

The hooks queueweb
-
它的初始狀態在首次渲染時被建立。 -
她的狀態能夠即時更新。 -
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 hooksapp
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} />
)


本文分享自微信公衆號 - 前端技術江湖(bigerfe)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。