React 副作用的含义
“副作用”是一个模糊的概念,具有一词多义的特点。
就像 Hook 也是一个模糊的概念。模糊的概念就是既有 A 含义,也有 B 含义。
从 A 角度解释可以,从 B 角度解释也可以。
先谈谈 Hook
Hook 在逆向领域是劫持代码逻辑的意思。
JS Web 逆向的 Hook
如 cxxjackie 大佬的杰作 ajaxHooker:https://bbs.tampermonkey.net.cn/thread-3284-1-1.html
这种脚本语言的 Hook,一般被称之为 monkey patch,维基百科链接:https://en.wikipedia.org/wiki/Monkey_patch
X86 逆向的 Hook(可直接跳过)
这里不展开探讨,不是重点。
在 X86 逆向中,插入 Hook,就是保存原来的汇编指令,然后 jmp 到自定义 Hook 逻辑的地址。
随后执行原来的指令,再执行自己的 Hook 逻辑,最后 jmp 回去。
这种执行流程,如果画图的话,如同一个钩子一样,这便是逆向领域的 Hook。
正向开发的 Hook
正常开发中也有 Hook 的概念,如回调函数。因为其执行流程的图像也像个钩子,所以也叫钩子回调。
回调函数也被称为“好莱坞”函数,意思是在好莱坞面试后,不要给面试官打电话,面试官自然会给你打电话。
这些乱七八糟的叫法非常多,主要还是看上下文。
百度百科:好莱坞原则:https://baike.baidu.com/item/%E5%A5%BD%E8%8E%B1%E5%9D%9E%E5%8E%9F%E5%88%99/16019700
说回 React
在继续探讨前,我们先来说说纯函数:
纯函数是函数式编程中的核心概念:它在相同输入的情况下,总是返回相同的输出,并且不依赖或修改外部全局变量。也就是说,纯函数只依赖参数,且不产生副作用。
现实生活中的副作用
我们生病吃药,本意是为了缓解病情,结果药物带来了恶心反胃或头晕,这便是副作用。
编程也是同理,我调用一个函数,结果它修改了全局变量,这不符合预期,也不符合最佳实践。
React 的副作用
React 要求函数式组件像“纯函数”一样,不要修改 props,调用时不要产生副作用。
对于 React 来说,“副作用”就是不良影响。
例如,React 调用你的组件,结果产生了内存泄漏,内存占用越来越大。
这可能是因为忘记清理资源导致的,如注册了事件总线,却忘记清理。
这种不良影响便是副作用。
以下是常见的副作用:
- 未清理的 setInterval/setTimeout。
- 未取消的网络请求(如 fetch 或 axios 请求)。
- 修改 DOM 元素(违反 React 的声明式原则)。
Hook 的作用
我们需要通过 React 的 Hook 来清除副作用。
如使用 useEffect,可以让 React 知道在卸载组件时要调用哪些清理函数。
Hook 在 React 的语境下,就是将代码逻辑钩入 React 的执行体系内。
通过 useEffect,我们消除了副作用,让函数变成了 React 期待的不产生副作用的“纯函数”。
为什么我的“纯函数”打了引号?因为在 React 中,许多函数式组件其实不是严格意义上的“纯函数”:固定输入 props 产生固定的 JSX。
组件仍会受用户输入、组件状态等隐式输入的影响,就如同隐式读取了全局变量一样。
React 所指的“纯函数”更像是 React 下函数式组件的最佳实践,而不是函数式编程的“纯函数”。
只要函数式组件调用后不会导致内存泄漏、没有修改 props 等 React 不期待的问题,就算是 React 语境下的“纯函数”了。
也就是说:即使是非纯函数(如依赖用户输入),只要通过 Hook 正确管理副作用,React 仍认为它是“符合最佳实践的”。
Jetpack Compose 下的副作用
这篇帖子的文末,有相关学习资料推荐:https://bbs.tampermonkey.net.cn/thread-8727-1-1.html
“脱离框架的控制 就是 副作用”,在 Jetpack Compose 下体现得更加明显。
Jetpack Compose 参考了 React 的许多设计。
其 Composable 就是参考 React 的函数式组件。
Jetpack Compose 有一个 Hook 叫 LaunchEffect。
其功能是用来启动协程,在组件卸载时自动关闭协程。
这避免了组件卸载后,协程仍在运行,影响性能,甚至可能导致内存泄漏。
如果我们脱离 LaunchEffect,直接启动协程,会导致脱离 Jetpack Compose 的控制。
这会产生所谓的“副作用”,也就是不被 Jetpack Compose 所期待的作用。
例如,组件卸载了,协程还在发送网络请求或进行耗时操作,可能导致内存泄漏。
这些是 Jetpack Compose 所不期待的,因此被称之为“副作用”。
通过 Hook 将代码逻辑钩入 Jetpack Compose 后,就消除了“副作用”,符合最佳实践。
尾声
感谢你的阅读!