React&Vue 系列:变量改动的监听

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 中提供了 useEffectuseMemo 等 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 内部还提供了 useLayoutEffectuseInsertionEffect 两个处理副作用的 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 中就提供了两个函数(watchwatchEffect),是专门用于变量的监听,就没有 React 组件那一套重新渲染流程。是不是心情愉悦一点啦~~

在没开始之前,先说说个人感受吧。虽然 Vue 提供了两个函数,是便于理解了;但是吧,其中的语法要点也确实太多了,给人一种学不尽的感觉。当然,仅仅只是基本使用的话,还是比较容易上手的。

在 Vue3 中提供了两个函数:watchwatchEffect,使用的时候导入即可。

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?)

两个参数:

  1. setup 函数(也就是所谓的副作用),返回一个清理副作用函数(cleanup)
  2. dependencies 依赖,可选参数。

不同点

函数的执行时机不同

  • useEffect 在渲染之后执行。
  • useLayoutEffect 在 DOM 更新完成之后,渲染之前执行。
  • useInserttionEffect 在 DOM 更新之前执行。

使用场景不同

  • useEffect 99% 的场景是都能使用的,也是最常使用的方法。
  • useLayoutEffect 使用场景,就是针对屏幕有闪烁的效果才使用(闪烁:就是副作用执行时间过长,视觉有所感受)
  • useInsertionEffect 是针对 css-in-js 设计的,除非正在使用 css-in-js 样式,否则应该放弃使用。

为了深入理解,读取了梳理 useEffect 和 useLayoutEffect 的原理与区别,从源码层面理解一下它们之间的区别,整理了一张流程图。

我是一个小小小菜鸡,我看不懂源码,哈哈哈,只是根据别人的思路,理顺一下知识点。

React&Vue 系列:变量改动的监听

总结

React 通过 useEffectuseMemo 函数来实现对变量改动的监听。

Vue 通过 watchwatchEffect 函数来实现对变量改动的监听。

在这里还是说一下自己的感受,Vue3 设计走向函数式编程,在 watch,watchEffect 两个函数里面充分体现出来,不停的函数嵌套,哈哈哈。watchwatchEffect 的语法确实过于偏多,学习难度较大。

React 和 Vue 在这一块,各自的难点:

  • React 难点在于理解组件渲染
  • Vue 难点在于 api 语法的学习

相信你们都能掌握吧。有误,请多多指教~~~

原文链接:https://juejin.cn/post/7257873848518934584 作者:copyer_xyf

(0)
上一篇 2023年7月21日 上午10:00
下一篇 2023年7月22日 上午10:00

相关推荐

发表回复

登录后才能评论