typescript学习记录

100 阅读7分钟

环境搭建

执行npm install -g typescript命令之后,全局会增加一个tsc命令

执行tsc 1.ts就可以将1.ts转换为1.js

每次都通过命令执行较为繁琐,可以在vscode中安装code runner,右击run code执行,在执行ts脚本的过程中,会提示没有ts-node这个命令,可以通过npm install ts-node -g全局安装一下

tsc --watch也可以监听文件变化

非空断言操作符:

案例1:赋值时使用

function simpleExample(a: number | undefined) {
	// ts认为,a有可能是number,也可能是undefined,用了a!之后,就告诉ts a一定不是undefined
	const b: number = a; // COMPILATION ERROR: undefined is not assignable to number.
	const c: number = a!; // OK
}

案例2:调用方法时使用

let strOrNum: string | number;

// ! 非空断言,我断定这个变量 一定有值. 出错了 自负责
strOrNum!.toString()

案例3:非空断言和as一起使用

let strOrNum: string | number;
(strOrNum! as string).toLowerCase()

在非strictNullChecks值为false的模式下,undefined null是任意类型的子类型

双重断言:

let name4:string|number;
//这种写法可以避免编译器报错
console.log(name4! as any as boolean);

断言不推荐写法(容易和泛型混淆)

let strOrNum: string | number;
(<number>strOrNum!).toFixed()

字面量类型

const username: "jw" = "jw";
const password: 123456 = 123456;

常用到的场景:

// type关键字 和 enum 都是ts提供的 和js没关系
type Direction = "up" | "down" | "left" | "right";
let direction: Direction = "down"; // 字面量类型 就是限定了值 和 枚举类似

type和interface的区别

type用来定义一个类型或者用来做类型别名用,相当于存储类型的一个变量,而interface是接口 interface可以定义多个同名的接口,但type不可以 interface可以extends继承或者implements实现,但type不可以,type需要用&连接两个类型以达到类似目的

// 对象可以有任意属性
interface Person2 {
	[key:string]: any;
}

组合优于继承,因此能用接口就不用type

函数类型接口

interface Discount {
	(price: number): number
}
const discount: Discount = (price: number): number=>{
    return price*.8;
}

可索引接口

//对数组和对象进行约束 
interface User{
    [xx:number]:string
}
let user: User={
    0:'0',1:'1',2:'2'
}
let arr: User=['1','2','3'];

构造函数类型

class Animal{
    constructor(public name:string){

    }
}
//如果是修饰普通函数 
//加上new之后就是用来描述类的构造函数
interface WithNameClass{
    new(name:string):any
}
let wc: WithNameClass = Animal
function createClass(clazz: WithNameClass,name:string){
    return new clazz(name);
}

泛型接口

第1种:在方法的定义阶段就需要确定类型

interface Calculate<T>{
  (a:T,b:T):T
}
let sum: Calculate<number> = function (a: number, b: number): number {
  return a + b;
};
sum(1,2);

第2种:在方法的调用阶段确定类型

interface Calculate2{
  <T>(a:T,b:T):T
}
let sum2: Calculate2 = function <T>(a: T, b: T): T {
  return a;
};
sum2<number>(1, 2);

第3种

interface Calculate3<T>{
  <U>(a:T,b:T):U
}
let sum3: Calculate3<number> = function <U>(a: number, b: number): U {
  return a as any;
};
sum3<string>(1, 2);

泛型约束

interface LengthWise{
  length:number
}
//非常非常非常重要
function logger2<T extends LengthWise>(val: T) {
  console.log(val.length);
}
logger2<string>(‘abc’);
let obj  = {
  length:10
}
type Obj = typeof obj;
logger2<Obj>(obj);

函数兼容性

type Func = (a:number,b:number)=>void;
let sum: Func;
function f1(a: number, b: number):void {
}
sum = f1;
//参数少一个可以
function f2(a: number): void {}
sum = f2;
//少二个参数也可以
function f3(): void {}
sum = f3;
function f4(a: number, b: number,c:number): void {}
//sum = f4;


type GetPerson = ()=>{name:string,age:number}
let getPerson: GetPerson;

function g1(){
    return {name:'zhufeng',age:10};
}
getPerson=g1;
function g2() {
  return { name: "zhufeng", age: 10,gender:0 };
}
getPerson = g2;
function g3() {
  return { name: "zhufeng" };
}
// g3不能赋值给getPerson,因为g3调用后可能去取age属性,但g3明显没返回age属性,因此会有问题
//getPerson = g3;

==================================

class Animal {}
class Dog extends Animal {
  public name: string = "Dog";
}
class BlackDog extends Dog {
  public age: number = 10;
}
class WhiteDog extends Dog {
  public home: string = "北京";
}
type Callback = (dog: Dog) => Dog;
function exec(callback: Callback): void {}

type ChildToChild = (blackDog: BlackDog) => BlackDog;
let childToChild: ChildToChild;
exec(childToChild);//n
// Argument of type 'ChildToChild' is not assignable to parameter of type 'Callback'.
//   Types of parameters 'blackDog' and 'dog' are incompatible.
//     Property 'age' is missing in type 'Dog' but required in type 'BlackDog'.ts(2345)
// 18.ts(9, 10): 'age' is declared here.

type ChildToParent = (blackDog: BlackDog) => Animal;
let childToParent: ChildToParent;
exec(childToParent);//n

type ParentToParent = (animal: Animal) => Animal;
let parentToParent: ParentToParent;
exec(parentToParent);//n
// Argument of type 'ParentToParent' is not assignable to parameter of type 'Callback'.
//  Property 'name' is missing in type 'Animal' but required in type 'Dog'.ts(2345)
// 18.ts(6, 10): 'name' is declared here.


type ParentToChild = (animal: Animal) => BlackDog;
let parentToChild: ParentToChild;
exec(parentToChild);//y

原则:为了在使用的时候能够访问到对应的成员

既然要使用一个东西,首先就需要找到这个东西的提供方,即明确提供方和使用方

提供方提供了我使用时需要的,那就OK,否则就不可以

以上述案例exec(childToChild);为例:

对于childToChild这个回调来说,它里面可能会用到blackDog这个参数,这个参数需要用到name age两个属性,因此这个回调里的函数体就是使用方

谁给childToChild这个函数体提供这个参数呢?自然是调用childToChild的地方,谁来调用呢?exec的函数体中会调用它,因此,exec的函数体就是提供方,那exec提供方又提供了什么对象呢?需要到exec的形参中去找,exec的形参是一个会返回Dog的对象,它提供的属性只有name

因此提供了name,但需要用到name age,因此校验不通过

而对于exec这个方法来说,它里面会用到调用childToChild返回的对象,因此这次提供方变成childToChild的返回值,而使用方变成了exec的方法体

提供方提供的返回值是BlackDog类型,是一个拥有name age的对象,而使用方exec需要的是Dog类型,它只需要name属性即可,因此校验通过

==========================

type S = (a: number, b: string) => void
type T = (x: number) => void

let fn: S = (x: number, b: string) => {}

let func: T = fn
// Type 'S' is not assignable to type 'T'.
// 类型 S 不能赋值给类型 T,因为参数个数不兼容

==========================

type T = (a: number, b: string) => void
type S = (x: number) => void

let fn: S = (x: number) => {}
let func: T = fn

类型谓词

y is Bird整体是一个类型

function isBird(y:Bird|Dog):y is Bird{
  return (y as Bird).swing == 2;
}
function getAnimal(x: Bird | Dog) {
    if(isBird(x)){
        console.log(x);
    }else{
        console.log(x);
    }
}

unknown

unknown,是any的安全类型

交叉类型

理解时需要按照子类型理解,而不能按照并集和交集的思路去想

interface A{name:string,c:number}
interface B{age:number,c:number}
let a:A;
let b:B;
type C = A&B;
let c:C = {name:'zhufeng',age:10,c:10};
// 原则:A和B交叉以后的类型可以赋值给A或B
a=c;
b=c;
// 从限制范围的角度也可以理解:当给一个对象加的属性越来越多的时候,表示的范围会越来越窄,因此c虽然看起来属性多,但是反而是限制更死了,限制了A&B 必须是满足接口A的所有对象中包含age属性的那部分对象,或者是满足接口B中的所有对象中包含name属性的那部分对象

type AA = string|number;
type BB = string|boolean;
// CC只有是string时才可以既赋值给AA,也可以赋值给BB
type CC = AA&BB;
// 实例
function mixin<T,U>(one:T,two:U){
    const result = <(T & U)>{};
    for(let key in one)
       (<T>result)[key] = one[key];

    for (let key in two)
       (<U>result)[key] = two[key];
   return result;  
}

const x = mixin({name:'zhufeng'},{age:11});
console.log(x.name,x.age);

==========================

Partial类型:

type Partial<T> = {
    [key in keyof T]?: T[key]
}
type PPerson = Partial<Person6>

==========================

条件类型:

interface Fish{
    name1:string;
}
interface Water{
    name2:string;
}
interface Bird{
    name3:string;
}
interface Sky{
    name4:string;
}
//type Condition<T> = T extends Fish?Water:Sky;
type Condition<T> = { t: T } extends { t: Fish} ? Water : Sky;
//let con:Condition<Fish> = {name2:'水'};

//条件类型的分发
let con1: Condition<Fish | Bird> = { name4:''};
let con2: Condition<Fish | Bird> = { name4: '' };


// 找出T中不包含U的部分
type Diff<T,U> = T extends U?never:T;
type R = Diff<'a'|'b'|'c'|'d' , 'a'|'b'|'c'>;  
type R2 = 'd';


type Filter<T,U> = T extends U?T:never;
type R3 = Filter<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>;  

==========================

内置条件类型

//Exclude
type Exclude<T, U> = T extends U ? never : T;
type R4 = Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>;  
type Extract<T, U> = T extends U ? T : never;
//Extract
type R5 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>;  //Pick
//NonNullable
type NonNullable<T> = T extends null | undefined ? never : T;
type R6 = NonNullable<'a' | null | undefined>;  
//ReturnType
// infer R相当于定义了一个变量,这个语法用来获取一个表达式中某部分对应的类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
(...args: any[]) => infer R 是一个表达式
T extends (...args: any[]) => infer R 是一个表达式

function getUser(a:string,b:number){
    return {name:'zhufeng',age:10};
}
//let t = getUser();
type GetUserType = typeof getUser;
type ReturnUser = ReturnType<GetUserType>;
let u: ReturnUser = {
    name:'zf',
    age:10
}