Vue学习记录之基础篇——vue3基础之破坏式更新

68 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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>