前言
Vue3.0的步伐越来越近了,是时候了解起来了,虽然嘴上还喊学不动了,但是,身体还得诚实起来,接着学。。。
通过各种博客资料,还有前段时间尤雨溪大佬的直播Vue3相对Vue2的比较大的变化有以下几种:
- 使用 TypeScript
- 放弃 class 采用 function-based API
- option API => Composition API
- 重构 complier
- 重构 virtual DOM
- 新的响应式机制
使用ts的话就是抛弃了谷歌的flow选择拥抱微软的ts。从这个情况也看得出来,不会ts的该学起来了,比如我。。。
放弃class采用function-based API据说也是为了更好支持ts,为了更灵活的逻辑复用能力,代码更容易压缩等...
option Api到Composition Api应该是对我们去写代码影响最大的一部分,稍后可以看代码体验一下。
重构compiler与virtual DOM使Vue变得更快,也是Vue越来越优秀的原因。
新的响应式机制采用了ES6的Proxy
Api,抛弃了之前的Object.defineProperty()
比较直观的解决的是Vue2中这两点问题:
关于对象:Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在
data
对象上存在才能让 Vue 将它转换为响应式的。-
关于数组:Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
- 当你利用索引直接设置一个数组项时,例如:
在官网深入响应式原理一章有较详细阐述,针对以上两种情况解决方法,官网也有给出答案,那就是使用set
方法。
而Proxy
可以完美的解决该问题,当然好处应该不止这些,剩下的慢慢探究吧,Proxy也有缺点,那就是兼容性问题,有一些浏览器不支持,而且无完全polyfill,浏览器支持程度可以查看https://caniuse.com/#search=Proxy
简单了解Proxy
Vue核心就是响应式数据,Vue3.0中的响应式采用了Proxy
那就简单看看Proxy
是怎么个样子的呢。
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。。。
from MDN,学习一个新的Api官方文档还是要读一下哈,虽然读不懂这么深奥的描述。。。
语法:
const p = new Proxy(target, handler)
参数target表示要使用Proxy包装的对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
参数handler是一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p
的行为
看看代码吧:
let obj = {
a: 1,
b: 2
}
const proxy = new Proxy(obj, {
get: function(target, prop, receiver) {
return prop in target ? target[prop] : 0
},
set: function(target, prop, value, receiver) {
target[prop] = 666
}
})
console.log(proxy.a) // 1
console.log(proxy.c) // 0
proxy.a = 10
console.log(proxy.a) // 666
obj.b = 10
console.log(proxy.b) // 不是666 而是10
以上代码中obj
是我们要代理的目标对象,get
,set
方法是参数handler的两个属性,具体如下:
handler.get()
接收三个参数,第一个参数target
为代理的目标对象,第二个参数prop
是代理的目标对象的属性,第三个参数是Proxy
或者继承Proxy
的对象,通常是proxy本身。
handler.set()
接收四个参数,其中三个参数都与get
方法相同,唯独多出来一个value
表示新的属性值。
上述代码表示当访问proxy
的属性时,进行拦截判断,该属性是否是目标对象的属性,如果是那么就将其值返回出来,否则就返回0。
当对proxy
上的属性进行重写时,将重写的该属性赋值为666。
注意:此时对数据的劫持,只是劫持了代理对象proxy
,而跟原对象obj
没有任何关系,对obj
进行操作,也不会监听到。
用proxy
实现一个简易版的数据响应:
let app = document.getElementById('app')
let input = document.getElementById('input')
let obj = { // 源数据
text:'hello world'
}
let proxy = new Proxy(obj, {
set: function(target, prop, value){ // input事件触发进行劫持,触发update方法
target[prop] = value
update(value)
}
})
function update(value){ // update方法用于同步dom更新
app.innerHTML = value
input.value = value
}
input.addEventListener('input',function(e){ // 监听input数据变化,并修改proxy的值
proxy.text = e.target.value
})
proxy.text = obj.text // 初始化源数据
使用Vue CLI体验Vue3.0
第一步,安装vue-cli
npm install -g @vue/cli
安装完成后查看是否已安装成功
vue -V
@vue/cli 4.4.4
如果cli已安装需要注意其版本应该高于cli4.x。
第二步,初始化vue项目
vue create vue-next-test
输入命令后,出现命令行交互,跟之前一样,主要是在初始时勾选上vue-router,vuex,避免在升级vue3的过程中手写初始化代码,会自动生成初始化代码。
注意:vue3.0项目目前不能直接创建,需从vue2.x升级。
第三步,升级成Vue3.0项目
以上只是创建了Vue2.x的项目,需要手动升级成Vue3.0的项目
进入vue-next-test文件夹
cd vue-next-test
输以下指令:
vue add vue-next
执行上述指令会自动安装vue-cli-plugin-vue-next插件,该插件会自动完成以下操作
- 安装vue3.0beta版依赖
- 配置webpack去在vue3中编译.vue文件
- 自动迁移全局api(创建新模板)
- 升级安装Vue Router4.0和Vuex 4.0,如果默认为未安装,则不升级。
- 自动修改Vue Router 和Vuex模板代码
升级完成之后就可以看代码啦!
第四步,查看Vue3.0的部分新的东西
- Vuex对比
3.0版本Vuex
import Vuex from 'vuex'
export default Vuex.createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
2.x版Vuex
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
Vue2.x版本采用构造函数构建Vue Router实例,而Vue3.0使用createStore方法来构建Vue实例,Vuex语法和Api基本没有发生变化。和之前一样,该怎么样写state,mutations等还是怎么写,该怎么调还怎么调。
- Vue Router对比
3.0版本
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/test',
name: 'test',
component: () => import('../views/Test.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
2.x版本
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/test',
name: 'test',
component: () => import('../views/Test.vue')
}
]
const router = new VueRouter({
mode:"history",
routes
})
export default router
同样的Vue Router也是之前采用构造函数形式,3.0采用createRouter方法去创建Vue Router实例,配置方法都一样。在路由模式配置上,之前是配置mode option,3.0则是采用vue-router中的createWebHistory
方法去创建history属性,我默认选择的是history模式用的是createWebHistory
方法创建history属性,如果要修改为hash模式则需要使用createWebHashHistory
方法来创建。
总结:总的来说,构建Vue Router和Vuex的方式变了,但是它们的配置方式都和之前保持一致,可以无缝衔接使用。
- Composition API
在3.0代码基础上继续往下看,创建一个新的组件
,在
组件中认识一下Composition API
之前2.x版本是采用了Options API的模式,可以理解为选项式的组件代码编写,Vue官方规定好的写法,响应式数据,methods,computed,components以及生命周期这些都是规定好的,需要在哪里写,你就得在哪里写。
Vue3.0采用Composition API的模式,可以理解成组合API,怎么个组合法呢?就类似于,在组件中实现的这些东西,响应式数据,生命周期,计算属性等,都可以在Vue中获取对应方法,然后在一个方法中组合起来统一对外输出。
Composition API提供了以下一些函数,
- ref
- reactive
- toRefs
- computed
- watch
- getCurrentInstance
- 生命周期hooks
- ...
在体验Composition API之前需要认识一个函数叫做setup()
,这个函数的主要功能是Composition API的入口,它在生命周期beforeCreate
生命周期执行之前被调用,接收props对象作为第一个参数,接收来的props对象,可以通过watch监视其变化。接受context对象作为第二个参数,这个对象包含attrs,slots,emit三个属性。多说无益,直接看代码吧。
import { ref, reactive } from 'vue'
export default {
setup(props, context){
const count = ref(0) // 定义响应式数据count
const num = ref(1) // 定义响应式数据num
const objData = {
name: 'erha',
age: '1',
skill: '拆家'
}
const obj = reactive(objData) // 定义响应式数据obj
return {
count,
num,
obj
}
},
name:'test'
}
在Vue3.0中创建响应式数据需要引用ref,reactive这两个方法,ref一般用来创建基本数据类型的响应式数据,reactive一般用来创建引用数据类型的响应式数据。
在模板中使用,跟之前没有区别,需要注意的是,ref属于将基本类型数据包装成应用类型,在模板中正常使用。在方法中访问的时候需要带上.value才能访问到。
以下代码我简写了,比如有一个按钮点击会触发一个方法,该方法是让count自增,那么应该这样写:
setup(props, context){
const count = ref(0)
const addCount = () => {
count.value ++
}
return {
count,
addCount
}
}
为什么要这么写呢?是因为Proxy的原因,Proxy要进行数据劫持的时候需要接收一个对象,所以ref就对基本数据类型的数据进行了包装,使其可以进行响应式。在方法中需要使用count.value去操作,而在模板中进行了处理,所以可以直接使用count进行渲染。
由于Proxy的机制原因,如果将reactive中的响应式数据进行解构,那么原先的响应式数据就变成不可响应的了。
import { reactive } from 'vue'
const data =reactive({
name:'lisa',
age:18
})
let { name , age} = data
data.age = 20 // 响应式
age = 30 // 非响应式
为什么将可观察对象中的属性解构出来后,变成不再可观察了呢?因为通过reactive方法创建的可观察对象,内部的属性本身并不是可观察的,而是通过Proxy代理实现的读写观察,如果将这些属性解构,这些属性就不再通过原对象的代理来访问了,就无法再进行观察。
Composition API提供了一种方法来解决此机制带来的问题,那就是toRefs,它可以将reactive创建的可观察对象,转换成可观察的ref对象
import {reactive, toRefs} from "vue"
const data =reactive({
name:'lisa',
age:18
})
let { name , age} = toRefs(data)
data.age = 20 // 响应式
age = 30 // 响应式
在模板中使用reactive生成的可观察对象的时候是这样的:
{{obj.name}}
当使用了toRefs的时候在模板中只需要使用name即可
{{name}}
Composition API提供的computed方法就相当于2.x版本中的计算属性。使用如下:
import { ref, computed } from "vue"
const count = ref(0)
const doubleCount = computed(()=>{
return count.value*2
})
Composition API提供的watch方法相当于就是2.x的观察属性。使用如下:
import { ref, watch } from "vue"
const count = ref(0)
const num = ref(1)
watch(() => { return count.value }, (newcount) => {
console.log('count变啦', newcount)
})
watch方法接收两个参数,第一个参数是一个函数,第二个参数也是个函数,第一个参数函数返回值表示要监听哪个数据,第二个参数函数,表示监听成功后的逻辑,该函数的第一个参数就是监听到目标数据变化后的值。
同时watch可以监听多个数据。
watch(
[() => count.value, () => num.value],
([count, num], [oldCount, oldNum]) => { // watch 同时观察count和num两个值
console.log(`count:${count},num:${num} oldCount:${oldCount},oldNum:${oldNum}`)
})
在Vue2.x版本中频繁出现的this,在Vue3.0中也消失了,取而代之的是Composition API提供的getCurrentInstance
方法,用来获取当前组件实例,然后通过ctx获取当前上下文。
import {getCurrentInstance} from "vue"
export default{
setup(){
const {ctx} = getCurrentInstance()
console.log(ctx)
}
}
大概是这么个东西
可以和Vue2.x中this输出对比一下。还是有不小的变动,但常用api都没有发生变化。比如切换路由
const pushRoute = () => { // 编程导航
ctx.$router.push({
path: '/about'
})
}
整体的Options API,到Composition API,大致就是之前很多挂载在Vue原型上的东西,现在都独立成一个方法然后去引用使用。之前组件中的this容易把人绕迷糊,如果采用Composition API就会好很多。之前Vue组件中强制data写在哪里,methods写在哪里,computed写在哪里,而在Vue3.0中这种规定被打破,开发者可以比较自由的组织自己的代码,两者都有自己的好处与弊端。详见可以参考文章https://juejin.im/post/5eb17a0fe51d454dd60cfe0f
最后看一下Vue3.0中的生命周期,生命周期也是有所改动,钩子函数名称均发生变化。beforeCreate,created生命周期在setup
方法中自动执行,其余生命周期钩子函数都是从Vue中引入使用(注意在setup方法中使用)
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
export default {
setup (props, context) {
// console.log(props.msg, context)
const a = ref(0)
const setA = () => {
return a.value++
}
// 相当于 beforeMount
onBeforeMount(() => {
console.log('onBeforeMount')
})
// 相当于 mounted
onMounted(() => {
console.log('onMounted')
})
// 相当于 beforeUpdate
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
// 相当于 updated
onUpdated(() => {
console.log('onUpdated')
})
// 相当于 beforeDestroy
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
})
// 相当于 destroyed
onUnmounted(() => {
console.log('onUnmounted')
})
onErrorCaptured(() => { // 错误监控 参考文章 https://zhuanlan.zhihu.com/p/37404624
console.log('onErrorCaptured')
})
onRenderTracked(() => { // 已渲染
console.log('onRenderTracked')
})
onRenderTriggered(() => { // 当组件更新时会首先触发此生命周期钩子 onRenderTriggered->onRenderTracked->onBeforeUpdate->onUpdated
console.log('onRenderTriggered')
})
return {
a,
setA
}
},
name: 'HelloWorld',
props: {
msg: String
}
}
onRenderTracked
生命周期钩子函数表示组件已渲染。组件首次渲染经历过程为onRenderTracked->onBeforeMount->onMounted
onErrorCaptured(err,vm,info)
生命周期钩子表示捕获子孙组件中的发生错误时的异常。err:错误对象 vm:发生错误的vuez组件实例 info:Vue特定错误信息,比如发生错误的生命周期
onRenderTriggered
组件更新时会触发此钩子函数。触发生命周期钩子函数过程为onRenderTriggered->onRenderTracked->onBeforeUpdate->onUpdated
在Vue3.0中由于外界声音反响比较大的原因,尤大以及团队考虑在3.0版本中可以持续使用2.x的东西,比如可以同时写mounted和onMounted两个生命周期,但是不建议这样做,如果使用Vue3.0那么就踏踏实实用3.0的东西去写。如果使用2.x版本的话,可以引用一些方法等,按照需要一点点向3.0慢慢过渡。总之,任何一个框架都是需要更新的,更新肯定会有变化,那么就慢慢学吧。
我的练习源码在github上里面有我写的一些注释,有兴趣的也可以看一看https://github.com/Mstian/Vue3.0-test
文中如有错误,还望不吝指出,谢谢。
参考文章:
vue 3.0 初体验 (项目搭建)
简明扼要聊聊 Vue3.0 的 Composition API 是啥东东!
VUE 3.0 学习探索入门系列 - Vue3.x 生命周期 和 Composition API 核心语法理解(6)
聊聊vue3.0新特性:compositon api 用法和注意事项
Vue源码系列(二):错误处理
偶然发现一些比较不错的资料:
https://www.yuque.com/vueconf/2019