1. 概念:
- 以JS为基础构建的语言
- 一个JS的超集
- 可以在任何支持JS的平台中执行
- TS扩展了JS并添加了类型
- TS不能被JS解析器直接执行
Typed JavaScript at Any Scale.
添加了类型系统的 JavaScript,适用于任何规模的项目
它强调了 TypeScript 的两个最重要的特性——类型系统、适用于任何规模。
1.1 TypeScript 的特性
-
- 类型是最核心的特性
- JavaScript比较灵活,是动态类型语言,没有类型限制,可以隐式类型转换(弱类型),但也因此有了一定的风险。
类型系统按照「类型检查的时机」来分类,可以分为动态类型和静态类型。
动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。
JavaScript 是一门解释型语言,没有编译阶段,所以它是动态类型
- TypeScrip 是静态类型
静态类型是指编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。
TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 `TypeScript` 是静态类型
- TypeScript 是强类型
类型系统按照「是否允许隐式类型转换」来分类,可以分为强类型和弱类型
JS是弱类型,允许隐式转换
var a
a = 1
a = '33'
TS不允许隐式类型转换
以下这段代码不管是在 JavaScript 中还是在 TypeScript 中都是可以正常运行的,运行时数字 `1` 会被隐式类型转换为字符串 `'1'`,加号 `+` 被识别为字符串拼接,所以打印出结果是字符串 `'11'`
console.log(1 + '1');
// 打印出字符串 '11'
-
- 适用于任何规模 TypeScript 非常适用于大型项目——这是显而易见的,类型系统可以为大型项目带来更高的可维护性,以及更少的 bug
在中小型项目中推行 TypeScript 的最大障碍就是认为使用 TypeScript 需要写额外的代码,降低开发效率。但事实上,由于有[类型推论],大部分类型都不需要手动声明了。
日常使用TypeScript:在 VSCode 编辑器中编写 JavaScript 时,代码补全和接口提示等功能
-
- 与标准同步发展 TypeScript 的另一个重要的特性就是坚持与 ECMAScript 标准[10]同步发展
1.2 TS增加了什么
- 类型
- 支持ES的新特性
- 强大的开发工具
- 添加ES不具备的性特性
- 丰富的配置选项
2. 基础类型
2.1 js的八种基本类型
- 字符串(string)
- 数字(number)
- 布尔值(boolean)
- 未定义(undefined)
- 空值(null)
- 对象(object)
- 大整数(bigInt,ES6 新增)
- 符号(symbol,ES6 新增)
// JS中`Symbol`类型是为了解决属性名冲突的问题
// 创建`symbol`变量最简单的方法是用`Symbol()`函数。`sysmbol`变量有两点比较特别:
// 1. 它可以作为对象属性名。只有字符串和 `symbol` 类型才能用作对象属性名。
// 2. 没有两个`symbol` 的值是相等的。
const symbol1 = Symbol();
const symbol2 = Symbol();
symbol1 === symbol2; // false
const obj = {};
obj[symbol1] = 'Hello';
obj[symbol2] = 'World';
2.2 对应的TS类型
let name: string = 'zs'
let age: number = 233
let isVisible: boolean = false
let a: undefined = undefined
let b: null = null
let obj: object = {
name: 'cici'
}
let myInt: bigint = 100n // 目标低于 ES2020 时,bigInt 文本不可用
let mySymol: symbol = Symbol('me')
2.2.1 Array
// 元素类型[]
let list: number[] = [1, 2, 3];
// Array<元素类型>
let list: Array<number> = [1, 2, 3];
2.2.2 Tuple 元祖
// 元组类型允许表示一个已知元素数量(长度)和类型的数组
let c: [number, string]
c = [10, 'ok'] // ok
c = ['ok', 10] // error
2.2.3 Void 空值
// JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 `void` 表示没有任何返回值的函数:
// 以函数为例,就表示没有返回值的函数
function fn ():void {
console.log("My name is Tom")
}
// 声明一个 `void` 类型的变量没有什么用,因为你只能为它赋予 `undefined` 和 `null`
let unusable: void = undefined
2.2.4 Null 和 Undefined
// 在 TypeScript 中,可以使用 `null` 和 `undefined` 来定义这两个原始数据类型
let u: undefined = undefined;
let n: null = null;
// 默认情况下`null`和`undefined`是所有类型的子类型。
// 就是说你可以把`null`和`undefined`赋值给`number`类型的变量
// 注意:当你使用严格模式("strict": true,)`null`和`undefined`只能赋值给`void`和它们各自
let u: undefined = undefined;
let n: null = null;
2.2.5 Any
// any 表示是任意类型,一个变量设置类型为any相当于对该变量关闭了TS的类型检测 (不建议使用 any)
let d: any = 4;
d = 'hello';
d = true;
// 声明变量不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any)
let e // 隐式声明
e = 678
e = false
// e 的类型是any,他可以赋值给任何变量,任何类型的值可以赋值给`any`
2.2.6 unknown
// unknown 实际上就是一个类型安全的any
// unknown类型的变量,不能直接赋值给其他变量
let notSure: unknown = 4;
let num: number
num = notSure // 不能将类型“unknown”分配给类型“number”
// (1) 通过 typeof
if (typeof notSure === 'number') {
num = notSure
}
// (2) 通过 类型断言
num = notSure as number
// 任何类型的值都可以赋值给它,但它只能赋值给`unknown`和`any`
let notSure: unknown = 4;
let uncertain: any = notSure; // OK
let notSure: any = 4;
let uncertain: unknown = notSure; // OK
let notSure: unknown = 4;
let uncertain: number = notSure; // Error
2.2.7 enum 枚举类型
枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举
枚举是一种数据结构,使用枚举我们可以定义一些带名字的常量,清晰地表达意图或创建一组有区别的用例。 TS支持数字的和基于字符串的枚举
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
// 枚举成员会被赋值为从 `0` 开始递增的数字,我们可以像访问对象属性一样访问枚举成员:
console.log(Days.Sun) // 0
console.log(Days.Mon) // 1
......
console.log(Days.Sat) // 6
3. 类型断言
// 有些情况下,变量的类型对于我们来说是很明确,但是TS编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型
// 类型断言,可以用来告诉解析器变量的实际类型
// 语法: 变量 as 类型; <类型>变量
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
// 我们还可以初始化枚举成员,那么该初始化成员后面的成员会在它的基础上自动增长`1`:
enum Days {Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days.Sun) // 1
console.log(Days.Mon) // 2
......
console.log(Days.Sat) // 7
// 字符串枚举很简单,直接赋给每个成员字符串字面量
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
4. 类型推论
// 如果没有明确的指定类型,那么 `TypeScript` 会依照类型推论的规则推断出一个类型。
let n = 'one'
console.log(n) // n: string
// `TypeScript` 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
// 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 `any` 类型而完全不被类型检查
let myNumber;
myNumber = 'seven';
myNumber = 7;
5.联合类型
// 联合类型表示取值可以为多种类型中的一种,使用 `|` 分隔每个类型。
let aa: string | number
aa = 'fine'
aa = 66
6. 交叉类型
// 交叉类型是将多个类型合并为一个类型。
// 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,使用`&`定义交叉类型
interface A {
name: string,
age: number
}
interface B {
name: string,
gender: string
}
let a: A & B = { // OK
name: "兔兔",
age: 18,
gender: "男"
};
// `a`既是`A`类型,同时也是`B`类型。
7.类型声明
// 描述一个对象的类型
// 类型声明:名称唯一,不能重复
type myType = {
name: string
age: number
}
const obj: myType = {
name: 'ccc',
age: 33 // 这里的属性要和类型声明中一致,不能少,不能多
}
8. 接口和抽象类
TypeScript也能够用接口来明确的强制一个类去符合某种契约。 我们也可以用类去实现接口,这里使用关键字implements:
// 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
// 同时,接口也可以当成类型声明来使用
interface yourType {
name: string
age: number
}
interface yourType {
gender: string
}
const you: yourType = {
name: 'youyou',
age: 333,
gender: '女'
}
// 可以用类去实现接口,这里使用关键字`implements`
interface Title {
title: string
}
class title implements Title {
title: string = '兔子'
age: number = 3 // 在实现接口的基础上,也可以添加其他的属性和方法
}
// 一个类可以实现多个接口:
interface Age {
age: number;
}
interface Title{
title: string;
}
class title implements Title, Age{
title: string = '兔兔';
age: number = 18;
}
// ts 数组类型不一致,可以定义接口来约束返回数据类型
interface Test{
arr:string[]
}
const arr= reactive<Test>({arr:[]})
// 接口可以在定义类的时候去限制类的结构
// 接口中所有的属性都不能有实际的值
// 接口只定义对象的结构,不考虑实际值
// 在接口中,所有的方法都是抽象方法
// 接口:规范,对类的限制
// 接口和抽象类非常相似,
// 区别是:(1)抽象类中的方法可以是普通方法,可以是抽象方法; 接口必须是抽象方法 (2)定义抽象:abstract; 定义接口:interface
- 抽象类
// 以abstract开头的类是抽象类
// 抽象类和其他类区别不同,只是不能用来创建对象
// 抽象类就是专门用来被继承的类
abstract class Animal{
name: string
constructor (name: string) {
this.name = name
}
// 定义一个抽象方法
// 抽象方法使用 abstract 开头,没有方法体
// 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
abstract sayHello(): void
}
class Dog extends Animal {
sayHello() {
// super.sayHello()
console.log('!!!!!!')
}
}
const dog = new Dog('xiaohei')
console.log('dog', dog)
9. 泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
// 定义函数时使用泛型
function fn<T>(a: T): T {
return a
}
// 可以直接调用具有泛型的函数
let res1 = fn(10) // 不指定泛型,TS可以自动对类型进行判断
let res2 = fn<string>('hi') // 指定泛型
// 泛型可以同时指定多个
function fn1<T, S>(a: T, b: S):T {
console.log(a, b)
return a
}
fn1<string, number>('ok', 333)
// 定义类时使用泛型
class MyClass<T> {
name: T
constructor(name: T) {
this.name = name
}
getName(x: T) {
return x
}
}
const getClass = new MyClass<string>('zs')
10. ! 和 ?.
type A = {
name?: string, // 这里的name属性是可选的
age!: number // age属性是必选的
}
// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
return new Error().stack.split('\n');
// 我们可以添加?操作符,当stack属性存在时,调用 stack.split。若stack不存在,则返回空
return new Error().stack?.split('\n');
// 以上代码等同以下代码, 感谢 @dingyanhe 的监督
const err = new Error();
return err.stack && err.stack.split('\n');
// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
new Error().stack.split('\n');
// 我们确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在
new Error().stack!.split('\n');
11. 扩展已经定义的类型
TypeScript 还允许你通过 declare module 的语法来扩展已有模块中定义的类型
declare module 'axios' {
/**
* Costom Axios Field.
*/
export interface AxiosRequestConfig {
redirect?: string
}
}
通过declare global,可以拓展全局变量的类型和方法
declare global {
interface IRequestData {
error?: number
data: any
msg?: string
}
}
参考:
ts.xcatliu.com/introductio…
juejin.cn/post/700317…
juejin.cn/post/684490…
github.com/e2tox/blog/…