8 Vue2升级Vue3

main.js

    // 之前 - Vue 2
    import Vue from 'vue'
    import App from './App'
    Vue.config.productionTip = false    // vue3 不再需要
    App.mpType = 'app'    // vue3 不再需要
    const app = new Vue({
    ...App
    })
    app.$mount()
    import App from './App'
    import { createSSRApp } from 'vue'
    // 不能修改导出的 createApp 方法名,不能修改从 Vue 中导入的 createSSRApp。
    export function createApp() {
      const app = createSSRApp(App)
      return {
          app
      }
    }

环境变量

    // 配置环境变量
    // 根目录.env文件 必须 VUE_APP_ 开头
    VUE_APP_SOME_KEY = 123
    
    // 获取环境变量
    process.env.NODE_ENV         // 应用运行的模式
    process.env.VUE_APP_SOME_KEY // 123
    
    // vue3
    // 配置环境变量
    // 根目录.env文件 必须 VITE_ 开头
    VITE_SOME_KEY = 123
    
    // 获取环境变量
    process.env.NODE_ENV          // 应用运行的模式
    import.meta.env.VITE_SOME_KEY // 123

全局属性

例如:全局网络请求

    // 之前 - Vue 2
    Vue.prototype.$http = () => {};
    
    // 之后 - Vue 3
    const app = createApp({});
    app.config.globalProperties.$http = () => {};

插件使用

例如:使用 vuex 的 store

    // 之前 - Vue 2
    import store from "./store";
    Vue.prototype.$store = store;
    
    // 之后 - Vue 3
    import store from "./store";
    const app = createApp(App);
    app.use(store);

项目根目录必需创建 index.html 文件

粘贴复制如下内容:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
        />
        <title></title>
        <!--preload-links-->
        <!--app-context-->
      </head>
      <body>
        <div id="app"><!--app-html--></div>
        <script type="module" src="/main.js"></script>
      </body>
    </html>

只支持使用 ES6 模块规范

commonJS 需改为 ES6 模块规范

模块导入

    // 之前 - Vue 2, 使用 commonJS
    var utils = require("../../../common/util.js");
    
    // 之后 - Vue 3, 只支持 ES6 模块
    import utils from "../../../common/util.js";

模块导出

    // 之前 - Vue 2, 依赖如使用 commonJS 方式导出
    module.exports.X = X;
    
    // 之后 - Vue 3, 只支持 ES6 模块
    export default { X };

vuex 用法

    // vue2
    import Vue from "vue";
    import Vuex from "vuex";
    Vue.use(Vuex);
    const store = new Vuex.Store({
      state: {},
    });
    export default store;
    
    // vue3
    import { createStore } from "vuex";
    const store = createStore({
      state: {},
    });
    export default store;

补充(现代做法):Vue3 + uni-app 项目目前更推荐用 Pinia 取代 Vuex。HBuilderX 较新版本和 Vite 模板已内置 Pinia 支持,写法更简洁、对 TypeScript 友好、无需 mutations。示例:import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ token: '' }), actions: { setToken(t){ this.token = t } } }),入口 app.use(createPinia())。Vuex 仍可用,但官方已将其置于维护状态。

生命周期的适配

在 Vue3 中组件卸载的生命周期被重新命名

  • destroyed 修改为 unmounted
  • beforeDestroy 修改为 beforeUnmount

事件的适配

Vue3 现在提供了一个emits选项,类似于现有props选项。此选项可用于定义组件可以向其父对象发出的事件, 更多 (opens new window)

强烈建议使用emits记录每个组件发出的所有事件。

这一点特别重要,因为去除了.native修饰符。emits 现在在未使用声明的事件的所有侦听器都将包含在组件的中$attrs,默认情况下,该侦听器将绑定到组件的根节点。

    <template>
      <button @click="onClick">OK</button>
    </template>
    <script>
      export default {
        emits: ["click"],
        methods: {
          onClick() {
            this.$emit("click", "OK");
          },
        },
      };
    </script>

Vue3 项目部分小程序端事件延迟或调用失败

可在执行事件的元素上添加 data-eventsync="true" 属性以解决此问题,如:

    <template>
      <button @click="onClick" data-eventsync="true">OK</button>
    </template>

v-model 的适配

Vue3 的 v-model 相对 Vue2 来说 ,有了较大的改变。可以使用多 model,相应语法也有变化。更多 (opens new window)

修改 modelValue

用于自定义组件时,Vue3 v-model prop 和事件默认名称已更改 props.value 修改为 props.modelValue ,event.value 修改为 update:modelValue

    export default {
      props: {
        // value:String,
        // 替换 value 为 modelValue
        modelValue: String,
      },
    };

事件返回

将之前的 this.$emit('input') 修改为 this.$emit('update:modelValue') ,vue3 中将省略这一步骤

自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:

    <ChildComponent v-model="pageTitle" />
    
    <!-- 是以下的简写: -->
    
    <ChildComponent
      :modelValue="pageTitle"
      @update:modelValue="pageTitle = $event"
    />

若需要更改 model 名称,作为组件内 model 选项的替代,现在我们可以将一个 argument 传递给 v-model:

    <ChildComponent v-model:title="pageTitle" />
    
    <!-- 是以下的简写: -->
    
    <ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

插槽的适配

Vue3 将不支持 slot="xxx" 的用法 ,请使用 v-slot:xxx 用法。[更多 (opens new window)](https://v3.cn.vuejs.org/guide/component- slots.html#%E5%85%B7%E5%90%8D%E6%8F%92%E6%A7%BD)

    <!--  Vue2 支持的用法 -->
    <uni-nav-bar>
      <view slot="left" class="city">
        <!-- ... -->
      </view>
    </uni-nav-bar>
    <!--  Vue3 支持的用法 -->
    <uni-nav-bar>
      <template v-slot:left>
        <view class="city">
          <!-- ... -->
        </view>
      </template>
    </uni-nav-bar>

不再支持过滤器

从 Vue 3.0 开始,过滤器已删除,不再支持,建议用方法调用或计算属性替换它们。更多 (opens new window)

API Promise 化 调用结果的方式

在 Vue3 中,处理 API Promise 化 调用结果的方式不同于 Vue2。更多 (opens new window)

  • Vue3 中,调用成功会进入 then 方法,调用失败会进入 catch 方法
  • Vue2 中,调用无论成功还是失败,都会进入 then 方法,返回数据的第一个参数是错误对象,第二个参数是返回数据

转换方法

    // Vue 2 转 Vue 3, 在 main.js 中写入以下代码即可
    function isPromise(obj) {
      return (
        !!obj &&
        (typeof obj === "object" || typeof obj === "function") &&
        typeof obj.then === "function"
      );
    }
    
    uni.addInterceptor({
      returnValue(res) {
        if (!isPromise(res)) {
          return res;
        }
        return new Promise((resolve, reject) => {
          res.then((res) => {
            if (res[0]) {
              reject(res[0]);
            } else {
              resolve(res[1]);
            }
          });
        });
      },
    });
    // Vue 3 转 Vue 2, 在 main.js 中写入以下代码即可
    function isPromise(obj) {
      return (
        !!obj &&
        (typeof obj === "object" || typeof obj === "function") &&
        typeof obj.then === "function"
      );
    }
    
    uni.addInterceptor({
      returnValue(res) {
        if (!isPromise(res)) {
          return res;
        }
        const returnValue = [undefined, undefined];
        return res
          .then((res) => {
            returnValue[1] = res;
          })
          .catch((err) => {
            returnValue[0] = err;
          })
          .then(() => returnValue);
      },
    });

生命周期钩子的组合式 API 使用方式

在 Vue3 组合式 API 中,也需要遵循 uni-app 生命周期钩子规范, 如 onLaunch 等应用生命周期仅可在 App.vue 中监听,使用中请注意生命周期钩子的适用范围。查看全部生命周期钩子 (opens new window)

只能在 <script setup> 单文件语法糖或 setup() 方法中使用生命周期钩子,以 A 页面跳转 B 页面传递参数为例:

    // 从 A 页面跳转 B 页面时传递参数 ?id=1&name=uniapp,xxx 为跳转的页面路径
    //uni.navigateTo({
    //  url: 'xxx?id=1&name=uniapp'
    //})
    
    // 方法一:在 B 页面 <script setup> 中
    <script setup>
      import {
        onLoad,
        onShow
      } from "@dcloudio/uni-app";
    
      // onLoad 接受 A 页面传递的参数
      onLoad((option) => {
        console.log("B 页面 onLoad:", option); //B 页面 onLoad: {id: '1', name: 'uniapp'}
      });
    
      onShow(() => {
        console.log("B 页面 onShow");
      });
    </script>
    // 方法二:在 B 页面 setup() 中
    <script>
      import {
        onLoad,
        onShow,
      } from "@dcloudio/uni-app";
    
      export default {
        setup() {
          // onLoad 接受 A 页面传递的参数
          onLoad((option) => {
            console.log("B 页面 onLoad:", option); //B 页面 onLoad: {id: '1', name: 'uniapp'}
          });
    
          onShow(() => {
            console.log("B 页面 onShow");
          });
        }
      }
    </script>

$mp调整为 $scope

在 Vue3 中,this 对象下的 $mp 调整为 $scope

在 nvue 使用 Vuex

在 Vue3 中,如果 nvue 使用了 Vuex 的相关 API,需要在 main.js 的 createApp 的返回值中 return 一下 Vuex 示例:

    import Vuex from "vuex";
    export function createApp() {
      const app = createSSRApp(App);
      app.use(store);
      return {
        app,
        Vuex, // 如果 nvue 使用 vuex 的各种map工具方法时,必须 return Vuex
      };
    }

需主动开启 sourcemap

App,小程序端源码调试,需要在 vite.config.js 中主动开启 sourcemap

    import { defineConfig } from "vite";
    import uni from "@dcloudio/vite-plugin-uni";
    
    /**
     * @type {import('vite').UserConfig}
     */
    
    export default defineConfig({
      build: {
        sourcemap: true,
      },
    
      plugins: [uni()],
    });

小程序平台中监听原生的点击事

在 vue3 的小程序平台中,监听原生的点击事件可以先使用 tap。 在 vue3 中,移除了.native 修饰符,所以编译器无法预知 click 是要触发原生事件,还是组件的自定义事件,故并未转换成小程序的 tap 事件。

vue3 支持的手机版本最低到多少

vue3 支持的范围是:Android > 4.4(具体因系统 webview 版本而异,原生安卓系统升级过系统 webview 一般 5.0 即可,国产安卓系统未使用 x5 内核时一般需 7.0 以上), ios >= 10

Android < 4.4,配置 X5 内核支持,首次需要联网下载,可以配置下载 X5 内核成功后启动应用,详情 (opens new window)

vue3 nvue 暂不支持 recycle-list 组件

vue3 nvue 暂不支持 recycle-list 组件

h5 平台发行时,会默认启动摇树

vue3 在 h5 平台发行时,为了优化包体积大小,会默认启动摇树,仅打包明确使用的 api, 如果要关闭摇树,可以在 manifest.json 中配置:

    "h5": {
        "optimization": {
            "treeShaking": {
                "enable": false
            }
        }
    }

通过 props 来获取页面参数

vue3 全平台新增:通过 props 来获取页面参数的使用方式

    <script setup>
      // 页面可以通过定义 props 来直接接收 url 传入的参数
      // 如:uni.navigateTo({ url: '/pages/index/index?id=10' })
      const props = defineProps({
        id: String,
      });
      console.log("id=" + props.id); // id=10
    </script>
    <script>
      // 页面可以通过定义 props 来直接接收 url 传入的参数
      // 如:uni.navigateTo({ url: '/pages/index/index?id=10' })
      export default {
        props: {
          id: {
            type: String,
          },
        },
        setup(props) {
          console.log("id=" + props.id); // id=10
        },
      };
    </script>
Last Updated:
Contributors: leeguooooo