开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
vue2 -- vue3 的升级 可以拆分成几个方面,我们可以从这几点开分析, 响应式、DIFF、语法(API)、新增特性、生态, 下面咱们就先逐一查看他都更新了哪些东西, 不过有一些是 破坏式更新,咱们可以留到最后一起搞他
vue3 的升级在原有基础 api 兼容的一部分,还有一部分是不兼容的,也就是原有的一节 api 是不能使用的, 这一点比较重要,特别是从 vue2 研发了多年 迁移到 vue3 的开发人员,更要关注这一点,下面咱们就从以下几个方面 分析一下都有哪些是不能复用的 api
全局 API
- vue2 中的全局配置 vue2 中我们可以使用 实例 Vue 来配置全局组件,或者指令 如
Vue.component('button-counter', {
data: () => ({ count: 0 }),
template: '<button @click="count++">Clicked {{ count }} times.</button>'
})
Vue.directive('focus',
{ inserted: (el) => el.focus() })
// 再如 mixin api , 这种会影响到所有的根实例
Vue.minxn({ })
vue2 的实例 实际上是利用 Vue 这个实例对象来进行挂载
- vue3 的 creatApp 调用 createApp 返回一个应用实例, 在上面 vue 实例上的全局 api 都已经转移到了 app 上
import { createApp } from 'vue'
const app = createApp({})
移除了 Vue.config.productionTip
移除了 Vue.extend
| 在 vue2 中 我们 使用 Vue.extend
进行 TypeScript 类型推断 , 在vue3 中可以使用 defineComponent
- app.use 替代 vue.use
- Provide / Inject
// 在入口中
app.provide('guide', 'Vue 3 Guide')
// 在子组件中
export default {
inject: {
book: {
from: 'guide'
}
},
template: `<div>{{ book }}</div>`
}
我们都是在 入口文件 main.js 中 进行 vue 的配置, 或者 main.ts 中, 下面引用一个项目中完整的入口配置项,以供大家参考
import { createApp } from 'vue';
import { init } from '@okay/monitor-browser';
import { vuePlugin } from '@okay/monitor-vue';
import App from './App.vue';
import router from './router';
import { createPinia } from 'pinia';
import '@/components/common/theme/common.less';
import './index.less';
const app = createApp(App);
window.monitorIns = init({
platForm: {
name: '课',
detail: '课-首页'
},
vue: app,
environment: ENV === 'prod' ? 'online' : 'test',
}, [vuePlugin]);
app.directive('click-outside', {
mounted(el: any, bindings: any) {
el.handler = function (e: any) {
if (!el.contains(e.target)) {
bindings?.value(e)
}
}
document.addEventListener('click', el.handler, true)
},
unmounted(el: any) {
document.removeEventListener('click', el.handler, true)
}
})
const pinia = createPinia();
app.use(router);
app.use(pinia);
app.mount('#app');
模版指令
- v-model
使用这个指令一般是在双向绑定的时候,在 input 这种带 value 的标签中,可以直接绑定值,那么这个值可以是我们的 ref, 实现响应式
在我们封装组件的时候,bind 可以直接使用 在 : 简写 进行绑定, 子组件通过 props 接收, 也是响应式的, 如下面的代码中的
readonly
这一点是没有变化的
<template>
<div>
<input v-model = "name" :readonly="inputNameReadonly" />
</div>
</template>
<script>
import { defineComponent,ref } from 'vue'
export default defineComponent({
setup(){
const name = ref('')
return {
name
}
}
})
</script>
- key
vue2 在template
中使用 for 的时候,key需要使用在子节点中, vue3 中可以直接使用在template
中
<template v-for="item in list" :key="item.id">
<div>...</div>
</template>
- v-if 与 v-for
同一元素,在 vue2 总 for 的优先级会高于 if, 而在 vue3 中 if 的优先级高于 for 我们在写代码的时候,应该尽量避免 if 和 for 同时使用在同一节点上的情况,可以使用template
- v-on的native 已移除
组件
vue2 中的函数式组件 可以让提升性能,分离组件状态, vue3的有状态组件已经得到了质的提升
- 异步组件 通过将组件定义为返回 Promise 的函数来创建, 下面是我自己写的一个 demo 里面包含了 函数 组件 和异步 组件
<template>
<component :is="asyncComp"/>
<AsyncCompA />
</template>
<script>
import { shallowRef } from 'vue'
// 重试机制的实现
function fetch(){
return new Promise((res,rej)=>{
// 请求会在 1s 以后 失败
setTimeout(()=>{
rej('err')
},1000)
})
}
// load 函数接收一个 onError 回调函数
function load(onError){
// 请求接口,得到 Promise 实例
const p = fetch()
// 捕获错误
return p.catch((err)=>{
// 当错误发生时,返回一个新的 Promise 实例, 并调用 onError 回调, 同时将 retry 函数作为 onError 回调的参数
return new Promise((res,rej)=>{
// retry 函数, 用来执行重试的函数,执行该函数会重新调用 load 函数 并发起请求
const retry = () => res(load(onError))
const fail = () => rej(err)
onError(retry,fail)
})
})
}
// defineAsyncComponet 函数用于定义一个 异步组件, 接收一个异步组件加载器作为参数
function defineAsyncComponet(options){
// options 可以是配置项(对象),也可以是加载器 ()=>import('a') 函数
if(typeof options === 'function'){
// 如果 options 是加载器, 则将其格式化为配置项
options ={
loader:options,
}
}
const { loader } = options
// 一个变量, 用来存储异步加载的组件
let InnerComp = null
// 记录重试次数
let retries = 0
// 封装 load 函数用来加载异步组件
function load(){
return loader()
.catch((err)=>{
// 如果用户指定了 onError 回调, 则将该控制权交给用户
if(options.onError){
// 返回一个新的 Promise
return new Promise((res,rej)=>{
// 重试
const retry = () => {
res(load())
retries++
}
// 失败
const fail = () => rej(err)
// 作为 onError 回调函数的参数, 让用户来决定下一步怎么做
options.onError(retry,fail,retries)
})
}else{throw error}
})
}
// 返回一个包装组件
return{
name:'AsnycComponentWrapper',
setup(){
// 异步组件是否加载成功
const loaded = ref(false)
// 定义 error , 当错误发生时,用来存储错误对象
const error = shallowRef(null)
// 一个标志,代表是否正在加载,默认为 false
const loading = ref(false)
let loadingTimer = null
//如果配置中 存在 delay, 则开启一个定时器计时,当延迟到时后,将 loading.value 设置为 true
if(options.delay){
loadingTimer = setTimeout(()=>{
loading.value = true
},options.delay)
}else{
// 如果配置项中没有 delay, 则直接标记为 加载中
loading.value = true
}
// 调用 load 加载组件
// 加载成功以后,将加载成功的组件赋值给 InnerComp, 并将 loaded 标记为 true, 代表加载成功
load().then(c=>{
InnerComp = c
loaded.value = true
}).catch((err)=>error.value = err)
.finally(()=>{
loading.value = false,
// 加载完毕,无论成功与否, 都要清除延时定时器
clearTimeout(loadingTimer)
})
let timer = null
if(options.timeout){
// 如果指定了超时时长, 则开启一个定时器计时
timer = setTimeout(()=>{
// 超时后 创建一个错误对象,并复制给 error.value
const err = new Error(`Async Component timed out after ${options.timeout}ms`)
error.value = err
},options.timeout)
}
// 包装组件被卸载时, 清楚定时器
onUnmounted(()=>clearTimeout(timer))
// 占位内容
const placeholder = { type:Text, children:''}
return () => {
if(loaded.value){
// 如果组件异步加载成功,则渲染被加载的组件
return { type:InnerComp }
} else if(error.value && options.errorComponent){
// 只有当错误存在,并且用户指定了 Error 组件, 则渲染该组件, 同时将 errpr 作为 props 传递
return { type: options.errorComponent, props:{ error:error.value} }
}else if(loading.value && options.loadingComponent){
// 如果异步加载组件正在加载,并且用户指定了 loading 组件, 则渲染 loading 组件
return { type:options.loadingComponent}
}else{
// 否则渲染一个占位内容
return placeholder
}
}
}
}
}
export default defineComponent({
components:{
// 使用 defineAsyncComponet 定义一个异步组件, 它接受一个加载器作为参数
AsyncCompA:defineAsyncComponet({
loader:()=>import('a'), // 动态导入异步组件 import 语法 返回一个 promise
timeout:2000, // 超时时长 单位 ms
errorComponent: MyErrorComp ,//指定出错时要渲染的组件
delay: 200, // 延迟200ms 展示 loading 组件
loadingComponent:{
setup(){
return () => {
return {type:'h2',children:'Loading...'}
}
}
}
})
},
setup(){
const asyncComp = shallowRef(null)
// 异步加载组件
import('a.vue').then(a=>asyncComp.value = a)
return{
asyncComp,
AsyncCompA
}
}
})
</script>
移除的 apis
- vue3 中移除了部分 api ,因为我本身写vue2的时间很短,所以对 vue2 的了解还不是非常透彻,想了解的话,大家可以通过连接 直接访问 v3-migration.vuejs.org/zh/breaking…
vue-router
keep-alive
- vue2 中 使用
keep-alive
的方式 在 我们 vue3 的项目使用vue-router
V4 版本的时候已经不能再用了, 是破坏性的更新, vue3 中我们可以这样
<template>
<div class="app">
<topNav />
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { OkConfigProvider } from '@okay/okui';
import zhCn from '@okay/okui/lib/locale/lang/zh-cn';
import topNav from '@/components/session/top_nav/index.vue';
export default defineComponent({
components: {
topNav,
[OkConfigProvider.name]: OkConfigProvider,
},
setup() {
const locale = ref(zhCn);
return {
locale,
};
},
});
</script>
<style lang="less" scoped>
.app {
min-width: 440px;
}
</style>