TypeScript 快速入门

[Toc]

typescript 的产生意义

  • javascript 的特性:
    • javascript 是一门弱类型的,动态类型检查的语言。这两个特性在带来灵活与便捷的同时,也天生有一些缺陷。
    • 弱类型
      弱类型指的是,早声明一个变量的时候,不需要指定类型,在给这个变量重新赋值的时候也不需要是固定的类型。不像 java 等强类型的语言,声明一个变量的时候需要指定类型,且不能被赋予非指定类型的值。
    • 动态类型检查
      静态类型语言会在编译阶段就会抛出类型错误,避免了在线上出类型问题。而js 的类型检查不是在编译阶段进行,而是在执行阶段进行的。当产生类型检查的错误的时候,只有在执行的的时候才会显现出来。 例如:下面有一个分割字符串的函数,但是如果不小心在调用的时候传入其他类型的数据作为参数的话,在书写和编译的时候不会抛出错误,但是会在执行时抛出错误。这种错误往往会引起整个程序的崩溃。下面的代码因为不小心给函数 someFunc 传了个字符串类型的参数,所以运行时报错了......
      const someFunc = (string) => {
          return string.split('');
      } 
      someFunc(3);
      // Uncaught SyntaxError: string.split is not a function
      复制代码
  1. typescript的作用:
  • 编译时的类型检查,在编译时对代码错误进行检查并抛出错误
  • ide的增强,代码智能提示,interface的提示等
  • 提升代码可读性,稳定性、可重构性。

类型

1. 基础数据类型
  • 1.1 boolean
    const isTrue: bollean: true;
    复制代码
  • 1.2 number
    let num: number = 2333;
    num = 'abc';  // Error: 不能将类型“"abc"”分配给类型“number”
    复制代码
  • 1.3 string
    let str: string = '嘿嘿嘿';
    str = 0;  // Error: 不能将类型“0”分配给类型“string”。
    复制代码
  • 1.4 null 和 undefined

在非严格空检查模式下,null 和 undefined 是所有类型的子类型,可以作为任何类型变量的值;
在严格空检查模式(strictNullChecks)下,其是独立的类型。

非严格空检查模式下:以下三种情况都不会报错:
严格空检查模式下:以下三种情况都会报错:

let str: string = undefined;

let obj: object = undefined;

let num: number = 2;
num = null;
复制代码
  • 1.5 void

void 表示空类型,void 类型只能赋值为 null || undefined。也可以在函数中表示没有返回值。

let v: void = null;

let func = (): void => {
    alert('没有返回值');
}
复制代码
  • 1.6 never

never 表示其有无法达到的重点,never 是任何类型的子类型,但没有任何类型是 never 的子类型;

const error = (message: string): never => {
    throw new Error(message);
}
// 虽然这个函数规定必须有 string 类型的返回值,但是由于 never 是任何类型的子类型,所以这里不会报错
const error = (message: string): string => {
    throw new Error(message);
}
复制代码
  • 1.7 any

any 表示该值是任意类型,编辑时将跳过对他的类型检查。应在代码中尽量避免 any 类型的出现,因为这会失去 ts 的大部分作用 -- 类型检查。其使用的场景在于接受的数据时动态不确定的类型,或者用来在第三方库的 module.d.ts 声明文件文件里跳过对第三方库的编译。

// 例如在 React 的声明文件里,因为不确定传入的nextProps 与 nextContext 是什么类型,所以使用了any
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;

复制代码
  • 1.8 object

object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。其意义在于,在不知道传入的对象长什么样子的情况下,更容易表示Obiect的api,例如hasOwnProperty

const func = (arg: object): void => {
    console.log(arg.hasOwnProperty('name')); // => true
}
func({name: 'liuneng'})
复制代码
  • 1.9 数组与元组

    1. 数组

    有两种定义数组类型的方式,一种是直接在类型后面加上[], 表示元素为该类型的数组

    let arr: number[] = [];
    arr.push(1); 
    arr.push('2');  // Error: 类型“"2"”的参数不能赋给类型“number”的参数
    复制代码

    第二种是使用数组泛型, 这种方式可以在不想在外边声明类型时候使用

    let list: Array<1 | 2> = [];
    list.push(3);  // Error: 类型“3”的参数不能赋给类型“1 | 2”的参数
    复制代码
    1. 元组用来表示已知元素数量与类型的数组,在赋值时内部元素的类型必须一一对应,访问时也会得到正确类型。当给元组添加元素或者访问未知索引元素的时候,会使用他们的联合类型
    let tuple: [string, number];
    tuple = [1, 'a'];  // Error: 不能将类型“[number, string]”分配给类型“[string, number]”
    tuple = ['a', 1];
    
    tuple.push(true);  // 类型"true"的参数不能赋给类型“string | number”的参数。
    复制代码
  • 2.0 类型断言

其表示在不确定该变量类型时,指定其类型,表示明确知道他的类型,不用去检查了

// 虽然 param 是any,但“我”保证传入的一定是个 string 类型的参数
const func = (param: any) => {
    return (param as string).length;
};
复制代码
2.枚举 (enum)

使用枚举类型可以为一组数值赋予有意义的名字

  • 例如说,现在有一个接口用来过滤一个列表,其接受一个参数,0表示不过滤,1表示过滤男性, 2表示过滤女性
const param = {
    filterType: 0,
};

fetch('/getfilterList', param)
  .then((res: any[]) => {
    console.log(res);
  });
复制代码

上面的这行代码,用眼睛看根本不知道请求的是什么类型的列表,只能通过注释 与 文档来判断它的意义

enum filterMap {
    All = 0,
    Men = 1,
    Women = 2,
}
const param = {
    filterType: filterMap.Men,
};

fetch('/getfilterList', param)
  .then((res: any[]) => {
    console.log(res);
  });
复制代码

上面这段代码,用枚举列出了所有过滤条件的选项,使用时直接像使用对象一样枚举,从语义上很容易理解这段代码想要获取的是男性列表,代码即是文档。尤其是当做常量使用更加统一与方便理解。

  • enum 的值
    声明 enum 类型的时候,可以指定 value 也可以不指定 value。
    不指定 value 的话他会从零后续依次递增 1。
    当通过 value 访问 key 的时候,如果有相同的 value,取最后一个
enum AbcMap {
    A,
    B = 1,
    C,
    D = 2,
    E = 2,
}
console.log(AbcMap.A);  // => 0   因为后面的 B 是1,所以自动 -1
console.log(AbcMap.C);  // => 2   因为前面的 B 是1,所以自动 +1
console.log(AbcMap.[2]); // => E 有三个2,取最后一个的 key
复制代码
3.接口 (interface)

interface 是对对象形状的描述,其规定这个类型的对象应该长什么样子,编译的时候回去检查以他为描述的对象符不符合其结构

interface IPerson {
    name: string;
    readonly isMan: boolean;  // 只读属性,创建后不可以改写
    age?: number;   // 可选属性,实现的时候可以没有这个属性
}

const xiaohong: IPerson = {
    name: '小红',
    isMan: false,
};

xiaohong.isMan = true;  // Error: isMan 是常数或只读属性。

xiaohong.love = '周杰伦'; // Error: “love”不在类型“IPerson”中。
复制代码

上面给小红添加 love 属性的时候报错了,因为 IPerson 中没有规定这个属性

但是有时候我们不确定在 interface 外有没有别的属性,这时候可以使用索引签名。但是此时已确定的类型必须是他的子类型

interface IPerson {
    [key: string]: string;
}

const xiaoming: IPerson = {
    name: '小红',
    love: '周杰伦'
};
复制代码
4.函数

ts 可以给函数的参数 与 返回值指定类型。使用时候不能使用多余参数

  • 函数声明:

现在定义一个加法的函数表达式

const sum: (x: number, y: number) => number = (x: number, y: number): number => x + y;
sum(1, 2); // => 3
复制代码

上面的代码看起来可能有点不好理解,左边是给 sum 定义类型,右半部分是 一个具体函数,这是 ts 函数完整的写法。通过 ts 类型推论的特性,可以把左半部分省略掉;也可以给变量定义类型而省略右边

const sum = (x: number, y: number): number =>  x + y;
复制代码

上面的代码看起来就比较好理解了,但是如果我们有一个乘法的方法,还有减法的方法等等等等,其输入类型和输出的类型都是 number,这个时候如果感觉在每个方法上都去定义参数与返回值的类型会觉得有点麻烦。此时,可以单独抽出一种函数类型,在函数表达式中使用。

type INumFunc = (x: number, y: number) => number ;

const sum: INumFunc = (x, y) =>  x + y;
const sub: INumFunc = (x, y) =>  x - y;
const multip: INumFunc = (x, y) =>  x * y;
复制代码

上面的代码定义了一个函数类型,要求输入输出都为 number;此时 ts 会自动给右边的函数体确定函数类型。如果右边函数体与左边类型声明不一致就会报错。

  • 参数:
    ts 当不确定函数的参数的时候,可以定义可选参数,其余 interface 的可选属性使用方法类似,是一个问号。也可以使用默认参数,其与 ES6 的默认参数一致
// 可选参数
const sub = (x: number, y: number = 5, y?: number): number =>  {
    if (y) {
        return x - y - z;
    } else {
        return 0;
    }
};
sub(10, 1, 1)  // -> 8
sub(10, 1)     // -> 0


// 默认参数
const sum = (x: number, y: number = 5): number =>  x + y;
sum(1, 2);      // -> 3
sum(1);         // -> 6
sum(1, null);   // -> 6
复制代码

js 里有 arguments 的存在,所以我们可以给一个函数传任意个参数。在 ts 里,不确定参数的个数的话,可以使用剩余参数,将多出的参数放入一个数组, 其和 ES6 的剩余参数使用方法一致

const sum = (x: number, ...y: number[]): number =>  {
    console.log(y);
    let sum = x;
    if (y && y.length > 0) {
        for (let index = 0; index < y.length; index++) {
            sum = sum + y[index];
        }
    }
    return sub;
};

sum(1, 2, 3);  // res -> 6  ,  log -> [2, 3]
复制代码
5. 类(class)

ts 的类 与 ES6 中的类大体相同,不过 class 中的属性可以添加修饰符

static 静态属性,其是这个类的属性,而不是实例的属性 public: 访问该成员的时候没有限制;
protected: 在派生类中可以访问该属性,但是不能再外部访问;
private: 私有成员,只能自己访问
readonly: 只读属性
abstract: 用于修饰抽象类或属性,必须在派生类中方实现它,自己不能实现。

class Person {
    static desc() {
        console.log('It's a class of "person");
    }

    protected name: string;
    private age: number = '8';
    readonly sex: string = 'boy';
    
    constructor (theName: string) {
        this.name = theName;
    }
    
    public like() {
        console.log('footbal');
    }
    
    abstract eat(): void;  // 必须在派生类中实现它
}

class kids extends Person {
    constructor(name) {
        super(name);
    }
    sayName() {
        console.log(this.name);
    }
    
    eat() {
        console.log('面包');
    }
}

const xiaohong = new kids('小红');

Person.desc();   // 静态成员直接使用 class 访问,不用实例

xiaohong.like(); // -> 'footbal'  public 属性访问没限制

console.log(xiaohong.name);  // Error: 小红是 protected 属性,只能在基类与派生类里面访问
xiaohong.sayName();  // -> '小红' 小红的内部方法里可以访问 protected 属性

console.log(xiaohong.age) // age 是 私有属性,不能在外部访问

console.log(xiaohong.sex); // -> boy
xiaohong.sex = 'girl'; // Error: sex 是只读属性,不能修改
复制代码
6. 类型推论

TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。

  • 赋值
let str = 'string';
str = 1;
// Error: Type 'number' is not assignable to type 'string'.
// str 在声明的时候并没有指定类型,但是 ts 自动推断为 string, 所以在给它赋值为 number 的时候报错了

let someVar;
someVar = 'string';
someVar = 3;
// 如果在声明一个变量的时候并没有给它赋值,ts 自动给它推断为 any 类型,所以这里跳过了类型检查,没有报错。
复制代码
  • 函数
const sum = (x: number, y: number) => x + y;
sum(1, 2);
// 上面函数,没有并没有给其指定 return 的类型,但这是被允许的,因为 ts 可以自动推断出其返回值的类型。
复制代码
  • 对象字面量
const obj = {
    a: 1,
    b: 2,
};
obj.a = 'str';  // Error: 不能将类型“"str"”分配给类型“number”
// 虽然 obj 在声明的时候并没有指定类型,但是 ts 自动将其推断为 {a: number, b: number} 所以报错

// 解构也是一样的
let { a } = obj;
a = 'str';     // Error: 不能将类型“"str"”分配给类型“number”
复制代码
  • 数组

下面的代码将 arr 推断为了 Array<string | number>

const arr = ['a', 'b', 1];
arr[0] = true;  // Error: 不能将类型“true”分配给类型“string | number”
复制代码
  • 类型保护

    ts 甚至能根据某些代码特征进行推断出正确的类型范围。

    • typeof

    下面的 if 代码块中,param 被推断为类型 string

      const func = (arg: number | string) => {
          if (typeof arg === 'string') {
              console.log(arg.split(''));  // OK
          }
          console.log(arg.split(''));  // Error: 类型“number”上不存在属性“split”。
      }
    复制代码
    • instanceof

    下面的代码可以根据 instanceof 推断出其参数类型,甚至可以自动推断出 else 代码块中的类型

      class A {
          public name: string = 'hehe';
      }
    
      class B {
          public age: number = 8;
      }
    
      const func = (arg: A | B) => {
          if (arg instanceof A) {
              console.log(arg.name);  // OK
              console.log(arg.age);  // Error: 类型“A”上不存在属性“age”。
          } else {
              console.log(arg.name); // Error: 类型“B”上不存在属性“name”。
              console.log(arg.age);  // OK
          }
      }
    复制代码
7. 泛型

有时候,当时用一个组件的时候,并不能确定其数据类型是什么样子,或者说为了达到复用组件的目的,可以使用泛型来创建可重用的组件。

例如,现在需要一个函数,其要求可以输出任意类型的参数,但是输入与输出必须是同一类型。如果不使用泛型的话,只能使用联合类型,或者 any 来实现。使用泛型可以这样做:

function identity<T>(arg: T): T {
    return arg;
}
identity<string>('str'); // -> 'str'
复制代码
  • 泛型既可以代表未来使用时的类型,也可以作为类型的一部分
function objToArr<T>(arg: T): T[] {
    return [arg];
}

objToArr({a: 1});  // -> [{a: 1}]
复制代码

上面的代码表示输入 T 类型的参数时,返回一个 T 类型成员的数组

// 创建一个接口,其属性 list 的类型在使用前并不确定
interface IData<T> {
    list: T[];
    status: number;
}

const numItemData: IData<number> = {
    list: [1, 2, 3],
    status: 1,
};

const strItemData: IData<number> = {
    list: ['a', 'b', 'c'],  // Error: 不能将类型“string[]”分配给类型“number[]”。
    status: 1,
};
复制代码

上面的例子创建了接口 IData,其在使用的时候,传入类型约束, 这样可以最大程度的复用 IData 接口。因为 strItemData 的赋值与泛型传入的类型不一致所以报错

  • 配合 fetch 使用。 大部分情况下,我们在处理 feth 请求的时候,与后端约定,后端返回的数据格式是固定的,只不过 data 部分可能并不确定。
// 我们与后端约定, response 的格式如下,但 data 部分依具体使用场景而定, 泛型可以给个默认值 - any
interface IResponse<T = any> {
    status: number;
    message: string;
    data: T;
}
复制代码
// 我们封装了一个 fetch API,里面对请求进行了处理,例如header、toaster
import fetchData from 'XXX/fetchData';

// 引入上面定义的通用的 response 接口
import { IResponse } from 'XXX/response';

export const getUser<T> = (param: IInput): Promise<IResponse<T>> => {
    return fetchData('xxx/getData').then((res: IResponse<T>) => {
        return res;
    });
};
复制代码

使用的时候:

import getUser from 'XXX/getUser';

// 定义 response 中 data 的类型
interface IData {
    name: string;
    age: number;
}

// 将 data 的类型约束传入泛型
const userInfo = getUser<IData>();

userInfo.data.name = '小刚';  // Right
userInfo.data.name = 666;   // Error
// ts 推断出 data.name 是 string 类型,所以在赋值为 666 的时候报错了
复制代码
  • 泛型类
class Person<TName, TAge> {
    name: TName;
    age: TAge;
}

let xiaoming = new Person<string, number>();
xiaoming.name = '小明';  // Right
xiaoming.age = '8';  // Error: [ts] 不能将类型“8”分配给类型“number”。
复制代码

上面代码因为在创建 xiaoming 的时候规定了 age 类型必须为 number,所以报错了

  • 泛型约束 可以对泛型的结构进行约束
interface IData {
    a: number;
}

function objToArr<T extends IData>(arg: T): T[] {
    console.log(arg.a);
    return [arg];
}

objToArr({a: 1, b: 2});  // -> [{a: 1, b: 2}]
objToArr({b: 2});  // Error: 类型“{ b: number; }”的参数不能赋给类型“IData”的参数。
复制代码
  • 内置对象

TS 为我们的 javascript 的内置对象提供了类型,并且在使用内置对象的时候自动为我们进行类型检测 例如:

let body: HTMLElement = document.body;

let div: HTMLDivElement = document.createElement('div');

document.addEventListener('click', function(e: MouseEvent) {
    console.log('MouseEvent');
});

Math.round('3.3');  // 类型“"3.3"”的参数不能赋给类型“number”的参数
// 因为 Math 对象 round 需要接受一个 number 类型的参数所以报错了,
// 下面是TS核心库定义文件中对 Math 对象的定义
/**
interface Math {
    pow(x: number, y: number): number;
    random(): number;
    round(x: number): number;
    sin(x: number): number;
    // ......
}
declare const Math: Math;
**/
复制代码
  • 不建议使用的内置类型

TS 也定义了 Number,String,Boolean, Object, 但是并不推荐区用这些类型,而是应该使用 number, string, bollean, obiect

let str: String;  // Don't use 'String' as a type. Avoid using the `String` type. Did you mean `string`
复制代码

类型别名

可以给类型起个名字

  • 可以将字面量作为一个类型
type str = 'a';
type num = 1;
const ab: str = 'ab'; // Error: 不能将类型“"ab"”分配给类型“"a"”。
const someNum: num = 2; // Error: 不能将类型“2”分配给类型“1”。
复制代码
  • 可以像 interface 一样使用

虽然使用方式类似,但是类型别名并不能被继承、导出等操作。只能作为

type Person = {
    name: string;
    age: number;
};

const xiaoming: Person = {
    name: '小明',
    age: 18,
};
复制代码
  • 类型别名配合 泛型
type Person<T> = {
    name: string;
    like: <T>[];
};

const xiaohong: Person<string> = {
    name: '小红',
    like: ['dance', 'football'],
};
复制代码
  • 利用类型别名来进行遍历对象

当我们想要比那里一个对象的时候,需要指定每一项元素的 key 的索引签名,否则会报错,比如像下面这样

const obj = {a: 1, b: 2};
for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
        console.log(obj[key]);
    }
}
// Error: 元素隐式具有 "any" 类型,因为类型“{ a: number; b: number; }” 没有索引签名。
复制代码

可以使用 类型别名 + 索引类型来避免该问题

const obj = {a: 1, b: 2};
type ObjKey = keyof typeof obj; // => 'a' | 'b'

for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
        console.log(obj[key as ObjKey]);
    }
}
复制代码

声明文件

声明文件是对某个库的环境声明,文件内对库向外暴露的 API 进行类型注解,使其在使用的时候可以享受到 TS 类型系统带来的检查

  • 声明变量与函数
// 声明变量
declare var foo: number;

// 声明函数
declare function add(x: number, y: number): number;
复制代码
  • 声明一个 interface
// 直接将 interface 导出就行
复制代码
  • 声明一个对象

// 1. 使用命名空间的方式
declare namespace person {
    let name: string;
    let age: number;
}

// 2. 使用 interface
interface IPerson {
    name: string;
    age: number;
}
declare const person: IPerson;
复制代码
  • 声明一个类
declare class Person {
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        console.log(`I'm ${this.name}`);
    }
}

复制代码
  • 在安装 TypeScript 的时候,会自动安装 lib.d.ts 等声明文件。其内部包含了 JavaScript 内置对象及 DOM 中存在各种常见的环境声明。例如: es5.d.ts
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值