资源
1. TypeScript参考:http://www.typescriptlang.org2. Vue+TypeScript:https://cn.vuejs.org/v2/guide/typescript.html
知识点
1. ts核心语法2. ts+vue
3. 装饰器应用4. 装饰器原理5. vue-property-decorator源码解析
准备工作
新建一个基于ts的vue项目
在已存在项目中安装typescript
命令:vue add @vue/typescript
请暂时忽略引发的几处Error,它们不会影响项目运行,我们将在后面处理它们。
TS特点
类型注解、类型检测
类
接口
泛型
装饰器
类型声明
类型注解和编译时类型检查
使用类型注解约束变量类型,编译器可以做静态类型检查,使程序更加健壮
类型基础
main.js中引入:
let var1: string; // 类型注解 var1 = "尤雨溪真帅"; // 正确var1 = 4; // 错误// 编译器类型推断可省略这个语法 let var2 = true;var2 = 4; // 错误// 常见原始类型: string,number,boolean,undefined,null,symbol// 类型数组let arr: string[];arr = ['Tom']; // 或Array// 任意类型any let varAny: any; varAny = 'xx'; varAny = 3;// any类型也可用于数组let arrAny: any[];arrAny = [1, true, "free"]; arrAny[1] = 100;// 函数中的类型约束function greet(person: string): string { return 'hello, ' + person;}// void类型,常用于没有返回值的函数 function warn(): void {}
范例,HelloWorld.vue
<template> <div> <ul> <li v-for="feature in features" :key="feature">{{feature}}li> ul> div>template><script lang='ts'>import { Component, Prop, Vue } from "vue-property-decorator";//方式一:class-style(用的比较多)@Componentexport default class Hello extends Vue { @Prop() private msg!:string; //属性将成为data中数据 features: string[] = ["类型注解", "编译型语言"]; }//方式二:option-style(官方:该方式会稍微有点问题)import Vue form 'vue'export default Vue.extend({data(this)})//this出现导致ts编译无法知道一些动态属性,经常情况下this.$XX,导致很难推断script>
方式三:jsx方式
特点:上下文很清楚;代码提示很强,可以做类型检测
使用:
类型别名
使用类型别名自定义类型
// 可以用下面这样方式定义对象类型const objType: { foo: string, bar: string }// 使用type定义类型别名,使用更便捷,还能复用 type Foobar = { foo: string, bar: string } const aliasType: Foobar
接口和类型别名是可以互换的,没有什么区别。对ide有影响,程序本身没影响。2.7版本之前不支持类型别名。对于通用性来讲,接口比别名更好一些。
范例:使用类型别名定义Feature,types/index.ts
export type Feature = { id: number, name: string}
使用自定义类型,HelloWorld.vue
<template> <div> <li v-for="feature in features" :key="feature.id">{{feature.name}}li> div>template><script lang='ts'>// 导入接口import { Feature } from "@/types";@Componentexport default class Hello extends Vue {// 修改数据结构features: Feature[] = [{ id: 1, name: "类型注解" }]; }script>
联合类型
希望某个变量或参数的类型是多种类型其中之一
let union: string | number;union = '1'; // okunion = 1; // ok
使用:扩大范围:
交叉类型
想要定义某种由多种类型合并而成的类型使用交叉类型
type First = {first: number};type Second = {second: number};// FirstAndSecond将同时拥有属性first和second type FirstAndSecond = First & Second;
范例:利用交叉类型给Feature添加一个selected属性
// types/index.tsexport type Feature={ id:number; name:string;}type Select = { selected: boolean}export type FeatureSelect = Feature & Select
使用:
使用这个FeatureSelect,HelloWorld.vue
features: FeatureSelect[] = [ { id: 1, name: "类型注解", selected: false }, { id: 2, name: "编译型语言", selected: true }];
class="{selected: feature.selected}">{{feature.name}}</li>
.selected { background-color: rgb(168, 212, 247);}
函数
必填参:参数一旦声明,就要求传递,且类型需符合
function greeting(person: string): string { return "Hello, " + person;}greeting('tom')
可选参数:参数名后面加上问号,变成可选参数
function greeting(person: string, msg?: string): string { return "Hello, " + person;}
默认值(默认值与?可选冲突)默认值与可选参数放在必须参数后面
function greeting(person: string, msg = ''): string { return "Hello, " + person;}
*函数重载:以参数数量或类型区分多个同名函数,先声明,再实现。
// 重载1function watch(cb1: () => void): void;// 重载2function watch(cb1: () => void, cb2: (v1: any, v2: any) => void): void; // 实现function watch(cb1: () => void, cb2?: (v1: any, v2: any) => void) { if (cb1 && cb2) { console.log('执行watch重载2'); } else { console.log('执行watch重载1'); }}
范例:新增特性,Hello.vue
<div><input type="text" placeholder="输入新特性" @keyup.enter="addFeature">div>
addFeature(e: KeyboardEvent) {//类型断言// e.target是EventTarget类型,需要断言为HTMLInputElement const inp = e.target as HTMLInputElement;const feature: FeatureSelect = { id: this.features.length + 1, name: inp.value, selected: false } this.features.push(feature); inp.value = "";}
范例:生命周期钩子,Hello.vue
created() { this.features = [{ id: 1, name: "类型注解" }];}
类
class的特性
ts中的类和es6中大体相同,这里重点关注ts带来的访问控制等特性
class Parent { private _foo = "foo"; // 私有属性,不能在类的外部访问 protected bar = "bar"; // 保护属性,可以在子类中访问 // 参数属性:构造函数参数加修饰符,能够定义为成员属性 constructor(public tua = "tua") {} // 方法也有修饰符 private someMethod() {} // 存取器:属性方式访问,可添加额外逻辑,控制读写性 get foo() { return this._foo; } set foo(val) { this._foo = val; }}
范例:利用getter设置计算属性,Hello.vue
<template> <li>特性数量:{{count}}li>template><script lang="ts">export default class HelloWorld extends Vue { // 定义getter作为计算属性 get count() { return this.features.length; }}script>
接口
接口仅约束结构,不要求实现,使用更简单
// Person接口定义了结构 interface Person { firstName: string; lastName: string;}// greeting函数通过Person接口约束参数解构 function greeting(person: Person) { return 'Hello, ' + person.firstName + ' ' + person.lastName;}greeting({firstName: 'Jane', lastName: 'User'}); // 正确 greeting({firstName: 'Jane'}); // 错误
范例:Feature也可用接口形式约束,./types/index.ts
// 接口中只需定义结构,不需要初始化 export interface Feature {id: number; name: string;}
Interface vs type aliases:https://www.typescriptlang.org/docs/handbook/advanced-types.html#interfaces-vs-type-aliases
恭喜你,今天又进步了一点点!