Typescript夯实基础——打通任通二脉

643 阅读3分钟

typescript 基础系列

Typescript夯实基础——打通任通二脉

Typescript夯实基础——react

ts 比较难,核心是,里面有好多专有名词,设计新的语法糖。eg:类型推断,类型守卫,类型断言。

而解决它就是了解 它解决什么问题。

0.ts 解决问题

核心是 创建一套 静态类型系统

The TypeScript Tax 是一篇非常优秀的文章,阅读这篇文章能让我们更为客观看待 TS,虽然站在作者的角度看,TS 弊大于利,主要原因是

  1. TS 提供的功能大多都可以用其它工具配合在一定程度上代替,而且类型系统会需要写太多额外的代码,类型系统在一定程度上也破坏了动态语言的灵活性,让一些动态语言特有的模式很难在其中被应用。

作者最终的结论带有很强的主观色彩,但是这篇文章的分析过程非常精彩,就 TS 的各种特性和现在的 JS 生态进行了对比,能让我们对 TS 有一个更全面的了解。

1.类型

要学好ts 就的先了解其类型系统

基本类型

编程实际上就是对数据进行操作和加工的过程。类型系统能辅助我们对数据进行更为准确的操作。TypeScript 的核心就在于其提供一套类型系统,让我们对数据类型有所约束。约束有时候很简单,有时候很抽象。

TS 支持的类型如下:boolean,number,string``[],Tuple,enum,any,void,null,undefined,never,Object

TS 中更复杂的数据结构其实都是针对上述类型的组合,关于类型的基础知识,推荐先阅读基础类型一节,这里只讨论一些容易造成困扰的概念:

enum

注意:是'=' 而不是':'

const enum MediaTypes {
  JSON = "application/json"
}

fetch("https://xxxx/", {
  headers: {
      Accept: MediaTypes.JSON
  }
})
.then((res) => res.json())

never

never 代表代码永远不会执行到这里,常常可以应用在 switch casedefault 中,防止我们遗漏 case 未处理,比如:

代码健壮性

enum ShirTSize {
  XS,
  S,
  M,
  L,
  XL
}

function assertNever(value: never): never {
  console.log(Error(`Unexpected value '${value}'`));
}

function prettyPrint(size: ShirTSize) {
  switch (size) {
      case ShirTSize.S: console.log("small");
      case ShirTSize.M: return "medium";
      case ShirTSize.L: return "large";
      case ShirTSize.XL: return "extra large";
        // case ShirTSize.XS: return "extra small";
      default: return assertNever(size);
  }
}

Any 类型

在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。

let notSure: any = 666;
notSure = "Semlinker";
notSure = false;

any 类型本质上是类型系统的一个逃逸舱。作为开发者,这给了我们很大的自由:TypeScript 允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查。比如:

let value: any;
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK

在许多场景下,这太宽松了。使用 any 类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用 any 类型,就无法使用 TypeScript 提供的大量的保护机制。为了解决 any 带来的问题,TypeScript 3.0 引入了 unknown 类型。

Unknown 类型

就像所有类型都可以赋值给 any,所有类型也都可以赋值给 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)。下面我们来看一下 unknown 类型的使用示例:

let value: unknown;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK

value 变量的所有赋值都被认为是类型正确的。但是,当我们尝试将类型为 unknown 的值赋值给其他类型的变量时会发生什么?

let value: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error

unknown 类型只能被赋值给 any 类型和 unknown 类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。

现在让我们看看当我们尝试对类型为 unknown 的值执行操作时会发生什么。以下是我们在之前 any 章节看过的相同操作:

let value: unknown;

value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error

value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。

内置类型

DOM 和 BOM 提供的内置对象有:

Document、HTMLElement、Event、NodeList 等。

TypeScript 中会经常用到这些类型:

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

它们的定义文件在 TypeScript 核心库的定义文件中。

类型推断

参考

TypeScript 能根据一些简单的规则推断(检查)变量的类型

let foo = 123
    foo ='abc' //不能将类型“"abc"”分配给类型“number”。ts(2322)
function fa(a: number, b: number){
    return a +b
}
let t = fa(1,2)  //number

不能推断的

function foo(a: number, b: number) {
  return a + addOne(b);
}

// 一些使用 JavaScript 库的特殊函数
function addOne(a) {
  return a + 1;
}

let fo = foo(1,2) //any

noImplicitAny

选项 noImplicitAny 用来告诉编译器,当无法推断一个变量时发出一个错误(或者只能推断为一个隐式的 any 类型),你可以:

  • 通过显式添加 :any 的类型注解,来让它成为一个 any 类型;
  • 通过一些更正确的类型注解来帮助 TypeScript 推断类型。

类型断言

参考

TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。

interface Foo {
  bar: number;
  bas: string;
}

const foo = {} as Foo;

类型守卫(类型收窄)

参考

TS 在遇到以下这些条件语句时,会在语句的块级作用域内「收紧」变量的类型,这种类型推断的行为称作类型守卫 (Type Guard)。

  • 类型判断:typeof
  • 实例判断:instanceof
  • 属性判断:in
  • 字面量相等判断:==, ===, !=, !==
function test(input: string | number) {

  if (typeof input == 'string') {
    // 这里 input 的类型「收紧」为 string
  } else {
    // 这里 input 的类型「收紧」为 number
  }
}

类型守卫使用场景

image.png

function test( padding: string | number) {
  if (typeof padding === "number") {
      return  padding * 1000
  }
  if (typeof padding === "string") {
      return padding + 'abc';
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

类型拓展

参考

let name = "semlinker";
//此时变量 name 的类型会被推断为 string 基本类型,因为这是用于初始化它的值的类型。
//从表达式推断变量、属性或函数结果的类型时,源类型的拓宽形式用作目标的推断类型。
//类型的拓宽是所有出现的空类型和未定义类型都被类型 any 替换。

2.泛型

参考

泛型解决什么问题?

function identity (value) {
  return value;
}

console.log(identity(1)) // 1

现在,我们将 identity 函数做适当的调整,以支持 TypeScript 的 Number 类型的参数:

function identity (value: Number) : Number {
  return value;
}
console.log(identity(1)) // 1

如果 value不是 number 是string 怎么处理?

能不能通用点

function identity <T>(value: T) : T {
  return value;
}

console.log(identity<Number>(1)) // 1

解释一下

image.png

**总结一下,**设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。

泛型原理

image.png

function identity <T, U>(value: T, message: U) : T {
  console.log(message);
  return value;
}

console.log(identity(68, "Semlinker"));

对于上述代码,编译器足够聪明,能够知道我们的参数类型,并将它们赋值给 T 和 U,而不需要开发人员显式指定它们。下面我们来看张动图,直观地感受一下类型传递的过程:

1_Zz4Y9ScEbGbRrtIWby4msg.gif

3. tsconfig.json

参考

告诉tsc 引擎如何解析ts

image.png