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
)
这里是伪代码,理解其中的意思即可。
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 语法的学习
相信你们都能掌握吧。有误,请多多指教~~~
转载自:https://juejin.cn/post/7257873848518934584