TS 速通教程
直接通过常见的写法来学习 TS 的基本使用。
三十的前端课:教程来源
1.why TS
为什么需要使用 TS?
看下面的三个场景:
- 项目需要编写一个全局数组的排序方法
- 你是公司的负责人,现在需要带领团队写一系列数组的相关方法
- 你写了一个组件,该组件的事件必须具有一个特定的参数
这时候就会存在几个问题:
- 项目其他成员怎么知道你这个方法是需要传入一个数组的?
- 你的团队中的成员怎么知道这一系列方法的参数是什么?
- 组件的参数的类型别人怎么知道?
通过以上的例子,我们就可以知道 TS 的目的就是为了通过类型约束来保证代码的健壮性。
2.基础环境搭建
TS 的运行环境只需要一个编译器,将 TS 编译为 JS 再执行。
可以通过 tsc
或者 ts-node
来运行。
npm install -g typescript
npm install -g ts-node
3.基础类型
基础类型有下面这些:
string number null undefined boolean
// 这是五个比较常见的基本类型
let a: string = 'hello'
let b: boolean = true
let c: number = 1
let d: null = null
let e: undefined = undefined
// 这里的 null 是不会报错,因为没有开启严格模式
// a = null
// 这里的 100 会报错,因为 number 不能赋值给 string
// a = 100
// 这里只能是这三个值,叫做字面量类型
let ENV: 'dev' | 'test' | 'prod' = 'dev'
let F: 1 | 2 | 3 = 1
// 或者 | -> 联合类型,即一个变量可以是多个类型中的一个
let a1: string | number = 'hello'
a1 = 100
// 不能将类型“String”分配给类型“string”。
// “string”是基元,但“String”是包装器对象。如可能首选使用“string”。
// let f: string = new String('hello')
4.数组和对象
数组
// 数组有这两种定义方式,一种是类型+方括号,一种是泛型
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]
// 空数组的定义方式
let arr3: [] = []
// 数组中是数字或者字符串的定义方式
let arr4: (number | string)[] = [1, 2, 3, '4']
// 元组的定义方式
// 元组和数组之间的区别是,元组长度是固定的,而数组长度是不固定的
let tuple: [number, string] = [1, '2']
// 某一个元素可以是undefined
let tuple1: [number, string, string?] = [1, '2']
let tuple2: [number, string, ...(string | number)[]] = [1, '2', '3', '4']
对象
// 对象的定义方式
let obj: {} = { name: 'lisi' }
let obj1: { name: string } = { name: 'lisi' }
let obj2: { name: string; age?: number } = { name: 'lisi' }
// 除了 name z之外,还可以有其他的属性,数量不确定,key是字符串,value是任意类型
let obj3: { name: string; [propName: string]: any } = { name: 'lisi', age: 18 }
let obj4: {
a: {
b: 1
}
}
// 使用 ? 表示可选属性
// 我们如果后端没有返回 a 属性,那么 a 就是 undefined,如果再取 b 属性,就会报错
// 所以我们可以使用 ? 来表示 a 是可选的,如果 a 不存在,那么就不会取 b 属性,也不会报错
// es5 的解决方案是 obj4 && obj4.a && obj4.a.b
obj4?.a?.b
5.函数
// 函数的定义方式
function f1(a: number): number {
return a + 1
}
let f2: (a: number) => number = function (a) {
return a + 1
}
// 参数可选
function f3(a: number, b?: number): number {
return a + b
}
// 参数默认值
function f4(a: number, b: number = 10): number {
return a + b
}
// 剩余参数
function f5(a: number, ...rest: number[]): number {
return a + rest.reduce((pre, cur) => pre + cur)
}
// this 指向, this 参数必须在参数列表的最前面,表示这个函数的调用者
function f6(this: Window, a: number): number {
return a + 1
}
// 重载
function f7(a: number): number
function f7(a: string): string
function f7(a: any): any {
return a
}
6.类
TS 中的 class
// ts 中的 class, 相比 js 多了访问修饰符,只读属性,静态属性,抽象类,抽象方法,接口等等
class TsClass {
public name: string = 'TsClass'
static age: number = 18
readonly d = 1
protected e = 2 // 只能够在当前类和子类中访问
private f1() {
console.log('f1')
}
}
let tsClass = new TsClass()
// tsClass.f1() // 报错,因为 f1 是私有的
// 读取静态属性
console.log(TsClass.age)
// 更改只读属性
// tsClass.d = 2 // 报错,因为 d 是只读的
编译为 JS:
// ts 中的 class, 相比 js 多了访问修饰符,只读属性,静态属性,抽象类,抽象方法,接口等等
var TsClass = /** @class */ (function () {
function TsClass() {
this.name = 'TsClass';
this.d = 1;
this.e = 2; // 只能够在当前类和子类中访问
}
TsClass.prototype.f1 = function () {
console.log('f1');
};
TsClass.age = 18;
return TsClass;
}());
var tsClass = new TsClass();
// tsClass.f1() // 报错,因为 f1 是私有的
// 读取静态属性
console.log(TsClass.age);
// 更改只读属性
// tsClass.d = 2 // 报错,因为 d 是只读的
类型注释
class TsClass {
a: number
b: string
constructor() {
this.a = 123
this.b = '456'
}
f(p: number): number {
return p + 1
}
}
此外,ts 中的 class 还可以作为类型来标注对象。
7.特殊类型和高级类型
any 和 unkonwn
let value: unknown = 1
value = 'foo'
value = true
value = undefined
value = null
value = {}
value = []
value = () => {}
let value1: any = 1
value1 = 'foo'
value1 = true
value1 = undefined
value1 = null
value1 = {}
value1 = []
value1 = () => {}
// 任何类型的值都可以赋值给 unkonwn 和 any 类型,any 直接相当于放弃了类型检查,而 unknown 会在使用前进行类型检查,这是 unknown 和 any 的重要区别
// unknown 类型的值不能直接赋值给其他类型变量,需要先进行类型断言
// any
let value3: () => {} = value1
// unknown 需要先进行类型断言
let value4: () => {} = value as () => {}
never
永远不会出现
// never 表示永远不会返回结果
function test(): never {
while (true) {}
}
function test2(a: string | number) {
if (typeof a === 'string') {
return a
} else if (typeof a === 'number') {
return a
} else {
return a // 这里的 a 的类型是 never
}
}
联合类型与交叉类型
联合类型就是我们之前一直使用的 |
操作符号。
交叉类型是 &
,即两种类型必须同时满足。
// 交叉类型
class A {
a: number
}
class B {
b: string
}
let obj: A & B = {
a: 1,
b: '2'
}
8.接口与 type
type 是一个关键字,用来给一个类型命名,经常用来定义基础类型、联合类型、交叉类型。
interface 意思是接口,区别于 type,这是定义了一个可继承的接口。常用于类、对象的定义。
// 定义联合类型
type sOrn = string | number
let a: sOrn = 'a'
let b: sOrn = 'b'
// 定义交叉类型
type obj1 = {
a: number
}
type obj2 = {
b: string
}
type objAll = obj1 & obj2
let c: objAll = {
a: 1,
b: '2'
}
// 定义函数
type f = (a: string) => string
let f1: f = (a: string) => {
return '1'
}
// 接口一般用来定义函数、对象、类
// 定义对象
interface obj3 {
type: {
a1: number
}
type2?: string
[propsName: string]: any
}
function f2(a: obj3) {
console.log(a.type.a1)
}
f2({ type: { a1: 1 } })
// 定义函数
interface f3 {
(a: number, b: number): string
}
let f4: f3 = (a: number, b: number) => {
return '1'
}
// 定义类
interface person {
age: number
eat(food: string): void
}
class Marry implements person {
age: number
eat(food) {
console.log(food)
}
constructor(age: number) {
this.age = age
}
}
// 定义数组
interface arr {
[index: number]: string
}
let arr1: arr = ['1', '2']
type 和 interface 之间有什么区别?
一般我们用 type 定义一些比较简单的类型,interface 一般用来定义类和对象和函数。
此外,还有以下的区别:
10.泛型和断言
泛型
可以看成一个公用的方法,到时候需要根据传入的不同类型的参数来做出对应的操作。
function find<T = number>(arg: T[], target: T): T {
let _result
arg.forEach(item => {
if (item === target) {
_result = item
}
})
return _result
}
find<number>([1, 2, 3], 3)
// 定义多个泛型
function objExtends<T, U>(a: T, b: U): T & U {
// let res = {} as T & U
// res = Object.assign(a, b)
// return res
// 或者直接下面这么写
return Object.assign(a, b)
}
// 接口中使用泛型
interface inter1<T> {
a1: T
a2: number
}
let obj: inter1<string> = {
a1: '1',
a2: 2
}
// 泛型类
class TestClass<T> {
a: T
constructor(a: T) {
this.a = a
}
}
// new TestClass<number>(1)
// 或者直接这么写,ts会自动推断类型
new TestClass(1)
// 泛型约束
function find1<T extends number | string>(arg: T[], target: T): T {
let _result
arg.forEach(item => {
if (item === target) {
_result = item
}
})
return _result
}
find1([1, 2, 3], 3)
断言
很多时候我们已经确定这个变量是什么类型了,但是 ts 的推断是无法推断出来的,这时候就需要我们使用断言,直接确定这个变量的类型。
interface obj {
a: number
b: number
}
// let obj1 = {}
// 这里如果赋值的话,会报错,因为这个对象没有定义属性
// obj1.a = 1
let obj1 = {} as obj
obj1.a = 1
11.ts 的类型推导机制
类型推断
如果你没有定义类型,ts 会自己跟你的上下文推断。
类型兼容性
interface inter1 {
a: number
b: number
}
let obj1 = {
a: 1,
b: 2
}
// 这里就是类型保护,obj1 的结构和 inter1 一样,所以可以赋值
let obj2: inter1 = obj1
如果结构不同,则会报错:
函数的类型保护机制:
let f1 = function (a: number, b: number) {}
let f2 = function (c: number, d: number) {}
f1 = f2 // 可以赋值,因为函数的结构一样
let f3 = function (a: number, b: number) {}
let f4 = function (a: number, b: number, c: number) {}
// f3 = f4 // 不可以赋值,因为函数的结构不一样
let f5 = function (a: number, b: number, c: number) {}
let f6 = function (a: number, b: number) {}
f5 = f6 // 可以赋值 ,被赋值的函数的参数是被赋值的函数的参数的子集
类的类型保护机制:
class class1 {
a: number
static c: string
constructor(a: number, b) {
this.a = a
}
}
class class2 {
a: number
static d: number
constructor(a: number, b, c, d) {
this.a = a
}
}
let instance1: class1
let instance2: class2
instance1 = instance2! // 可以赋值
12.枚举和 symbol
symbol
const symbol: symbol = Symbol('test')
枚举
一组按顺序递增的数据。
// 枚举
enum list {
a = 1,
b,
c,
d
}
enum list1 {
a = 'hello',
b = 'world'
}
13.工程化编译和 tsconfig
工程化
在使用构建工具的时候,构建工具会使用功能 loader 将 ts 转为 js,在 webpack 中可以理解为 ts-loader,其实本质上还是调用 TypeScript 的 tsc 命令。
tsconfig
{
"compilerOptions":{}, // 编译的标准
"files":[], // 编译那些文件
"include":[], // 编译那些路径下的内容
"exclude":[], // 那些路径下的内容不需要编译
"extends":[], // 配置文件是可以继承另外一个配置文件的
"typeAcquisition":{} // 制定声明文件
}
14.vue 和 react 实战 ts
这部分是最终的目的,就是在开发项目过程中使用 ts。
直接看视频:教程来源