<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 Hooks(萬字長文,快速入門必備)

          共 10631字,需瀏覽 22分鐘

           ·

          2020-10-27 20:00

          什么是 Hooks

          Hook 是 React 16.8 的新增特性。

          Hooks 本質上就是一類特殊的函數(shù),它們可以為你的函數(shù)型組件(function component)注入一些特殊的功能,讓您在不編寫類的情況下使用 state(狀態(tài)) 和其他 React 特性。

          為什么要使用 React Hooks

          • 狀態(tài)邏輯難以復用: 業(yè)務變得復雜之后,組件之間共享狀態(tài)變得頻繁,組件復用和狀態(tài)邏輯管理就變得十分復雜。使用 redux 也會加大項目的復雜度和體積。
          • 組成復雜難以維護: 復雜的組件中有各種難以管理的狀態(tài)和副作用,在同一個生命周期中你可能會因為不同情況寫出各種不相關的邏輯,但實際上我們通常希望一個函數(shù)只做一件事情。
          • 類的 this 指向性問題: 我們用 class 來創(chuàng)建 react 組件時,為了保證 this 的指向正確,我們要經(jīng)常寫這樣的代碼:const that = this,或者是this.handleClick = this.handleClick.bind(this)>;一旦 this 使用錯誤,各種 bug 就隨之而來。

          為了解決這些麻煩,hooks 允許我們使用簡單的特殊函數(shù)實現(xiàn) class 的各種功能。

          useState

          在 React 組件中,我們經(jīng)常要使用 state 來進行數(shù)據(jù)的實時響應,根據(jù) state 的變化重新渲染組件更新視圖。

          因為純函數(shù)不能有狀態(tài),在 hooks 中,useState就是一個用于為函數(shù)組件引入狀態(tài)(state)的狀態(tài)鉤子。

          const?[state,?setState]?=?useState(initialState);

          useState 的唯一參數(shù)是狀態(tài)初始值(initial state),它返回了一個數(shù)組,這個數(shù)組的第[0]項是當前當前的狀態(tài)值,第[1]項是可以改變狀態(tài)值的方法函數(shù)。

          延遲初始化

          initialState 參數(shù)是初始渲染期間使用的狀態(tài)。在隨后的渲染中,它會被忽略了。如果初始狀態(tài)是高開銷的計算結果,則可以改為提供函數(shù),該函數(shù)僅在初始渲染時執(zhí)行

          function?Counter({initialCount?=?0})?{
          ??//?初始值為1
          ??const?[count,?setCount]?=?useState(()?=>?initialCount?+?1);
          ??return?(
          ????<>
          ??????Count:?{count}
          ??????<button?onClick={()?=>?setCount(0)}>Resetbutton>

          ??????<button?onClick={()?=>?setCount(count?+?1)}>+button>
          ??????<button?onClick={()?=>?setCount(prevCount?=>?prevCount?-?1)}>-button>
          ????
          ??);
          }

          函數(shù)式更新對比普通更新

          如果需要使用前一時刻的 state(狀態(tài)) 計算新 state(狀態(tài)) ,則可以將 函數(shù) 傳遞給 setState 。該函數(shù)將接收先前 state 的值,并返回更新的 state

          那么setCount(newCount)setCount(preCount => newCount)有什么區(qū)別呢,我們寫個例子來看下:

          function?Counter()?{
          ??const?[count,?setCount]?=?useState(0);
          ??function?add()?{
          ????setTimeout(()?=>?{
          ??????setCount(count?+?1);
          ????},?3000);
          ??}
          ??function?preAdd(){
          ????setTimeout(()?=>?{
          ??????//?根據(jù)前一時刻的?count?設置新的?count
          ??????setCount(count?=>?count?+?1);
          ????},?3000);
          ??}
          ??//?監(jiān)聽?count?變化
          ??useEffect(()?=>?{
          ????console.log(count)
          ??},?[count])
          ??return?(
          ????<>
          ??????Count:?{count}
          ??????<button?onClick={add}>addbutton>

          ??????<button?onClick={preAdd}>preAddbutton>
          ????
          ??);
          }
          簡單計數(shù)器

          我們首先快速點擊 add 按鈕三次,三秒后 count 變?yōu)?1;然后快速點擊 preAdd 三下,三秒后依次出現(xiàn)了 2、3、4。測試結果如下:

          三次add三次preAdd

          為什么setCount(count + 1)好像只執(zhí)行了一次呢,因為每次更新都是獨立的閉包,當點擊更新狀態(tài)的時候,函數(shù)組件都會重新被調(diào)用。 快速點擊時,當前 count 為 0,即每次點擊傳入的值都是相同的,那么得到的結果也是相同的,最后 count 變?yōu)?1 后不再變化。

          為什么setCount(count => count + 1)好像能執(zhí)行三次呢,因為當傳入一個函數(shù)時,回調(diào)函數(shù)將接收當前的 state,并返回一個更新后的值。 三秒后,第一次setCount獲取到最新的 count 為 1,然后執(zhí)行函數(shù)將 count 變?yōu)?2,接著第二次獲取到當前 count 為 2,執(zhí)行函數(shù)將 count 變?yōu)榱?3。每次獲取到的最新 count 不一樣,最后結果自然也不同。

          那么進行第二次實驗,我先快速點擊 preAdd 三下,然后接著快速點擊 add 按鈕三次,三秒后結果會怎么樣呢。根據(jù)以上結論猜測,preAdd 是根據(jù)最新值,所以 count 依次變?yōu)?1、2、3,然后 add 是傳入的當前 count 為 0,最后變?yōu)?1。最后結果應該是 1、2、3、1,測試結果正確:

          useReducer

          const?[state,?dispatch]?=?useReducer(reducer,?initialState,?initialFunc);

          useReducer 可以接受三個參數(shù),第一個參數(shù)接收一個形如(state, action) => newState 的 reducer 純函數(shù),使用時可以通過dispatch(action)來修改相關邏輯。

          第二個參數(shù)是 state 的初始值,它返回當前 state 以及發(fā)送 action 的 dispatch 函數(shù)。

          你可以選擇惰性地創(chuàng)建初始 state,為此,需要將 init 函數(shù)作為 useReducer 的第三個參數(shù)傳入,這樣初始 state 將被設置為 init(initialArg)。

          對比 useState 的優(yōu)勢

          useReducer 是 React 提供的一個高級 Hook,它不像 useEffect、useState 等 hook 一樣必須,那么使用它有什么好處呢?如果使用 useReducer 改寫一下計數(shù)器例子:

          //官方示例
          function?countReducer(state,?action)?{
          ??switch?(action.type)?{
          ????case?'add':
          ??????return?state?+?1;
          ????case?'minus':
          ??????return?state?-?1;
          ????default:
          ??????return?state;
          ??}
          }
          function?initFunc(initialCount)?{
          ??return?initialCount?+?1;
          }
          function?Counter({initialCount?=?0})?{
          ??const?[count,?dispatch]?=?useReducer(countReducer,?initialCount,?initFunc);
          ??return?(
          ????<div>
          ??????<p>Count:?{count}p>

          ??????<button?onClick={()?=>?{?dispatch({?type:?'add'?});?}}?>
          ????????點擊+1
          ??????button>
          ??????<button?onClick={()?=>?{?dispatch({?type:?'minus'?});?}}?>
          ????????點擊-1
          ??????button>
          ????div>
          ??);
          }

          對比 useState 可知,看起來我們的代碼好像變得復雜了,但實際應用到復雜的項目環(huán)境中,將狀態(tài)管理和代碼邏輯放到一起管理,使我們的代碼具有更好的可讀性、可維護性和可預測性。

          useEffect

          useEffect(create,?deps);

          useEffect()用來引入具有副作用的操作,最常見的就是向服務器請求數(shù)據(jù)。該 Hook 接收一個函數(shù),該函數(shù)會在組件渲染到屏幕之后才執(zhí)行

          和 react 類的生命周期相比,useEffect Hook 可以當做 componentDidMount,componentDidUpdate 和 componentWillUnmount 的組合。默認情況下,react 首次渲染和之后的每次渲染都會調(diào)用一遍傳給 useEffect 的函數(shù)。

          useEffect 的性能問題

          因為 React 首次渲染和之后的每次渲染都會調(diào)用一遍傳給 useEffect 的函數(shù),所以大多數(shù)情況下很有可能會產(chǎn)生性能問題。

          為了解決這個問題,可以將數(shù)組作為可選的第二個參數(shù)傳遞給 useEffect。數(shù)組中可選擇性寫 state 中的數(shù)據(jù),代表只有當數(shù)組中的 state 發(fā)生變化是才執(zhí)行函數(shù)內(nèi)的語句,以此可以使用多個useEffect分離函數(shù)關注點。如果是個空數(shù)組,代表只執(zhí)行一次,類似于 componentDidUpdata。

          解綁副作用

          在 React 類中,經(jīng)常會需要在組件卸載時做一些事情,例如移除監(jiān)聽事件等。在 class 組件中,我們可以在 componentWillUnmount 這個生命周期中做這些事情,而在 hooks 中,我們可以通過 useEffect 第一個函數(shù)中 return 一個函數(shù)來實現(xiàn)相同效果。以下是一個簡單的清除定時器例子:

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

          ??useEffect(()?=>?{
          ????const?timer?=?setInterval(()?=>?{
          ??????setCount(count?=>?count?+?1);
          ????},?1000);
          ????return?()?=>?clearInterval(timer);
          ??},?[]);

          ??return?(
          ????<>
          ??????Count:?{count}
          ????
          ??);
          }

          useLayoutEffect

          useLayoutEffect(create,?deps);

          它和 useEffect 的結構相同,區(qū)別只是調(diào)用時機不同。

          • useEffect 在渲染時是異步執(zhí)行,要等到瀏覽器將所有變化渲染到屏幕后才會被執(zhí)行。
          • useLayoutEffect 會在 瀏覽器 layout 之后,painting 之前執(zhí)行,
          • 可以使用 useLayoutEffect 來讀取 DOM 布局并同步觸發(fā)重渲染
          • 盡可能使用標準的 useEffect 以避免阻塞視圖更新

          useEffect 和 useLayoutEffect 的差別

          為了更清晰的對比 useEffect 和 useLayoutEffect,我們寫個 demo 來看看兩種 hook 的效果:

          function?Counter()?{
          ??function?delay(ms){
          ????const?startTime?=?new?Date().getTime();
          ????while?(new?Date().getTime()???}
          ??const?[count,?setCount]?=?useState(0);

          ??//?useLayoutEffect(()?=>?{
          ??//???console.log('useLayoutEffect:',?count)
          ??//???return?()?=>?console.log('useLayoutEffectDestory:',?count)
          ??//?},?[count]);

          ??useEffect(()?=>?{
          ????console.log('useEffect:',?count)
          ????//?延長一秒看效果
          ????if(count?===?5)?{
          ??????delay(1000)
          ??????setCount(count?=>?count?+?1)
          ????}
          ????return?()?=>?console.log('useEffectDestory:',?count)
          ??},?[count]);

          ??return?(
          ????<>
          ??????Count:?{count}
          ??????<button?onClick={()?=>?setCount(5)}>setbutton>

          ????
          ??);
          }

          首先我們先看看 useEffect 的執(zhí)行效果:

          useEffect 和 useEffectDestroy 的執(zhí)行順序也很好理解,先執(zhí)行了 useEffectDestroy 銷毀了 0,然后在 useEffect 修改 count 為 5,這時,count 可見已經(jīng)變成了 5,然后銷毀 5,設置 count 為 6,然后渲染 6。

          整個渲染過程可以很明顯的看到 count 0->5->6 的過程,如果在實際項目中,這種情況會出現(xiàn)閃屏效果,很影響用戶體驗。因為useEffect 在渲染時是異步執(zhí)行,并且要等到瀏覽器將所有變化渲染到屏幕后才會被執(zhí)行,所以,我們盡量不要在 useEffect 里面進行 DOM 操作。

          再將 setCount 操作放到 useLayoutEffect 里的執(zhí)行看看效果:

          useLayoutEffect 和 useLayoutEffectDestroy 的執(zhí)行順序和 useEffect 一樣,都是在下一次操作之前先銷毀,但是整個渲染過程和 useEffect 明顯不一樣。雖然在打印的 useLayoutEffect 中有明顯停頓,但在渲染過程只能看到 count 0->6 的過程,這是因為 useLayoutEffect 的同步特性,會在瀏覽器渲染之前同步更新 DOM 數(shù)據(jù),哪怕是多次的操作,也會在渲染前一次性處理完,再交給瀏覽器繪制。這樣不會導致閃屏現(xiàn)象發(fā)生,但是會阻塞視圖的更新。

          最后,我們同時看看兩個 setCout 分別在兩個 hook 的執(zhí)行時機;

          在 useEffect 執(zhí)行效果:

          在 useLayoutEffect 執(zhí)行效果:

          我們可以發(fā)現(xiàn)無論在哪兒執(zhí)行 setCount,hooks 的先后順序都不變,始終是先 useLayoutEffect 銷毀,然后 useLayoutEffect 執(zhí)行,再然后才是 useEffect 銷毀,useEffect 執(zhí)行。但是頁面渲染的不同和打印時的明顯卡頓,我們知道 hooks 的執(zhí)行時機應該是useLayoutEffectDestory -> useLayoutEffect -> 渲染 -> useEffectDestory -> useEffect

          useMemo

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

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

          useMemo 和 useEffect 的區(qū)別

          useMemo 看起來和 useEffect 很像,但是如果你想在 useMemo 里面 setCount 或者其他修改了 DOM 的操作,那你可能會遇到一些問題。因為傳入 useMemo 的函數(shù)會在渲染期間執(zhí)行,你可能看不到想要的效果,所以請不要在這個函數(shù)內(nèi)部執(zhí)行與渲染無關的操作。

          useMemo 還返回一個 memoized 值,之后僅會在某個依賴項改變時才重新計算 memoized 值。這種優(yōu)化有助于避免在每次渲染時都進行高開銷的計算,具體應用看以下例子:

          function?Counter()?{
          ??const?[count,?setCount]?=?useState(1);
          ??const?[val,?setValue]?=?useState('');

          ??const?getNum?=?()?=>?{
          ????console.log('compute');
          ????let?sum?=?0;
          ????for?(let?i?=?0;?i?100;?i++)?{
          ??????sum?+=?i;
          ????}
          ????return?sum;
          ??}

          ??const?memoNum?=?useMemo(()?=>?getNum(),?[count])

          ??return?<div>
          ????<h4>總和:{getNum()}?{memoNum}h4>

          ????<div>
          ??????<button?onClick={()?=>?setCount(count?+?1)}>+1button>
          ??????<input?value={val}?onChange={event?=>?setValue(event.target.value)}/>
          ????div>
          ??div>;
          }

          useMemo 效果:

          正常情況下,當你在 input 框輸入時,因為修改了 val,所以頁面會重新渲染,那么就需要重新計算 getNum,但使用 useMemo 后,因為依賴的 count 沒變,則 memoNum 不會重新計算。

          useCallback

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

          返回一個 memoized 回調(diào)函數(shù)。

          把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本,該回調(diào)函數(shù)僅在某個依賴項改變時才會更新。當你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將非常有用。

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

          useRef

          const?refContainer?=?useRef(initialValue);
          • 類組件、React 元素用 React.createRef,函數(shù)組件使用 useRef
          • useRef 返回一個可變的 ref 對象,其 current 屬性被初始化為傳入的參數(shù)(initialValue
          • useRef 返回的 ref 對象在組件的整個生命周期內(nèi)保持不變,也就是說每次重新渲染函數(shù)組件時,返回的 ref 對象都是同一個(使用 React.createRef ,每次重新渲染組件都會重新創(chuàng)建 ref)
          //?官網(wǎng)例子
          function?TextInputWithFocusButton()?{
          ??const?inputEl?=?useRef(null);
          ??const?onButtonClick?=?()?=>?{
          ????//?`current`?指向已掛載到?DOM?上的文本輸入元素
          ????inputEl.current.focus();
          ??};
          ??return?(
          ????<>
          ??????
          ??????Focus?the?input
          ????
          ??);
          }

          useImperativeHandle

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

          useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父組件的實例值。

          如下,渲染 的父組件可以調(diào)用 inputRef.current.focus()

          //?官網(wǎng)例子
          function?FancyInput(props,?ref)?{
          ??const?inputRef?=?useRef();
          ??useImperativeHandle(ref,?()?=>?({
          ????focus:?()?=>?{
          ??????inputRef.current.focus();
          ????}
          ??}));
          ??return?<input?ref={inputRef}?...?/>;
          }
          FancyInput?=?forwardRef(FancyInput);

          useContext

          在 hooks 中,組件都是函數(shù),所以我們可以通過參數(shù)的方式進行傳值,但是有時候我們也會遇到兄弟組件和爺孫組件之間的傳值,這時候通過函數(shù)參數(shù)傳值就不太方便了。hooks 提供了 useContext(共享狀態(tài)鉤子)來解決這個問題。

          useContext 接受一個 context 對象(從 React.createContext 返回的值)并返回當前 context 值,由最近 context 提供程序給 context 。

          當組件上層最近的 更新時,該 Hook 會觸發(fā)重渲染,并使用最新傳遞給 Context provider 的 context value 值。

          在 hooks 中使用 content,需要使用 createContext,useContext:

          //?context.js??新建一個context
          import?{?createContext?}?from?'react';
          const?AppContext?=?React.createContext({});
          //?HooksContext.jsx??父組件,提供context
          import?React,?{?useState?}?from?'react';
          import?AppContext?from?'./context';

          function?HooksContext()?{
          ??const?[count,?setCnt]?=?useState(0);
          ??const?[age,?setAge]?=?useState(16);

          ??return?(
          ????<div>
          ??????<p>年齡{age}p>

          ??????<p>你點擊了{count}次p>
          ??????<AppContext.Provider?value={{?count,?age?}}>
          ????????<div?className="App">
          ??????????<Navbar?/>
          ??????????<Messages?/>
          ????????div>
          ??????AppContext.Provider>
          ????div>
          ??);
          }
          //?子組件,使用context
          import?React,?{?useContext?}?from?'react';
          import?AppContext?from?'./context';

          const?Navbar?=?()?=>?{
          ??const?{?count,?age?}?=?useContext(AppContext);
          ??return?(
          ????<div?className="navbar">
          ??????<p>使用contextp>

          ??????<p>年齡{age}p>
          ??????<p>點擊了{count}次p>
          ????div>
          ??);
          }

          構建自定義 Hook

          當我們想要在兩個 JavaScript 函數(shù)之間共享邏輯時,我們會將共享邏輯提取到第三個函數(shù)。組件和 Hook 都是函數(shù),所以通過這種辦法可以調(diào)用其他 Hook。

          例如,我們可以把判斷朋友是否在線的功能抽出來,新建一個 useFriendStatus 的 hook 專門用來判斷某個 id 是否在線:

          //?官網(wǎng)例子
          import?{?useState,?useEffect?}?from?'react';

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

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

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

          ??return?isOnline;
          }

          這時候我們就可以在需要 FriendStatus 組件的地方為所欲為、為所欲為:

          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>

          ??);
          }

          簡單總結

          hook功能
          useState設置和改變 state,代替原來的 state 和 setState
          useReducer代替原來 redux 里的 reducer,方便管理狀態(tài)邏輯
          useEffect引入具有副作用的操作,類比原來的生命周期
          useLayoutEffect與 useEffect 作用相同,但它會同步調(diào)用 effect
          useMemo可根據(jù)狀態(tài)變化控制方法執(zhí)行,優(yōu)化無用渲染,提高性能
          useCallback類似 useMemo,useMemo 優(yōu)化傳值,usecallback 優(yōu)化傳入的方法
          useContext上下文爺孫組件及更深層組件傳值
          useRef返回一個可變的 ref 對象
          useImperativeHandle可以讓你在使用 ref 時自定義暴露給父組件的實例值

          參考文章

          React Hooks

          React Hooks 入門教程 - 阮一峰

          React Hooks 詳解 【近 1W 字】+ 項目實戰(zhàn)



          ●?JavaScript 測試系列實戰(zhàn)(四):掌握 React Hooks 測試技巧

          ●?用動畫和實戰(zhàn)打開 React Hooks(一):useState 和 useEffect

          ●?Taro 小程序開發(fā)大型實戰(zhàn)(五):使用 Hooks 版的 Redux 實現(xiàn)應用狀態(tài)管理(下篇)



          ·END·

          圖雀社區(qū)

          匯聚精彩的免費實戰(zhàn)教程



          關注公眾號回復 z 拉學習交流群


          喜歡本文,點個“在看”告訴我

          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产 无码 成人免费 | 99热视屏| 夜夜嗨色刺激 | 国产极品久久7777777 | 国产性爱第一页 |