本文是《约束即类型、TypeScript 编程内参》系列第一篇:约束即类型,主要记述 TypeScript 的基本使用和语法。
PS: 本文语境下的「约束」指的是「类型对值的约束」
TS 大家都听说或者使用过,是 Angular 的官方语言,提供了静态类型检查,是 JavaScript 这门语言的超集,也就是说:
TS = JS + 静态类型检查
TS 今年开始火了,越来越多的 js 项目开始用 ts 来实现,因此有了一句广为流传的名言(捏他)
任何用 js 写的项目终将用 ts 重构
那么,你了解 ts 吗?类型本质上是对变量的约束,理解类型,首先要理解的是变量的值,然后 ......
本文是本系列的第一篇约束即类型,面向的是「有一定 JS 开发经验的学习者」 ,推荐前端/node工程师学习,建议跟随本文的代码边写边看,包教不教会。
通过以下方式初始化一个 ts 项目并编译运行:
$ npm i -g typescript # 安装 ts
$ mkdir my-ts-learn # 搭建 playground
$ cd my-ts-learn # 进入目录
$ tsc --init # 初始化一个 ts 工程
$ echo "console.log('hello, ts');" >> h.ts # 创建代码
$ tsc # 编译
$ node h # 运行
???关于 tsconfig.json 我们会在本系列的其他篇目介绍,敬请期待 对于初学者,暂时先使用默认的配置就好
「any 即无约束」它代表着任意,如下所示:
let a: any = 123;
a = {};
a = () => {};
a = '不会报错';
电视里常说 AnyScrtipt 指的就是无理由的在 TS 里大量使用 any;越是使用 any,则 ts 越像 js,不建议这样做,这样会失去使用 ts 的意义。
???类型本身就是对程序的证明
const num: number = 123;
const str: string = 'eczn';
很多情况下,我们并不需要显式地指明类型是什么,ts 会帮我们自动地进行「类型推断」,比方说下面这样写的话,ts 会自动推断出 val 的类型是 string:
let val = '123';
val = 123; // 不行,会报错
???好的 ts 代码总是这样的:大部分变量的类型是 ts 自动推断出来的,而不是程序员到处给变量加类型(这样就成 java 了)
一般情况下,我们可以利用 interface
和 type
声明来创造对 JS 对象的「约束」。
interface Person1 {
name: string;
}
type Person2 = {
name: string;
}
const p1: Person1 = { name: '001 号居民' };
const p2: Person2 = { name: '002 号居民' };
但是有一点要清楚,TS 的类型学名上叫做 结构化类型 Structral Type
,跟其他语言里的 具名类型 Naming Type
不太一样,譬如说:
interface Person3 = {
name: string;
}
const p1: Person1 = { name: '001 号居民' };
const p3: Person3 = p1;
// 在 naming type 的语言中,这样会报错
// 但在 ts 这种 structral type 的语言下,这样不会报错
简而言之,structral type 进行类型检查的时候比较的是两类型的结构,如果一样说明类型一样;而 naming type 仅是比较类型的标识符是不是一样,不一样则认为类型不合。
所以我更倾向于认为,structral type 的作用,其实对值的一种约束。
???type 和 interface 两者在很多情况下是可以等价互相转换的,但实际上两者是有很大不同的,文章系列后文会描述
函数的类型由这三者描述:i 入参、ii 返回值、iii 上下文:
interface Person { name: string; };
// 需注意,这里的 this 经过 ts 编译后会消失
// 同学们可以自行编译体会个中奥义
function sayName(this: Person, suffix: string) {
return this.name + ' ' + suffix;
}
const 无名先生 = { sayName };
const 阿J = { name: 'j', sayName };
// 报错,无名先生无名,不满足 this 上下文类型约束
无名先生.sayName('先生');
// 这里 阿 J 有 name 属性,满足 this 约束
阿J.sayName('先生');
// 注意,箭头函数在语言定义上是没有上下文的
// 因此下面这个会报错
const test = (this: any, x: string): string => x;
当然,在类里面声明的类型,其 this 已经默认为其本身了:
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
sayName(suffix: string) {
// 这里 ts 能正确识别 this 指的是 Person 类
// 不会报错
return this.name + ' ' + suffix;
}
}
???很多时候我们不必吧函数的上下文也定义出来,一般都是这样定义函数的:con
st func = (a: X): Y = { /* function body */ }
对于值来说,字面量类型是比基本类型更窄的约束,比如:
function sayHello(suffix: '先生') {
return 'hello,' + suffix;
}
sayHello('先生'); // 满足字面量类型,不会报错
sayHello('女士'); // 不满足,类型不合
我们熟知的 number 类型也有对应的字面量类型,读者可以自行写 demo 验证推敲。
ts 拓展了 js 语法里面的 typeof
,使其可以在 ts 进行类型声明的时候获取某个变量的类型:
let num = 123;
type MyNumber = typeof num;
// MyNumber 指的是就是 number 类型
ts 很多情况下是不用声明类型的,ts 会自动推断的,比如下面 UP_POSITION
的类型 ts 会自动推断为类型 string
:
let UP_POSITION = '向上';
但是下面这种情况呢?
const UP_POSITION_2 = '向上';
这种情况下我们得到的类型是一个字面量类型 '向上'
,如下所示
鼠标放在 Test 上可以看到精确定义
造成这种原因的结果是 const 声明的变量不会再变了,因此出来的是字面量类型,而 let 声明的类型是不确定的,因此用的是 string
那如果,我一定要在 let 的情况下推断某变量的类型为字面量呢?可以这样:
let UP_POSITION = '向上' as const;
type Test = typeof UP_POSITION;
// Test 是 '向上'
as const 会告诉 ts 请勿扩大范围。
???仔细想一想下面三行代码以及背后的运作原理 ?let a = 123; let t = typeof a; // "number" type T = typeof a; // number
泛型的意义在于,他声明了类型的上下文环境,使类型可以作为参数进行传递,比如我想实现一个数学上的常函数 x => x,ts 实现如下(需要用到泛型):
常函数 x => x
这里的 ts 声明描述了:
当具体 ts 去推断 val 的类型的时候,就可以发现 val 是 id('123') 这时候 T = '123' 因此 val 的类型就是 '123'。
泛型无处不在,它是类型的拓展,我们一般利用泛型去定义 可拓展的数据结构/接口/类型, 如 js 一些原生类里面就有泛型的影子:
// 求和 arr 并结果将其以 promise 的形式包裹返回
function sum(arr: Array<number>): Promise<number> {
const sum = arr.reduce((a, b) => a + b);
return Promise.resolve(sum);
}
sum([1, 2, 3]).then(console.log);
// => 6
???泛型里的泛是宽泛的泛,而不是范式的范。
最后一例:利用泛型实现链表:
// 链表, 这里声明了泛型 T
class CommomList<T> {
value: T;
// 这里的意思几乎等价于下面这种写法,用于声明可能不存在的字段:
// next: CommomList<T> | undefined;
next?: CommomList<T>;
// 构造函数
constructor(value: T, next?: CommomList<T>) {
this.value = value;
this.next = next;
}
// 设置 next
setNext(next: CommomList<T>) { // next 是下一个节点
this.next = next;
return next;
}
// 链表生长
grow(value: T) { // value 下一个节点的值
const t = new CommomList(value);
this.setNext(t);
return t;
}
// 递归地打印自己
toString(): string {
return this.next ?
JSON.stringify(this.value) + ' -> ' + this.next.toString() :
JSON.stringify(this.value) + ' -> null';
}
}
const _1 = new CommomList(1);
_1.grow(2).grow(3).grow(4);
// _1 是头
console.log(_1.toString());
本文说明了 TS 的基本概念和使用方式,以下是总结 CheckList:
本文的下一篇是「构造类型抽象、TypeScript 编程内参(二)」,敬请期待