vue + typescript 类组件教程

时间:2022-07-24
本文章向大家介绍vue + typescript 类组件教程,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

typescript 简介

TypeScriptJavaScript 的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。

TypeScriptJavaScript 的超集,这意味着他支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。这样会大大提升代码的可阅读性。

与此同时,TypeScript 也是 JavaScript ES6 的超集,GoogleAngular 2.0 也宣布采用 TypeScript 进行开发。这更是充分说明了这是一门面向未来并且脚踏实地的语言。

强类型语言的优势在于静态类型检查,具体可以参见 http://www.zhihu.com/question... 的回答。概括来说主要包括以下几点:

  1. 静态类型检查
  2. IDE 智能提示
  3. 代码重构
  4. 可读性

❝静态类型检查可以避免很多不必要的错误, 不用在调试的时候才发现问题 ❞

项目搭建

  • Generate project in current directory?是否在当前目录创建项目
  • Choose Vue version,Babel,Ts,Router,Vuex,CSS Pre-processor
  • Use class-style component syntax?
  • Use Babel alongside Typescript
  • Use history mode for router?
  • Pick a CSS Pre-processor:Less
  • Where do you prefer placing config for Babel,ESLint,
  • Save this as a preset for future project?
  • Save preset as

可能需要等待几分钟安装依赖,安装成功之后,使用 yarn serve 运行

概览

Vue 类组件是一个库,可让你使用类的语法制作 Vue 组件。例如,我们可以使用 Vue 类语法制作一个计算器组件:

src/components 新建 Counter.vuehtml 部分跟往常一样。

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

重点部分就是 javascript

您可以通过使用@Component装饰器为类添加注释,从而以直观和标准的类语法定义组件数据和方法。您可以简单地用类样式的组件替换组件定义,因为它等同于组件定义的普通options对象样式。

通过以类样式定义组件,不仅可以更改语法,还可以利用某些ECMAScript语言功能,例如类继承和装饰器。Vue类组件还提供了一个用于mixin继承的mixins助手,以及一个轻松创建自己的装饰器的createDecorator函数。

您可能还需要检查Vue Property Decorator提供的@Prop@Watch装饰器

<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>

src/views/About.vue 引入 Couter.vue

通过 vue-class-component 导入 Component 装饰器函数,注册局部组件 Couter

<template>
<div class="about">
    <h1>This is an about page</h1>
    <Counter />
</div>
</template>

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

@Component({
    components: {
        Counter
    }
})
export default class About extends Vue {

}
</script>

效果如下:

类组件

数据

我们可以这样初始化 data 数据:

About 类组件中,定义 message 变量,在模板中使用 {{}} 插值。

<template>
<div class="about">
    <h1>{{message}}</h1>
    <Counter />
</div>
</template>

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

@Component({
    components: {
        Counter
    }
})
export default class About extends Vue {
    message = "This is an about page"
}
</script>

上面的组件按原样在组件数据中呈现Hello World!<h1>元素中message

请注意,如果初始值为undefined,则class属性不会是反应性的,这意味着将不会检测到对属性的更改:

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

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

为了避免这种情况,您可以使用nullvalue或使用datahook来代替:

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

@Component
export default class About 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 About extends Vue {
  // Declared as component method
  hello() {
    console.log('Hello World!')
  }
}
</script>

计算属性

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

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

@Component
export default class About 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>

生命钩子

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

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

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

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

组件注册

<template>
<div class="about">
    <h1>This is an about page</h1>
    <Counter />
</div>
</template>

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

@Component({
    components: {
        Counter
    }
})
export default class About extends Vue {

}
</script>

路由钩子

如果使用Vue Router等Vue插件,则可能希望类组件解析它们提供的钩子。在这种情况下,Component.registerHooks允许您注册这样的钩子:

<template>
<div class="about">
    <h1>{{message}}</h1>
    <Counter />
    <button v-on:click="hello">Click</button>
    <input v-model="name" />
</div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";
import Counter from "../components/Counter";
// Register the router hooks with their names
Component.registerHooks([
    'beforeRouteEnter',
    'beforeRouteLeave',
    'beforeRouteUpdate'
])

@Component({
    components: {
        Counter
    }
})
export default class About extends Vue {
    message = "This is an about page";
    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] || "";
    }
    hello() {
        console.log("Hello World!");
    }
    // Declare mounted lifecycle hook
    mounted() {
        console.log("mounted");
    }

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

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

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

    beforeRouteLeave(to, from, next) {
        console.log('beforeRouteLeave')
        next()
    }
}
</script>

定制装饰器

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

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

创建Log装饰器的示例,该装饰器在调用装饰的方法时输出带有方法名称和传递的参数的日志消息:

src/components/ 新建 decorators.js

// 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)
  }
})

使用它作为方法装饰器:

<template>
<div class="about">
    <h1>{{message}}</h1>
    <Counter />
    <button v-on:click="hello">Click</button>
    <input v-model="name" />
</div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";
import Counter from "../components/Counter";
import {
    Log
} from '../components/decorators'
// Register the router hooks with their names
Component.registerHooks([
    'beforeRouteEnter',
    'beforeRouteLeave',
    'beforeRouteUpdate'
])

@Component({
    components: {
        Counter
    }
})
export default class About extends Vue {
    message = "This is an about page";
    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] || "";
    }
    @Log
    hello(value) {
        console.log("Hello World!", value);
    }
    // Declare mounted lifecycle hook
    mounted() {
        console.log("mounted");
    }

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

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

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

    beforeRouteLeave(to, from, next) {
        console.log('beforeRouteLeave')
        next()
    }
}
</script>

扩展和混合

扩展

您可以将现有的类组件扩展为本机类继承。假设您具有以下超类组件:

src/components/ 新建 super.js

// 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'
}

您可以使用本机类继承语法对其进行扩展:

<template>
<div class="about">
    <h1>{{message}}</h1>
    <Counter />
    <button v-on:click="hello">Click</button>
    <input v-model="name" />
</div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";
import Counter from "../components/Counter";
import {
    Log
} from '../components/decorators'
import Super from '../components/super'
// Register the router hooks with their names
Component.registerHooks([
    'beforeRouteEnter',
    'beforeRouteLeave',
    'beforeRouteUpdate'
])

@Component({
    components: {
        Counter
    }
})
export default class About extends Super {
    message = "This is an about page";
    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] || "";
    }
    @Log
    hello(value) {
        console.log("Hello World!", value);
    }
    // Declare mounted lifecycle hook
    mounted() {
        console.log("mounted");
    }
    created() {
        console.log(this.superValue)
    }
    // Declare render function
    render() {
        return <div > Hello World! < /div>;
    }

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

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

    beforeRouteLeave(to, from, next) {
        console.log('beforeRouteLeave')
        next()
    }
}
</script>

混入

Vue类组件提供了mixins辅助功能,以类样式方式使用mixins。通过使用mixins帮助程序,TypeScript可以推断混合类型并在组件类型上继承它们。

声明mixins Hello和的示例World

// 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 About extends mixins(Hello, World) {
  created () {
    console.log(this.hello + ' ' + this.world + '!') // -> Hello World!
  }
}

类组件警告

Vue类组件通过实例化底层的原始构造函数,将类属性收集为Vue实例数据。尽管我们可以像本地类方式那样定义实例数据,但有时我们需要知道其工作方式。

this 属性初始值设定项中的值

如果将箭头函数定义为类属性并对其进行访问this,则它将无法正常工作。这是因为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

当调用原始构造函数以收集初始组件数据时,建议不要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
      })
  }
}

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

建议写生命周期挂钩,例如created,而不是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
      })
  }
}

Prop 属性定义

Vue类组件没有提供用于道具定义的专用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类型时,可以通过扩展它在类组件中使用它们。

如果您要扩展一个超类组件或mixin,请使用mixins辅助程序将定义的prop与它们组合:

<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>