这是新入职以来的第一篇技术博客。虽然目前工作中暂时没有用到 Vue3.0,但是还是要和社区保持同步,了解一门新的技术还有它的底层原理是件快乐的事。这个周末宅在家里无所事事,把李南江老师关于 Vue3.0 的讲解视频刷了几遍,鉴于我金鱼般三秒的记性,觉得有必要在此做个记录。冬季的午后暖洋洋,小奶狗躺在我的腿上打鼾,多么美好的周末吖~
Composition API
诞生背景
组合API(Composition API)是 Vue3.0 的一个新特性。它的诞生主要是解决 Vue2.0 存在的问题,即数据和业务逻辑分离的问题。比方说我们要实现一个功能,我们首先要在 data 中初始化相关的数据,然后再找到 methods 方法(有时候是在 computed 或者 watch),在里面书写我们的业务逻辑。这种数据和业务逻辑完全分离的书写方式,在页面代码量大的情况下,阅读起来可是相当费劲呀,而且也不利于代码的管理和维护。
使用方式
组合API是在 setup() 中定义的,下面我们来简单认识下 setup() 的用法。
<template>
<div>
<p>{{count}}</p>
<button @click="myBtn">加法按钮</button>
</div>
</template>
<script>
//ref函数注意点:
//ref函数只能监听简单类型的变化,不能监听复杂类型的变化
import {ref} from 'vue'
export default {
name: 'App',
// setup函数是组合API的入口函数
setup() {
// 定义一个count变量,初始值为0
// ref()函数的作用是
// 当变量发生改变的时候,
// Vue会自动更新UI界面
let count = ref(0)
// 在组合API中,可以直接定义方法
function myBtn() {
count.value++
}
// 在组合API中定义的变量和方法,必须暴露出去才能被外界使用
return {count,myBtn}
}
}
</script>
初识组合API的魅力之后,我们来看看它到底是如何解决 Vue2.0 将数据和业务逻辑分离的问题。
<template>
<div>
<form>
<input type="text" v-model="state2.dogs.id"/>
<input type="text" v-model="state2.dogs.name"/>
<input type="text" v-model="state2.dogs.age"/>
<input type="submit" @click="addItem"/>
</form>
<ul>
<li v-for="item in state.dogs" :key="item.id" @click="deleteItem(item.id)">
{{item.name}} - {{item.age}}
</li>
</ul>
</div>
</template>
<script>
//ref函数注意点:
//ref函数只能监听简单类型的变化,不能监听复杂类型的变化
//reactive函数可以监听复杂数据类型的变化
import {ref,reactive} from 'vue'
export default {
name: 'App',
// setup函数是组合API的入口函数
setup() {
let state = reactive({
dogs: [{
id: 1, name: 'Sugar', age: 1
},{
id: 2, name: 'Momo', age: 2
},{
id: 3, name: 'Bobo', age: 3
},{
id: 4, name: 'Kara', age: 4
}]
})
let state2 = reactive({
dogs: {
id: '',
name: '',
age: ''
}
})
// 点击删除该数据
function deleteItem (id) {
state.dogs = state.dogs.filter(item => id !== item.id)
console.log(state.dogs)
}
// 点击添加数据
function addItem (e) {
e.preventDefault()
const dog = Object.assign({}, state2.dogs)
state.dogs.push(dog)
}
return {state,state2,deleteItem,addItem}
},
}
</script>
当然我们也可以将方法抽取出来
<script>
import {ref,reactive} from 'vue'
export default {
name: 'App',
// setup函数是组合API的入口函数
setup() {
let {state,deleteItem} = useDeleteDogModule()
let {state2,addItem} = useAddDogModule(state)
return {state,state2,deleteItem,addItem}
},
}
// 删除模块
function useDeleteDogModule () {
let state = reactive({
dogs: [{
id: 1, name: 'Sugar', age: 1
},{
id: 2, name: 'Momo', age: 2
},{
id: 3, name: 'Bobo', age: 3
},{
id: 4, name: 'Kara', age: 4
}]
})
// 点击删除该数据
function deleteItem (id) {
state.dogs = state.dogs.filter(item => id !== item.id)
console.log(state.dogs)
}
return {state,deleteItem}
}
// 新增模块
function useAddDogModule (state) {
let state2 = reactive({
dogs: {
id: '',
name: '',
age: ''
}
})
// 点击添加数据
function addItem (e) {
e.preventDefault()
const dog = Object.assign({}, state2.dogs)
state.dogs.push(dog)
}
return {state2,addItem}
}
</script>
这样的话就实现了数据和业务逻辑在同一个模块中,便于后续的管理和维护。 (此图来自李南江老师的视频 www.bilibili.com/video/BV14k…)
关于 setup() 函数注意点
- setup 函数只能是同步的不能是异步的;
- setup 函数的执行时机是在 beforeCreate 和 cerated 的生命周期期间,所以在 setup 函数中无法获取 data 和 methods 中的数据,setup 函数中的 this 为 undefined。
响应式原理
我们都知道 Vue2.0 的响应式原理是利用 Object.defineProperty,它有很多弊端,比如不能监听新增和删除属性的变化。而 Vue3.0 则是利用 ES6 的 Proxy。Proxy 的性能和功能都要比 Object.defineProperty 更好。Vue3.0 在实现响应式功能方面封装了好几个 API,下面我们来了解一下。
- shallowReactive 和 shallowRef 的区别:都是只监听第一层数据的变化(浅监听)。shallowRef 只能监听简单数据的变化,它的底层也是调用 shallowReactive 。shallowReactive 可以监听复杂数据的变化。
- reactive 和 ref 的区别:递归监听每一层数据的变化。ref 只能监听简单数据的变化,它的底层也是调用 reactive 。reactive 可以监听复杂数据的变化。
- shallowReadonly 和 readonly 的区别:表示数据不可修改。shallowReadonly 使得第一层数据是可读的。readonly 递归遍历每一层数据,使得每一层数据都是可读的。
手写 shallowReactive
/**
*
* @param {*} obj
* 该API只会监听到复杂数据第一层数据的变化
* 所以只会打印一次 更新UI界面
*/
function shallowReactive(obj) {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key,val) {
obj[key] = val
console.log('更新UI界面')
return true
}
})
}
// test
let obj = {
a: 'a',
b: {
c: 'c',
d: {
e: 'e',
f: {
g: 'g'
}
}
}
}
let state = shallowReactive(obj)
state.a = '1'
state.b.c = '2'
state.b.d.e = '3'
state.b.d.f.g = '4'
// 更新UI界面
手写 shallowRef
/**
*
* @param {*} obj
* 该API只会监听到复杂数据第一层数据的变化
* 所以只会打印一次 更新UI界面
*/
function shallowReactive(obj) {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key,val) {
obj[key] = val
console.log('更新UI界面')
return true
}
})
}
/**
*
* @param {*} obj
* 该API底层是走shallowReactive方法
* 只会监听value属性的变化
*/
function shallowRef(val) {
return shallowReactive({value: val})
}
// shallowRef test
let obj = {
a: 'a',
b: {
c: 'c',
d: {
e: 'e',
f: {
g: 'g'
}
}
}
}
let state = shallowRef(obj)
// 注意这里的修改方式
state.value = {
a: '1',
b: {
c: '2',
d: {
e: '3',
f: {
g: '4'
}
}
}
}
// 更新UI界面
// 写成这样则无法修改
// state.value.a = '1'
手写 reactive
/**
* @param {*} obj
* 该API可以监听到复杂数据每一层数据的变化
* 所以只会打印四次 更新UI界面
*/
function reactive(obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
obj.forEach((item, index) => {
if (typeof item === 'object') {
obj[index] = reactive(item)
}
})
} else {
for (key in obj) {
const item = obj[key]
if (typeof item === 'object') {
obj[key] = reactive(item)
}
}
}
} else {
console.warn('必须传入对象')
}
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key,val) {
obj[key] = val
console.log('更新UI界面')
return true
}
})
}
// test
let obj = {
a: 'a',
b: {
c: 'c',
d: {
e: 'e',
f: {
g: 'g'
}
}
}
}
let state = reactive(obj)
state.a = '1'
state.b.c = '2'
state.b.d.e = '3'
state.b.d.f.g = '4'
//更新UI界面
//更新UI界面
//更新UI界面
//更新UI界面
手写 ref
/**
* @param {*} obj
* 该API底层是走reactive方法
* 同shallowReactive和shallowRef的区别
*/
function ref(val) {
return reactive({value:val})
}
手写 shallowReadonly
/**
*
* @param {*} obj
* 该API表示数据第一层是可读的,不可修改
* 其他层的数据可以修改
*/
function shallowReadonly(obj) {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key,val) {
// 此处不给赋值
console.warn(`${key}是只读的。`)
}
})
}
// test
let obj = {
a: 'a',
b: {
c: 'c',
d: {
e: 'e',
f: {
g: 'g'
}
}
}
}
let state = shallowReadonly(obj)
state.a = '1'
state.b.c = '2'
state.b.d.e = '3'
state.b.d.f.g = '4'
console.log(state)
// a是只读的。
// { a: 'a', b: { c: '2', d: { e: '3', f: [Object] } } }
手写 readonly
/**
* @param {*} obj
* 该API保证每层的数据都是只读的
*/
function readonly(obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
obj.forEach((item, index) => {
if (typeof item === 'object') {
obj[index] = readonly(item)
}
})
} else {
for (key in obj) {
const item = obj[key]
if (typeof item === 'object') {
obj[key] = readonly(item)
}
}
}
} else {
console.warn('必须传入对象')
}
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key,val) {
console.warn(`${key}是只读的。`)
}
})
}
// test
let obj = {
a: 'a',
b: {
c: 'c',
d: {
e: 'e',
f: {
g: 'g'
}
}
}
}
let state = readonly(obj)
state.a = '1'
state.b.c = '2'
state.b.d.e = '3'
state.b.d.f.g = '4'
// a是只读的。
// c是只读的。
// e是只读的。
// g是只读的。
so easy too happy ~!