成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Antd Mobile 作者教你寫 React 受控組件和非受控組件

開發(fā) 前端
在這篇文章,我們將一起聊聊怎么去讓一個(gè)組件像 antd 的 Input 組件這樣,既支持受控模式,又支持非受控模式。讓我們從最簡單和基礎(chǔ)的部分出發(fā),一點(diǎn)點(diǎn)來分析和演進(jìn),看看會(huì)遇到哪些問題,又如何一步步解決。

曾經(jīng),我每次面試時(shí)幾乎都會(huì)問一個(gè)問題:antd 中的 Input 組件是受控組件還是非受控組件?

有些人會(huì)毫不猶豫的回答:是受控組件,因?yàn)橛?nbsp;value 和 onChange,而另外也有一些人會(huì)比較猶豫,因?yàn)榈拇_似乎說 Input 是受控組件或非受控組件都說得過去。當(dāng)然,實(shí)際上 Input 組件既可以是受控組件,也可以是非受控組件,這完全取決于業(yè)務(wù)項(xiàng)目中怎么去使用它。

在這篇文章,我們將一起聊聊怎么去讓一個(gè)組件像 antd 的 Input 組件這樣,既支持受控模式,又支持非受控模式。讓我們從最簡單和基礎(chǔ)的部分出發(fā),一點(diǎn)點(diǎn)來分析和演進(jìn),看看會(huì)遇到哪些問題,又如何一步步解決。

什么是受控組件?什么又是非受控組件?

讓我們先來看一個(gè)簡單的例子,這個(gè) Input 組件有一個(gè)內(nèi)部的狀態(tài)(State)value,而且它沒有任何屬性,因此很顯然,它是一個(gè)非受控的組件,它的組件狀態(tài)并不受外部環(huán)境控制,而是封閉在組件內(nèi)部。

圖片圖片

而如果我們稍微對(duì)它做一點(diǎn)調(diào)整,把原本的內(nèi)部狀態(tài) value? 去掉,放到 props 上去,它就變成了受控組件:

圖片圖片

很顯然,此時(shí)輸入框的值是取決于外部傳遞進(jìn)來的 props。

如果我們畫個(gè)圖,那可以很清楚的看到受控和非受控的區(qū)別:

圖片圖片

圖中藍(lán)色的方框表示組件,黃色的圓圈表示組件內(nèi)的狀態(tài)。

既受控組件又非受控?

盡管在業(yè)務(wù)項(xiàng)目中,我們寫的組件都是明確的受控或者非受控,但對(duì)于組件庫來說,有非常多的組件需要做到既支持受控模式,又支持非受控模式。以 antd-mobile 現(xiàn)在的 5.17 版本為例,幾乎全部的涉及到輸入值、切換、展開收起的組件,都是需要做到既受控又非受控的。

盡管聽起來似乎不難,但實(shí)際寫起來還是會(huì)遇到一些困難的,讓我們來試一試。

如何實(shí)現(xiàn)

最簡單的方案:內(nèi)外兩個(gè)狀態(tài),手動(dòng)同步

考慮到實(shí)現(xiàn)成本的復(fù)雜度,我們需要讓組件邏輯在兩種模式下,盡可能的保持一致,減少邏輯分支意味著更好的可維護(hù)性和可讀性。所以,自然而然的,我們可以很容易想到這個(gè)方案:

Child 組件內(nèi)部始終存在一個(gè)狀態(tài),不管它處于哪種模式,它都直接使用自己內(nèi)部的狀態(tài)。而當(dāng)它處于受控模式時(shí),我們讓它的內(nèi)部狀態(tài)和 Parent 組件中的狀態(tài)手動(dòng)保持同步。

下面的示意圖中加上了兩個(gè)對(duì)勾標(biāo)記,被勾選的狀態(tài)表示 Child 組件實(shí)際在使用哪個(gè)狀態(tài)。?

圖片圖片

這套方案聽起來是可行的,我們把它寫成代碼:

圖片

仔細(xì)看上面的代碼,我們會(huì)發(fā)現(xiàn)在受控模式下存在兩個(gè)問題:

  1. ?原子性:Child 內(nèi)部狀態(tài)的更新會(huì)比 Parent 組件晚一個(gè)渲染周期,存在 tearing 的問題。
  2. 性能:因?yàn)槭窃趗seEffect? 中通過setState 來做的狀態(tài)同步,所以會(huì)額外的觸發(fā)一次渲染,存在性能問題。

明確問題之后,我們來逐個(gè)解決:

解決問題 1:原子性

這個(gè)問題其實(shí)很好解決,我們其實(shí)并不需要 Child 和 Parent 的狀態(tài)保持非常嚴(yán)格的每時(shí)每刻都一致,我們只需要判斷,如果組件此時(shí)處于受控模式,那么直接使用來自外部的狀態(tài)就可以了:

圖片

這樣,即便狀態(tài)的同步是存在延遲的,但是 Child 組件所真正使用到的值一定是最新的。

代碼如下:

圖片圖片

解決問題 2:性能

因?yàn)槲覀兪窃?nbsp;useEffect 去做狀態(tài)同步的,所以自然會(huì)額外的多觸發(fā)一次 Child 組件的重渲染。如果 Child 組件比較簡單的話,那出現(xiàn)的性能影響可以忽略不計(jì)。但是對(duì)于一些復(fù)雜的組件(例如 Picker),多渲染一次帶來的性能問題是比較嚴(yán)重的。

那有沒有辦法在 Child 組件的 render 階段就直接更新 value 狀態(tài)呢?

并不可以,React 不允許我們?cè)?render 過程中調(diào)用 setState。

似乎進(jìn)入了死胡同,但我們可以停下來,重新考慮一下這行 useState 的代碼:

圖片圖片

當(dāng)我們創(chuàng)建這個(gè) State 時(shí)?我們的目的是什么?State 的本質(zhì)是什么?

如果比較簡單粗暴的分析,我們可以把 State 拆成兩部分:

  1. State 是用來存放數(shù)據(jù)的,它讓我們?cè)诮M件的渲染函數(shù)之外,可以“持久化”一些數(shù)據(jù)。
  2. State 的更新可以觸發(fā)重新渲染,因?yàn)?React 會(huì)感知 State 的更新。

如果寫一個(gè)公式的話,可以寫成:

State = 存放數(shù)據(jù) + 觸發(fā)重新渲染

而但就存放數(shù)據(jù)來看,我們可以直接使用 Ref;同樣,如果只是需要觸發(fā)重新渲染,我們可以使用類似于 setFlag({}) 或者 setCount(v => v + 1) 這樣的強(qiáng)制方式(雖然很蠢,但想必 90% 的 React 開發(fā)者都曾經(jīng)這么寫過)。

那我們根據(jù)這個(gè)推斷來調(diào)整一下上面的公式:

State = Ref + forceUpdate()

我們已經(jīng)非常接近了,根據(jù)這個(gè)公式,我們可以把 Child 組件中的 State 拆成一個(gè) Ref 和一個(gè) forceUpdate 函數(shù):

下圖中的虛線淺色圓圈表示 ref,刷新圖標(biāo)表示 forceUpdate 函數(shù)”。

圖片圖片

這樣一來,我們就可以直接在 render 階段直接更新 ref 的值了:

圖片圖片

再回頭看下代碼,會(huì)發(fā)現(xiàn),為什么還需要判斷根據(jù)受控和非受控模式來使用不同的值呢?(上面代碼塊中的第 12 行)。既然 stateRef.current 一定是最新的值,那么完全可以簡化成 Child 組件永遠(yuǎn)使用內(nèi)部存放的數(shù)據(jù)(Ref):

圖片

除此之外,我們還可以把手動(dòng)實(shí)現(xiàn)的 forceUpdate 替換成 ahooks 的 useUpdate:

圖片

抽象與復(fù)用:usePropsValue

到這里,我們已經(jīng)基本實(shí)現(xiàn)了所有的功能,但我們只是實(shí)現(xiàn)了一個(gè) Input 組件,在 antd-mobile 這樣的組件庫中,會(huì)有很多很多組件都需要支持能夠切換受控和非受控模式。所以,為了更好的可復(fù)用性,我們把上面的邏輯抽離成一個(gè)自定義 Hook:

圖片圖片

這樣,在各種組件中,我們可以直接使用 usePropsValue,用法和 useState 非常接近:

圖片圖片

不過,我們忽略了 defaultValue,在 antd-mobile 中,value onChange defaultValue 總是成組出現(xiàn)的:

圖片圖片

接下來,讓我們對(duì)它再做一點(diǎn)優(yōu)化,讓它變得更像 useState。useState 得到的 setState 函數(shù),支持傳入一個(gè)更新函數(shù),而 usePropsValue 目前還不支持這種用法,所以我們來改造一下:

圖片圖片

一個(gè)隱藏的小 bug

我本以為已經(jīng)完工了,直到某天在 GitHub 上收到了一條 issue:TabBar 的 onChange 為什么在同 key 的情況也會(huì)觸發(fā) #5409[1]。

這條 issue 揭示了一個(gè)隱藏已久的 bug,舉個(gè)例子:

假如當(dāng)前的 state 為 1,如果我們用的是 React 的 useState,那執(zhí)行 setState(1) 不會(huì)有任何效果,React 會(huì)幫我們過濾掉這次的更新。而 usePropsValue 不會(huì)。

對(duì)用戶來說,點(diǎn)擊同一個(gè) Tab 并沒有觸發(fā)切換,也因此不應(yīng)該觸發(fā) onChange 事件,所以我們還需要額外的增加一點(diǎn)判斷,來解決這個(gè) bug:

圖片

在 antd-mobile 中,我們也有一個(gè)這樣的 usePropsValue 工具 Hook,和上面文章中所描述的幾乎是一樣的,如果你想了解更多,可以去這里[2]翻閱代碼。

勘誤

上面“解決問題 2:性能”章節(jié)中提到“React 不允許我們?cè)?render 過程中調(diào)用 setState”,但經(jīng)評(píng)論區(qū)@fenoob[3]。

指正,其實(shí)是 React 是允許我們?cè)?render 函數(shù)中調(diào)用 setState 的,只是限制了只能觸發(fā)當(dāng)前組件自己的 state 更新。我在這里寫了一個(gè) demo[4] 驗(yàn)證了一下。

參考資料

[1]TabBar 的 onChange 為什么在同 key 的情況也會(huì)觸發(fā) #5409:https://github.com/ant-design/ant-design-mobile/issues/5409。

[2]這里:https://github.com/ant-design/ant-design-mobile/blob/fae45549bcadb2b3c7f1dea27462543230e3b795/src/utils/use-props-value.ts。

[3]@fenoob://www.zhihu.com/people/05bdf67112572afd5f3526f2eaa425c8。

[4]demo:https://codesandbox.io/s/condescending-pare-1utvlt?file=/src/App.js。

責(zé)任編輯:姜華 來源: 前端桃園
相關(guān)推薦

2021-07-09 08:33:35

React組件受控

2021-03-18 08:00:55

組件Hooks React

2020-10-21 08:38:47

React源碼

2021-09-26 18:43:48

表單受控React

2021-12-13 14:37:37

React組件前端

2011-07-21 16:10:11

button按鈕jQuery Mobi

2019-03-13 10:10:26

React組件前端

2011-07-21 15:50:42

jQuery Mobi頁面對(duì)話框

2021-09-14 18:33:39

React 數(shù)據(jù)交互

2022-09-22 12:38:46

antd form組件代碼

2011-04-02 13:44:08

2011-07-21 16:10:48

jQuery Mobi工具欄

2011-07-26 08:40:31

jQuery Mobi組件內(nèi)容格式

2019-07-22 10:42:11

React組件前端

2023-10-12 10:10:00

微軟Windows

2020-11-20 10:52:54

Antd表格日程

2021-10-13 14:01:00

函數(shù)React進(jìn)階

2021-09-07 18:40:55

單向數(shù)據(jù)流數(shù)據(jù)

2017-02-28 21:57:05

React組件

2017-03-21 21:37:06

組件UI測試架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 99爱视频| av天天干 | 99爱国产| 黄色网址在线免费观看 | 天天爱天天操 | 羞羞在线视频 | 精品一二区| 精品一区二区电影 | 成人免费观看男女羞羞视频 | 国产精品区二区三区日本 | 中文字幕日韩一区 | 久久草在线视频 | 日韩一二三区视频 | 啪啪免费 | 欧美一区二区三区在线 | 亚洲在线视频 | 成人一区二区三区在线观看 | 九九热在线视频观看这里只有精品 | www在线视频 | 亚洲精品18 | 黄免费观看视频 | 午夜在线视频 | 日韩一区中文字幕 | 国产精品久久久久久久久久久久久 | 亚洲免费三级 | 国产精品一区二区在线 | 国产精品日韩欧美一区二区三区 | 日韩综合网 | 婷婷桃色网 | 在线播放91| 一区二区三区中文字幕 | 成人高清视频在线观看 | 欧美综合久久久 | 久久久久久久久久久91 | 一二三四在线视频观看社区 | 国产视频一区二区在线观看 | 国产久| 亚洲福利av | 亚洲va国产日韩欧美精品色婷婷 | 午夜伊人 | 91精品国产综合久久久久久漫画 |