千家信息网

使用React Hooks时要避免哪些错误

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,小编给大家分享一下使用React Hooks时要避免哪些错误,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!主要介绍一下 R
千家信息网最后更新 2026年11月01日使用React Hooks时要避免哪些错误

小编给大家分享一下使用React Hooks时要避免哪些错误,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

主要介绍一下 React hooks 错误使用方式,以及如何解决它们。

  • 不要更改 Hook 调用顺序

  • 不要使用过时状态

  • 不要创建过时的闭包

  • 不要将状态用于基础结构数据

  • 不要忘记清理副作用

1.不要更改 Hook 调用顺序

在写这篇文章的前几天,我编写了一个通过id获取游戏信息的组件,下面是一个简单的版本 FetchGame:

function FetchGame({ id }) {   if (!id) {     return 'Please select a game to fetch';   }    const [game, setGame] = useState({      name: '',     description: ''    });    useEffect(() => {     const fetchGame = async () => {       const response = await fetch(`/api/game/${id}`);       const fetchedGame = await response.json();       setGame(fetchedGame);     };     fetchGame();   }, [id]);    return (     
Name: {game.name}
Description: {game.description}
);

组件FetchGame 接收 id(即要获取的游戏的ID)。useEffect() 在await fetch(/game/${id})提取游戏信息并将其保存到状态变量game中。

打开演示(https://codesandbox.io/s/hooks-order-warning-rdxpg?file=/pages/index.js) 。组件正确地执行获取操作,并使用获取的数据更新状态。但是看看tab Eslint警告: 有 Hook 执行顺序不正确的问题。

问题发生在这一判断:

function FetchGame({ id }) {  if (!id) { return 'Please select a game to fetch'; }      // ... }

当id为空时,组件渲染'Please select a game to fetch'并退出,不调用任何 Hook。

但是,如果 id不为空(例如等于'1'),则会调用useState()和 useEffect()。

有条件地执行 Hook 可能会导致难以调试的意外错误。React Hook的内部工作方式要求组件在渲染之间总是以相同的顺序调用 Hook。

这正是钩子的第一条规则:不要在循环、条件或嵌套函数内调用 Hook。

解决方法就是将条件判断放到 Hook 后面:

function FetchGame({ id }) {   const [game, setGame] = useState({      name: '',     description: ''    });    useEffect(() => {     const fetchGame = async () => {       const response = await fetch(`/api/game/${id}`);       const fetchedGame = await response.json();       setGame(fetchedGame);     };  if (id) {      fetchGame();     }  }, [id]);   if (!id) { return 'Please select a game to fetch'; }   return (     
Name: {game.name}
Description: {game.description}
); }

现在,无论id是否为空,useState()和useEffect() 总是以相同的顺序被调用,这就是 Hook 应该始终被调用的方式。

2.不要使用过时状态

下面的组件MyIncreaser在单击按钮时增加状态变量count:

function MyIncreaser() {   const [count, setCount] = useState(0);    const increase = useCallback(() => {     setCount(count + 1);   }, [count]);    const handleClick = () {  increase(); increase(); increase();  };    return (     <>    
Counter: {count}
); }

这里有趣一点的是,handleClick调用了3次状态更新。

现在,在打开演示之前,问一个问题:如果单击一次按钮,计数器是否增加3?

打开演示(https://codesandbox.io/s/stale-variable-jo32q?file=/src/index.js),点击按钮一次,看看结果。

不好意思,即使在handleClick()中3次调用了increase(),计数也只增加了1。

问题在于setCount(count + 1)状态更新器。当按钮被点击时,React调用setCount(count + 1)3次

const handleClick = () {    increase();    increase();    increase();  };  / 等价:   const handleClick = () {    setCount(count + 1);    // count variable is now stale    setCount(count + 1);    setCount(count + 1);  };

setCount(count + 1)的第一次调用正确地将计数器更新为count + 1 = 0 + 1 = 1。但是,接下来的两次setCount(count + 1)调用也将计数设置为1,因为它们使用了过时的stale状态。

通过使用函数方式更新状态来解决过时的状态。我们用setCount(count => count + 1)代替setCount(count + 1):

function MyIncreaser() {   const [count, setCount] = useState(0);    const increase = useCallback(() => {  setCount(count => count + 1);  }, []);    const handleClick = () {     increase();     increase();     increase();   };    return (     <>    
Counter: {count}
); }

这里有一个好规则可以避免遇到过时的变量:

如果你使用当前状态来计算下一个状态,总是使用函数方式来更新状态:setValue(prevValue => prevValue + someResult)。

3.不要创建过时的闭包

React Hook 很大程序上依赖于闭包的概念。依赖闭包是它们如此富有表现力的原因。

JavaScript 中的闭包是从其词法作用域捕获变量的函数。不管闭包在哪里执行,它总是可以从定义它的地方访问变量。

当使用 Hook 接受回调作为参数时(如useEffect(callback, deps), useCallback(callback, deps)),你可能会创建一个过时的闭包,一个捕获了过时的状态或变量的闭包。

我们来看看一个使用useEffect(callback, deps) 而忘记正确设置依赖关系时创建的过时闭包的例子。

在组件中,useEffect()每2秒打印一次count的值

 const [count, setCount] = useState(0);    useEffect(function() {     setInterval(function log() {       console.log(`Count is: ${count}`);     }, 2000);   }, []);    const handleClick = () => setCount(count => count + 1);    return (     <>  
Counter: {count}
); }

打开演示(https://codesandbox.io/s/stale-variable-jo32q?file=/src/index.js),点击按钮。在控制台查看,每2秒打印的都 是 Count is: 0,,不管count状态变量的实际值是多少。

为啥这样子?

第一次渲染时, log 函数捕获到的 count 的值为 0。

之后,当按钮被单击并且count增加时,setInterval取到的 count 值仍然是从初始渲染中捕获count为0的值。log 函数是一个过时的闭包,因为它捕获了一个过时的状态变量count。

解决方案是让useEffect()知道闭包log依赖于count,并正确重置计时器

function WatchCount() {   const [count, setCount] = useState(0);    useEffect(function() {     const id = setInterval(function log() {       console.log(`Count is: ${count}`);     }, 2000);  return () => clearInterval(id); }, [count]);   const handleClick = () => setCount(count => count + 1);    return (     <>    
Counter: {count}
); }

正确设置依赖关系后,一旦count发生变化,useEffect()就会更新setInterval()的闭包。

为了防止闭包捕获旧值:确保提供给 Hook 的回调函数中使用依赖项。

4.不要将状态用于基础结构数据

有一次,我需要在状态更新上调用副作用,在第一个渲染不用调用副作用。useEffect(callback, deps)总是在挂载组件后调用回调函数:所以我想避免这种情况。

我找到了以下的解决方案

function MyComponent() {   const [isFirst, setIsFirst] = useState(true);   const [count, setCount] = useState(0);    useEffect(() => {     if (isFirst) {       setIsFirst(false);       return;     }     console.log('The counter increased!');   }, [count]);    return (        ); }

状态变量isFirst用来判断是否是第一次渲染。一旦更新setIsFirst(false),就会出现另一个无缘无故的重新渲染。

保持count状态是有意义的,因为界面需要渲染 count 的值。但是,isFirst不能直接用于计算输出。

是否为第一个渲染的信息不应存储在该状态中。基础结构数据,例如有关渲染周期(即首次渲染,渲染数量),计时器ID(setTimeout(),setInterval()),对DOM元素的直接引用等详细信息,应使用引用useRef()进行存储和更新。

我们将有关首次渲染的信息存储到 Ref 中:

const isFirstRef = useRef(true);  const [count, setCount] = useState(0);   useEffect(() => {    if (isFirstRef.current) {      isFirstRef.current = false;      return;    }    console.log('The counter increased!');  }, [count]);   return (      );

isFirstRef是一个引用,用于保存是否为组件的第一个渲染的信息。isFirstRef.current属性用于访问和更新引用的值。

重要说明:更新参考isFirstRef.current = false不会触发重新渲染。

5.不要忘记清理副作用

很多副作用,比如获取请求或使用setTimeout()这样的计时器,都是异步的。

如果组件卸载或不再需要该副作用的结果,请不要忘记清理该副作用。

下面的组件有一个按钮。当按钮被点击时,计数器每秒钟延迟增加1:

function DelayedIncreaser() {   const [count, setCount] = useState(0);   const [increase, setShouldIncrease] = useState(false);    useEffect(() => {     if (increase) {       setInterval(() => {         setCount(count => count + 1)       }, 1000);     }   }, [increase]);    return (     <>  
Count: {count}
); }

打开演示(https://codesandbox.io/s/unmounted-state-update-n1d3u?file=/src/index.js),点击开始按钮。正如预期的那样,状态变量count每秒钟都会增加。

在进行递增操作时,单击umount 按钮,卸载组件。React会在控制台中警告更新卸载组件的状态。

修复DelayedIncreaser很简单:只需从useEffect()的回调中返回清除函数:

// ...   useEffect(() => {    if (increase) {      const id = setInterval(() => {        setCount(count => count + 1)      }, 1000); return () => clearInterval(id);    }  }, [increase]);   // ...

也就是说,每次编写副作用代码时,都要问自己它是否应该清理。计时器,频繁请求(如上传文件),sockets 几乎总是需要清理。

6. 总结

从React钩子开始的最好方法是学习如何使用它们。

但你也会遇到这样的情况:你无法理解为什么他们的行为与你预期的不同。知道如何使用React Hook还不够:你还应该知道何时不使用它们。

首先不要做的是有条件地渲染 Hook 或改变 Hook 调用的顺序。无论Props 或状态值是什么,React都期望组件总是以相同的顺序调用Hook。

要避免的第二件事是使用过时的状态值。要避免过时 状态,请使用函数方式更新状态。

不要忘记指出接受回调函数作为参数的 Hook 的依赖关系:例如useEffect(callback, deps),useCallback(callback, deps),这可以解决过时闭包问题。

不要将基础结构数据(例如有关组件渲染周期,setTimeout()或setInterval())存储到状态中。经验法则是将此类数据保存在 Ref 中。

以上是"使用React Hooks时要避免哪些错误"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

状态 过时 组件 闭包 更新 函数 变量 按钮 副作用 顺序 信息 数据 方式 问题 演示 错误 基础 条件 篇文章 结构 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 湖北九亚互联网络科技有限公司 南京欣网互联网络科技有限公司 亿人帮互联网科技有限公司 互联网科技官网 互联网巨头反垄断金融科技 青岛互联网科技公司 智网互联网科技 贷财互联网科技 互联网科技缩写 碳银互联网科技有限公司 互联网金融科技委员会 富国互联网科技创新基金 苏州五指互联网络科技有限 搜狐科技互联网 恒生互联网科技指数跳水 易企秀互联网科技是做什么的 互联网公司属于科技吗 广州三三互联网科技有限公司 黑龙江互联网科技公司 广州全民伙伴互联网科技有限公司 上海睿民互联网科技有限公司 新华互联网科技 北京路客互联网科技有限公司 中国互联网科技股 青岛互联网科技公司 互联网科技股午盘股票涨跌 福州新华互联网科技学校 知乎 现代互联网科技股 互联网公司属于科技吗 艺电能源互联网科技有限公司招聘
0