本文已获得原作者Shadeed授权翻译。您可能已经阅读了很多有关如何使用ReactHooks的内容。但有时,知道何时不使用它与知道如何使用它同样重要。在这篇文章中,我主要介绍了Reacthooks的错误使用以及如何解决。不要改变Hook调用的顺序不要使用陈旧的状态不要创建陈旧的闭包不要为基础设施数据使用状态不要忘记清理副作用1.不要改变Hook调用的顺序ThefunctionFetchGame({id}){if(!id){return'Pleaseselectagametofetch';}const[game,setGame]=useState({name:'',description:''});useEffect(()=>{constfetchGame=async()=>{constresponse=awaitfetch(`/api/game/${id}`);constfetchedGame=awaitresponse.json();setGame(fetchedGame);};fetchGame();},[id]);return(
名称:{game.name}
描述:{game.description}
名称:{game.name}
描述:{game.description}
计数器:{count}
/>);}这里有趣的是handleClick调用状态更新3次。现在,在打开演示之前,问一个问题:如果单击按钮一次,计数器会增加3吗?打开demo(https://codesandbox.io/s/stale-variable-jo32q?file=/src/index.js),点击按钮一次查看结果。抱歉,即使在handleClick()中调用了3次increase(),计数也只增加了1。问题出在setCount(count+1)状态更新器上。单击按钮时,React调用setCount(count+1)3次//countvariableisnowstalesetCount(count+1);setCount(count+1);};第一次调用setCount(count+1)正确地将计数器更新为count+1=0+1=1。但是,接下来的两个setCount(count+1)调用也将计数设置为1,因为它们使用陈旧状态。通过使用功能方法更新状态来解决陈旧状态。我们使用setCount(count=>count+1)而不是setCount(count+1):functionMyIncreaser(){const[count,setCount]=useState(0);constincrease=useCallback(()=>{setCount(count=>count+1);},[]);constandleClick=(){increase();increase();increase();};return(<>计数器:{count}
>);}这里有一个避免陈旧变量的好规则:如果你使用当前状态计算下一个状态,总是使用函数方法来更新状态:setValue(prevValue=>prevValue+someResult)。3.不要创建过时的闭包ReactHook以编程方式严重依赖闭包的概念。依赖闭包是使它们如此富有表现力的原因。JavaScript中的闭包是从其词法范围捕获变量的函数。无论闭包在哪里执行,它总是可以从定义它的地方访问变量。当使用接受回调作为参数的Hook时(例如useEffect(callback,deps)、useCallback(callback,deps)),你可能会创建一个陈旧的闭包,一个捕获陈旧状态或变量的闭包。让我们看一下使用useEffect(callback,deps)并忘记正确设置依赖项时创建的陈旧闭包的示例。在组件中,useEffect()每2秒打印一次count的值const[count,setCount]=useState(0);useEffect(function(){setInterval(functionlog(){console.log(`Countis:${count}`);},2000);},[]);constandleClick=()=>setCount(count=>count+1);return(<计数器:{count}
>);}打开演示(https://codesandbox.io/s/stale-variable-jo32q?file=/src/index.js),点击按钮。在控制台上查看,每2秒打印一次Countis:0,不管count状态变量的实际值是多少。为什么会这样?第一次渲染时log函数捕获的count的值为0。稍后,当单击按钮并且计数增加时,setInterval获取的计数值仍然是从初始渲染中捕获计数0的值。日志函数是一个陈旧的闭包,因为它捕获了一个陈旧的状态变量计数。解决方法是让useEffect()知道闭包日志依赖count,重新设置定时器functionWatchCount(){const[count,setCount]=useState(0);useEffect(function(){constid=setInterval(functionlog()){console.log(`Countis:${count}`);},2000);return()=>clearInterval(id);},[count]);constandleClick=()=>setCount(count=>count+1);return(<>计数器:{count}
/>);正确设置依赖关系后,一旦计数发生变化,useEffect()将更新setInterval()的闭包。为防止闭包捕获旧值:确保在提供给Hook的回调函数中使用依赖项。4.不要对基础设施数据使用状态有一次,我需要在状态更新时调用副作用,而不是在第一次渲染时调用副作用。useEffect(callback,deps)总是在组件挂载后调用回调函数:所以我想避免这种情况。我找到了以下解决方案return;}console.log('Thecounterincreased!');},[count]);return(Count:{count}
/>);}打开demo(https://codesandbox.io/s/unmounted-state-update-n1d3u?file=/src/index.js),点击开始按钮。正如预期的那样,状态变量计数每秒递增。递增时,点击umount按钮卸载组件。React会在控制台警告更新未挂载组件的状态。修复DelayedIncreaser很容易:只需从useEffect()的回调中返回清除函数://...useEffect(()=>{if(increase){constid=setInterval(()=>{setCount(count=>count+1)},1000);return()=>clearInterval(id);}},[increase]);//...就是每次写副作用代码的时候,问问自己是不是该清理一下.定时器,频繁的请求(比如上传文件),sockets几乎总是需要清理的。6.总结开始使用Reacthooks的最好方法是学习如何使用它们。但是您也会遇到无法理解为什么他们的行为与您预期不同的情况。知道如何使用ReactHooks是不够的:您还应该知道什么时候不使用它们。首先不要做的是有条件地渲染Hook或改变Hook调用的顺序。React希望组件总是以相同的顺序调用Hooks,无论props或state值是什么。要避免的第二件事是使用陈旧的状态值。为避免陈旧状态,请使用功能方法来更新状态。不要忘记指明接受回调函数作为参数的Hooks的依赖项:例如useEffect(callback,deps)、useCallback(callback,deps),这可以解决陈旧的关闭问题。不要将基础结构数据(例如关于组件渲染周期、setTimeout()或setInterval())存储到状态中。经验法则是将此类数据保存在Ref中。最后,不要忘记清除副作用。~完了,我是小智,我来洗碗了。作者:Shadeed译者:前端小智来源:dmitripavlutin原文:https://dmitripavlutin.com/react-hooks-mistakes-to-avoid/代码注意。转载本文请联系大千世界公众号。