一文教你如何避免React中常見的8個錯誤
1. 組件卸載后執(zhí)行狀態(tài)更新
報錯信息: Can’t perform a React state update on an unmounted component
這個報錯就是因為在組件樹的某個地方,狀態(tài)更新被觸發(fā)到已經(jīng)卸載的組件上了。也就是說,我們不能在組件銷毀后設置 state,防止出現(xiàn)內(nèi)存泄漏。
const Component = () => { const [data, setData] = useState(null); useEffect(() => { fetchAsyncData().then((data) => setData(data)); }, []); // ... };
比如,在請求數(shù)據(jù)時,由于跳轉到了B頁面,A頁面的數(shù)據(jù)請求還在進行中,但是頁面已經(jīng)銷毀了,就會出現(xiàn)這種情況。那該如何解決這個問題呢?有兩種方法:
(1)組件卸載時取消異步請求
第一種方法(推薦),就是在組件卸載時取消異步請求。一些異步請求庫提供了取消異步請求的方法。如果沒有使用第三方庫,可以使用 AbortController
來取消。這種方法本質上就是在組件卸載時取消副作用:
const Component = () => { const [data, setData] = useState(null); useEffect(() => { const controller = new AbortController(); fetch(url, { signal: controller.signal }).then((data) => setData(data)); return () => { controller.abort(); } }, []); // ... };
(2)跟蹤組件是否已掛載
另外,可以跟蹤組件的掛載狀態(tài),如果還沒掛載或已經(jīng)卸載,返回 false;否則返回 true:
const Component = () => { const [data, setData] = useState(null); const isMounted = useRef(true); useEffect(() => { fetchAsyncData().then(data => { if(isMounted.current) { setData(data); } }); return () => { isMounted.current = false; }; }, []); // ... }
不過,不建議使用這種方法。這樣保留了未掛載組件的引用,可能會導致內(nèi)存泄漏和性能問題。
2. 渲染列表時不使用 key
報錯信息: Warning: Each child in a list should have a unique key prop
React 開發(fā)中最常見的就是遍歷數(shù)組來渲染組件。在JSX中,可以使用Array.map
將該邏輯嵌入到組件中,并在回調中返回所需的組件。如下:
import { Card } from "./Card"; const data = [ { id: 1, text: "JavaScript" }, { id: 2, text: "TypeScript" }, { id: 3, text: "React" } ]; export default function App() { return ( <div className="container"> {data.map((content) => ( <div className="card"> <Card text={content.text} /> </div> ))} </div> ); }
這樣會收到如下警告:Warning: Each child in a list should have a unique key prop
,這表示需要給生成的每個組件一個唯一的key。所以,要在map
回調返回的JSX的最外層元素添加一個key
值,該值應該是一個字符串或者數(shù)字,并且在這個組件列表中應該是唯一的。
export default function App() { return ( <div className="container"> {data.map((content) => ( <div key={content.id} className="card"> <Card text={content.text} /> </div> ))} </div> ); }
盡管不遵守這個要求也不會導致應用崩潰,但它可能會導致一些意外的情況。React 會使用這些key
來確定列表中的哪些子項發(fā)生了更改,并使用此信息來確定可以重用先前 DOM 的哪些部分,以及在重新渲染組件時應該重新計算哪些部分。 因此,建議添加 key。
3. Hooks 調用順序錯誤
報錯信息: React Hook "useXXX" is called conditionally. React Hooks must be called in the exact same order in every component render
先來看下面的代碼:
const Toggle = () => { const [isOpen, setIsOpen] = useState(false); if (isOpen) { return <div>{/* ... */}</div>; } const openToggle = useCallback(() => setIsOpen(true), []); return <button onClick={openToggle}>{/* ... */}</button>; };
當 isOpen
的值為true
時,就會直接return
那個div
元素。這樣當isOpen
的值為true
和false
時useCallback
Hook的調用順序就不一致了。這時React就會警告我們:React Hook "useCallback" is called conditionally. React Hooks must be called in the exact same order in every component render
。這其實就是React官方文檔中所說的,不要在循環(huán),條件或嵌套函數(shù)中調用 Hook, 確??偸窃?React 函數(shù)的最頂層以及任何 **return**
之前調用他們。遵守這條規(guī)則才能確保 Hook 在每一次渲染中都按照同樣的順序被調用。
可以這樣來修改上面的代碼:
const Toggle = () => { const [isOpen, setIsOpen] = useState(false); const openToggle = useCallback(() => setIsOpen(true), []); if (isOpen) { return <div>{/* ... */}</div>; } return <button onClick={openToggle}>{/* ... */}</button>; };
4. useEffect 缺少依賴
報錯信息: React Hook useEffect has a missing dependency: 'XXX'. Either include it or remove the dependency array
先來看看 React 官網(wǎng)給出的例子:
function Example({ someProp }) { function doSomething() { console.log(someProp); } useEffect(() => { doSomething(); }, []); }
在useEffect
中定義空的依賴數(shù)組是不安全,因為它調用的 doSomething
函數(shù)使用了 someProp
。這時就會報錯:React Hook useEffect has a missing dependency: 'XXX'. Either include it or remove the dependency array
。當props
中的someProp
發(fā)生變化時,函數(shù)doSomething
的結果就會發(fā)生變化,然而useEffect
的依賴數(shù)組為空,所以就不會執(zhí)行回調中的內(nèi)容。
有兩種方式來解決這個問題:
在useEffect
中聲明其所需函數(shù),這種方式適用于只需要調用一次的函數(shù),比如初始化函數(shù):
function Example({ someProp }) { useEffect(() => { function doSomething() { console.log(someProp); } doSomething(); }, [someProp]); }
使用useCallback
來定義依賴項,確保當自身依賴發(fā)生改變時函數(shù)主體也會改變:
function Example({ someProp }) { const doSomething = useCallback(() => { console.log(someProp); }, [someProp]) useEffect(() => { doSomething(); }, [doSomething]); }
5. 重新渲染過多
報錯信息: Too many re-renders. React limits the number of renders to prevent an infinite loop
這個報錯就是說重新渲染過多。React 限制渲染的數(shù)量以防止無限循環(huán)。當組件在很短的時間有太多狀態(tài)更新時,就可能會發(fā)生這種情況。導致無限循環(huán)的最常見原因是:
- 直接在渲染中執(zhí)行狀態(tài)更新;
- 未向事件處理程序提供適當?shù)幕卣{。
如果遇到這個警告,可以檢查組件的這兩個方面:
const Component = () => { const [count, setCount] = useState(0); setCount(count + 1); // 渲染中的狀態(tài)更新 return ( <div className="App"> {/* onClick 沒有正確的回調 */} <button onClick={setCount((prevCount) => prevCount + 1)}> Increment that counter </button> </div> ); }
6. 渲染的單條數(shù)據(jù)為對象
報錯信息: Objects are not valid as a React child / Functions are not valid as a React child
在 React 中,我們可以在組件中渲染到 DOM 中的東西有很多,比如:HTML標簽、JSX元素、原始 JavaScript 值、JavaScript 表達式等。但是不能將對象和函數(shù)渲染到 DOM 中,因為這兩個值不會解析為有意義的值,如果渲染了對象或函數(shù),就會報上面的錯誤。解決這個問題的方法很簡單,就是檢查渲染的內(nèi)容是否是有效的值:
const Component = ({ body }) => ( <div> <h1>{/* */}</h1> {/* 必須確保 body 是有效的 React child */} <div className="body">{body}</div> </div> );
7. 相鄰JSX元素沒有包裝在封閉標記中
報錯信息: Adjacent JSX elements must be wrapped in an enclosing tag
這個報錯就是說相鄰JSX元素必須包裝在封閉標記中,也就是必須要有一個根元素:
const Component = () => ( <Nice /> <Bad /> );
從 React 開發(fā)人員的角度來看,這個組件只會在另一個組件內(nèi)部使用。 因此,在他們的心智模型中,從一個組件返回兩個元素是有意義的,因為生成的 DOM 結構將是相同的,無論外部元素是在此組件中定義還是在父組件中定義。但是,React 無法做出這種假設。該組件可能會在根目錄中使用并破壞應用,因為它會導致無效的 DOM 結構。
所以,應該始終將組件返回的多個 JSX 元素包裝在一個封閉標記中??梢允且粋€元素、一個組件或者 React Fragment:
const Component = () => ( <React.Fragment> <Nice /> <Bad /> </React.Fragment> );
或者直接使用一個空標簽來包裝兩個 JSX 元素:
const Component = () => ( <> <Nice /> <Bad /> </> );
8. 使用舊的狀態(tài)
先來看一個計數(shù)器的例子:
const Increaser = () => { const [count, setCount] = useState(0); const increase = useCallback(() => { setCount(count + 1); }, [count]); const handleClick = () => { increase(); increase(); increase(); }; return ( <> <button onClick={handleClick}>+</button> <div>Counter: {count}</div> </> ); }
這里的handleClick
方法會在點擊按鈕后執(zhí)行三次增加狀態(tài)變量count
的操作。那么點擊一次是否會增加3呢?事實并非如此。點擊按鈕之后,count
只會增加1。問題就在于,當我們點擊按鈕時,相當于下面的操作:
const handleClick = () => { setCount(count + 1); setCount(count + 1); setCount(count + 1); };
當?shù)谝淮握{用setCount(count + 1)
時是沒有問題的,它會將count
更新為1。接下來第2、3次調用setCount
時,count
還是使用了舊的狀態(tài)(count為0),所以也會計算出count
為1。發(fā)生這種情況的原因就是狀態(tài)變量會在下一次渲染才更新。
解決這個問題的辦法就是,使用函數(shù)的方式來更新狀態(tài):
const Increaser = () => { const [count, setCount] = useState(0); const increase = useCallback(() => { setCount(count => count + 1); }, [count]); const handleClick = () => { increase(); increase(); increase(); }; return ( <> <button onClick={handleClick}>+</button> <div>Counter: {count}</div> </> ); }
這樣改完之后,React就能拿到最新的值,當點擊按鈕時,就會每次增加3。所以需要記?。?strong>如果要使用當前狀態(tài)來計算下一個狀態(tài),就要使用函數(shù)的式方式來更新狀態(tài):
setValue(prevValue => prevValue + someResult)
以上就是一文教你如何避免React中常見的8個錯誤的詳細內(nèi)容,更多關于React常見錯誤避免的資料請關注腳本之家其它相關文章!
相關文章
React實現(xiàn)antdM的級聯(lián)菜單實例
這篇文章主要為大家介紹了React實現(xiàn)antdM的級聯(lián)菜單實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10React中的useEffect useLayoutEffect到底怎么用
這篇文章主要介紹了React中的useEffect useLayoutEffect具體使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-02-02React性能優(yōu)化系列之減少props改變的實現(xiàn)方法
這篇文章主要介紹了React性能優(yōu)化系列之減少props改變的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01ES6 class類鏈式繼承,實例化及react super(props)原理詳解
這篇文章主要介紹了ES6 class類鏈式繼承,實例化及react super(props)原理,結合實例形式詳細分析了ES6 中class類鏈式繼承,實例化及react super(props)原理相關概念、原理、定義與使用技巧,需要的朋友可以參考下2020-02-02