日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線(xiàn)溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
Antd Mobile 作者教你寫(xiě) React 受控組件和非受控組件

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

專(zhuān)注于為中小企業(yè)提供成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)岐山免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了近千家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

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

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

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

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

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

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

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

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

既受控組件又非受控?

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

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

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

最簡(jiǎ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)。?

這套方案聽(tīng)起來(lái)是可行的,我們把它寫(xiě)成代碼:

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

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

明確問(wèn)題之后,我們來(lái)逐個(gè)解決:

解決問(wèn)題 1:原子性

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

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

代碼如下:

解決問(wèn)題 2:性能

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

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

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

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

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

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

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

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

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

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

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

State = Ref + forceUpdate()

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

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

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

再回頭看下代碼,會(huì)發(fā)現(xiàn),為什么還需要判斷根據(jù)受控和非受控模式來(lái)使用不同的值呢?(上面代碼塊中的第 12 行)。既然 stateRef.current 一定是最新的值,那么完全可以簡(jiǎn)化成 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 這樣的組件庫(kù)中,會(huì)有很多很多組件都需要支持能夠切換受控和非受控模式。所以,為了更好的可復(fù)用性,我們把上面的邏輯抽離成一個(gè)自定義 Hook:

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

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

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

一個(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ì)幫我們過(guò)濾掉這次的更新。而 usePropsValue 不會(huì)。

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

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

勘誤

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

指正,其實(shí)是 React 是允許我們?cè)?render 函數(shù)中調(diào)用 setState 的,只是限制了只能觸發(fā)當(dāng)前組件自己的 state 更新。我在這里寫(xiě)了一個(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。


網(wǎng)站標(biāo)題:Antd Mobile 作者教你寫(xiě) React 受控組件和非受控組件
網(wǎng)頁(yè)網(wǎng)址:http://m.5511xx.com/article/dhecoos.html