原文: 本人掘金文章
关注公众号: 微信搜索 前端工具人
; 收货更多的干货
一、 官方中文文档链接
https://vue3js.cn/docs/zh/
二、开篇
- vue3.0 从去年预热到9月18号晚上,已正式发布
vue3.0 beta
版本; beta
版意味着vue3.0
开业正式投入到项目中了;大家可以开心的学习了(前端新技术你继续出,我还学得动...因为要生活要吃饭!!!);- 前端技术生态一直不断的更新换代,许多人都觉得亚历山大,学不动了;但不是学不动就可以不学了么。。。
0.0
;学不动也得学,不然适应自己的只会是淘汰
三、vue2.0 项目的建议
引用官方文档作者的话:
提示:
我们仍在开发 Vue 3 的专用迁移版本,该版本的行为与 Vue 2 兼容,运行时警告不兼容。如果你计划迁移一个非常重要的 Vue 2 应用程序,我们强烈建议你等待迁移版本完成以获得更流畅的体验
。
目前作者的意思是:对于 vue2.0
的项目强烈不建议
升到 vue3.0
;因为目前的beta
版本以及现有的框架及插件,不是很支持和兼容vue3.0
语法; 所以肯定有很多预想不到的问题;对于跃跃欲试升级vue3.0
的小伙伴们,只能等待官方的兼容版本开发完,在做迁移; 毕竟线上项目不是开玩笑的,出了一个bug
都可能是重大损失。 这个锅家里没矿的基本背不动...
四、介绍
Vue3带来些什么? 参考至: 公众号:前端早读课文章
详细文档请参考: 官方中文文档链接
- 更快
- 重构了
Virtual DOM
- 标记静态内容,并区分动态内容
- 更新时只
diff
动态的部分
- 重构了
双向数据绑定
Object.defineProperty() --> Proxy API
Proxy
对于复杂的数据结构减少了循环递归的监听;初始渲染循环递归是非常耗性能的;Proxy
对于数组的变异方法(会修改原数组),不在需要单独用数组原生方法重写、处理- 语法也比
defineProperty
简洁多了,直接监听某个属性即可;
- 事件缓存
vue2
中,针对绑定事件,每次触发都要重新生成全新的function
去更新;Vue3
中,提供了事件缓存对象cacheHandlers
,当cacheHandlers
开启的时候,编译会自动生成一个内联函数,将其变成一个静态节点,这样当事件再次触发时,就无需重新创建函数直接调用缓存的事件回调方法即可
- 重构了
- 更小 (
Tree shaking
支持)- 简而言之: 不会把所有的都打包进来,只会打包你用到的
api
;大项目你会发现热加载、初始渲染提升了很多 - 很大程度的减少了开发中的冗余代码,提升编译速度
- 简而言之: 不会把所有的都打包进来,只会打包你用到的
- 更易于维护
Vue3
从Flow
迁移到TypeScript
- 多人协同开发的情况下,用了
TypeScript
之后的酸爽你会吐槽,为什么早不出现TypeScript
- 多人协同开发的情况下,用了
- 代码目录结构遵循monorepo
- 核心观点: 代码分割到一个个小的模块中, 开发者大部分只是工作在少数的几个文件夹,并且也只会编译自己负责的模块;而不是整个项目编译
- 新功能和特性
Composition API
- 不要在意越来越像react-hook;毕竟别人的优点是值得自己学习的;
Composition API
函数式开发,很大程度的提高组件、业务逻辑的复用性;高度解耦;提升代码质量、开发效率;减少代码体积
- 提升开发效率
vite
的支持 (当然目前来说vite
功能还不够强大和稳定, 但尤大把它作为vue3
官方构建工具,那肯定尤大会完善它的; 可自由选择webpack
还是vite
)vite
在开发环境下基于浏览器原生ES imports
开发,在生产环境下基于Rollup
打包- 快速的冷启动
- 即时的模块热更新
- 真正的按需编译
vue2.0
相信很多小伙伴都是结合webpack
开发; 但是有没有发现初期项目小的时候很爽运行、编译、热加载都很快; 项目一大... 打包、运行、改个功能热加载的时候... 我们先去上个厕所/接个水;忙的时候挺烦这环节vue3.0
结合vite
作者的介绍是不跟项目体积庞大而影响,开始啥样现在也啥样; 当然夸张是夸张了点, 但是相差应该不大;
五、 环境搭建
// 对于 Vue 3,应该 npm 上可用的 Vue CLI v4.5 作为 @vue/cli@next
yarn global add @vue/cli@next
# OR
npm install -g @vue/cli@next
// 创建项目
npm init vite-app <project-name>
# OR
yarn create vite-app <project-name>
// 下载依赖及运行项目, 已 npm 方式为例; 详细步骤官方文档的安装页都有
cd <project-name>
npm install
npm run dev // 项目就能跑起来并且访问了
拓展: 项目引入其他插件比如 vue-router4.0、vuex4.0、typescript等请参考 Vue3.0环境搭建
六、 语法介绍
上手之前应该先阅读一遍 vue3 对于 vue2 的一些变更; 详情点击 官网文档重大变更
vue2
中使用的是Options API
; vue3
中的是Composition API
简称纯函数式API
6.1、 setup
vue3
组件入口为 setup(){}
函数作为入口, 默认只执行一次;执行顺序在 beforeCreate
之后 created
之前;
...
// 使用props和this
setup (props, ctx) {
// props 组件间传递的参数;
// ctx 组件的实例的执行上下文(可以理解为 vue2 this)
/* 可执行 下面等操作:例 ctx.$emit()
attrs: Object
emit: ƒ ()
listeners: Object
parent: VueComponent
refs: Object
root: Vue
*/
// 注意 steup 中没有this了, 拿不到this
}
6.2、 生命周期
我记得早期是说 vue3
中是移除掉了 beforeCreate
、created
两个生命周期; 但是实践的时候我发现还是可以写的; 因为vue2
、 vue3
写法目前相兼容;
created () { console.log('created') }
setup (props, ctx) {
console.log('setup')
// mounted 新写法 记住一句话 所有的方式都是以函数的形式呈现
onMounted(() => {})
}
mounted () { console.log('mounted') }
// 执行顺序 setup created mounted
虽然兼容但尽量不要这样写;向前看齐嘛; 强烈推荐全部都放在steup
函数中
6.3、 reactive、ref、tofefs、isRef
创建响应式对象 reactive、ref、tofefs
用法, 对应 vue2
中的 data
推荐写法3
// 写法一:响应式数据一多, return 要很多次; 使用数据的时候要通过state拿到
<template>
<div>
<p>{{state.count}}</p>
</div>
</template>
import {reactive} from 'vue'
...
setup(props, ctx) {
const state = reactive({
count: 0
})
return { state }
}
// 写法二
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive} from 'vue'
...
setup(props, ctx) {
const state = reactive({
count: 0
})
return {
count: state.count
}
}
// 写法三:推荐 通过 toRefs 代理对象, 再通过解构的方式取值
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs} from 'vue'
...
setup(props, ctx) {
const state = reactive({
count: 0
})
return {
...toRefs(state)
}
}
// 写法四:通过 ref() 函数包装, 返回值是一个对象,对象上只包含一个 value 属性, 就是要的属性值
<template>
<div>
<p>{{count}}</p>
<p>{{count1}}</p>
</div>
</template>
import {reactive, toRefs, ref} from 'vue'
...
setup(props, ctx) {
// 父组件传递count属性
// 写法1
const count = ref(props.count)
console.log(count.value) // 对应props.count的值
// 写法2
const state = reactive({
count1: ref(props.count)
})
return {
count,
...toRefs(state)
}
}
// isRef 来判断某个值是否为 ref() 创建出来的对象
import { ref, isRef } from 'vue';
export default {
setup(props, ctx) {
const refCount = ref(0)
const count = isRef(refCount) ? refCount : 1
}
};
6.4、 computed
例子场景:结合 vue-router
根据当前路劲为count
赋值, 也扩展下vue-router
的用法
<template>
<div>
<p>{{count}}</p>
<p>{{count1}}</p>
</div>
</template>
import {reactive, toRefs, computed} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
const route = useRoute()
const state = reactive({
// 计算属性 写法1
count: computed(() => {
return route.path
})
})
// 计算属性 写法2
const count1 = computed(() => {
return route.path
})
return {
...toRefs(state),
// 计算属性不需要通过 toRefs 结构, 因为他就是一个具体的值就是响应式的
count1
}
}
6.5、watch 、watchEffect
例子场景:同 computed 一样
watchEffect
与 watch
有什么不同:
watchEffect
不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行watch
只能监听指定的属性watch
可以获取到新值与旧值,而watchEffect
不行watchEffect
在组件初始化的时候就会执行一次用以收集依赖(与computed
同理),后续收集的依赖发生变化,这个回调才会再次执行
// watch 用法 监听单个属性
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs, watch} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
const route = useRoute()
const state = reactive({
count: 0,
})
// 监听路由路劲, immediate 是否立即执行一次
watch(() => route.path, (newValue) => {
state.count = newValue
}, { immediate: true })
return {
...toRefs(state),
}
}
// watch 用法 监听ref数据源
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs, ref, watch} from 'vue'
...
setup(props, ctx) {
// 定义数据源
let count = ref(0);
// 指定要监视的数据源
watch(count, (count, prevCount) => {
console.log(count, prevCount)
})
setInterval(() => {
count.value += 2
}, 2000)
console.log(count.value)
return {
count
}
}
// watch 用法 监听多个属性
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs, watch} from 'vue'
...
setup(props, ctx) {
const state = reactive({
name: 'vue',
age: 3
})
watch(
// 监听name、 age
[() => state.name, () => state.age],
// 如果属性改变、则执行以下回调
([newName, newAge], [oldname, oldAge]) => {
console.log(oldname, oldname)
console.log(oldAge, oldAge)
},
{ lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
)
setTimeout(() => {
state.name = 'react'
state.age += 1
}, 3000)
return {
...toRefs(state),
}
}
// watchEffect 用法
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs, ref, watchEffect} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
const route = useRoute()
const state = reactive({
count: 0,
})
// 当 route.path 变化时就会执行打印, 有点类似 react-hook 的 useEffect 第二个参数效果
watchEffect(() => {
count = route.path
console.log(route.path)
})
// watchEffect、 watch 都可以主动停止监听
const stop = watchEffect(() => {
count = route.path
console.log(route.path)
})
// 在某个时机下 执行 stop() 停止watchEffect监听
if (...) { stop() }
return {
...toRefs(state),
}
}
七、 Vue3中移除的一些API和方法
7.1 取消KeyboardEvent.keyCode
在Vue2.x
中,绑定键盘事件会用到如下代码:
<!-- keyCode version -->
<input v-on:keyup.13="submit" />
<!-- alias version -->
<input v-on:keyup.enter="submit" />
或者是:
Vue.config.keyCodes = {
f1: 112
}
<!-- keyCode version -->
<input v-on:keyup.112="showHelpText" />
<!-- custom alias version -->
<input v-on:keyup.f1="showHelpText" />
在事件中,给keyup
配置一个指定按钮的keyCode
(数字)在Vue3
中将不会生效,但是依然可以使用别名,例如:
<input v-on:keyup.delete="confirmDelete" />
7.2 移除 $on,$off 和 $once方法
在Vue2.x
中可以通过EventBus
的方法来实现组件通信:
var EventBus = new Vue()
Vue.prototype.$EventBus = EventBus
...
this.$EventBus.$on() this.$EventBus.$emit()
这种用法在Vue3
中就不行了,在Vue3
中移除了 $on,$off
等方法(参考rfc),而是推荐使用mitt
方案来代替:
import mitt from 'mitt'
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// fire an event
emitter.emit('foo', { a: 'b' })
7.3 移除filters
在Vue3
中,移除了组件的filters
项,可以使用methods
的或者computed
来进行替代:
<template>
<p>{{ accountBalance | currencyUSD }}</p>
</template>
<script>
export default {
filters: {
currencyUSD(value) {
return '$' + value
}
}
}
</script>
替换为:
<template>
<p>{{ accountInUSD }}</p>
</template>
<script>
export default {
props: {
accountBalance: {
type: Number,
required: true
}
},
computed: {
accountInUSD() {
return '$' + this.accountBalance
}
}
}
</script>
八、Vue3中改变的API和写法
8.1 实例初始化
在vue2.x
中通过new Vue()
的方法来初始化:
import App from './App.vue'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
在vue3
中Vue
不再是一个构造函数,通过createApp
方法初始化:
import App from './App.vue'
createApp(App).use(store).mount('#app')
8.2 全局API调用方式改变
在Vue2.x
中,大部分全局API
都是通过Vue.xxx
或者Vue.abc()
方式调用,例如:
import Vue from 'vue'
Vue.mixin()
Vue.use()
而在Vue3
中,这些方式将会改变,取而代之的是如下:
import { createApp } from 'vue'
const app = createApp({})
app.mixin()
app.use()
同时,可以只引入一些需要的API
,不需要的不用引入,这样也符合Three Shaking
的要求,例如:
import { nextTick,reactive,onMounted } from 'vue'
nextTick(() => {
})
onMounted(() => {
})
由于Vue3
中全局API
都会通过app.xxx
的方法调用,所以之前通过Vue.prototype.xxx
绑定的全局方法和变量将无法使用,可以采用如下方式来代替:
//在main.js中:
app.config.globalProperties.http = function(){}
//在vue组件中:
this.http()
8.3 render方法修改
在Vue2.x
中,有时会自定义render
方法来返回模板内容,如下:
export default {
render(h) {
return h('div')
}
}
在Vue3
中,h
通过vue
来引入,如下:
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
8.4 新的异步组件创建方式
在Vue2.x
中,尤其是在Vue Router
中,会经常使用到异步组件,借助webpack
的打包方式,可以将一个组件的代码进行异步获取,例如:
const asyncPage = () => import('./NextPage.vue')
const asyncPage = {
component: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}
在Vue3
中,提供了defineAsyncComponent()
方法创建异步组件,同时可以返回一个Promise
对象来自己控制加载完成时机,如下:
import { defineAsyncComponent } from 'vue'
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
})
const asyncComponent = defineAsyncComponent(
() =>
new Promise((resolve, reject) => {
/* ... */
})
)
九、暂时性的结尾:
- 写了大半天,写的有点啰嗦,新API基本都写了几种编码方式,根据自己的爱好取舍
- 后续慢慢加入自己对一些新API认识及用法
- 本编文章部分内容参考链接有:
- 前端早读课文章: 最全的Vue3.0升级指南
- Vue中文社区文章:一篇文章上手Vue3中新增的API