<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>

          超實(shí)用的 React Hooks 常用場(chǎng)景總結(jié)

          共 20662字,需瀏覽 42分鐘

           ·

          2021-05-05 10:31

          前言

          文章雖然比較長(zhǎng),但是可以說(shuō)是全網(wǎng)最全最有用的總結(jié)了,學(xué)會(huì)的記得分享、點(diǎn)贊、收藏、謝謝支持

          React 在 v16.8 的版本中推出了 React Hooks 新特性。在我看來(lái),使用 React Hooks 相比于從前的類組件有以下幾點(diǎn)好處:

          1. 代碼可讀性更強(qiáng),原本同一塊功能的代碼邏輯被拆分在了不同的生命周期函數(shù)中,容易使開(kāi)發(fā)者不利于維護(hù)和迭代,通過(guò) React Hooks 可以將功能代碼聚合,方便閱讀維護(hù);

          2. 組件樹(shù)層級(jí)變淺,在原本的代碼中,我們經(jīng)常使用 HOC/render props 等方式來(lái)復(fù)用組件的狀態(tài),增強(qiáng)功能等,無(wú)疑增加了組件樹(shù)層數(shù)及渲染,而在 React Hooks 中,這些功能都可以通過(guò)強(qiáng)大的自定義的 Hooks 來(lái)實(shí)現(xiàn);

          關(guān)于這方面的文章,我們根據(jù)使用場(chǎng)景分別進(jìn)行舉例說(shuō)明,幫助你認(rèn)識(shí)理解并可以熟練運(yùn)用 React Hooks 大部分特性。辛苦整理良久,還望手動(dòng)點(diǎn)贊鼓勵(lì)~

          一、State Hook

          1、基礎(chǔ)用法

          function State(){
            const [count, setCount] = useState(0);
            return (
                <div>
                    <p>You clicked {count} times</p>
                    <button onClick={() => setCount(count + 1)}>
                        Click me
                    </button>
                </div>

            )
          }

          2、更新

          更新分為以下兩種方式,即直接更新和函數(shù)式更新,其應(yīng)用場(chǎng)景的區(qū)分點(diǎn)在于:

          直接更新不依賴于舊 state 的值;函數(shù)式更新依賴于舊 state 的值;

          // 直接更新
          setState(newCount);

          // 函數(shù)式更新
          setState(prevCount => prevCount - 1);

          3、實(shí)現(xiàn)合并

          與 class 組件中的 setState 方法不同,useState 不會(huì)自動(dòng)合并更新對(duì)象,而是直接替換它。我們可以用函數(shù)式的 setState 結(jié)合展開(kāi)運(yùn)算符來(lái)達(dá)到合并更新對(duì)象的效果。

          setState(prevState => {
            // 也可以使用 Object.assign
            return {...prevState, ...updatedValues};
          });

          4、惰性初始化 state

          initialState 參數(shù)只會(huì)在組件的初始渲染中起作用,后續(xù)渲染時(shí)會(huì)被忽略。其應(yīng)用場(chǎng)景在于:創(chuàng)建初始 state 很昂貴時(shí),例如需要通過(guò)復(fù)雜計(jì)算獲得;那么則可以傳入一個(gè)函數(shù),在函數(shù)中計(jì)算并返回初始的 state,此函數(shù)只在初始渲染時(shí)被調(diào)用:

          const [state, setState] = useState(() => {
            const initialState = someExpensiveComputation(props);
            return initialState;
          });

          5、一些重點(diǎn)

          • (1)不像 class 中的 this.setState ,Hook 更新 state 變量總是替換它而不是合并它;
          • (2)推薦使用多個(gè) state 變量,而不是單個(gè) state 變量,因?yàn)?state 的替換邏輯而不是合并邏輯,并且利于后續(xù)的相關(guān) state 邏輯抽離;
          • (3)調(diào)用 State Hook 的更新函數(shù)并傳入當(dāng)前的 state 時(shí),React 將跳過(guò)子組件的渲染及 effect 的執(zhí)行。(React 使用 Object.is 比較算法 來(lái)比較 state。)

          二、Effect Hook

          1、基礎(chǔ)用法

          function Effect(){
            const [count, setCount] = useState(0);
            useEffect(() => {
              console.log(`You clicked ${count} times`);
            });

            return (
                <div>
                    <p>You clicked {count} times</p>
                    <button onClick={() => setCount(count + 1)}>
                        Click me
                    </button>
                </div>

            )
          }

          2、清除操作

          為防止內(nèi)存泄漏,清除函數(shù)會(huì)在組件卸載前執(zhí)行;如果組件多次渲染(通常如此),則在執(zhí)行下一個(gè) effect 之前,上一個(gè) effect 就已被清除,即先執(zhí)行上一個(gè) effect 中 return 的函數(shù),然后再執(zhí)行本 effect 中非 return 的函數(shù)。

          useEffect(() => {
            const subscription = props.source.subscribe();
            return () => {
              // 清除訂閱
              subscription.unsubscribe();
            };
          });

          3、執(zhí)行時(shí)期

          與 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 調(diào)度的 effect 不會(huì)阻塞瀏覽器更新屏幕,這讓你的應(yīng)用看起來(lái)響應(yīng)更快;(componentDidMount 或 componentDidUpdate 會(huì)阻塞瀏覽器更新屏幕)

          4、性能優(yōu)化

          默認(rèn)情況下,React 會(huì)每次等待瀏覽器完成畫面渲染之后延遲調(diào)用 effect;但是如果某些特定值在兩次重渲染之間沒(méi)有發(fā)生變化,你可以通知 React 跳過(guò)對(duì) effect 的調(diào)用,只要傳遞數(shù)組作為 useEffect 的第二個(gè)可選參數(shù)即可:如下所示,如果 count 值兩次渲染之間沒(méi)有發(fā)生變化,那么第二次渲染后就會(huì)跳過(guò) effect 的調(diào)用;

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

          5、模擬 componentDidMount

          如果想只運(yùn)行一次的 effect(僅在組件掛載和卸載時(shí)執(zhí)行),可以傳遞一個(gè)空數(shù)組([ ])作為第二個(gè)參數(shù),如下所示,原理跟第 4 點(diǎn)性能優(yōu)化講述的一樣;

          useEffect(() => {
            .....
          }, []);

          6、最佳實(shí)踐

          要記住 effect 外部的函數(shù)使用了哪些 props 和 state 很難,這也是為什么 通常你會(huì)想要在 effect 內(nèi)部 去聲明它所需要的函數(shù)。

          // bad,不推薦
          function Example({ someProp }{
            function doSomething({
              console.log(someProp);
            }

            useEffect(() => {
              doSomething();
            }, []); // ?? 這樣不安全(它調(diào)用的 `doSomething` 函數(shù)使用了 `someProp`)
          }

          // good,推薦
          function Example({ someProp }{
            useEffect(() => {
              function doSomething({
                console.log(someProp);
              }

              doSomething();
            }, [someProp]); // ? 安全(我們的 effect 僅用到了 `someProp`)
          }

          如果處于某些原因你無(wú)法把一個(gè)函數(shù)移動(dòng)到 effect 內(nèi)部,還有一些其他辦法:

          你可以嘗試把那個(gè)函數(shù)移動(dòng)到你的組件之外。那樣一來(lái),這個(gè)函數(shù)就肯定不會(huì)依賴任何 props 或 state,并且也不用出現(xiàn)在依賴列表中了;萬(wàn)不得已的情況下,你可以 把函數(shù)加入 effect 的依賴但 把它的定義包裹 進(jìn) useCallback Hook。這就確保了它不隨渲染而改變,除非它自身的依賴發(fā)生了改變;

          推薦啟用 eslint-plugin-react-hooks 中的 exhaustive-deps 規(guī)則,此規(guī)則會(huì)在添加錯(cuò)誤依賴時(shí)發(fā)出警告并給出修復(fù)建議 ;

          // 1、安裝插件

          npm i eslint-plugin-react-hooks --save-dev

          // 2、eslint 配置

          {
            "plugins": [
              // ...
              "react-hooks"
            ],
            "rules": {
              // ...
              "react-hooks/rules-of-hooks""error",
              "react-hooks/exhaustive-deps""warn"
            }
          }

          7、一些重點(diǎn)

          • (1)可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate和 componentWillUnmount這三個(gè)函數(shù)的組合;
          • (2)在 React 的 class 組件中,render 函數(shù)是不應(yīng)該有任何副作用的;一般來(lái)說(shuō),在這里執(zhí)行操作太早了,我們基本上都希望在 React 更新 DOM 之后才執(zhí)行我們的操作。

          三、useContext

          用來(lái)處理多層級(jí)傳遞數(shù)據(jù)的方式,在以前組件樹(shù)中,跨層級(jí)祖先組件想要給孫子組件傳遞數(shù)據(jù)的時(shí)候,除了一層層 props 往下透?jìng)髦猓覀冞€可以使用 React Context API 來(lái)幫我們做這件事。使用例子如下所示 (1)使用 React Context API,在組件外部建立一個(gè) Context

          import React from 'react';
          const ThemeContext = React.createContext(0);
          export default ThemeContext;

          (2)使用 Context.Provider提供了一個(gè) Context 對(duì)象,這個(gè)對(duì)象可以被子組件共享

          import React, { useState } from 'react';
          import ThemeContext from './ThemeContext';
          import ContextComponent1 from './ContextComponent1';

          function ContextPage ({
            const [count, setCount] = useState(1);
            return (
              <div className="App">
                <ThemeContext.Provider value={count}>
                  <ContextComponent1 />
                </ThemeContext.Provider>
                <button onClick={() => setCount(count + 1)}>
                        Click me
                </button>
              </div>

            );
          }

          export default ContextPage;

          (3)useContext()鉤子函數(shù)用來(lái)引入 Context 對(duì)象,并且獲取到它的值 // 子組件,在子組件中使用孫組件

          import React from 'react';
          import ContextComponent2 from './ContextComponent2';
          function ContextComponent ({
            return (
              <ContextComponent2 />
            );
          }
          export default ContextComponent;

          // 孫組件,在孫組件中使用 Context 對(duì)象值

          import React, { useContext } from 'react';
          import ThemeContext from './ThemeContext';
          function ContextComponent ({
            const value = useContext(ThemeContext);
            return (
              <div>useContext:{value}</div>
            );
          }
          export default ContextComponent;

          四、useReducer

          1、基礎(chǔ)用法

          比 useState 更適用的場(chǎng)景:例如 state 邏輯處理較復(fù)雜且包含多個(gè)子值,或者下一個(gè) state 依賴于之前的 state 等;例子如下所示

          import React, { useReducer } from 'react';
          interface stateType {
            count: number
          }
          interface actionType {
            type: string
          }
          const initialState = { count0 };
          const reducer = (state:stateType, action:actionType) => {
            switch (action.type) {
              case 'increment':
                return { count: state.count + 1 };
              case 'decrement':
                return { count: state.count - 1 };
              default:
                throw new Error();
            }
          };
          const UseReducer = () => {
            const [state, dispatch] = useReducer(reducer, initialState);

            return (
              <div className="App">
                <div>useReducer Count:{state.count}</div>
                <button onClick={() => { dispatch({ type: 'decrement' }); }}>useReducer 減少</button>
                <button onClick={() => { dispatch({ type: 'increment' }); }}>useReducer 增加</button>
              </div>

            );
          };

          export default UseReducer;

          2、惰性初始化 state

          interface stateType {
            count: number
          }
          interface actionType {
            type: string,
            paylod?: number
          }
          const initCount =0 
          const init = (initCount:number)=>{
            return {count:initCount}
          }
          const reducer = (state:stateType, action:actionType)=>{
            switch(action.type){
              case 'increment':
                return {count: state.count + 1}
              case 'decrement':
                return {count: state.count - 1}
              case 'reset':
                return init(action.paylod || 0)
              default:
                throw new Error();
            }
          }
          const UseReducer = () => {
            const [state, dispatch] = useReducer(reducer,initCount,init)

            return (
              <div className="App">
                <div>useReducer Count:{state.count}</div>
                <button onClick={()=>{dispatch({type:'decrement'})}}>useReducer 減少</button>
                <button onClick={()=>{dispatch({type:'increment'})}}>useReducer 增加</button>
                <button onClick={()=>{dispatch({type:'reset',paylod:10 })}}>useReducer 增加</button>
              </div>

            );
          }
          export default UseReducer;

          五、Memo

          如下所示,當(dāng)父組件重新渲染時(shí),子組件也會(huì)重新渲染,即使子組件的 props 和 state 都沒(méi)有改變

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

          // 子組件
          const ChildComp = () => {
            console.log('ChildComp...');
            return (<div>ChildComp...</div>);
          };

          // 父組件
          const Parent = () => {
            const [count, setCount] = useState(0);

            return (
              <div className="App">
                <div>hello world {count}</div>
                <div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
                <ChildComp/>
              </div>

            );
          };

          export default Parent;

          改進(jìn):我們可以使用 memo 包一層,就能解決上面的問(wèn)題;但是僅僅解決父組件沒(méi)有傳參給子組件的情況以及父組件傳簡(jiǎn)單類型的參數(shù)給子組件的情況(例如 string、number、boolean等);如果有傳復(fù)雜屬性應(yīng)該使用 useCallback(回調(diào)事件)或者 useMemo(復(fù)雜屬性)

          // 子組件
          const ChildComp = () => {
            console.log('ChildComp...');
            return (<div>ChildComp...</div>);
          };

          const MemoChildComp = memo(ChildComp);

          六、useMemo

          假設(shè)以下場(chǎng)景,父組件在調(diào)用子組件時(shí)傳遞 info 對(duì)象屬性,點(diǎn)擊父組件按鈕時(shí),發(fā)現(xiàn)控制臺(tái)會(huì)打印出子組件被渲染的信息。

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

          // 子組件
          const ChildComp = (info:{info:{name: string, age: number}}) => {
            console.log('ChildComp...');
            return (<div>ChildComp...</div>);
          };

          const MemoChildComp = memo(ChildComp);

          // 父組件
          const Parent = () => {
            const [count, setCount] = useState(0);
            const [name] = useState('jack');
            const [age] = useState(11);
            const info = { name, age };

            return (
              <div className="App">
                <div>hello world {count}</div>
                <div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
                <MemoChildComp info={info}/>
              </div>

            );
          };

          export default Parent;

          分析原因:

          點(diǎn)擊父組件按鈕,觸發(fā)父組件重新渲染;父組件渲染,const info = { name, age } 一行會(huì)重新生成一個(gè)新對(duì)象,導(dǎo)致傳遞給子組件的 info 屬性值變化,進(jìn)而導(dǎo)致子組件重新渲染。

          解決:

          使用 useMemo 將對(duì)象屬性包一層,useMemo 有兩個(gè)參數(shù):

          • 第一個(gè)參數(shù)是個(gè)函數(shù),返回的對(duì)象指向同一個(gè)引用,不會(huì)創(chuàng)建新對(duì)象;
          • 第二個(gè)參數(shù)是個(gè)數(shù)組,只有數(shù)組中的變量改變時(shí),第一個(gè)參數(shù)的函數(shù)才會(huì)返回一個(gè)新的對(duì)象。
          import React, { memo, useMemo, useState } from 'react';

          // 子組件
          const ChildComp = (info:{info:{name: string, age: number}}) => {
            console.log('ChildComp...');
            return (<div>ChildComp...</div>);
          };

          const MemoChildComp = memo(ChildComp);

          // 父組件
          const Parent = () => {
            const [count, setCount] = useState(0);
            const [name] = useState('jack');
            const [age] = useState(11);
            
            // 使用 useMemo 將對(duì)象屬性包一層
            const info = useMemo(() => ({ name, age }), [name, age]);

            return (
              <div className="App">
                <div>hello world {count}</div>
                <div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
                <MemoChildComp info={info}/>
              </div>

            );
          };

          export default Parent;

          七 、useCallback

          接著第六章節(jié)的例子,假設(shè)需要將事件傳給子組件,如下所示,當(dāng)點(diǎn)擊父組件按鈕時(shí),發(fā)現(xiàn)控制臺(tái)會(huì)打印出子組件被渲染的信息,說(shuō)明子組件又被重新渲染了。

          import React, { memo, useMemo, useState } from 'react';

          // 子組件
          const ChildComp = (props:any) => {
            console.log('ChildComp...');
            return (<div>ChildComp...</div>);
          };

          const MemoChildComp = memo(ChildComp);

          // 父組件
          const Parent = () => {
            const [count, setCount] = useState(0);
            const [name] = useState('jack');
            const [age] = useState(11);
            const info = useMemo(() => ({ name, age }), [name, age]);
            const changeName = () => {
              console.log('輸出名稱...');
            };

            return (
              <div className="App">
                <div>hello world {count}</div>
                <div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
                <MemoChildComp info={info} changeName={changeName}/>
              </div>

            );
          };

          export default Parent;

          分析下原因:

          點(diǎn)擊父組件按鈕,改變了父組件中 count 變量值(父組件的 state 值),進(jìn)而導(dǎo)致父組件重新渲染;父組件重新渲染時(shí),會(huì)重新創(chuàng)建 changeName 函數(shù),即傳給子組件的 changeName 屬性發(fā)生了變化,導(dǎo)致子組件渲染;

          解決:

          修改父組件的 changeName 方法,用 useCallback 鉤子函數(shù)包裹一層, useCallback 參數(shù)與 useMemo 類似

          import React, { memo, useCallback, useMemo, useState } from 'react';

          // 子組件
          const ChildComp = (props:any) => {
            console.log('ChildComp...');
            return (<div>ChildComp...</div>);
          };

          const MemoChildComp = memo(ChildComp);

          // 父組件
          const Parent = () => {
            const [count, setCount] = useState(0);
            const [name] = useState('jack');
            const [age] = useState(11);
            const info = useMemo(() => ({ name, age }), [name, age]);
            const changeName = useCallback(() => {
              console.log('輸出名稱...');
            }, []);

            return (
              <div className="App">
                <div>hello world {count}</div>
                <div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
                <MemoChildComp info={info} changeName={changeName}/>
              </div>

            );
          };

          export default Parent;

          八、useRef

          以下分別介紹 useRef 的兩個(gè)使用場(chǎng)景:

          1、指向 dom 元素

          如下所示,使用 useRef 創(chuàng)建的變量指向一個(gè) input 元素,并在頁(yè)面渲染后使 input 聚焦

          import React, { useRef, useEffect } from 'react';
          const Page1 = () => {
            const myRef = useRef<HTMLInputElement>(null);
            useEffect(() => {
              myRef?.current?.focus();
            });
            return (
              <div>
                <span>UseRef:</span>
                <input ref={myRef} type="text"/>
              </div>

            );
          };

          export default Page1;

          2、存放變量

          useRef 在 react hook 中的作用, 正如官網(wǎng)說(shuō)的, 它像一個(gè)變量, 類似于 this , 它就像一個(gè)盒子, 你可以存放任何東西. createRef 每次渲染都會(huì)返回一個(gè)新的引用,而 useRef 每次都會(huì)返回相同的引用,如下例子所示:

          import React, { useRef, useEffect, useState } from 'react';
          const Page1 = () => {
              const myRef2 = useRef(0);
              const [count, setCount] = useState(0)
              useEffect(()=>{
                myRef2.current = count;
              });
              function handleClick(){
                setTimeout(()=>{
                  console.log(count); // 3
                  console.log(myRef2.current); // 6
                },3000)
              }
              return (
              <div>
                <div onClick={()=> setCount(count+1)}>點(diǎn)擊count</div>
                <div onClick={()=> handleClick()}>查看</div>
              </div>

              );
          }

          export default Page1;

          九、useImperativeHandle

          使用場(chǎng)景:通過(guò) ref 獲取到的是整個(gè) dom 節(jié)點(diǎn),通過(guò) useImperativeHandle 可以控制只暴露一部分方法和屬性,而不是整個(gè) dom 節(jié)點(diǎn)。

          十、useLayoutEffect

          其函數(shù)簽名與 useEffect 相同,但它會(huì)在所有的 DOM 變更之后同步調(diào)用 effect,這里不再舉例。

          useLayoutEffect 和平常寫的 Class 組件的 componentDidMount 和 componentDidUpdate 同時(shí)執(zhí)行;

          useEffect 會(huì)在本次更新完成后,也就是第 1 點(diǎn)的方法執(zhí)行完成后,再開(kāi)啟一次任務(wù)調(diào)度,在下次任務(wù)調(diào)度中執(zhí)行 useEffect;

          總結(jié)

          關(guān)于這方面的文章,我們根據(jù)使用場(chǎng)景分別進(jìn)行舉例說(shuō)明,希望有幫助到你認(rèn)識(shí)理解并可以熟練運(yùn)用 React Hooks 大部分特性。

          辛苦整理良久,還望手動(dòng)點(diǎn)贊鼓勵(lì)~

          作者: 超級(jí)英雄 

          https://juejin.cn/post/6918896729366462471

          瀏覽 78
          點(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>
                  超碰人人奸 | 韩国三级片久久久久 | 操逼大秀| 欧美性妇| 色哟哟一区二区三区四区 |