React Hook 搞定 Race Condition

4

欢迎关注我的公众号睿Talk,获取我最新的文章:
clipboard.png

一、前言

Race Condition 是开发中经常遇到的问题,比如查询天气的时候,先输入“北京”,再输入“深圳”,这时将发起 2 个请求。很可第一个请求花的时间比第二个请求长,如果不做处理,最终看到的是北京的天气,而不是深圳。本文要讨论的就是如何使用 React Hooks 解决这种问题。

二、场景

假设有如下搜索的场景,当用户输入关键字的时候,系统根据关键字搜索,然后实时显示搜索结果。代码如下:

// 模拟网络请求,可以指定延迟时间
function getData(data, delay) {
  return new Promise(resolve => {
    setTimeout(()=>{
      resolve(`${data} result`);
    }, delay);
  })
}

function App() {
  const [query, setQuery] = useState('react');
  const [result, setResult] = useState();

  useEffect(() => {
    const fetchData = async () => {
      // 搜索 react1 时(第一个请求),2 秒后返回,其余 500 毫秒后返回
      const delay = query === 'react1' ? 2000 : 500;
      
      const result = await getData(query, delay);
      
      setResult(result); 
    }

    fetchData();
  }, [query]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <div>
        result: <span>{result}</span>
      </div>
    </Fragment>
  );
}

当我们输入react12345时,可以看到最终的结果是react1 result,而我们期望看到的结果是react12345 result

这现象的原因是更新数据的时候,没有对结果的有效性进行判断,用过期的数据覆盖了最新的数据。

三、解决方案

解决方式很简单,就是在更新数据前判断其有效性,改造一下useEffect部分的代码:

useEffect(() => {
  // 有效性标识
  let didCancel = false;

  const fetchData = async () => {
    const delay = query === 'react1' ? 2000 : 500;
    const result = await getData(query, delay);

    // 更新数据前判断有效性
    if (!didCancel) {
      setResult(result); 
    }
  }

  fetchData();

  return () => {
    // query 变更时设置数据失效
    didCancel = true;
  }
}, [query]);

这里利用了useEffect数据清理的特性,当 query 发生变化时,将之前的数据请求设置为失效。

四、更好的方案

上面这种方案虽然解决了问题,但体验并不好。在输入数据的过程中,并不能看到输入过程中返回的结果,只能看到最终的结果。我们期望的效果是输入过程中能实时展示有效的结果。再改造下代码:

// 请求序号
let seqenceId = 0;
// 上一个有效请求的序号
let lastId = 0;

function App() {
  const [query, setQuery] = useState('react');
  const [result, setResult] = useState();

  useEffect(() => {
    const fetchData = async () => {
      // 发起一个请求时,序号加 1
      const curId = ++seqenceId;

      const delay = query === 'react1' ? 2000 : 500;
      const result = await getData(query,delay);

      // 只展示序号比上一个有效序号大的数据
      if (curId > lastId) {
        setResult(result); 
        lastId = curId;
      } else {
        console.log(`discard ${result}`); 
      }
    }

    fetchData();
  }, [query]);

  return (
    ...
  );
}

这里引入了 2 个变量,一个变量用来标识当前请求的序号,另一个记录上一个有效请求的序号。只有序号比上一个有效序号大的时候,才展示数据。这样就保证了旧的请求不会覆盖新的请求。

最终的代码可以看这里

五、总结

本文讨论了开发过程中经常遇到的 Race Condition 问题,结合 React Hooks 给出了 2 种解题思路。一种是只展示最终的结果,隐藏过程结果;另一种是将过程中有效的结果也展示出来。可以根据实际的应用场景按需选用。


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

CRIMX · 7月28日

RxJS 一个 switchMap 就搞定

回复

1

用RxJS是很方便,但引入了额外的复杂度。本文主要是讲解题思路,完全可以封装一个自定义hook,用起来也很方便

dabai 作者 · 7月28日
载入中...