摘要
在上一篇文章中,我們已經實現了函數組件。同時可以正常通過render進行渲染。
而通過之前的文章,beginWork和completeWork也已經有了基本的架子。現在我們可以去實現useState了。
實現之前,我們要先修改一下我們的index.js文件:
import jsx from '../src/react/jsx.js'
import ReactDOM from '../src/react-dom/index'
import { useState } from './react-dom/filberHook.js';const root = document.querySelector('#root');function App() {const [name, setName] = useState('kusi','key');window.setName = setName;const [age, setAge] = useState(20)window.setAge = setAge;return jsx("div", {ref: "123",children: jsx("span", {children: name + age})});
}ReactDOM.createRoot(root).render(<App />)
由于我們這一篇并不會實現React的事件機制,所以我們先將setState的方法掛載在window上進行調試。有了基礎,我們現在開始實現useState。
1.renderWithHook
在實現之前,我們先來思考一個問題。在之前實現beginWork機制的時候,我們為了兼容函數組件。獲取子FilberNode的時候,函數組件是直接調用拿到返回值。
那么如果函數直接調用,是不是就已經調用了我們在函數里寫的Hook。
所以我們把這一部分拆出來:
function updateFunctionComponent(filberNode) {const nextChildren = renderWithHook(filberNode);const newFilberNode = reconcileChildren(nextChildren);filberNode.child = newFilberNode;newFilberNode.return = filberNode;beginWork(newFilberNode)
}
在更新函數節點的時候,通過renderWithHook拿到函數執行的返回值:
那我們在renderWithHook里除了拿到函數執行的返回值,還要做什么呢?
這里值得注意的是,我們知道通過setState,函數組件會重新執行渲染。在這里,我們將函數的執行分為兩種:第一次mount和后面的update。
就是執行useState這個過程,要分為兩種,一種是mount下的useState,一種是update下的useState。OK,現在我們用一個標志去表示這兩種狀態,并且在renderWithHook下去改變它。
let hookWithStatus;
let workInPropgressFilber = null;export const renderWithHook = (filberNode) => {if(filberNode.child){//更新hookWithStatus = 'update'}else{//mounthookWithStatus = 'mount'}workInPropgressFilber = filberNode;const nextChildren = filberNode.type();return nextChildren;
}
2.實現mountState和Hook結構
現在我們在beginWork執行完后,會執行renderWithHook,執行后會改變hookWithStatus這個標志。再然后就是調用函數本身了。
所以現在我們根據這個標志實現兩種不同的useState:
export const useState = (state) => {if(hookWithStatus === 'mount'){return mountState(state)}else if(hookWithStatus === 'update'){return updateState(state)}
}
也就是頁面第一次渲染時,執行函數組件里的內容,我們要調用mountState。現在我們實現mountState。
實現之前,我們先說一下在React中,是如何將組件中的Hook存儲的。在React中是通過鏈表的方式,將不同的Hook存儲起來。現在我們定義一下Hook的結構:
它具有三個屬性。memoizedState表示存儲的state值,updateQueue表示需要更新的值,next表示指向的下一個hook。
class Hook {constructor(memoizedState, updateQueue, next){this.memoizedState = memoizedStatethis.updateQueue = updateQueuethis.next = next;}
}
所以在mountStaet中,我們要將這個鏈表結構實現出來:
這里我們定義一個headHook指向最外層的hook,workinProgressHook指向當前的hook。
function mountState(state) {const memoizedState = typeof state === 'function' ? state() : state;const hook = new Hook(memoizedState);hook.updateQueue= createUpdateQueue()if(workInPropgressHook === null){workInPropgressHook = hook;headHook = hook;}else{workInPropgressHook.next = hook;workInPropgressHook = hook;}return [memoizedState]
}
現在我們可以看一下HOOK的結構:
可以看出它是一個鏈表的結構,memoizedState保存的就是setState的初始值。
3.實現dispach更新
現在經過mount階段后,我們已經有了一個基本的Hook鏈表。現在如果我在window下調用setState,那肯定是什么都不會發生的。
所以我們要實現setState方法,但是要調用setState方法是一定要更新的,所以我們將beginWork中的updateContainer方法修改一下,并且暴露出來:
function updateContainer(root, element) {const hostRootFilber = root.current;const update = createUpdate(element);hostRootFilber.updateQueue = createUpdateQueue()enqueueUpdate(hostRootFilber.updateQueue, update);wookLoop(root,hostRootFilber)
}export const wookLoop = (root,hostRootFilber) => {if(!hostRootFilber){hostRootFilber = root.current}beginWork(hostRootFilber);completeWork(hostRootFilber);root.finishedWork = hostRootFilber;console.log(root)commitWork(root)
}
這樣我就可以在hook的機制里面調用wookLoop了。現在我們實現dispatch:
function disaptchState(filber, hook, action) {const update = createUpdate(action);enqueueUpdate(hook.updateQueue, update);workUpdateHook = hook;wookLoop(filber.return.stateNode)
}
dispatchState方法傳入當前的filberNode, 還有就是對應的hook,以及需要更新的action。
同時我們將準備更新的hook進行標記。
所以在mountState中:
function mountState(state) {const memoizedState = typeof state === 'function' ? state() : state;const hook = new Hook(memoizedState);//其他代碼。。。const disaptch = disaptchState.bind(null,workInPropgressFilber,hook)return [memoizedState,disaptch]
}
我們將dispatch需要的參數傳進去,并且只給外面放開action。這樣就實現好了dispatch方法。
4.實現updateState方法
當我們將上面的過程實現完之后,如果在控制臺調用setState。那么就會觸發workLoop,同時會再走一次beginWork。
此時再進入renderWithHook之后,就不會再走mountState了,而是進入updateState。
而在updateState中,我們要做的事情也不是很復雜,只需要從頭遍歷Hook鏈表,如果是標記更新的Hook,就返回更新的內容。如果不是,就正常返回它的memoizedState就好了。
function updateState(state) {if(currentHook === workUpdateHook){const newHook = new Hook(workUpdateHook.updateQueue.shared.pending.action)newHook.updateQueue = createUpdateQueue();const disaptch = disaptchState.bind(null,workInPropgressFilber,newHook)currentHook = currentHook.next;const result = [workUpdateHook.updateQueue.shared.pending.action,disaptch];return result;}else{let result = currentHook.memoizedState;const disaptch = disaptchState.bind(null,workInPropgressFilber,currentHook)currentHook = currentHook.next;return [result,disaptch]}
}
所以這也是為什么,在React中,不能在條件語句里面使用Hook,如果你mountState生成的Hook鏈表會發生變化。那么在updateState里面,遍歷鏈表的時候,就會出現值錯位的情況。
OK,到這里useState的方法也已經實現完了。