请教一个 react hook 的问题

2024-03-29 10:34:07 +08:00
 Asuler

关于 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

3819 次点击
所在节点    React
53 条回复
realJamespond
2024-03-29 13:49:13 +08:00
用 ref 是正解,不过如果 aHandle ,bHandle 没有其他依赖项也可以正常引用,就是一直保持组件渲染第一次的地址
Radix10
2024-03-29 14:32:16 +08:00
这个应该不需要 hook 吧
super996
2024-03-29 14:46:41 +08:00
在切换 type 的那个 onChange/onSelect/onClick 做,
onChange={(type: string) => {
if (type === 'aaa') {
aHandle()
} else if (type === 'bbb') {
bHandle()
}
// ...
}}
Marthemis
2024-03-29 15:04:35 +08:00
meta575 super996 是正解。useEffect 是处理函数的副作用,而不是去监听值(这两者在某些场景下容易混淆)
wang4012055
2024-03-29 15:11:16 +08:00
函数式编程理念是函数无副作用,所以你的调用函数最好不要引用外部状态,使用传参方式.这样使用 callback 就没什么问题了.
jinliming2
2024-03-29 15:33:36 +08:00
不知道你的 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]);
IvanLi127
2024-03-29 16:08:29 +08:00
按你这个需求,好像直接把那一行的 lint 禁用掉好了……
Terry166
2024-03-29 16:17:25 +08:00
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
w4ngzhen
2024-03-29 16:42:37 +08:00
```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 。
w4ngzhen
2024-03-29 16:45:24 +08:00
另外,useEffect 一定要分两步看:它“吃”了一个匿名函数,但是不意味着它立刻会调用;调用的时机取决 React 所的定义的依赖判定。
w4ngzhen
2024-03-29 16:46:46 +08:00
“那我就需要把 aHandle 和 bHandle 放入依赖项中,否则 eslint 或者 sonarlint 会报警告” —— eslint 具体的警告是什么?感觉你这种场景是很常见的。aHandle 和 bHandle 有什么特殊之处吗?
w4ngzhen
2024-03-29 16:48:33 +08:00
@meta575 你说的这种也是正确的处理方式,通过封装无状态的方法,在多个 type 变化的地方调用也是解决方案。
wpzz
2024-03-29 16:50:31 +08:00
useEffect 不要这么写,如果里面代码量上来了,refs [] 会监听超级多变量和函数。

这里面函数又套了 useCallback ,维护会爆炸。
Makabaka01
2024-03-29 17:17:33 +08:00
把 lint 禁掉就行了,hooks lint 不太智能,挺烦的
neotheone2333
2024-03-29 17:32:21 +08:00
非常的典型的 useEvent 场景,直接用 useMemoizedFn 就行了。否则就放在 onClick 里面做
leoskey
2024-03-29 17:46:51 +08:00
正如前排所说你可能不需要使用 useEffect 。useEffect 不应该用于监听某个东西的变化,应该用于组件时卸载清理的工作,例如取消订阅。

官方文档专门对这种情况进行了说明 https://react.dev/learn/you-might-not-need-an-effect
leoskey
2024-03-29 17:48:35 +08:00
非要这么写,那就按你第一份代码,把那一行的 lint 禁用
otakustay
2024-03-29 18:03:37 +08:00
最新版本是 useEffectEvent ,以前版本是用 useRef 存它,然后再补个 useEffect 更新它
connection
2024-03-29 20:18:10 +08:00
@otakustay 以前都得这么干 TAT
iOCZS
2024-03-29 20:30:47 +08:00
主要是 aHandle 是不是纯的,它是纯的话,你甚至可以拿到组件外边去。如果不纯的话,它依赖什么数据?依赖变化的时候,它是需要更新的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1028021

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX