Skip to content

Vue2 升级 Vue3 注意点

挂载全局对象和方法

Vue3 已经不支持直接 Vue.prototype.$xxx = xxx 这种方式来挂载全局对象,这是由于 globalVue 不再是构造函数,因此不再支持该构造函数。

可以通过 config.globalProperties 进行全局挂载。

js
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
// Vue3全局挂载名称
app.config.globalProperties.$vueName = "Vue3全局挂载名称";
app.mount("#app");
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
// Vue3全局挂载名称
app.config.globalProperties.$vueName = "Vue3全局挂载名称";
app.mount("#app");
js
import { defineComponent, getCurrentInstance } from "vue";
export default defineComponent({
  setup() {
    // 获取全局挂载的实例
    const { proxy } = getCurrentInstance();
    console.log(proxy.$vueName);
    return {};
  },
});
import { defineComponent, getCurrentInstance } from "vue";
export default defineComponent({
  setup() {
    // 获取全局挂载的实例
    const { proxy } = getCurrentInstance();
    console.log(proxy.$vueName);
    return {};
  },
});

事件总线问题

Vue3 中不可以使用 new Vue() 的方式来使用事件总线。

主要原因是:

方案 1: 使用第三方事件总线。

vue3-eventbus, mitt

方案 2: 自己实现一个简单的事件总线。

js
export default class EventBus {
  constructor() {
    this.events = {};
  }
  emit(eventName, data) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(function (fn) {
        fn(data);
      });
    }
  }
  on(eventName, fn) {
    this.events[eventName] = this.events[eventName] || [];
    this.events[eventName].push(fn);
  }

  off(eventName, fn) {
    if (this.events[eventName]) {
      for (var i = 0; i < this.events[eventName].length; i++) {
        if (this.events[eventName][i] === fn) {
          this.events[eventName].splice(i, 1);
          break;
        }
      }
    }
  }
}
export default class EventBus {
  constructor() {
    this.events = {};
  }
  emit(eventName, data) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(function (fn) {
        fn(data);
      });
    }
  }
  on(eventName, fn) {
    this.events[eventName] = this.events[eventName] || [];
    this.events[eventName].push(fn);
  }

  off(eventName, fn) {
    if (this.events[eventName]) {
      for (var i = 0; i < this.events[eventName].length; i++) {
        if (this.events[eventName][i] === fn) {
          this.events[eventName].splice(i, 1);
          break;
        }
      }
    }
  }
}

装饰器替代方案

使用 https://facing-dev.github.io/vue-facing-decorator/#/zh-cn/readme

js
import { Component, Vue } from "vue-facing-decorator";
@Component
export default class MyComponent extends Vue {
  // 这是一个vue响应式属性
  text = "Example code";

  // 这是一个vue组件方法
  method() {
    console.log(this.text);
  }

  // 这是一个vue生命周期钩子
  mounted() {
    this.method();
  }
}
import { Component, Vue } from "vue-facing-decorator";
@Component
export default class MyComponent extends Vue {
  // 这是一个vue响应式属性
  text = "Example code";

  // 这是一个vue组件方法
  method() {
    console.log(this.text);
  }

  // 这是一个vue生命周期钩子
  mounted() {
    this.method();
  }
}

vuex-class

目前业界没有代替方案,手动改成原始写法,或者自己实现装饰器。

一个思路:

vuex-class 使用了 vue-class-component 的自定义装饰器实现,具体可查看源码:

https://github.com/ktsn/vuex-class/blob/master/src/bindings.ts

js
import { createDecorator } from "vue-class-component";
import { createDecorator } from "vue-class-component";

其实 vue-facing-decorator 也提供了自定义装饰器,但只在 vue-facing-decorator 2.x 文档中看到 3-beta 中消失了。

https://facing-dev.github.io/vue-facing-decorator/#/en/custom/custom

ts
import { createDecorator, Component, Vue } from "vue-facing-decorator";

function Log(prefix: string) {
  return createDecorator(function (options, key) {
    const old = options.methods?.[key];
    if (!old) {
      throw "not found";
    }
    options.methods[key] = function (...args: any[]) {
      old.apply(this, args);
    };
  });
}

@Component
export default class Comp extends Vue {
  @Log("prefix")
  method() {}
}
import { createDecorator, Component, Vue } from "vue-facing-decorator";

function Log(prefix: string) {
  return createDecorator(function (options, key) {
    const old = options.methods?.[key];
    if (!old) {
      throw "not found";
    }
    options.methods[key] = function (...args: any[]) {
      old.apply(this, args);
    };
  });
}

@Component
export default class Comp extends Vue {
  @Log("prefix")
  method() {}
}

props 不规范用法导致的报错

在升级项目的时候,发现项目中有对 props 中的内容做修改的代码,如:

js
this.xxx = xx; // xxx 是个 prop
this.xxx = xx; // xxx 是个 prop

这种方式在 Vue2 中不会引发 crash,但在 Vue3 中会。

解决方案:移除不规范的用法。

路由 params 传递对象的不规范用法导致报错

旧版的路由可以将一个 Object 作为 params 传递,新版路由将无法使用这种方式,必须是基础类型。

解决方案:移除不规范的用法。

i18n 问题

项目中有一些老旧的写法,最多的问题是:

js
$t("xxx", { 0: "x", 1: "xx" }); // 这种写法在新的 i18n 中报错
$t("xxx", { 0: "x", 1: "xx" }); // 这种写法在新的 i18n 中报错

需要改成:

js
$t("xxx", ["x", "xx"]);
$t("xxx", ["x", "xx"]);

TIP

如果不想一个个改,可以使用全局 t 函数替换法。

echarts 等类库异常

本身 echarts 部分应该是和 Vue 无关的,但是我们发现 Vue3proxy 包装会影响 echarts 实例的部分功能。

这部分问题应该是 Vue3 的响应式机制引起的,解决方式是 echarts 实例不要使用响应式对象。

element 样式问题

这个是我们在项目中花最多时间去处理的问题,因为我们项目对 element-ui 进行了比较高度的定制化,升级到 element-plus 之后,由于 class 的结构发生了变化,很多页面发生了样式错乱。

解决方案:暂无自动化方案,需要人工介入。

element 类型问题

针对 TypeScript 的项目,element-ui 中有个 types 目录可以提供类型,但升级 element-plus 之后,这个目录没有了。

解决方案:直接从 element-plus 引入,然后通过 TypeScript 的工具类型转换成需要的类型。

ts
import { ElSelect } from "element-plus";
type ElSelectInstanceType = InstanceType<typeof ElSelect>;
import { ElSelect } from "element-plus";
type ElSelectInstanceType = InstanceType<typeof ElSelect>;

TypeScript 工具类型参考

element 时间选择器问题

  1. 选择器的默认时间需要从 string 类型改为 Date 类型
  2. 选择器的 option 需要使用新的书写方式,具体看官方文档。

element popover 手动触发问题

项目中好多 popover 使用了手动触发模式,这个模式在 plus 中需要加 trigger 去掉。然后将 v-model 修改为 visible

element 图标库的不兼容

plus 中使用了 svg 作为图标,和之前 font 方式区别较大,如果为了兼容,可以引入之前的 font-face

最后编辑时间:

Version 4.0 (framework-1.0.0-rc.20)