<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    React Hook 避坑指南(useState & useEffect)

    共 15028字,需瀏覽 31分鐘

     ·

    2023-10-31 07:49

    useState


    const [state, setState] = useState(initialState);
    • 返回一個(gè) state,以及更新 state 的函數(shù)。

    • 在初始渲染期間,返回的狀態(tài) (state) 與傳入的第一個(gè)參數(shù) (initialState) 值相同。

    • setState 函數(shù)用于更新 state。它接收一個(gè)新的 state 值并將組件的一次重新渲染加入隊(duì)列。

    state的更新

    通過 setState 方法可以更新state。例如:查看在線示例

    const [count, setCount] = useState(0);
    function handleOnClick() { setCount(count + 1); setCount(count + 1); setCount(count + 1);}
    return ( <div> <div> count: {count} </div> <button onClick={handleOnClick}> +1 </button> </div>);

    如果點(diǎn)擊按鈕后連續(xù)調(diào)用3次 setCount(count + 1),你會(huì)發(fā)現(xiàn)界面上count的值并沒有 +3,仍然是 + 1。

    函數(shù)式更新

    如果新的 state 需要通過使用先前的 state 計(jì)算得出,那么可以將函數(shù)傳遞給 setState。該函數(shù)將接收先前的 state,并返回一個(gè)更新后的值。

    setCount(count => count + 1);setCount(count => count + 1);setCount(count => count + 1);

    更新對(duì)象

    當(dāng)useState的值為對(duì)象時(shí),可能會(huì)存在視圖不更新的情況,例如:查看在線示例

    const [list, setList] = useState([0, 1, 2]);const [useInfo, setUserInfo] = useState({    name: "張三",    age: 18});
    function handleOnClick() { list.push(4); list.push(4); setList(list);
    useInfo.name = "李四"; useInfo.age = 20; setUserInfo(useInfo);}
    return ( <div> <p>姓名:{useInfo.name}</p> <p>年齡:{useInfo.age}</p> <p>ist.length: {list.length}</p> <button onClick={handleOnClick}> 修改 </button> </div>);

    問題原因:React 中默認(rèn)是淺監(jiān)聽,當(dāng)state的值為對(duì)象時(shí),棧中存的是對(duì)象的引用(地址),setState改變的是堆中的數(shù)據(jù),棧中的地址還是原地址,React淺監(jiān)聽到地址沒變,故會(huì)認(rèn)為State并未改變,所以沒有重渲染頁面。

    解決方案:只要改變了原對(duì)象的地址即可,可通過以下幾種方式實(shí)現(xiàn)

    • 將原對(duì)象進(jìn)行克隆

    • 使用ES6的拓展運(yùn)算符

    對(duì)于數(shù)組我們可以使用一些數(shù)組自身的方法來進(jìn)行深拷貝:

    // 使用Array.sliceconst nextList = list.slice(0);nextList.push("slice");setList(nextList);
    // 使用Array.concatconst nextList = list.concat();nextList.push("concat");setList(nextList);

    總結(jié):無論是在 useState 中,還是傳入函數(shù)中的參數(shù),都不要直接去操作對(duì)象本身,先克隆出一份來再操作,避免引起一些意想不到的問題。


    無法在setSate后拿到最新的值

    由于setSate后并不會(huì)立即更新,React會(huì)在某個(gè)時(shí)候?qū)⒍鄠€(gè) setSate進(jìn)行合并后再更新。因此無法在 setState后拿到最新的值。一般有以下幾種方式可以拿到最新值:

    • 使用 useRef ,但是數(shù)據(jù)的更新不會(huì)引起視圖的更新

    • 使用 useEffect ,這種方式在很多場(chǎng)景下也不適用,每次更新都會(huì)執(zhí)行 useEffect 中的內(nèi)容,往往我們?cè)谛枨蟛⒉皇侨绱?/span>

    • 使用函數(shù)式更新

    • 使用 ahooks 的 useGetState    【原理:使用useRef將useState的值存起來】

    查看在線示例

    const [count, setCount] = useState(0);const countRef = useRef(0);
    useEffect(() => { console.log("useEffect", count);}, [count]);
    function handleOnClick() { countRef.current += 1; setCount(count + 1); console.log("正常打印", count); console.log("countRef", countRef.current); setCount(count => { console.log("函數(shù)式更新獲取最新值", count); return count; });}
    return ( <div> <div> count: {count} </div> <button onClick={handleOnClick}> +1 </button> </div>);

    查看在線示例

    const useGetState = (initiateState) => {      const [state, setState] = useState(initiateState);      const stateRef = useRef(state);      stateRef.current = state;          const getState = useCallback(() => stateRef.current, []);          return [state, setState, getState];};

    定時(shí)器中獲取最新值

    在下面的例子中,無論是視圖還是打印,count 的值永遠(yuǎn)都是0。查看在線示例

    const [count, setCount] = useState(0);useEffect(() => {    const interval = setInterval(() => {        console.log(count);        setCount(count + 1);    }, 1000);    return () => {        clearInterval(interval);    }}, []);

    問題原因:定時(shí)器在創(chuàng)建后一直都沒有被清除,因此內(nèi)部獲取的狀態(tài)始終都是創(chuàng)建時(shí)state的狀態(tài)

    解決方案

       (1)定時(shí)器內(nèi)部更新state使用函數(shù)式更新,函數(shù)式更新可以獲取到state的最新狀態(tài)。此方法可以解決視圖更新問題,但是在定時(shí)器中的打印仍然是0。

    (2)將state作為 useEffect 的依賴,state發(fā)生變化后會(huì)重新創(chuàng)建定時(shí)器


    useEffect


    如果你熟悉 React class 的生命周期函數(shù),你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdatecomponentWillUnmount 這三個(gè)函數(shù)的組合。

    componentDidMount、componentDidUpdate 不同的是,傳給 useEffect 的函數(shù)會(huì)在瀏覽器完成布局與繪制之后,在一個(gè)延遲事件中被調(diào)用。這使得它適用于許多常見的副作用場(chǎng)景,比如設(shè)置訂閱和事件處理等情況,因?yàn)榻^大多數(shù)操作不應(yīng)阻塞瀏覽器對(duì)屏幕的更新。查看官方文檔

    import React, { useState, useEffect } from 'react';
    function Example() { const [count, setCount] = useState(0);
    // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; });
    return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> );}

    useEffect 在每次渲染后都會(huì)執(zhí)行,包括第一次渲染后和每次更新。React 保證了每次運(yùn)行 effect 的同時(shí),DOM 都已經(jīng)更新完畢。

    可以通過第二個(gè)參數(shù)來控制 useEffect 在什么情況下才執(zhí)行:查看在線示例

    import { useState, useEffect } from "react";
    export default () => { const [count, setCount] = useState(0); const [number, setNumber] = useState(0);
    // 沒有任何依賴,每次重新渲染都要執(zhí)行 useEffect(() => { console.log("null", count); });
    // 依賴值為空,只在第一次渲染后執(zhí)行一次 useEffect(() => { console.log("[]", count); }, []);
    // 只有依賴值發(fā)生變化后,才會(huì)執(zhí)行;第一次渲染也會(huì)執(zhí)行 useEffect(() => { console.log("count", count); }, [count]);
    function addCount() { setCount(count + 1); }
    function addNumber() { setNumber(number + 1); }
    return ( <div> <div>count: {count}</div> <div>number: {number}</div> <button onClick={addCount}>count+1</button> <button onClick={addNumber}>number+1</button> </div> );};

    依賴值為對(duì)象的時(shí)

    我們經(jīng)常會(huì)將一個(gè)對(duì)象作為依賴,一般我們都是希望對(duì)象的內(nèi)容發(fā)生變化時(shí),去執(zhí)行某些操作。在實(shí)際的業(yè)務(wù)開發(fā)中,我們會(huì)遇到一些莫名其妙的坑,列舉幾個(gè)常見的現(xiàn)象:

    • 明明對(duì)象的內(nèi)容已經(jīng)發(fā)生了變化,但是為什么沒有觸發(fā)useEffect

    • 明明對(duì)象的內(nèi)容沒有發(fā)生變化,但是為什么一直觸發(fā)useEffect

    這看起來有點(diǎn)像在說繞口令,出現(xiàn)問題的本質(zhì)就是因?yàn)閷?duì)象是引用類型,通過下面幾個(gè)例子可以更加深入的理解

    案例1:改變對(duì)象中的屬性值,未觸發(fā)useEffect

    const [info, setInfo] = useState({    name: "張三",    age: 18});
    useEffect(() => {console.log("info", info);}, [info]);
    function handleChangeName(e) { const value = e.target.value; setInfo((info) => { info.name = value; return info; });}
    return <input onChange={handleChangeName} />;

    問題原因:調(diào)用 setInfo 時(shí),是直接改變的入?yún)?,此時(shí)返回改變后的信息其引用是沒有發(fā)生變化的。

    注意點(diǎn):在任何情況下,都不能直接去改變?nèi)雲(yún)?,或者是直接改變state值本身。

    // 錯(cuò)誤寫法info.name = value;setInfo(info);
    // 錯(cuò)誤寫法setInfo((info) => { info.name = value; return info;});
    // 正確寫法setInfo({ ...info, name: value});
    // 正確寫法setInfo((info) => { return { ...info, name: value };});

    案例2:接受父組件的對(duì)象屬性作為依賴,useEffect頻繁觸發(fā)

    開發(fā)組件時(shí),對(duì)某些屬性需要設(shè)置默認(rèn)值,一般的寫法就是解構(gòu)props時(shí)同時(shí)賦予默認(rèn)值

    const {    count = 0,    list = []} = props;

    如果父組件沒有傳遞list屬性,每當(dāng)父組件重新渲染時(shí),子組件會(huì)跟隨重新渲染,每次渲染都會(huì)觸發(fā)useEffect。在線查看示例

    import { useState, useEffect } from "react";
    const Com = () => { const [count, setCount] = useState(0);
    function hanleOnClick() { setCount((count) => count + 1); }
    return ( <div> <button onClick={hanleOnClick}>add</button> <SubCom count={count} /> </div> );};
    const SubCom = (props) => { const { list = [], count } = props;
    useEffect(() => { console.log(list); }, [list]);
    return <div>子組件{count}</div>;};
    export default Com;

    問題原因:當(dāng)父組件更新時(shí),會(huì)重新渲染子組件,每次渲染,props.list 都被賦予了新的引用, 雖然看起來都是空數(shù)組,但是useEffect 是判斷l(xiāng)ist的引用發(fā)生了變化,所以就會(huì)執(zhí)行。一旦該組件用于復(fù)雜場(chǎng)景,導(dǎo)致更新頻繁就會(huì)出現(xiàn)白屏現(xiàn)象。

    正確寫法:在用到的地方去做兼容處理,而不是直接賦予默認(rèn)值。


    案例3:對(duì)象內(nèi)容未變化時(shí),我們不希望觸發(fā)useEffect

    將對(duì)象作為依賴時(shí),往往都是希望其內(nèi)容發(fā)生變化時(shí),才觸發(fā)相應(yīng)的執(zhí)行。但是 useEffect 的本質(zhì)是監(jiān)聽引用的變化,很多情況下這與我們實(shí)際的業(yè)務(wù)開發(fā)有點(diǎn)不相符。

    • 業(yè)務(wù)層經(jīng)常會(huì)對(duì)一些狀態(tài)進(jìn)行重置,setState([]) 或者 setState({}) 。有可能本身state的值就是 [] 或者 {} ,重置后,內(nèi)容未發(fā)生變化,但是引用已經(jīng)改變,從而導(dǎo)致觸發(fā) useEffect 。查看在線示例

    import { useState, useEffect } from "react";
    const Com = () => { const [list, setList] = useState([]);
    function reset() { setList([]); }
    return ( <div> <p>{list.join(",")}</p> <button onClick={reset}>reset</button> <SubCom list={list} /> </div> );};
    const SubCom = (props) => { const { list } = props;
    useEffect(() => { console.log(list); }, [list]);
    return <div>子組件</div>;};
    export default Com;

    解決方案

    • 將對(duì)象轉(zhuǎn)為字符串后再作為useEffect的依賴。

    useEffect(() => {console.log(list);}, [JSON.stringify(list)]);
    • 使用 ahooks 的 useDeepCompareEffect 來解決。用法與 useEffect 一致,但 deps 通過 lodash isEqual 進(jìn)行深比較。

    import { useRef } from 'react';import type { DependencyList, useEffect, useLayoutEffect } from 'react';import isEqual from 'lodash/isEqual';
    type EffectHookType = typeof useEffect | typeof useLayoutEffect;type CreateUpdateEffect = (hook: EffectHookType) => EffectHookType;
    const depsEqual = (aDeps: DependencyList = [], bDeps: DependencyList = []) => { return isEqual(aDeps, bDeps);};
    export const createDeepCompareEffect: CreateUpdateEffect = (hook) => (effect, deps) => { const ref = useRef<DependencyList>(); const signalRef = useRef<number>(0);
    // 本地更新的依賴值與緩存的依賴深比較 if (deps === undefined || !depsEqual(deps, ref.current)) { // 將依賴保存一份 ref.current = deps; // 如果發(fā)現(xiàn)變更,則改變signalRef的值,是為了觸發(fā)真正的useEffect signalRef.current += 1; }
    hook(effect, [signalRef.current]);};

    案例4:第一次渲染時(shí),不希望觸發(fā)useEffect

    useEffect第一次渲染后和每次更新 都會(huì)執(zhí)行。

    有的業(yè)務(wù)場(chǎng)景并不希望在第一次加載的時(shí)候觸發(fā),此場(chǎng)景可通過創(chuàng)建一個(gè)標(biāo)志位來解決。當(dāng)然可以直接使用 ahooks 中的 useUpdateEffect 這個(gè)hook,其原理也是使用標(biāo)志位來實(shí)現(xiàn)的。查看在線示例

    import { useState, useEffect, useRef } from "react";
    export default () => { const [count, setCount] = useState(0); const isMounted = useRef(false);
    // 第一次渲染置為false useEffect(() => { isMounted.current = false; }, []);
    useEffect(() => { console.log("第一渲染時(shí)會(huì)執(zhí)行"); }, [count]);
    // 第一次渲染將標(biāo)志位置為true useEffect(() => { if (!isMounted.current) { isMounted.current = true; } else { console.log("第一渲染時(shí)不會(huì)執(zhí)行,后續(xù)更新才會(huì)執(zhí)行"); } }, [count]);
    return ( <div> <button onClick={() => setCount((c) => c + 1)}>+1</button> </div> );};

    案例5:兩個(gè)useEffect更新相互依賴,無限更新導(dǎo)致白屏

    const {    value,    defaultValue = 0.5,    onChange} = props;
    const [innerValue, setInnerValue] = useState<number>(defaultValue);
    // 取名為useEffect1useEffect(() => { if (value !== undefined) { setInnerValue(value); }}, [value]);
    // 取名為useEffect2useEffect(() => { onChange?.(innerValue);}, [innerValue]);

    組件功能:這里是一個(gè)自定義的表單組件,其中 value 是受控屬性,當(dāng)改變表單值時(shí),通過 onChange 通知上層,上層改變 value 值。

    如果業(yè)務(wù)層在初始化時(shí),對(duì)value 賦予的初始值不是undefined 并且不等于 defaultValue 的值,則會(huì)導(dǎo)致白屏現(xiàn)象,下面來分析一下整個(gè)過程:

    • 假設(shè)業(yè)務(wù)層對(duì) value 賦予了一個(gè)初始值0.6。在第一次加載時(shí),useEffect1 和 useEffect2 都會(huì)執(zhí)行一遍。

    • useEffect1 執(zhí)行時(shí),會(huì)將 innerValue 的值設(shè)置為 0.6

    • useEffect2 執(zhí)行時(shí),會(huì)將 innerValue 的值通過onChange 方法通知到業(yè)務(wù)層,這里要注意,此時(shí)的 innerValue 值為 defaultValue 的值,是0.5 。并不是 useEffect1 中改變后的 0.6;

    • 當(dāng)業(yè)務(wù)層監(jiān)聽到調(diào)用了 onChange 時(shí),會(huì)將 onChange 傳過來的值也就是0.5更新到 value上。

    • 當(dāng)進(jìn)入第二次更新時(shí),useEffect1 監(jiān)聽到 value 的值從0.6變?yōu)榱?.5,因此會(huì)執(zhí)行useEffect1 。useEffect2 監(jiān)聽到 innerValue 的值從0.5 變?yōu)榱?.6,因此也會(huì)執(zhí)行useEffect2,從而又觸發(fā)了onChange

    • 由于 value 與 innerValue 的值永遠(yuǎn)都在同一次更新中,更新為了不同的值,會(huì)導(dǎo)致這個(gè)更新會(huì)無限的循環(huán)執(zhí)行下去,從而導(dǎo)致白屏。


    問題點(diǎn):

    • 在第一次加載時(shí),就會(huì)觸發(fā)useEffect2導(dǎo)致調(diào)用onChange方法。

    • 如果是業(yè)務(wù)層手動(dòng)變更了value值,也會(huì)觸發(fā)onChange

    正確寫法:

    • 在真正手動(dòng)改變表單值的時(shí)候,去調(diào)用 onChange,而不是直接去使用useEffect監(jiān)聽innerValue的變化


    案例6:不要將普通變量作為依賴

    查看在線示例

    import { useState, useEffect } from "react";
    export default () => { const [count, setCount] = useState(0);
    const list = [];
    useEffect(() => { console.log("觸發(fā)useEffect", count); }, [list]);
    return ( <div> <p>{count}</p> <button onClick={() => setCount((c) => c + 1)}>+1</button> </div> );};

    問題原因: 組件在每次更新時(shí),會(huì)對(duì)list賦予新的值,與 案例2 原理相同。


    案例7:依賴監(jiān)聽useRef的值,有時(shí)可以觸發(fā)更新,有時(shí)無法觸發(fā)更新

    查看在線示例

    import { useState, useEffect, useRef } from "react";
    export default () => { const [count, setCount] = useState(0); const countRef = useRef(0); // 取名為useEffect1 useEffect(() => { console.log("count", count); }, [count]);
    // 取名為useEffect2 useEffect(() => { console.log("countRef", countRef); }, [countRef.current]);

    return ( <div> <p>{count}</p> <button onClick={() => setCount((c) => c + 1)}>button1</button> <button onClick={() => (countRef.current += 1)}>button2</button> </div> );};

    現(xiàn)象

    • 點(diǎn)擊 button1 時(shí),會(huì)觸發(fā) useEffect1

    • 點(diǎn)擊 button2 時(shí),不會(huì)觸發(fā) useEffect2

    • 再次點(diǎn)擊 button1 時(shí),會(huì)觸發(fā) useEffect1useEffect2

    問題原因:只有狀態(tài)變更的時(shí)候,才會(huì)觸發(fā)更新,而狀態(tài)變更,只有 useStateuseReducer 可以觸發(fā)更新。

    使用指南:建議不要使用 useRef 的值作為依賴,除非你十分確定當(dāng) useRef 的值改變時(shí),有state發(fā)生了改變。



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

    手機(jī)掃一掃分享

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

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    天天弄| 3p在线国产| 国产美女被干网站 | 欧美精品久久久久久久久爆乳 | 婷婷美女五月天亚洲综合网 |