useState异步回调获取不到最新值
原文链接:https://blog.csdn.net/qq_47305413/article/details/135703377
类组件中定义state,通过this.setState更新state里的值。而函数组件中的useState可以使函数组件像类组件一样拥有state。
类组件中通过this.setState更新state,进而改变UI视图。函数组件中通过useState来改变UI视图。
1、useState用法: 三个参数用法
import React, { useState } from 'react';
const A = props => {
const [state, dispatch] = useState(initData)
}
export default A
//state:目的提供给 UI ,作为渲染视图的数据源。
//dispatch:改变 state 的函数,推动函数组件渲染的渲染函数
dispatch两种使用方式
// 第一种:作为非函数,作为新的值,赋予给 state,作为下一次渲染使用
const [ count, setCount ] = React.useState(0)
const handleClick=()=>{
setNumber(1)
}
console.log(count, 'count') // 1
// 第二种:作为函数,这里可以称它为reducer,reducer 参数,是上一次返回最新的 state,返回值作为新的 state。
const [ number , setNumber ] = React.useState(0)
const handleClick=()=>{
setNumber((state)=> state + 1) // state - > 0 + 1 = 1
setNumber(8) // state - > 8
setNumber((state)=> state + 1) // state - > 8 + 1 = 9
}
initData:两种使用方式
// 第一种:作为非函数,作为 state 初始化的值。
const [count, setCount] = useState(0)
// 第二种:作为函数,函数的返回值作为 useState 初始化的值
const [count, setCount] = React.useState(() => {
if(props.c < 5) return 0
if(props.c < 10) return 1
return 2
})
// props中的参数c小于数字5, 返回0,即count=0。
// c小于数字10,大于5, 返回1,即count=1。
// c大于10,返回2,即count=2
2、如何监听state的变化
类组件 setState 中,有第二个参数 callback 或者是生命周期componentDidUpdate 可以检测监听到 state 改变或是组件更新。
在函数组件中,通过useEffect监听。把 state 作为依赖项传入 useEffect 第二个参数 deps ,但是注意 useEffect 初始化会默认执行一次。
import React, { useState } from 'react';
export default function A (props){
const [ number , setNumber ] = useState(0)
/* 监听 number 变化 */
React.useEffect(()=>{
console.log('监听number变化,此时的number是: ' + number )
},[ number ])
const handerClick = ()=>{
/** 高优先级更新 **/
ReactDOM.flushSync(()=>{
setNumber(2)
})
/* 批量更新 */
setNumber(1)
/* 滞后更新 ,批量更新规则被打破 */
setTimeout(()=>{
setNumber(3)
})
}
console.log(number)
return <div>
<span> { number }</span>
<button onClick={ handerClick }>点击</button>
</div>
}
// 2
// 监听number变化,此时的number是: 2
// 1
// 监听number变化,此时的number是: 1
// 3
// 监听number变化,此时的number是: 3
3、dispatch更新特点
在使用useState 有一点值得注意,当调用改变 state 的函数dispatch,函数组件中dispatch 更新效果和类组件是一样的,是获取不到最新的 state 值的
const [ number , setNumber ] = React.useState(0)
const handleClick = ()=>{
ReactDOM.flushSync(()=>{
setNumber(2)
console.log(number, '2')
})
setNumber(1)
console.log(number, '1')
setTimeout(()=>{
setNumber(3)
console.log(number, '3')
})
}
// 0 '2'
// 0 '1'
// 0 '3'
4、解决上述demo中使用useState 异步回调获取不到最新值
useState中的dispatch参数,既可以作为非函数,也可以作为函数。
4.1 下面这种情况的写法,仍获取不到最新值:
本应输出 [0, 1, 2],打印结果却不是
import React, { useState, useEffect } from 'react';
const App = () => {
const [arr, setArr] = useState([0]);
useEffect(() => {
console.log(arr);
}, [arr]);
const handleClick = () => {
Promise.resolve().then(() => {
setArr([...arr, 1]); // 此时赋值前 arr 为:[0]
})
.then(() => {
setArr([...arr, 2]); // 此时赋值前 arr 为旧状态仍然为:[0]
});
}
return (
<>
<button onClick={handleClick}>change</button>
</>
);
}
export default App;
// 输出结果
[0] useEffect 初始化会默认执行一次
[0,1]
[0,2]
// 例2:
const [number, setNumber] = useState([0]);
const handleClick= () => {
setTimeout(()=>{
setNumber([...number, 1])
},100)
setTimeout(()=>{
setNumber([...number, 2])
},500)
}
useEffect(() => {
console.log(number, 'number' )
}, [number]);
return (
<>
<button onClick={handleClick}>change</button>
</>
);
// [0] useEffect 初始化会默认执行一次
// [0,1]
// [0,2]
第一次 setArr 后 arr 的值确实更新了,我们也可以在上面输出结果中看到,但此次执行的 handleClick 事件处理函数作用域还是旧的,里面引用的 arr 仍然为旧的,导致第二次 setArr 后结果为 [0, 2]
4.2 解决方案1:dispatch参数,作为函数。
import React, { useState, useEffect } from 'react';
const App = () => {
const [arr, setArr] = useState([0]); // 声明一个数组 const arr = [0]
useEffect(() => {
console.log(arr);
}, [arr]);
const handleClick = () => {
Promise.resolve().then(() => {
setArr(prevState => [...prevState, 1]); // 或者这样写:setArr([...arr, 1]);
})
.then(() => {
setArr(prevState => [...prevState, 2]); // 这里必须改成回调函数传参方式,否则会读取旧状态,导致异常
});
}
return (
<>
<button onClick={handleClick}>change</button>
</>
);
}
export default App;
// 输出结果
[0] useEffect 初始化会默认执行一次
[0,1]
[0,1,2]
// 例2:
const [fields, setfields] = React.useState([0]);
const intervalRef = React.useRef();
function change(){
setTimeout(()=>{
setfields([...intervalRef.current, 1])
},100)
setTimeout(()=>{
setfields([...intervalRef.current, 2])
},500)
}
React.useEffect(() => {
intervalRef.current = fields;
console.log(fields, '123' )
}, [fields]);
// [0] '123' useEffect 初始化会默认执行一次
// [0,1] '123'
// [0,1,2] '123'
4.3 解决方案二
利用 ref ,state 发生改变同时将值映射到 ref, ref 的改变不会触发页面更新,但在异步中一定能拿到最新值,
所以需要在页面上用就使用 state,在异步逻辑中用就使用 ref
import React, { useState, useRef, useEffect } from 'react';
const App = () => {
const [arr, setArr] = useState([0]);
let refArr = useRef(); // 1. 声明一个refArr
useEffect(() => {
// 2. 把最新的arr的值赋值给refArr.current。第一次时refArr.current = [0]
refArr.current = arr;
console.log(arr);
}, [arr]);
const handleClick = () => {
Promise.resolve().then(() => {
// 3. ...拓展运算符展开refArr.current,然后再与1进行合并,声明一个变量now接受新合并的值,得到 const now = [0,1],
const now = [...refArr.current, 1];
setArr(now); // 4. 通过setArr更新arr数组
// 5. 这一步代码必须有,也是最关键的一步,这一步代码中把拿到最新now的值 再 重新赋值给refArr.current,保证了第6步中2能添加进去。如果不写这一步,里面引用的 arr 仍然为旧的,导致第二次 setArr 后结果为 [0, 2]
refArr.current = now;
})
.then(() => {
setArr([...ref.current, 2]); // 6. ...拓展运算符展开refArr.current,然后再与2进行合并
});
}
return (
<>
<h1>{arr.toString()}</h1>
<button onClick={handleClick}>change</button>
</>
);
}
export default App;
// [0] useEffect 初始化会默认执行一次
// [0,1]
// [0,1,2]
// 例2
const [fields, setfields] = React.useState([0]);
const intervalRef = React.useRef();
React.useEffect(() => {
intervalRef.current = fields;
console.log(fields, '123' )
}, [fields]);
function change(){
setTimeout(()=>{
setfields([...intervalRef.current, 1])
},100)
setTimeout(()=>{
setfields([...intervalRef.current, 2])
},500)
}
// [0] '123'
// [0,1] '123'
// [0,1,2] '123'
4.4 解决方案三(方案二优化版)
封装一个 hooks 将 state 和 ref 进行关联,同时再提供一个方法供异步中获取最新值使用,例如:
// 封装hooks
const useGetState = (initVal) => {
const [state, setState] = useState(initVal);
const ref = useRef(initVal);
const setStateCopy = (newVal) => {
ref.current = newVal;
setState(newVal);
}
const getState = () => ref.current;
return [state, setStateCopy, getState];
}
/////////////////////////////////////////
// 使用示例
const App = () => {
const [arr, setArr, getArr] = useGetState([0]);
useEffect(() => {
console.log(arr);
}, [arr]);
const handleClick = () => {
Promise.resolve().then(() => {
setArr([...getArr(), 1]);
})
.then(() => {
setArr([...getArr(), 2]);
});
}
return (
<>
<h1>{arr.toString()}</h1>
<button onClick={handleClick}>change</button>
</>
);
}

浙公网安备 33010602011771号