useState原理以及其它react-hook的使用

前言

自react16.8發(fā)布了正式版hook用法以來徘跪,我們公司組件的寫法逐漸由class向函數(shù)式組件+hook的方向轉(zhuǎn)移垮庐,雖然用了這么久的hook哨查,但是用得多的基本就useState寒亥、useEffectuseMemo褂傀,其他的官方hook因?yàn)槭褂脠鼍安幻鲗?dǎo)致基本沒用過,所以這兩天特地去了解了一下其他hook的使用場景以及useState的原理加勤,然后用這篇文章記錄一下仙辟。

useState的使用及其原理

在hook版本出來之前同波,react函數(shù)組件無法擁有自身內(nèi)部的狀態(tài),而useState賦予了函數(shù)組件擁有內(nèi)部狀態(tài)的能力欺嗤,并且它的使用非常簡單卫枝。

  • useState用法
    useState是一個(gè)函數(shù)煎饼,它接收一個(gè)初始值,并返回一個(gè)數(shù)組校赤,該數(shù)組的第一位是一個(gè)state吆玖,第二位則是改變這個(gè)的state的函數(shù),比如下面這個(gè)計(jì)數(shù)器的例子马篮,當(dāng)我點(diǎn)擊按鈕+的時(shí)候沾乘,數(shù)字就+1:

    image.png

    上例中的n就是這個(gè)函數(shù)組件的內(nèi)部狀態(tài)了。

  • useState原理
    在上面的例子中浑测,我們每次點(diǎn)擊按鈕+的時(shí)候翅阵,數(shù)字都會(huì)增加1,也就是說App這個(gè)函數(shù)會(huì)被重新執(zhí)行一次:

    image.png

    image.png

    既然App會(huì)被重新執(zhí)行迁央,那么useState(0)也會(huì)被重新執(zhí)行一次掷匠,但是為什么n的值不會(huì)被重置為0呢?
    原因是,第二次useState執(zhí)行后返回的n并非之前的n岖圈,setN改變的并不是之前返回出來的那個(gè)n讹语,setN改變的數(shù)值存儲(chǔ)于其它地方而非n,之后useState通過閉包的形式將這個(gè)新的數(shù)值返回了出來蜂科,并且執(zhí)行dom的更新顽决,我們可以在下面的實(shí)現(xiàn)一個(gè)useState看得更清楚。

  • 實(shí)現(xiàn)一個(gè)useState

    1. 首先通過上面的原理的解析导匣,setN改變的并非n才菠,而是另一個(gè)變量,所以我們創(chuàng)建這個(gè)變量_state贡定,并創(chuàng)建myUseState函數(shù):

      image.png

    2. 之后myUseState接收一個(gè)初始值赋访,并返回一個(gè)數(shù)組,注意這個(gè)數(shù)組的第一項(xiàng)返回的并不是接收到的初始值而是第一步_state厕氨,而第二項(xiàng)則是改變_state的函數(shù):

      image.png

      另外需要注意的是进每,在第一次執(zhí)行myUseState的時(shí)候需要將初始值賦值給_state,而第二次執(zhí)行的時(shí)候則是將之前的_state賦值回_state:
      image.png

    3. 接著useState在更新state的時(shí)候會(huì)重新渲染Dom命斧,所以我們在setState函數(shù)中執(zhí)行重新渲染的步驟(這里為了方便簡化了更新步驟):

      image.png

    4. 這時(shí)候我們自己的useState就實(shí)現(xiàn)完成分了田晚,用來測試一下:

      image.png

      結(jié)果可見是成功的:
      image.png

      但是此時(shí)我們的myUseState存在一個(gè)嚴(yán)重的bug,如果一個(gè)組件內(nèi)存在多個(gè)state国葬,而_state卻只有一個(gè)贤徒,就會(huì)導(dǎo)致多個(gè)state都共用了一個(gè)狀態(tài)芹壕,比如下面的組件:
      image.png

      image.png

  • 修復(fù)myUseState的bug

    1. 針對上面所說的bug,在組件擁有多個(gè)state的情況下接奈,useState的執(zhí)行存在由上到下的順序踢涌,那么我們就可以將_state改造為一個(gè)數(shù)組,用于存儲(chǔ)多個(gè)state序宦,另外還需要新建一個(gè)變量index用于表明state在_state中的順序:

      image.png

    2. 之后在myUseState中要做的第一步就是將接收到的state放入到_state中(注意這里和之前一樣睁壁,第一次執(zhí)行放入的是初始state,從第二次開始變成_state中對應(yīng)的state):

      image.png

    3. 第三步我們在myUseState中創(chuàng)建一個(gè)能夠修改_state中對應(yīng)數(shù)據(jù)的函數(shù)setState并將其返回出來:

      image.png

    4. 之后需要考慮互捌,setState執(zhí)行的時(shí)候會(huì)重新渲染組件潘明,所以在這一步中需要重置index:

      image.png

    5. 另外,為了保證每個(gè)_state中的state的順序是一致的秕噪,所以在myUseState中將state放入到_state之后钳降,將index + 1,這樣我們就修復(fù)了之前多個(gè)state沖突的問題了:

      image.png

    6. 測試結(jié)果和代碼總覽:


      image.png
import React, { useState } from 'react'
import ReactDOM from 'react-dom'

const _state = []
let index = 0

const myUseState = initialValue => {
    const currentIndex = index
    _state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex]
    index = index + 1

    const setState = newValue => {
        _state[currentIndex] = newValue
        index = 0
        ReactDOM.render(<App />, document.getElementById('app'))
    }
    return [_state[currentIndex], setState]
}

const App = () => {
    const [n, setN] = myUseState(0)
    const [m, setM] = myUseState(0)

    const clickN = () => {
        setN(n + 1)
    }

    const clickM = () => {
        setM(m + 1)
    }

    return (
        <div>
            <div>{n}</div>
            <button onClick={clickN}>+</button>
            <div>{m}</div>
            <button onClick={clickM}>+</button>
        </div>
    )
}

ReactDOM.render(<App />, document.getElementById('app'))
  • useState的一些其他知識
    1. useState不能在條件語句后使用的原因: 原因根據(jù)上面自己實(shí)現(xiàn)的myUseState中就能看出來了腌巾,如果放在條件語句后使用遂填,那么就有可能打破_state存放state的順序?qū)е聅tate錯(cuò)亂。
    2. 看下面代碼澈蝙,雖然用了兩次setN吓坚,但實(shí)際上點(diǎn)擊按鈕+后實(shí)際上數(shù)字只會(huì)加一:
      image.png

      image.png

      我們可以將clickN中的代碼改成如下,使其變成每次都能+2:
      image.png

      image.png

useEffect和useLayoutEffect的使用及其異同

  • useEffect
    useEffect接收兩個(gè)參數(shù)碉克,第一個(gè)參數(shù)是函數(shù)凌唬,用于當(dāng)組件內(nèi)的state產(chǎn)生變化之后執(zhí)行,而第二個(gè)參數(shù)(非必傳)是一個(gè)數(shù)組漏麦,接收依賴的state客税,比如下面的例子,當(dāng)n變化的時(shí)候?qū)?huì)打印出n的數(shù)值:

    image.png

    另外useEffetc接收的函數(shù)參數(shù)可以返回一個(gè)函數(shù)撕贞,這個(gè)函數(shù)將在該組件注銷時(shí)執(zhí)行更耻,類似于class組件的componentWillUnmount
    例如下面的組件捏膨,在組件掛載后會(huì)設(shè)置一個(gè)定時(shí)器秧均,每一秒鐘打印一個(gè)1出來,當(dāng)該組件被注銷后号涯,這個(gè)定時(shí)也會(huì)被注銷:
    image.png

    另外還需要注意目胡,usweEffect接收的函數(shù)是在組件渲染完畢之后才執(zhí)行的。

  • useLayoutEffect
    useLayoutEffect用的非常少链快,這是一個(gè)有點(diǎn)像vue的v-cloak的功能誉己,比如下面的代碼,當(dāng)組件掛載之后域蜗,把div里面的文字從value: 0改成value: 1000:

    image.png

    我們看到的效果確實(shí)也是這樣的:
    image.png

    但是當(dāng)你刷新多幾次的時(shí)候巨双,仔細(xì)觀察就會(huì)發(fā)現(xiàn)噪猾,每次加載進(jìn)來頁面都會(huì)看到value: 0閃爍一下然后變成value: 1000,這是因?yàn)?code>useEffect接收的函數(shù)是在組件被渲染之后才會(huì)執(zhí)行的筑累。
    這時(shí)候要解決這個(gè)問題袱蜡,就需要將useEffect改成useLayoutEffect了,就不會(huì)存在這個(gè)閃爍的問題慢宗,而是直接顯示value: 1000
    image.png

    原因在于坪蚁,useLayoutEffect接收到的函數(shù)參數(shù)在組件渲染之前就會(huì)被執(zhí)行,也就是說useEffectuseLayoutEffect功能其實(shí)是類似的婆廊,但是執(zhí)行的時(shí)機(jī)不同迅细,我們可以從下面的執(zhí)行順序看出來:
    image.png

    打印的順序確實(shí)是1 2 3 4:
    image.png

  • 注意:
    雖然說useLayoutEffect能夠在useEffect之前就執(zhí)行,但是在不改變網(wǎng)頁Dom文字樣式的情況下淘邻,還是推薦使用useEffect的,在需要改變網(wǎng)頁Dom文字樣式的情況下再使用useLayoutEffect

useReducer以及useContext

  • useReducer
    useReducer的使用和redux的使用有些類似湘换,useReducer接收兩個(gè)參數(shù)宾舅,第一個(gè)是reducer(和redux中的一模一樣),第二個(gè)參數(shù)是初始state彩倚,之后他會(huì)返回一個(gè)數(shù)組筹我,數(shù)組第一項(xiàng)是state,第二項(xiàng)是改變state的函數(shù)dispatch帆离,比如下面的例子:

    image.png

    測試結(jié)果:
    image.png

  • useContext
    useContext需要和createContext結(jié)合起來使用蔬蕊,實(shí)際上他們所要解決的問題和redux、mobx是類似的哥谷,都是夸組件間的數(shù)據(jù)傳遞岸夯,比如下面的例子,存在App組件们妥,一個(gè)父親組件猜扮,一個(gè)兒子組件,我們就通過創(chuàng)建一個(gè)Context监婶,并用這個(gè)Context將App組件包裹起來旅赢,將App組件內(nèi)的state傳入到Context,使得父親組件和兒子組件都能夠通過useContext拿到App組件的state:

    image.png

  • useReducer和useContext結(jié)合搭建狀態(tài)管理系統(tǒng)
    使用useContext可以在任意被對應(yīng)Context包裹的組件中拿到傳入的數(shù)據(jù)惑惶,將其和useReducer結(jié)合起來煮盼,
    就可以創(chuàng)建一個(gè)組件的狀態(tài)管理系統(tǒng),如何搭建可以參考我的這篇文章從零搭建項(xiàng)目(5) --- 前端: 搭建路由和狀態(tài)管理

React.memo带污、useMemo和useCallback

這三個(gè)Api通常都在優(yōu)化組件的時(shí)候使用僵控,并且他們使用的都是記憶化函數(shù)的原理,關(guān)于記憶化函數(shù)可以參考我之前寫的這篇文章: 再談js中的函數(shù)

  • React.memo
    memo的功能其實(shí)之前class組件的pureComponent差不多刮刑,但是這個(gè)memo是用在函數(shù)式組件上的喉祭。
    首先我們來看下面的例子养渴,Child組件引用了App組件的狀態(tài)m,狀態(tài)n和Child組件并無關(guān)系:

    image.png

    但實(shí)際上我點(diǎn)擊按鈕并執(zhí)行setN的時(shí)候泛烙,Child組件也被更新了:
    image.png

    原因是Child組件被App組件所包裹理卑,而執(zhí)行setN的時(shí)候,App組件被重新渲染了蔽氨,那么在其之中的Child組件自然也就被重新渲染了藐唠。
    所以這時(shí)候我們就需要用到memo來優(yōu)化一下,使得我在執(zhí)行setN的時(shí)候鹉究,Child組件不會(huì)跟著一起被渲染宇立。
    memo的使用也非常簡單,直接用它包裹需要被優(yōu)化的組件即可自赔,在本例中就是Child組件妈嘹,所以代碼可以修改為如下:
    image.png

    這時(shí)候我們執(zhí)行setN的時(shí)候就不會(huì)使得Child組件跟著重新渲染了,只有執(zhí)行setM的時(shí)候Child才會(huì)重新渲染:
    image.png

  • useCallback
    在上面使用memo的例子中绍妨,存在一個(gè)問題润脸,當(dāng)Child接收的props中存在函數(shù)的時(shí)候,之前使用memo做的優(yōu)化就無效了他去,比如下面的代碼:

    image.png

    結(jié)果:
    image.png

    原因和之前一樣毙驯,由于App組件的重新渲染,所以const test = () => {}這段代碼也被重新執(zhí)行了灾测,而test是一個(gè)函數(shù)爆价,函數(shù)是引用類型,所以傳入到Child中的test也和之前的test函數(shù)不一樣媳搪,導(dǎo)致Child組件重新渲染铭段。
    這時(shí)候我們就可以使用useCallback對其進(jìn)行優(yōu)化了。
    useCallback接收兩個(gè)參數(shù)蛾号,首參是一個(gè)函數(shù)稠项,在本例子中就是test函數(shù),第二個(gè)參數(shù)是一個(gè)數(shù)組鲜结,這個(gè)數(shù)組接收的是改變這個(gè)函數(shù)引用的依賴展运,比如下面例子,m的值被改變的時(shí)候精刷,test函數(shù)的引用才會(huì)被改變拗胜,Child組件才會(huì)被重新渲染:
    image.png

    優(yōu)化結(jié)果:
    image.png

  • useMemo類似于vue中computed的功能,他接收兩個(gè)參數(shù)怒允,第一個(gè)參數(shù)是一個(gè)函數(shù)并通過計(jì)算得出一個(gè)state埂软,第二個(gè)參數(shù)是計(jì)算這個(gè)state所需要的依賴,比如下面的例子,Child組件接收多一個(gè)props: num勘畔,這個(gè)num是n與m相加得出的:
    image.png

    結(jié)果:
    image.png

useRef和forwardRef

  • useRef
    useRef接收一個(gè)參數(shù)作為初始值所灸,返回一個(gè)可變的 ref 對象,這個(gè) ref 對象含有.current屬性炫七,該屬性可以在整個(gè)組件色生命周期內(nèi)不變爬立。
    通常useRef被用作獲取某個(gè)Dom,比如下面的例子:

    image.png

    image.png

  • forwardRef
    forwardRef這個(gè)函數(shù)用的場景相對較少万哪,它主要用于在父組件獲取子組件的Dom作為自己的ref的時(shí)候使用侠驯,比如下面的例子:

    image.png

    但實(shí)際上這樣做是有問題的,會(huì)報(bào)錯(cuò)奕巍,并且buttonRef也沒有獲取到:
    image.png

    這時(shí)候我們就可以使用forwardRef對Button組件進(jìn)行包裹吟策,forwardRef會(huì)為Button組件注入一個(gè)新的參數(shù)ref:
    image.png

    這時(shí)候父組件就可以獲取得到這個(gè)子組件的Dom了:
    image.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市的止,隨后出現(xiàn)的幾起案子檩坚,更是在濱河造成了極大的恐慌,老刑警劉巖冲杀,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件效床,死亡現(xiàn)場離奇詭異,居然都是意外死亡权谁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門憋沿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旺芽,“玉大人,你說我怎么就攤上這事辐啄〔烧拢” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵壶辜,是天一觀的道長悯舟。 經(jīng)常有香客問我,道長砸民,這世上最難降的妖魔是什么抵怎? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮岭参,結(jié)果婚禮上反惕,老公的妹妹穿的比我還像新娘。我一直安慰自己演侯,他們只是感情好姿染,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秒际,像睡著了一般悬赏。 火紅的嫁衣襯著肌膚如雪狡汉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天闽颇,我揣著相機(jī)與錄音盾戴,去河邊找鬼。 笑死进萄,一個(gè)胖子當(dāng)著我的面吹牛捻脖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播中鼠,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼可婶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了援雇?” 一聲冷哼從身側(cè)響起矛渴,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惫搏,沒想到半個(gè)月后具温,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筐赔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年铣猩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茴丰。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡达皿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贿肩,到底是詐尸還是另有隱情峦椰,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布汰规,位于F島的核電站汤功,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏溜哮。R本人自食惡果不足惜滔金,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茬射。 院中可真熱鬧鹦蠕,春花似錦、人聲如沸在抛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肠阱,卻和暖如春票唆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屹徘。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工走趋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人噪伊。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓簿煌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鉴吹。 傳聞我的和親對象是個(gè)殘疾皇子姨伟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容