大家好,我是Kason。您或您的同事在使用useEffect时是否遇到过以下场景:当状态a发生变化后,你想“发起请求”,于是使用useEffect:useEffect(()=>{fetch(xxx);},[a])这段代码运行正常,上线后没有问题。随着需求不断迭代,其他地方也会修改状态a。但是在那个需求中,状态a改变后不需要发起请求。不想动之前的代码,又要修复这个bug,所以加了一个判断条件:useEffect(()=>{if(xxxx){fetch(xxx);}},[a])某天,需求又变了!该请求现在还需要b字段。很简单,你只是把b添加为useEffect的依赖:useEffect(()=>{if(xxxx){fetch(xxx);}},[a,b])随着时间的推移,你逐渐发现“是否发送请求”与“if条件”相关。“是否发送请求”还与“a、b等依赖关系”有关。“A、B等依赖关系”与“许多需求”有关。真的很难说什么时候发送请求……如果上面的场景看起来很熟悉,那么新的React文档已经明确提供了解决方案。一些理论知识新文档的这一部分称为SynchronizingwithEffects[1],目前处于草案状态。但是其中提到的一些概念对于所有React开发者来说应该都很清楚。首先,效果部分属于EscapeHatches[2]一章。从命名上可以看出,开发者并不一定需要使用effect,只是特殊情况下的逃生通道。React中有两个重要的概念:渲染代码。事件处理程序。渲染代码指的是“开发者编写的组件渲染逻辑”,最终会返回一段JSX。例如,渲染代码在以下组件中:functionApp(){const[name,update]=useState('KaSong');return
Hello{name}
;}渲染代码特点是:他应该是“无副作用的纯函数”。以下渲染代码包含副作用(计数变化),不推荐:letcount=0;functionApp(){count++;const[name,update]=useState('KaSong');return
Hello{name}
;}Handlingsideeffects事件处理程序是“包含在组件内部的函数”,用于执行用户操作,可以包含副作用。以下操作属于事件处理程序:更新输入框。提交表格。导航到其他页面。在下面的例子中,组件内部的changeName方法属于Eventhandlers:functionApp(){const[name,update]=useState('KaSong');constchangeName=()=>{update('卡卡松');}return
Hello{name} ;}然而,并不是所有的副作用都可以在事件处理器中解决。例如,在聊天室中,“发送消息”是由用户触发的,应该由事件处理程序处理。另外,聊天室需要随时与服务器保持长连接。“保持长连接”的行为是一个副作用,但它不是由用户行为触发的。对于这种:视图渲染后触发的副作用属于effect,应该由useEffect处理。回到一开始的例子:当状态a发生变化后想要“发起请求”时,首先要明确你的需求是:“状态a发生变化,接下来需要发起请求”还是“某个用户行为需要发起请求,请求依赖状态a作为参数”?如果是后者,这是由用户操作触发的副作用,那么相关逻辑应该放在事件处理程序中。假设前面的代码逻辑是:点击按钮触发状态a的变化。使用Effect执行并发送请求。应该修改为:点击按钮,在事件回调中获取状态a的值。在事件回调中发送请求。此次修改后,“stateachange”与“sendrequest”之间不存在因果关系,后续对statea的修改将不再有“无意中触发请求”的顾虑。总结当我们写组件的时候,我们应该尽量把组件写成纯函数。对于组件中的副作用,首先要明确:是“用户行为触发”还是“视图渲染后主动触发”?对于前者,将逻辑放在事件处理程序中。对于后者,使用useEffect处理它。这就是为什么useEffect所在的部分在新文档中被称为EscapeHatches——在大多数情况下,你不会使用useEffect,只是其他情况不适用escapepod。参考资料[1]与效果同步:https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/synchronizing-with-effects。[2]逃生舱口:https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/escape-hatches。