相关库版本要求

构建

npm i -g @vue/cli

vue create <Project Name> // 选择安装配置后完成

// 或者采用可视化构建方式

vue ui

迁移

将所有库更新到支持 Vue3 的版本后,执行 npm i 安装依赖,接下需要手动更改各项 API 调用以及插件的安装方式。

全局 API 的更换

入口文件 main.js :

// Vue2.x,vue实例方式为使用 Vue 构造函数实例化

import Vue from 'vue'
import App from './App.vue'
import router from '@/router'

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
// Vue3.0,调用 createApp 方法创建实例

import { createApp } from "vue"; // 从vue模块引入的不再是一个构造函数,而是一个对象
import App from './App.vue'
import router from "@/router";

const app = createApp(App);

app
  .use(router)
  .mount("#app");

路由 @/router/index

// 结合 Vue2.x 版本的 router 创建方式是基于构造函数 Router 的实例化,并

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router) // 需要调用 构造函数Vue的静态方法use安装一次,将router提供的方法和属性挂载到 Vue 原型上
const router = new Router({
 ...
})
// Vue3.0,同样类似的方式,vue-router 模块暴露出来的为一个对象而不再是一个构造函数,需要调用里面的 createRouter 方法创建 router

import { createRouter } from "vue-router";

// 无需再调用Vue.use 安装插件

const router = createRouter({
routes:[...]
})

Vuex @/store/index.js

// Vue2.x 安装方式

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
const store =  new Vuex.Store({
...
})
// Vue3.0 安装方式

import { createStore } from "vuex";

const store = createStore({
...
});

Vue.prototype => app.config.globalProperties

Vue2.x 最常用的全局变量设置是通过在构造函数 Vue 的原型上挂上常用的属性和方法

let someProps = 'some things'
Vue.prototype.$someProps = someProps

// 组件中访问

console.log(this.$someProps); // 'some things'

// 通过原型访问

import Vue from 'vue'

console.log(Vue.prototype.$someProps); // 'some things'

对比 Vue3.0 使用 app.config.globalProperties

main.js

import { createApp } from 'vue'

const app = createApp()
let someProps = 'some things'
app.config.globalPropertis.$someProps =someProps

...

export defaul app; // 需要将 app 实例暴露出去
// 通过调用 API 获取当前实例访问全局 property

import { getCurrentInstance } from "vue";

const currentInstance = getCurrentInstance(); //获取当前实例

console.log(currentInstance.appContext.config.globalProperties)

// 组件中通过 this 访问 全局 property

// SomeComp.vue

console.log(this.$someProps) // 'some things'

// 通过引入实例访问

import app from '@/main.js'

console.log(app.config.globalProperties.$someProps); // 'some things'

注意: 通过引入实例访问是需要与项目的 app 根实例建立联系,而并非重新创建 app 实例,因此在 main.js 入口文件处要将 app 对外暴露。

Provide / Inject

跨层级组件传值一般不必用全局属性时,可以考虑到 provide / inject

// Provide.vue

import { provide } from "vue";

setup() {
  let str = "provide str";
  provide("str", str);
}
// Inject.vue

import { inject } from "vue";

setup() {
   let injectStr = inject("str");
   console.log(injectStr); // 'provide str'
 }

组件注册

// Vue2.x 注册方式

import Vue from 'vue'
import someComp from './someComp.vue'

Vue.component('SomeComp',someComp)
//对比 Vue3.x 注册
import { createApp } from 'vue'
import someComp from './someComp.vue'

const app = createApp()

app.component('SomeComp',someComp)

自定义插件安装

// somePlugin.js

export default {
 install(Vue,config){

  ...

 Vue.prototype.$pluginHandler = ()=>{...}
 }
}


// main.js

import Vue from 'vue'
import somePlugin from './somePlugin.js'

Vue.use(somePlugin)

对比 Vue3.0

// somePlugin.js

export default {
 install(app){
  //通过参数 app 传递实例的引用

  ...

  app.config.globalProperties.$pluginHandler = ()=>{...}
 }
}


// main.js

import { createApp } from 'vue
import somePlugin from './somePlugin.js'

const app = createApp()

app.use(somePlugin)

小结

全局 API 组织方式的重构(由从原型上共享改为以模块暴露)的好处:

  1. 结合 Treeshaking 和便于打包工具确定依赖关系,将无用的内容去除,按需输入,得到体积更小的生产包。
  2. 在需要不同的独立的 Vue 实例场景下,以在原型上操作的方式,依然会使不同实例之间内容产生联系,因此取代的新方式将实现这种隔离。

新特性

组合式 API setup

Vue2.x 组件结构可能会类似如下:

// src/components/SomeComp.vue

export default {
  data:()=>({
   res1:[],
   res2:[],
   res3:[]
  }) ,
  methods: {
   handler1(){
    // ...
    // 处理得到数据并赋值给 res1
   },
   handler2(){
    // ...
    // 处理得到数据并赋值给 res2
   },
   handler3(){
    // ...
    // 处理得到数据并赋值给 res3
   }
  },
}

官方给图:

Alt text

Vue3.0 使用 setup 将相关逻辑组织在一块,便于解读、维护。

export default {

 setup(props,context){

  // ----------业务逻辑1------------
  const res1 = ref([])
  const handler1 = ()=>{
   // ...
   // 处理得到数据并赋值给 res1
  }

  // ----------业务逻辑2------------
  const res2 = ref([])
  const handler2 = ()=>{
   // ...
   // 处理得到数据并赋值给 res2
  }

  // ----------业务逻辑3------------
  const res3 = ref([])
  const handler3 = ()=>{
   // ...
   // 处理得到数据并赋值给 res3
  }

  // -------将属性暴露给组件---------

  return {
   res1,
   handler1,
   res2,
   handler2,
   res3,
   handler3
  }
 }
}

setup 中注册 生命周期钩子

可将需要在某个生命周期执行的方法,在对应的钩子注册方法通过回调传入,届时会在对应的生命周期所触发。

import { onBeforeMount, onMounted } from 'vue'

export default {
  setup(){

    onBeforeMount(()=>{
     conosle.log('onBeforeMount')
    })

    onMounted(()=>{
     conosle.log('onMounted1')
    })

    onMounted(()=>{
     conosle.log('onMounted2')
    })
  }
}

watchcomputed 新的用法

import { watch , computed } from 'vue'

export default {
  props: {
    someProp: {
      default: () => 0,
      type: Number,
    },
  },
  setup(props) {
    const { someProp } = toRefs(props);

    watch(someProp, (newValue) => {
      console.log(newValue);
    });

    let computedVal = computed(() => someProp.value * 10);

    return {
      computedVal,
    };
  }
};

响应性 API

  • reactive 以引用类型的数据创建一个响应式对象,即以组件 data() 返回的对象创建响应式的原理。
  • ref 以基础类型的数据创建响应式对象,并会将原始值包裹在一个对象的 value 属性下。(测试发现其实也可以根据引用类型创建,但响应性不高)
  • toRefs 将响应式对象转换为普通对象(方便解构获取值),并且结果中的每个 property 保持对响应式对象的指向。若不用该方法转换,取到的值将不具有响应性,也不为 ref
import { reactive, ref } from "vue";

export default {

  setup() {

    // let num = 0; // 视图不更新
    let num = ref(0); // 视图更新

    setInterval(() =>  {num.value++ }, 500);

    // let obj = { num: 0 }; // 视图不更新
    let obj = reactive({ num: 0 }); // 视图更新

    setInterval(() => {
      obj.num++;
      console.log(obj); // 数据更新
    }, 500);

    // let { time } = foo; // 视图不更新
    let { time } = toRefs(foo);

    setInterval(() => {
      time.value = Date.now();
      console.log(time.value === foo.time); // true
    }, 1000);

    return {
      num,
      obj
    };

  }
};

异步更改响应式对象进行视图更新时,需要注意:将 已暴露的属性 指向 新的响应式对象 是无效的操作,要响应则需要直接对 在 setup 阶段暴露的响应式对象 进行操作。

import { reactive, ref } from "vue";

export default{
  setup(){
    let arr = reactive([])

    setInterval(()=>{
      // arr = [1,2,3] // 不更新
      // arr = reactive([1,2,3]) // 不更新
      arr.push(1,2,3) // 更新
    },1000)


    // 或者外面包一层,并在该对象在属性节点上进行重新指向的方式

    let obj = reactive({
      arr:[]
    })

    setInterval(()=>{

      obj.arr = [1,2,3] // 更新

    },1000)

    // 但在第二种方式下,以结构的方式访问属性虽然没有问题,但勿在解构赋予的变量上做相同的错误操作。例如:

    let { arr } = obj

    setInterval(()=>{

      arr = reactive([1,2,3]) // 模板上的{{ arr }}是obj.arr,此时指向了新的响应式对象,因此不会更新

    },1000)

    export {
      arr,
      obj
    }

  }
}

丑哭
6 声望0 粉丝

好好学习 天天上当