React Hook 搞定 Race Condition

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

1、前言

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

2、场景

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

// 模拟网络请求,能够指定延迟时间
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 resultsegmentfault

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

3、解决方案

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

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 发生变化时,将以前的数据请求设置为失效。fetch

4、更好的方案

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

// 请求序号
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 个变量,一个变量用来标识当前请求的序号,另外一个记录上一个有效请求的序号。只有序号比上一个有效序号大的时候,才展现数据。这样就保证了旧的请求不会覆盖新的请求。code

最终的代码能够看这里ip

5、总结

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

相关文章
相关标签/搜索