typescript 基础系列
ts 比较难,核心是,里面有好多专有名词,设计新的语法糖。eg:类型推断,类型守卫,类型断言。
而解决它就是了解 它解决什么问题。
0.ts 解决问题
核心是 创建一套 静态类型系统
The TypeScript Tax 是一篇非常优秀的文章,阅读这篇文章能让我们更为客观看待 TS,虽然站在作者的角度看,TS 弊大于利,主要原因是
- TS 提供的功能大多都可以用其它工具配合在一定程度上代替,而且类型系统会需要写太多额外的代码,类型系统在一定程度上也破坏了动态语言的灵活性,让一些动态语言特有的模式很难在其中被应用。
作者最终的结论带有很强的主观色彩,但是这篇文章的分析过程非常精彩,就 TS 的各种特性和现在的 JS 生态进行了对比,能让我们对 TS 有一个更全面的了解。
1.类型
要学好ts 就的先了解其类型系统
基本类型
编程实际上就是对数据进行操作和加工的过程。类型系统能辅助我们对数据进行更为准确的操作。TypeScript 的核心就在于其提供一套类型系统,让我们对数据类型有所约束。约束有时候很简单,有时候很抽象。
TS 支持的类型如下:boolean
,number
,string``[]
,Tuple
,enum
,any
,void
,null
,undefined
,never
,Object
。
TS 中更复杂的数据结构其实都是针对上述类型的组合,关于类型的基础知识,推荐先阅读基础类型一节,这里只讨论一些容易造成困扰的概念:
enum
注意:是'=' 而不是':'
const enum MediaTypes {
JSON = "application/json"
}
fetch("https://xxxx/", {
headers: {
Accept: MediaTypes.JSON
}
})
.then((res) => res.json())
never
never
代表代码永远不会执行到这里,常常可以应用在 switch case
的 default
中,防止我们遗漏 case
未处理,比如:
代码健壮性
enum ShirTSize {
XS,
S,
M,
L,
XL
}
function assertNever(value: never): never {
console.log(Error(`Unexpected value '${value}'`));
}
function prettyPrint(size: ShirTSize) {
switch (size) {
case ShirTSize.S: console.log("small");
case ShirTSize.M: return "medium";
case ShirTSize.L: return "large";
case ShirTSize.XL: return "extra large";
// case ShirTSize.XS: return "extra small";
default: return assertNever(size);
}
}
Any 类型
在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。
let notSure: any = 666;
notSure = "Semlinker";
notSure = false;
any
类型本质上是类型系统的一个逃逸舱。作为开发者,这给了我们很大的自由:TypeScript 允许我们对 any
类型的值执行任何操作,而无需事先执行任何形式的检查。比如:
let value: any;
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK
在许多场景下,这太宽松了。使用 any
类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用 any
类型,就无法使用 TypeScript 提供的大量的保护机制。为了解决 any
带来的问题,TypeScript 3.0 引入了 unknown
类型。
Unknown 类型
就像所有类型都可以赋值给 any
,所有类型也都可以赋值给 unknown
。这使得 unknown
成为 TypeScript 类型系统的另一种顶级类型(另一种是 any
)。下面我们来看一下 unknown
类型的使用示例:
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK
对 value
变量的所有赋值都被认为是类型正确的。但是,当我们尝试将类型为 unknown
的值赋值给其他类型的变量时会发生什么?
let value: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error
unknown
类型只能被赋值给 any
类型和 unknown
类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown
类型的值。毕竟我们不知道变量 value
中存储了什么类型的值。
现在让我们看看当我们尝试对类型为 unknown
的值执行操作时会发生什么。以下是我们在之前 any
章节看过的相同操作:
let value: unknown;
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error
将 value
变量类型设置为 unknown
后,这些操作都不再被认为是类型正确的。通过将 any
类型改变为 unknown
类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。
内置类型
DOM 和 BOM 提供的内置对象有:
Document、HTMLElement、Event、NodeList 等。
TypeScript 中会经常用到这些类型:
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
它们的定义文件在 TypeScript 核心库的定义文件中。
类型推断
TypeScript 能根据一些简单的规则推断(检查)变量的类型
let foo = 123
foo ='abc' //不能将类型“"abc"”分配给类型“number”。ts(2322)
function fa(a: number, b: number){
return a +b
}
let t = fa(1,2) //number
不能推断的
function foo(a: number, b: number) {
return a + addOne(b);
}
// 一些使用 JavaScript 库的特殊函数
function addOne(a) {
return a + 1;
}
let fo = foo(1,2) //any
noImplicitAny
选项 noImplicitAny
用来告诉编译器,当无法推断一个变量时发出一个错误(或者只能推断为一个隐式的 any
类型),你可以:
- 通过显式添加
:any
的类型注解,来让它成为一个any
类型; - 通过一些更正确的类型注解来帮助 TypeScript 推断类型。
类型断言
TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。
interface Foo {
bar: number;
bas: string;
}
const foo = {} as Foo;
类型守卫(类型收窄)
TS 在遇到以下这些条件语句时,会在语句的块级作用域内「收紧」变量的类型,这种类型推断的行为称作类型守卫 (Type Guard)。
- 类型判断:
typeof
- 实例判断:
instanceof
- 属性判断:
in
- 字面量相等判断:
==
,===
,!=
,!==
function test(input: string | number) {
if (typeof input == 'string') {
// 这里 input 的类型「收紧」为 string
} else {
// 这里 input 的类型「收紧」为 number
}
}
function test( padding: string | number) {
if (typeof padding === "number") {
return padding * 1000
}
if (typeof padding === "string") {
return padding + 'abc';
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
类型拓展
let name = "semlinker";
//此时变量 name 的类型会被推断为 string 基本类型,因为这是用于初始化它的值的类型。
//从表达式推断变量、属性或函数结果的类型时,源类型的拓宽形式用作目标的推断类型。
//类型的拓宽是所有出现的空类型和未定义类型都被类型 any 替换。
2.泛型
泛型解决什么问题?
function identity (value) {
return value;
}
console.log(identity(1)) // 1
现在,我们将 identity 函数做适当的调整,以支持 TypeScript 的 Number 类型的参数:
function identity (value: Number) : Number {
return value;
}
console.log(identity(1)) // 1
如果 value不是 number 是string 怎么处理?
能不能通用点
function identity <T>(value: T) : T {
return value;
}
console.log(identity<Number>(1)) // 1
解释一下
**总结一下,**设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
泛型原理
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
对于上述代码,编译器足够聪明,能够知道我们的参数类型,并将它们赋值给 T 和 U,而不需要开发人员显式指定它们。下面我们来看张动图,直观地感受一下类型传递的过程:
3. tsconfig.json
告诉tsc 引擎如何解析ts