React&Vue 系列:变量改动的监听
❝
背景:作为使用三年 react.js 的搬运工,目前正在积极学习 vue.js 语法,而在学习的过程中,总是喜欢和 react.js 进行对比,这里也不是说谁好谁坏,而是怀有「他有我也有,他没我还有」的思想去学习,去总结。
React18 Vue3 ❞
在上一章,介绍了React&Vue 系列: 变量的定义。那么本章就来介绍,定义变量之后,当变量被修改了,如何进行监听。
「为什么需要进行监听呢」?因为当变量改变之后,会存在相应的副作用操作(比如说网络请求,修改 DOM 状态等等),那么这时候就需要进行监听。
好了,直接进入正题。
❝
老规矩,对 React 比较熟悉,React 有优先权。
❞
React 监听变量改动
在 React 中,并没有单独的提供方法用来监听变量的改动。而且还必须清楚一点,当变量发生改变时,组件就会重新渲染(re-render
)。到了这里,你也许还需要理解「React 批量更新」。
❝
Effect 在 React 中是专有定义——由渲染引起的副作用。为了指代更广泛的编程概念,也可以将其称为“副作用(side effect)”。
❞
当组件进行重新渲染之后,就会执行一些函数(类似生命周期),在这些函数中就能做一些副作用的逻辑操作。在 React 中提供了 useEffect
和 useMemo
等 hook。
import { useEffect, useMemo } from "react";
这里的 useMemo
也可以简单的看成是变量的副作用处理方式吧,当变量发生改变,会生成新的「衍生值」,进行渲染。
而最主要的处理副作用的函数就是 useEffect
, 监听变量发生改变,处理一系列的操作(比如说网络请求,DOM 操作等等)。
// 基本使用方式
useEffect(setup, dependencies?)
-
setup 副作用 -
dependencies 依赖收集,当重新渲染时,如果依赖发生了变化,就会触发副作用。
执行副作用
直接看代码吧
import { useState, useEffect } from "react";
const [pagination, setPagination] = useState(1);
/**
* 请求表格数据
* @param pagination: 页数
*/
function getTableDataSource(pagination) {
// fetch...
}
useEffect(() => {
getData(pagination);
}, [pagination]);
return (
// jsx 的 DOM
)
这里就想象成表格的分页场景吧,当点击表格分页,就会改变 pagination
变量,那么就会使当前组件重新渲染,而当前组件中的 useEffect 的依赖 pagination 发生了变动(内部通过 Object.is()
来进行比较), 就会执行副作用,重新请求表格数据。
这就是 React 监听变量改动,触发副作用的简单流程,还是比较简单的。
❝
当然,React 内部还提供了
useLayoutEffect
和useInsertionEffect
两个处理副作用的 hook,但是很少使用。该两个语法跟 useEffect 一致,但是它们还是有着各自的不同。「useEffect、useLayoutEffect、useInsertionEffect 的区别和选择」❞
取消副作用
组件的每次更新,都会触发副作用。那么如果更新过快,那么就会存在一种现象,就是上次副作用还没有执行完成,下一次的副作用执行又开始了,就又可能会形成一种「竞态」。所以,为了避免这种现象,在执行下一次副作用,就需要清空上一次的副作用。
在上面已经了解到, useEffect 的第一个参数为 setup
函数,该函数返回另外一个函数,就是用来清理副作用的。
useEffect(() => {
// 副作用函数体
return () => {
// cleanup 函数体
};
}, []);
「cleanup 的执行时机」:
-
当依赖项发生变化,使用旧的 state 和旧的 props 来执行 clearup 代码(着重体现一个:「旧」);然后再使用新的 state 和新的 props 来执行运行 setup 代码(着重体现一个:「新」) -
当组件从页面卸载了,cleanup 代码运行最后一次
案例演示:
import { useState, useEffect } from "react";
const [pagination, setPagination] = useState(1);
const controller = new AbortController();
const { signal } = controller;
async function getTableDataSource(pagination) {
const res = await fetch(xxx, {signal})
}
useEffect(() => {
getData(pagination);
return () => {
// 取消 fetch 请求
controller.abort()
}
}, [pagination]);
return (
// jsx 的 DOM
)
这里是伪代码,理解其中的意思即可。
TypeScript 使用
useEffect 在 ts 环境下使用,跟在 js 环境下使用,没有任何的区别。
Vue 监听变量改动
在 Vue 中监听变量改动就非常简单,并且容易理解。因为在 Vue 中就提供了两个函数(watch
,watchEffect
),是专门用于变量的监听,就没有 React 组件那一套重新渲染流程。是不是心情愉悦一点啦~~
❝
在没开始之前,先说说个人感受吧。虽然 Vue 提供了两个函数,是便于理解了;但是吧,其中的语法要点也确实太多了,给人一种学不尽的感觉。当然,仅仅只是基本使用的话,还是比较容易上手的。
❞
在 Vue3 中提供了两个函数:watch
和 watchEffect
,使用的时候导入即可。
import { watch, watchEffect } from "vue";
先从 watch
函数开说吧,watchEffect
函数是为了更加的方便使用 watch,当然这里不是指的语法糖哈。
watch
watch()
监听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
// 这里就不粘贴官网上的 ts 定义了,比较的繁琐
watch(source, callback, options?)
「第一个参数 source」: 监听的数据源,有四种类型:
-
ref 对象
-
reactive 对象(默认深度监听)
-
一个 getter 函数(就是为了处理不能监听特定的某一个值,比如对象中的一个属性)
-
一个数组,上面三种类型的组合(同时监听多个数据源)
❝
这里就类似于 React 中的 useEffect 添加依赖,只是没有这么多的类型
❞
「第二个参数 callback」: 副作用(一个回调函数) 当数据发生了变化,要执行的副作用函数代码。该回调函数也接受三个参数:
-
newValue 新值(其类型取决于 source 的类型,单个还是多个)
-
oldValue 旧值(其类型取决于 source 的类型,单个还是多个)
-
onCleanup 清除副作用函数,是一个函数,该函数接受一个函数作为参数,用来清除副作用。
❝
是不是很麻烦:
—| watch:第二个参数是一个函数 callback
————–| callback: 第三个参数是一个函数 onCleanup
—————————| onCleanup:第一个参数是一个函数 clear
❞
function clear() {
// 清除副作用逻辑
}
onCleanup(clear);
「第三个参数 options」: 配置项(可选参数)
-
immediate 初始时,是否立即执行 -
deep 是否深度监听(针对 reactive 对象,默认就是深度监听) -
flush 触发时机,有三个值可以选择: pre
|post
|sync
-
pre: 默认值,回调在渲染之前执行(就是拿取不到新的 dom) -
post: 回调再渲染之后执行(拿取新的 dom) -
sync: 就是变量改变之后,同步的立即执行(少使用,影响性能) -
onTrack / onTrigger:调试使用,可以忽略。
「返回值 stop」调用 watch 函数,也是存在一个返回值的,一个用于停止监听的函数。
const stop = watch(pagination, callback);
// 当页数大于 10 页时,就停止监听
if (pagination > 10) {
stop();
}
❝
到了这里,就会发现 watch 函数知识点真的比较多的。
❞
代码演示
import { watch, ref } from "vue";
const pagination = ref(1);
const controller = new AbortController();
const { signal } = controller;
// 数据请求
async function getTableDataSource(pagination) {
const res = await fetch(xxx, { signal });
}
function clear() {
// 取消 fetch 请求
controller.abort();
}
watch(
pagination,
(newValue, oldValue, onCleanup) => {
getTableDataSource(newValue);
// 清除副作用
onCleanup(clear);
},
{
immediate: true,
flush: "post",
}
);
伪代码,理解意思即可
watchEffect
watchEffect()
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
❝
为什么有了
watch()
函数,还需要watchEffect()
函数?
对于有多个依赖项的侦听器来说,使用 watchEffect()
可以消除手动维护依赖列表的负担。如果需要侦听一个嵌套数据结构中的几个属性, watchEffect()
可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性。❞
// 这里就不粘贴官网上的 ts 定义了,比较的繁琐
watchEffect(effect, options?)
「第一个参数 effect」: 副作用函数
当监听的依赖发生变化时,就会触发该副作用函数。该副作用函数接受一个参数:
-
onCleanup 清除副作用函数,是一个函数,该函数接受一个函数作为参数,用来清除副作用。
❝
—| watchEffect:参数是一个函数 effect
————–| effect: 参数是一个函数 onCleanup
—————————| onCleanup:第一个参数是一个函数 clear
❞
function clear() {
// 清除副作用逻辑
}
onCleanup(clear);
跟 watch 的使用方式是一样的。
「第二个参数 options」:配置项
❝
跟 watch 的配置项少了两个属性。
该函数本来就是立即执行,就是为了收集依赖,所以就不需要 immediate 属性
该函数时自动收集依赖的,特定值,就没有所谓的深度监听,所以就不需要 deep 属性。
❞
-
flush 触发时机,有三个值可以选择: pre
|post
|sync
-
pre: 默认值,回调在渲染之前执行(就是拿取不到新的 dom) -
post: 回调再渲染之后执行(拿取新的 dom) -
sync: 就是变量改变之后,同步的立即执行(少使用,影响性能) -
onTrack / onTrigger:调试使用,可以忽略。
这里还有两个语法糖的函数
import { watchPostEffect, watchSyncEffect } from "vue";
就是根据 watchEffect 的配置对象,组合成的函数。
「返回值 stop」调用 watch 函数,也是存在一个返回值的,一个用于停止监听的函数。
const stop = watchEffect(callback);
// 当页数大于 10 页时,就停止监听
if (pagination > 10) {
stop();
}
代码演示
import { watchEffect, ref } from "vue";
const pagination = ref(1);
const controller = new AbortController();
const { signal } = controller;
// 数据请求
async function getTableDataSource(pagination) {
const res = await fetch(xxx, { signal });
}
function clear() {
// 取消 fetch 请求
controller.abort();
}
watchEffect(() => {
// 自动收集了 pagination 依赖
getTableDataSource(pagination);
});
额外知识
React 批量更新
在 React 的函数组件中,是可以同时定义多个变量的。当一个变量发生改变的时候,组件就会重新渲染一次;当多个变量同时修改时,渲染又会存在不同的形式。
在个人前面的博客中,写了一篇关于 React 的批量更新,React 系列:useState 和 setState 的执行机制(对比,包含 React18),可以推荐看一下,这里就简单总结一下:
React 18 之前:
-
在「合成事件」和「钩子函数」中,如果同时修改多个变量,组件会重新渲染一次。 -
在「原生事件」和「异步函数」中,如果同时修改多个变量,那么组件就会重新渲染多次。
React 18:
-
无论是在「合成事件、钩子函数、原生事件、异步函数」中,如果同时修改多个变量,组件只会重新渲染一次。
useEffect、useLayoutEffect、useInsertionEffect 的区别和选择
「相同点」:
三者都是属于 React 提供的 hook,而 「useInsertionEffect」 是 React18 才出来的。它们的使用方式是都是一样的:
useEffect(setup, dependencies?)
两个参数:
-
setup
函数(也就是所谓的副作用),返回一个清理副作用函数(cleanup) -
dependencies
依赖,可选参数。
「不同点」:
函数的执行时机不同
-
useEffect 在渲染之后执行。 -
useLayoutEffect 在 DOM 更新完成之后,渲染之前执行。 -
useInserttionEffect 在 DOM 更新之前执行。
使用场景不同
-
useEffect 99% 的场景是都能使用的,也是最常使用的方法。 -
useLayoutEffect 使用场景,就是针对屏幕有闪烁的效果才使用(闪烁:就是副作用执行时间过长,视觉有所感受) -
useInsertionEffect 是针对 css-in-js 设计的,除非正在使用 css-in-js 样式,否则应该放弃使用。
为了深入理解,读取了梳理 useEffect 和 useLayoutEffect 的原理与区别,从源码层面理解一下它们之间的区别,整理了一张流程图。
❝
我是一个小小小菜鸡,我看不懂源码,哈哈哈,只是根据别人的思路,理顺一下知识点。
❞

总结
React 通过 useEffect
,useMemo
函数来实现对变量改动的监听。
Vue 通过 watch
,watchEffect
函数来实现对变量改动的监听。
在这里还是说一下自己的感受,Vue3 设计走向函数式编程,在 watch,watchEffect 两个函数里面充分体现出来,不停的函数嵌套,哈哈哈。watch
,watchEffect
的语法确实过于偏多,学习难度较大。
React 和 Vue 在这一块,各自的难点:
-
React 难点在于理解组件渲染 -
Vue 难点在于 api 语法的学习
相信你们都能掌握吧。有误,请多多指教~~~
原文始发于微信公众号(石膏银程序员):React&Vue 系列:变量改动的监听
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/187254.html