0.前言
目前react已全面擁抱hook,但使用hook進行開發時,你會發現state的值往往跟你想象的不一樣,為什么state會這么奇怪呢,通過以下案例,讓我們一探究竟吧。
1. state類型為Object或Array時,setState無法生效。
說明
當我們state所定義的state類型為Object或Array時,在回調中直接setState是無法成功的,demo如下:
function App() { const [obj,setObj] = useState({ num:1 }); const clickMe = () => { setObj(v => { let newObj = v newObj.num = v.num + 1 // 直接修改num的值不成功 return newObj }) } return ( <button onClick={clickMe}>{obj.num}</button> ); }
樣例——此時num的值一直為1。
原因
由於Object為引用類型,setState通過回調函數的形式賦值,其參數v存的是obj的地址,此時let newObj = v操作將newObj指向obj的地址,由於react中state是只讀的,因此newObj.num = v.num + 1這個操作相當於obj.num = obj.num +1,因此無法成功。
解決方案
通過淺拷貝或者深拷貝(相關資料網上很多)可解決此問題,將代碼修改如下:
function App() { const [obj,setObj] = useState({ num:1 }); const clickMe = () => { setObj(v => { let newObj = Object.assign({},v) // 對v進行淺拷貝 newObj.num = v.num + 1 return newObj }) } return ( <button onClick={clickMe}>{obj.num}</button> ); }
樣例此時newObj指向一個新的拷貝對象,可以任意修改newObj值,原值保持不變。
2. setState后值未立即發生改變
修改state后,如果直接調用此state,你會發現state的值未發生改變,demo如下:
function App() { const [num,setNum] = useState(0); const clickMe = () => { setNum(num+1) console.log(num) } return ( <button onClick={clickMe}>{num}</button> ); }
此時點擊button,第一次button顯示的num值變為1,而后台的num值顯示為0,多次點擊,后台總比視圖要少1。
原因
與react的更新有關,當調用setState時,react是異步更新state的,如果setState后立即獲取state的值,此時state尚未更新,因此為舊的狀態。
解決方案
修改state的同時需要使用state的值時,建議使用函數的方式修改並進行相關的使用操作,將上面的方法修改如下:
function App() { const [num,setNum] = useState(0); const clickMe = () => { setNum(num => { let newVal = num + 1 console.log(newVal) return num+1 }) } return ( <button onClick={clickMe}>{num}</button> ); }
3. 異步獲取的state值不是最新的state的值
當我們在一個異步函數中獲取state值時,如果異步未執行完成時修改這個state的值,異步結束后獲取的值仍然為原來的值,具體demo如下:
function App() { const [num, setNum] = useState(0); const clickMe = () => { setTimeout(() => alert(num), 2000); }; return ( <> <button onClick={clickMe}>click me</button> <input onChange={e => { setNum(e.target.value); }} /> </> ); }
樣例——在輸入框先輸入一組數字,點擊click me后再輸入幾個數字,彈出的信息為click時的數字。
原因
這是由於函數組件中state是閉包的,因此每次調用函數獲取的state只與當時的值有關(為什么要這樣設計可查看dan的文章:函數式組件與類組件有何不同?)。設想如果setTimeout是一個請求,那么請求成功后我們想要的應該是調用這個函數時的state,但有時候我們就是需要修改后的state,所以我們要使用其他方法去獲取這個值。
解決方案
通過useRef獲取當前值,useRef 返回一個可變的 ref 對象,num變化時修改numRecent.current的值,可將numRecent的值保持最新狀態。
function App() { const [num, setNum] = useState(0); const numRecent = useRef(''); const clickMe = () => { setTimeout(() => alert(numRecent.current), 2000); }; return ( <> <button onClick={clickMe}>click me</button> <input onChange={e => { numrecent.current = e.target.value; setNum(e.target.value); }} /> </> ); }
樣例-此時state始終與視圖保持一致。
4.利用通用方法避坑
實際開發中會經常遇到如上幾個問題,通過setState修改狀態的同時需要根據新的狀態進行一些操作,比如進行請求,修改obj的結構等,每次都要進行拷貝操作會讓代碼顯得冗余,狀態不一致性也讓人頭痛,因此建議將其簡單封裝為一個通用函數,具體如下:
const setState = (newState,changeStateFn, callback) => { changeStateFn((state) => { if(state.constructor === Object) { state = Object.assign({},state,newState) } if(state.construct === Array) { state = newState.slice() } callback(state) return state }) }
然后修改第1部分的方法如下:
const clickMe = () => { setState({num:obj.num+1},setObj,(v) =>{ console.log(v.num) }) } return ( <button onClick={clickMe}>{obj.num}</button> );
是不是清晰了很多呢?
附完整代碼:
import React, { Component,useState } from 'react'; import { render } from 'react-dom'; function App() { const [obj,setObj] = useState({ num:1 }); const setState = (newState,changeStateFn, callback) => { changeStateFn((state) => { if(state.constructor === Object) { state = Object.assign({},state,newState) } if(state.construct === Array) { state = newState.slice() } callback(state) return state }) } const clickMe = () => { setState({num:obj.num+1},setObj,(v) =>{ console.log(v.num) }) } return ( <button onClick={clickMe}>{obj.num}</button> ); } render(<App />, document.getElementById('root'));
5. 總結
以上都是開發中經常遇到的問題,希望能夠幫到大家,如果對您有幫助,還請幫忙點個贊呦。
轉載:https://blog.csdn.net/qq27229639/article/details/105531459