Vue3 的新特性(一):ref()、reactive()、toRef() 和 toRefs()
1. 前言
vue3 更好的支持了 Typescript
,新增了 CompositionAPI
,而且在性能方面有很大提升
- 打包大小减少 41%
- 初次渲染快 55%,更新快 133%
- 内存使用减少 54%
这篇文章主要来学习以下 vue3 的以下新特性:ref()
、reactive()
、toRef()
、toRefs()
2. 版本要求
对于 Vue3,node 版本 10+、Vue Cli v4.5+的版本可用。已经安装过@vue/cli
需更新至最新版本
npm install -g @vue/cli
or
yarn global add @vue/cli
3. 配置 Vue3 开发环境
vue create [project-name]
?Please pick a preset:
Manually select feature(手动选择一些特性)
然后,是一系列可插拔的支持,囊括各种功能,充分体现了渐进式的特点
?Check the feature needed for your project:
Choose vue version
Babel
Typescript
Linter/Formatter(代码格式检查工具)
?Choose a version of vue.js that you want to start the project with:
3.X(Preview)
? Use class-style component snytax?
(类类型的组件)
No
?Use Babel alongSide Typescript?
No
?Pick a linter / formatter config:
Eslint with error prevention only
? Pick additional lint features:
Lint on save
? Where do you prefer placing config for Babel,Eslint ,etc.?
In dedicated config files
? Save this as a preset for future projects?
No(根据自己的需要选择)
OR 使用 UI 界面:
vue ui
进入图文界面进行选择配置
4. vue2 与 vue3 的响应式实现
我们知道,vue2 中的 data 返回的是一个响应式对象,其原理是通过 Object.defineProperty()
实现的,但是会有一些弊端,比如它对于对象上新增的属性无能为力;对于数组则需要拦截它的原型方法来实现响应式。
那么,vue3 踏着五彩祥云就来了。vue3 是使用 ES6 中的新特性 Proxy 来实现响应式。先来对 Proxy
进行一下预热,
Proxy
对象用于定义基本操作的自定义行为(如:属性查找、赋值、枚举、函数调用等)。
Proxy
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
更多内容可以参考 ES6 Proxy
5. ref() 和 reactive()
ref()
函数接收一个基本数据类型的参数同时返回一个基于该值的响应性对象,该对象内部有且仅有一个属性 value,该对象中的值一旦被改变和访问就会被跟踪到,通过修改refData.value
的值,可以触发模版的重新的渲染,显示最新的值。reactive
则是修改state.reactiveField
的值。
reactive()
函数接收一个复杂数据类型的数据(对象或数组)作为参数,并返回一个响应式代理对象。(响应式数据即当数据发生变化时 UI 也会自动更新)
<template>
<div>
<h3>{{ temp }}</h3>
<p>{{ user.name }}</p>
<p>{{ user.age }}</p>
<button @click="increase">click me!</button>
</div>
</template>
import { ref, reactive } from 'vue'
export default {
setup() {
const temp = ref(0)
temp.value = 'hello'
const user = reactive({ name: 'lemon', age: 20 })
console.log(temp)
console.log(temp.value) // hello
console.log(user) // Proxy {name:'lemon',age:20}
const increase = () => {
user.age++
}
return { temp, user, increase }
}
}
reactive()
函数可以代理一个复杂数据类型比如:对象、数组,但不能代理基本类型值,例如字符串、数字、boolean 等,这是 js 语言的限制,因此我们需要使用 ref()
函数来间接对基本类型值进行处理。ref
的本质还是reactive
系统会自动根据ref()
函数的入参将其转换成ref(x)
即reactive({value:x})
综上,
ref(user.name)
就相当于ref('lemon')
也相当于reactive({value:'lemon'})
注意:
- 在 vue 模板中使用
ref
的值不需要通过value
属性获取(vue 会自动给 ref 的值加上.value) - 在 js 中使用
ref
的值要通过.value
获取
6. toRef() 和 toRefs()
toRef
是将个对象 A 中的某个属性 x 转换为响应式数据,其接收两个参数,第一个参数为对象 A,第二个参数为对象中的某个属性名 x。
import { toRef } from 'vue'
export default {
setup() {
const user = { name: 'lemon', age: 3 }
const userRef = toRef(user, 'age')
return { userRef }
}
}
到这里你应该会有一个疑问,toRef()
和ref()
都是创建响应式数据的函数,它们之间有什么不同呢?我们来测试一下。
<template>
<p>
响应式数值ref0:
<button @click="add0">add {{ state0 }}</button>
</p>
<p>
响应式对象ref1:
<button @click="add1">Add {{ state1 }}</button>
</p>
<p>
响应式对象toRef2:
<button @click="add2">Add {{ state2 }}</button>
</p>
</template>
<script lang="ts">
import { ref, toRef } from "vue";
export default {
setup() {
const temp = { count: 1 };
const temp0 = 1;
const state0 = ref(temp0);
const state1 = ref(temp.count);
const state2 = toRef(temp, "count");
const add0 = () => {
state0.value++;
console.log("原始值:", temp0); //原始值:1
console.log("响应式数据对象ref:", state0.value); //响应式数值ref:2
};
const add1 = () => {
state1.value++;
console.log("原始值:", temp); //原始值:1
console.log("响应式数据对象ref:", state1.value); //响应式对象ref:2
};
const add2 = () => {
state2.value++;
console.log("原始值:", temp); // 原始值:2
console.log("响应式数据对象toRef:", state2.value); //响应式对象toRef:2
};
return { state0, state1, state2, add0, add1, add2 };
},
};
</script>
综上,ref()和 refs()有以下区别:
- 参数不同:
ref()
接收一个 js 基本数据类型的参数;toRef()
接收两个参数,第一个为对象,第二个为对象中的某个属性; - 原理不同:
ref()
是对原数据的一个深拷贝,当其值改变时不会影响到原始值;toRef()
是对原数据的一个引用,当值改变时会影响到原始值; - 响应性不同:
ref()
创建的数据会触发 vue 模版更新;toRef()
创建的响应式数据并不会触发 vue 模版更新,所以toRef()
的本质是引用,与原始数据有关联。
toRefs()
接收一个对象作为参数,并遍历对象身上的所有属性,然后逐个调用toRef()
执行。以此,将响应式对象转化为普通对象,便于在模版中可以直接使用属性。
当我们希望对象的多个属性都变成响应式数据,并且要求响应式数据和原始数据相关联,并且更新响应式数据时不更新界面,这时候toRefs()
就派上用场了,它用于批量设置多个响应式数据。
那么,到这里又有疑问了,通过上面的学习我们知道使用 reactive()创建的数据已经具有响应式了,为什么还要再 toRefs()呢?
往下看,
setup() {
// 创建一个响应式对象state
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state) // 将响应式的对象变为普通对象结构
// The ref and the original property is "linked"
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
return { temp, userRefs, ...stateAsRefs, add };
}
对于以上代码,toRefs()将响应式的对象 state 变为普通对象 stateAsRefs 后,return 时使用 ES6 的扩展运算符,在模版中可以直接使用其内部属性,且仍具有响应性( 对响应式对象 state 使用扩展运算符后,其内部属性就失去了响应性 )
官方案例:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// logic operating on state
// convert to refs when returning(返回时转为refs)
return toRefs(state)
}
export default {
setup() {
// can destructure without losing reactivity(可以解构而不失去其响应性)
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
对响应式对象进行 toRefs 后,可以对其进行解构方便 vue 模版使用,但是不会使其失去响应性。