typescript

418 阅读9分钟

安装

npm i typescript -g

转换成js

  • 方法一
tsc 文件名.ts
  • 方法二
    • vscode > terminal > run task > tsc:watch -tsconfig.json
    • vscode > terminal > run task > tsc:build -tsconfig.json
  • 方法三
    • package.json中写脚本: "build": "tsc",npm run build

生成配置文件tsconfig.json

tsc --init

数据类型

  • 如果文件里出现了export或import,ts会把这个文件当成一个模块,文件中的变量就会变成私有变量,不会和全局变量冲突
// 布尔
let married: boolean = false;

// 数字
let age: number = 0;

// 字符串
let name: string = 'zhangsan'

// 数组 类型唯一,数量不稳定
let arr1:number[] = [1,2,3]
let arr2: Array<number> = [7,8,9]

// 元组tuple  跟数组很像,表示一个已知数量和类型的数组
let school: [string,number] = ['zhangsan',5]

// 普通枚举
enum Gender {
    MALE,
    FEMAL
}

// 不普通枚举 常量枚举
const enum Colors {
    Red,
    Yellow,
    Blue
}

// 任意类型
/**
 * any 可以赋值给任意类型
 * - 第三方库 没有提供类型声明文件的时候
 * - 类型转换遇到困难
 * - 数据结构太复杂
 */
 let root: HTMLElement | null = document.getElementById('root')
 root!.style.color = 'red';  // 非空断言 告诉ts我确定root不可能为空,就不报错了
 let root: any = document.getElementById('root')
 root.style.color = 'red'
 
 // null undefined 是其他类型的子类型
 // 当tsconfig.json中strictNullChecks = false时可以赋值给其他类型
let x:number;
x = 1;
x = undefined;
x = null;

// void 空 表示没有任何类型 可以兼容undefined
// 当tsconfig.json中strictNullChecks = false时可以返回null
function greeting(name: string): void {
    console.log(name);
}

// never 永远不 永远不会出现的值
// 是(null undefined)的子类型
function error(msg: string): never {
    console.log(msg);
    throw new Error(msg);// 抛出异常此函数会异常终止,永远不会有返回值
    console.log('over message')
}
let ret: never = error('hello')
// 还可以表示永远达不到终点
functiion loop(): never {
    while(true) {}
}

function sum(x:number|string){
    if (typeof x === 'number') {
        console.log(x)
    } else if(typeof x === 'string') {
        console.log(x)
    } else {
        // 永远不可能走到这
        console.log(x)
    }
}

// never void 区别
// void 可以赋值为null undefined,但never不包含任何值
// 定义void返回值类型的函数能正常运行 拥有never返回值的函数永远无法返回
export {}

枚举类型

普通枚举编译成es5
enum Gender {
    MALE,
    FEMAL
}
console.log(Gender)
// 编译得:
var Gender;
(function(Gender) {
    Gender[Gender['MALE'] = 0] = 'MALE';
    Gender[Gender['FEMALE'] = 1] = 'FEMALE';
})(Gender || (Gender = {}))
console.log(Gender) // {'0':'MALE', '1':'FEMALE', MALE:0, FEMALE:1}
常量枚举编译成es5
const enum Colors {
    Red,
    Yellow,
    Blue
}
let myColors = [Colors.Red, Colors.Yellow, Colors.Blue]
// 编译得:
var myColors = [0/*Red*/,1/*Yellow*/,2/*Blue*/]
枚举类型与数字类型兼容
enum Colors {
    Red,
    Yellow
}
let c: Colors
c = Colors.Red//0
c = 0

let n: number
n = Colors.Yellow

类型断言

let name: string|number;//联合
(name as string).length;
(name as number).toFixed(2);
(<number>name).toFixed(2)

字面量类型

  • 字面量是值的联合,联合是类型的联合
  • type是TS的关键字,用来声明一个新的类型
  • 可以把字符串/数字/布尔值字面量组成一个联合类型
// 只能是这三个值
type myType = 1|'one'|true;

let t1: myType = 1
let t2: myType = 'one'
let t3: myType = true

函数

type UserFunction = (a: string, b: string) => string
let getUserName: UserFunction = function(firstName: string, lastName: string) {
    return firstName + lastName;
}


// 可选参数 必须是最后一个
function print(name: string, age?:number): void {
    console.log(name,age)
}

// 默认参数
function ajax(url: string, method: string = 'GET') {
    console.log(url,method)
}

// 剩余参数
function sum(prefix: string, ...numbers: number[]) {
    return prefix + numbers.reduce((a,b) => a+b)
}

// 函数的重载
// 注意:函数的重载和函数的实现需要紧紧挨着Function implementation is missing or not immediately following the declaration.ts
let obj: any = {};
function attr(a:string,b:string);
function attr(a:number,b:number);
function attr(a:string|number,b:string|number){
    if (typeof val === 'string') {
        obj.name = val;
    } else if(typeof val === 'number') {
        obj.age = val;
    }
}
attr(1,1);//正确的调用
attr('a','b');//正确的调用
attr(1,'b');//No overload matches this call

// 函数的兼容性1
// 少传参数是可以的 多传参数报错
type functionType = (a: number, b: number) => number
let sum: functionType;
function f1(a:number):number {
    return a
}
sum = f1


// 函数的兼容性2
// 函数返回值多属性可以,少属性不可以
type GetPerson = () => {name: string,age:number}
let getPerson: GetPerson;
function g1() {
    return {name:'zz',age:10,home:'beijing'}
}
getPerson = g1

// 函数的兼容性3
// 类型更多可以 类型变少不可以
let sourceFunc = (a: number | string) => {}
let targetFunc = (a: number | string | boolean) => {}

基本类型的兼容性

let num: string | number;
let str: string = 'zll';
num = str

let num2:{
    toString(): string
}
let str2: string = 'jiagou'//有toString方法返回string
num2 = str2

class Person {
    name: string;
    getName():void {
        console.log(this.name)
    }
}
let p1: Person = new Person;
p1.name = 'zll';
pi.getName()

存取器

class User {
    myName: string = '';
    constructor(myName: string) {
        this.myName = myName;
    }
    get name(){
        return this.myName;
    }
    set name(value) {
        this.myName = value.toUpperCase();
    }
}
let user = new User('');
user.name = 'zll'

// public声明 可以省略两行代码
class User {
    // myName: string = '';
    constructor(public myName: string) {
        // this.myName = myName;
    }
    get name(){
        return this.myName;
    }
    set name(value) {
        this.myName = value.toUpperCase();
    }
}
//readonly 只读
// readonly 是在编译阶段进行的检查
// const 是在运行阶段进行的检查
class User {
    public readonly PI = 3.14;
    // myName: string = '';
    constructor(public myName: string) {
        // this.myName = myName;
    }
    get name(){
        return this.myName;
    }
    set name(value) {
        this.myName = value.toUpperCase();
    }
}

public private protected

  • public 属性意味着这个属性可以被自己类本身.子类和其他类访问
  • protected属性表示这个属性只有自己类本身和子类能访问,其他类不能访问
  • private属性表示这个属性只能被本身访问,子类和其他类都不能访问
class Father {
    // public name: string;
    // protected age: number;
    // private money: number;
    constructor(public name: string, protected age:number, private money:number) {
        // this.name = name;
        // this.age = age;
        // this.money = money;
    }
    getMoney(){
        console.log(this.money)
    }
}
class Child extends Father {
    constructor(name: string,age:number,money:number) {
        super(name,age,money)
    }
    getName(){
        console.log(this.name)
    }
    getAge(){
        console.log(this.age)
    }
}
let child = new Child('zll',10,100);
child.getName();
child.getAge();
child.getMoney();//不行
child.name;
child.age;//不行
child.money;//不行

装饰器

  • 装饰器其实是一个函数
类装饰器
// 同名的类和接口定义的类型,属性会进行合并
//interface Person {
//    name: string,
//    eat:() => void
//}

// type Person = {name: string, eat: Function};//实例的类型
// target是Person类 类型:typeof Person/Function/new () => Person
// 如果Person constructor中有参数要添加参数:new (a: string) => Person
function enchancer(target:new () => Person) {
    target.prototype.name = 'zll';
    target.prototype.eat = function() {
        console.log(this.name)
    }
}

@enchancer
claass Person {

}
let p: any = new Person();//可通过interface解决any
p.name;
p.eat()
装饰器工厂1
function enhancer(name: string) {
    return function(target:new () => Person) {
        target.prototype.name = name;
        target.prototype.eat = function() {
            console.log(this.name)
        }
    }
}

@enhancer('zll')
class Person {

}
let p: any = new Person();
p.name;
p.eat()
装饰器工厂2
// 替换掉class constructor中参数需要一致
function enchancer(target:new () => Person) {
    return class {
        constructor(public age:number) {
        
        }
        name: string = 'zll';
        eat(){
            console.log(this.name)
        }
    }
}

@enchancer
claass Person {
    constructor(public age: number){
    
    }
}
let p = new Person(10);//不需要any类型即可调用name和eat
p.name;
p.eat()
属性装饰器
/**
 * 属性装饰器有两个参数
 * @param target 如果是静态成员,target就是类的构造函数,如果是实例成员就是类的原型对象
 * @param property 方法或者属性的名称
 */
function upperCase(target: any, property: string) {
    let value = target[property];
    const getter = () => value;
    const setter = (newValue: string) => {
        value = newValue.toUpperCase();
    } 
    if(delete target[property]) {
        Object.defineProperty(target,property,{
            get:getter,
            set: setter,
            enumerable: true,
            configurable:true
        })
    }
}
class Person {
    @upperCase
    name: string = 'zll';
}
let p = new Person
p.name = 'wang'
方法装饰器
/**
 * @param target 如果是静态成员,target就是类的构造函数,如果是实例成员就是类的原型对象
 * @param property 方法的名称
 * @param descriptor 方法的描述器
 */
function noEnumerable(target: any, property: string, descriptor: propertyDescriptor){
    descriptor.enumerable = false;
}
class Person {
    name: string = 'zll'
    @noEnumerable
    getName() {
        console.log(this.name)
    }
}
参数装饰器
/**
 * @param target 如果是静态成员,target就是类的构造函数,如果是实例成员就是类的原型对象
 * @param methodName 方法的名称
 * @param paramsIndex 参数的索引
 */
 interface Person {
     age: number
 }
function addAge(target: any, methodName: string, paramsIndex:number) {
    target.age = 10
}
class Person {
    login(username: string, @addAge password: string) {
        console.log(username, password)
    }
}

抽象类

  • 抽象描述一种抽象的状态,无法被实例化,只能被继承
  • 抽象方法不能在抽象类中实现,只能在具体子类中实现
  • 重写和重载
    • 重载指的是为一个函数提供多个类型 定义,或者说函数声明
    • 重写指的是不同的子类以不同的方式实现父类的方法
abstract class Animal {
    name: string;
    abstract speak(): void
}

// 猫
class Cat extends Animal {
    speak():void {
        console.log('喵喵喵')
    }
}
let cat =  new Cat();
cat.speak()

// 狗
class Dog extends Animal {
    speak():void {
        console.log('汪汪汪')
    }
}
let dog =  new Dog();
dog.speak()

兼容性

class Animal {
    name: striing
}
class Bird extends Animal {
    swing: number
}
let a: Animal;
a = new Bird();

接口

对象的形状

interface Speakable {
    name: string;
    speak(): void;
}
let person: Speakable = {
    name: 'zll',
    speak() {}
}

行为的抽象

interface Speakable {
    speak(): void;
}
interface Eatable {
    eat(): void;
}
class Person implements Speakable,Eatable {
    speak() {
        console.log('speak')
    }
    eat(): void {
        console.log('eat')
    }
}

任意属性

//interface Person {
//    name: string,
//    age: number,
//    [key: string]: any //任意属性
//}

// Record是内置的类型type Record
interface Person extends Record<string, any>{
    name: string,
    age: number
}
let p:Person={
    name: 'zll',
    age: 10,
    home: 'beijing',
    today: 1
}

函数接口类型

// type Cost = (price: number) => number
interface Cost {
    (price: number): number
}
let cost: Cost = function(price: number): number {
    return price * 8
}

可索引接口

interface UserInterface {
    [index: number] : string
}
let arr: UserInterface = ['1','2']
let obj:UserInterface = {
    0: 'a',
    1: 'b'
}

类implements接口

interface Speakable {
    name: string,
    speak(words): void
}
class Dog implements Speakable {
    name: string;
    speak(words): void {
        console.log(words)
    }
}
let dog = new Dog
dog.speak('汪汪汪')

接口约束类

class Animal {
    constructor(public name: string) {
    
    }
 }
 
interface WithNameClazz {
    new(name: string): Animal
}
function create(clazz: WithNameClazz,name: string) {
    return new clazz(name);
}
let a = create(Animal,'zll')
console.log(a)

兼容性1

  • 如果传入的变量和声明的类型不匹配 ts会进行兼容性检查
  • 目标类型声明的变量在源类型中都存在,那么就是兼容的
interface Animal {
    name: string,
    age: number
}
interface Person {
    name: string,
    age: number,
    gender: number
}

function getName(animal: Animal): string {
    return animal.name
}
let p: Person = {name:'zll', age:10, gender:0}
getName(p)

兼容性2

  • 父接口可以传入子接口定义的参数
interface Event {
    timeStamp: number
}
interface MouseEvent extends Event {
    eventX: number,
    eventY: number
}
interface KeyEvent extends Event {
    keyCode: number
}
function addEventListener (eventType: string, handler:(event: MouseEvent) => void) {}
addEventListener('cliick', (event: Event) => {})

问题

  • interface对比type?
    • 相同点 都能描述对象类型 都能实现继承,interface用extends,type配合交叉类型
    • 不同点 type除了能描述对象还可以用来自定义其他类型 同名的interface会合并,同名type会报错
  • 类和interface的区别?
    • 接口只是一个类型,用来修饰对象,或者被类去实现,经过ts编译之后就消失
    • 类既是类型(类的实例的类型),也是值(构造函数)
  • 类的接口和抽象类的区别?
    • 类的接口是被类去实现
    • 抽象类是被继承

泛型(难点)

  • 在定义函数/接口/类的时候,不预先指定具体的类型,而是使用的时候指定
function createArray<T>(length: number, value: T):T[] {
    let result: T[] = [];
    for(let i = 0;i<length;i++) {
        result[i] = value;
    }
    return result;
}
let result = createArray<string>(3,'x');
console.log(result)

类数组

function sum(...args: number[]) {
    for(let i = 0;i<args.length;i++) {
        console.log(args[i])
    }
}
sum(1,2,3)

泛型类

class MyArray<t> {
    private list: T[] = [];
    add(value:T) {
        this.list.push(value)
    }
}
let arr = new MyArray<number>()
arr.add(1)

泛型接口

interface Calculate {
    <T>(a:T,b:T):T
}
let add: Calculate = function<T>(a:T,b:T) {
    return a;
}
interface Calculate<A> {
    <B>(a:A,b:B):A
}
let add: Calculate<number> = function<B>(a:number,b:B) {
    return a;
}
add<string>(1,'b')

默认泛型

interface Calculate<A = number> {
    <B>(a:A,b:B):A
}
let add: Calculate = function<B>(a:number,b:B) {
    return a;
}
add<string>(1,'b')

接口泛型约束

  • 默认情况下,不能调用泛型上的任何属性和方法,因为在定义的时候根本不知道将会传入什么值
interface LengthWise {
    length: number
}
function logger<T extends LengthWise>(val:T) {
    console.log(val.length)
}
logger<string>('d')

泛型类型别名

type Cart<T> = {list:T[]} | T[]
let c1: Cart<string> = {list:['1']}
let c2: Cart<number> = [1,2,3]

泛型兼容性

interface Empty<T> {}
let x!: Empty<string>;
let y!: Empty<number>;
x = y//可以

interface Empty1<T> {
    data: T
}
let x1!: Empty1<string>;
let y1!: Empty1<number>;
x1 = y1;//报错