React 中 useState 清理的必须性

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React 中 useState 清理的必须性相关的知识,希望对你有一定的参考价值。

React 中 useState 清理的必须性

最近踩到了一个坑,属于以前没有碰到过的问题,就是在本地测试的时候,docker 的 API 不知道为什么突然有了延迟,以至于在状态更新的时候,之前调用的数据重写了后来调用的数据。

本地写了一个可以复刻这个情况的代码,服务端:

const express = require('express');
const cors = require('cors');
// remove cors error
const corsOptions = 
  origin: 'http://localhost:3001',
  credentials: true, //access-control-allow-credentials:true
  optionSuccessStatus: 200,
;

const app = express();

app.use(cors(corsOptions));

// respond with "hello world" when a GET request is made to the homepage
app.get('/products/1', (req, res) => 
  setTimeout(() => 
    res.json(
      id: 1,
    );
  , 1000);
);

app.get('/products/2', (req, res) => 
  res.json(
    id: 2,
  );
);

app.listen(3030, () => 
  console.log('port listening to 3030');
);

本来想找个 Mock API 用的,不过没有延迟的设置,所以只能这么做了(叹气)。这个 dummy 服务端是只能接受 /products/1 /products/2 这两个 endpoints,对于 demo 来说够用了。主要用到的 packages 就 express 和 cors,如果想跑,就 init 一个项目直接 npm i 上面俩包就行。

客户端:

import  useEffect, useState  from 'react';

import './App.css';

function App() 
  const [timer, setTimer] = useState(1);
  const [data, setData] = useState(null);

  useEffect(() => 
    const controller = new AbortController();
    fetch(`http://localhost:3030/products/$timer`, 
      signal: controller.signal,
    )
      .then((data) => 
        if (data.ok) return data.json();
      )
      .then((res) => 
        console.log(res);
        setData(res);
      )
      .catch((e) => 
        console.log(e);
      );
  , [timer]);

  return (
    <div className="App">
      <input
        type="text"
        value=timer
        onChange=(e) => 
          setTimer(e.target.value);
        
      />
      <br />
      JSON.stringify(data)
    </div>
  );


export default App;

效果如下:

尽管输入的 id 是 2,理论上来说我想显示的内容就是 2,不过因为 1 有延迟,所以延迟的数据重写了本来应有的数据,导致渲染是正常的,数据是异常的这种事情。

这也说明了之前的项目有可能会出现同样的问题(心虚),这也算是切身体会了,对于所有 useEffect 中的异步操作,清理都是非常必要的事情。

解决方案也比较简单,fetch 支持 AbortController,在清理函数中调用即可。

实现效果如下:

代码如下:

// 其他一致
useEffect(() => 
  const controller = new AbortController();
  fetch(`http://localhost:3030/products/$timer`, 
    signal: controller.signal,
  )
    .then((data) => 
      if (data.ok) return data.json();
    )
    .then((res) => 
      console.log(res);
      setData(res);
    )
    .catch((e) => 
      console.log(e);
    );

  // 主要就是这里
  return () => 
    console.log('aborted');
    controller.abort();
  ;
, [timer]);

axios 部分也可以一样使用——axios 在 v0.22.0 之后就支持 AbortController 去取消 API 的调用:

useEffect(() => 
  const controller = new AbortController();

  axios
    .get(`/products/$timer`,  signal: controller.signal )
    .then((data) => 
      setData(data);
    );
  return () => 
    console.log('aborted');
    controller.abort();
  ;
, [timer]);

关于 Axios 其他的简易封装,可以参考这篇:axios 的简易封装,这里不多赘述。

初始化是必须的,不初始化会导致出错……至于为什么……我还得找下资料……

这个可运行的案例写的……真的是为了这碟醋,特地包了这盘饺子

以上是关于React 中 useState 清理的必须性的主要内容,如果未能解决你的问题,请参考以下文章

在useState中获得[react [duplicate]]后立即获取值]

React useState 和 useEffect 混淆

react使用hook——useState的坑

React useState:如何将三元运算符放在三元运算符中?

如何在 React 函数组件(useState 挂钩)中访问 ag-Grid API?

建议减少 React useState hooks 的数量