在 vue2.x 下使用 typescript
前言
今天我们来看看如何在 vue 项目中使用 ts,目前使用公司项目用的较多的还是 vue2.x,vue 是渐进式的框架, 我们学习也要渐进式的, 所以本文也先围绕 vue2.x 来对 Ts 进行实战, 为后期切换 vue3.0 打下基础。
简介
在使用前我们要先在项目中安装 Ts, 安装过程就不细说了, 如果只是学习, 推荐用 vue 的官方脚手架, 里面就带有安装 Ts 选项。接着要安装下 vue-class-component
和 vue-property-decorator
安装之前我们先了解下 vue-class-component
和 vue-property-decorator
vue-class-component
是 vue 的官方库, 作用是以 class 的模式编写组件。这种编写方式使 vue 组件可以使用继承、混入等高级特性,更重要的是使 Vue 组件更好的跟 TS 结合使用。
vue-property-decorator
是社区出的, 基于 vue-class-component
拓展出了很多操作符 @Prop @Emit @Inject 等;可以说是 vue-class-component
的一个超集, 使代码更为简洁明了, options
里面需要配置 decorator
库不支持的属性, 比如 components
, filters
, directives
等。
这两者都是离不开装饰器的, 装饰器已在 ES 提案中。decorator
是装饰器模式的实践。装饰器模式呢, 它是继承关系的一个替代方案。动态地给对象添加额外的职责。在不改变接口的前提下, 增强类的功能。
使用
Component
装饰器可以接收一个对象作为参数, 可以在对象中声明 components
, filters
, directives
等未提供装饰器的选项。
<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>