多译 · 更新日志页面开发(三) 初识TypeScript

前几天看完React的视频,我觉得我懂了,信心满满的要去进行开发了。但是当我把项目clone下来以后,发现色老师的项目我根本难以读懂。

或者说,代码的大多数语法我都不能够理解。

因为色老师的项目使用的是TypeScript 和 TSX。

无奈,我只能继续学习新的知识,看来距离能够上手开发还是有一段距离,或许还是很遥远的距离

好在,至少我从高一就开始学C++。学习一门新的语言对于我来说应该难度不算很大。。。

1、为什么要有TypeScript

还是同样的学习哲学,我们首先要思考ts存在的意义。

顾名思义,有类型的script,正如文档首页的自我描述:

TypeScript 是 JavaScript 类型的超集,它可以编译成纯JavaScript

我们可以将它理解为有类型的js。

如果说弱类型带来的灵活性是js的优点,那么弱类型带来的潜在的不可控则是js的缺点

ts就是为了解决这一问题,通过对js的变量进行类型限制,取舍一定的灵活性,换来更好的稳定性,至少是可控性。

2、基础类型

  • boolean
  • number 所有数字都是浮点数
  • string
  • array number[] or Array<number>
  • Tuple
  • enum
  • any 不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。
  • void 没有类型
  • null
  • undefined
  • never 表示的是那些永不存在的值的类型.比如那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;
  • Object 非原始类型

类型断言

类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。

  • 尖括号语法
    1
    2
    let someValue: any = "this is a string";
    let strLength: number = (<string>someValue).length;
  • as 语法
    1
    2
    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;

    在TypeScript里使用JSX时,只有 as语法断言是被允许的。

3、变量声明

文档在这一部分中重申了let、const以及var的区别,其中一个例子值得注意:

1
2
3
4
5
6
7
8
9
10
11
12
13
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
for (var i = 0; i < 10; i++) {
// capture the current state of 'i'
// by invoking a function with its current value
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
for (let i = 0; i < 10 ; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}

第一个输出全是10,后面两组输出是正常的0-9
具有一定js基础的话,对于前两组的结果是很容易理解的。而对于第三组,文档中的解释让我对于作用域有了进一步理解

直观地讲,每次进入一个作用域时,它创建了一个变量的 环境。 就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。

当let声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对 每次迭代都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事,所以在 setTimeout例子里我们仅使用let声明就可以了。

本章节文档的第二部分主要讲了解构与展开,这部分和es6的标准是一样的。

4、接口

ts的核心原则之一就是对值所具有的结构进行类型检查。ts的接口就是为了让我们能够对js中灵活的变量定义类型。

4.1 冒号定义类型

1
2
3
4
5
6
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

如上图,函数 printLabel 的参数 labelledObj 后面用 :{label: string} 表示其类型是一个具有 label 属性的对象。
label 后面用 :string 来表示 label 属性是一个 string 类型。

上面的例子可以通过用 interface 关键字来定义接口名称来重写

1
2
3
4
5
6
7
8
9
10
interface LabelledValue {
label: string;
}

function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

这个例子中,我们为 labelledObj 的类型命名为 LabelledValue

4.2 可选属性 option bags

1
2
3
4
interface SquareConfig {
color?: string;
width?: number;
}

4.3 只读属性 readonly

1
2
3
4
interface Point {
readonly x: number;
readonly y: number;
}

ts还具有ReadonlyArray,可以将数组的所有可变方法都屏蔽掉

1
2
3
4
5
6
7
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
a = ro as number[];

4.4 对象字面量的额外属性检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

// 下面的写法则不会报错
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

对比上面的例子和4.1中的例子,可以看到当我们有可选属性的时候,ts回对对象的字面量进行额外的属性检查。

4.5 函数类型

1
2
3
4
5
6
7
8
9
interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}

定义参数列表和返回值类型。参数列表里的每个参数都需要名字和类型。其中函数返回值的类型写在函数定义中 括号的后面
函数的参数名不需要与接口里定义的名字相匹配

4.6 可索引的类型

1
2
3
4
5
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
}

TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用”100”(一个string)去索引,因此两者需要保持一致。

4.7 混合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

上述例子中 Counter 定义了一个函数对象的接口,第一行定义了其作为函数的类型,后面两行定义了其作为对象的类型。

function getCounter():Counter 表示该函数的返回值的类型是 Counter

在函数体内可以看到其按照接口逐步完成了Counter对象的构造

5、类与泛型

读完 TypeScript 的类的文档以后,我惊奇的发现它与Java多么的相近。之前听qc科普js的原型链的时候,我其实是对于js的面向对象的理念不太理解的。但是ts这种类java语法的面向对象,我很容易就能够理解了。

1
2
3
4
5
6
7
8
9
10
11
class Greeter {
private greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

let greeter = new Greeter("world");

上面一个简单的例子。其中关于 private protected public 关键字的用法和Java也是类似的。

并且ts也有 static 关键字, 静态成员这一部分和java也很类似。

ts中的泛型和java也是很类似的,这里着重记录一下ts的泛型类型

1
2
3
4
5
6
7
8
9
interface GenericIdentityFn {
<T>(arg: T): T;
}

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

let myIdentity: GenericIdentityFn = identity;
1
2
3
4
5
6
7
8
9
interface GenericIdentityFn<T> {
(arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity;

下面的这种写法可以让接口中的其他属性也获取到泛型变量T

其实这些用法都可以用java来类比

小结

由于这次看ts的目的主要是为了开发miniproject,没有太多的时间去深入了解ts以及其高级的语言特性。结合色老师的代码,阅读完文档的前几章节的基础部分之后,我觉得我已经能够看懂多译的主页项目了,因此关于ts文档的阅读就先告一段落,以后有机会的时候可以仔细研究一下ts的高级特性。