NIUCLOUD是一款SaaS管理后台框架多应用插件+云编译。上千名开发者、服务商正在积极拥抱开发者生态。欢迎开发者们免费入驻。一起助力发展! 广告
### 修改状态是异步的 这个其实比较基础了。 ~~~ import React, { useState } from 'react'; export const MyComponent: React.FC<{}> = () => { const [value, setValue] = useState(0); function dealClick() { setValue(100); console.log(value); // <- 0 } return ( <span>Value is {value}, AnotherValue is {anotherValue}</span> ); } 复制代码 ~~~ `useState` 返回的修改函数是异步的,调用后并不会直接生效,因此立马读取 `value` 获取到的是旧值(`0`)。 React 这样设计的目的是为了性能考虑,争取把所有状态改变后只重绘一次就能解决更新问题,而不是改一次重绘一次,也是很容易理解的。 ### 在 timeout 中读不到其他状态的新值 ~~~ import React, { useState, useEffect } from 'react'; export const MyComponent: React.FC<{}> = () => { const [value, setValue] = useState(0); const [anotherValue, setAnotherValue] = useState(0); useEffect(() => { window.setTimeout(() => { console.log('setAnotherValue', value) // <- 0 setAnotherValue(value); }, 1000); setValue(100); }, []); return ( <span>Value is {value}, AnotherValue is {anotherValue}</span> ); } 复制代码 ~~~ 这个问题和上面使用 `useState` 去记录 `timer` 类似,在生成 timeout 闭包时,value 的值是 0。虽然之后通过 `setValue` 修改了状态,但 React 内部已经指向了新的变量,而旧的变量仍被闭包引用,所以闭包拿到的依然是旧的初始值,也就是 0。 要修正这个问题,也依然是使用 `useRef`,如下: ~~~ import React, { useState, useEffect, useRef } from 'react'; export const MyComponent: React.FC<{}> = () => { const [value, setValue] = useState(0); const [anotherValue, setAnotherValue] = useState(0); const valueRef = useRef(value); valueRef.current = value; useEffect(() => { window.setTimeout(() => { console.log('setAnotherValue', valueRef.current) // <- 100 setAnotherValue(valueRef.current); }, 1000); setValue(100); }, []); return ( <span>Value is {value}, AnotherValue is {anotherValue}</span> ); } 复制代码 ~~~ ### 还是 timeout 的问题 假设我们要实现一个按钮,默认显示 false。当点击后更改为 true,但两秒后变回 false( true 和 false 可以互换)。考虑如下代码: ~~~ import React, { useState } from 'react'; export const MyComponent: React.FC<{}> = () => { const [flag, setFlag] = useState(false); function dealClick() { setFlag(!flag); setTimeout(() => { setFlag(!flag); }, 2000); } return ( <button onClick={dealClick}>{flag ? "true" : "false"}</button> ); } 复制代码 ~~~ 我们会发现点击时能够正常切换,但是两秒后并不会变回来。究其原因,依然在于 `useState` 的更新是重新指向新值,但 timeout 的闭包依然指向了旧值。所以在例子中,`flag` 一直是 `false`,虽然后续 `setFlag(!flag)`,但依然没有影响到 timeout 里面的 `flag`。 解决方法有二。 第一个还是利用 `useRef` ~~~ import React, { useState, useRef } from 'react'; export const MyComponent: React.FC<{}> = () => { const [flag, setFlag] = useState(false); const flagRef = useRef(flag); flagRef.current = flag; function dealClick() { setFlag(!flagRef.current); setTimeout(() => { setFlag(!flagRef.current); }, 2000); } return ( <button onClick={dealClick}>{flag ? "true" : "false"}</button> ); } ~~~ 第二个是利用 `setFlag` 可以接收函数作为参数,并利用闭包和参数来实现 ~~~ import React, { useState } from 'react'; export const MyComponent: React.FC<{}> = () => { const [flag, setFlag] = useState(false); function dealClick() { setFlag(!flag); setTimeout(() => { setFlag(flag => !flag); }, 2000); } return ( <button onClick={dealClick}>{flag ? "true" : "false"}</button> ); } ~~~ 当 `setFlag` 参数为函数类型时,这个函数的意义是告诉 React 如何从**当前状态**产生出**新的状态**(类似于 redux 的 reducer,不过是只针对一个状态的子 reducer)。既然是当前状态,因此返回值取反,就能够实现效果。