关于 react 的 useEffect ,如果我要监听一个值,并且在值变化的时候做一些函数调用
useEffect(()=>{
if(type === aaa){
aHandle();
}else if(type === bbb){
bHandle();
}
}, [type])

那我就需要把 aHandle 和 bHandle 放入依赖项中,否则 eslint 或者 sonarlint 会报警告
useEffect(()=>{
if(type === aaa){
aHandle();
}else if(type === bbb){
bHandle();
}
}, [type, aHandle, bHandle])

那这样岂不是每次 render ,都会重新生成新的 ahandle 和 bHandle ,每次都会触发 useEffect 吗?
我知道有 useCallback 和 useRef 可以解决函数在每次 render 都重新生成的问题,但问题是假如我在 aHandle 里去调接口,也要获取很多放在 state 中的值作为参数,那么 useCallback 还得把那些值全部放在 useCallback 的依赖项里,搞得越来越复杂了。
难道只能用 useRef 或者基于 useRef+useCallback 封装的一些 hook ,把每个 aHandle 或者 bHandle 给套上吗。
有没有更优雅一点的写法,我想要有一个 useXXXEffect,可以只监听一个 type ,在这里面获取到的其他值都是最新的,不再额外需要传入 aHandle 或者 bHandle 。
有没有这样的 hook 或者封装成这种效果的 hook

aHandle bHandle 不会每次 render 重新生成

使用 react-query 或者类似的东西。

啊?

ahooks/useRequest 感觉可以满足你的需求

这个场景不需要 useEffect, 如果你需要根据某个值触发对应操作, 应该是在获取到 type 的位置调用对应函数, 即便有多个位置.

react unforget

meta575 说的对,你没要实现这个功能的话,可以试试 ahooks 提供的 useMemoizedFn

我是这样这样处理的:每次渲染重新生成函数,然后用 ref 引用这个函数,这样的话函数就能获取到最新值,也不用把函数加到 useEffect 的依赖里,不知道有没有优雅的写法。aHandleRef.current = () => {};useEffect(() => { if (type === "a") { aHandleRef.current(); }}, [type])

如果你的 type 是外部传进来的,其实你完全可以直接写不需要 useEffect

但是用了 useRef 的话,其实就跟 ahook 里的 useMemoizedFn 类似处理了,这样子就又回到我说的问题了: 难道只能用 useRef 或者基于 useRef+useCallback 封装的一些 hook ,把每个 aHandle 或者 bHandle 给套上吗害,有没有更优雅的方式

type 是存在 state 中的一个状态,类似小程序底部 tabbar 选中高亮的一中选中状态

react.dev/reference/react/useEffect#removing-unnecessary-function-dependencies把 aHandle 或 bHandle 函数定义放在组件外面或者 useEffect(() => { function aHandle() {} })

之前在 reactjs/rfcs 看到的 useEvent 就是用来解决这种问题的function useEvent(handler) { const handlerRef = useRef(null); useLayoutEffect(() => { handlerRef.current = handler; }); return useCallback((...args) => { const fn = handlerRef.current; return fn(...args); }, []);}或者直接const xxx = useRef();xxx = () => {};...xxx.current();

useEvent 其实就跟 ahook 的 useMemoizedFn 一样,但都是用在函数上的,如果涉及的函数很多,每一个都要这样套上,感觉又不太好

// eslint-disable-next-line react-hooks/exhaustive-deps 😏

React 是这样的,不愿意这样做那就只能状态和函数一起外移了

#10 我之前用 react 也遇到同样的问题,最后使用的 8 楼的 ref 方式(但是这样写有种不得劲的感觉,总有种在 react/vue 里面直接操作 dom 一样的违和感)

正解,useEffect 里面拿到的函数 aHandle ,bHandle 都是最新的,不需要放到依赖项里面。

具体逻辑不清楚,如果没看过建议先看看 zh-hans.react.dev/learn/you-might-not-need-an-effect ,有时候并不需要 useEffect

很简单,你不要在 aHandle 和 bHandle 里用 prop 和 state 就可以了,你把他们当成 sateless 的方法,需要这类状态直接通过 effect 里传进去,就不会有依赖问题了

用 ref 是正解,不过如果 aHandle ,bHandle 没有其他依赖项也可以正常引用,就是一直保持组件渲染第一次的地址

这个应该不需要 hook 吧

在切换 type 的那个 onChange/onSelect/onClick 做,onChange={(type: string) => { if (type === 'aaa') { aHandle() } else if (type === 'bbb') { bHandle() } // ... }}

meta575 super996 是正解。useEffect 是处理函数的副作用,而不是去监听值(这两者在某些场景下容易混淆)

函数式编程理念是函数无副作用,所以你的调用函数最好不要引用外部状态,使用传参方式.这样使用 callback 就没什么问题了.

不知道你的 aHandle 和 bHandle 的具体逻辑,不过仅目前的这段代码的逻辑来说,按照我的思路,我会把 aHandle 和 bHandle 直接写成 useEffect 。useEffect(() => {if (type !== aaa) return;// aHandle 的函数体,直接处理,而不是调函数}, [type]);useEffect(() => {if (type !== bbb) return;// bHandle 的函数体,如果要异步处理,就立即执行包一下let cancel = false;(async () => {await xxx;if (cancel) {return;}//...})();return () => {cancel = true;};}, [type]);

按你这个需求,好像直接把那一行的 lint 禁用掉好了……

Effect Events are not reactive and must always be omitted from dependencies of your Effect. This is what lets you put non-reactive code (where you can read the latest value of some props and state) inside of them. 参考文档: react.dev/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events

jsxconst App = () => { const [count, setCount] = useState(1); const handle = () => { console.log('做些事'); } useEffect(() => { handle(); }, [count]) return <button onClick={() => setCount(count + 1)}>Add One</div>}感觉大家没有回答到点上啊。首先,React 中的函数式组件,每“运行”一次,是一个时刻的结果。比如上面的 App 函数,完成一次加载以后。实际上就是运行了一次 App 函数,对于第一次视为 t1 ,t1 流程是:1. 初始化了一个 count 的 state2. 通过变量handle定义了一个函数3. 执行了一次 useEffect4. 返回了一个<button />这里面最关键的点是步骤 3 执行 useEffect 。在第一次运行的时候,这个匿名方法:js// t1 时刻的匿名函数() => { handle(); // t1 时刻的 handle 变量}被 React 存放到了内部,并且它捕获了 t1 时刻的变量handle,并且,通过[count]定义了依赖项。并且,t1 的匿名函数会执行一次。当你点击按钮的时候,由于调用了 setCount ,在上述场景下,会导致 App 重新执行一次,我们把第二次执行的流程视为 t2 。它的过程是:1. 由于第 2 次了,useState 拿到的值不再是初始值,而是上一次 set 的值,在上面的例子是 2 ;2. 通过变量handle定义了一个函数。这里的 handle ,跟 t1 阶段的 handle 完全是两个变量,它们仅仅是名字一样,代码块一样而已。3. 执行一次 useEffect 。此时,生成了一个 t2 时刻的匿名函数:js// t2 匿名() => { handle(); // 这里的 handle 也是 t2 时刻的 handle ,跟 t1 的 handle 没有任何关系}此时,t1 的 count = 1 与 t2 的 count = 2 不一样了,所以,useEffect 中的匿名函数( t2 版本)会执行一次,handle 自然就是 t2 版本的 handle 。另外,上述场景中的 handle 能用 count 这个 state 吗?当然可以,因为 t2 时刻的 handle 捕获的是 t2 时刻的 count 。

另外,useEffect 一定要分两步看:它“吃”了一个匿名函数,但是不意味着它立刻会调用;调用的时机取决 React 所的定义的依赖判定。

“那我就需要把 aHandle 和 bHandle 放入依赖项中,否则 eslint 或者 sonarlint 会报警告” —— eslint 具体的警告是什么?感觉你这种场景是很常见的。aHandle 和 bHandle 有什么特殊之处吗?

你说的这种也是正确的处理方式,通过封装无状态的方法,在多个 type 变化的地方调用也是解决方案。

useEffect 不要这么写,如果里面代码量上来了,refs [] 会监听超级多变量和函数。这里面函数又套了 useCallback ,维护会爆炸。

把 lint 禁掉就行了,hooks lint 不太智能,挺烦的

非常的典型的 useEvent 场景,直接用 useMemoizedFn 就行了。否则就放在 onClick 里面做

正如前排所说你可能不需要使用 useEffect 。useEffect 不应该用于监听某个东西的变化,应该用于组件时卸载清理的工作,例如取消订阅。官方文档专门对这种情况进行了说明 react.dev/learn/you-might-not-need-an-effect

非要这么写,那就按你第一份代码,把那一行的 lint 禁用

最新版本是 useEffectEvent ,以前版本是用 useRef 存它,然后再补个 useEffect 更新它

以前都得这么干 TAT

主要是 aHandle 是不是纯的,它是纯的话,你甚至可以拿到组件外边去。如果不纯的话,它依赖什么数据?依赖变化的时候,它是需要更新的。

eslint 或者 sonarlint 警告就是说你没把 aHandle 和 bHandle 加到依赖项里,他们的特殊之处就是里面包含了一些异步的复杂逻辑,需要每次调用时都取到外部最新的值

统一回复下:eslint 和 sonarlint 不是我说禁用就能禁用的,公司私有化部署了代码扫描平台,会用这些扫描代码,作为员工绩效的一部分,我实属无奈呀,非常难受,如果是自己的项目,那我就直接不管了,害

所以说,useEffect 的用法,我其实理解错了,不应该这么用对么

再统一回复下:未出世的 useEvent ,useRef ,useMemoizedFn 之类的,本质上都要对那些 aHandle ,bHandle 套上,但是1. 我需要给每个 handle 函数都给套上2. 如果有些函数本身已经套了某种 hook 的,我难道还要再套一层?比如 useDebounceFn ,useThrottleFn ,已经套过一层了,难道要再套一层,我感觉不太优雅了

是不是把 vue 里用 watch 的习惯带来了?type 在哪里变的就在哪里挂处理函数,useeffect 不是干这个的

闭包问题,在 react 里面没有太好的解决方案,op 说的是否还要再往上套的问题是肯定的,如果想要保持引用稳定就是要付出这个代价,这也就是 react 目前的问题。vue 或者 solidjs 这些没有这个问题是因为他们所有的数据外面都有一层壳,vue 的 val.value ,solidjs 的 value(),都是帮你在外面加了一层,当然渲染机制也不同,所以加壳就加吧。

#45 别洗了,缺点就是缺点

这种感觉就是 eslint 的锅, 这种场景还是很多的,因为 useEffect 不能直接用 async ,所以函数定义在 useEffect 里又不能共用,抽出来又出现你这种依赖报错问题,但是实际上不加函数依赖是正常的,用这种依赖问题又一大堆冗余代码。如果全局 eslint 改不掉建议用 eslint-disable-next-line 这种禁用掉。

vue boy 这就破防了?vue 里面不是一样不推荐滥用 watch 吗?到处 watch 的屎山我是见过的,哪里改变状态在哪里处理 state 不很正常吗?react 手动依赖收集是比较麻烦,但这跟不该用 useeffect 监听状态修改有一毛钱关系吗?

#49 哈哈,说点 react 的缺点就被喷是 vue boy ,祝你生活愉快😁,告辞