docs: finish chapter assert
This commit is contained in:
@@ -10,3 +10,5 @@
|
||||
- interface.md: interface
|
||||
- class.md: 类
|
||||
- generics.md: 泛型
|
||||
- enum.md: Enum 类型
|
||||
- assert.md: 类型断言
|
||||
|
||||
@@ -13,9 +13,9 @@ let bar:T = foo; // 报错
|
||||
|
||||
上面示例中,最后一行报错,原因是 TypeScript 推断变量`foo`的类型是`string`,而变量`bar`的类型是`'a'|'b'|'c'`,前者是后者的父类型。父类型不能赋值给子类型,所以就报错了。
|
||||
|
||||
这时,TypeScript 提供了“类型断言”这样一种手段,允许开发者在代码中“断言”某个值的类型,提示编译器此处的值是什么类型。TypeScript 一旦发现存在类型断言,就不再对该值进行类型推断,而是直接采用断言给出的类型。
|
||||
TypeScript 提供了“类型断言”这样一种手段,允许开发者在代码中“断言”某个值的类型,告诉编译器此处的值是什么类型。TypeScript 一旦发现存在类型断言,就不再对该值进行类型推断,而是直接采用断言给出的类型。
|
||||
|
||||
这种做法的实质是,允许开发者在某个位置“绕过”编译器的类型推断,使其能够通过类型检查,避免编译器报错。这样虽然削弱了 TypeScript 类型系统的严格性,但是为开发者带来了方便,毕竟开发者比编译器更了解自己的代码。
|
||||
这种做法的实质是,允许开发者在某个位置“绕过”编译器的类型推断,让本来通不过类型检查的代码能够通过,避免编译器报错。这样虽然削弱了 TypeScript 类型系统的严格性,但是为开发者带来了方便,毕竟开发者比编译器更了解自己的代码。
|
||||
|
||||
回到上面的例子,解决方法就是进行类型断言,在赋值时断言变量`foo`的类型。
|
||||
|
||||
@@ -65,7 +65,7 @@ const p:{ x: number } = { x: 0, y: 0 };
|
||||
// 正确
|
||||
const p0:{ x: number } =
|
||||
{ x: 0, y: 0 } as { x: number };
|
||||
|
||||
|
||||
// 正确
|
||||
const p1:{ x: number } =
|
||||
{ x: 0, y: 0 } as { x: number; y: number };
|
||||
@@ -83,7 +83,7 @@ if (username) {
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,变量`username`的类型是`HTMLElement|null`,排除了`null`的情况以后,HTMLElement 类型是没有`value`属性的。如果`username`是一个输入框,那么就可以通过类型断言,将它的类型改成`HTMLInputElement`,就可以读取`value`属性。
|
||||
上面示例中,变量`username`的类型是`HTMLElement | null`,排除了`null`的情况以后,HTMLElement 类型是没有`value`属性的。如果`username`是一个输入框,那么就可以通过类型断言,将它的类型改成`HTMLInputElement`,就可以读取`value`属性。
|
||||
|
||||
注意,上例的类型断言的圆括号是必需的,否则`username`会被断言成`HTMLInputElement.value`,从而报错。
|
||||
|
||||
@@ -118,12 +118,12 @@ const s2:string = value as string; // 正确
|
||||
|
||||
```typescript
|
||||
const s1:number|string = 'hello';
|
||||
const s2:number = s1 as number;
|
||||
const s2:number = s1 as number;
|
||||
```
|
||||
|
||||
上面示例中,变量`s1`是联合类型,可以断言其为联合类型里面的一种具体类型,再将其赋值给变量`s2`。
|
||||
|
||||
## 类型断言的前提
|
||||
## 类型断言的条件
|
||||
|
||||
类型断言并不意味着,可以把某个值断言为任意类型。
|
||||
|
||||
@@ -167,10 +167,10 @@ const m:string = n as unknown as string; // 正确
|
||||
如果没有声明变量类型,let 命令声明的变量,会被类型推断为 TypeScript 内置的基本类型之一;const 命令声明的变量,则被推断为值类型常量。
|
||||
|
||||
```typescript
|
||||
// 类型推断为 string
|
||||
// 类型推断为基本类型 string
|
||||
let s1 = 'JavaScript';
|
||||
|
||||
// 类型推断为 JavaScript
|
||||
// 类型推断为字符串 “JavaScript”
|
||||
const s2 = 'JavaScript';
|
||||
```
|
||||
|
||||
@@ -181,7 +181,7 @@ const s2 = 'JavaScript';
|
||||
```typescript
|
||||
let s = 'JavaScript';
|
||||
|
||||
type Lang =
|
||||
type Lang =
|
||||
|'JavaScript'
|
||||
|'TypeScript'
|
||||
|'Python';
|
||||
@@ -228,7 +228,13 @@ let s = 'JavaScript';
|
||||
setLang(s as const); // 报错
|
||||
```
|
||||
|
||||
上面示例中,`as const`断言用于变量`s`,就报错了。
|
||||
上面示例中,`as
|
||||
const`断言用于变量`s`,就报错了。下面的写法可以更清晰地看出这一点。
|
||||
|
||||
```typescript
|
||||
let s1 = 'JavaScript';
|
||||
let s2 = s1 as const; // 报错
|
||||
```
|
||||
|
||||
另外,`as const`也不能用于表达式。
|
||||
|
||||
@@ -286,7 +292,7 @@ const a2 = [1, 2, 3] as const;
|
||||
由于`as const`会将数组变成只读元组,所以很适合用于函数的 rest 参数。
|
||||
|
||||
```typescript
|
||||
function add(x: number, y: number) {
|
||||
function add(x:number, y:number) {
|
||||
return x + y;
|
||||
}
|
||||
|
||||
@@ -311,8 +317,8 @@ Enum 成员也可以使用`as const`断言。
|
||||
|
||||
```typescript
|
||||
enum Foo {
|
||||
X,
|
||||
Y,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
let e1 = Foo.X; // Foo
|
||||
let e2 = Foo.X as const; // Foo.X
|
||||
@@ -326,16 +332,17 @@ let e2 = Foo.X as const; // Foo.X
|
||||
|
||||
```typescript
|
||||
function f(x?:number|null) {
|
||||
validateNumber(x);
|
||||
validateNumber(x); // 自定义函数,确保 x 是数值
|
||||
console.log(x!.toFixed());
|
||||
}
|
||||
|
||||
function validateNumber(e?:number|null) {
|
||||
// 如果 e 不是数值,就抛出错误
|
||||
function validateNumber(e?:number|null) {
|
||||
if (typeof e !== 'number')
|
||||
throw new Error('Not a number');
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,变量`x`的类型是`number|null`,即可能为空。如果为空,就不存在`.toFixed()`方法,编译时会报错。但是,开发者有时可以确认,变量`x`不会为空,这时就可以使用非空断言,为函数体内部的变量`x`加上后缀`!`,编译就不会报错了。
|
||||
上面示例中,函数`f()`的参数`x`的类型是`number|null`,即可能为空。如果为空,就不存在`x.toFixed()`方法,这样写会报错。但是,开发者可以确认,经过`validateNumber()`的前置检验,变量`x`肯定不会为空,这时就可以使用非空断言,为函数体内部的变量`x`加上后缀`!`,`x!.toFixed()`编译就不会报错了。
|
||||
|
||||
非空断言在实际编程中很有用,有时可以省去一些额外的判断。
|
||||
|
||||
@@ -348,7 +355,7 @@ root.addEventListener('click', e => {
|
||||
});
|
||||
```
|
||||
|
||||
上面示例中,`getElementById()`有可能返回空值`null`,即变量`root`可能为空,这时对它调用`addEventListener()`方法就会报错,通不过编译。但是一般来说,开发者可以确认`root`元素肯定会在网页中存在,这时就可以使用非空断言。
|
||||
上面示例中,`getElementById()`有可能返回空值`null`,即变量`root`可能为空,这时对它调用`addEventListener()`方法就会报错,通不过编译。但是,开发者如果可以确认`root`元素肯定会在网页中存在,这时就可以使用非空断言。
|
||||
|
||||
```typescript
|
||||
const root = document.getElementById('root')!;
|
||||
@@ -356,13 +363,13 @@ const root = document.getElementById('root')!;
|
||||
|
||||
上面示例中,`getElementById()`方法加上后缀`!`,表示这个方法肯定返回非空结果。
|
||||
|
||||
非空断言会造成安全隐患,只有在确定一个表达式的值不为空时才能使用。比较保险的做法还是手动检查一下是否为空。
|
||||
不过,非空断言会造成安全隐患,只有在确定一个表达式的值不为空时才能使用。比较保险的做法还是手动检查一下是否为空。
|
||||
|
||||
```typescript
|
||||
const root = document.getElementById('root');
|
||||
|
||||
if (root === null) {
|
||||
throw Error('Unable to find DOM element #root');
|
||||
throw new Error('Unable to find DOM element #root');
|
||||
}
|
||||
|
||||
root.addEventListener('click', e => {
|
||||
@@ -387,7 +394,7 @@ class Point {
|
||||
|
||||
上面示例中,属性`x`和`y`会报错,因为 TypeScript 认为它们没有初始化。
|
||||
|
||||
这时就可以使用非空断言,表示这两个属性肯定有值,这样就不会报错了。
|
||||
这时就可以使用非空断言,表示这两个属性肯定会有值,这样就不会报错了。
|
||||
|
||||
```typescript
|
||||
class Point {
|
||||
@@ -507,8 +514,8 @@ function assertIsDefined<T>(
|
||||
const assertIsNumber = (
|
||||
value:unknown
|
||||
):asserts value is number => {
|
||||
if (typeof value !== 'number')
|
||||
throw Error('Not a number');
|
||||
if (typeof value !== 'number')
|
||||
throw Error('Not a number');
|
||||
};
|
||||
|
||||
// 写法二
|
||||
@@ -516,8 +523,8 @@ type AssertIsNumber =
|
||||
(value:unknown) => asserts value is number;
|
||||
|
||||
const assertIsNumber:AssertIsNumber = (value) => {
|
||||
if (typeof value !== 'number')
|
||||
throw Error('Not a number');
|
||||
if (typeof value !== 'number')
|
||||
throw Error('Not a number');
|
||||
};
|
||||
```
|
||||
|
||||
@@ -568,7 +575,8 @@ function loadPerson(): Person | null {
|
||||
let person = loadPerson();
|
||||
|
||||
function assert(
|
||||
condition:unknown, message:string
|
||||
condition: unknown,
|
||||
message: string
|
||||
):asserts condition {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
@@ -585,3 +593,4 @@ console.log(person.name);
|
||||
- [Const Assertions in Literal Expressions in TypeScript](https://mariusschulz.com/blog/const-assertions-in-literal-expressions-in-typescript), Marius Schulz
|
||||
- [Assertion Functions in TypeScript](https://mariusschulz.com/blog/assertion-functions-in-typescript), Marius Schulz
|
||||
- [Assertion functions in TypeScript](https://blog.logrocket.com/assertion-functions-typescript/), Matteo Di Pirro
|
||||
|
||||
|
||||
51
docs/enum.md
51
docs/enum.md
@@ -20,7 +20,7 @@ if (color === BLUE) {/* */}
|
||||
throw new Error('wrong color');
|
||||
```
|
||||
|
||||
上面示例中,常量`RED`、`GREEN`、`BLUE`是相关的,而且它们具体等于什么值并不重要,只要不相等就可以了。
|
||||
上面示例中,常量`RED`、`GREEN`、`BLUE`是相关的,意为变量`color`的三个可能的取值。它们具体等于什么值其实并不重要,只要不相等就可以了。
|
||||
|
||||
TypeScript 就设计了 Enum 结构,用来将相关常量放在一个容器里面,方便使用。
|
||||
|
||||
@@ -37,7 +37,7 @@ enum Color {
|
||||
使用时,调用 Enum 的某个成员,与调用对象属性的写法一样,可以使用点运算符,也可以使用方括号运算符。
|
||||
|
||||
```typescript
|
||||
let c = Color.Green; // 1
|
||||
let c = Color.Green; // 1
|
||||
// 等同于
|
||||
let c = Color['Green']; // 1
|
||||
```
|
||||
@@ -76,10 +76,10 @@ let Color = {
|
||||
Enum 结构比较适合的场景是,成员的值不重要,名字更重要,从而增加代码的可读性和可维护性。
|
||||
|
||||
```typescript
|
||||
enum Operator {
|
||||
ADD,
|
||||
DIV,
|
||||
MUL,
|
||||
enum Operator {
|
||||
ADD,
|
||||
DIV,
|
||||
MUL,
|
||||
SUB
|
||||
}
|
||||
|
||||
@@ -87,19 +87,19 @@ function compute(
|
||||
op:Operator,
|
||||
a:number,
|
||||
b:number
|
||||
) {
|
||||
switch (op) {
|
||||
case Operator.ADD:
|
||||
) {
|
||||
switch (op) {
|
||||
case Operator.ADD:
|
||||
return a + b;
|
||||
case Operator.DIV:
|
||||
return a / b;
|
||||
case Operator.DIV:
|
||||
return a / b;
|
||||
case Operator.MUL:
|
||||
return a * b;
|
||||
case Operator.SUB:
|
||||
return a - b;
|
||||
return a - b;
|
||||
default:
|
||||
throw new Error('wrong operator');
|
||||
}
|
||||
throw new Error('wrong operator');
|
||||
}
|
||||
}
|
||||
|
||||
compute(Operator.ADD, 1, 3) // 4
|
||||
@@ -110,16 +110,16 @@ compute(Operator.ADD, 1, 3) // 4
|
||||
Enum 作为类型有一个缺点,就是输入任何数值都不报错。
|
||||
|
||||
```typescript
|
||||
enum Bool {
|
||||
enum Bool {
|
||||
No,
|
||||
Yes
|
||||
Yes
|
||||
}
|
||||
|
||||
function foo(noYes:Bool) {
|
||||
// ...
|
||||
}
|
||||
|
||||
func(33); // 不报错
|
||||
foo(33); // 不报错
|
||||
```
|
||||
|
||||
上面代码中,函数`foo`的参数`noYes`只有两个可用的值,但是输入任意数值,编译都不会报错。
|
||||
@@ -146,7 +146,7 @@ enum Foo {
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
|
||||
const Bar = {
|
||||
A: 0,
|
||||
B: 1,
|
||||
@@ -173,7 +173,7 @@ enum Color {
|
||||
Blue
|
||||
}
|
||||
|
||||
// 等同于
|
||||
// 等同于
|
||||
enum Color {
|
||||
Red = 0,
|
||||
Green = 1,
|
||||
@@ -288,13 +288,13 @@ const y = 1 /* Color.Green */;
|
||||
const z = 2 /* Color.Blue */;
|
||||
```
|
||||
|
||||
上面示例中,由于 Enum 结构前面加了`const`关键字,所以编译产物里面就没有生成对应的对象,而是把所有 Enum 成员出现的场合,都替换成对应的常量。
|
||||
上面示例中,由于 Enum 结构前面加了`const`关键字,所以编译产物里面就没有生成对应的对象,而是把所有 Enum 成员出现的场合,都替换成对应的常量。
|
||||
|
||||
如果希望加上`const`关键词后,运行时还能访问 Enum 结构(即编译后依然将 Enum 转成对象),需要在编译时打开`preserveConstEnums`参数。
|
||||
如果希望加上`const`关键词后,运行时还能访问 Enum 结构(即编译后依然将 Enum 转成对象),需要在编译时打开`preserveConstEnums`编译选项。
|
||||
|
||||
## 同名 Enum 的合并
|
||||
|
||||
多个同名的 Enum 结构合并成一个 Enum 结构。
|
||||
多个同名的 Enum 结构会自动合并。
|
||||
|
||||
```typescript
|
||||
enum Foo {
|
||||
@@ -404,7 +404,7 @@ enum Foo {
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,`A`之前没有其他成员,所以可以不设置初始值,默认等于`0`;`C`之前有一个字符串成员,必须有初始值,不赋值就报错了。
|
||||
上面示例中,`A`之前没有其他成员,所以可以不设置初始值,默认等于`0`;`C`之前有一个字符串成员,必须`C`必须有初始值,不赋值就报错了。
|
||||
|
||||
Enum 成员可以是字符串和数值混合赋值。
|
||||
|
||||
@@ -515,9 +515,9 @@ type Foo = keyof typeof MyEnum;
|
||||
注意,这里的`typeof`是必需的,否则`keyof MyEnum`相当于`keyof number`。
|
||||
|
||||
```typescript
|
||||
type Foo = keyof MyEnum;
|
||||
// "toString" | "toFixed" | "toExponential" |
|
||||
// "toPrecision" | "valueOf" | "toLocaleString"
|
||||
type Foo = keyof MyEnum;
|
||||
```
|
||||
|
||||
上面示例中,类型`Foo`等于类型`number`的所有原生属性名组成的联合类型。
|
||||
@@ -553,7 +553,7 @@ enum Weekdays {
|
||||
Sunday
|
||||
}
|
||||
|
||||
console.log(Weekdays[3]) // Wednesday
|
||||
console.log(Weekdays[3]) // Wednesday
|
||||
```
|
||||
|
||||
上面示例中,Enum 成员`Wednesday`的值等于3,从而可以从成员值`3`取到对应的成员名`Wednesday`,这就叫反向映射。
|
||||
@@ -603,3 +603,4 @@ var MyEnum;
|
||||
MyEnum["B"] = "b";
|
||||
})(MyEnum || (MyEnum = {}));
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user