若是已經使用過 Hook,相信你必定回不去了,這種用函數的方式去編寫有狀態組件簡直太爽啦。javascript
若是還沒使用過 Hook,那你要趕忙升級你的 React(v16.8+),投入 Hook 的懷抱吧。html
至於 Hook 的好處這裏就很少說了,上一篇已經講過了——React Hook上車。java
Hook 雖好,操做不當但是容易翻車的哦。react
下面,咱們就來聊聊在使用過程當中可能遇到的坑吧......git
坑:useState的初始值,只在第一次有效github
證據:npm
當點擊按鈕修改name的值的時候,我發如今Child組件,是收到了,可是並無經過useState
賦值給name!segmentfault
const Child = ({data}) =>{ console.log('child render...', data) // 每次更新都會執行 const [name, setName] = useState(data) // 只會在首次渲染組件時執行 return ( <div> <div>child</div> <div>{name} --- {data}</div> </div> ); } const Hook =()=>{ console.log('Hook render...') const [name, setName] = useState('rose') return( <div> <div> {count} </div> <button onClick={()=>setName('jack')}>update name </button> <Child data={name}/> </div> ) }
上面咱們已經知道了useState()
只會在第一次渲染的時候才執行,那麼這有什麼實用價值嗎?答案:能夠把第一次 render 前執行的代碼放入其中。數組
例如:緩存
const instance = useRef(null); useState(() => { instance.current = 'initial value'; });
相似 class component 裏的constructor
和componentWillMount
。
啥?你還不知道 immutable 是個啥?甩手就是兩個連接:Immutable.js 瞭解一下、Immutable 詳解及在 React 實踐。
什麼是 Immutable Data?
首先,你要知道 JavaScript 中的對象通常是可變的(Mutable Data),由於使用了引用賦值。
Immutable Data 就是一旦建立,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操做都會返回一個新的 Immutable 對象。
雖然 class component 的 state 也提倡使用 immutable data,但不是強制的,由於只要調用了setState
就會觸發更新。
可是使用useState
時,若是在更新函數裏傳入同一個對象將沒法觸發更新。
證據:
const [list, setList] = useState([2,32,1,534,44]); return ( <> <ol> {list.map(v => <li key={v}>{v}</li>)} </ol> <button onClick={() => { // bad:這樣沒法觸發更新! setList(list.sort((a, b) => a - b)); // good:必須傳入一個新的對象! setList(list.slice().sort((a, b) => a - b)); }} >sort</button> </> )
以前就說過,Hook 產生問題時,90%都是閉包引發的。下面就來看一下這個詭異的bug:
function DelayCount() { const [count, setCount] = useState(0); function handleClickAsync() { setTimeout(function delay() { setCount(count + 1); // 問題所在:此時的 count 爲1s前的count!!! }, 1000); } function handleClickSync() { setCount(count + 1); } return ( <div> {count} <button onClick={handleClickAsync}>異步加1</button> <button onClick={handleClickSync}>同步加1</button> </div> ); }
點擊「異步加1」按鍵,而後當即點擊「同步加1」按鈕。你會驚奇的發現,count 只更新到 1。
這是由於 delay() 是一個過期的閉包。
來看看這個過程發生了什麼:
delay() 是一個過期的閉包,它使用在初始渲染期間捕獲的過期的 count 變量。
爲了解決這個問題,可使用函數方法來更新 count
狀態:
function DelayCount() { const [count, setCount] = useState(0); function handleClickAsync() { setTimeout(function delay() { setCount(count => count + 1); // 重點:setCount傳入的回調函數用的是最新的 state!!! }, 1000); } function handleClickSync() { setCount(count + 1); } return ( <div> {count} <button onClick={handleClickAsync}>異步加1</button> <button onClick={handleClickSync}>同步加1</button> </div> ); }
上一篇文章中咱們提到過:useEffect的 callback 函數要麼返回一個能清除反作用的函數,要麼就不返回任何內容。
而 async 函數返回的是 Promise 對象,那咱們要怎麼在 useEffect 的callback 中使用 async 呢?
最簡單的方法是IIFE
(自執行函數):
useEffect(() => { (async () => { await fetchSomething(); })(); }, []);
useEffect(()=>{ setCount(count); }, [count]);
依賴 count,callback 中又 setCount(count)。推薦啓用 eslint-plugin-react-hooks
中的 exhaustive-deps
規則。此規則會在添加錯誤依賴時發出警告並給出修復建議。
有時候,咱們須要將函數做爲依賴項傳入依賴數組中,例如:
// 子組件 let Child = React.memo((props) => { useEffect(() => { props.onChange(props.id) }, [props.onChange, props.id]); return ( <div>{props.id}</div> ); }); // 父組件 let Parent = () => { let [id, setId] = useState(0); let [count, setCount] = useState(0); const onChange = (id) => { // coding setCount(id); } return ( <div> {count} <Child onChange={onChange} id={id} /> // 重點:這裏有性能問題!!! </div> ); };
代碼中重點位置,每次父組件render,onChange引用值確定會變。所以,子組件Child一定會render,子組件觸發useEffect,從而再次觸發父組件render....循環往復,這就會形成死循環。下面咱們來優化一下:
// 子組件 let Child = React.memo((props) => { useEffect(() => { props.onChange(props.id) }, [props.onChange, props.id]); return ( <div>{props.id}</div> ); }); // 父組件 let Parent = () => { let [id, setId] = useState(0); let [count, setCount] = useState(0); const onChange = useCallback(() => { // 重點:經過useCallback包裹一層便可達到緩存函數的目的 // coding }, [id]); // id 爲依賴值 return ( <div> {count} <Child onChange={onChange} id={id} /> // 重點:這個onChange在每次父組件render都會改變! </div> ); };
useCallback
將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子組件時,它將很是有用。
useCallback(fn, deps)
至關於useMemo(() => fn, deps)
useEffect
裏面使用到的 state 的值, 固定在了useEffect
內部,不會被改變,除非useEffect
刷新,從新固定 state 的值。
useRef
保存任何可變化的值,.current
屬性老是取最新的值。
function Example() { const [count, setCount] = useState(0); const latestCount = useRef(count); useEffect(() => { // Set the mutable latest value latestCount.current = count; setTimeout(() => { // Read the mutable latest value console.log(`You clicked ${latestCount.current} times`); }, 3000); });
以上只是收集了一部分工做中可能會遇到的坑,大體分爲2種:
之後遇到其餘的問題會繼續補充...
參考:
react hooks踩坑記錄