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