<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【React】1238- 完整上手 React Hook

          共 16858字,需瀏覽 34分鐘

           ·

          2022-02-22 10:56

          HookReact 16.8 的新增特性。它可以讓你在不編寫(xiě) class 的情況下使用 state 以及其他的 React 特性。本篇文章將介紹React Hook相關(guān)知識(shí)。

          1. Hook 和函數(shù)組件

          復(fù)習(xí)一下, React 的函數(shù)組件是這樣的:

          const?Example?=?(props)?=>?{
          ??//?你可以在這使用?Hook
          ??return?<div?/>;
          }

          或是這樣:

          function?Example(props)?{
          ??//?你可以在這使用?Hook
          ??return?<div?/>;
          }

          你之前可能把它們叫做“「無(wú)狀態(tài)組件」”。但現(xiàn)在我們?yōu)樗鼈円肓耸褂?React state 的能力,所以我們更喜歡叫它”函數(shù)組件”。

          Hookclass 內(nèi)部是不起作用的。但你可以使用它們來(lái)取代 class

          「什么是 Hook ?」

          Hook 是一個(gè)特殊的函數(shù),它可以讓你“鉤入” React 的特性。例如,useState 是允許你在 React 函數(shù)組件中添加 stateHook。稍后我們將學(xué)習(xí)其他 Hook

          2. useState

          如果你之前在 React 中使用過(guò) class,這段代碼看起來(lái)應(yīng)該很熟悉:

          class?Example?extends?React.Component?{
          ??constructor(props)?{
          ????super(props);
          ????this.state?=?{
          ??????count:?0
          ????};
          ??}

          ??render()?{
          ????return?(
          ??????<div>
          ????????<p>You?clicked?{this.state.count}?timesp>

          ????????<button?onClick={()?=>?this.setState({?count:?this.state.count?+?1?})}>
          ??????????Click?me
          ????????button>
          ??????div>
          ????);
          ??}
          }

          state 初始值為 { count: 0 } ,當(dāng)用戶(hù)點(diǎn)擊按鈕后,我們通過(guò)調(diào)用 this.setState() 來(lái)增加 state.count

          useState 用于在函數(shù)組件中調(diào)用給組件添加一些內(nèi)部狀態(tài) state,正常情況下純函數(shù)不能存在狀態(tài)副作用,通過(guò)調(diào)用該 Hook 函數(shù)可以給函數(shù)組件注入狀態(tài) state

          我們先將上述示例改成函數(shù)組件:

          import?React,?{?useState?}?from?'react';

          function?Example()?{
          ??//?聲明一個(gè)叫?"count"?的?state?變量
          ??const?[count,?setCount]?=?useState(0);

          ??return?(
          ????<div>
          ??????<p>You?clicked?{count}?timesp>

          ??????<button?onClick={()?=>?setCount(count?+?1)}>
          ????????Click?me
          ??????button>
          ????div>
          ??);
          }

          「調(diào)用 useState 方法的時(shí)候做了什么?」 它定義一個(gè) “state 變量”。我們的變量叫 count, 但是我們可以叫他任何名字,比如 banana。這是一種在函數(shù)調(diào)用時(shí)保存變量的方式 —— useState 是一種新方法,它與 class 里面的 this.state 提供的功能完全相同。一般來(lái)說(shuō),在函數(shù)退出后變量就會(huì)”消失”,而 state 中的變量會(huì)被 React 保留。

          「useState 需要哪些參數(shù)?」 useState() 方法里面唯一的參數(shù)就是初始 state。不同于 class 的是,我們可以按照需要使用數(shù)字或字符串對(duì)其進(jìn)行賦值,而不一定是對(duì)象。在示例中,只需使用數(shù)字來(lái)記錄用戶(hù)點(diǎn)擊次數(shù),所以我們傳了 0 作為變量的初始 state。(如果我們想要在 state 中存儲(chǔ)兩個(gè)不同的變量,只需調(diào)用 useState() 兩次即可。)

          「useState方法的返回值是什么?」 返回值為:當(dāng)前 state 以及更新 state 的函數(shù)。這就是我們寫(xiě) const [count, setCount] = useState() 的原因。這與 class 里面 this.state.countthis.setState 類(lèi)似,唯一區(qū)別就是你需要成對(duì)的獲取它們。

          既然我們知道了 useState 的作用,我們的示例應(yīng)該更容易理解了。

          3. useEffect

          數(shù)據(jù)獲取,設(shè)置訂閱以及手動(dòng)更改 React 組件中的 DOM 都屬于副作用。不管你知不知道這些操作,或是“副作用”這個(gè)名字,應(yīng)該都在組件中使用過(guò)它們。

          React 組件中有兩種常見(jiàn)副作用操作:需要清除的和不需要清除的。我們來(lái)更仔細(xì)地看一下他們之間的區(qū)別。

          3.1 無(wú)需清除的 effect

          有時(shí)候,我們只想「在 React 更新 DOM 之后運(yùn)行一些額外的代碼。「比如」發(fā)送網(wǎng)絡(luò)請(qǐng)求」,手動(dòng)變更 DOM,「記錄日志」,這些都是常見(jiàn)的無(wú)需清除的操作。因?yàn)槲覀冊(cè)趫?zhí)行完這些操作之后,就可以忽略他們了。讓我們對(duì)比一下使用 classHook 都是怎么實(shí)現(xiàn)這些副作用的。

          在 React 的 class 組件中,render 函數(shù)是不應(yīng)該有任何副作用的。一般來(lái)說(shuō),在這里執(zhí)行操作太早了,我們基本上都希望在 React 更新 DOM 之后才執(zhí)行我們的操作。

          這就是為什么在 React class 中,我們把副作用操作放到 componentDidMountcomponentDidUpdate 函數(shù)中。回到示例中,這是一個(gè) React 計(jì)數(shù)器的 class 組件。它在 React 對(duì) DOM 進(jìn)行操作之后,立即更新了 document 的 title 屬性

          class?Example?extends?React.Component?{
          ??constructor(props)?{
          ????super(props);
          ????this.state?=?{
          ??????count:?0
          ????};
          ??}

          ??componentDidMount()?{
          ????document.title?=?`You?clicked?${this.state.count}?times`;
          ??}
          ??componentDidUpdate()?{
          ????document.title?=?`You?clicked?${this.state.count}?times`;
          ??}

          ??render()?{
          ????return?(
          ??????<div>
          ????????<p>You?clicked?{this.state.count}?timesp>

          ????????<button?onClick={()?=>?this.setState({?count:?this.state.count?+?1?})}>
          ??????????Click?me
          ????????button>
          ??????div>
          ????);
          ??}
          }

          注意,「在這個(gè) class 中,我們需要在兩個(gè)生命周期函數(shù)中編寫(xiě)重復(fù)的代碼。」

          這是因?yàn)楹芏嗲闆r下,我們希望在組件加載和更新時(shí)執(zhí)行同樣的操作。從概念上說(shuō),我們希望它在每次渲染之后執(zhí)行 —— 但 React 的 class 組件沒(méi)有提供這樣的方法。即使我們提取出一個(gè)方法,我們還是要在兩個(gè)地方調(diào)用它。

          現(xiàn)在讓我們來(lái)看看如何使用 useEffect 執(zhí)行相同的操作。

          import?React,?{?useState,?useEffect?}?from?'react';

          function?Example()?{
          ??const?[count,?setCount]?=?useState(0);

          ??useEffect(()?=>?{
          ????document.title?=?`You?clicked?${count}?times`;
          ??});

          ??return?(
          ????<div>
          ??????<p>You?clicked?{count}?timesp>

          ??????<button?onClick={()?=>?setCount(count?+?1)}>
          ????????Click?me
          ??????button>
          ????div>
          ??);
          }

          useEffect 做了什么?」 通過(guò)使用這個(gè) Hook,你可以告訴 React 組件需要在渲染后執(zhí)行某些操作。React 會(huì)保存你傳遞的函數(shù)(我們將它稱(chēng)之為 “effect”),并且在執(zhí)行 DOM 更新之后調(diào)用它。在這個(gè) effect 中,我們?cè)O(shè)置了 document 的 title 屬性,不過(guò)我們也可以執(zhí)行數(shù)據(jù)獲取或調(diào)用其他命令式的 API。

          「為什么在組件內(nèi)部調(diào)用 useEffect?」useEffect 放在組件內(nèi)部讓我們可以在 effect 中直接訪問(wèn) count state 變量(或其他 props)。我們不需要特殊的 API 來(lái)讀取它 —— 它已經(jīng)保存在函數(shù)作用域中。Hook 使用了 JavaScript 的閉包機(jī)制,而不用在 JavaScript 已經(jīng)提供了解決方案的情況下,還引入特定的 React API。

          useEffect 會(huì)在每次渲染后都執(zhí)行嗎?」 是的,默認(rèn)情況下,它在第一次渲染之后和每次更新之后都會(huì)執(zhí)行。(我們稍后會(huì)談到如何控制它。React 保證了每次運(yùn)行 effect 的同時(shí),DOM 都已經(jīng)更新完畢。

          3.2 需要清除的 effect

          之前,我們研究了如何使用不需要清除的副作用,還有一些副作用是需要清除的。例如「訂閱外部數(shù)據(jù)源」。這種情況下,清除工作是非常重要的,可以防止引起內(nèi)存泄露!現(xiàn)在讓我們來(lái)比較一下如何用 Class 和 Hook 來(lái)實(shí)現(xiàn)。

          在 React class 中,你通常會(huì)在 componentDidMount 中設(shè)置訂閱,并在 componentWillUnmount 中清除它。例如,假設(shè)我們有一個(gè) ChatAPI 模塊,它允許我們訂閱好友的在線(xiàn)狀態(tài)。以下是我們?nèi)绾问褂?class 訂閱和顯示該狀態(tài):

          class?FriendStatus?extends?React.Component?{
          ??constructor(props)?{
          ????super(props);
          ????this.state?=?{?isOnline:?null?};
          ????this.handleStatusChange?=?this.handleStatusChange.bind(this);
          ??}

          ??componentDidMount()?{
          ????ChatAPI.subscribeToFriendStatus(
          ??????this.props.friend.id,
          ??????this.handleStatusChange
          ????);
          ??}
          ??componentWillUnmount()?{
          ????ChatAPI.unsubscribeFromFriendStatus(
          ??????this.props.friend.id,
          ??????this.handleStatusChange
          ????);
          ??}
          ??handleStatusChange(status)?{
          ????this.setState({
          ??????isOnline:?status.isOnline
          ????});
          ??}

          ??render()?{
          ????if?(this.state.isOnline?===?null)?{
          ??????return?'Loading...';
          ????}
          ????return?this.state.isOnline???'Online'?:?'Offline';
          ??}
          }

          你會(huì)注意到 componentDidMountcomponentWillUnmount 之間相互對(duì)應(yīng)。使用生命周期函數(shù)迫使我們拆分這些邏輯代碼,即使這兩部分代碼都作用于相同的副作用。

          那么如何使用 Hook 編寫(xiě)這個(gè)組件?

          你可能認(rèn)為需要單獨(dú)的 effect 來(lái)執(zhí)行清除操作。但由于添加和刪除訂閱的代碼的緊密性,所以 useEffect 的設(shè)計(jì)是在同一個(gè)地方執(zhí)行。如果你的 effect 返回一個(gè)函數(shù),React 將會(huì)在執(zhí)行清除操作時(shí)調(diào)用它:

          import?React,?{?useState,?useEffect?}?from?'react';

          function?FriendStatus(props)?{
          ??const?[isOnline,?setIsOnline]?=?useState(null);

          ??useEffect(()?=>?{
          ????function?handleStatusChange(status)?{
          ??????setIsOnline(status.isOnline);
          ????}
          ????ChatAPI.subscribeToFriendStatus(props.friend.id,?handleStatusChange);
          ????//?Specify?how?to?clean?up?after?this?effect:
          ????return?function?cleanup()?{
          ??????ChatAPI.unsubscribeFromFriendStatus(props.friend.id,?handleStatusChange);
          ????};
          ??});

          ??if?(isOnline?===?null)?{
          ????return?'Loading...';
          ??}
          ??return?isOnline???'Online'?:?'Offline';
          }

          「為什么要在 effect 中返回一個(gè)函數(shù)?」 這是 effect 可選的清除機(jī)制。每個(gè) effect 都可以返回一個(gè)清除函數(shù)。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分。

          「React 何時(shí)清除 effect?」 React 會(huì)在組件卸載的時(shí)候執(zhí)行清除操作。正如之前學(xué)到的,effect 在每次渲染的時(shí)候都會(huì)執(zhí)行。這就是為什么 React 會(huì)在執(zhí)行當(dāng)前 effect 之前對(duì)上一個(gè) effect 進(jìn)行清除。

          3.3 性能優(yōu)化

          在某些情況下,每次渲染后都執(zhí)行清理或者執(zhí)行 effect 可能會(huì)導(dǎo)致性能問(wèn)題。在 class 組件中,我們可以通過(guò)在 componentDidUpdate 中添加對(duì) prevPropsprevState 的比較邏輯解決:

          componentDidUpdate(prevProps,?prevState)?{
          ??if?(prevState.count?!==?this.state.count)?{
          ????document.title?=?`You?clicked?${this.state.count}?times`;
          ??}
          }

          這是很常見(jiàn)的需求,所以它被內(nèi)置到了 useEffect 的 Hook API 中。如果某些特定值在兩次重渲染之間沒(méi)有發(fā)生變化,你可以通知 React 跳過(guò)對(duì) effect 的調(diào)用,只要傳遞數(shù)組作為 useEffect 的第二個(gè)可選參數(shù)即可:

          useEffect(()?=>?{
          ??document.title?=?`You?clicked?${count}?times`;
          },?[count]);?//?僅在?count?更改時(shí)更新

          上面這個(gè)示例中,我們傳入 [count] 作為第二個(gè)參數(shù)。這個(gè)參數(shù)是什么作用呢?如果 count 的值是 5,而且我們的組件重渲染的時(shí)候 count 還是等于 5,React 將對(duì)前一次渲染的 [5] 和后一次渲染的 [5] 進(jìn)行比較。因?yàn)閿?shù)組中的所有元素都是相等的(5 === 5),React 會(huì)跳過(guò)這個(gè) effect,這就實(shí)現(xiàn)了性能的優(yōu)化。

          當(dāng)渲染時(shí),如果 count 的值更新成了 6,React 將會(huì)把前一次渲染時(shí)的數(shù)組 [5] 和這次渲染的數(shù)組 [6] 中的元素進(jìn)行對(duì)比。這次因?yàn)?5 !== 6,React 就會(huì)再次調(diào)用 effect。如果數(shù)組中有多個(gè)元素,即使只有一個(gè)元素發(fā)生變化,React 也會(huì)執(zhí)行 effect。

          如果想執(zhí)行只運(yùn)行一次的 effect(僅在組件掛載和卸載時(shí)執(zhí)行),可以傳遞一個(gè)空數(shù)組([])作為第二個(gè)參數(shù)。這就告訴 React 你的 effect 不依賴(lài)于 props 或 state 中的任何值,所以它永遠(yuǎn)都不需要重復(fù)執(zhí)行。這并不屬于特殊情況 —— 它依然遵循依賴(lài)數(shù)組的工作方式。

          如果你傳入了一個(gè)空數(shù)組([]),effect 內(nèi)部的 props 和 state 就會(huì)一直擁有其初始值。盡管傳入 [] 作為第二個(gè)參數(shù)更接近大家更熟悉的 componentDidMountcomponentWillUnmount 思維模式,但我們有更好的方式(后面會(huì)介紹)來(lái)避免過(guò)于頻繁的重復(fù)調(diào)用 effect。除此之外,請(qǐng)記得 React 會(huì)等待瀏覽器完成畫(huà)面渲染之后才會(huì)延遲調(diào)用 useEffect,因此會(huì)使得額外操作很方便。

          4. useContext

          Context 提供了一個(gè)無(wú)需為每層組件手動(dòng)添加 props ,就能在組件樹(shù)間進(jìn)行數(shù)據(jù)傳遞的方法,useContext 用于函數(shù)組件中訂閱上層 context 的變更,可以獲取上層 context 傳遞的 value prop 值

          useContext 接收一個(gè) context 對(duì)象(React.createContext的返回值)并返回 context 的當(dāng)前值,當(dāng)前的 context 值由上層組件中距離當(dāng)前組件最近的 value prop 決定

          const?value?=?useContext(MyContext);

          看完下面案例你將會(huì)知道如何使用

          import?React,?{?useContext,?useState?}?from?'react';

          const?themes?=?{
          ??light:?{
          ????foreground:?"#000000",
          ????background:?"#eeeeee"
          ??},
          ??dark:?{
          ????foreground:?"#ffffff",
          ????background:?"#222222"
          ??}
          };

          //?為當(dāng)前?theme?創(chuàng)建一個(gè)?context
          const?ThemeContext?=?React.createContext();

          export?default?function?Toolbar(props)?{
          ??const?[theme,?setTheme]?=?useState(themes.dark);

          ??const?toggleTheme?=?()?=>?{
          ????setTheme(currentTheme?=>?(
          ??????currentTheme?===?themes.dark
          ??????????themes.light
          ????????:?themes.dark
          ????));
          ??};

          ??return?(
          ????//?使用?Provider?將當(dāng)前?props.value?傳遞給內(nèi)部組件
          ????<ThemeContext.Provider?value={{theme,?toggleTheme}}>
          ??????<ThemeButton?/>
          ????ThemeContext.Provider>

          ??);
          }

          function?ThemeButton()?{
          ??//?通過(guò)?useContext?獲取當(dāng)前?context?值
          ??const?{?theme,?toggleTheme?}?=?useContext(ThemeContext);
          ??
          ??return?(
          ????<button?style={{background:?theme.background,?color:?theme.foreground?}}?onClick={toggleTheme}>
          ??????Change?the?button's?theme
          ????button>

          ??);
          }

          下面我們看看與之等價(jià) class 示例

          useContext(MyContext) 相當(dāng)于 class 組件中的 static contextType = MyContext 或者

          useContext 并沒(méi)有改變使用 context 的方式,它只為我們提供了一種額外的、更漂亮的方法來(lái)使用上層 context。在將其應(yīng)用于使用多 context 的組件時(shí)將會(huì)非常有用

          import?React?from?'react';

          const?themes?=?{
          ??light:?{
          ????foreground:?"#000000",
          ????background:?"#eeeeee"
          ??},
          ??dark:?{
          ????foreground:?"#ffffff",
          ????background:?"#222222"
          ??}
          };

          const?ThemeContext?=?React.createContext(themes.light);

          function?ThemeButton()?{
          ??return?(
          ????<ThemeContext.Consumer>
          ??????{
          ????????({theme,?toggleTheme})?=>?(
          ??????????<button?style={{background:?theme.background,?color:?theme.foreground?}}?onClick={toggleTheme}>
          ????????????Change?the?button's?theme
          ??????????button>

          ????????)
          ??????}
          ????ThemeContext.Consumer>
          ??);
          }

          export?default?class?Toolbar?extends?React.Component?{
          ??constructor(props)?{
          ????super(props);
          ????
          ????this.state?=?{
          ??????theme:?themes.light
          ????};

          ????this.toggleTheme?=?this.toggleTheme.bind(this);
          ??}

          ??toggleTheme()?{
          ????this.setState(state?=>?({
          ??????theme:
          ????????state.theme?===?themes.dark
          ????????????themes.light
          ??????????:?themes.dark
          ????}));
          ??}

          ??render()?{
          ????return?(
          ??????<ThemeContext.Provider?value={{?theme:?this.state.theme,?toggleTheme:?this.toggleTheme?}}>
          ????????<ThemeButton?/>
          ??????ThemeContext.Provider>

          ????)
          ??}

          5. useMemo

          const?memoizedValue?=?useMemo(()?=>?computeExpensiveValue(a,?b),?[a,?b]);

          把“創(chuàng)建”函數(shù)和依賴(lài)項(xiàng)數(shù)組作為參數(shù)傳入 useMemo,它僅會(huì)在某個(gè)依賴(lài)項(xiàng)改變時(shí)才重新計(jì)算 memoized 值。這種優(yōu)化有助于避免在每次渲染時(shí)都進(jìn)行高開(kāi)銷(xiāo)的計(jì)算。

          記住,傳入 useMemo 的函數(shù)會(huì)在渲染期間執(zhí)行。請(qǐng)不要在這個(gè)函數(shù)內(nèi)部執(zhí)行與渲染無(wú)關(guān)的操作,諸如副作用這類(lèi)的操作屬于 useEffect 的適用范疇,而不是 useMemo

          如果沒(méi)有提供依賴(lài)項(xiàng)數(shù)組,useMemo 在每次渲染時(shí)都會(huì)計(jì)算新的值。

          6. useCallback

          const?memoizedCallback?=?useCallback(
          ??()?=>?{
          ????doSomething(a,?b);
          ??},
          ??[a,?b],
          );

          把內(nèi)聯(lián)回調(diào)函數(shù)及依賴(lài)項(xiàng)數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本,該回調(diào)函數(shù)僅在某個(gè)依賴(lài)項(xiàng)改變時(shí)才會(huì)更新。當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過(guò)優(yōu)化的并使用引用相等性去避免非必要渲染,在 props 屬性相同情況下,React 將跳過(guò)渲染組件的操作并直接復(fù)用最近一次渲染的結(jié)果。

          useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)

          7. useRef

          const?refContainer?=?useRef(initialValue);

          useRef 返回一個(gè)可變的 ref 對(duì)象,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)持續(xù)存在。

          一個(gè)常見(jiàn)的用例便是命令式地訪問(wèn)子組件:

          function?TextInputWithFocusButton()?{
          ??const?inputEl?=?useRef(null);
          ??const?onButtonClick?=?()?=>?{
          ????//?`current`?指向已掛載到?DOM?上的文本輸入元素
          ????inputEl.current.focus();
          ??};
          ??return?(
          ????<>
          ??????<input?ref={inputEl}?type="text"?/>
          ??????<button?onClick={onButtonClick}>Focus?the?inputbutton>

          ????
          ??);
          }

          本質(zhì)上,useRef 就像是可以在其 .current 屬性中保存一個(gè)可變值的“盒子”。

          你應(yīng)該熟悉 ref 這一種訪問(wèn) DOM的主要方式。如果你將 ref 對(duì)象以

          形式傳入組件,則無(wú)論該節(jié)點(diǎn)如何改變,React 都會(huì)將 ref 對(duì)象的 .current 屬性設(shè)置為相應(yīng)的 DOM 節(jié)點(diǎn)。

          然而,useRef()ref 屬性更有用。它可以「很方便地保存任何可變值」,其類(lèi)似于在 class 中使用實(shí)例字段的方式。

          這是因?yàn)樗鼊?chuàng)建的是一個(gè)普通 Javascript 對(duì)象。而 useRef() 和自建一個(gè) {current: ...} 對(duì)象的唯一區(qū)別是,useRef 會(huì)在每次渲染時(shí)返回同一個(gè) ref 對(duì)象。

          8. useImperativeHandle

          useImperativeHandle(ref,?createHandle,?[deps])

          useImperativeHandle 可以讓你在使用 ref 時(shí)自定義暴露給父組件的實(shí)例值。在大多數(shù)情況下,應(yīng)當(dāng)避免使用 ref 這樣的命令式代碼。useImperativeHandle 應(yīng)當(dāng)與 React.forwardRef 一起使用:

          function?FancyInput(props,?ref)?{
          ??const?inputRef?=?useRef();
          ??useImperativeHandle(ref,?()?=>?({
          ????focus:?()?=>?{
          ??????inputRef.current.focus();
          ????}
          ??}));
          ??return?<input?ref={inputRef}?...?/>;
          }
          FancyInput?=?React.forwardRef(FancyInput);

          9. useLayoutEffect

          useLayoutEffectuseEffect 類(lèi)似,與 useEffect 在瀏覽器 layout 和 painting 完成后異步執(zhí)行 effect 不同的是,它會(huì)在瀏覽器布局 layout 之后,painting 之前同步執(zhí)行 effect。

          useLayoutEffect 的執(zhí)行時(shí)機(jī)對(duì)比如下:

          import?React,?{?useState,?useEffect,?useLayoutEffect?}?from?'react';

          export?default?function?LayoutEffect()?{
          ??const?[width,?setWidth]?=?useState('100px');

          ??//?useEffect?會(huì)在所有?DOM?渲染完成后執(zhí)行?effect?回調(diào)
          ??useEffect(()?=>?{
          ????console.log('effect?width:?',?width);
          ??});
          ??//?useLayoutEffect?會(huì)在所有的?DOM?變更之后同步執(zhí)行?effect?回調(diào)
          ??useLayoutEffect(()?=>?{
          ????console.log('layoutEffect?width:?',?width);
          ??});
          ??
          ??return?(
          ????<>
          ??????<div?id='content'?style={{?width,?background:?'red'?}}>內(nèi)容div>

          ??????<button?onClick={()?=>?setWidth('100px')}>100pxbutton>
          ??????<button?onClick={()?=>?setWidth('200px')}>200pxbutton>
          ??????<button?onClick={()?=>?setWidth('300px')}>300pxbutton>
          ????
          ??);
          }

          //?使用?setTimeout?保證在組件第一次渲染完成后執(zhí)行,獲取到對(duì)應(yīng)的?DOM
          setTimeout(()?=>?{
          ??const?contentEl?=?document.getElementById('content');
          ??//?監(jiān)視目標(biāo)?DOM?結(jié)構(gòu)變更,會(huì)在?useLayoutEffect?回調(diào)執(zhí)行后,useEffect?回調(diào)執(zhí)行前調(diào)用
          ??const?observer?=?new?MutationObserver(()?=>?{
          ????console.log('content?element?layout?updated');
          ??});
          ??observer.observe(contentEl,?{
          ????attributes:?true
          ??});
          },?1000);

          10. useReducer

          const?[state,?dispatch]?=?useReducer(reducer,?initialArg,?init);

          useState 的替代方案。它接收一個(gè)形如 (state, action) => newStatereducer,并返回當(dāng)前的 state 以及與其配套的 dispatch 方法。(如果你熟悉 Redux 的話(huà),就已經(jīng)知道它如何工作了。)

          在某些場(chǎng)景下,useReducer 會(huì)比 useState 更適用,例如 state 邏輯較復(fù)雜且包含多個(gè)子值,或者下一個(gè) state 依賴(lài)于之前的 state 等。

          以下是用 reducer 重寫(xiě) useState 一節(jié)的計(jì)數(shù)器示例:

          const?initialState?=?{count:?0};

          function?reducer(state,?action)?{
          ??switch?(action.type)?{
          ????case?'increment':
          ??????return?{count:?state.count?+?1};
          ????case?'decrement':
          ??????return?{count:?state.count?-?1};
          ????default:
          ??????throw?new?Error();
          ??}
          }

          function?Counter()?{
          ??const?[state,?dispatch]?=?useReducer(reducer,?initialState);
          ??return?(
          ????<>
          ??????Count:?{state.count}
          ??????<button?onClick={()?=>?dispatch({type:?'decrement'})}>-button>

          ??????<button?onClick={()?=>?dispatch({type:?'increment'})}>+button>
          ????
          ??);
          }

          useReducer 初始化 state 的方式有兩種

          //?方式1
          const?[state,?dispatch]?=?useReducer(
          ?reducer,
          ??{count:?initialCount}
          );

          //?方式2
          function?init(initialClunt)?{
          ??return?{count:?initialClunt};
          }

          const?[state,?dispatch]?=?useReducer(reducer,?initialCount,?init);

          11. Hook 規(guī)則

          Hook 本質(zhì)就是 JavaScript 函數(shù),但是在使用它時(shí)需要遵循兩條規(guī)則。我們提供了一個(gè) linter 插件來(lái)強(qiáng)制執(zhí)行這些規(guī)則:

          1. 「只在最頂層使用 Hook」

          「不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook,」 確保總是在你的 React 函數(shù)的最頂層以及任何 return 之前調(diào)用他們。遵守這條規(guī)則,你就能確保 Hook 在每一次渲染中都按照同樣的順序被調(diào)用。這讓 React 能夠在多次的 useStateuseEffect 調(diào)用之間保持 hook 狀態(tài)的正確。

          1. 「只在 React 函數(shù)中調(diào)用 Hook」

          「不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook。」 你可以:

          • ? 在 React 的函數(shù)組件中調(diào)用 Hook
          • ? 在自定義 Hook 中調(diào)用其他 Hook

          遵循此規(guī)則,確保組件的狀態(tài)邏輯在代碼中清晰可見(jiàn)。

          官方發(fā)布了一個(gè)名為 eslint-plugin-react-hooks 的 ESLint 插件來(lái)強(qiáng)制執(zhí)行這兩條規(guī)則。如果你想嘗試一下,可以將此插件添加到你的項(xiàng)目中:

          打算后續(xù)版本默認(rèn)添加此插件到 Create React App 及其他類(lèi)似的工具包中。

          npm?install?eslint-plugin-react-hooks?--save-dev
          //?你的?ESLint?配置
          {
          ??"plugins":?[
          ????//?...
          ????"react-hooks"
          ??],
          ??"rules":?{
          ????//?...
          ????"react-hooks/rules-of-hooks":?"error",?//?檢查?Hook?的規(guī)則
          ????"react-hooks/exhaustive-deps":?"warn"?//?檢查?effect?的依賴(lài)
          ??}
          }

          12. 自定義 Hook

          通過(guò)自定義 Hook,可以將組件邏輯提取到可重用的函數(shù)中。

          在我們學(xué)習(xí)useEffect 時(shí),我們已經(jīng)見(jiàn)過(guò)這個(gè)聊天程序中的組件,該組件用于顯示好友的在線(xiàn)狀態(tài):

          import?React,?{?useState,?useEffect?}?from?'react';

          function?FriendStatus(props)?{
          ??const?[isOnline,?setIsOnline]?=?useState(null);
          ??useEffect(()?=>?{
          ????function?handleStatusChange(status)?{
          ??????setIsOnline(status.isOnline);
          ????}
          ????ChatAPI.subscribeToFriendStatus(props.friend.id,?handleStatusChange);
          ????return?()?=>?{
          ??????ChatAPI.unsubscribeFromFriendStatus(props.friend.id,?handleStatusChange);
          ????};
          ??});

          ??if?(isOnline?===?null)?{
          ????return?'Loading...';
          ??}
          ??return?isOnline???'Online'?:?'Offline';
          }

          現(xiàn)在我們假設(shè)聊天應(yīng)用中有一個(gè)聯(lián)系人列表,當(dāng)用戶(hù)在線(xiàn)時(shí)需要把名字設(shè)置為綠色。我們可以把上面類(lèi)似的邏輯復(fù)制并粘貼到 FriendListItem 組件中來(lái),但這并不是理想的解決方案:

          import?React,?{?useState,?useEffect?}?from?'react';

          function?FriendListItem(props)?{
          ??const?[isOnline,?setIsOnline]?=?useState(null);
          ??useEffect(()?=>?{
          ????function?handleStatusChange(status)?{
          ??????setIsOnline(status.isOnline);
          ????}
          ????ChatAPI.subscribeToFriendStatus(props.friend.id,?handleStatusChange);
          ????return?()?=>?{
          ??????ChatAPI.unsubscribeFromFriendStatus(props.friend.id,?handleStatusChange);
          ????};
          ??});

          ??return?(
          ????<li?style={{?color:?isOnline???'green'?:?'black'?}}>
          ??????{props.friend.name}
          ????li>

          ??);
          }

          相反,我們希望在 FriendStatusFriendListItem 之間共享邏輯。

          目前為止,在 React 中有兩種流行的方式來(lái)共享組件之間的狀態(tài)邏輯: render props 和高階組件,現(xiàn)在讓我們來(lái)看看 Hook 是如何在讓你不增加組件的情況下解決相同問(wèn)題的。

          1. 「提取自定義 Hook」

          當(dāng)我們想在兩個(gè)函數(shù)之間共享邏輯時(shí),我們會(huì)把它提取到第三個(gè)函數(shù)中。而組件和 Hook 都是函數(shù),所以也同樣適用這種方式。

          「自定義 Hook 是一個(gè)函數(shù),其名稱(chēng)以 “use” 開(kāi)頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook。」 例如,下面的 useFriendStatus 是我們第一個(gè)自定義的 Hook:

          import?{?useState,?useEffect?}?from?'react';

          function?useFriendStatus(friendID)?{
          ??const?[isOnline,?setIsOnline]?=?useState(null);

          ??useEffect(()?=>?{
          ????function?handleStatusChange(status)?{
          ??????setIsOnline(status.isOnline);
          ????}

          ????ChatAPI.subscribeToFriendStatus(friendID,?handleStatusChange);
          ????return?()?=>?{
          ??????ChatAPI.unsubscribeFromFriendStatus(friendID,?handleStatusChange);
          ????};
          ??});

          ??return?isOnline;
          }

          此處并未包含任何新的內(nèi)容——邏輯是從上述組件拷貝來(lái)的。與組件中一致,請(qǐng)確保只在自定義 Hook 的頂層無(wú)條件地調(diào)用其他 Hook。

          與 React 組件不同的是,自定義 Hook 不需要具有特殊的標(biāo)識(shí)。我們可以自由的決定它的參數(shù)是什么,以及它應(yīng)該返回什么(如果需要的話(huà))。換句話(huà)說(shuō),它就像一個(gè)正常的函數(shù)。但是它的名字應(yīng)該始終以 use 開(kāi)頭,這樣可以一眼看出其符合 「Hook 的規(guī)則」

          此處 useFriendStatus 的 Hook 目的是訂閱某個(gè)好友的在線(xiàn)狀態(tài)。這就是我們需要將 friendID 作為參數(shù),并返回這位好友的在線(xiàn)狀態(tài)的原因。

          function?useFriendStatus(friendID)?{
          ??const?[isOnline,?setIsOnline]?=?useState(null);

          ??//?...

          ??return?isOnline;
          }

          現(xiàn)在讓我們看看應(yīng)該如何使用自定義 Hook。

          1. 「使用自定義 Hook」

          我們一開(kāi)始的目標(biāo)是在 FriendStatusFriendListItem 組件中去除重復(fù)的邏輯,即:這兩個(gè)組件都想知道好友是否在線(xiàn)。

          現(xiàn)在我們已經(jīng)把這個(gè)邏輯提取到 useFriendStatus 的自定義 Hook 中,然后就可以使用它了:

          function?FriendStatus(props)?{
          ??const?isOnline?=?useFriendStatus(props.friend.id);
          ??if?(isOnline?===?null)?{
          ????return?'Loading...';
          ??}
          ??return?isOnline???'Online'?:?'Offline';
          }

          function?FriendListItem(props)?{
          ??const?isOnline?=?useFriendStatus(props.friend.id);
          ??return?(
          ????<li?style={{?color:?isOnline???'green'?:?'black'?}}>
          ??????{props.friend.name}
          ????li>

          ??);
          }

          「這段代碼等價(jià)于原來(lái)的示例代碼嗎?」 等價(jià),它的工作方式完全一樣。如果你仔細(xì)觀察,你會(huì)發(fā)現(xiàn)我們沒(méi)有對(duì)其行為做任何的改變,我們只是將兩個(gè)函數(shù)之間一些共同的代碼提取到單獨(dú)的函數(shù)中。「自定義 Hook 是一種自然遵循 Hook 設(shè)計(jì)的約定,而并不是 React 的特性。」

          「自定義 Hook 必須以 “use” 開(kāi)頭嗎?」 必須如此。這個(gè)約定非常重要。不遵循的話(huà),由于無(wú)法判斷某個(gè)函數(shù)是否包含對(duì)其內(nèi)部 Hook 的調(diào)用,React 將無(wú)法自動(dòng)檢查你的 Hook 是否違反了 「Hook 的規(guī)則」

          「在兩個(gè)組件中使用相同的 Hook 會(huì)共享 state 嗎?」 不會(huì)。自定義 Hook 是一種重用狀態(tài)邏輯的機(jī)制(例如設(shè)置為訂閱并存儲(chǔ)當(dāng)前值),所以每次使用自定義 Hook 時(shí),其中的所有 state 和副作用都是完全隔離的。

          「自定義 Hook 如何獲取獨(dú)立的 state?」 每次調(diào)用 Hook,它都會(huì)獲取獨(dú)立的 state。由于我們直接調(diào)用了 useFriendStatus,從 React 的角度來(lái)看,我們的組件只是調(diào)用了 useStateuseEffect

          「參考」

          • Hook 簡(jiǎn)介 – React (reactjs.org)

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(mén)(上)||?Webpack4 入門(mén)(下)
          6.?MobX 入門(mén)(上)?||??MobX 入門(mén)(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 130+ 篇原創(chuàng)文章

          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天干天天操青青草 | 操bb影视 | 丁香五月婷婷啪啪啪 | 青青草成人在线观看 | 日韩 欧美 高清 |