[react]从一次 BUG 调试修复说起 -- react 竞态

609 阅读2分钟

最近在调试一个 react 项目的 bug 时,发现 props 的值会出现不一致的情况,分析了一下原因,发现出现不一致的情况时,错误的 props 传递均是在父组件调用 useEffect() 时传递的,具体来说是子组件调用了父组件的获取数据的方法,父组件自己又调用了一次这个方法,导致错误出现,由此引出这个一不注意就会出现的问题 --竞态(Race Conditions)。

useEffect中异步获取数据,之后把返回的数据设置给state,这是在 react 中很常见的写法,但是如果接口返回的时间不一样, 就会导致页面上显示的是返回最慢的接口数据,不是最后一次发起请求的数据,下面是模拟的请求数据和场景:

import { useState, useEffect } from 'react';
const Race = () => {
  const [data, setData] = useState({});
  const [id, setId] = useState(1);
  // 模拟数据
  const fechFakeData = (id: number): {} => {
    // 模拟不同的返回时间
    const delayTime = id === 2 ? 2000 : 500;
    return new Promise((res) => {
      setTimeout(() => {
        res({ id, name: `user--${id}` });
      }, delayTime);
    });
  };
  useEffect(() => {
    const fetchData = async () => {
      const response = await fechFakeData(id);
      setData(response);
    };
    fetchData();
  }, [id]);
  return (
    <div>
      <p>id:{id}</p>
      <p>{JSON.stringify(data)}</p>
      <button
        onClick={() => {
          setId(2);
        }}
      >
        get user 2 data
      </button>
      <button
        onClick={() => {
          setId(3);
        }}
      >
        get user 3 data
      </button>
    </div>
  );
};

export default Race;

可以看到,点击 get user 2 data 按钮之后在 2s 之内再去点击 get user 3 data 按钮,数据不一致的情况就出现了,明明 id 为 3,但是显示的确实 id 为 2 的数据,这就是竞态导致的问题

解决方法就是新增一个变量去控制是否去setData

// ...
useEffect(() => {
  // 新增flag控制
  let active = true;
  const fetchData = async () => {
    const response = await fechFakeData(id);
    if (active) {
      setData(response);
    }
  };
  fetchData();
  //  id 变化时设置数据失效
  //  active = false;
  return () => {
    active = false;
  };
}, [id]);
// ...

我们来看一下ahookuseRequest 是怎么解决这个问题的源码

WechatIMG13480.png 基本原理差不多,就是设置一个count变量去控制,请求一次就你 count+=1 ,同时赋值给一个常量currentCount, 二者相等的时候才去setState,最后在组件卸载的时候count+=1 不执行最后一次请求

WechatIMG13484.png