react Hook踩坑指北—一文解决你所有关于setState的疑惑
Posted taohuaya
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react Hook踩坑指北—一文解决你所有关于setState的疑惑相关的知识,希望对你有一定的参考价值。
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
以上是关于react Hook踩坑指北—一文解决你所有关于setState的疑惑的主要内容,如果未能解决你的问题,请参考以下文章
三分钟入坑指北 🔜 Docsify + Serverless Framework 快速创建个人博客系统