Vue-Class-Component中文文档

13,877 阅读5分钟

概述

Vue类组件是一个库,它允许您以类样式的语法创建Vue组件。例如,下面是一个用Vue类组件编写的简单计数器组件:

<template>
  <div>
    <button v-on:click="decrement">-</button>
    {{ count }}
    <button v-on:click="increment">+</button>
  </div>
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'

// Define the component in class-style
@Component
export default class Counter extends Vue {
  // Class properties will be component data
  count = 0

  // Methods will be component methods
  increment() {
    this.count++
  }

  decrement() {
    this.count--
  }
}
</script>

如示例所示,通过使用@Component装饰器对类进行注解,可以用直观和标准的类语法定义组件数据和方法。您可以简单地用类样式的组件替换组件定义,因为它等价于组件定义中的普通选项对象样式。

通过以类样式定义组件,不仅可以更改语法,还可以利用一些ECMAScript语言特性,如类继承和装饰器。Vue类组件还为mixin继承提供了一个mixin助手,并提供了一个createDecorator函数来轻松创建自己的decorator。

您可能还想看看 Vue Property Decorator提供的@Prop@Watch装饰器。

指南

Class Component

@Componen装饰器使你的类成为一个Vue组件:

import Vue from 'vue'
import Component from 'vue-class-component'

// HelloWorld class will be a Vue component
@Component
export default class HelloWorld extends Vue {}

Data

初始数据可以声明为类属性:

<template>
  <div>{{ message }}</div>
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // Declared as component data
  message = 'Hello World!'
}
</script>

上面的组件渲染Hello World! 在div元素中message是组件数据。

注意,如果初始值是undefined,class属性将不会是响应性的,这意味着属性的变化将不会被检测到:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // `message` will not be reactive value
  message = undefined
}

为了避免这种情况,你可以使用nulldata hook来代替:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // `message` will be reactive with `null` value
  message = null

  // See Hooks section for details about `data` hook inside class.
  data() {
    return {
      // `hello` will be reactive as it is declared via `data` hook.
      hello: undefined
    }
  }
}

Methods

组件方法可以直接声明为类原型方法:

<template>
  <button v-on:click="hello">Click</button>
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // Declared as component method
  hello() {
    console.log('Hello World!')
  }
}
</script>

Computed 属性

计算属性可以声明为类属性getter / setter:

<template>
  <input v-model="name">
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  firstName = 'John'
  lastName = 'Doe'

  // Declared as computed property getter
  get name() {
    return this.firstName + ' ' + this.lastName
  }

  // Declared as computed property setter
  set name(value) {
    const splitted = value.split(' ')
    this.firstName = splitted[0]
    this.lastName = splitted[1] || ''
  }
}
</script>

Hooks

datarender和所有Vue生命周期钩子也可以直接声明为类原型方法,但是不能在实例本身上调用它们。在声明自定义方法时,应避免使用这些保留名称。

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // Declare mounted lifecycle hook
  mounted() {
    console.log('mounted')
  }

  // Declare render function
  render() {
    return <div>Hello World!</div>
  }
}

其他

对于所有其他选项,将它们传递给装饰器函数:

<template>
  <OtherComponent />
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'
import OtherComponent from './OtherComponent.vue'

@Component({
  // Specify `components` option.
  // See Vue.js docs for all available options:
  // https://vuejs.org/v2/api/#Options-Data
  components: {
    OtherComponent
  }
})
export default class HelloWorld extends Vue {}
</script>

额外的Hooks

如果使用一些Vue插件(如 Vue Router),则可能需要类组件来解析它们提供的挂钩。在这种情况下,组件。registerHooks允许你注册这样的钩子:

// class-component-hooks.js
import Component from 'vue-class-component'

// Register the router hooks with their names
Component.registerHooks([
  'beforeRouteEnter',
  'beforeRouteLeave',
  'beforeRouteUpdate'
])

注册了钩子后,类组件将它们实现为类原型方法:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // The class component now treats beforeRouteEnter,
  // beforeRouteUpdate and beforeRouteLeave as Vue Router hooks
  beforeRouteEnter(to, from, next) {
    console.log('beforeRouteEnter')
    next()
  }

  beforeRouteUpdate(to, from, next) {
    console.log('beforeRouteUpdate')
    next()
  }

  beforeRouteLeave(to, from, next) {
    console.log('beforeRouteLeave')
    next()
  }
}

建议在一个单独的文件中编写这个注册代码,因为您必须在任何组件定义之前注册它们。你可以通过将hook注册的import语句放在主文件的顶部来确定执行顺序:

// main.js

// Make sure to register before importing any components
import './class-component-hooks'

import Vue from 'vue'
import App from './App'

new Vue({
  el: '#app',
  render: h => h(App)
})

自定义构造器

您可以通过创建自己的装饰器来扩展这个库的功能。Vue类组件提供了createDecorator帮助器来创建定制的decorator。createDecorator期望一个回调函数作为第一个参数,回调函数将接收以下参数:

  • options:Vue组件选项对象。此对象的更改将影响所提供的组件。
  • key:应用装饰器的属性或方法键。
  • parameterIndex`:如果自定义装饰器用于参数,则为已装饰参数的索引。

创建日志装饰器的示例,当被装饰的方法被调用时,它打印带有方法名和传递参数的日志消息:

// decorators.js
import { createDecorator } from 'vue-class-component'

// Declare Log decorator.
export const Log = createDecorator((options, key) => {
  // Keep the original method for later.
  const originalMethod = options.methods[key]

  // Wrap the method with the logging logic.
  options.methods[key] = function wrapperMethod(...args) {
    // Print a log.
    console.log(`Invoked: ${key}(`, ...args, ')')

    // Invoke the original method.
    originalMethod.apply(this, args)
  }
})

使用它作为方法装饰器:

import Vue from 'vue'
import Component from 'vue-class-component'
import { Log } from './decorators'

@Component
class MyComp extends Vue {
  // It prints a log when `hello` method is invoked.
  @Log
  hello(value) {
    // ...
  }
}

在上面的代码中,当用42作为参数调用hello方法时,将打印以下日志:

Invoked: hello( 42 )

继承(Extend)和混入(Mixins)

继承(Extend)

您可以将现有的类组件扩展为本地类继承。假设你有以下父类组件:

// super.js
import Vue from 'vue'
import Component from 'vue-class-component'

// Define a super class component
@Component
export default class Super extends Vue {
  superValue = 'Hello'
}

你可以通过使用原生类继承语法来扩展它:

import Super from './super'
import Component from 'vue-class-component'

// Extending the Super class component
@Component
export default class HelloWorld extends Super {
  created() {
    console.log(this.superValue) // -> Hello
  }
}

注意,每个父类都必须是类组件。换句话说,它需要继承Vue构造函数,并由@Component装饰器进行装饰。

混入(Mixins)

Vue类组件提供了mixins 函数,以类样式的方式使用mixins。通过使用mixins函数,TypeScript可以推断出mixin类型,并在组件类型上继承它们。

声明mixins HelloWorld的例子:

// mixins.js
import Vue from 'vue'
import Component from 'vue-class-component'

// You can declare mixins as the same style as components.
@Component
export class Hello extends Vue {
  hello = 'Hello'
}

@Component
export class World extends Vue {
  world = 'World'
}

在类样式组件中使用它们:

import Component, { mixins } from 'vue-class-component'
import { Hello, World } from './mixins'

// Use `mixins` helper function instead of `Vue`.
// `mixins` can receive any number of arguments.
@Component
export class HelloWorld extends mixins(Hello, World) {
  created () {
    console.log(this.hello + ' ' + this.world + '!') // -> Hello World!
  }
}

和父类一样,所有的mixin都必须声明为类组件。

类组件的注意事项

Vue类组件通过在底层实例化原始构造函数来收集类属性作为Vue实例数据。虽然我们可以像原生类方式那样定义实例数据,但有时需要知道它是如何工作的。

属性初始化项中的this

如果你定义一个箭头函数作为一个类属性并在其中访问它,它将不起作用。这是因为在初始化类属性时,这只是Vue实例的代理对象:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class MyComp extends Vue {
  foo = 123

  // DO NOT do this
  bar = () => {
    // Does not update the expected property.
    // `this` value is not a Vue instance in fact.
    this.foo = 456
  }
}

在这种情况下,你可以简单地定义一个方法而不是类属性,因为Vue会自动绑定实例:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class MyComp extends Vue {
  foo = 123

  // DO this
  bar() {
    // Correctly update the expected property.
    this.foo = 456
  }
}

使用生命周期钩子而不是构造函数(constructor)

由于原始构造函数被调用来收集初始组件数据,所以建议不要自己声明构造函数:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class Posts extends Vue {
  posts = []

  // DO NOT do this
  constructor() {
    fetch('/posts.json')
      .then(res => res.json())
      .then(posts => {
        this.posts = posts
      })
  }
}

上面的代码打算在组件初始化时获取post列表,但是由于Vue类组件的工作方式,该获取将被意外地调用两次。

建议编写生命周期钩子,比如create而不是constructor:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class Posts extends Vue {
  posts = []

  // DO this
  created() {
    fetch('/posts.json')
      .then(res => res.json())
      .then(posts => {
        this.posts = posts
      })
  }
}

TypeScript 指南

Props定义

Vue类组件没有为props定义提供专用的API。但是,您可以使用Vue.extendAPI:

<template>
  <div>{{ message }}</div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'

// Define the props by using Vue's canonical way.
const GreetingProps = Vue.extend({
  props: {
    name: String
  }
})

// Use defined props by extending GreetingProps.
@Component
export default class Greeting extends GreetingProps {
  get message(): string {
    // this.name will be typed
    return 'Hello, ' + this.name
  }
}
</script>

通过Vue.extend扩展定义的prop类型,可以在类组件中使用它们。

如果你有一个父类组件或mixins要扩展,使用mixins帮助将定义的props与它们组合起来:

<template>
  <div>{{ message }}</div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component, { mixins } from 'vue-class-component'
import Super from './super'

// Define the props by using Vue's canonical way.
const GreetingProps = Vue.extend({
  props: {
    name: String
  }
})

// Use `mixins` helper to combine defined props and a mixin.
@Component
export default class Greeting extends mixins(GreetingProps, Super) {
  get message(): string {
    // this.name will be typed
    return 'Hello, ' + this.name
  }
}
</script>

属性声明

有时候,你必须从类组件中定义组件属性和方法。例如,Vue的官方状态管理库 Vuex提供了mapGetters和mapActions助手来将存储映射到组件属性和方法。这些帮助程序需要在组件选项对象中使用。

即使在这种情况下,你也可以把组件选项传递给@Component装饰器的参数。但是,当属性和方法在运行时工作时,它不会在类型级别上自动声明它们。

你需要在class组件中手动声明它们的类型:

import Vue from 'vue'
import Component from 'vue-class-component'
import { mapGetters, mapActions } from 'vuex'

// Interface of post
import { Post } from './post'

@Component({
  computed: mapGetters([
    'posts'
  ]),

  methods: mapActions([
    'fetchPosts'
  ])
})
export default class Posts extends Vue {
  // Declare mapped getters and actions on type level.
  // You may need to add `!` after the property name
  // to avoid compilation error (definite assignment assertion).

  // Type the mapped posts getter.
  posts!: Post[]

  // Type the mapped fetchPosts action.
  fetchPosts!: () => Promise<void>

  mounted() {
    // Use the mapped getter and action.
    this.fetchPosts().then(() => {
      console.log(this.posts)
    })
  }
}

$refs 扩展

组件的$refs类型被声明为最广泛的类型,以处理所有可能的ref类型。虽然理论上它是正确的,但在大多数情况下,每个ref实际上只有一个特定的元素或组件。

你可以通过重写类组件中的$refs类型来指定一个特定的ref类型:

<template>
  <input ref="input">
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class InputFocus extends Vue {
  // annotate refs type.
  // The symbol `!` (definite assignment assertion)
  // is needed to get rid of compilation error.
  $refs!: {
    input: HTMLInputElement
  }

  mounted() {
    // Use `input` ref without type cast.
    this.$refs.input.focus()
  }
}
</script>

您可以通过$refs访问输入类型而不需要进行类型转换。输入类型是在上面示例中的class组件上指定的。

注意,它应该是一个类型注释(使用冒号:)而不是值赋值(=)。

Hooks 自动完成

Vue类组件提供了内置的钩子类型,可以在类组件声明中自动完成数据、渲染和其他生命周期钩子。要启用它,您需要导入位于vue-class-component/hooks中的钩子类型。

// main.ts
import 'vue-class-component/hooks' // import hooks type to enable auto-complete
import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')

如果你想让它与自定义钩子一起工作,你可以自己手动添加它:

import Vue from 'vue'
import { Route, RawLocation } from 'vue-router'

declare module 'vue/types/vue' {
  // Augment component instance type
  interface Vue {
    beforeRouteEnter?(
      to: Route,
      from: Route,
      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
    ): void

    beforeRouteLeave?(
      to: Route,
      from: Route,
      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
    ): void

    beforeRouteUpdate?(
      to: Route,
      from: Route,
      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
    ): void
  }
}

在装饰器中注释组件类型

有些情况下,你想在@Component装饰器参数中的函数上使用组件类型。例如,要访问一个监视处理程序中的组件方法:

@Component({
  watch: {
    postId(id: string) {
      // To fetch post data when the id is changed.
      this.fetchPost(id) // -> Property 'fetchPost' does not exist on type 'Vue'.
    }
  }
})
class Post extends Vue {
  postId: string

  fetchPost(postId: string): Promise<void> {
    // ...
  }
}

上面的代码产生了一个类型错误,它表明fetchPost在watch处理程序中不存在。之所以会出现这种情况,是因为@Component装饰器参数中的这种类型是基于Vue类型。

要使用自己的组件类型(本例中为Post),可以通过它的类型参数来注释装饰器。

// Annotate the decorator with the component type 'Post' so that `this` type in
// the decorator argument becomes 'Post'.
@Component<Post>({
  watch: {
    postId(id: string) {
      this.fetchPost(id) // -> No errors
    }
  }
})
class Post extends Vue {
  postId: string

  fetchPost(postId: string): Promise<void> {
    // ...
  }
}