
响应式一直都是 Vue 的特色功能之一。
Vue 中用过三种响应式解决方案,分别是 defineProperty、Proxy 和 value setter。
Object.defineProperty() (opens new window) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
let getDouble = n => n * 2;
let obj = {};
let count = 1;
let double = getDouble(count);
Object.defineProperty(obj, 'count', {
get: () => count,
set: value => {
count = value;
double = getDouble(count);
}
});
console.log(double); // 2
obj.count = 2;
console.log(count); // 2
console.log(double); // 4 double 会跟着变化
但 defineProperty API 作为 Vue 2 实现响应式的原理,它的语法中也有一些缺陷。比如在下面代码中,删除 obj.count 属性,set 函数就不会执行,double 还是之前的数值。这也是为什么在 Vue 2 中,需要 $delete 一个专门的函数去删除数据。
delete obj.count;
console.log(double); // 4
Vue 3 的响应式机制是基于 Proxy (opens new window) 实现的,其重要意义在于它解决了 Vue 2 响应式的缺陷。
let proxy = new Proxy(obj, {
get: function (target, prop) {
return target[prop];
},
set: function (target, prop, value) {
target[prop] = value;
if (prop === 'count') {
double = getDouble(value);
}
},
deleteProperty: function (target, prop) {
delete target[prop];
if (prop === 'count') {
double = NaN;
}
}
});
console.log(obj.count, double); // 1 2
proxy.count = 2;
console.log(obj.count, double); // 2 4
delete proxy.count;
console.log(obj.count, double); // undefined NaN
Proxy 是针对对象来监听,而不是针对某个具体属性,所以不仅可以代理那些定义时不存在的属性,还可以代理更丰富的数据结构,比如 Map、Set 等,并且也能通过 deleteProperty 实现对删除操作的代理。
Vue 3 的 reactive 函数可以把一个对象变成响应式数据,而 reactive 就是基于 Proxy 实现的。还可以通过 watchEffect,在 obj.count 修改之后,执行数据的打印。
import { reactive, computed, watchEffect } from 'vue';
let obj = reactive({
count: 1
});
let double = computed(() => obj.count * 2);
obj.count = 2;
watchEffect(() => {
console.log('data updated', obj.count, double.value);
});
在 Vue 3 中还有另一个响应式实现的逻辑,就是利用对象的 get 和 set 函数来进行监听,这种响应式的实现方式,只能拦截某一个属性的修改,这也是 Vue 3 中 ref 这个 API 的实现。
let getDouble = n => n * 2;
let _value = 1;
double = getDouble(_value);
let count = {
get value() {
return _value;
},
set value(value) {
_value = value;
double = getDouble(_value);
}
};
console.log(count.value, double); // 1 2
count.value = 2;
console.log(count.value, double); // 2 4

让 TodoList 和本地存储同步。
import { ref, computed, watchEffect } from 'vue';
let todolist = ref(JSON.parse(localStorage.getItem('todolist') || '[]'));
watchEffect(() => {
localStorage.setItem('todolist', JSON.stringify(todolist.value));
});
抽离 useStorage 函数,在响应式的基础之上,把任意数据响应式的变化同步到本地存储。
function useStorage (name, value = []) {
let data = ref(JSON.parse(localStorage.getItem(name) || value));
watchEffect(() => {
localStorage.setItem(name, JSON.stringify(data.value));
});
return data;
}
社区也有非常优秀的 Vueuse 工具库,包含了大量类似 useStorage 的工具函数库。
可以把日常开发中用到的数据,无论是浏览器的本地存储,还是网络数据,都封装成响应式数据,统一使用响应式数据开发的模式。这样,开发项目的时候,只需要修改对应的数据就可以了。

VueUse (opens new window) 趁着这一波 Vue 3 的更新,跟上了响应式 API 的潮流。VueUse 的官方的介绍说这是一个 Composition API 的工具集合,适用于 Vue 2.x 或者 Vue 3.x,用起来和 React Hooks 很像。
# 安装
npm install @vueuse/core
VueUse 中包含了很多常用的工具函数,可以把网络状态、异步请求的数据、动画和事件等功能,都看成是响应式的数据去管理。
<template>
<h1 @click="toggle">toggle</h1>
</template>
<script setup>
import { useFullscreen } from '@vueuse/core';
const { isFullscreen, enter, exit, toggle } = useFullscreen();
</script>