Gowhich

Durban's Blog

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.9

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

比较两个函数

相对来讲,在比较原始类型和对象类型的时候是比较容易理解的,问题是如何判断两个函数是兼容的。 下面我们从两个简单的函数入手,它们仅是参数列表略有不同:

1
2
3
4
5
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // Error

要查看x是否能赋值给y,首先看它们的参数列表。 x的每个参数必须能在y里找到对应类型的参数。 注意的是参数的名字相同与否无所谓,只看它们的类型。 这里,x的每个参数在y中都能找到对应的参数,所以允许赋值。

第二个赋值错误,因为y有个必需的第二个参数,但是x并没有,所以不允许赋值。你可能会疑惑为什么允许忽略参数,像例子y = x中那样。 原因是忽略额外的参数在JavaScript里是很常见的。 例如, Array#forEach给回调函数传3个参数:数组元素,索引和整个数组。 尽管如此,传入一个只使用第一个参数的回调函数也是很有用的:

1
2
3
4
5
6
7
let items = [1, 2, 3];

// Don't force these extra arguments
items.forEach((item, index, array) => console.log(item));

// Should be OK!
items.forEach((item) => console.log(item));

下面来看看如何处理返回值类型,创建两个仅是返回值类型不同的函数:

1
2
3
4
5
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});

x = y; // OK
y = x; // Error 因为x()缺少一个location属性

类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。

函数参数双向协变

当比较函数参数类型时,只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。 这是不稳定的,因为调用者可能传入了一个具有更精确类型信息的函数,但是调用这个传入的函数的时候却使用了不是那么精确的类型信息。 实际上,这极少会发生错误,并且能够实现很多JavaScript里的常见模式。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
enum EventType {
Mouse,
Keyboard,
};

interface Event {
timestamp: number;
}

interface MouseEvent extends Event {
eventX: number;
eventY: number;
}

interface KeyEvent extends Event {
keyCode: number;
}

function listenEvent(eventType: EventType, handler: (n:Event) => void) {
// other
}

listenEvent(EventType.Mouse, (e: MouseEvent)=>console.log(e.eventX+', '+e.eventY));

// 报类型异常
listenEvent(EventType.Mouse, (e: Event) => console.log(<MouseEvent>e.eventX + ', ' + <MouseEvent>e.eventY));

listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.eventX + ', ' + e.eventY)))

// 报类型异常
listenEvent(EventType.Mouse, (e: number) => console.log(e))

运行后,详细的报错信息类似如下

1
2
3
4
5
6
7
$ npx ts-node src/type_compatibility_1.ts
⨯ Unable to compile TypeScript:
src/type_compatibility_1.ts(40,70): error TS2339: Property 'eventX' does not exist on type 'Event'.
src/type_compatibility_1.ts(40,100): error TS2339: Property 'eventY' does not exist on type 'Event'.
src/type_compatibility_1.ts(44,30): error TS2345: Argument of type '(e: number) => void' is not assignable to parameter of type '(n: Event) => void'.
Types of parameters 'e' and 'n' are incompatible.
Type 'Event' is not assignable to type 'number'.

可选参数及剩余参数

比较函数兼容性的时候,可选参数与必须参数是可互换的。 源类型上有额外的可选参数不是错误,目标类型的可选参数在源类型里没有对应的参数也不是错误。

当一个函数有剩余参数时,它被当做无限个可选参数。

这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些undefinded。有一个好的例子,常见的函数接收一个回调函数并用对于程序员来说是可预知的参数但对类型系统来说是不确定的参数来调用,如下:

1
2
3
4
5
6
7
function invokeLater(args: any[], callback: (...args: any[]) => void) {
// other
}

invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));

invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));

函数重载

对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.4.0

介绍

TypeScript中的类型兼容性基于结构子类型。
结构类型是一种仅根据其成员关联类型的方式。它正好与名义(nominal)类型形成对比。请看如下代码:

1
2
3
4
5
6
7
8
9
10
11
interface Named {
name: string;
}

class Person {
name: string;
}

let p: Named;
// OK, because of structural typing
p = new Person();

在使用基于名义类型的语言,比如C#或Java中,这段代码会报错,因为Person类没有明确说明其实现了Named接口。

TypeScript的结构性子类型是根据JavaScript代码的典型写法来设计的。 因为JavaScript里广泛地使用匿名对象,例如函数表达式和对象字面量,所以使用结构类型系统来描述这些类型比使用名义类型系统更好。

关于可靠性的注意事项

TypeScript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时,被当做是“不可靠”的。TypeScript允许这种不可靠行为的发生是经过仔细考虑的。通过这篇文章,我们会解释什么时候会发生这种情况和其有利的一面。

开始

TypeScript结构化类型系统的基本规则是,如果x要兼容y,那么y至少具有与x相同的属性。比如:

1
2
3
4
5
6
7
8
interface Named {
name: string;
}

let x: Named;
// y的推断类型 is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y;

这里要检查y是否能赋值给x,编译器检查x中的每个属性,看是否能在y中也找到对应属性。 在这个例子中, y必须包含名字是name的string类型成员。y满足条件,因此赋值正确。

检查函数参数时使用相同的规则:

1
2
3
4
function greet(n: Named) {
alert('Hello, ' + n.name);
}
greet(y); // OK

注意,y有个额外的location属性,但这不会引发错误。 只有目标类型(这里是 Named)的成员会被一一检查是否兼容。

这个比较过程是递归进行的,检查每个成员及子成员。

介绍

本次分享介绍TypeScript里的类型推论。即,类型是在哪里如何被推断的。

基础

TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型。如下面的例子

1
let x = 3;

变量x的类型被推断为数字。 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时。

大多数情况下,类型推论是直截了当地。 后面的小节,我们会浏览类型推论时的细微差别。

最佳通用类型

当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。例如,

1
let x = [0, 1, null];

为了推断x的类型,我们必须考虑所有元素的类型。 这里有两种选择: number和null。 计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。

由于最终的通用类型取自候选类型,有些时候候选类型共享相同的通用类型,但是却没有一个类型能做为所有候选类型的类型。例如:

1
let zoo = [new Rhino(), new Elephant(), new Snake()];

这里,我们想让zoo被推断为Animal[]类型,但是这个数组里没有对象是Animal类型的,因此不能推断出这个结果。 为了更正,当候选类型不能使用的时候我们需要明确的指出类型:

1
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

如果没有找到最佳通用类型的话,类型推断的结果为联合数组类型,(Rhino | Elephant | Snake)[]。

上下文类型

TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。按上下文归类会发生在表达式的类型与所处的位置相关时。比如:

1
2
3
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); //<- Error
};

这个例子会得到一个类型错误,TypeScript类型检查器使用Window.onmousedown函数的类型来推断右边函数表达式的类型。 因此,就能推断出 mouseEvent参数的类型了。 如果函数表达式不是在上下文类型的位置, mouseEvent参数的类型需要指定为any,这样也不会报错了。

如果上下文类型表达式包含了明确的类型信息,上下文的类型被忽略。 重写上面的例子:

1
2
3
window.onmousedown = function(mouseEvent: any) {
console.log(mouseEvent.button); //<- Now, no error is given
};

这个函数表达式有明确的参数类型注解,上下文类型被忽略。 这样的话就不报错了,因为这里不会使用到上下文类型。

上下文归类会在很多情况下使用到。 通常包含函数的参数,赋值表达式的右边,类型断言,对象成员和数组字面量和返回值语句。 上下文类型也会做为最佳通用类型的候选类型。比如:

1
2
3
function createZoo(): Animal[] {
return [new Rhino(), new Elephant(), new Snake()];
}

这个例子里,最佳通用类型有4个候选者:Animal,Rhino,Elephant和Snake。 当然, Animal会被做为最佳通用类型。

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.8

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

枚举

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。

运行时的枚举

枚举是在运行时真正存在的对象。 例如下面的枚举:

1
2
3
enum E {
X, Y, Z
}

实际上可以传递给函数

1
2
3
4
5
6
7
8
enum E {
X,Y,Z
}
function f(obj: { X: number }) {
return obj.X
}

console.log(f(E));

反向映射

除了创建一个以属性名做为对象成员的对象之外,数字枚举成员还具有了 反向映射,从枚举值到枚举名字。 例如,在下面的例子中:

1
2
3
4
5
6
7
enum Enum {
A,
}

let a = Enum.A;
let nameOfA = Enum[a];
console.log(nameOfA);

TypeScript可能会将这段代码编译为下面的JavaScript:

1
2
3
4
5
6
7
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a];
console.log(nameOfA);

生成的代码中,枚举类型被编译成一个对象,它包含了正向映射( name -> value)和反向映射( value -> name)。 引用枚举成员总会生成为对属性访问并且永远也不会内联代码。

要注意的是 不会为字符串枚举成员生成反向映射。

const枚举

大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义。

1
2
3
4
const enum Enum {
A = 1,
B = A * 2,
}

常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为,常量枚举不允许包含计算成员。

1
2
3
4
5
6
7
8
const enum Directions {
Up,
Down,
Left,
Right,
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

生成后的代码为:

1
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

外部枚举

外部枚举用来描述已经存在的枚举类型的形状。

1
2
3
4
5
declare enum Enum {
A = 1,
B,
C = 2
}

外部枚举和非外部枚举之间有一个重要的区别,在正常的枚举里,没有初始化方法的成员被当成常数成员。 对于非常数的外部枚举而言,没有初始化方法时被当做需要经过计算的。
本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.9

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.7

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

枚举

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。

联合枚举与枚举成员的类型

存在一种特殊的非计算的常量枚举成员的子集:字面量枚举成员。 字面量枚举成员是指不带有初始值的常量枚举成员,或者是值被初始化为

  • 任何字符串字面量(例如: “foo”, “bar”, “baz”)
  • 任何数字字面量(例如: 1, 100)
  • 应用了一元 -符号的数字字面量(例如: -1, -100)

当所有枚举成员都拥有字面量枚举值时,它就带有了一种特殊的语义。

首先,枚举成员成为了类型! 例如,我们可以说某些成员 只能是枚举成员的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum ShapeKind {
Circle,
Square,
}

interface Circle {
kind: ShapeKind.Circle,
radius: number,
}

interface Square {
kind: ShapeKind.Square,
sideLength: number,
}

let c: Circle = {
kind: ShapeKind.Square,
redius: 100,
}

运行后会有如下错误提示

1
2
3
$ npx ts-node src/generics_8.ts
⨯ Unable to compile TypeScript:
src/generics_8.ts(17,5): error TS2322: Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.

另一个变化是枚举类型本身变成了每个枚举成员的 联合。 虽然我们还没有讨论[联合类型](./Advanced Types.md#union-types),但你只要知道通过联合枚举,类型系统能够利用这样一个事实,它可以知道枚举里的值的集合。 因此,TypeScript能够捕获在比较值的时候犯的愚蠢的错误。 例如:

1
2
3
4
5
6
7
8
9
10
enum E {
Foo,
Bar,
}

function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {

}
}

运行后会有如下错误提示

1
2
3
$ npx ts-node src/generics_8.ts
⨯ Unable to compile TypeScript:
src/generics_8.ts(27,23): error TS2367: This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.

这个例子里,我们先检查 x是否不是 E.Foo。 如果通过了这个检查,然后 ||会发生短路效果, if语句体里的内容会被执行。 然而,这个检查没有通过,那么 x则 只能为 E.Foo,因此没理由再去检查它是否为 E.Bar。

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.8

TypeScript 3.0 新功能介绍(二)

New unknown top type

TypeScript 3.0引入了一种新的顶级类型unknown。
unknown是任何类型安全的对应物。
任何东西都可以分配给unknown,但是unknown的东西除了本身以及任何没有类型断言或基于控制流的缩小之外的任何东西都不能分配。
同样,如果没有先断言或缩小到更具体的类型,则不允许对unknown操作进行操作。具体看下官方给的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// In an intersection everything absorbs unknown

type T00 = unknown & null; // null
type T01 = unknown & undefined; // undefined
type T02 = unknown & null & undefined; // null & undefined (which becomes never)
type T03 = unknown & string; // string
type T04 = unknown & string[]; // string[]
type T05 = unknown & unknown; // unknown
type T06 = unknown & any; // any

// In a union an unknown absorbs everything

type T10 = unknown | null; // unknown
type T11 = unknown | undefined; // unknown
type T12 = unknown | null | undefined; // unknown
type T13 = unknown | string; // unknown
type T14 = unknown | string[]; // unknown
type T15 = unknown | unknown; // unknown
type T16 = unknown | any; // any

// Type variable and unknown in union and intersection

type T20<T> = T & {}; // T & {}
type T21<T> = T | {}; // T | {}
type T22<T> = T & unknown; // T
type T23<T> = T | unknown; // unknown

// unknown in conditional types

type T30<T> = unknown extends T ? true : false; // Deferred
type T31<T> = T extends unknown ? true : false; // Deferred (so it distributes)
type T32<T> = never extends T ? true : false; // true
type T33<T> = T extends never ? true : false; // Deferred

// keyof unknown

type T40 = keyof any; // string | number | symbol
type T41 = keyof unknown; // never

// Only equality operators are allowed with unknown

function f10(x: unknown) {
x == 5;
x !== 10;
x >= 0; // Error
x + 1; // Error
x * 2; // Error
-x; // Error
+x; // Error
}

// No property accesses, element accesses, or function calls

function f11(x: unknown) {
x.foo; // Error
x[5]; // Error
x(); // Error
new x(); // Error
}

// typeof, instanceof, and user defined type predicates

declare function isFunction(x: unknown): x is Function;

function f20(x: unknown) {
if (typeof x === "string" || typeof x === "number") {
x; // string | number
}
if (x instanceof Error) {
x; // Error
}
if (isFunction(x)) {
x; // Function
}
}

// Homomorphic mapped type over unknown

type T50<T> = { [P in keyof T]: number };
type T51 = T50<any>; // { [x: string]: number }
type T52 = T50<unknown>; // {}

// Anything is assignable to unknown

function f21<T>(pAny: any, pNever: never, pT: T) {
let x: unknown;
x = 123;
x = "hello";
x = [1, 2, 3];
x = new Error();
x = x;
x = pAny;
x = pNever;
x = pT;
}

// unknown assignable only to itself and any

function f22(x: unknown) {
let v1: any = x;
let v2: unknown = x;
let v3: object = x; // Error
let v4: string = x; // Error
let v5: string[] = x; // Error
let v6: {} = x; // Error
let v7: {} | null | undefined = x; // Error
}

// Type parameter 'T extends unknown' not related to object

function f23<T extends unknown>(x: T) {
let y: object = x; // Error
}

// Anything but primitive assignable to { [x: string]: unknown }

function f24(x: { [x: string]: unknown }) {
x = {};
x = { a: 5 };
x = [1, 2, 3];
x = 123; // Error
}

// Locals of type unknown always considered initialized

function f25() {
let x: unknown;
let y = x;
}

// Spread of unknown causes result to be unknown

function f26(x: {}, y: unknown, z: any) {
let o1 = { a: 42, ...x }; // { a: number }
let o2 = { a: 42, ...x, ...y }; // unknown
let o3 = { a: 42, ...x, ...y, ...z }; // any
}

// Functions with unknown return type don't need return expressions

function f27(): unknown {
}

// Rest type cannot be created from unknown

function f28(x: unknown) {
let { ...a } = x; // Error
}

// Class properties of type unknown don't need definite assignment

class C1 {
a: string; // Error
b: unknown;
c: any;
}

在JSX中支持 defaultProps

TypeScript 2.9及更早版本没有利用JSX组件中的React defaultProps声明。
用户通常必须声明属性可选并在render中使用非null断言,或者在导出之前使用type-assertions来修复组件的类型。
TypeScript 3.0添加支持JSX名称空间中名为LibraryManagedAttributes的新类型别名。
在用于检查以其为目标的JSX表达式之前,此助手类型定义组件的Props类型的转换;
从而允许自定义,例如:如何处理提供的道具和推断道具之间的冲突,如何映射推理,如何处理选项,以及如何组合来自不同地方的推理。
简而言之,我们可以使用这种通用类型来模拟React的特定行为,例如defaultProps,以及某种程度上propTypes。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export interface Props {
name: string;
}

export class Greet extends React.Component<Props> {
render() {
const { name } = this.props;
return <div>Hello ${name.toUpperCase()}!</div>;
}
static defaultProps = { name: "world"};
}

// Type-checks! No type assertions needed!
let el = <Greet />
1
**注意事项**

defaultProps上的显式类型

默认ed属性是从defaultProps属性类型推断出来的。
如果添加了显式类型注释,例如
static defaultProps:Partial <Props>;
编译器将无法识别哪些属性具有默认值(因为defaultProps的类型包括Props的所有属性)。
使用static defaultProps:Pick <Props,"name">;
相反,作为显式类型注释,或者不像上面的示例中那样添加类型注释。
对于无状态功能组件(SFC),请使用ES2015 SFC的默认初始化程序:

1
2
3
function Greet({ name = "world" }: Props) {
return <div>Hello ${name.toUpperCase()}!</div>;
}

对@types/React的更改

仍然需要在@types/React中将LibraryManagedAttributes定义添加到JSX名称空间的相应更改。
请记住,有一些限制。

/// 引用指令

TypeScript添加了一个新的三斜杠引用指令(/// <reference lib="..." />),允许文件显式包含现有的内置lib文件。
内置的lib文件以与tsconfig.json中的”lib”编译器选项相同的方式引用(例如,使用lib =”es2015”而不是lib =”lib.es2015.d.ts”等)。
对于在内置类型上进行中继的声明文件作者,例如
建议使用DOM API或内置的JS运行时构造函数(如Symbol或Iterable,三斜杠引用lib指令)。
以前这些.d.ts文件必须添加此类型的前向/重复声明。

/// <reference lib="es2017.string" />用于编译中的一个文件相当于使用–lib es2017.string进行编译。

1
2
3
/// <reference lib="es2017.string" />

"foo".padStart(4);

介绍就这些,如果有不对或者想看具体详情请到官网查看 http://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.6

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

枚举

枚举

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。

异构枚举(Heterogeneous enums)

从技术的角度来说,枚举可以混合字符串和数字成员,但是似乎你并不会这么做:

1
2
3
4
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}

除非你真的想要利用JavaScript运行时的行为,否则我们不建议这样做。

计算的和常量成员

每个枚举成员都带有一个值,它可以是 常量或 计算出来的。 当满足如下条件时,枚举成员被当作是常量:

它是枚举的第一个成员且没有初始化器,这种情况下它被赋予值 0:

1
2
// E.X is constant:
enum E { X }

它不带有初始化器且它之前的枚举成员是一个 数字常量。 这种情况下,当前枚举成员的值为它上一个枚举成员的值加1。

1
2
3
4
5
enum E1 { X, Y, Z }

enum E2 {
A = 1, B, C
}

枚举成员使用 常量枚举表达式初始化。 常数枚举表达式是TypeScript表达式的子集,它可以在编译阶段求值。 当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:

  • 一个枚举表达式字面量(主要是字符串字面量或数字字面量)
  • 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
  • 带括号的常量枚举表达式
  • 一元运算符 +, -, ~其中之一应用在了常量枚举表达式
  • 常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaN或 Infinity,则会在编译阶段报错。

所有其它情况的枚举成员被当作是需要计算得出的值。

1
2
3
4
5
6
7
8
9
enum FileAccess {
// 常量
None,
Read= 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// 计算出来的
G = "123".length,
}

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.7

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.5

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

枚举

枚举

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。

数字枚举

首先我们看看数字枚举,如果你使用过其它编程语言应该会很熟悉。

1
2
3
4
5
6
enum Derection {
Up = 1,
Down,
Left,
Right
}

如上,我们定义了一个数字枚举, Up使用初始化为 1。 其余的成员会从 1开始自动增长。 换句话说, Direction.Up的值为 1, Down为 2, Left为 3, Right为 4。

我们还可以完全不使用初始化器,如下

1
2
3
4
5
6
enum Derection {
Up,
Down,
Left,
Right
}

现在, Up的值为 0, Down的值为 1等等。 当我们不在乎成员的值的时候,这种自增长的行为是很有用处的,但是要注意每个枚举成员的值都是不同的。
使用枚举很简单:通过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型,如下示例

1
2
3
4
5
6
7
8
9
10
enum ResponseOther {
No = 0,
Yes = 1,
}

function respond(re: string, me: ResponseOther) {
// other doing
}

respond("message", ResponseOther.No)

数字枚举可以被混入到 计算过的和常量成员(如下所示)。 简短地说,不带初始化器的枚举或者被放在第一的位置,或者被放在使用了数字常量或其它常量初始化了的枚举后面。 换句话说,下面的情况是不被允许的:

1
2
3
4
enum E {
A = getSomeValue(),
B, // error! 'A' is not constant-initialized, so 'B' needs an initializer
}

字符串枚举

字符串枚举的概念很简单,但是有细微的 运行时的差别。 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

1
2
3
4
5
6
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}

由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 - 它并不能表达有用的信息(尽管 反向映射会有所帮助),字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.3.6

TypeScript 3.0 新功能介绍(一)

项目引用

TypeScript 3.0引入了项目引用(project references)的新概念。
项目引用允许TypeScript项目依赖于其他TypeScript项目 - 特别是允许tsconfig.json文件引用其他tsconfig.json文件。
指定这些依赖项可以更容易地将代码拆分为更小的项目,因为它为TypeScript(及其周围的工具)提供了一种理解构建顺序和输出结构的方法。
TypeScript 3.0还引入了一种新的tsc模式,即–build标志,它与项目引用一起工作,以实现更快的TypeScript构建。
有关更多文档,请参阅项目参考手册页面。我这里只简单介绍下官方介绍的一小部分新功能

剩余参数和传播表达式中的元组

TypeScript 3.0增加了对多个新功能的支持,以与函数参数列表作为元组类型进行交互。
TypeScript 3.0增加了如下支持:

  • Expansion of rest parameters with tuple types into discrete parameters.
  • Expansion of spread expressions with tuple types into discrete arguments.
  • Generic rest parameters and corresponding inference of tuple types.
  • Optional elements in tuple types.
  • Rest elements in tuple types.

通过这些功能,可以强大地键入一些转换函数及其参数列表的高阶函数。

使用元组类型的剩余参数

当剩余参数具有元组类型时,元组类型将扩展为一系列离散参数。
例如,以下两个声明是等效的:

1
2
declare function foo(...args: [number, boolean, string]): void;
declare function foo(args_0: number, args_1: boolean, args_2: string): void;

使用元组类型传播表达式

当函数调用包括元组类型的扩展表达式作为最后一个参数时,扩展表达式对应于元组元素类型的离散参数序列。
因此,以下调用是等效的:

1
2
3
4
const args: [number, boolean, string] = [42, true, "hello"];
foo(42, true, "hello");
foo(args[0], args[1], args[2])
foo(...args)

通用剩余参数

允许剩余参数具有约束为数组类型的泛型类型,并且类型推断可以推断这些通用剩余参数的元组类型。
这使得部分参数列表的高阶捕获和传播成为可能,如下实例

1
2
3
4
5
6
7
8
9
10
11
12
13
declare function bind<T, U extends any[], V>(
f: (x: T, ...args: U) => V, x: T): (...args: U) => V;

declare function f3(x: number, y: string, z: boolean): void;

const f2 = bind(f3, 42); // (y: string, z: boolean) => void
const f1 = bind(f2, "hello"); // (z: boolean) => void
const f0 = bind(f1, true); // () => void

f3(42, "hello", true);
f2("hello", true);
f1(true);
f0();

在上面的f2声明中,类型推断分别为T,U和V推断类型数,[string,boolean]和void。
请注意,当从一系列参数推断出元组类型并随后扩展为参数列表时(如U的情况),原始参数名称将用于扩展(但是,名称没有语义含义,否则不会
观察到的)。

元组类型中的可选元素

元组类型现在允许?后缀
元素类型的后缀表示该元素是可选的,如下实例

1
2
3
4
let t: [number, string?, boolean?];
t = [42, "hello", true];
t = [42, "hello"];
t = [42];

--strictNullChecks模式下,一个?修饰符在元素类型中自动包含undefined,类似于可选参数。
如果元素具有?后缀,则元组类型允许省略元素,它的类型上的修饰符和它右边的所有元素也有?修饰符。
当为元组类型数据被推断为剩余参数时,源中的可选参数将成为推断类型中的可选元组元素。

带有可选元素的元组类型的length属性表示可能长度的字面类型的并集。
例如,元组类型[number,string?,boolean?]中的length属性的类型是1|2|3。

在元组类型中剩余元素

元组类型的最后一个元素可以是形式为…X的剩余元素,其中X是数组类型。
剩余元素表示元组类型是开放式的,并且可能具有零个或多个数组元素类型的附加元素。
例如,[number, …string[]]表示带有数字元素后跟任意数量的字符串元素的元组。如下实例

1
2
3
4
5
6
7
function tuple<T extends any[]>(...args: T): T {
return args;
}

const numbers: number[] = getArrayOfNumbers();
const t1 = tuple("foo", 1, true); // [string, number, boolean]
const t2 = tuple("bar", ...numbers); // [string, ...number[]]

具有剩余元素的元组类型的length属性的类型是number。

其余特性请留意后续分享。

在Web开发中,无论是PHP的框架还是Python的框架,都会遇到使用模板的时候
在使用模板的时候就会遇到一个问题,就是使用模板编写的代码通过查看源代码的时候,会发现代码混乱不堪,对于代码格式又嫉妒追求的我来说我因受不了,但是目前也没有找到什么好的格式化输出的办法
但是格式化输出的话,也会需要处理一个压缩的问题,最终还是选择一个方案,开发的时候为了查看代码修改代码,就不做处理,但是上线的时候还是要做下压缩的处理,就是将无用的空格或者换行之类的全部删除掉。

问题前提已经抛出,现在看看如何解决这个问题,为了防止重复早轮子网上也查了一遍,结果也找到了,但是用composer安装的时候又是各种的不兼容,于是看了下源代码,其实很简单。这里我就简答的说下如何使用
具体的逻辑我就不多说了,其实自己理解了下面的使用流程,自己改写也不是太难的事情

第一步 功能开发

创建两个文件一个是components/HtmlMinify.php,代码逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php

namespace app\components;

use app\helpers\HtmlMinifyHelper;
use Yii;
use yii\base\Component;
use yii\base\Event;
use yii\web\Response;
use yii\web\View;

class HtmlMinify extends Component
{
/**
* Minify html. Process before response send
* @var bool
*/
public $html = false;
/**
* Minify css on page, added by registerCss. Process before render page in view component
* @var bool
*/
public $css = false;
/**
* Minify css on page. Process before render page in view component
* @var bool
*/
public $js = false;
/**
* Response formats list, where enable minify html
* @var array
*/
public $formats = [
Response::FORMAT_HTML,
];

public function init()
{
/** @var $this View */
Yii::$app->view->on(View::EVENT_END_PAGE, [$this, 'onEventEndPage']);
Yii::$app->response->on(Response::EVENT_BEFORE_SEND, [$this, 'onEventBeforeSend']);
}

public function onEventEndPage(Event $event)
{
$view = $event->sender;
if ($this->css && !empty($view->css)) {
foreach ($view->css as &$css) {
$css = HtmlMinifyHelper::css($css);
}
}
if ($this->js && !empty($view->js)) {
foreach ($view->js as &$list) {
foreach ($list as &$js) {
$js = HtmlMinifyHelper::js($js);
}
}
}
}

public function onEventBeforeSend(Event $event)
{
$response = $event->sender;
if ($this->html & in_array($response->format, $this->formats)) {
if (!empty($response->data)) {
$response->data = HtmlMinifyHelper::html($response->data);
}
if (!empty($response->content)) {
$response->content = HtmlMinifyHelper::html($response->content);
}
}
}
}

另外一个文件上是helpers/HtmlMinifyHelper.php,代码逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<?php
namespace app\helpers;

class HtmlMinifyHelper
{
public static function html($input)
{
if (trim($input) === "") {
return $input;
}
// Remove extra white-space(s) between HTML attribute(s)
$input = preg_replace_callback('#<([^\/\s<>!]+)(?:\s+([^<>]*?)\s*|\s*)(\/?)>#s', function ($matches) {
return '<' . $matches[1] . preg_replace('#([^\s=]+)(\=([\'"]?)(.*?)\3)?(\s+|$)#s', ' $1$2', $matches[2]) . $matches[3] . '>';
}, str_replace("\r", "", $input));
// Minify inline CSS declaration(s)
if (strpos($input, ' style=') !==false){ $input=preg_replace_callback('#<([^<]+?)\s+style=([\'"])(.*?)\2(?=[\/\s>])#s',function ($matches){ return '<' . $matches[1] . ' style=' . $matches[2] . self::css($matches[3]) . $matches[2];
}, $input);
}
return preg_replace(
[
// t = text
// o = tag open
// c = tag close
// Keep important white-space(s) after self-closing HTML tag(s)
'#<(img|input)(>| .*?>)#s',
// Remove a line break and two or more white-space(s) between tag(s)
'#(<!--.*?-->)|(>)(?:\n*|\s{2,})(<)|^\s*|\s*$#s',
'#(<!--.*?-->)|(?<!\>)\s+(<\/.*?>)|(<[^\/]*?>)\s+(?!\<)#s', // t+c || o+t
'#(<!--.*?-->)|(<[^\/]*?>)\s+(<[^\/]*?>)|(<\/.*?>)\s+(<\/.*?>)#s', // o+o || c+c
'#(<!--.*?-->)|(<\/.*?>)\s+(\s)(?!\<)|(?<!\>)\s+(\s)(<[^\/]*?\/?>)|(<[^\/]*?\/?>)\s+(\s)(?!\<)#s', // c+t || t+o || o+t -- separated by long white-space(s)
'#(<!--.*?-->)|(<[^\/]*?>)\s+(<\/.*?>)#s', // empty tag
'#<(img|input)(>| .*?>)<\/\1>#s', // reset previous fix
'#(&nbsp;)&nbsp;(?![<\s])#', // clean up ...
'#(?<=\>)(&nbsp;)(?=\<)#', // --ibid
// Remove HTML comment(s) except IE comment(s)
'#\s*<!--(?!\[if\s).*?-->\s*|(?<!\>)\n+(?=\<[^!])#s',
],
[
'<$1$2</$1>',
'$1$2$3',
'$1$2$3',
'$1$2$3$4$5',
'$1$2$3$4$5$6$7',
'$1$2$3',
'<$1$2',
'$1 ',
'$1',
"",
],
$input);
}

public static function css($input)
{
if (trim($input) === "") {
return $input;
}
return preg_replace(
[
// Remove comment(s)
'#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')|\/\*(?!\!)(?>.*?\*\/)|^\s*|\s*$#s',
// Remove unused white-space(s)
'#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\'|\/\*(?>.*?\*\/))|\s*+;\s*+(})\s*+|\s*+([*$~^|]?+=|[{};,>~+]|\s*+-(?![0-9\.])|!important\b)\s*+|([[(:])\s++|\s++([])])|\s++(:)\s*+(?!(?>[^{}"\']++|"(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')*+{)|^\s++|\s++\z|(\s)\s+#si',
// Replace `0(cm|em|ex|in|mm|pc|pt|px|vh|vw|%)` with `0`
'#(?<=[\s:])(0)(cm|em|ex|in|mm|pc|pt|px|vh|vw|%)#si',
// Replace `:0 0 0 0` with `:0`
'#:(0\s+0|0\s+0\s+0\s+0)(?=[;\}]|\!important)#i',
// Replace `background-position:0` with `background-position:0 0`
'#(background-position):0(?=[;\}])#si',
// Replace `0.6` with `.6`, but only when preceded by `:`, `,`, `-` or a white-space
'#(?<=[\s:,\-])0+\.(\d+)#s',
// Minify string value
'#(\/\*(?>.*?\*\/))|(?<!content\:)([\'"])([a-z_][a-z0-9\-_]*?)\2(?=[\s\{\}\];,])#si',
'#(\/\*(?>.*?\*\/))|(\burl\()([\'"])([^\s]+?)\3(\))#si',
// Minify HEX color code
'#(?<=[\s:,\-]\#)([a-f0-6]+)\1([a-f0-6]+)\2([a-f0-6]+)\3#i',
// Replace `(border|outline):none` with `(border|outline):0`
'#(?<=[\{;])(border|outline):none(?=[;\}\!])#',
// Remove empty selector(s)
'#(\/\*(?>.*?\*\/))|(^|[\{\}])(?:[^\s\{\}]+)\{\}#s',
],
[
'$1',
'$1$2$3$4$5$6$7',
'$1',
':0',
'$1:0 0',
'.$1',
'$1$3',
'$1$2$4$5',
'$1$2$3',
'$1:0',
'$1$2',
],
$input);
}

public static function js($input)
{
if (trim($input) === "") {
return $input;
}
return preg_replace(
[
// Remove comment(s)
'#\s*("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')\s*|\s*\/\*(?!\!|@cc_on)(?>[\s\S]*?\*\/)\s*|\s*(?<![\:\=])\/\/.*(?=[\n\r]|$)|^\s*|\s*$#',
// Remove white-space(s) outside the string and regex
'#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\'|\/\*(?>.*?\*\/)|\/(?!\/)[^\n\r]*?\/(?=[\s.,;]|[gimuy]|$))|\s*([!%&*\(\)\-=+\[\]\{\}|;:,.<>?\/])\s*#s',
// Remove the last semicolon
'#;+\}#',
// Minify object attribute(s) except JSON attribute(s). From `{'foo':'bar'}` to `{foo:'bar'}`
'#([\{,])([\'])(\d+|[a-z_][a-z0-9_]*)\2(?=\:)#i',
// --ibid. From `foo['bar']` to `foo.bar`
'#([a-z0-9_\)\]])\[([\'"])([a-z_][a-z0-9_]*)\2\]#i',
],
[
'$1',
'$1$2',
'}',
'$1$3',
'$1.$3',
],
$input);
}
}

第二步 功能配置

修改配置文件文件,这里修改config/web.php
components中加入如下代码

1
2
3
4
'htmlMinify' => [
'class' => 'app\components\HtmlMinify',
'html' => !YII_ENV_DEV, // 这里只开启了html的
],

在bootstrap中加入如下代码

1
'bootstrap' => ['log', 'htmlMinify'], // log是默认加的, htmlMinify是我们自己加的

到这里就结束了配置可以试着在生产环境试下

如有问题请加群沟通交流或下面留言

0%