「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
⏳ 前言
Vue3自2020年发布以来就广受开发者热捧,其中最值得我们学习的就是新推出的Composition API,这也是在前端面试中必问的内容,这篇文章用于记录学习Vue3.0 Composition API的过程
🔧 创建vue3项目
npm install -g @vue/cli
vue create my-app
cd my-app
npm run serve
🚗 Compisition API新特性
Vue2.0升级到Vue3.0后,对开发者来说比较明显的一个改变是Vue从选项式API(Options API)
变成了Composition API
框架的设计模式,也就是说Vue将很多API拆分成了一个一个的hook,组合起来形成了Composition API。我们在Vue2中的methods
、computed
、watch
、mounted
等...都变成了Vue3中的每一个钩子函数hook
,并提供给开发者按需导入并在setup
函数作用域中使用
// Vue2.0 Options API
<script>
import { watch, computed, mounted } from 'vue';
export default {
name: "App",
data() {
return {}
},
mounted() {
// ...
},
computed() {
// ...
}
}
</script>
// Vue3.0 Composition API
<script>
import { onUpdated, watch, computed, onUnmounted } from 'vue';
export default {
name: "App",
setup() {
onUpdated(() => {
// ...
});
onUnmounted(() => {
// ...
});
watch(() => {}, () => {});
return {
// ...
};
}
}
</script>
Setup
setup是Composition API的入口,它的存在是为了能让开发者能够使用Compisition API。(It serves as the entry point for composition APIs.
)
setup函数在组件示例被创建时且props
被初始化后被调用。从生命周期的角度上说,它在beforeCreate
前被调用
setup函数接收两个参数:props
和context
。setup函数中的props
是响应式的,当传入新的prop时,它将被更新。
注意: 不要对props解构,否则会丢失响应式的特性
However, do NOT destructure the
props
object, as it will lose reactivity
我们都知道,在Vue2中要想在模板中使用数据,必须在data
中返回,到了Vue3中,这个函数被替换成了setup,也就是说我们需要在setup函数中返回一个对象,对象中的每一个属性会被合并到render函数上下文中,这样一来模板就可以获取到render函数中的数据,从而渲染到模板中去
<template>
<div>{{ readersNumber }} {{ book.title }}</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
const readersNumber = ref(0)
const book = reactive({ title: 'Vue 3 Guide' })
// expose to template
return {
readersNumber,
book
}
}
}
</script>
reactive
reactive接收一个对象作为参数,并将它转化为proxy响应式对象,当数据更新时,UI界面也会更新
<template>
<div id="app">
{{data.count}}
<button @click="data.increase">👍+1</button>
</div>
</template>
<script>
import { computed, reactive, ref } from '@vue/reactivity';
export default {
name: 'App',
setup() {
const data = reactive({
count: 0,
increase: () => {
data.count ++
}
})
return {
...data
}
}
}
</script>
这个时候如果我们尝试将reactive对象data解构,直接得到count
和increase
运行项目后我们会发现点击按钮时页面数据并没有更新,这是因为上文提到的:将响应式对象解构会使它失去响应式。
如果你觉得每次需要data.
来获取数据有些繁琐,执意要对data解构,但又不想丢失数据的响应式,那么你可以使用toRefs将reactive对象中的每一个属性都转化为ref对象,toRefs会很好解决这个问题,这个会在后面提及
ref
ref函数接收一个原始值inner value
,返回一个响应式的ref对象,它的作用是将原始类型转化为响应式的对象。ref对象只有一个属性value
,通过.value
的方式可以获取ref对象的inner value
<template>
<div id="app">
{{count}}
<button @click="increase">👍+1</button>
</div>
</template>
<script>
import { ref } from '@vue/reactivity';
export default {
name: 'App',
setup() {
const count = ref(0)
const increase = () => {
count.value ++
}
return {
count,
increase
}
}
}
</script>
<style>
</style>
toRef
我们知道用ref可以用于创建一个响应式数据,而toRef也可以创建一个响应式数据,那他们之间有什么区别呢? 事实上,如果利用ref函数将某个对象中的属性变成响应式数据,修改响应式数据是不会影响到原始数据。 例如:
<template>
<div id="app">
{{obj.name}}
{{newObj}}
<button @click="change">change</button>
</div>
</template>
<script>
import { computed, reactive, ref, toRefs } from '@vue/reactivity';
export default {
name: "App",
setup() {
let obj = {
name: "张三",
age: 34
};
let newObj = ref(obj.name);
function change() {
newObj.value = "李四";
console.log(obj, newObj);
}
return { obj, newObj, change };
}
};
</script>
执行change函数后,可以看到,ref对象newObj
发生改变,但原始数据obj
没有改变,原因是ref的本质是拷贝,与原始数据没有引用关系
而如果使用toRef将某个对象中的属性变成响应式数据,修改响应式数据是会影响到原始数据的。但是需要注意,如果修改通过toRef创建的响应式数据,并不会触发UI界面的更新。
所以,toRef的本质是引用,与原始数据有关联
<script>
import { reactive, ref, toRefs } from '@vue/reactivity';
export default {
name: "App",
setup() {
let obj = {
name: "张三",
age: 34
};
let newObj = ref(obj.name);
function change() {
newObj.value = "李四";
console.log(obj, newObj);
}
return { obj, newObj, change };
}
};
</script>
当change执行时,响应式数据发生改变,原始数据obj
也随之改变,但页面并不会更新。
ps:如果希望原始数据跟随toRef改变的话,可以将obj转化为reactive响应式对象
ref和toRef的区别:
- ref的本质是拷贝,ref数据改变不会影响原始数据。toRef的本质是引用,它的数据改变会影响原始数据
- ref数据修改后UI界面会更新,toRef数据修改后UI界面不会更新
- ref接受一个原始数据作为参数,而toRef接收两个参数,第一个参数是对象,第二个参数是对象的属性,表明需要将对象的哪个属性转化为ref响应式对象
toRefs
有的时候,我们希望将对象的多个属性都变成响应式数据,并且要求响应式数据和原始数据关联,并且更新响应式数据的能够同时更新UI界面,就可以使用toRefs将对象上的多个数据转化为为响应式数据。
toRefs接收一个对象作为参数,它会遍历对象身上的所有属性,然后将对象上的每一个属性都转化为ref对象。
<template>
<div id="app">
{{count}}
<button @click="increase">👍+1</button>
</div>
</template>
<script>
import { computed, reactive, ref, toRefs } from '@vue/reactivity';
export default {
name: 'App',
setup() {
const data = reactive({
count: 0,
increase: () => data.count ++
})
const refData = toRefs(data)
return {
...refData
}
}
}
</script>
刚才我们提到可以使用toRefs将reactive对象中的每一个属性都转化为ref对象,从而保留了数据的响应式
<template>
<div id="app">
{{data.count}}
<button @click="data.increase">👍+1</button>
</div>
</template>
<script>
import { computed, reactive, ref } from '@vue/reactivity';
export default {
name: 'App',
setup() {
const data = reactive({
count: 0,
increase: () => {
data.count ++
}
})
return {
...toRefs(data)
}
}
}
</script>
isRef
isRef用于判断一个值是否是ref对象
<script>
import { reactive, ref, toRef, toRefs, isRef } from "@vue/reactivity";
export default {
name: "App",
setup() {
const state = reactive({ count:1 })
const stateRefs = toRefs(state)
console.log(isRef(stateRefs)) // false
console.log(isRef(stateRefs.count)) // true
}
};
</script>