React源碼解析18(6)------ 實現useState

摘要

在上一篇文章中,我們已經實現了函數組件。同時可以正常通過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的方法也已經實現完了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/41849.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/41849.shtml
英文地址,請注明出處:http://en.pswp.cn/news/41849.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

DAY2,ARM(特殊功能寄存器,數據操作指令,跳轉指令)

1.cmp、sub、b指令的使用&#xff1b; 代碼&#xff1a; .text .global _start _start:mov r0,#9mov r1,#15loop:cmp r0,r1beq stopsubcc r1,r1,r0subhi r0,r0,r1b loopstop:b stop .end結果&#xff1a; 2.匯編指令計算1~100之間和&#xff1b; 代碼&#xff1a; .text .gl…

【從零學習python 】47. 面向對象編程中的繼承概念及基本使用

文章目錄 繼承的基本使用代碼逐行講解說明:進階案例 繼承的基本使用 在現實生活中&#xff0c;繼承一般指的是子女繼承父輩的財產&#xff0c;父輩有的財產&#xff0c;子女能夠直接使用。 程序里的繼承 繼承是面向對象軟件設計中的一個概念&#xff0c;與多態、封裝共為面向對…

Android 13 Launcher——屏蔽上拉到應用列表

背景 Launcher定制需要將原先的應用列表去掉,可以從根源去掉,就是將上拉出現應用列表的上拉手勢直接屏蔽,讓其不能上拉出現應用列表界面,在研究的過程中順便將下拉出現負一屏的邏輯也研究了下,如下就是具體實現。 目錄 背景 一.如何屏蔽上拉出現應用列表 一.如何屏蔽上拉…

培訓報名小程序-用戶注冊

目錄 1 創建數據源2 注冊用戶3 判斷用戶是否注冊4 完整代碼總結 我們的培訓報名小程序&#xff0c;用戶每次打開時都需要填寫個人信息才可以報名&#xff0c;如果用戶多次報名課程&#xff0c;每次都需要填寫個人信息&#xff0c;比較麻煩。 本篇我們就優化一下功能&#xff0c…

線上售樓vr全景看房成為企業數字化營銷工具

在房地產業中&#xff0c;VR全景拍攝為買家提供了虛擬看房的全新體驗。買家可以通過相關設備&#xff0c;遠程參觀各個樓盤的樣板間和實景&#xff0c;感受房屋的空間布局和環境氛圍&#xff0c;極大地提高了購房決策的準確性。對于房地產開發商和中介機構來說&#xff0c;VR全…

@Async用哪個線程池

一共可以分三種情況 第一種 未在手動在項目中配置任何線程池 spring boot 會默認添加一個coreSize8的 無界線程池&#xff0c;名稱為applicationTaskExecutor &#xff08;源碼&#xff1a;org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration&…

如何搭建個人郵件服務hmailserver并實現遠程發送郵件

文章目錄 1. 安裝hMailServer2. 設置hMailServer3. 客戶端安裝添加賬號4. 測試發送郵件5. 安裝cpolar6. 創建公網地址7. 測試遠程發送郵件8. 固定連接公網地址9. 測試固定遠程地址發送郵件 hMailServer 是一個郵件服務器,通過它我們可以搭建自己的郵件服務,通過cpolar內網映射工…

計算機競賽 GRU的 電影評論情感分析 - python 深度學習 情感分類

1 前言 &#x1f525;學長分享優質競賽項目&#xff0c;今天要分享的是 &#x1f6a9; GRU的 電影評論情感分析 - python 深度學習 情感分類 &#x1f947;學長這里給一個題目綜合評分(每項滿分5分) 難度系數&#xff1a;3分工作量&#xff1a;3分創新點&#xff1a;4分 這…

代碼隨想錄算法訓練營第三十八天 | 理論基礎,509. 斐波那契數,70. 爬樓梯,746. 使用最小花費爬樓梯

代碼隨想錄算法訓練營第三十八天 | 理論基礎&#xff0c;509. 斐波那契數&#xff0c;70. 爬樓梯&#xff0c;746. 使用最小花費爬樓梯 理論基礎什么是動態規劃動態規劃的解題步驟動態規劃應該如何debug 509. 斐波那契數遞歸解法 70. 爬樓梯746. 使用最小花費爬樓梯 理論基礎 視…

計蒜客T1170——人民幣支付

超級水&#xff0c;不解釋&#xff0c;代碼的處理方式減低了繁瑣程度&#xff0c; #include <iostream> using namespace std;int main(int argc, char** argv) {int num0;cin>>num;int money[6]{100,50,20,10,5,1};for(int i0;i<5;i){int count0;countnum/mone…

SkyWalking 部署(包含ES)

SkyWalking安裝 結構 首先SkyWalking主要需要oapService、webApp、Elasticsearch&#xff08;可選存儲&#xff09;三個&#xff0c;接下來講一下這三個的安裝步驟&#xff0c;安裝過程中出現了一些細小的配置錯誤&#xff0c;導致用了快兩天才弄好&#xff0c;麻木了&#x…

C++超基礎語法

&#x1f493;博主個人主頁:不是笨小孩&#x1f440; ?專欄分類:數據結構與算法&#x1f440; C&#x1f440; 刷題專欄&#x1f440; C語言&#x1f440; &#x1f69a;代碼倉庫:笨小孩的代碼庫&#x1f440; ?社區&#xff1a;不是笨小孩&#x1f440; &#x1f339;歡迎大…

IDEA常用工具配置

IDEA常用工具&配置 如果發現插件市場用不了&#xff0c;可以設置Http Proxy&#xff0c;在該界面上點擊”Check connection“并輸入的地址&#xff1a;https://plugins.jetbrains.com/ 。 一、常用插件 1、MybatisX Mybaits Plus插件&#xff0c;支持java與xml互轉 2、F…

Vue-10.集成.env

.env、.env.development 和 .env.preview .env、.env.development 和 .env.preview 文件是用于配置環境變量和應用程序設置的文件&#xff0c;它們在項目開發和部署過程中起到關鍵作用。這些文件用于在不同的環境中設置不同的變量值&#xff0c;以滿足不同環境下的配置需求。 …

日志系統——日志格式化模塊設計

一&#xff0c;模塊主要成員 該模塊的主要作用是對日志消息進行格式化&#xff0c;將日志消息組織成制定格式的字符串。 該模塊主要成員有兩個&#xff1a;1.格式化字符串。 2.格式化子項數組 1.1 格式化字符串 格式化字符串的主要功能是保存日志輸出的格式字符串。其格式化字…

WPF 界面結構化處理

文章目錄 概要一、xaml界面結構化處理二、邏輯樹與視覺樹 概要 WPF 框架是開源的&#xff0c;但是不能跨平臺&#xff0c;可以使用MAUI&#xff0c;這個框架可以跨平臺&#xff0c;WPF源碼可以在github上下載&#xff0c;下載地址&#xff1a;https://gitbub.com/dotnet/wpf。…

【C++ 記憶站】命名空間

文章目錄 命名空間概念命名空間的定義1、正常的命名空間定義2、命名空間可以嵌套3、同一個工程中允許存在多個相同名稱的命名空間,編譯器最后會合成同一個命名空間中 命名空間的使用1、加命名空間名稱及作用域限定符2、使用using將命名空間中某個成員引入3、使用using namespac…

初試時間官宣!研招網發布下半年重要時間節點!今日速報來了

距24考研初試還有127天&#xff0c;今天給大家帶來初試和報名時間官宣消息、考研報名注意事項、研招網發布的2024考研“保姆級”下半年重要時間節點。有用記得收藏 24考研報名和初試時間官宣 已有學校在招生簡章中明確24考研初試時間 初試時間預計為&#xff1a;2023年12月23…

初試rabbitmq

rabbitmq的七種模式 Hello word 客戶端引入依賴 <!--rabbitmq 依賴客戶端--><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.8.0</version></dependency> 生產者 imp…

邀請函|澎峰科技邀您參加CCF HPC China2023

一年一度的全球超算盛會&#xff01; 以“算力互聯智領未來”為主題的第十九屆全國高性能計算學術年會&#xff08;CCF HPC China 2023&#xff09;將于8月24-26日&#xff08;展覽23-25日&#xff09;在青島紅島國際會議展覽中心舉辦。 九大院士領銜 打造頂級超算盛會 力邀…