# 前言

前文中写了 Ts 的一些类型、属性等简单语法,以及类、接口、装饰等高级用法后,今天我们来看看如何在 vue 项目中使用 ts,目前使用公司项目用的较多的还是 vue2.x,vue 是渐进式的框架,我们学习也要渐进式的,所以本文也先围绕 vue2.x 来对 Ts 进行实战,为后期切换 vue3.0 打下基础。

# 简介

在使用前我们要先在项目中安装Ts,安装过程就不细说了,如果只是学习,推荐用vue的官方脚手架,里面就带有安装Ts选项。接着要安装下vue-class-componentvue-property-decorator

安装之前我们先了解下 vue-class-componentvue-property-decorator

vue-class-componentvue 的官方库,作用是以 class 的模式编写组件。这种编写方式使 vue 组件可以使用继承、混入等高级特性,更重要的是使 Vue 组件更好的跟 TS 结合使用。

vue-property-decorator 是社区出的, 基于 vue-class-component 拓展出了很多操作符 @Prop @Emit @Inject 等;可以说是 vue-class-component 的一个超集, 使代码更为简洁明了,options 里面需要配置 decorator 库不支持的属性, 比如 components, filters, directives 等。

这两者都是离不开装饰器的,装饰器已在 ES 提案中。decorator是装饰器模式的实践。装饰器模式呢,它是继承关系的一个替代方案。动态地给对象添加额外的职责。在不改变接口的前提下,增强类的功能。

# 使用

# Component

装饰器可以接收一个对象作为参数,可以在对象中声明 componentsfiltersdirectives 等未提供装饰器的选项。

<template>
  <div class="home">
    {{ num | addOne('过滤器第二个参数') }}
    <Test ref="helloWorld" v-test="'h'" />
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Test from '@/components/Test.vue';
@Component({
  name: 'Home',
  //组件
  components: {
    Test,
  },
  //局部指令
  directives: {
    test(el: HTMLElement, binding) {
      console.log('DOW:', el, '局部指令:', binding);
    },
  },
  // 局部过滤
  filters: {
    addOne(num: number, towParam: string) {
      console.log(towParam, '局部过滤器');
      return num + 3;
    },
  },
  //混入
  // mixins: [ResizeMixin]
})
export default class extends Vue {
  private num: number = 1; //定义一个变量
}
</script>

提示

要使用 Ts 需要在 script 标签的 lang 属性值设为 ts

# 生命周期

<template>
  <div class="home"></div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component({
  name: 'LifeCycle',
})
export default class extends Vue {
  private num = 1;
  private created(): void {
    console.log(this.num);
  }
  private mounted(): void {
    console.log(this.num);
  }
}
</script>

# 方法、属性

<template>
  <div class="home">
    <button @click="addAge">加1</button>
    {{ num }}
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component({
  name: 'AttrMethod',
})
export default class extends Vue {
  private num = 1; //属性
  private checked = true;
  //方法
  private addAge(): void {
    this.num++;
    this.checked = false;
  }
  private mounted(): void {
    console.log(this.num);
  }
}
</script>

# computer(计算属性)

<template>
  <div class="computer">
    {{ count(this.num, 2) }}
    {{ msg }}
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component({
  name: 'Computers',
})
export default class extends Vue {
  private num = 1;
  private mounted(): void {
    console.log(this.num);
  }
  /*计算属性*/
  //传参写法
  private get count() {
    return function(num: number, numbers: number) {
      return num + numbers;
    };
  }
  //普通写法
  private get msg() {
    return '普通写法的计算属性';
  }
}
</script>

# watch(监听)

<template>
  <div class="watch">
    {{ num }}
  </div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';
@Component({
  name: 'Watch',
})
export default class extends Vue {
  private num = 1;
  private mounted(): void {
    this.timeOut();
  }
  private timeOut() {
    setTimeout(() => {
      this.num++;
    }, 1000);
  }
  //监听
  @Watch('num', { immediate: true, deep: true })
  onNumChange(val: string, old: string) {
    console.log(val, old, 'watch');
  }
}
</script>

提示

onNumChange 方法要紧挨着@Watch,它们中间不能有其他代码,而且这个方法名称可以自定义,没有强制要求。

# ref

<template>
  <div class="watch">
    <img alt="Vue logo" src="../assets/logo.png" ref="img" />
    {{ num }}
    <Test ref="test" />
  </div>
</template>
<script lang="ts">
import { Component, Vue, Ref } from 'vue-property-decorator';
import Test from '@/components/Test.vue';
@Component({
  name: 'Watch',
  components: {
    Test,
  },
})
export default class extends Vue {
  private num = 1;
  @Ref() readonly test!: Test; //引入的组件的ref
  @Ref('img') readonly img!: HTMLButtonElement; //普通html标签的ref
  private mounted(): void {
    console.log('普通的ref使用方式:', this.$refs.test, '定义变量的ref使用方式:', this.test, '引入组件的ref');
    console.log(this.img, this.$refs.img, '普通img标签的ref');
  }
}
</script>

# 依赖注入

  • Provide
<template>
  <div class="home">
    <Inject ref="helloWorld" />
  </div>
</template>
<script lang="ts">
// @ is an alias to /src
/*eslint-disable */
import { Component, Vue, Provide } from 'vue-property-decorator';
import Inject from '@/components/Inject.vue';
const symbol = Symbol('baz');
//装饰器注明此类
@Component({
  name: 'Provide',
  components: {
    Inject,
  },
  // mixins: [ResizeMixin]
})
export default class extends Vue {
  @Provide() foo = 'foo'; //依赖注入
  @Provide() optional = 'optional'; //依赖注入
  @Provide('bar') baz = 'bar';
}
</script>
  • Inject
<template>
  <div class="hello">
    <h1 @click="returnValue">{{ msg }}</h1>
  </div>
</template>
<script lang="ts">
import {
  Component,
  Vue,
  Inject
} from 'vue-property-decorator'
@Component
export default class HelloWorld extends Vue {
  @Inject() readonly foo!: string //接收依赖注入的值
  @Inject({ from: 'optional', default: 'default' }) readonly optional!: string //父组件,爷爷组件没传optional时,使用default设置默认值
  @Inject('bar') readonly bar!: string
  private moun ted(): void {
    console.log( 22, this.foo, this.optional, this.bar)
  }
}
</script>

# Prop

子组接收父组件传进来的值

  • 父组件
<template>
  <div class="home">
    <Props :msg="msg" prop-c="11" />
  </div>
</template>
<script lang="ts">
// @ is an alias to /src
/*eslint-disable */
import { Component, Vue } from 'vue-property-decorator';
import Props from '@/components/Prop.vue';
const symbol = Symbol('baz');
//装饰器注明此类
@Component({
  name: 'Prop',
  components: {
    Props,
  },
})
export default class extends Vue {
  private msg: string = 'hello';
  private name: string = 'sss';
  private checked: boolean = true;
  private num: number = 1;
}
</script>
  • 子组件
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <span>{{ propB }}</span>
    <span>{{ propC }}</span>
  </div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string; //!,非null和undefined
  @Prop(Number) readonly propA: number | undefined;
  @Prop({ default: 'default value' }) readonly propB!: string;
  @Prop([String, Boolean]) readonly propC: string | boolean | undefined;
}
</script>

# Emit

向父组件发射个方法

  • 父组件
<template>
  <div class="home">
    <EmitChild @return-value="returnValue" />
  </div>
</template>
<script lang="ts">
// @ is an alias to /src
/*eslint-disable */
import { Component, Vue } from 'vue-property-decorator';
import EmitChild from '@/components/Emit.vue';
const symbol = Symbol('baz');
//装饰器注明此类
@Component({
  name: 'Emit',
  components: {
    EmitChild,
  },
})
export default class extends Vue {
  private returnValue(aa: number): void {
    console.log(aa);
  }
}
</script>
  • 子组件
<template>
  <div class="hello">
    <p @click="returnValue">
      emit
    </p>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Emit } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
  @Emit() //把方法发射出去可以让父组件使用
  returnValue() {
    return 10;
  }
}
</script>
  • PropSync

实现 sync 修饰符(prop 双向绑定)

  • 父组件
<template>
  <div>
    <button @click="exportName">输出name</button>
    <PropSyncChild :name.sync="name" />
  </div>
</template>
<script lang="ts">
// @ is an alias to /src
/*eslint-disable */
import { Component, Vue } from 'vue-property-decorator';
import PropSyncChild from '@/components/PropSync.vue';
//装饰器注明此类
@Component({
  name: 'PropSync',
  components: {
    PropSyncChild,
  },
})
export default class extends Vue {
  private name: string = 'sss';
  exportName(): void {
    console.log(this.name);
  }
}
</script>
  • 子组件
<template>
  <div class="hello">
    <p @click="setSyncedName">
      我是子组件: 同步、子组件修改父组件
    </p>
  </div>
</template>
<script lang="ts">
import { Component, Vue, PropSync } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
  @PropSync('name', { type: String }) syncedName!: string; //同步,可让子组件修改父组件的值
  public setSyncedName(): void {
    console.log('prop双向绑定');
    this.syncedName = '同步、子组件修改父组件';
  }
}
</script>

# Model

实现 v-model 双向绑定

  • 父组件
<template>
  <div>
    <button @click="setChecked">修改checked</button>
    <ModelChild v-model="checked" />
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ModelChild from '@/components/Model.vue';
//装饰器注明此类
@Component({
  name: 'Model',
  components: {
    ModelChild,
  },
})
export default class extends Vue {
  private checked = false;
  setChecked(): void {
    this.checked = !this.checked;
  }
}
</script>
  • 子组件
<template>
  <div class="hello">我是子组件的checked: {{ checked }}</div>
</template>
<script lang="ts">
import { Component, Vue, Model } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
  @Model('change', { type: Boolean }) readonly checked!: boolean; //v-model
}
</script>

带你走进 TS(二) (opens new window)