TypeScript 基础 及 深入理解高级用法
一、基础类型
TypeScript 支持与 JavaScript 几乎相同的数据类型。
注:类型注解
作用:相当于强类型语言(GO、C++等)中的类型声明
语法:(变量/函数): type
1.JavaScript 已有的数据类型
String(字符串):使用 string
表示文本数据类型。 和 JavaScript 一样,可以使用双引号 "
或单引号 '
表示字符串, 反引号 ` 来定义多行文本和内嵌表达式
let str: string = 'abc'
Number(数字):和 JavaScript 一样,TypeScript 里的所有数字都是浮点数(不论是整数还是小数,都被视为浮点数来处理)。这些浮点数的类型是 number
。
let notANumber: number = NaN
let num: number = 123 //普通数字
let infinityNumber: number = Infinity //无穷大
let decimal: number = 6 //十进制
let hexLiteral: number = 0xf00d //十六进制
let binaryLiteral: number = 0b1010 //二进制
let octalLiteral: number = 0o744 //八进制
Boolean(布尔):我们使用 boolean
表示布尔类型,表示逻辑值 true
/ false
let bool: boolean = true
Object(Array、Function)(数组部分):TypeScript 有两种定义数组的方式:第一种,可以在元素类型后加上 []
。 第二种,可以使用数组泛型 Array<元素类型>
。 此外,在元素类型中可以使用联合类型。 符号 |
表示或
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]
let arr3: Array<number | string> = [1, 2, 3, 'a']
Object(Array、Function)(对象部分):TypeScript 有两种定义对象的方式。 第一种,可以在元素后加上 object
。 第二种,可以使用 { key: 元素类型 }
形式。 同样在元素类型中可以使用联合类型。注意第一种形式对象元素为只读。
let obj1: object = { x: 1, y: 2 }
obj1.x = 3 // Error: Property 'x' does not exist on type 'object'.
let obj2: { x: number, y: number } = { x: 1, y: 2 }
obj2.x = 3
Object(Array、Function)(函数部分):(key1:参数类型 1,key2:参数类型 2):函数返回值类型
let plus: (x: number, y: number) => number
plus = (a, b) => a + b
plus(2, 2) // 2
还可以用接口来定义它
interface Add {
(x: number, y: number): number
}
let add: Add = (a, b) => a + b
Symbol(独一无二的值)
undefined:undefined
表示未定义的值
null:null
表示对象值缺失
2.TypeScript 新增的数据类型
void(表示函数不返回任何值)
function logMessage(message: string): void {
console.log(message);
}
any(任意类型,可以绕过 TypeScript 的类型检查,适用于那些类型不确定或需要兼容多种类型的场景)
let value: any = "hello";
value = 42; // 后续可以赋予任何类型的值
never(表示永远不会出现的类型。它用于函数的返回类型,表明该函数永远不可能正常结束(总是抛出异常或无限循环))never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是
never的子类型或可以赋值给
never类型(除了
never本身之外)。 即使
any也不可以赋值给
never
function throwError(message: string): never {
throw new Error(message);
}
元组(表示一个已知元素数量和类型的数组,每个元素的类型可以不同,对应位置的类型必须相同)
let person: [string, number];
person = ["Alice", 30]; // 正确
枚举(为一组命名的常量提供便捷的语法。枚举可以为一组相关的值提供易于记忆的名字,增加代码的可读性,枚举成员值只读,不可修改。)
enum Color {Red, Green, Blue}
let favoriteColor: Color = Color.Green;
-
数字枚举:初始值为 0, 逐步递增,也可以自定义初始值,之后根据初始值逐步递增
enum Role { Reporter = 1, Developer, Maintainer, Owner, Guest } console.log(Role.Developer) // 2 console.log(Role[2]) // Developer
-
字符串枚举
enum Message { Success = '成功', Fail = '失败' }
高级类型:
-
类型别名:为类型创建一个新的名字,提高代码的可读性
type StringOrNumber = string | number;
-
联合类型:表示一个值可以是几种类型之一
function combine(a: number | string, b: number | string) { // ... }
-
交叉类型:表示一个值同时满足多种类型
interface Developer { name: string; skills: string[]; } interface Person { age: number; } type FullStackDeveloper = Developer & Person;
-
映射类型:根据现有类型创建新类型,常用于创建像 Partial、Readonly 这样的实用类型
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
二、接口
在 TypeScript 中,我们可以使用接口 interface
来定义对象类型
interface Person {
name: string
age: number
}
let man: Person = {
name: 'James',
age: 30
}
我们定义了一个接口 Person
和变量 man
,变量的类型是 Person
。 这样我们就约束了该变量的值中对象的 key
和 value
要和接口一致。
需要注意的是:
- 接口规范首字母大写
- 被赋值的变量必须和接口的定义保持一致,参数不能多也不能少
- 类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型正确即可
1.可选属性
interface Person {
name: string
age?: number
}
let man: Person = {
name: 'James'
}
2.索引签名
[propName: string]: any
它用于定义对象类型,允许对象的属性名是字符串类型,且对应的值可以是任意类型(any
)。这种写法常用于表示一个对象可以接受任意数量的、键为字符串、值为任意类型的属性
interface Person {
name: string
age: number
[x: string]: any
}
let man: Person = {
name: 'James',
age: 30,
height: '180cm'
}
除了 name
和 age
必须一致以外,其他属性可以随意定义数量不限
3.只读属性
属性名前使用 readonly
关键字制定为只读属性,初始化后不可更改
interface Person {
readonly name: string
age: number
}
let man: Person = {
name: 'James',
age: 30
}
man.name = 'Tom' // Error: Cannot assign to 'name' because it is a read-only property.
三、函数
TypeScript 函数可以创建有名字的函数或匿名函数,四种定义函数的写法:
function add (x: number, y: number) {
return x + y
}
const add: (x: number, y: number) => number
type add = (x: number, y: number) => number
interface add {
(x: number, y: number) => number
}
TypeScript 里的每个函数参数都是必要的。这里不是指不能把 null
、undefined
当做参数
1.可选参数
function add (x: number, y: number, z?: number) {
return x + y
}
add(1, 2)
注意:可选参数必须在必选参数之后
2.默认参数
与 JavaScript 相同,在 TypeScript 里函数参数同样可以设置默认值,用法一致
function add (x: number, y = 2) {
return x + y
}
3.剩余参数
与 JavaScript 相同。TypeScript 可以把所有参数收集到一个变量里
function add (x: number, ...rest: number[]) {
return x + rest.reduce((prev, curr) => prev + curr)
}
add(1, 2, 3) // 6
注意:剩余参数必须在必选参数之后,可选参数不允许和剩余参数共同出现在一个函数内
4.函数兼容性
// 固定参数
let a = (p1: number, p2: number) => {}
// 可选参数
let b = (p1?: number, p2?: number) => {}
// 剩余参数
let c = (...args: number[]) => {}
a = b
a = c
b = a // Error
b = c // Error
c = a
c = b
固定参数兼容可选参数和剩余参数。可选参数不兼容固定参数和剩余参数
四、类
public、private、protected、readonly
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public
、private
和 protected
-
public
修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public
class Animal { public name: string public constructor (name: string) { this.name = name } } let a = new Animal('Jack') console.log(a.name) // Jack a.name = 'Tom' console.log(a.name) // Tom
-
private
修饰的属性或方法是私有的,不能在声明它的类的外部访问,包括继承它的类也不可以访问class Animal { private name: string public constructor (name: string) { this.name = name } } let a = new Animal('Jack') console.log(a.name) // Error: Property 'name' is private and only accessible within class 'Animal'. class Cat extends Animal { constructor (name: string) { super(name) console.log(this.name) // Error: // Property 'name' is private and only accessible within class 'Animal'. } }
-
protected
修饰的属性或方法是受保护的,它和private
类似,区别是它在子类中也是允许被访问class Animal { protected name: string public constructor (name: string) { this.name = name } } class Cat extends Animal { constructor (name: string) { super(name) console.log(this.name) } }
-
以上三种可以修饰构造函数,默认为
public
,当构造函数为private
时,该类不允许被继承或实例化;当构造函数为protected
时,该类只允许被继承。 -
readonly
修饰的属性为只读属性,只允许出现在属性声明或索引签名中。
注意如果 readonly
和其他访问修饰符同时存在的话,需要写在其后面
class Animal {
// public readonly name: string
public constructor (public readonly name: string) {
this.name = name
}
}
五、接口
接口继承接口
interface Animal {
name: string
eat (): void
}
interface Predators extends Animal {
run (): void
}
class Cat implements Predators {
constructor (name: string) {
this.name = name
}
name: string
eat () {}
run () {}
}
继承多个接口用 ,
分割,同理实现多个接口方式相同
interface Animal {
name: string
eat (): void
}
interface Lovely {
cute: number
}
interface Predators extends Animal, Lovely {
run (): void
}
class Cat implements Predators {
constructor (name: string, cute: number) {
this.name = name
this.cute = cute
}
name: string
cute: number
eat () {}
run () {}
}
混合类型
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1
}
一个函数还可以有自己的属性和方法
//接口定义
interface Counter {
(start: number): string // 定义了作为函数的行为,接受一个number类型的参数,返回string
interval: number // 定义了一个名为interval的属性,类型为number
reset(): void // 定义了一个名为reset的方法,无参数,返回void
}
//定义了一个Counter接口,它不仅描述了一个函数类型(接受一个数字参数并返回字符串),还包含了额外的属性(interval)和方法(reset)
//类型断言
let counter = <Counter>function (start: number) {}
//使用<Counter>进行类型断言,告诉TypeScript编译器我们打算将这个匿名函数赋予Counter接口的形状。这意味着该函数除了作为普通函数使用外,还会拥有interval属性和reset方法
//扩展函数功能
counter.interval = 123; // 给函数添加interval属性
counter.reset = function () {}; // 给函数添加reset方法
//通过直接赋值的方式,给counter函数对象添加了interval属性和reset方法,使其符合Counter接口的定义
//使用实例
let c = getCounter();
c(10); // 调用作为函数的功能
c.reset(); // 调用reset方法
c.interval = 5.0; // 修改interval属性
六、泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。可以把泛型理解为代表类型的参数
缺陷展示:
function createArray(length: number, value: any): Array<any> {
let result = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型,可以传入任意类型:
Array<any>
允许数组的每一项都为任意类型。如果我们想将类型固定住,泛型就派上用场了
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray<string>(3, 'x') // ['x', 'x', 'x']
上例中,我们在函数名后添加了 <T>
,其中 T
用来指代任意输入的类型,在后面的输入 value: T
和输出 Array<T>
中即可使用了
当value
为string
类型的时候,函数返回值也会变成Array<string>
类型;而当value
为 number 类型的时候,自然函数返回值也会变成Array<number>
类型
多个类型参数
定义泛型的时候,可以一次定义多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
swap([7, 'seven']) // ['seven', 7]
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型(可能是 string、number 等任意类型),所以不能随意的操作它的属性或方法
function loggingIdentity<T>(arg: T): T {
console.log(arg.length) // Error: Property 'length' does not exist on type 'T'.
return arg
}
这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length
属性的变量
interface Lengthwise {
length: number
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
上例中,我们使用了 extends
约束了泛型 T
必须符合接口 Lengthwise
的形状,也就是必须包含 length
属性
泛型函数
可以用泛型来约束函数的参数和返回值类型
type Log = <T>(value: T) => T
let log: Log = (value) => {
console.log(value)
return value
}
log<number>(2) // 2
log('2') // '2'
log(true) // <boolean>true
泛型接口
之前学习过,可以使用接口的方式来定义一个函数需要符合的形状
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function (source: string, subString: string) {
return source.search(subString) !== -1
}
同样也可以使用含有泛型的接口来定义函数的形状
//定义泛型接口
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>
}
//实现泛型接口
let createArray: CreateArrayFunc<string> = function(length: number, value: string): Array<string> {
let result: string[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
七、高级类型
交叉类型、联合类型、条件类型
1.交叉类型
&
符号,多个类型合并为一个类型,新的类型具有所有类型的特性
interface DogInterface {
run (): void
}
interface CatInterface {
jump (): void
}
let pet: DogInterface & CatInterface = {
run () {},
jump () {}
}
2.联合类型
取值可以为多种类型中的一种
let a: number | string = 1 // or '1'
字面量联合类型
let a: 'a' | 'b' | 'c'
let b: 1 | 2 | 3
对象联合类型
interface DogInterface {
run (): void
}
interface CatInterface {
jump (): void
}
class Dog implements DogInterface {
run () {}
eat () {}
}
class Cat implements CatInterface {
jump () {}
eat () {}
}
enum Master { Boy, Girl }
//getPet 方法体内的 pet 变量被推断为 Dog 和 Cat 的联合类型。在类型未确定的情况下,只能访问联合类型的公有成员 eat 方法
function getPet (master: Master) {
let pet = master === Master.Boy ? new Dog() : new Cat()
pet.eat()
return pet
}
3.条件类型
形式为 T extends U ? X : Y
,如果类型 T
可以赋值为 U
结果就为 X
反之为 Y
type TypeName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'function' :
'object'
type T1 = TypeName<string> // type T1 = "string"
type T2 = TypeName<string[]> // type T2 = "object"
深入理解 TypeScript 高级用法篇
一、Typescript 类型系统
1.Typescript 的类型是支持定义 “函数定义” 的
如何在 Typescript 类型系统中定义函数呢?
Typescript
中类型系统中的的函数被称作 泛型操作符
,其定义的简单的方式就是使用 type
关键字:
// 这里我们就定义了一个最简单的泛型操作符
type foo<T> = T;
这里的代码如何理解呢,其实这里我把代码转换成大家最熟悉的 Javascript
代码其实就不难理解了
// 把上面的类型代码转换成 `JavaScript` 代码
function foo(T) {
return T
}
其实类型系统中的函数还支持对入参的约束
// 这里我们就对入参 T 进行了类型约束
type foo<T extends string> = T;
那么把这里的代码转换成我们常见的 Typescript
是什么样子的呢
function foo(T: string) {
return T
}
当然啦我们也可以给它设置默认值
// 这里我们就对入参 T 增加了默认值
type foo<T extends string = 'hello world'> = T;
那么这里的代码转换成我们常见的 Typescript
就是这样的
function foo(T: string = 'hello world') {
return T
}
2.Typescript 的类型是支持 “条件判断” 的
这在 Typescript
官方文档被称为 条件类型(Conditional Types)
,定义的方法也非常简单,就是使用 extends
关键字
T extends U ? X : Y;
这不就是 三元运算符
嘛!
结合上面的 “函数”,我们就可以实现了一个简单的带判断逻辑的函数
type num = 1;
type str = 'hello world';
type IsNumber<N> = N extends number ? 'yes, is a number' : 'no, not a number';
type result1 = IsNumber<num>; // "yes, is a number"
type result2 = IsNumber<str>; // "no, not a number"
3.Typescript 的类型是支持 “作用域” 的
全局作用域
就像常见的编程语言一样,在 Typescript
的类型系统中,也是支持 全局作用域 的。换句话说,你可以在没有 导入 的前提下,在 任意文件任意位置 直接获取到并且使用它。
通常使用 declare
关键字来修饰,例如我们常见的 图片资源
的类型定义:
declare module '*.png';
declare module '*.svg';
declare module '*.jpg';
当然我们也可以在 全局作用域 内声明一个类型
declare type str = string;
declare interface Foo {
propA: string;
propB: number;
}
需要注意的是,如何你的模块使用了 export
关键字导出了内容,上述的声明方式可能会失效,如果你依然想要将类型声明到全局,那么你就需要显式地声明到全局:
declare global {
const ModuleGlobalFoo: string;
}
类型也可以支持闭包
function Foo<T> () {
return function(param: T) {
return param;
}
}
const myFooStr = Foo<string>();
// const myFooStr: (param: string) => string
// 这里触发了闭包,类型依然可以被保留
const myFooNum = Foo<number>();
// const myFooNum: (param: number) => number
// 这里触发了闭包,类型也会保持相互独立,互不干涉