7 Vue3

相比 Vue2


1. 优点

  • vue3支持vue2的大多数特性,实现对vue2的兼容
  • vue3对比vue2具有明显的性能提升
    • 打包大小减少41%
    • 初次渲染快55%,更新快133%
    • 内存使用减少54%
  • 更好的支持TypeScript
  • 使用Proxy代替defineProperty实现响应式数据

2. 性能提升的原因

静态标记

  • vue2从根节点开始对虚拟dom进行全量对比(每个节点不论写死的还是动态的都会一层一层比较)
  • vue3新增了静态标记 与上次虚拟dom对比的时候,只对比带有 patchFlags 的节点。跳过一些静态节点对比

hoistStatic 静态提升

  • vue2里每当触发更新的时候,不管元素是否参与更新,每次都会重新创建
  • vue3为了避免每次渲染的时候都要重新创建这些对象,会把不参与更新的元素保存起来,只创建一次,每次复用

cacheHandlers 事件缓存

  • vue2里绑定事件都要重新生成新的function去更新
  • vue3会自动生成一个内联函数,同时生成一个静态节点。onclick时会读取缓存,如果缓存没有的话,就把传入的事件存到缓存里

3. 响应式数据的变化

vue2 响应式

  • 核心
    • Object.defineProperty 来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调
    • 对象:递归调用defineProperty对对象已有属性值的读取和修改进行拦截
    • 数组:重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
  • 问题
    • 不能监听到对象属性的添加和删除,需要Vue.set()来添加和删除。
    • 不能通过下标替换元素或更新length

vue3 响应式

  • 核心
    • 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等
    • 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作

Vue3 应用实例 API


createApp()

创建一个应用实例。

function createApp(rootComponent: Component, rootProps?: object): App
  • 第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props

实际使用

// 可以直接内联根组件
import {
   
    createApp } from 'vue'

const app = createApp({
   
   
  /* root component options */
})

// 也可以使用从别处导入的组件
import {
   
    createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

createSSRApp()

SSR 激活模式创建一个应用实例。用法与 createApp() 完全相同。

app.mount()

将应用实例挂载在一个容器元素中。

interface App {
   
   
  mount(rootContainer: Element | string): ComponentPublicInstance
}
  • 参数可以是一个实际的 DOM 元素或一个 CSS 选择器 (使用第一个匹配到的元素)。返回根组件的实例。
  • 如果该组件有模板或定义了渲染函数,它将替换容器内所有现存的 DOM 节点。否则在运行时编译器可用的情况下,容器元素的 innerHTML 将被用作模板。
  • 在 SSR 激活模式下,它将激活容器内现有的 DOM 节点。如果出现了激活不匹配,那么现有的 DOM 节点将会被修改以匹配客户端的实际渲染结果。
  • 对于每个应用实例,mount() 仅能调用一次。

实际使用

import {
   
    createApp } from 'vue'
const app = createApp(/* ... */)

app.mount('#app')

// 也可以挂载到一个实际的 DOM 元素
app.mount(document.body.firstChild)

app.unmount()

卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。

interface App {
   
   
  unmount(): void
}

app.provide()

提供一个值,可以在应用中的所有后代组件中注入使用。

interface App {
   
   
  provide<T>(key: InjectionKey<T> | symbol | string, value: T): this
}
  • 第一个参数应当是注入的 key,第二个参数则是提供的值。返回应用实例本身。

Provide (提供)

通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦:
要为组件后代提供数据,可以使用到 provide() 函数:

<script setup>
import { provide } from 'vue'

provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>

如果不使用 <script setup>,请确保 provide() 是在 setup() 同步调用的:

import {
   
    provide } from 'vue'

export default {
   
   
  setup() {
   
   
    provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
  }
}

provide() 函数接收两个参数。第一个参数被称为注入名,可以是一个字符串或是一个 Symbol。后代组件会用注入名来查找期望注入的值。一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。
第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref:

import {
   
    ref, provide } from 'vue'

const count = ref(0)
provide('key', count)

提供的响应式状态使后代组件可以由此和提供者建立响应式的联系。

应用层 Provide

除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖:

import {
   
    createApp } from 'vue'

const app = createApp({
   
   })

app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')

在应用级别提供的数据在该应用内的所有组件中都可以注入。这在你编写插件时会特别有用,因为插件一般都不会使用组件形式来提供值。

Inject (注入)

要注入上层组件提供的数据,需使用 inject() 函数:

<script setup>
import {
   
    inject } from 'vue'

const message = inject('message')
</script>

如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。
同样的,如果没有使用 <script setup>inject() 需要在 setup() 内同步调用:

import {
   
    inject } from 'vue'

export default {
   
   
  setup() {
   
   
    const message = inject('message')
    return {
   
    message }
  }
}

注入默认值

默认情况下,inject 假设传入的注入名会被某个祖先链上的组件提供。如果该注入名的确没有任何组件提供,则会抛出一个运行时警告。
如果在注入一个值时不要求必须有提供者,那么我们应该声明一个默认值,和 props 类似:

// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')

在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值:

const value = inject('key', () => new ExpensiveClass())

和响应式数据配合使用

当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
有的时候,我们可能需要在注入方组件中更改数据。在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数:

<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{
  
  { location }}</button>
</template>

最后,如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。

<script setup>
import { ref, provide, readonly } from 'vue'

const count = ref(0)
provide('read-only-count', readonly(count))
</script>

使用 Symbol 作注入名

至此,我们已经了解了如何使用字符串作为注入名。但如果你正在构建大型的应用,包含非常多的依赖提供,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol 来作为注入名以避免潜在的冲突。
我们通常推荐在一个单独的文件中导出这些注入名 Symbol:

// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import {
   
    provide } from 'vue'
import {
   
    myInjectionKey } from './keys.js'

provide(myInjectionKey, {
   
    /*
  要提供的数据
*/ });
// 注入方组件
import {
   
    inject } from 'vue'
import {
   
    myInjectionKey } from './keys.js'

const injected = inject(myInjectionKey)

app.component()

如果同时传递一个组件名字符串及其定义,则注册一个全局组件;如果只传递一个名字,则会返回用该名字注册组件 (如果存在的话)。

interface App {
   
   
  component(name: string): Component | undefined
  component(name: string, component: Component): this
}

全局注册

我们可以使用 Vue 应用实例app.component() 方法,让组件在当前 Vue 应用中全局可用。

import {
   
    createApp } from 'vue'

const app = createApp({
   
   })

app.component(
  // 注册的名字
  'MyComponent',
  // 组件的实现
  {
   
   
    /* ... */
  }
)

如果使用单文件组件,你可以注册被导入的 .vue 文件:

import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

app.component() 方法可以被链式调用:

app
  .component('ComponentA', ComponentA)
  .component('ComponentB', ComponentB)
  .component('ComponentC', ComponentC)

全局注册的组件可以在此应用的任意组件的模板中使用:

<!-- 这在当前应用的任意组件中都可用 -->
<ComponentA/>
<ComponentB/>
<ComponentC/>

所有的子组件也可以使用全局注册的组件,这意味着这三个组件也都可以在_彼此内部_使用。

局部注册

全局注册虽然很方便,但有以下几个问题:

  1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
  2. 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。

相比之下,局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。

在使用 <script setup> 的单文件组件中,导入的组件可以直接在模板中使用,无需注册:

<script setup>
import ComponentA from './ComponentA.vue'
</script>

<template>
  <ComponentA />
</template>

如果没有使用 <script setup>,则需要使用 components 选项来显式注册:

import ComponentA from './ComponentA.js'

export default {
   
   
  components: {
   
   
    ComponentA
  },
  setup() {
   
   
    // ...
  }
}

对于每个 components 对象里的属性,它们的 key 名就是注册的组件名,而值就是相应组件的实现。上面的例子中使用的是 ES2015 的缩写语法,等价于:

export default {
   
   
  components: {
   
   
    ComponentA: ComponentA
  }
  // ...
}

请注意:局部注册的组件在后代组件中并可用。在这个例子中,ComponentA 注册后仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。

组件名格式

在整个指引中,我们都使用 PascalCase 作为组件名的注册格式,这是因为:

  1. PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 中导入和注册组件都很容易,同时 IDE 也能提供较好的自动补全。
  2. <PascalCase /> 在模板中更明显地表明了这是一个 Vue 组件,而不是原生 HTML 元素。同时也能够将 Vue 组件和自定义元素 (web components) 区分开来。

在单文件组件和内联字符串模板中,我们都推荐这样做。但是,PascalCase 的标签名在 DOM 模板中是不可用的,详情参见 DOM 模板解析注意事项

为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyComponent 为名注册的组件,在模板中可以通过 <MyComponent><my-component> 引用。这让我们能够使用同样的 JavaScript 组件注册代码来配合不同来源的模板。

DOM 模板解析注意事项

大小写区分

  • HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。这意味着当你使用 DOM 内的模板时,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式

闭合标签

  • 闭合标签 (self-closing tag):Vue 的模板解析器支持任意标签使用 /> 作为标签关闭的标志。
  • 然而在 DOM 模板中,我们必须显式地写出关闭标签:这是由于 HTML 只允许一小部分特殊的元素省略其关闭标签,最常见的就是 <input><img>。对于其他的元素来说,如果你省略了关闭标签,原生的 HTML 解析器会认为开启的标签永远没有结束

元素位置限制

  • 某些 HTML 元素对于放在其中的元素类型有限制,例如 <ul><ol><table><select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li><tr><option>
  • 这将导致在使用带有此类限制元素的组件时出现问题。例如:
<table>
  <blog-post-row></blog-post-row>
</table>
  • 自定义的组件 <blog-post-row> 将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is attribute 作为一种解决方案:
<table>
  <tr is="vue:blog-post-row"></tr>
</table>

app.directive()

如果同时传递一个名字和一个指令定义,则注册一个全局指令;如果只传递一个名字,则会返回用该名字注册的指令 (如果存在的话)。

interface App {
   
   
  directive(name: string): Directive | undefined
  directive(name: string, directive: Directive): this
}

自定义指令

除了 Vue 内置的一系列指令 (比如 v-modelv-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。
我们已经介绍了两种在 Vue 中重用代码的方式:组件组合式函数。组件是主要的构建模块,而组合式函数则侧重于有状态的逻辑。另一方面,自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。下面是一个自定义指令的例子,当一个 input 元素被 Vue 插入到 DOM 中后,它会被自动聚焦:

<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>

假设你还未点击页面中的其他地方,那么上面这个 input 元素应该会被自动聚焦。该指令比 autofocus attribute 更有用,因为它不仅仅可以在页面加载完成后生效,还可以在 Vue 动态插入元素后生效。
<script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。在上面的例子中,vFocus 即可以在模板中以 v-focus 的形式使用。
在没有使用 <script setup> 的情况下,自定义指令需要通过 directives 选项注册:

export default {
   
   
  setup() {
   
   
    /*...*/
  },
  directives: {
   
   
    // 在模板中启用 v-focus
    focus: {
   
   
      /* ... */
    }
  }
}

将一个自定义指令全局注册到应用层级也是一种常见的做法:

const app = createApp({
   
   })

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
   
   
  /* ... */
})

TIP 只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。其他情况下应该尽可能地使用 v-bind 这样的内置指令来声明式地使用模板,这样更高效,也对服务端渲染更友好。

指令钩子

一个指令的定义对象可以提供几种钩子函数 (都是可选的):

const myDirective = {
   
   
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
   
   
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {
   
   },
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {
   
   },
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {
   
   },
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {
   
   },
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {
   
   },
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {
   
   }
}

钩子参数

指令的钩子会传递以下几种参数:

  • el:指令绑定到的元素。这可以用于直接操作 DOM。
  • binding:一个对象,包含以下属性。
    • value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2
    • oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
    • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    • instance:使用该指令的组件实例。
    • dir:指令的定义对象。
  • vnode:代表绑定元素的底层 VNode。
  • prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdateupdated 钩子中可用。

举例来说,像下面这样使用指令:

<div v-example:foo.bar="baz">

binding 参数会是一个这样的对象:

{
   
   
  arg: 'foo',
  modifiers: {
   
    bar: true },
  value: /* `baz` 的值 */,
  oldValue: /* 上一次更新时 `baz` 的值 */
}

和内置指令类似,自定义指令的参数也可以是动态的。举例来说:

<div v-example:[arg]="value"></div>

这里指令的参数会基于组件的 arg 数据属性响应式地更新。

Note 除了 el 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现。

简化形式

对于自定义指令来说,一个很常见的情况是仅仅需要在 mountedupdated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令,如下所示:

<div v-color="color"></div>
app.directive('color', (el, binding) => {
   
   
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value
})

对象字面量

如果你的指令需要多个值,你可以向它传递一个 JavaScript 对象字面量。别忘了,指令也可以接收任何合法的 JavaScript 表达式。

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
   
   
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})

在组件上使用

当在组件上使用自定义指令时,它会始终应用于组件的根节点,和透传 attributes 类似。

<MyComponent v-demo="test" />
<!-- MyComponent 的模板 -->

<div> <!-- v-demo 指令会被应用在此处 -->
  <span>My component content</span>
</div>

需要注意的是组件可能含有多个根节点。当应用到一个多根组件时,指令将会被忽略且抛出一个警告。和 attribute 不同,指令不能通过 v-bind="$attrs" 来传递给一个不同的元素。总的来说,推荐在组件上使用自定义指令。

app.use()

安装一个插件。

interface App {
   
   
  use(plugin: Plugin, ...options: any[]): this
}
  • 第一个参数应是插件本身,可选的第二个参数是要传递给插件的选项。
  • 插件可以是一个带 install() 方法的对象,亦或直接是一个将被用作 install() 方法的函数。插件选项 (app.use() 的第二个参数) 将会传递给插件的 install() 方法。
  • app.use() 对同一个插件多次调用,该插件只会被安装一次。

插件概述

插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。下面是如何安装一个插件的示例:

import {
   
    createApp } from 'vue'

const app = createApp({
   
   })

app.use(myPlugin, {
   
   
  /* 可选的选项 */
})

一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装它的应用实例和传递给 app.use() 的额外选项作为参数:

const myPlugin = {
   
   
  install(app, options) {
   
   
    // 配置此应用
  }
}

插件没有严格定义的使用范围,但是插件发挥作用的常见场景主要包括以下几种:

  1. 通过 app.component()app.directive() 注册一到多个全局组件或自定义指令。
  2. 通过 app.provide() 使一个资源可被注入进整个应用。
  3. app.config.globalProperties 中添加一些全局实例属性或方法
  4. 一个可能上述三种都包含了的功能库 (例如 vue-router)。

编写一个插件

为了更好地理解如何构建 Vue.js 插件,我们可以试着写一个简单的 i18n (国际化 (Internationalization) 的缩写) 插件。
让我们从设置插件对象开始。建议在一个单独的文件中创建并导出它,以保证更好地管理逻辑,如下所示:

// plugins/i18n.js
export default {
   
   
  install: (app, options) => {
   
   
    // 在这里编写插件代码
  }
}

我们希望有一个翻译函数,这个函数接收一个以 . 作为分隔符的 key 字符串,用来在用户提供的翻译字典中查找对应语言的文本。期望的使用方式如下:

<h1>{
  
  { $translate('greetings.hello') }}</h1>

这个函数应当能够在任意模板中被全局调用。这一点可以通过在插件中将它添加到 app.config.globalProperties 上来实现:

// plugins/i18n.js
export default {
   
   
  install: (app, options) => {
   
   
    // 注入一个全局可用的 $translate() 方法
    app.config.globalProperties.$translate = (key) => {
   
   
      // 获取 `options` 对象的深层属性
      // 使用 `key` 作为索引
      return key.split('.').reduce((o, i) => {
   
   
        if (o) return o[i]
      }, options)
    }
  }
}

我们的 $translate 函数会接收一个例如 greetings.hello 的字符串,在用户提供的翻译字典中查找,并返回翻译得到的值。
用于查找的翻译字典对象则应当在插件被安装时作为 app.use() 的额外参数被传入:

import i18nPlugin from './plugins/i18n'

app.use(i18nPlugin, {
   
   
  greetings: {
   
   
    hello: 'Bonjour!'
  }
})

这样,我们一开始的表达式 $translate('greetings.hello') 就会在运行时被替换为 Bonjour! 了。

TIP 请谨慎使用全局属性,如果在整个应用中使用不同插件注入的太多全局属性,很容易让应用变得难以理解和维护。

插件中的 Provide / Inject

在插件中,我们可以通过 provide 来为插件用户供给一些内容。举例来说,我们可以将插件接收到的 options 参数提供给整个应用,让任何组件都能使用这个翻译字典对象。

// plugins/i18n.js
export default {
   
   
  install: (app, options) => {
   
   
    app.config.globalProperties.$translate = (key) => {
   
   
      return key.split('.').reduce((o, i) => {
   
   
        if (o) return o[i]
      }, options)
    }

    app.provide('i18n', options)
  }
}

现在,插件用户就可以在他们的组件中以 i18n 为 key 注入并访问插件的选项对象了。

<script setup>
import { inject } from 'vue'

const i18n = inject('i18n')

console.log(i18n.greetings.hello)
</script>

app.mixin()

应用一个全局 mixin (适用于该应用程序的范围)。一个全局的 mixin 会作用于应用中的每个组件实例。

不推荐 Mixins 在 Vue 3 支持主要是为了向后兼容,因为生态中有许多库使用到。在新的应用中应尽量避免使用 mixin,特别是全局 mixin。

若要进行逻辑复用,推荐用组合式函数来替代。

interface App {
   
   
  mixin(mixin: ComponentOptions): this
}

app.version

提供当前应用所使用的 Vue 版本号。这在插件中很有用,因为可能需要根据不同的 Vue 版本执行不同的逻辑。

interface App {
   
   
  version: string
}

实际使用

// 在一个插件中对版本作判断
export default {
   
   
  install(app) {
   
   
    const version = Number(app.version.split('.')[0])
    if (version < 3) {
   
   
      console.warn('This plugin requires Vue 3')
    }
  }
}

app.config

每个应用实例都会暴露一个 config 对象,其中包含了对这个应用的配置设定。你可以在挂载应用前更改这些属性 (下面列举了每个属性的对应文档)。

import {
   
    createApp } from 'vue'

const app = createApp(/* ... */)

console.log(app.config)

app.config.errorHandler

用于为应用内抛出的未捕获错误指定一个全局处理函数。

  • 类型
interface AppConfig {
   
   
  errorHandler?: (
    err: unknown,
    instance: ComponentPublicInstance | null,
    // `info` 是一个 Vue 特定的错误信息
    // 例如:错误是在哪个生命周期的钩子上抛出的
    info: string
  ) => void
}
  • 详细信息错误处理器接收三个参数:错误对象、触发该错误的组件实例和一个指出错误来源类型信息的字符串。它可以从下面这些来源中捕获错误:
    • 组件渲染器
    • 事件处理器
    • 生命周期钩子
    • setup() 函数
    • 侦听器
    • 自定义指令钩子
    • 过渡 (Transition) 钩子
  • 示例
app.config.errorHandler = (err, instance, info) => {
   
   
  // 处理错误,例如:报告给一个服务
}

app.config.warnHandler

用于为 Vue 的运行时警告指定一个自定义处理函数。

  • 类型
interface AppConfig {
   
   
  warnHandler?: (
    msg: string,
    instance: ComponentPublicInstance | null,
    trace: string
  ) => void
}
  • 详细信息警告处理器将接受警告信息作为其第一个参数,来源组件实例为第二个参数,以及组件追踪字符串作为第三个参数。这可以用户过滤筛选特定的警告信息,降低控制台输出的冗余。所有的 Vue 警告都需要在开发阶段得到解决,因此仅建议在调试期间选取部分特定警告,并且应该在调试完成之后立刻移除。

TIP 警告仅会在开发阶段显示,因此在生产环境中,这条配置将被忽略。

  • 示例
app.config.warnHandler = (msg, instance, trace) => {
   
   
  // `trace` is the component hierarchy trace
}

app.config.performance

设置此项为 true 可以在浏览器开发工具的“性能/时间线”页中启用对组件初始化、编译、渲染和修补的性能表现追踪。仅在开发模式和支持 performance.mark API 的浏览器中工作。

app.config.compilerOptions

配置运行时编译器的选项。设置在此对象上的值将会在浏览器内进行模板编译时使用,并会影响到所配置应用的所有组件。另外你也可以通过 compilerOptions 选项在每个组件的基础上覆盖这些选项。

重要 此配置项仅在完整构建版本,即可以在浏览器中编译模板的 vue.js 文件中可用。如果你用的是带构建的项目配置,且使用的是仅含运行时的 Vue 文件版本,那么编译器选项必须通过构建工具的相关配置传递给 @vue/compiler-dom

app.config.compilerOptions.isCustomElement

用于指定一个检查方法来识别原生自定义元素。

  • 类型 (tag: string) => boolean
  • 详细信息如果该标签需要当作原生自定义元素则应返回 true。对匹配到的标签,Vue 会将其渲染为原生元素而非将其视为一个 Vue 组件来解析。原生 HTML 和 SVG 标签不需要在此函数中进行匹配,Vue 的解析器会自动识别它们。
  • 示例
// 将所有标签前缀为 `ion-` 的标签视为自定义元素
app.config.compilerOptions.isCustomElement = (tag) => {
   
   
  return tag.startsWith('ion-')
}

app.config.compilerOptions.whitespace

用于调整模板中空格的处理行为。

  • 类型 'condense' | 'preserve'
  • 默认 'condense'
  • 详细信息Vue 移除/缩短了模板中的空格以求更高效的模板输出。默认的策略是“缩短”,表现行为如下:

设置该选项为 'preserve' 则会禁用 (2) 和 (3) 两项。

  1. 元素中开头和结尾的空格字符将被缩短为一个空格。
  2. 包含换行的元素之间的空白字符会被删除。
  3. 文本节点中连续的空白字符被缩短成一个空格。
  • 示例
app.config.compilerOptions.whitespace = 'preserve'

app.config.compilerOptions.delimiters

用于调整模板内文本插值的分隔符。

  • 类型 [string, string]
  • 默认 ['{ {', '}}']
  • 详细信息此项通常是为了避免与同样使用 mustache 语法的服务器端框架发生冲突。
  • 示例
// 分隔符改为ES6模板字符串样式
app.config.compilerOptions.delimiters = ['${', '}']

app.config.compilerOptions.comments

用于调整是否移除模板中的 HTML 注释。

  • 类型 boolean
  • 默认 false
  • 详细信息默认情况下,Vue 会在生产环境移除所有注释,设置该项为 true 会强制 Vue 在生产环境也保留注释。在开发过程中,注释是始终被保留的。这个选项通常在 Vue 与其他依赖 HTML 注释的库一起使用时使用。
  • 示例
app.config.compilerOptions.comments = true

app.config.globalProperties

一个用于注册能够被应用内所有组件实例访问到的全局属性的对象。

  • 类型
interface AppConfig {
   
   
  globalProperties: Record<string, any>
}
  • 详细信息这是对 Vue 2 中 Vue.prototype 使用方式的一种替代,此写法在 Vue 3 已经不存在了。与任何全局的东西一样,应该谨慎使用。如果全局属性与组件自己的属性冲突,组件自己的属性将具有更高的优先级。
  • 用法
app.config.globalProperties.msg = 'hello'

这使得 msg 在应用的任意组件模板上都可用,并且也可以通过任意组件实例的 this 访问到:

export default {
   
   
  mounted() {
   
   
    console.log(this.msg) // 'hello'
  }
}

app.config.optionMergeStrategies

一个用于定义自定义组件选项的合并策略的对象。

  • 类型
interface AppConfig {
   
   
  optionMergeStrategies: Record<string, OptionMergeFunction>
}

type OptionMergeFunction = (to: unknown, from: unknown) => any
  • 详细信息一些插件或库对自定义组件选项添加了支持 (通过注入全局 mixin)。这些选项在有多个不同来源时可能需要特殊的合并策略 (例如 mixin 或组件继承)。可以在 app.config.optionMergeStrategies 对象上以选项的名称作为 key,可以为一个自定义选项注册分配一个合并策略函数。合并策略函数分别接受在父实例和子实例上定义的该选项的值作为第一和第二个参数。
  • 示例
const app = createApp({
   
   
  // option from self
  msg: 'Vue',
  // option from a mixin
  mixins: [
    {
   
   
      msg: 'Hello '
    }
  ],
  mounted() {
   
   
    // 在 this.$options 上暴露被合并的选项
    console.log(this.$options.msg)
  }
})

// 为  `msg` 定义一个合并策略函数
app.config.optionMergeStrategies.msg = (parent, child) => {
   
   
  return (parent || '') + (child || '')
}

app.mount('#app')
// 打印 'Hello Vue'

全局通用 API

version

暴露当前所使用的 Vue 版本。

  • 类型 string
  • 示例
import {
   
    version } from 'vue'

console.log(version)

nextTick()

等待下一次 DOM 更新刷新的工具方法。

  • 类型
function nextTick(callback?: () => void): Promise<void>
  • 详细信息当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
  • 示例
<script>
import { nextTick } from 'vue'

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    async increment() {
      this.count++

      // DOM 还未更新
      console.log(document.getElementById('counter').textContent) // 0

      await nextTick()
      // DOM 此时已经更新
      console.log(document.getElementById('counter').textContent) // 1
    }
  }
}
</script>

<template>
  <button id="counter" @click="increment">{
  
  { count }}</button>
</template>

defineComponent()

在定义 Vue 组件时提供类型推导的辅助函数。

  • 类型
function defineComponent(
  component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor

为了便于阅读,对类型进行了简化。

  • 详细信息第一个参数是一个组件选项对象。返回值将是该选项对象本身,因为该函数实际上在运行时没有任何操作,仅用于提供类型推导。注意返回值的类型有一点特别:它会是一个构造函数类型,它的实例类型是根据选项推断出的组件实例类型。这是为了能让该返回值在 TSX 中用作标签时提供类型推导支持。你可以像这样从 defineComponent() 的返回类型中提取出一个组件的实例类型 (与其选项中的 this 的类型等价):
const Foo = defineComponent(/* ... */)

type FooInstance = InstanceType<typeof Foo>
  • webpack Treeshaking 的注意事项因为 defineComponent() 是一个函数调用,所以它可能被某些构建工具认为会产生副作用,如 webpack。即使一个组件从未被使用,也有可能不被 tree-shake。为了告诉 webpack 这个函数调用可以被安全地 tree-shake,我们可以在函数调用之前添加一个 /*#__PURE__*/ 形式的注释:
export default /*#__PURE__*/ defineComponent(/* ... */)

请注意,如果你的项目中使用的是 Vite,就不需要这么做,因为 Rollup (Vite 背后在生产环境使用的打包器) 可以智能地确定 defineComponent() 实际上并没有副作用,所以无需手动注释。

defineAsyncComponent()

定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。

  • 类型

function defineAsyncComponent(
source: AsyncComponentLoader | AsyncComponentOptions
): Component
type AsyncComponentLoader = () => Promise
interface AsyncComponentOptions {
loader: AsyncComponentLoader
loadingComponent?: Component
errorComponent?: Component
delay?: number
timeout?: number
suspensible?: boolean
onError?: (
error: Error,
retry: () => void,
fail: () => void,
attempts: number
) => any
}



## defineCustomElement()

> 这个方法和 [`defineComponent`](https://cn.vuejs.org/api/general.html#definecomponent) 接受的参数相同,不同的是会返回一个原生[自定义元素](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)类的构造器。

- **类型**

```ts
function defineCustomElement(
  component:
    | (ComponentOptions & { styles?: string[] })
    | ComponentOptions['setup']
): {
  new (props?: object): HTMLElement
}

为了便于阅读,对类型进行了简化。

  • 详细信息除了常规的组件选项,defineCustomElement() 还支持一个特别的选项 styles,它应该是一个内联 CSS 字符串的数组,所提供的 CSS 会被注入到该元素的 shadow root 上。返回值是一个可以通过 customElements.define() 注册的自定义元素构造器。
  • 示例

import { defineCustomElement } from ‘vue’
const MyVueElement = defineCustomElement({
/* 组件选项 */
})
// 注册自定义元素
customElements.define(‘my-vue-element’, MyVueElement)



# Vue3 组合式API

****

## setup()

> `setup()` 钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
>
> 1. 需要在非单文件组件中使用组合式 API 时。
> 2. 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
>
> **其他情况下,都应优先使用 <script serup> 语法。**

### 基本使用

> 我们可以使用[响应式 API](https://cn.vuejs.org/api/reactivity-core.html) 来声明响应式的状态,在 `setup()` 函数中返回的对象会暴露给模板和组件实例。其它的选项也可以通过组件实例来获取 `setup()` 暴露的属性:

```vue
<script>
import { ref } from 'vue'

export default {
setup() {
  const count = ref(0)

  // 返回值会暴露给模板和其他的选项式 API 钩子
  return {
    count
  }
},

mounted() {
  console.log(this.count) // 0
}
}
</script>

<template>
<button @click="count++">{
  
  { count }}</button>
</template>

请注意在模板中访问从 setup 返回的 ref 时,它会自动浅层解包,因此你无须再在模板中为它写 .value。当通过 this 访问时也会同样如此解包。

TIP setup() 自身并不含对组件实例的访问权,即在 setup() 中访问 this 会是 undefined。你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。

访问 Props

setup 函数的第一个参数是组件的 props。和标准的组件一致,一个 setup 函数的 props 是响应式的,并且会在传入新的 props 时同步更新。

export default {
   
   
  props: {
   
   
    title: String
  },
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

taciturn丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值