除了composition API,vue3.0文档又带来了什么新东西?

异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。

2.0中 我们这么用??

1
2
3
4
5
6
7
8
9
const asyncPage = () => import('./NextPage.vue') //return a promise

const asyncPage = {
  component: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  error: ErrorComponent,
  loading: LoadingComponent
}

3.0用法(由于组件被定义为纯函数,我们需要引入defineAsyncComponent实现异步)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

// Async component without options
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))

// Async component with options
const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})

自定义指令

2.0中 我们这么用??

1
2
3
4
5
6
7
8
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

钩子函数:

3.0用法(添加了更加丰富的生命周期钩子函数)

1
2
3
4
5
6
7
8
const MyDirective = {
  beforeMount(el, binding, vnode, prevVnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {}, // new
  unmounted() {}
}
1
2
3
4
5
6
7
const app = Vue.createApp({})

app.directive('highlight', {
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value
  }
})

自定义元素互操作更改(破坏性改变)

2.0中 我们这么用??

1. ignoredElements

1
2
3
4
5
6
7
Vue.config.ignoredElements = [
  'my-custom-web-component',
  'another-web-component',
  // 用一个 `RegExp` 忽略所有“ion-”开头的元素
  // 仅在 2.5+ 支持
  /^ion-/
]

使 Vue 忽略在 Vue 之外的自定义元素 (e.g. 使用了 Web Components APIs)。否则,它会假设你忘记注册全局组件或者拼错了组件名称,从而抛出一个关于 Unknown custom element 的警告。

举例:
假如我使用了未定义的组件


报错:(这个检查在vue3.0中在编译时就会进行,而2.0是在运行时进行的)


使用场景:有时候我们会再引入其他第三方库的组件,但是vue会抛出错误,我们要避免这个错误,可以在Vue.config.ignoredElements配置里面配置下。

3.0用法

1
2
3
4
5
6
7
8
9
10
11
12
13
// in webpack config
rules: [
  {
    test: /\\.vue$/,
    use: 'vue-loader',
    options: {
      compilerOptions: {
        isCustomElement: tag => tag === 'plastic-button'
      }
    }
  }
  // ...
]

改变原因:自定义ignoredElements现在在模板编译期间执行,应通过编译器选项(而不是运行时配置)进行配置。

2.is属性的使用

2.0中 我们这么用??

1
<button is="plastic">Click Me!</button>

它被解释为使用name渲染Vue组件plastic,等同于:

1
<plastic> Click Me!</plastic>

或者

1
<component v-bind:is="currentTabComponent" class="tab"></component>

这里的用法是动态组件渲染
不了解is具体使用的看这里:用于动态组件且基于 DOM 内模板的限制来工作。

在3.0中,仅将Vue对 is prop 的特殊处理限制在标签上。

1
<component v-bind:is="currentTabComponent"></component>

也即是说在上面button标签例子中的is在vue3.0只会呈现一个普通prop的效果

3.is进行In-DOM模板解析的变通办法

这个不太常见,只在HTML中使用vue会出现这个问题,.vue文件没有这个情况

2.0中 我们这么用??

1
2
3
4
5
 <ul>
  <li></li>
  <li></li>
  <li></li>
</ul>

总所周知,ul里面嵌套li的写法是html语法的固定写法(还有如table,select等)

1
2
3
4
<ul>
  <my-component></my-component>
  <my-component></my-component>
</ul>

my-component是我们自己写的组件,但是html在渲染dom的时候,my-component对ul来说并不是有效的dom,甚至会报错。

解决方法:

1
2
3
<ul>
  <li is='my-component'></li>
</ul>

在3.0中

1
2
3
<ul>
  <li v-is='"my-component"'></li>
</ul>

用v-is取代了is,在写法上和上面一种is的功能也区分开了

data定义(破坏性改变)

2.0中 我们这么用??

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- Object Declaration -->
<script>
  const app = new Vue({
    data: {
      apiKey: 'a1b2c3'
    }
  })
</script>

<!-- Function Declaration -->
<script>
  const app = new Vue({
    data() {
      return {
        apiKey: 'a1b2c3'
      }
    }
  })
</script>

Data支持两种格式(Function/Object)的定义

vue3.0中只支持Function这一种定义方法

Fragment

2.0中 我们这么用??

1
2
3
4
5
6
7
8
<!-- Layout.vue -->
<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

如果不加这个

就会报下面这个错:
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead

3.0中支持不止一个根标签:

1
2
3
4
5
6
<!-- Layout.vue -->
<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

渲染API

2.0中 我们这么用??

1
2
3
4
5
export default {
  render(h) {
    return h('div')
  }
}

3.0中h 函数现在已全局导入,而不是传递给渲染函数作为参数

1
2
3
4
5
6
import { h } from 'vue'
export default {
  render() {
    return h('div')
  }
}

v-model

2.0中 我们这么用??

1
2
3
4
5
<ChildComponent v-model="pageTitle" />

<!-- 上下等效 -->

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

3.0中变化为这样:

1
2
3
4
5
<ChildComponent v-model="pageTitle" />

<!-- 上下等效 -->

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

1.一个组件支持多个v-model

1
2
3
4
5
6
7
8
9
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />


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

2.v-model自定义修饰符

类似于2.0的.tirm修饰符

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div id="app">
  <my-component v-model.capitalize="myText"></my-component>
  {{ myText }}
</div>

const app = Vue.createApp({
  data() {
    return {
      myText: ''
    }
  }
})

app.component('my-component', {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  },
  template: `<input
    type="text"
    v-bind:value="modelValue"
    v-on:input="emitValue">`
})

app.mount('#app')

Vue实例

2.0中 我们这么用??

1
2
3
4
5
6
7
8
9
10
11
Vue.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

Similarly, this is how a global directive is declared:
Vue.directive('focus', {
  inserted: el => el.focus()
})

从技术上讲,Vue 2没有app的概念。我们定义为app的只是通过创建的根Vue实例new Vue()从同一个Vue构造函数创建的每个根实例都共享相同的全局配置。用起来很方便,但结果是不可避免的造成了全局污染,并且全局配置使测试过程中意外污染其他测试案例变得容易。

vue3.0:

调用createApp返回一个新的vue实例,这是Vue 3中的一个新概念,每个实例拥有了自己的配置项。

1
2
3
import { createApp } from 'vue'

const app = createApp()


1.挂载app实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { createApp } from 'vue'
import MyApp from './MyApp.vue'

const app = createApp(MyApp)
app.mount('#app')

const app = createApp(MyApp)

app.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

app.directive('focus', {
  mounted: el => el.focus()
})

app.mount('#app')

2.在app之间共享配置

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createApp } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'

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

  return app
}

createMyApp(Foo).mount('#foo')
createMyApp(Bar).mount('#bar')

现在,该focus指令将在Foo和Bar实例及其后代中可用。

相关参考文档:https://v3.vuejs.org/