Vue.js 3.0 在北京时间 2020 年 9 月 19 日凌晨,终于发布了 3.0 版本,代号:One Piece。

查看 vue 3.0 文档 后,总结记录一下从 vue 2 迁移到 vue 3 所需的一些更改,方便日后对比学习新版本。

与 vue 2.x 比较,将 vue 3.0 中的改动分类为 3 种:

  1. new:新增部分;
  2. breaking: 具有破坏性的部分。对 vue 2.x 中的对应功能的语法、语义等做出了调整;
  3. removed:从 Vue 3.0 中删除,不再受支持。

下面主要记录 vue 3.0 中的写法。

一、 new

1. 异步组件

新增了 defineAsyncComponent 方法,用于显式地定义异步组件。

import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

// 不带选项的异步组件
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))

// 带选项的异步组件
const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})
2. 定制内置元素

针对 DOM 模板,使用 v-is 来动态渲染组件:

<tr v-is="'blog-post-row'"></tr>
3. 片段

在 vue 3 中,组件支持多根节点组件,即片段。

<!-- Layout.vue -->
<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

二、 breaking

1. v-for 中的 Ref 数组

v-for 中的 ref 绑定为一个函数,而不是直接的一个数组。

<div v-for="item in list" :ref="setItemRef"></div>
export default {
  data() {
    return {
      itemRefs: []
    }
  },
  methods: {
    setItemRef(el) {
      this.itemRefs.push(el)
    }
  },
  beforeUpdate() {
    this.itemRefs = []
  },
  updated() {
    console.log(this.itemRefs)
  }
}

// 或者
import { ref, onBeforeUpdate, onUpdated } from 'vue'

export default {
  setup() {
    let itemRefs = []
    const setItemRef = el => {
      itemRefs.push(el)
    }
    onBeforeUpdate(() => {
      itemRefs = []
    })
    onUpdated(() => {
      console.log(itemRefs)
    })
    return {
      itemRefs,
      setItemRef
    }
  }
}
2. 自定义指令

重命名自定义指令 API,以便更好地与组件生命周期保持一致。

const MyDirective = {
  beforeMount(el, binding, vnode, prevVnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {}, // 新
  unmounted() {}
}
3. 自主定制元素

is 特性用法仅限于保留的 标记,在其它标记上将失效。

对于 SFC(单文件组件),is prop 的使用格式为:

// 在 vue 3 中渲染的将会是普通的 button
<button is="plastic-button">点击我!</button>
4. Data 选项

data 组件选项声明不再接收纯 JavaScript object,而需要 function 声明。

<script>
  import { createApp } from 'vue'

  createApp({
    data() {
      return {
        apiKey: 'a1b2c3'
      }
    }
  }).mount('#app')
</script>

当来自组件的 data() 及其 mixin 或 extends 基类被合并时,现在将浅层次执行合并。

5. 事件 API

$on,$off 和 $once 实例方法已被移除,$emit 尚保留为一个 API。

现可以通过外部库 mitt 来替换现有的事件中心。

或者,在兼容性构建中,也可以支持这些方法。

6. 函数式组件

在 Vue 3 中,所有的函数式组件都是用普通函数创建的,换句话说,不需要定义 { functional: true } 组件选项。

import { h } from 'vue'

const DynamicHeading = (props, context) => {
  return h(`h${props.level}`, context.attrs, context.slots)
}

DynamicHeading.props = ['level']

export default DynamicHeading
7. 全局 API

调用 createApp 返回一个应用实例,这是 Vue 3 中的新概念:

import { createApp } from 'vue'

const app = createApp({})

应用程序实例暴露当前全局 API 的子集,经验法则是,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上,以下是当前全局 API 及其相应实例 API 的表:

2.x 全局 API3.x 实例 API(app)
Vue.configapp.config
Vue.config.productionTipremoved
Vue.config.ignoredElementsapp.config.isCustomElement
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use

所有其他不全局改变行为的全局 API 现在被命名为 exports

在应用程序之间共享配置 (如组件或指令) 的一种方法是创建工厂功能,如下所示:

import { createApp } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'

const createMyApp = options => {
  const app = createApp(options)
  app.directive('focus' /* ... */)

  return app
}

createMyApp(Foo).mount('#foo')
createMyApp(Bar).mount('#bar')
8. 全局 API Treeshaking

在 vue 2.x 中,tree-shaking 针对全局 API Vue.nextTick() 无计可施。

在 Vue 3 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,全局 API 现在只能作为 ES 模块构建的命名导出进行访问。

import { nextTick } from 'vue'

nextTick(() => {
  // 一些和DOM有关的东西
})
import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'
import { nextTick } from 'vue'

test('an async feature', async () => {
  const wrapper = shallowMount(MyComponent)

  // 执行一些DOM相关的任务

  await nextTick()

  // 运行你的断言
})

9. 内敛模板 Attribute

在 vue 3.0 中,对内联特性(inline-template attribute)的支持已经废除。

现在,有 2 种迁移策略:

  1. 使用
<script type="text/html" id="my-comp-template">
  <div>{{ hello }}</div>
</script>

// 在组件中,使用选择器将模板作为目标:
const MyComp = {
  template: '#my-comp-template'
  // ...
}
  1. 默认 Slot
<!-- 2.x 语法 -->
<my-comp inline-template :msg="parentMsg">
  {{ msg }} {{ childState }}
</my-comp>

<!-- 默认 Slot 版本 -->
<my-comp v-slot="{ childState }">
  {{ parentMsg }} {{ childState }}
</my-comp>
10. key attribute

key attribute 被用于提示 Vue 的虚拟 DOM 算法对节点身份保持持续的追踪,以便 vue 知道在何时能够重用节点、何时能够修改节点、何时能够对节点重新排序或者重新创建。

  1. vue 3.0 中,会自动为每个分支生成唯一的 key,不推荐自己为分支设置 key 了。但是,为了兼容,设置 key 也是可以的,但是一定要保证 key 的唯一性。
<!-- Vue 2.x -->
<div v-if="condition" key="a">Yes</div>
<div v-else key="a">No</div>

<!-- Vue 3.x (recommended solution: remove keys) -->
<div v-if="condition">Yes</div>
<div v-else>No</div>

<!-- Vue 3.x (alternate solution: make sure the keys are always unique) -->
<div v-if="condition" key="a">Yes</div>
<div v-else key="b">No</div>
  1. 在 vue 3.0 中, 当使用 时,key 应该被设置在<template> 标签上。
<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
  <div>...</div>
  <span>...</span>
</template>

<!-- Vue 2.x -->
<template v-for="item in list">
  <div v-if="item.isVisible" :key="item.id">...</div>
  <span v-else :key="item.id">...</span>
</template>

<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
  <div v-if="item.isVisible">...</div>
  <span v-else>...</span>
</template>
11. 按键修饰符

在 vue 3 中,config.keyCodes 现在也已弃用,不再受支持;也不再支持使用数字 (即键码) 作为 v-on 修饰符。建议对任何要用作修饰符的键使用 kebab-cased (短横线) 大小写名称。

<!-- Vue 3 在 v-on 上使用 按键修饰符 -->
<input v-on:keyup.delete="confirmDelete" />
12. 在 prop 的默认函数中访问 this

生成 prop 默认值的工厂函数不再能访问 this。

import { inject } from 'vue'

export default {
  props: {
    theme: {
      default (props) {
        // `props` 是传递给组件的原始值。
        // 在任何类型/默认强制转换之前
        // 也可以使用 `inject` 来访问注入的 property
        return inject('theme', 'default-theme')
      }
    }
  }
}
13. 渲染函数 API

在 3.x 中,h 现在是全局导入的,而不是作为 render 函数的参数自动传递。

// Vue 3 渲染函数示例
import { h } from 'vue'

export default {
  render() {
    return h('div')
  }
}
14. Slot 统一

在 3.x 中,插槽被定义为当前节点的子对象:

// 3.x Syntax
h(LayoutComponent, {}, {
  header: () => h('div', this.header),
  content: () => h('div', this.content)
})

当你需要以编程方式引用作用域 slot 时,它们现在被统一到 s l o t s 选 项 中 。 在 3. x 中 , 将 所 有 t h i s . slots 选项中。在 3.x 中,将所有 this. slots3.xthis.scopedSlots 替换为 this.$slots。

// 2.x 语法
this.$scopedSlots.header

// 3.x 语法
this.$slots.header
15. 过渡的 class 名更改
  1. 将 .v-enter 字符串替换为 .v-enter-from;
  2. 将 .v-leave 字符串替换为 .v-leave-from。

<transition> 过渡组件相关属性名需要进行字符串实例替换,规则如下:

  1. leave-class -> leave-from-class (在渲染函数或 JSX 中可以写为:leaveFromClass)
  2. enter-class -> enter-from-class (在渲染函数或 JSX 中可以写为:enterFromClass)
.v-enter-from,
.v-leave-to {
  opacity: 0;
}

.v-leave-from,
.v-enter-to {
  opacity: 1;
}
16. v-model

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

<ChildComponent v-model="pageTitle" />

<!-- 简写: -->

<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>

若需要更改 model 名称,而不是更改组件内的 model 选项,可以将一个 argument 传递给 model:

<ChildComponent v-model:title="pageTitle" />

<!-- 简写: -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

在自定义组件上使用多个 v-model:

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

<!-- 简写: -->

<ChildComponent
  :title="pageTitle"
  @update:title="pageTitle = $event"
  :content="pageContent"
  @update:content="pageContent = $event"
/>

3.x 还支持自定义修饰符。

调整方法:

  1. 检查你的代码库中所有使用 .sync 的部分并将其替换为 v-model:
<ChildComponent :title.sync="pageTitle" />

<!-- 替换为 -->

<ChildComponent v-model:title="pageTitle" />
  1. 对于所有不带参数的 v-model,请确保分别将 prop 和 event 命名更改为 modelValue 和 update:modelValue:
<ChildComponent v-model="pageTitle" />
// ChildComponent.vue

export default {
  props: {
    modelValue: String // 以前是`value:String`
  },
  methods: {
    changePageTitle(title) {
      this.$emit('update:modelValue', title) // 以前是 `this.$emit('input', title)`
    }
  }
}
17. v-if 与 v-for 的优先级对比

2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用;

3.x 版本中 v-if 总是优先于 v-for 生效。

比起在模板层面管理相关逻辑,更好的办法是通过创建计算属性筛选出列表,并以此创建可见元素。

18. v-bind 合并行为

在 3.x,如果一个元素同时定义了 v-bind=“object” 和一个相同的单独的 property,那么声明绑定的顺序决定了它们如何合并。换句话说,相对于假设开发者总是希望单独的 property 覆盖 object 中定义的内容,现在开发者对自己所希望的合并行为有了更好的控制。

<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="blue"></div>

<!-- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div>

三、removed

1. 过滤器

Filters 已从 Vue 3.0 中删除,不再受支持。

可以使用计算属性或方法来代替过滤器。

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountInUSD }}</p>
</template>

<script>
  export default {
    props: {
      accountBalance: {
        type: Number,
        required: true
      }
    },
    computed: {
      accountInUSD() {
        return '$' + this.accountBalance
      }
    }
  }
</script>
Logo

前往低代码交流专区

更多推荐