您现在的位置是:首页 >其他 >Vue3-02-响应性 reactive网站首页其他
Vue3-02-响应性 reactive
1. setup
setup 是一个组件选项,组合式 API 就定义在 setup 中,包括 data、methods、computed 和 watch 等,都定义在 setup 中。
setup 实际上是一个生命周期钩子函数,执行时间点相当于 Vue2 中 beforeCreate 和 created 的结合。
2. ref
ref 将传入参数的值包装为一个带 value 属性的 ref 对象,使用 value 属性来访问之前的值,ref 对象的 value 属性也是响应式的。
ref 对象被传递给函数或是从一般对象上被解构时,不会丢失响应性。
在模板中使用 ref 时可以省略 .value。
案例场景:一个计数器组件 Counter
<template>
<div class="home">
<h1>{{ count }}</h1>
<h1>{{ double }}</h1>
<button type="button" @click="increase">button</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
export default defineComponent({
name: 'Counter',
setup() {
const count = ref(0);
const double = computed(() => count.value * 2);
const increase = () => { count.value++; };
return {
count,
double,
increase
}
}
});
</script>
为了更高效的打包代码,Vue3 中的所有模块,包括 ref 和 computed 都要进行引入。
上面这段代码还是有可优化空间的,因为我们发现,这些变量、方法和计算属性都是分散的。那我们有什么办法将这些元素都集中起来呢?就可以使用 reactive。
3. reactive
reactive 的作用是返回对象的响应式副本。响应式对象其实是 JavaScript Proxy,其行为表现与一般对象相似。不同之处在于 Vue 能够跟踪对响应式对象属性的访问与更改操作。
当我们调用 reactive 方法时,可以给它传递一个对象,最终返回的就是传入的对象的一个响应式副本。
reactive 有2条限制:
(1) 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
(2) 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失。
同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性。
对上面的代码使用 reactive 进行改造,如下:
setup() {
const data = reactive({
count: 0,
double: computed(() => data.count * 2),
increase: () => { data.count++; },
});
return {
count: data.count,
double: data.double,
increase: data.increase
}
}
我们将上面的 count、double 和 increase 放入一个变量,然后使用 data 变量来接收 reactive 返回的对象副本,再将 data 暴露给模板。这样做看似没问题,但会出现如下报错:
表示变量 data 隐式的具有 any 类型,它没有类型批注等信息。这里报错的主要原因是因为我们没有定义变量 data 的类型,没有告诉 TS 编译器 data 应该包含哪些属性。
这里我们并没有享受到 TS 带来的好处,反而不能按写 JS 代码的方式来写 TS 代码了。解决方案为新定义一个接口 DataProps,然后将变量 data 的类型设置为 DataProps。
interface DataProps {
count: number;
double: number;
increase: () => void;
}
setup() {
const data: DataProps = reactive({
count: 0,
double: computed(() => data.count * 2),
increase: () => { data.count++; },
});
return {
count: data.count,
double: data.double,
increase: data.increase
}
}
这次看似又好像没问题了,然后我们打开浏览器访问,发现点击按钮时页面上的数字并没有变。最主要的原因是因为当我们要把 data.count、data.double 和 data.increase 的值单独拿出来时,都会失去它们的响应性。
这里我们还需要用到 Vue 提供的另一个方法 toRefs。
4. toRefs
toRefs 方法的作用是将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。
setup() {
const data: DataProps = reactive({
count: 0,
double: computed(() => data.count * 2),
increase: () => {
data.count++;
},
});
// toRefs
const state = toRefs(data); // { count: ref(0), ... }
return {
count: state.count,
double: state.double,
increase: state.increase
}
}
这时再打开浏览器访问,点击按钮页面上的数字就可以自增了。
这里还可以使用 ES 6 的剩余运算符和解构赋值进行代码优化:
const state = toRefs(data); // { count: ref(0), ... }
return {
...state
}
本文案例的完整代码如下:
<template>
<div class="home">
<h1>{{ count }}</h1>
<h1>{{ double }}</h1>
<button type="button" @click="increase">button</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, computed } from 'vue';
interface DataProps {
count: number;
double: number;
increase: () => void;
}
export default defineComponent({
name: 'Counter',
setup() {
const data: DataProps = reactive({
count: 0,
double: computed(() => data.count * 2),
increase: () => { data.count++; },
});
// toRefs
const state = toRefs(data); // { count: ref(0), ... }
return {
...state
}
}
});
</script>