React useEffect依赖state更新未触发、执行时机异常问题咨询
问题核心原因
你遇到的现象是认知误区加代码bug共同导致的,逐个说明:
- useEffect执行规则认知错误
所有useEffect在组件首次挂载完成后,无论依赖数组配置什么内容,都会默认执行一次。依赖数组的作用仅为:组件完成首次渲染后的后续更新阶段,只有依赖项的值发生引用变化时,才会重新触发effect执行,不会跳过首次执行。
另外,同一组件内的多个useEffect在首次挂载阶段会严格按照代码书写顺序依次执行,你看到依赖token的effect先打印undefined,不是它执行更早:
- 首先执行空数组依赖的登录effect:内部调用
attemptLogin()时,await标记的fetch是异步网络请求,发起请求后会立刻让出主线程,不会阻塞后续代码运行,这个effect很快就执行完毕 - 紧接着执行依赖
token的effect:此时网络请求还没返回,token还是useState()初始化的默认值undefined,所以打印undefined,完全符合React执行逻辑
- Promise链式调用写法错误,导致setToken未正确触发更新
你的Promise链存在传值断裂问题:
.then(response => response.json()) .then(data => console.log(data.data)) .then(data => setToken(JSON.stringify(data)))
console.log的返回值固定为undefined,因此第三个then拿到的入参data已经不是接口返回的对象,而是undefined。此时JSON.stringify(undefined)会直接返回undefined(注意不是字符串"undefined",JSON对顶层传入的undefined会直接返回undefined值),相当于你调用了setToken(undefined),和token的初始值完全相等(Object.is(undefined, undefined)判定为true),React会直接跳过这次状态更新,不会触发重渲染,也不会重新执行依赖token的effect,这就是你看到setToken调用后effect没反应的根本原因。
修复方案
优先推荐用async/await配合try/catch的写法,避免then链的传值断裂问题,同时给token设置明确初始值,在effect里加空值拦截跳过首次无意义执行:
// 给token设置明确初始值null,避免和业务中的undefined混淆 const [token, setToken] = useState(null); useEffect(() => { async function attemptLogin() { try { const response = await fetch('http://localhost:3000/login'); const resData = await response.json(); console.log(resData.data); // 按业务需求存储token,如果你需要存的是接口返回的token字段,替换为resData对应字段即可 setToken(JSON.stringify(resData)); } catch (err) { console.error('error occured: ', err.message); } } attemptLogin(); }, []); useEffect(() => { // 首次执行时token为null,直接拦截不执行业务逻辑 if (!token) return; console.log('token更新后执行业务逻辑'); console.log(token); // 此处写需要携带token的后续接口请求等业务逻辑 }, [token]);
如果你要保留then链式写法,必须在每个then中手动return需要传递给下一环的数据:
fetch('http://localhost:3000/login') .then(response => response.json()) .then(data => { console.log(data.data); return data; // 手动返回接口数据,保证下一个then能拿到正确值 }) .then(data => setToken(JSON.stringify(data))) .catch(err => { console.error('error occured: ', err.message); });
额外注意点
- 不要在useEffect回调上直接加async,useEffect的回调返回值只能是清理函数,async函数默认返回Promise,会导致React无法正确执行清理逻辑
- 异步请求发起后不会阻塞主线程后续代码执行,不要写“等前面的请求执行完再跑后面effect”的错误逻辑,所有依赖异步结果的逻辑,都要放在对应state变化触发的effect中,同时做好空值判断
- 尽量避免混用async/await和then链的写法,减少隐式返回值导致的逻辑bug
内容的提问来源于stack exchange,提问作者Kolyo Peev
相关产品推荐
相关产品推荐

