React Hook 搞定 Race Condition

项目延期
• 阅读 5024

欢迎关注我的公众号睿Talk,获取我最新的文章:
React Hook 搞定 Race Condition

一、前言

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 种解题思路。一种是只展示最终的结果,隐藏过程结果;另一种是将过程中有效的结果也展示出来。可以根据实际的应用场景按需选用。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
4年前
TurnipBit开发板DIY呼吸的吃豆人教程实例
  转载请以链接形式注明文章来源(MicroPythonQQ技术交流群:157816561,公众号:MicroPython玩家汇)  0x00前言  吃豆人是耳熟能详的可爱形象,如今我们的TurnipBit也集成了这可爱的图形,我们这就让他来呼吸了~。  0x01效果展示  先一起看下最终的成品演示视频:  http:/
Wesley13 Wesley13
4年前
Uber准备放弃自动驾驶,转手卖给前谷歌无人车CTO,估值曾被孙正义炒到72.5亿美元
!(https://oscimg.oschina.net/oscnet/0fe7cb00a0cf4872b022342d1e21d47e.png)杨净发自凹非寺量子位报道|公众号QbitAI最新消息,Uber要出售无人驾驶部门(ATG)了。据TechCrunch报道,Uber有意向出售,而也有人愿意买。
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Stella981 Stella981
4年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Stella981 Stella981
4年前
Linux日志安全分析技巧
0x00前言我正在整理一个项目,收集和汇总了一些应急响应案例(不断更新中)。GitHub地址:https://github.com/Bypass007/EmergencyResponseNotes本文主要介绍Linux日志分析的技巧,更多详细信息请访问Github地址,欢迎Star。0x01日志简介Lin