《Typescript 全面进阶指南》小册--学习笔记

139 阅读9分钟

大家好。这是掘金小册Typescript 全面进阶指南的学习笔记。

Day 1

VS Code 插件

  • TypeScript Importer,自动补全类型导入
  • Move TS,修改ts文件路径时可以把与他相关的其他路径都改了
  • ErrorLens,将ts报错信息标记在出错的代码旁边

官方Playground

工具

  • ts-node,ts的repl。与tsc的关系是:tsc是ts编译器,ts-node是ts执行环境,后者依赖前者
  • ts-node-dev,能监听文件变化并重启的ts-node,基于ts-nodenode-dev实现
  • declare 关键字:用于存放 TypeScript 类型信息的内存空间,声明了一个仅在类型空间存在的变量
  • tsd,类似断言
import { expectType } from 'tsd';

expectType<string>("linbudu"); // √
expectType<string>(599); // ×

结构大致是这样:expectType<你预期的类型>(表达式或变量等)

扩展

require.extension: 是ts-node、require-ts (允许你去 require 一个 TS 文件)这些工具库,它们的核心逻辑其实都是通过 require.extension,注册了 .ts 文件的处理逻辑。

Day 2

null 与 undefined

两者在没有开启 strictNullChecks 检查的情况下,会被视作其他类型的子类型,比如 string 类型会被认为包含了 null 与 undefined 类型。 void表示一个空类型,而nullundefined是具有意义的实际类型。

viod

void 运算符对给定的表达式进行求值,然后返回 undefined

void (9) // undefined  等同于`void 0
void 1 // undefined
void [] // undefined
void {} // undefined
void [1] // undefined
void { a:1 } // undefined
void '' // undefined
void null // undefined
void undefined // undefined

const output = void 1;
console.log(output);
// Expected output: undefined

void console.log('expression evaluated');
// Expected output: "expression evaluated"

void (function iife() {
  console.log('iife is executed');
})();
// Expected output: "iife is executed"

void function test() {
  console.log('test function executed');
};
try {
  test();
} catch (e) {
  console.log('test function is not defined');
  // Expected output: "test function is not defined"
}

TypeScript 的原始类型标注中也有 void,用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值

function func1() {}
function func2() {
  return;
}
function func3() {
  return undefined;
}

数组

const arr1: string[] = [];
const arr2: Array<string> = [];

const arr3: readonly string[] = [];
const arr4: ReadonlyArray<string> = [];
元组Tuple

元组支持了在某一个位置上的可选成员

const arr6: [string, number?, boolean?] = ['linbudu'];
// 下面这么写也可以
// const arr6: [string, number?, boolean?] = ['linbudu', , ,];

对于标记为可选的成员,在 --strictNullCheckes 配置下会被视为一个 string | undefined 的类型。此时元组的长度属性也会发生变化,比如上面的元组 arr6 ,其长度的类型为 1 | 2 | 3

type TupleLength = typeof arr6.length; // 1 | 2 | 3

在 TypeScript 4.0 中,有了具名元组

const arr7: [name: string, age: number, male?: boolean] = ['linbudu', 599, true];

显式和隐式地越界访问

// 显式地越界访问
const arr4: [string, string, string] = ['lin', 'bu', 'du']; 

console.log(arr4[599]);
// 隐式地越界访问
const arr5: [string, number, boolean] = ['linbudu', 599, true];
// 长度为 "3" 的元组类型 "[string, number, boolean]" 在索引 "3" 处没有元素。
const [name, age, male, other] = arr5;

type与interface

type(Type Alias,类型别名)职责:将一个函数签名、一组联合类型、一个工具类型等等抽离成一个完整独立的类型。

interface职责:用来描述对象、类的结构

但大部分场景下接口结构都可以被类型别名所取代

object、Object 以及 { }

Object是装箱类型,原型链的顶端是 Object 以及 Function,这也就意味着所有的原始类型与对象类型最终都指向 Object。和 Object 类似的还有 Boolean、Number、String、Symbol,这几个装箱类型(Boxed Types) 同样包含了一些超出预期的类型。

在任何情况下,你都不应该使用这些装箱类型。

object它代表所有非原始类型的类型,即数组、对象与函数类型这些

const tmp17: object = undefined;
const tmp18: object = null;
const tmp19: object = void 0;

const tmp20: object = 'linbudu';  // X 不成立,值为原始类型
const tmp21: object = 599; // X 不成立,值为原始类型

const tmp22: object = { name: 'linbudu' };
const tmp23: object = () => {};
const tmp24: object = [];

{}代表对象字面量类型,或者叫内部无属性定义的空对象,可以表示任何非 null / undefined 的值,不应该使用它。

const tmp25: {} = undefined; // 仅在关闭 strictNullChecks 时成立,下同
const tmp26: {} = null;
const tmp27: {} = void 0; // void 0 等价于 undefined

const tmp28: {} = 'linbudu';
const tmp29: {} = 599;
const tmp30: {} = { name: 'linbudu' };
const tmp31: {} = () => {};
const tmp32: {} = [];

无法对这个变量进行任何赋值操作

Day 3

null 与 undefined

两者在没有开启 strictNullChecks 检查的情况下,会被视作其他类型的子类型,比如 string 类型会被认为包含了 null 与 undefined 类型。 void表示一个空类型,而nullundefined是具有意义的实际类型。

viod

void 运算符对给定的表达式进行求值,然后返回 undefined

void (9) // undefined  等同于`void 0
void 1 // undefined
void [] // undefined
void {} // undefined
void [1] // undefined
void { a:1 } // undefined
void '' // undefined
void null // undefined
void undefined // undefined

const output = void 1;
console.log(output);
// Expected output: undefined

void console.log('expression evaluated');
// Expected output: "expression evaluated"

void (function iife() {
  console.log('iife is executed');
})();
// Expected output: "iife is executed"

void function test() {
  console.log('test function executed');
};
try {
  test();
} catch (e) {
  console.log('test function is not defined');
  // Expected output: "test function is not defined"
}

TypeScript 的原始类型标注中也有 void,用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值

function func1() {}
function func2() {
  return;
}
function func3() {
  return undefined;
}

数组

const arr1: string[] = [];
const arr2: Array<string> = [];
// 只读
const arr3: readonly string[] = [];
const arr4: ReadonlyArray<string> = [];
元组Tuple

元组支持了在某一个位置上的可选成员

const arr6: [string, number?, boolean?] = ['linbudu'];
// 下面这么写也可以
// const arr6: [string, number?, boolean?] = ['linbudu', , ,];

对于标记为可选的成员,在 --strictNullCheckes 配置下会被视为一个 string | undefined 的类型。此时元组的长度属性也会发生变化,比如上面的元组 arr6 ,其长度的类型为 1 | 2 | 3

type TupleLength = typeof arr6.length; // 1 | 2 | 3

在 TypeScript 4.0 中,有了具名元组

const arr7: [name: string, age: number, male?: boolean] = ['linbudu', 599, true];

显式和隐式地越界访问

// 显式地越界访问
const arr4: [string, string, string] = ['lin', 'bu', 'du']; 

console.log(arr4[599]);
// 隐式地越界访问
const arr5: [string, number, boolean] = ['linbudu', 599, true];
// 长度为 "3" 的元组类型 "[string, number, boolean]" 在索引 "3" 处没有元素。
const [name, age, male, other] = arr5;

type与interface的取舍

type(Type Alias,类型别名)职责:将一个函数签名、一组联合类型、一个工具类型等等抽离成一个完整独立的类型。

interface职责:用来描述对象、类的结构

但大部分场景下接口结构都可以被类型别名所取代

object、Object 以及 { }

Object是装箱类型,原型链的顶端是 Object 以及 Function,这也就意味着所有的原始类型与对象类型最终都指向 Object。和 Object 类似的还有 Boolean、Number、String、Symbol,这几个装箱类型(Boxed Types) 同样包含了一些超出预期的类型。

在任何情况下,你都不应该使用这些装箱类型。

object它代表所有非原始类型的类型,即数组、对象与函数类型这些

const tmp17: object = undefined;
const tmp18: object = null;
const tmp19: object = void 0;

const tmp20: object = 'linbudu';  // X 不成立,值为原始类型
const tmp21: object = 599; // X 不成立,值为原始类型

const tmp22: object = { name: 'linbudu' };
const tmp23: object = () => {};
const tmp24: object = [];

{}代表对象字面量类型,或者叫内部无属性定义的空对象,可以表示任何非 null / undefined 的值,不应该使用它。

const tmp25: {} = undefined; // 仅在关闭 strictNullChecks 时成立,下同
const tmp26: {} = null;
const tmp27: {} = void 0; // void 0 等价于 undefined

const tmp28: {} = 'linbudu';
const tmp29: {} = 599;
const tmp30: {} = { name: 'linbudu' };
const tmp31: {} = () => {};
const tmp32: {} = [];

无法对这个变量进行任何赋值操作

Day 4

字面量类型

字面量类型,它代表着比原始类型更精确(值级别的字面量一致)的类型, 字面量类型,是原始类型的子类型 字面量类型,主要包括字符串字面量类型数字字面量类型布尔字面量类型对象字面量类型,它们可以直接作为类型标注:

const str: "abc" = "abc";
const num: 599 = 599;
const bool: true = true;

str = ''; // 不能将类型“""”分配给类型“"abc"”
联合类型

它代表了一组类型的可用集合

interface Tmp {
  mixed: true | string | 599 | {} | (() => {}) | (1 | 2)
}
  • 对于联合类型中的函数类型,需要使用括号()包裹起来
  • 函数类型并不存在字面量类型,因此这里的 (() => {}) 就是一个合法的函数类型
  • 你可以在联合类型中进一步嵌套联合类型,但这些嵌套的联合类型最终都会被展平到第一级中
对象字面量类型

对象字面量类型是一个对象类型的值。对象的值全都为字面量值。

interface Tmp {
  obj: {
    name: "a",
    age: 18
  }
}

const tmp: Tmp = {
  obj: {
    name: "a",
    age: 18
  }
}

无论是原始类型还是对象类型的字面量类型,它们的本质都是类型而不是值

枚举

枚举在某些方面则可以理解为是对对象类型的扩展。 延迟求值的枚举值

const returnNum = () => 100 + 499;

enum Items {
  Foo = returnNum(),
  Bar = 599,
  Baz
}

注意,延迟求值的枚举值是有条件的。如果你使用了延迟求值,那么没有使用延迟求值的枚举成员必须放在使用常量枚举值声明的成员之后(如上例),或者放在第一位

枚举和对象的重要差异在于:

  • 对象是单向映射的,键->值;
  • 枚举是双向映射的,键 -> 值 和 值 -> 键。
enum Items {
  Foo,
  Bar,
  Baz
}
const fooValue = Items.Foo; // 0
const fooKey = Items[0]; // "Foo"

仅有值为数字的枚举成员才能够进行这样的双向枚举,字符串枚举成员仍然只会进行单次映射

enum Items {
  Foo,
  Bar = "BarValue",
  Baz = "BazValue"
}

// 编译结果,只会进行 键-值 的单向映射
"use strict";
var Items;
(function (Items) {
    Items[Items["Foo"] = 0] = "Foo";
    Items["Bar"] = "BarValue";
    Items["Baz"] = "BazValue";
})(Items || (Items = {}));
常量枚举

常量枚举和枚举相似,只是其声明多了一个 const:

const enum Items {
  Foo,
  Bar,
  Baz
}

const fooValue = Items.Foo; // 0

只能通过枚举成员访问枚举值(而不能通过值访问成员)