关于 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
1
NessajCN 237 天前
aHandle bHandle 不会每次 render 重新生成
|
2
weixind 237 天前
使用 react-query 或者类似的东西。
|
4
sadyx 237 天前
ahooks/useRequest 感觉可以满足你的需求
|
5
Nyeshuai 237 天前 2
这个场景不需要 useEffect, 如果你需要根据某个值触发对应操作, 应该是在获取到 type 的位置调用对应函数, 即便有多个位置.
|
6
10bkill1p 237 天前
react unforget
|
7
XFeng34 237 天前 1
meta575 说的对,你没要实现这个功能的话,可以试试 ahooks 提供的 useMemoizedFn
|
8
lisianthus 237 天前
我是这样这样处理的:每次渲染重新生成函数,然后用 ref 引用这个函数,这样的话函数就能获取到最新值,也不用把函数加到 useEffect 的依赖里,不知道有没有优雅的写法。
aHandleRef.current = () => {}; useEffect(() => { if (type === "a") { aHandleRef.current(); } }, [type]) |
9
sjhhjx0122 237 天前
如果你的 type 是外部传进来的,其实你完全可以直接写不需要 useEffect
|
10
Asuler OP 但是用了 useRef 的话,其实就跟 ahook 里的 useMemoizedFn 类似处理了,这样子就又回到我说的问题了: 难道只能用 useRef 或者基于 useRef+useCallback 封装的一些 hook ,把每个 aHandle 或者 bHandle 给套上吗
害,有没有更优雅的方式 |
11
Asuler OP type 是存在 state 中的一个状态,类似小程序底部 tabbar 选中高亮的一中选中状态
|
12
7anshuai 237 天前
https://react.dev/reference/react/useEffect#removing-unnecessary-function-dependencies
把 aHandle 或 bHandle 函数定义放在组件外面或者 useEffect(() => { function aHandle() {} }) |
13
sweetcola 237 天前
之前在 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(); ``` |
14
Asuler OP @sweetcola useEvent 其实就跟 ahook 的 useMemoizedFn 一样,但都是用在函数上的,如果涉及的函数很多,每一个都要这样套上,感觉又不太好
|
15
leroy20317 237 天前 5
// eslint-disable-next-line react-hooks/exhaustive-deps 😏
|
17
HTML001 237 天前 1
@Asuler #10 我之前用 react 也遇到同样的问题,最后使用的 8 楼的 ref 方式(但是这样写有种不得劲的感觉,总有种在 react/vue 里面直接操作 dom 一样的违和感)
|
18
zkkz 237 天前
@leroy20317 正解,useEffect 里面拿到的函数 aHandle ,bHandle 都是最新的,不需要放到依赖项里面。
|
19
DesnLee 237 天前
具体逻辑不清楚,如果没看过建议先看看 https://zh-hans.react.dev/learn/you-might-not-need-an-effect ,有时候并不需要 useEffect
|
20
jjwjiang 237 天前
很简单,你不要在 aHandle 和 bHandle 里用 prop 和 state 就可以了,你把他们当成 sateless 的方法,需要这类状态直接通过 effect 里传进去,就不会有依赖问题了
|
21
realJamespond 237 天前
用 ref 是正解,不过如果 aHandle ,bHandle 没有其他依赖项也可以正常引用,就是一直保持组件渲染第一次的地址
|
22
Radix10 237 天前
这个应该不需要 hook 吧
|
23
super996 237 天前 1
在切换 type 的那个 onChange/onSelect/onClick 做,
onChange={(type: string) => { if (type === 'aaa') { aHandle() } else if (type === 'bbb') { bHandle() } // ... }} |
24
Marthemis 237 天前
meta575 super996 是正解。useEffect 是处理函数的副作用,而不是去监听值(这两者在某些场景下容易混淆)
|
25
wang4012055 237 天前
函数式编程理念是函数无副作用,所以你的调用函数最好不要引用外部状态,使用传参方式.这样使用 callback 就没什么问题了.
|
26
jinliming2 237 天前 via iPhone
不知道你的 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]); |
27
IvanLi127 237 天前
按你这个需求,好像直接把那一行的 lint 禁用掉好了……
|
28
Terry166 237 天前
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.
参考文档: https://react.dev/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events |
29
w4ngzhen 237 天前
```jsx
const 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 的 state 2. 通过变量`handle`定义了一个函数 3. 执行了一次 useEffect 4. 返回了一个`<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 。 |
30
w4ngzhen 237 天前
另外,useEffect 一定要分两步看:它“吃”了一个匿名函数,但是不意味着它立刻会调用;调用的时机取决 React 所的定义的依赖判定。
|
31
w4ngzhen 237 天前
“那我就需要把 aHandle 和 bHandle 放入依赖项中,否则 eslint 或者 sonarlint 会报警告” —— eslint 具体的警告是什么?感觉你这种场景是很常见的。aHandle 和 bHandle 有什么特殊之处吗?
|
33
wpzz 237 天前
useEffect 不要这么写,如果里面代码量上来了,refs [] 会监听超级多变量和函数。
这里面函数又套了 useCallback ,维护会爆炸。 |
34
Makabaka01 237 天前
把 lint 禁掉就行了,hooks lint 不太智能,挺烦的
|
35
neotheone2333 237 天前
非常的典型的 useEvent 场景,直接用 useMemoizedFn 就行了。否则就放在 onClick 里面做
|
36
leoskey 237 天前
正如前排所说你可能不需要使用 useEffect 。useEffect 不应该用于监听某个东西的变化,应该用于组件时卸载清理的工作,例如取消订阅。
官方文档专门对这种情况进行了说明 https://react.dev/learn/you-might-not-need-an-effect |
37
leoskey 237 天前
非要这么写,那就按你第一份代码,把那一行的 lint 禁用
|
38
otakustay 237 天前
最新版本是 useEffectEvent ,以前版本是用 useRef 存它,然后再补个 useEffect 更新它
|
39
connection 236 天前
@otakustay 以前都得这么干 TAT
|
40
iOCZS 236 天前
主要是 aHandle 是不是纯的,它是纯的话,你甚至可以拿到组件外边去。如果不纯的话,它依赖什么数据?依赖变化的时候,它是需要更新的。
|
41
Asuler OP @w4ngzhen eslint 或者 sonarlint 警告就是说你没把 aHandle 和 bHandle 加到依赖项里,他们的特殊之处就是里面包含了一些异步的复杂逻辑,需要每次调用时都取到外部最新的值
|
42
Asuler OP 统一回复下:eslint 和 sonarlint 不是我说禁用就能禁用的,公司私有化部署了代码扫描平台,会用这些扫描代码,作为员工绩效的一部分,我实属无奈呀,非常难受,如果是自己的项目,那我就直接不管了,害
|
43
Asuler OP 所以说,useEffect 的用法,我其实理解错了,不应该这么用对么
|
44
Asuler OP 再统一回复下:未出世的 useEvent ,useRef ,useMemoizedFn 之类的,本质上都要对那些 aHandle ,bHandle 套上,但是
1. 我需要给每个 handle 函数都给套上 2. 如果有些函数本身已经套了某种 hook 的,我难道还要再套一层?比如 useDebounceFn ,useThrottleFn ,已经套过一层了,难道要再套一层,我感觉不太优雅了 |
45
rocmax 236 天前 via Android
是不是把 vue 里用 watch 的习惯带来了?
type 在哪里变的就在哪里挂处理函数,useeffect 不是干这个的 |
46
lee88688 236 天前
闭包问题,在 react 里面没有太好的解决方案,op 说的是否还要再往上套的问题是肯定的,如果想要保持引用稳定就是要付出这个代价,这也就是 react 目前的问题。
vue 或者 solidjs 这些没有这个问题是因为他们所有的数据外面都有一层壳,vue 的 val.value ,solidjs 的 value(),都是帮你在外面加了一层,当然渲染机制也不同,所以加壳就加吧。 |
48
dudubaba 236 天前
这种感觉就是 eslint 的锅, 这种场景还是很多的,因为 useEffect 不能直接用 async ,所以函数定义在 useEffect 里又不能共用,抽出来又出现你这种依赖报错问题,但是实际上不加函数依赖是正常的,用这种依赖问题又一大堆冗余代码。如果全局 eslint 改不掉建议用 eslint-disable-next-line 这种禁用掉。
|
49
rocmax 236 天前 via Android
@zbowen66 vue boy 这就破防了?
vue 里面不是一样不推荐滥用 watch 吗?到处 watch 的屎山我是见过的,哪里改变状态在哪里处理 state 不很正常吗? react 手动依赖收集是比较麻烦,但这跟不该用 useeffect 监听状态修改有一毛钱关系吗? |
51
jjwjiang 234 天前
@w4ngzhen 确实是这样,但是如果在 function 里有闭包那就是另一回事了。所以我觉得 eslint 这规则大部分时候是没用的,这规则最终导致的结果就是需要 memo 一大堆东西
|
52
HTML001 232 天前
四天过去了,OP 最终选择什么方案?
|