前言
JavaScript是一种弱类型(或称动态类型)语言,即变量的类型是不确定的。如:
let x = 5; // 5
x = x + 'A'; // '5A' 进行了隐式类型转换
类型完全由当前的值决定,这样看起来十分灵活,代码也简洁,但是对于大型项目来说,强类型更有利,可以降低系统的复杂性。
// JS
function getData(url, params) {
...
}
// TS
interface ParamInterface {
id: string;
name: string;
}
function getData(url:string, param: ParamInterface) {
...
}
现有的能让javascript转成强类型语言有几种解决方案:TypeScript、FlowCheck、Flow(Flow,Facebook在2014年发布的一个类型检查工具,用来检查React的源码,也引入了Flow做静态类型检查)
TypeScript介绍
基础类型
接口
类
函数
泛型
高级类型
基础类型
布尔值
let isDone:boolean = false;
数字
let decLiteral:number = 6; // 十进制
let hexLiteral:number = 0xfood; // 十六进制
let binaryLiteral:number = 0b1010; // 二进制
let octalLiteral:number = 0o744; // 八进制
- 十进制:没有前导0的数值
- 八进制:有前缀0o或0O的数值,或者有前导0且只用到0-7的八个阿拉伯数字的数值
- 十六进制:有前缀0x或0X的数值
- 二进制:有前缀0b或0B的数值
数组
let list:number[] = [1,2,3];
// 或者数组泛型
let list: Array<number> = [1,2,3];
// 混合数组
let list:number[] = [1, 2, 3, 'a', 'b']; // 报错Type 'string' is not assignable to type 'number'
// 可使用联合类型来自定义
let list: (string|number)[] = [1, 2, 3, 'a', 'b'];
元组 Tuple
let list: [string, string, number] = ['张三', '男', 20];
// 使用场景
let list: [string,string,number][] = [
['张三', '男', 20],
['李四', '男', 21],
['王五', '男', 22],
]
枚举
if (res.data === '0') {
// 处理操作1
} else if (res.data === '1') {
// 处理操作2
}
// 换成枚举
enum Status {on, off}
if (res.data === Status.on) {
// 处理操作1
} else if (res.data === Status.off) {
// 处理操作2
}
// 可以是数字枚举或者是字符串枚举
enum Direction {
Up,
Down,
Left,
Right,
}
enum Direction {
Up = 1,
Down,
Left,
Right,
}
enum Direction {
Up = 'up',
Down = 'down',
Left = 'left',
Right = 'right',
}
// ts中的枚举类型和普通的JS对象本质上没有区别,
// 只是对于开发者来说,相较于直接使用值去做判断,枚举类型更易读,能够提升代码的可读性和易维护性
Any
// 不清楚变量具体是哪个类型的时候使用any,类型检查器不会对这些值进行检查而是直接让它们通过编译阶段
let notSure: any = 4;
notSure = 'maybe a string instead';
let list: any[] = [1, true, 'free'];
Void
// void类型像是与any类型相反,它表示没有任何类型,主要用在函数上
function warnUser():void {
console.log('This is my warning message');
}
Null和Undefined
let u: undefined = undefined;
let n: null = null;
Never
// never类型表示的是那些永不存在的值的类型,never类型常用在会抛出异常或根本就不会有返回值的函数中
function error(message: string):never {
throw new Error(message);
}
Object
表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型
类型断言
// 操作any类型的变量的时候,大多数情况都会用类型断言
let someValue: any = 'this is a string';
// 用法一:尖括号
let strLength: number = (<string>someValue).length;
// 用法二:as
let strLength: number = (someValue as string).length;
接口
TypeScript的核心原则之一是对值的结构进行类型检查,而接口可以对这些值的结构约束,包括对象、函数以及类的结构和类型。
描述普通对象
interface PersonInterface {
name: string,
age: number,
}
let person: PersonInterface = {
name: '张三',
age: 18,
gender: '男', // 加这行报错
}
// 类型兼容性
const person1 = {
name: '张三',
age: 18,
gender: '男',
}
person: PersonInterface = person1;
// 可选属性
interface PersonInterface {
name: string,
age?: number,
}
person: personInterface = {
name: '张三',
}
// 只读属性
interface PersonInterface {
readonly name: string,
readonly age: number,
}
person: PersonInterface = {
name: '张三',
age: 18,
}
person.age = 20; // 报错
// 字符串索引签名,可以有任意数量的属性
interface PersonInterface {
name: string,
age: number,
[propName: string]: any
}
person: PersonInterface = {
name: '远扬',
age: 18,
gender: 'F',
phone: '2312'
}
索引签名
// 数字索引
// 表示了当用number去索引StringArray时会得到string类型的返回值
interface StringArray {
[index:number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr:string = myArray[0];
// 字符串索引
interface ScoreMap = {
[subject:string]: number;
}
let data:ScoreMap = {
age: 18,
height: 178,
}
// 数字索引和字符串索引相结合,数字索引的返回值必须是字符串索引值返回值类型的子类型
interface ScoreMap {
[index:number]:string;
[name:string]:string;
}
let data: ScoreMap = {
0: 'index',
hobby: 'play',
}
描述函数类型
interface SearchFunc {
(source:string,subString:string):boolean;
}
let mySearch: SearchFunc;
mySearch = function(src:string,sub:string) {
let result = source.search(subString);
return result > -1;
}
描述类类型
interface test {
name: string,
value: number,
}
// 实现接口,接口描述了类的公共部分
class TestClass implementes test {
name: string;
value: number;
age: number;
constructor(name:string, value:number, age:number) {
this.name = name;
this.value = value;
this.age = age;
}
}
继承接口
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
混合类型
// 可以同时作为函数和对象使用,并带有额外的属性
interface Counter {
(start: number): string;
interval: number;
reset():void;
}
function getCounter():Counter{
let counter = <Counter>function(start:number) {};
counter.interval = 123;
counter.reset = function(){};
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
类
从ECMAScript6开始,支持基于类的面向对象的方式
// 声明一个Greeter类。这个类有3个成员:一个叫做greeting的属性,一个构造函数和一个greet方法
class Greeter {
greeting: string;
constructor(message:string) {
this.greeting = message;
}
greet() {
return 'Hello ,' + this.greeting;
}
}
let greeter = new Greeter("World");
继承
通过extends关键字实现继承
class Animal {
name: string;
constructor(theName:string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
// 继承Animal类
class Dog extends Animal {
constructor(name:string) {
super(name); // 构造函数里访问this的属性之前,一定要先调用super()
console.log('name', this.name);
}
bark() {
console.log('Woof! Woof!');
}
}
// 创建一个Dog的实例
const dog = new Dog('mike');
dog.bark();
dog.move(10);
dog.bark();
修饰符
公有,私有与受保护的(默认)修饰符:public、private、protected
// private
class Animal {
private name: String;
constructor(theName:string) {this.name = theName;}
}
class Rhino extends Animal {
constructor(name:string) {super(name);}
sayName(){
console.log(this.name); // 错误: 'name'是父类私有的
}
}
const cat = new Rhino('Cat');
cat.name; // 错误:'name'是私有的
cat.sayName(); // 错误:'name'是私有的
// protected
class Animal {
protected name: string;
constructor(theName:string) {this.name = theName;}
}
class Rhino extends Animal {
constructor(name:string) {super(name);}
sayName(){
console.log(this.name); // 不会报错
}
}
const cat = new Rhino('Cat');
cat.name; // 错误:name属性是被保护的,只有在本类或者派生类中才能访问
cat.sayName(); // 不会报错
静态属性
当类被实例化的时候才会被初始化的属性
class Grid {
static origin = {x:0,y:0};
staticValue(){
console.log(Grid.origin.x); // 访问静态属性的时候,要前面加上类名
}
construtor(public scale:number) {}
}
抽象类
抽象类的抽象方法不包含具体实现并且必须在派生类中实现,主要关键字abstract
abstract class AbstractTest {
constructor(public name:string) {
}
abstract printMeeting():void; // 必须在派生类中实现
}
class Test extends AbstractTest {
constructor(){
super('test'); // 在派生类的构造函数中必须调用super()
}
printMeeting(): void {
console.log('generateReports');
}
}
let test:AbstractTest; // 允许创建一个对象类型的引用
test = new AbstractTest(); // 错误:不能创建一个抽象类的实例
test = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
test.printMeeting();
test.generateReports(); // 错误:方法在声明的抽象类中不存在
函数
定义类型
// 参数类型和返回值类型
function add(x:number, y:number):number{
return x+y;
}
// 可选参数
function buildName(firstName: string, lastName?:string) {
if(lastName)
return `${firstName}${lastName}`;
else
return firstName;
}
// 参数默认值
function buildName(firstName:string, lastName = 'Smith') {
return `${firstName}${lastName}`;
}
// 剩余参数restOfName会被当做个数不限的可选参数。可以一个都没有,同样也可以有任意个
function buildName(firstName:string, ...restOfName:string[]) {
console.log(restOfName); // ["Samuel","Lucas","MacKinzie"]
return `${firstName}${restOfName.join(" ")}`;
}
const employeeName = buildName("Joseph","Samuel","Lucas","MacKinzie");
泛型
泛型是指在定义函数、接口或者类的时候,不预先指定其类型,而是在使用的时候再手动制定,这样做的好处是可兼容多种数据类型,提高代码的复用率
// 举个简单的例子,定义一个函数,参数和返回值都是Number的类型,如果后面想改为数组、字符串的话,还要另建一个函数,可以看出该函数并不是可扩展或通用的
function identity(value:Number):Number{
return value;
}
// 使用类型变量T,可以捕获用户传入的类型
function identity<T>(arg:T):T {
return arg;
}
let output = identity<string>("myString");
// 或者
let output = identity("myString");
例子: const f = x=>x,其中f是泛型。由用户传入的数据决定是什么类型。
泛型变量
泛型中必须正确地使用这个通用的类型,即必须把这些参数当做是任意或所有类型,不能把参数当做特定类型去操作
function loggingIdentity<T>(arg:T):T{
console.log(arg.length); // Error:T doesn't have .length
return arg;
}
// 泛型数组
function loggingIdentity<T>(arg:T[]):T[] {
console.log(arg.length); // Array has a .length,so no more error
return arg;
}
泛型类型
定义一个泛型类型的变量
function identity<T>(arg:T):T{
return arg;
}
let myIdentity: <T>(arg:T) => T = identity;
// 使用接口定义
interface GenericIdentityFn<T>{
(arg:T):T;
}
function identity<T>(arg:T):T{
return arg;
}
let myIdentity:GenericIdentityFn<string>=identity;
泛型类
class GenericNumber<T,U>{
name: T;
add: (x:U, y:U) => U;
}
let myGenericNumber = new GenericNumber<string,number>();
myGenericNumber.name = 'hello world';
myGenericNumber.add = function(x,y) {return x + y;}
泛型约束
function loggingIdentity<T>(arg:T):T {
console.log(arg.length); // 报错,T类型没有length属性
return arg;
}
// 使用接口约束泛型,T类型必须有length属性
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg:T):T{
console.log(arg.length); // 没问题
return arg;
}
loggingIdentity(3); // 报错,数字3没有length属性
高级类型
交叉类型、联合类型、类型别名
interface A {
x: number;
y: number;
}
interface B {
y: number;
z: number;
}
// 使用类型别名可以自定义自己想要的类型,起别名不会新建一个类型 - 它只是创建了一个新名字来引用那个类型
// 交叉类型U具有x,y,z三个属性
type U = A & B;
// 联合类型I只有y这个共有属性
type I = A | B;
Vue结合TypeScript
项目配置
tsconfig.json
{
"compilerOptions": { // 编译选项
"target": "esnext", // 指定ECMAScript目标版本,这里选esnext是可以将ES6还在草案规范的语法转成今天的语法
"module": "esnext", // 指定生成哪个模块系统代码
"strict": true, // 启用所有严格类型检查选项
"jsx": "preserve", // jsx有三个值:preserve,react和react-native
"importHelpers": true,
"moduleResolution": "node", // 如何处理模块,使用node模块解析机制
"experimentDecorators": true, // 启用实验性的装饰器特性
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"noImplicitAny": false, // 在表达式和声明上有隐含的any类型时报错。如果改为true,any类型的都会报错
"noImplicitThis": false, // 当this表达式的值为any类型的时候,生成一个错误
"suppressImplicitAnyIndexErrors": true,
"sourceMap": true, // 生成相应的.map文件
"baseUrl": ".", // 解析非相对模块名的基准目录
"resolveJsonModule": true,
"types": [ // 类型声明文件名列表
"webpack-env" // webpack类型定义
],
"paths": { // 路径映射的列表
"@/*": [
"src/*"
],
"@store/*": [
"src/store/*"
],
"@components/*": [
"src/components/*"
],
"@utils/*": [
"src/utils/*"
],
"@assets/*": [
"src/assets/*"
]
},
"lib": [ // 编译过程中需要引入的库文件的列表
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [ // 编译文件列表
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [ // 无需变异文件列表
"node_modules",
"src/components/create-form"
]
}
装饰器
装饰器是一种函数,写成@+函数名。它可以放在类和类方法的定义前面,是一种与类(class)相关的语法,用来注释或修改类和类方法。动态地给对象添加额外的职责。在不改变接口的前提下,增强类的性能
举个例子:
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
@testable就是一个装饰器。它修饰了MyTestableClass这个类的行为,为它加上了静态属性isTestable。testable函数的参数target是MyTestableClass类本身装饰器不仅可以装饰类,还可以装饰类的属性、方法。
vue-property-decorator
vue-property-decorator在vue-class-component的基础上增加了更多与Vue相关的装饰器,使Vue组件更好地跟TS结合使用。
-
@Component
@Component装饰器可以接收一个对象作为参数,可以在对象中声明components、filters、directives等未提供装饰器的选项 -
@Emit
指定事件emit,可以使用此修饰符,接收一个可选参数,该参数是$Emit的第一个参数,充当事件名。如果没有提供这个参数,$Emit会将回调函数名的camelCase(驼峰命名)转为kebab-case(短横线命名),并将其作为事件名,它会将回调函数的返回值作为第二个参数,在返回值之后,回调函数的参数也会一起被当做参数使用,也可以直接使用this.$emit() -
@Prop
接收一个参数,这个参数可以有三种写法 -
@Inject
指定依赖注入 -
@Provide
指定Provide -
@Watch
可接收2个参数,第一个参数是要监听的属性,第二个参数是对象,可配置deep、immediate属性
举个例子
import { Component, Prop, Watch, Vue } from 'vue-property-decorator'
@Component({
components: {
// 注册业务组件
},
})
export class MyComponent extends Vue {
dataA: string = 'test'
count = 0
@Prop(String) readonly name!string | undefined; // 指定prop的类型
@Prop({default: 30, type: Number}) private age!: number; // type、default、required、validator
@Prop([String, Boolean]) private sex!: string | boolean; // 指定prop的可选类型
@Emit('reset')
resetCount() {
this.count = 0
}
@Emit()
returnValue(){
return 10
}
@Emit()
onInputChange(e) {
return e.target.value
}
// watcher
@watch('child')
onChildChanged(val:string, oldVal:string) {}
@watch('person', { immediate: true,deep:true })
onPersonChanged(val:Person, oldVal:Person) {}
}
解析之后会变成
export default {
data() {
return {
dataA: 'test'
}
},
props: {
propA: {
type: Number
},
propB: {
type: Array,
default: [10, 20, 30, 50]
},
propC: {
type: String,
default: 'total, sizes, prev, next, jumper'
}
},
watch:{
'child': {
// handler:其值是一个回调函数。即监听到变化时应该执行的函数。
handler: 'onChildChanged',
// immediate:其值是true或false;
// immeditate:true代表立即先去执行里面的handler方法
// immeditate:false就跟我们以前的效果一样,不会在绑定的时候就执行
immediate: false,
// deep:其值是true或false;确认是否深入监听
// deep的意思就是深入观察,监听器会一层层地往下遍历,给对象的所有属性都加上这个监听器
// 受现代JavaScript的限制(以及废弃Object.observe),Vue不能检测到对象属性的添加或删除
deep: false
},
'person': {
handler: 'onPersonChanged',
immediate: true,
deep: true
}
},
methods: {
resetCount(){
this.count = 0;
this.$emit('reset')
},
returnValue() {
this.$emit('return-value', 10)
},
onInputChange(e) {
this.$emit('on-input-change', e.target.value, e)
},
onChildChanged(val, oldVal) {},
onPersonChanged(val, oldVal) {}
}
}
mixins的用法
import {
Vue,
Component,
Watch,
Inject,
Prop,
Mixins
} from 'vue-property-decorator';
import myMixin from './myMixin';
export default class BaseCheck extends Mixins(myMixin) {
}
// myMixin.ts
import {Vue, Component} from 'vue-property-decorator';
@Component({})
export default class LanMixin extends Vue {
}
声明文件
模块导出声明、全局类型声明放在描述文件*.d.ts