前端欧阳

涨见识了!脱离vue项目竟然也可以使用响应式API

大家好,我是欧阳,又跟大家见面啦!

欧阳建了一个高质量vue源码交流群,扫描文末的二维码加欧阳微信,拉你进群。

前言

vue3的响应式API大家应该都特别熟悉,比如 ref 、 watch 、 watchEffect 等。平时大家都是在 vue-cli 或者 vite 创建的vue项目里面使用的这些响应式API,今天欧阳给大家带来一些不一样的。 脱离vue项目,在 node.js 项目中使用vue的响应式API 。

直接上代码

话不多说,直接上代码。这个是我在本地新建的一个最简单的 node.js 项目,如下图:

从上图可以看到我们的 node.js 项目依赖只有一个: vue 。并且提供了一个名为dev的脚本命令,这个脚本命令实际是在node环境内执行 index.js 文件。 我们来看 index.js 文件,代码如下:
const { ref, watch, watchEffect } = require("vue");

const count = ref(0);

// 模拟count变量的值修改
setInterval(() => {
  count.value++;
}, 1000);

watch(count, (newVal) => {
  console.log("触发watch", newVal);
});

watchEffect(
  () => {
    console.log("触发watchEffect", count.value);
  },
  {
    flush: "sync",
  }
);
为了标明 index.js 文件是在 node.js 环境中运行的,所以这里我特地使用 require 去导入vue导出的 ref 、 watch 、 watchEffect 这三个响应式API。 并且我们还模拟了修改 count 响应式变量值的操作,使用 setInterval 每隔一秒让 count 的值 +1 。 在vue项目中一样使用 watch 和 watchEffect 去监听 count 变量的值。

在终端执行 yarn dev ,也就是执行 node index.js ,如下图:

从上图中可以看到在 node.js 中的执行结果和预期是一模一样的。

为什么可以这样写呢?

前面的那个例子是一个 node.js 项目,项目中我们并没有像vue项目那样去创建一个vue组件,然后在组件里面去使用响应式API。而是直接在一个普通的 node.js 文件中使用vue暴露出来的响应式API,并且 watch 和 watchEffect 在监听的值改变后同样触发了对应的watch回调,那么这个又是怎么做到的呢?

这得益于vue3优秀的模块化设计,他将核心功能拆分为多个独立的模块,如下图:

比如 reactivity 模块中就是响应式的核心代码、 compiler-core 模块就是编译相关的核心代码。

并且这些模块还被单独当作npm包进行发布,命名规则是 @vue+模块名 。比如 reactivity 模块对应的npm包就是 @vue/reactivity 。如下图:

得益于模块化的设计,响应式相关的API和vue组件并没有强关联的关系,所以我们可以在 node.js 应用中去直接使用响应式API。 这里使用到了三个响应式API,分别是: ref 、 watch 和 watchEffect 。在vue组件中的响应式的实现原理大家多多少少都有所听闻,其实在 node.js 项目中实现原理也是一样的,接下来我们讲讲是如何实现响应式的。 在我们这个demo中 count 是一个ref的响应式变量,当我们对 count 变量进行读操作时会触发 get 拦截。当我们对 count 变量进行写操作时会触发 set 拦截。 在我们这里使用 watch 和 watchEffect 的代码是下面这样的:
watch(count, (newVal) => {
  console.log("触发watch", newVal);
});

watchEffect(
  () => {
    console.log("触发watchEffect", count.value);
  },
  {
    flush: "sync",
  }
);
当代码首次执行到 watch 和 watchEffect 时都会对 count 变量进行读操作,并且 watch 和 watchEffect 都传入了一个回调函数。 由于对 count 变量进行读操作了,所以就会触发 get 拦截。在 get 拦截中会将当前 watch的回调函数 作为依赖收集到 count 变量中。收集的方式也很简单,因为 count 变量是一个对象,所以使用对象的 dep 属性进行依赖收集。因为 dep 属性是一个集合,所以可以收集多个依赖。 在我们这里 watch 和 watchEffect 都触发了 count 变量的get拦截,所以 watch 和 watchEffect 的回调函数都被 count 变量进行了依赖收集。 当修改 count 变量的值时会触发 set 拦截,在 set 拦截中做的事情也很简单。将 count 变量收集到的依赖全部取出来,然后执行一遍。这里收集的依赖是 watch 和 watchEffect 的回调函数,所以当 count 变量的值改变时会导致 watch 和 watchEffect 的回调函数重新执行。

这个是整个流程图:

从流程图可以看到响应式的实现原来完全不依赖vue组件,所以我们可以在 node.js 项目中使用vue的响应式API,这也是vue的设计奇妙之处。

总结

这篇文章讲了我们可以脱离vue项目,直接在 node.js 项目中使用vue的响应式API。接着讲了响应式的实现原理其实就是依靠 get 拦截进行依赖收集, set 拦截进行依赖触发。 搞清楚响应式原理后,我们发现响应式完全不依赖vue组件,所以我们可以在 node.js 项目中使用vue的响应式API,这也是vue的设计奇妙之处。 长按图片加欧阳微信 ,拉你进欧阳的高质量vue源码交流群,群里可谓是藏龙卧虎。

关注公众号,发送消息: 666 , 领取欧阳研究vue源码过程中收集的源码资料。 欧阳写文章有时也会参考这些资料,同时让你的朋友圈多一位对vue有深入理解的人。 因为微信公众号修改规则,如果不标星或点在看,你可能会收不到我公众号文章的推送,请大家将本 公众号星标 ,看完文章后记得 点下赞 或者 在看 ,谢谢各位!