Advertisement

typescript-基础入门

阅读量:

写在前面

JavaScript 是一门弱类型语言,变量的数据类型具有动态性,只有执行时才能确定变量的类型。以往的前端开发过程中,很容易出现以下的报错:

复制代码
    // 读取对象上不存在的属性
    Uncaught TypeError: Cannot read property ... of undefined
    
    // 类型错误
    TypeError: 'undefined' is not a ...

以对象 obj 为例,在 typescript 中可以用接口来定义一个对象类型

复制代码
    interface IObj {
      name: string
    }
    
    const obj: IObj;

通过这种方式限制 obj 的属性类型,在赋值时只能是添加 name 属性,并且不能给 name 赋值 string 以外的类型;每当你获取 obj 属性的值时,会在编译阶段检查该属性是否存在,不存在会提前抛出类型错误。

在变量声明之前做好类型定义,并通过提前暴露和修复这些可能出现的类型错误,可以大大降低应用在运行时出现错误的概率,使得 javascript 应用更加健壮,这一切得益于类型思维。

作用总结为以下几点:

  • 类型检查
  • 提供更丰富的语法(es6)
  • 编译器提示友好

思维方式决定了编程习惯,编程习惯奠定了工程质量,工程质量划定了能力边界

数据类型

number

数字类型,但是new Number(0)这种是对象,并不是数字类型

复制代码
    let num: number = 0;
    let num = 0; // 这样也可以,ts 会进行隐性的类型推论,不过还是建议明确定义类型

string

复制代码
    let str: string = "str";

boolean

复制代码
    let bool: boolean = true;

数组

主要有两种方式定义数组类型,一般只能定义数组元素都为某一种类型的数组

复制代码
    let arr: number[] = [1, 2, 3];
    let arr: Array<number> = [1, 2, 3];
    // 定义一个不确定元素类型的数组
    let arr: any[] = [];
    // 定义一个只读的数组
    let arr: ReadonlyArray<number> = [1, 2];

元组

元组可以用于定义一个具有多种类型元素的数组,元素数量已知,并且他们的顺序是确定的

复制代码
    const arr: [string, number] = ["cat", 1];
    const arr: [string, number?] = ["cat", 1];
    const arr: [string, number?] = ["cat"];
    function fn(...args: [number, string]): void;

TypeScript 4.0 支持为元组类型设置标签

复制代码
    function fn(...args: [age: number, id: string]): void;

枚举

列出所有可用值,默认初始值是 0。常用于状态值管理等

复制代码
    enum MyPets {
      cat = "lazy cat",
      dog,
      mouse,
    }
    let cat: MyPets = MyPets.cat;

枚举的原理其实也很简单,相当于定义对象时多做了 value–>key 的映射,以上面的代码为例

复制代码
    var MyPets;
    (function (MyPets) {
    MyPets["cat"] = "lazy cat";
    MyPets[MyPets["dog"] = void 0] = "dog";
    MyPets[MyPets["mouse"] = void 0] = "mouse";
    })(MyPets || (MyPets = {}));

null

类似于 js 的 null,是所有类型的子类型

undefined

类似于 js 的 undefined,是所有类型的子类型

void

当函数没有返回值时,可以直接用 void 定义返回值类型。

复制代码
    function func(): void {
      ...
    }

声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null

unknown

所有类型都可以分配给 unknown,但只能将 unknown 类型的变量赋值给 any 和 unknown

any

声明一个 any 类型的变量,它可以赋给任何类型值,也可以接受任何类型值,也就意味着放弃对这个变量的类型检查,不建议使用。

复制代码
    let demo: any = 2; // 接受任何类型值
    let demo_2: string;
    demo_2 = demo; // 赋给任何类型值

any 和 unknown 的最大区别是, unknown 是 top type (任何类型都是它的 subtype) , 而 any 即是 top type, 又是 bottom type (它是任何类型的 subtype ) ,这导致 any 基本上就是放弃了任何类型检查.

never

当一个函数永远不会返回时,我们可以声明返回值类型为 never

复制代码
    function func(): never {
      throw new Error("never reach");
    }

可以利用 never 在类型收窄时做全面性的检测,防止后面对 TPayload 的意外修改,确保类型安全

复制代码
    type TPayload = number | string;
    function func(payload: TPayload) {
      if (typeof payload === "number") {
    return 0;
      } else if (typeof payload === "string") {
    return "";
      } else {
    const res: never = payload;
    return res;
      }
    }

接口

接口(interface)常用于定义对象类型

复制代码
    interface IObj {
      name: string;
      age?: number; // 可选
      readonly type: string; // 只读属性,只能在创建的时候赋值
      [key: string]: string; // 索引类型,允许定义多个key为string的string类型值
    }

同名接口会合并成员,如果有相同的成员,必须为同类型,否则编译器会报错。

复制代码
    interface IObj {
      name: string;
      func(id: string): string;
    }
    interface IObj {
      age: number;
      func(id: number): number;
    }
    // 相当于
    interface IObj {
      name: string;
      age: number;
      func(id: number): number;
      func(id: string): string;
    }

更详细的合并规则可以参考 typescript 的声明合并

函数

定义入参和返回值类型,必传参数可具有默认值

复制代码
    // age是可选参数,需要放在必传参数name后面
    function func (id: string = '007', num?: number): string {
      ...
    }
    let func: (a: string) => string = function(a: string): string{
    return 'JacksonZhou'
    }

也可以通过接口定义函数,分为 callable 和 newable 两种形式

复制代码
    // callable
    interface Func {
      (id: number, ...others: any[]): string;
    }
    const fn: Func;
    // newable
    interface NewFunc {
      new (): string;
    }
    const fn: NewFunc;
    new fn();

函数重载

复制代码
    function func(a: number, b: number);
    function func(a: string, b: string);
    function func(a: number, b: number, c: number) {
      //...
    }

函数重载的原理是重新定义所有重名函数,并放到一个列表里,匹配函数时会从上到下,通过匹配参数类型来选择指定函数

interface 和 type 的区别是啥?

交叉类型

复制代码
    let id: string & number;

联合类型

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型;

复制代码
    let id: string | number;
    id = "2";
    console.log(id.length);
    id = 2;
    console.log(id.length); // 报错,数字类型没有length属性

类型断言

对类型有把握但是又不能准确定义的时候,可以使用类型断言

常规断言(<> or as):

复制代码
    const id: any = "hello";
    const length = (id as string).length;
    // const length = (<string>id).length;

实际开发中使用的几率不大,比如下面这种情况就可以使用断言来规避编译报错

复制代码
    interface Cat {
      name: string;
      run(): void;
    }
    interface Fish {
      name: string;
      swim(): void;
    }
    
    function isFish(animal: Cat | Fish) {
      if (typeof (animal as Cat).run === "function") {
    return true;
      }
      return false;
    }

非空断言(!):

复制代码
    function fn(maybeString: string | undefined | null) {
      const onlyString1: string = maybeString; // Error
      const onlyString2: string = maybeString!; // Ok
    }
    
    function fn(getNum: () => number | undefined) {
      const num1 = getNum(); // Error
      const num2 = getNum!(); // OK
    }

类型守卫

类型守卫指的就是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内,主要有以下几种方法检测类型:

  • in
  • typeof
  • instanceof
  • is
复制代码
    function isNumber(x: any): x is number {
      return typeof x === "number";
    }

泛型

简单讲,可以将类型作为参数,用来定义未来的数据类型,参考 https://www.tangshuang.net/7473.html

复制代码
    function func<T>(arg: T): T {
      return arg;
    }
    
    let func_1 = func<string>("cat");
    let func_2 = func("cat");

typescript 对类属性的访问控制

可访问性 public protected private
yes yes yes
子类 yes yes no
类实例 yes no no
复制代码
    class Cat {
      name: string; // 这个是 this.name 的类型定义
      food: string;
    
      constructor(name: string, food: string) {
    this.name = name;
    this.food = food;
      }
    
      eat() {
    console.log(this.name + " eat " + this.food);
      }
    }
    
    let myCat: Cat = new Cat("Tom", fish);
    console.log(myCat.eat());

操作符

?.

可选链,在引用某个对象的属性时,会经常用到,在遇到 undefined 和 null 时会立即阻止表达式的运行,并返回 undefined,编译后的 es5 代码如下:

复制代码
    a?.b?.c
    
    (_a = a === null || a === void 0 ? void 0 : a.b) === null || _a === void 0 ? void 0 : _a.c;

void 运算符能对给定的表达式进行求值,然后返回undefined,在这里void 0就相当于undefined,为什么不直接用undefined呢?原因是undefined并不是保留字,它只是全局对象的一个属性,虽然在 es5 是全局对象的一个只读属性,但是在局部作用域中依然可以被重写。

??

更多人会接触到短路操作符 ||,常用于数据兜底,但是它会影响 falsy
值,包括’'、0、false、undefined、null…,而 ?? 只会影响 undefined 和 null。?? 不能和 || 或者 && 并用,会报 SyntaxError 错误,但是可以用括号定义运算优先级来避免这种错误

!非空断言

可以用于忽略 undefined 和 null

复制代码
    function myFunc(id: string | undefined | null) {
      const myId: string = id;         // Error
      const ignoreUAndN: string = id!; // Ok
    }
    function myFunc(fn: Function | undefined) {
      fn!(); // Ok
    }

内置对象

object/Object/{}

  • object - 表示非原始类型
  • Object - 所有 Object 类的实例的类型
复制代码
    // node_modules/typescript/lib/lib.es5.d.ts
    interface Object {
      constructor: Function;
      toString(): string;
      toLocaleString(): string;
      valueOf(): Object;
      hasOwnProperty(v: PropertyKey): boolean;
      isPrototypeOf(v: Object): boolean;
      propertyIsEnumerable(v: PropertyKey): boolean;
    }
    
    interface ObjectConstructor {
      // Object 类的所有实例都继承了 Object 接口中的所有属性
      new (value?: any): Object;
      (value?: any): any;
      readonly prototype: Object;
      getPrototypeOf(o: any): any;
      // ···
    }
    
    declare var Object: ObjectConstructor;
  • {} - 没有成员的对象

DOM

Document、HTMLElement、Event、NodeList

其他

Boolean、Error、Date、RegExp

辅助工具

全部评论 (0)

还没有任何评论哟~