React Hook基本使用踩坑指南
React因為提倡函數(shù)式編程,所以提出了Hook思想來增強函數(shù)組件的功能,以此來替代基于Class的組件。但是我們有可能從基于Class這樣的實例化組件轉(zhuǎn)向函數(shù)組件時思想還沒有完全轉(zhuǎn)過來,還是用基于實例的思想考慮問題,忘記了函數(shù)有用完即銷毀(特別是純函數(shù))這樣的特點,導(dǎo)致在使用React Hook的時候可能會出一些問題。下面的內(nèi)容就來記錄一下,使用React Hook的新手可能會遇到的一些問題。
Hook使用原則
首先要強調(diào)一下Hook的使用原則,可以參考我之前寫的文章:如何使用React Hook最后的部分,也可以參考一下官網(wǎng)。遵守Hook使用原則,可以幫助你在使用Hook時避免很多問題。
useState數(shù)據(jù)只能通過setState來修改
我們舉一個代碼的例子:
import React, { useState } from 'react'// 子組件 function Child({ userInfo }) {// render: 初始化 state// re-render: 只恢復(fù)初始化的 state 值,不會再重新設(shè)置新的值// 只能用 setName 修改const [ name, setName ] = useState(userInfo.name)return <div><p>Child, props name: {userInfo.name}</p><p>Child, state name: {name}</p></div> }function App() {const [name, setName] = useState('中國')const userInfo = { name }return <div><div>Parent <button onClick={() => setName('美國')}>setName</button></div><Child userInfo={userInfo}/></div> }export default App這里有一個簡單的父子組件的例子,實現(xiàn)效果大概是這樣的:
當(dāng)我們點擊setName按鈕之后,結(jié)果變成這樣:
你可能會覺得很奇怪,我明明把父組件的數(shù)據(jù)傳遞給子組件了,但是我子組件的數(shù)據(jù)為什么還是中國而不是美國呢?
這是useState的一個規(guī)則,就是state值得更新,只能通過setState來去做,這是一條鐵律,即使父組件的值修改也不能改變子組件的值,要想修改子組件的值只能通過setState來去修改。
你可能又會有一個新的疑惑,函數(shù)組件不是執(zhí)行完之后就銷毀了嗎,re-render的時候,子組件怎么能記住之前的值呢?這是因為React內(nèi)部有一個記憶機制,會按順序記住之前函數(shù)組件的值。所以我們在使用Hook的時候一定不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook,因為只要有條件判斷,state在re-render重新賦值的時候,值就可能發(fā)生錯亂(React只能保證按順序賦值),導(dǎo)致最后輸出結(jié)果不符合預(yù)期。
useEffect修改state時本身狀態(tài)對結(jié)果影響
舉一下代碼的例子:
function UseEffectChangeState() {const [count, setCount] = useState(0)// 模擬 DidMountuseEffect(() => {console.log('useEffect...', count)// 定時任務(wù)const timer = setInterval(() => {console.log('setInterval...', count)// setCount(count + 1)setCount(count+1)}, 1000)// 清除定時任務(wù)return () => clearTimeout(timer)}, []) // 依賴為 []// 依賴為 [] 時: re-render 不會重新執(zhí)行 effect 函數(shù)// 沒有依賴:re-render 會重新執(zhí)行 effect 函數(shù)return <div>count: {count}</div> }export default UseEffectChangeState這是一個很簡單的定時任務(wù),我們希望使用useEffect來實現(xiàn)DidMount是添加一個定時器并每秒更新一下count的值。然而顯示結(jié)果卻是這樣的:
可以看到,我們的期望落空了,count值始終是1,即使間隔100多秒也沒有發(fā)生任何變化。我們之前明明說,可以通過setState來修改值,這里也的確是用了setState,可是為什么結(jié)果沒有達到預(yù)期呢?
因為如果useEffect是DidMount狀態(tài)話,re-render的時候是不會重新執(zhí)行useEffect的,也就是setState沒有執(zhí)行,所以count始終是1。如果useEffect有DidUpdate狀態(tài)的話,re-render的時候,就會重新執(zhí)行useEffect,那樣的話,count的值是會發(fā)生變化,符合我們預(yù)期。
useEffect使用引用類型可能會發(fā)生死循環(huán)
在講解這個問題之前首先要理解一個概念:基本類型和引用類型,如果對這個概念不太清楚地話,可以參考我之前寫的一篇文章:通俗易懂講解JavaScript深拷貝和淺拷貝
我們再來看一段代碼,這段代碼和第二部分的useEffect的代碼基本一致:
function UseEffectChangeState() {const [count, setCount] = useState(0)// 模擬 DidMountuseEffect(() => {console.log('useEffect...', count)// 定時任務(wù)const timer = setInterval(() => {console.log('setInterval...', count)// setCount(count + 1)setCount(count+1)}, 1000)// 清除定時任務(wù)return () => clearTimeout(timer)}, [{}]) // 依賴為 [] 時: re-render 不會重新執(zhí)行 effect 函數(shù)// 沒有依賴:re-render 會重新執(zhí)行 effect 函數(shù)return <div>count: {count}</div> }export default UseEffectChangeState只是有一個地方不同,就是useEffect最后不是[]而改變成[{}]。我們在學(xué)習(xí)Hook文檔時,它舉的更新監(jiān)聽數(shù)值,使用useEffect實現(xiàn)update功能的例子都是使用的值類型(或者叫基本類型),如果我們使用引用類型(對象、數(shù)組)會怎么樣呢?
運行代碼你會發(fā)現(xiàn),count值居然也更新了,而沒有出現(xiàn)第二部分剛開始的那種情況(即count值始終保持為1),這就有問題啊,我沒有監(jiān)聽count,但它也更新,這代碼不符合預(yù)期。
為什么會出現(xiàn)這種情況呢?是因為react監(jiān)聽值發(fā)生變化,那如何判斷數(shù)值的確是發(fā)生變化了呢?它使用的是Object.is()的方式。你可以執(zhí)行一下看看:
Object.is('foo', 'foo'); // true Object.is('foo', 'bar'); // falseObject.is([], []); // false Object.is({}, {}); // false對于引用類型的話,它始終都是false,判斷機制失效了,所以就會一直更新。所以我們在使用useEffect更新的時候,一定要使用基本類型,而不要使用引用類型。
參考資料
[1] 如何使用React Hook
總結(jié)
以上是生活随笔為你收集整理的React Hook基本使用踩坑指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何使用React Hook
- 下一篇: 用Hook的方式实现防抖