一、options api 选项式API
# 一、options api 选项式API
Options Api可以理解为就是组件的各个选项,data、methods、computed、watch等等就像是组件的一个个选项,在对应的选项里做对应的事情。
export default {
data () {
return {
// 定义响应式数据的选项
}
},
methods: {
// 定义相关方法的选项
},
computed: {
// 计算属性的选项
},
watch: {
// 监听数据的选项
}
...
}
在data中定义的数据,是无法做到响应式的,那是因为Object.definePropety只会对data选项中的数据进行递归拦截
在实际项目的开发过程中,数据定义在data中,方法定义在methods中,当我们的代码多起来,比如达到四、五百行的时候,如果我们想改动某个功能,就要去data中改数据,再去methods中改方法,来回地寻找。
# 二、composition api 组合式api
# 1、Composition Api
支持将相同的功能模块代码写在一起,甚至可以将某个功能单独的封装成函数,随意导入引用;也可以将任意的数据定义成响应式,再也不用局限于data中,我们只需要将每个实现的功能组合起来就可以了。
示例:
<template>
<div>{{count}}</div>
</template>
<script setup>
import { ref } from "vue";
let count = ref(0);
</script>
# 2、watchEffect
1、watchEffect是立即执行的,不需要添加immediate属性。
2、watchEffect不需要指定对某个具体的数据监听,watchEffect会根据内容自动去感知,所以我们也可以在一个watchEffect中添加多个数据的监听处理
3、watchEffect不能获取数据改变之前的值。
同时,watchEffect会返回一个对象watchEffectStop,通过执行watchEffectStop,我们可以控制监听在什么时候结束。
简单理解watchEffect会在第一次运行时创建副作用函数并执行一次,如果存在响应式变量,取值会触发get函数,这个时候收集依赖存储起来,当其他地方给响应式变量重新赋值的时候,set函数中会触发方法派发更新,执行收集到的副作用函数,如果不存在响应式变量,就不会被收集触发
vue3不再只能有一个根元素
# 3、ref和reactive
ref和reactive的区别是什么呢,我们可以这样简单理解,它们都是用来定义响应式数据的,但是ref是用来给简单的数据类型定义响应式数据的,比如number、string、boolean等,而reactive是针对复杂的数据结构的,比如一个对象。
它们写法的区别主要在:ref定义的数据,修改的时候是需改xxx.value的,而reactive定义的不用,产生这个区别的原因是它们实现响应式的方法不一样。
# 4、小结:
Options Api
1、选项式的api,相关代码必须写在规定的选项中,导致相同功能的代码被分割,代码量上来后查找相关代码很麻烦,后期维护修改难度较大。
2、数据都挂载在同一个this下,对typescript的支持不友好,类型推断很麻烦。
3、代码的复用能力很差。
Composition Api
1、组合式api,代码定义很自由,相同功能代码整合到一起,查找修改都很方便。
2、公共代码的复用很简单,不同功能的代码也可以自由组合。
3、Vue相关的api都是通过import导入的,这在打包的时候很友好。
另外,vue3是支持options api的写法的
# 三、Vue3 响应式的实现
在Vue2.x中,响应式的机制深入人心,我们只需要在data中定义我们需要的数据,就会在初始化时被自动转为响应式数据。
但是在Vue2中,响应式的使用还存在一些限制,比如对象属性的增加和删除等并不能被监听到,在Vue3中,重新设计了响应式系统来解决这些问题。
# 1、Vue2.x的响应式——Object.defineProperty
Vue2响应式失效的现象
<template>
<div>
<span>姓名:</span>
<span>{{person.name}}</span>
<span>年龄:</span>
<span>{{person.age}}</span>
<button @click="changeName">修改姓名</button>
<button @click="addAge">增加年龄</button>
</div>
</template>
export default {
data () {
return {
person: {
name: '小明'
}
}
},
methods: {
addAge() {
this.person.age = '18';
},
changeName() {
this.person.name = '小红';
}
}
...
}
data里面只定义了一个响应式对象person,我们定义了2个方法,一个是修改名称、一个是增加年龄,但是使用增加年龄方法时,会给响应式对象添加一个新的属性age,页面上的年龄部分并不会发生改变。
Vue2是通过Object.defineProperty循环遍历拦截data中的数据来实现响应式的。
Object.defineProperty其实不是真正的代理,而应该是拦截
而且Object.defineProperty也不是对对象进行拦截,而是拦截对象的具体的某个属性。
const person = {};
Object.defineProperty(person, 'name', {
set (value) {
console.log('name:', value)
},
get () {
return '小明'
}
});
console.log(person.name);
Vue2.x的响应式实现其实就是递归遍历data中返回的对象,对每一个属性都使用Object.defineProperty进行拦截,而不在data中被初始化的数据是没有添加拦截的。
Vue2如何添加和删除响应式数据?
需要额外的api来实现,Vue.$set 和Vue.$delete方法分别实现添加、删除响应式数据
Vue2响应式的局限性
1、无法监听整个对象,只能对每个属性单独监听。
2、无法监听对象的属性的新增,删除(需要补充额外的api来解决)。
3、无法监听数组的变化。
# 2、Vue3的响应式-proxy
proxy是真正地对整个对象进行代理,因为proxy可以劫持整个对象,所以Object.defineProperty中新增,删除某个属性无法检测的问题就不存在了,同时proxy也可以检测数组的变化
const person = {
name: '小明',
age: 18
};
const personProxy = new Proxy(person, {
get: function(target, prop) {
console.log(`获取了${prop}:`, target[prop]);
return target[prop];
},
set: function(target, prop, value) {
console.log(`修改了${prop}:`, value);
target[prop] = value;
}
});
console.log('name:', personProxy.name); // 获取了name:小明
personProxy.age = 20; // 修改了age:20
参数target,表示当前代理的对象,prop是我们具体要操作的属性,set多了一个参数value是我们对新属性的赋值。
从方法的参数我们其实就能看出来,proxy是真的对整个对象进行拦截的,我们如果有新增或删除的属性,也不需要单独去添加处理,可以直接被检测代理。
在添加删除属性时,无需额外的api。proxy不支持IE11。
vue3另外一个代理的方法,那就是对象本身的get、set方法
const count = {
_value: 0,
set value(num) {
console.log('修改了count:', num);
this._value = num;
},
get value() {
console.log('获取了count');
return this._value
}
}
console.log(count.value); // 获取了count
count.value = 1; // 修改了count: 1
这其实就是为什么我们使用ref定义的数据,赋值和取值的时候需要使用xxx.value了
一个Vue3 composition api常用的工具集:VueUse
# 四、Vue2升级到Vue3的非兼容性变更
Vue3中做了很多重构,有部分内容对于Vue2来说是不兼容的,所以说Vue2的代码直接升级到Vue3是不能直接运行的。
# 1、createApp的非兼容性变更
Vue2根实例挂载及全局组件注册方法:
import Vue from 'vue';
import App from './App.vue';
// 引入全局组件
import GlobalComponent from './GlobalComponent.vue';
// 注册全局组件
Vue.component('GlobalComponent', GlobalComponent);
new Vue({
render: h => h(App),
}).$mount('#app')
Vue3不直接在Vue对象上进行操作了,而是通过createApp来创建一个App应用实例,所有的操作都在App上进行
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app');
现在我们想要在一个App上引入store,就可以使用下面的写法(全局对象被共享是一件非常危险的事情)
// 引入封装好的store
import store from "./store";
createApp(App).use(store).mount('#app');
createApp(App2).mount('#app2');
App实例上的store不会影响App2
# 2、api的import导入
我们在使用这些挂载在Vue对象下的Api时,需要经过import导入的方式来使用。
import { nextTick } from 'vue';
nextTick(() => {
...
})
按需加载的使用。
在Vue2的Api中,都是挂载在Vue下面,那么在打包的时候,会不管你有没有使用到这个Api,都会一起打包进去,如果都是这样,随着Vue的全局Api越来越多,冗余的代码也就越多,打包的耗时、体积或者说代价也就越大。
在Vue3中,通过import导入Api来使用,那我们在打包的时候,则只会将对应的模块打包进去,做到真正的用了多少就打包多少,就算Vue中再增加多少代码,也不会影响我们打包的项目。
# 3、小结
升级Vue3不仅需要更换Vue版本,还有一些非兼容性变更内容需要了解
- 全局的操作不再使用Vue实例,而是使用通过createApp创建的app实例。
- 全局和内部API已经被重构,需要使用import导入使用,并且支持tree-shake。
如何将localStorage中的数据变成响应式的?
// tool.js
import {ref, watchEffect} from 'vue';
const useLocalStorage = (name, value = {}) => {
const localData = ref(JSON.parse(localStorage.getItem(name)) || value);
watchEffect(() => {
// 监听本地localstorage数据对应的响应式变量更改
localStorage.setItem(name, JSON.stringify(localData.value));
})
return localData;
}
export {
useLocalStorage
}
假设有一个计数器,需要将数据同步到本地的localStorage中,我们只需要在计数器文件中引入useLocalStorage方法即可:
<script setup>
import {useLocalStorage} from './useLocalStorage';
// 定义响应式数据
let count = useLocalStorage('count', 0);
const addCount = () => {
count.value ++;
}
</script>