什么是TypeScript?
官方:TypeScript是一种由微软开发的开源编程语言,它是JavaScript的一个超集; **TypeScript 是面向对象的 JavaScript**
。意味着它包含了所有JavaScript的语法和特性,并且还添加了一些额外的功能和语法,以提高JavaScript的可靠性、可维护性和可扩展性。TypeScript可以被编译成JavaScript,并且可以在任何支持JavaScript的平台上运行,包括浏览器、Node.js和桌面应用程序。
TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。
TypeScript 是一门静态类型、弱类型的语言。
TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。
TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
TypeScript 拥有很多编译选项,类型检查的严格程度由你决定。
TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到TypeScript。
TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。
TypeScript 拥有活跃的社区,大多数常用的第三方库都提供了类型声明。
TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。
为什么要用TypeScript?
(一)类型检查,语法提示
在TS中允许你为变量指定类型(静态类型——当变量为静态类型时,变量将继承当前静态类型的属性和方法)。当你给已定义类型的变量赋值的时候,值的类型不对,便会有错误提示。而在JS中创建变量可以为其赋任意类型的值(动态类型)。
(二)约束类型,减少不必要的代码逻辑
typescript
//定义一个函数计算两个数据的合计
function sum(x,y){ if(typeof x != 'number') {
//对于形参的类型要添加转换
x = parseInt(x);
} return x+y };sum('1',2);
//TS的方式,直接约束了类型
function sum2(x:number,y:number){ return x+y };
# 常用类型
## 基础类型(string,boolean,number)
使用 :指定变量的类型,:的前后有没有空格都可以
```typescript
let num: number = 15;
num(变量名):number(类型) = 15(具体值)
//表示定义一个变量num,指定类型为number;
let name: string = "jeasu"
//表示定义一个变量name,指定类型为string;
```
## 任意值类型 any
```typescript
//如果是一个普通类型,
在赋值过程中改变类型是不被允许的,任意值(Any)用来表示允许赋值为任意类型。
let a1: string = ref('seven');
a1 = 7;
//error
//但如果是 any 类型,则允许被赋值为任意类型。
let sim: any = ref("seven");
sim = 7;
console.log(sim, "任意类型");
//7
//变量如果在声明的时候,未指定其类型,那么它会被识别为任意类型(动态类型)
let a3;
a3 = 'seven';
a3 = 7;
//相当于
let a3:any;
a3 = 'seven';
a3 = 7;
//any定义多元数组
let arr: number[] = [1, true, "hellow"];
arr[1] = 100;
console.log(arr, "any定义多元数组");
// [1,100,'hellow']
// 定义数组,并且每一项都是string的简写
let arr: Array<string> = [1, true, "hellow"];
// 这样就报错了,因为定义了数组类型是string,传入了其它。
```
## 数组类型 array
两种方式定义数组:
第一种方式:变量后面加上类型[ ]
```typescript
let list1: number[] = [1, 2, 3];
let list2: string[] = ["1", "2", "a"];
let list3: any[] = [1,true,'abc',{id:2},[1,2]]
```
第二种方式:元组
> [!IMPORTANT]
>>**在 TypeScript 中,元组(Tuple)是一种特殊的数据结构,它允许你表示一个固定长度的、
类型确定的数组。与普通的数组不同,元组中的每个位置可以拥有不同的数据类型。**
>> **元组的定义方式是将多个类型用逗号分隔,然后用方括号 [] 将它们括起来。**
>> `let myTuple: [string, number, boolean];`
>> `myTuple = ["hello", 123, true];`
>> `myTuple = ["hello", 123, 456];` 这个就报错了
>> 也可以定义复杂的数组,如下
>> `myTuple[] = [["hello", 123, true],["hello1", 1231, true]];`
```typescript
let hobbies: [string,number] = ["地理", 1];
hobbies.push(123);
hobbies.push('张三');
// 响应式元组的定义,数组中不能传入非定义的类型
const tupleData = ref<[string, number, boolean]>(['Hello', 123, true]);
//不报错
tupleData.value.push("haha");
// 报错:类型“{ a: number; b: number; }”的参数不能赋给类型“string | number | boolean”的参数。
tupleData.value.push({a:1,b:2});
console.log(tupleData.value);
```
注意:
数组的项中**不允许**出现其他的类型:
```typescript
let fibonacci: number[] = [1, '1', 2, 3, 5];
// Type 'string' is not assignable to type 'number'.
```
数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:
```typescript
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');
// Argument of type '"8"' is not assignable to parameter of type 'number'.
```
## 对象类型(引入概念:接口 Interfaces)
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象。接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述。
注意:
顺序可以乱,但是定义的对象要受到接口的约束,**赋值的时候,变量的形状必须和接口的形状保持一致**
。
```typescript
//定义对象属性
interface defineObj {
readonly name: string; //只读属性
age: number; //必填
sex?: string; //选填
call(): string; //有返回值,类型为string
action(): void; //必填、无返回值
}
const obj: defineObj = {
name: "abc", //只读,不可修改
age: 18, //
sex:'男',
call: () => "call",
action: function () {
console.log("void接口");
},
};
console.log(obj.call());
//callobj.action();
//void接口
```
## 函数类型
函数类型的定义有两点:参数类型、返回值类型
注意:
**输多或输少参数,是不被允许的。参数z为可选参数,可选参数放在最后,可选参数后加参数会报错**
```typescript
//有返回值
const f1 = (x: number, y: number,z?: number): number => { return x + y;};
console.log(f1(1, 3));
//4
//接口interface指定类型
interface IFunc {
(x: number, y: number, z?: number) : number
}
const func:IFunc = f1
//or
//注意这里的=>不是ES6的箭头函数,而是指定需要返回的类型,此种写法不能放在接口中
****const func:(x: number, y: number, z?: number) => number = f1
//无返回值
const f2 = (n1: number, n2: number): void => { if (n1 > n2) {
//处理逻辑
console.log(n1 + ">" + n2);
} else {
//处理逻辑
console.log(n1 + "<" + n2); }
};f2(9, 7);
//9>7
```
## 泛型(generics)
### 泛型是什么?泛型有什么作用
当我们定义一个变量不确定类型的时候有两种解决方式:
- 使用any:
使用any定义时存在的问题:虽然已知道传入值的类型但是无法获取函数返回值的类型;另外也失去了ts类型保护的优势
- 使用泛型:
泛型指的是在定义函数/接口/类型时,**不预先指定具体的类型,而是在使用的时候再指定类型限制**的一种特性。
```javascript
//使用any
function echo(arg: any): any {return arg}
let result = echo(123)
// echo返回值为any,result类型为any
or
let result: string = echo(123)
//echo返回值为any,result类型为string不会报错
//使用泛型
//泛型使用<T>定义,T为自定义泛型名字
// 定义一个名为 echo 的泛型函数
// 它接收一个类型为 T 的参数 arg,并返回一个类型也为 T 的值
function echo<T>(arg: T): T {
// 直接返回传入的参数 arg,不做任何修改
return arg
}
let result = echo<number>(123)
// echo返回值为number,result类型为number
// 定义一个名为 swap 的泛型函数
// 它接收一个包含两个元素的元组作为参数,这两个元素的类型分别是 T 和 U
// 函数返回一个新的元组,其中元素的顺序被交换了
function swap<T, U>(tuple: [T, U]): [U, T] {
// 返回一个新的元组,其中第一个元素是原元组的第二个元素,第二个元素是原元组的第一个元素
return [tuple[1], tuple[0]]
}
const result2 = swap(['str',123])
//result类型为[number,string]
```
### 约束泛型
在函数内部使用泛型变量的时候,由于不知道它是哪种类型,所以不能随意的操作他的属性或者方法。
```javascript
function echoLength<T>(arg: T): T {
console.log(arg.length);
return arg
}
//报错信息:类型“T”上不存在属性“length”。
//使用any时,有的类型也不支持length属性也会报错
解决方法一:
function echoLength<T>(arg: T[]): T[] {
console.log(arg.length);
return arg
}
const arrs = echoLength([1, 2, 3])
//不会报错:arrs为number[]类型,但是string、object都有length属性,不是最优方案
解决方案二:只允许传入包含length属性的变量
interface Ilength { length: number}
function echoLength<T extends Ilength>(arg: T): T {
console.log(arg.length);
return arg
}
const arrs = echoLength('str')
const arrs = echoLength({length:10,width:‘230’})
const arrs = echoLength(1234)
//报错信息:类型“number”的参数不能赋给类型“Ilength”的参数。number类型length是undefind
```
#### 类中使用泛型
```javascript
class Queue<T> {data:T[] = [] push(item: T) {
return this.data.push(item)
} pop():T {
return this.data.shift()
}
}
const queue = new Queue<number>()queue.push(321)queue.push('123')
//报错信息:Argument of type 'string' is not assignable to parameter of type 'number'console.log(queue.pop().toFixed());
```
### 接口中使用泛型
```javascript
interface Keypair<T, U> {
key: T, value: U
}
let kp1: Keypair<string, number> = {
key:'str',value:1
}
let kp2: Keypair<number, string> = {
key: 1, value: 'str'
}
let arr: number[] = [1, 2, 3]
//之前定义数组时
let arr2:Array<number> = [1, 2, 3]
//使用Array内置的interface以泛型的方式来展示
```
## 类型别名(type alias)
顾名思义是给一个类型起一个新的名字。使用类型别名可以使代码更加清晰、易读,也可以减少重复的类型声明。在TypeScript中,使用关键字**type**来定义类型别名。
```javascript
//创建联合类型
type strOrNum = string | number
let str: strOrNum = 'Hello World'let num: strOrNum = 1234
//创建函数类型
type num = (x: number, y: number) => number
let funcNum: num = (x: number, y: number) => {return x + y}
console.log(funcNum(1,2));
//3
```
## 字面量类型
字面量主要是源码中表示固定某些值且只能是原始数据类型的常量。
TypeScript支持多种字面量,包括数字字面量、字符串字面量、布尔字面量、数组字面量和对象字面量等。
```javascript
//字符串字面量
const str: 'name' = 'name'const str: 'name' = 'name2'
//报错信息:不能将类型“"name2"”分配给类型“"name"
//数字字面量
const num: 123 = 123const num: 123 = 456
//报错:不能将类型“456”分配给类型“123”
联合类型限定值(使用较广):
type Directions = 'Up' | 'Down' | 'Left' | 'Right'
let towhere: Directions = 'Left'
//towhere只能取类型Directions中的四个值
```
## 交叉类型
**使用&符号将多个类型合并为一个类型**。
这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
```javascript
interface IName { name: string }
interface ISex { sex:string}
type Person = IName & ISex & {age: number}
let person: Person = { name: '阿吨儿', age: 21, sex: '公' }
```
# 类(Class)
## 介绍
传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员来讲就有些棘手,因为他们用的是基于类的继承并且对象是由类构建出来的。 从ES6开始,JavaScript程序员将能够使用基于类的面向对象的方式。 使用TypeScript,我们允许开发者现在就使用这些特性,并且编译后的JavaScript可以在所有主流浏览器和平台上运行。
定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员):
- **字段** − 字段是类里面声明的变量。字段表示对象的有关数据。
- **构造函数** − 类实例化时调用,可以为类的对象分配内存。
- **方法** − 方法为对象要执行的操作。
## 创建类
以下实例我们声明了类 Car,包含字段为 engine(发动机),构造函数在类实例化后初始化字段 engine。
**this** 关键字表示当前类实例化的对象。注意构造函数的参数名与字段名相同,this.engine 表示类的字段。
此外我们也在类中定义了一个方法 disp()。
```typescript
class Car {
// 字段
engine:string;
// 构造函数
constructor(engine:string) {
this.engine = engine
}
// 方法
disp():void {
console.log("发动机为 : "+this.engine)
}
}
```
## 创建实例化对象
使用 new 关键字来实例化类的对象,语法格式如下:
```typescript
var obj = new Car("Engine 1")
```
类中的字段属性和方法可以使用 **.** 号来访问:
```typescript
//访问属性
obj.engine
// 访问方法
obj.disp()
```
## 完整实例
以下实例创建来一个 Car 类,然后通过关键字 new 来创建一个对象并访问属性和方法:
```typescript
class Car {
// 字段
engine:string;
// 构造函数
constructor(engine:string) {
this.engine = engine
}
// 方法
disp():void {
console.log("函数中显示发动机型号 : "+this.engine) }
}
// 创建一个对象
var obj = new Car("XXSY1")
// 访问字段
console.log("读取发动机型号 : "+obj.engine)
// 访问方法
obj.disp()
// 输出结果
// 读取发动机型号: XXSY1
// 函数中显示发动机型号: XXSY1
```
## 类的继承
### 基本用法
TypeScript 支持继承类,即我们可以在创建类的时候继承一个已存在的类,这个已存在的类称为父类,继承它的类称为子类。
类继承使用关键字 **extends**
,子类除了不能继承父类的私有成员(方法和属性)和构造函数,其他的都可以继承。
TypeScript 一次只能继承一个类,不支持继承多个类,但 TypeScript 支持多重继承(A 继承 B,B 继承 C)。
示例:
```typescript
class Shape {
Area:number
constructor(a:number) {
this.Area = a
}
}
class Circle extends Shape {
disp():void {
console.log("圆的面积: "+this.Area)
}
}
var obj = new Circle(223);
obj.disp()
// 输出结果
// 圆的面积: 223
```
### 多重继承(不常用)
需要注意的是子类只能继承一个父类,TypeScript 不支持继承多个类,但支持多重继承,如下实例:
```typescript
class Root { str:string; }
class Child extends Root {}
class Leaf extends Child {}
// 多重继承,继承了 Child 和 Root 类
var obj = new Leaf();
obj.str ="hello"
console.log(obj.str)
//输出结果
//hello
```
### 继承类的方法重写
类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写。
其中 super 关键字是对父类的直接引用,该关键字可以引用父类的属性和方法。
```typescript
class PrinterClass {
doPrint():void {
console.log("父类的 doPrint() 方法。")
}
}
class StringPrinter extends PrinterClass {
doPrint():void {
super.doPrint()
// 调用父类的函数
console.log("子类的 doPrint()方法。")
}
}
// 输出结果
// 父类的 doPrint() 方法。
// 子类的 doPrint()方法。
```
### static 关键字
static 关键字用于定义类的数据成员(属性和方法)为静态的,静态成员可以直接通过类名调用。
```typescript
class StaticMem {
static num:number;
static disp():void {
console.log("num 值为 "+ StaticMem.num)
}
}
StaticMem.num = 12 // 初始化静态变量
StaticMem.disp() // 调用静态方法
// 输出结果:
// num 值为 12
```
### instanceof 运算符
instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false。
```typescript
class Person{ }
var obj = new Person()
var isPerson = obj instanceof Person;
console.log("obj 对象是 Person 类实例化来的吗? " + isPerson);
// 输出结果:
// obj 对象是 Person 类实例化来的吗? true
```
### 访问控制修饰符
TypeScript 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。TypeScript 支持 3 种不同的访问权限。
- **public(默认)** : 公有,可以在任何地方被访问。
- **protected** : 受保护,可以被其自身以及其子类访问。
- **private** : 私有,只能被其定义所在的类访问。
以下实例定义了两个变量 str1 和 str2,str1 为 public,str2 为 private,实例化后可以访问 str1,如果要访问 str2 则会编译错误。
```typescript
class Encapsulate {
str1:string = "hello"
private str2:string = "world"
}
var obj = new Encapsulate()
console.log(obj.str1)
// 可访问 console.log(obj.str2)
// 编译错误, str2 是私有的
```
## 类和接口
类可以实现接口,使用关键字 implements,并将 interest 字段作为类的属性使用。
以下实例中 AgriLoan 类实现了 ILoan 接口:
```typescript
interface ILoan {
interest:number
}
class AgriLoan implements ILoan {
interest:number
rebate:number
constructor(interest:number,rebate:number) {
this.interest = interest
this.rebate = rebate
}
}
var obj = new AgriLoan(10,1)
console.log("利润为 : "+obj.interest+",抽成为 : "+obj.rebate )
```
## 类和接口的区别
`类和接口在TypeScript中各有其用途和优势。类主要用于定义具有特定行为和状态的实体,并提供了一种组织代码的方式,而接口则更多地用于定义类型和行为规范,确保代码的可互操作性和类型安全。`
- **类(Classes)**:
- 类是面向对象编程的核心概念之一,它允许你定义具有属性和方法的对象类型。在TypeScript中,类提供了封装数据和对数据的操作方法的方式,使得代码更加模块化和可重用。
- 类支持继承,这意味着一个类可以继承另一个类的属性和方法,从而实现代码的重用和扩展。这种继承机制使得我们可以创建更加复杂和灵活的应用程序。
- 类还支持私有成员,通过使用`private`关键字,可以限制成员的可见性和可访问性,从而更好地控制对象的状态和行为。
- **接口(Interfaces)**:
- 接口定义了一个对象应该具有的方法和属性的集合,但不涉及任何具体的实现。接口可以用来确保不同的对象具有相同的方法和属性,从而实现代码的可互操作性。
- 接口可以扩展,通过合并多个接口或者使用继承来实现更复杂的类型定义。
- 接口的类型检查功能非常强大,可以确保类型安全,避免在编译时出现类型错误。例如,如果一个接口定义了一个属性为字符串类型,那么在实现该接口的对象中,该属性的类型必须是字符串,否则编译器会报错。
# 接口(interface)
**官方对接口(interface)的描述**
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。TypeScript 中的 类型接口 是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。在TS中使用 interface 接口来描述对象数据的类型(**常用于给对象的属性和方法添加类型约束**)
**接口的作用:**
- 在面向对象编程中,接口是一种规范的定义,它定义了行为和动作规范;
- 在程序设计内,接口起到一种限制和规范的作用。
## **使用场景**
在常规业务开发中比较典型的就是前后端数据通信的场景:
- 前端向后端发送数据:收集表单对象数据时的类型校验;
- 前端使用后端数据:渲染后端对象数组列表时的智能提示。
## 接口定义
接口一般使用 interface 关键字来定义,名字首字母需要大写。在项目中定义接口的时候,一般在名字前加一个大写 **I** 字母,能够快速识别该类型是接口。**如:**
```typescript
interface IPerson {
name: string
age: number
sex: string
}
```
接口的主要作用就是用来进行约束对象的规范,**如:**
```typescript
interface IPerson{
name: string
age: number
sex: string
}
let webPerson: IPerson = {
name:"张三",
age:18,
sex:'boy'
}
```
使用上述 IPerson 定义对象的时候,上述三个属性都是必加的,如果漏掉一个就会有错误提示。那如果某些属性有些对象有,有些对象没有呢?比如“职业”在IPerson中没有怎么办?如下⬇️
接口成员也可以是缺省的,定义的时候使用 "?" ,**如:**
```typescript
interface IPerson{
name: string
age: number
sex: string
work?: string
}
```
但是在特殊情况下,有些人有饮食忌讳,大多数没有,有些人有特殊爱好,大多数也没有,此时这个属性不能具体,此时就可以添加任意属性,使用 propName(可自定义名称) 来定义,此中定义方式也称为:**索引签名**,**如:**
```typescript
interface IPerson{
name: string
age: number
sex: string
work?: string
[propName:string]: any
}
let webPerson: IPerson = {
name:"张三",
age:18,
sex: 'boy',
hobby: "跳舞",
refuse:"不吃羊肉"
}
```
**注意**:一个接口中只能有一个**任意属性**,如果接口中有多个类型的属性,则可以在**任意属性**中使用联合类型去定义:
```typescript
interface IPerson {
name: string;
age?: number;
[propName: string]: string | number | undefined;
}
let tom: IPerson = {
name: '张三',
age: 18,
class: 'Web'
};
```
接口中的属性可以设置只读,不允许修改,使用readonly修饰属性,**如:**
```typescript
interface IPerson {
name: string
readonly age: number
}
const o: IPerson = {
name: 'a',
age: 1,
}
// o.age = 2 // 错误信息:无法分配到 "age" ,因为它是只读属性。
console.log(o)
```
## 接口继承
接口和类一样,接口也可以相互继承。能够从一个接口里复制成员到另一个接口里,可以灵活地将接口分割到可重用的模块里面。继承的时候使用 extends 关键字。**如:**
```typescript
interface IPerson{
name: string
age: number
sex: string
work?: string
[propName:string]:any
}
interface IAdult extends IPerson{
isHaveChildren: boolean
isBoss: boolean
}
let Tom: IAdult = {
isBoss: false,
isHaveChildren: false,
name: '张三',
age: 18,
sex: "boy"
}
```
一个接口可以继承一个或多个接口,多个接口之间使用逗号 "," 分割。**如:**
```typescript
interface IPerson{
name: string
age: number
sex: string
}
interface IAdult extends IPerson{
isBoss: boolean
}
interface elderly extends IPerson, IAdult {
isRetire:boolean
}
```
接口的继承也就是接口的扩展,接口扩展就是多添加了一些约束,一个接口可以扩展多个接口。
## 类实现接口
在 typeScript 中,接口可以继承类,这样接口就具有了类的所有成员,同时这个接口只能引用这个类或者它的子类的实例。**如:**
```typescript
class Person{
name:string;
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
interface IAdult extends Person{
married:boolean
}
let Tom: IAdult = {
name: "Tom",
age: 20,
married: true
}
console.log(Tom);
```
## 类型别名(type)
就是“自定义类型”,为一个类型起一个新名字。
```typescript
type Name = string
type NameFn = () => string
type NameOrFn = Name | NameFn
function getName(str: NameOrFn): Name {
if (typeof str === 'string') {
return str
} else {
return str()
}
}
console.log(getName('hello world')) // 'hello world'
```
与接口类似,两者的区别在[常见问题](www.yuque.com/bugcao/bhz6…
## **内置帮助类型**
假设我们有一个人员接口,如下
```typescript
interface IInfo {
name: string; // 姓名
age: number; // 年龄
height: string; // 身高
phone: string; // 联系电话
email: string; // 邮箱
}
```
### **Partial**
- 在某些情况下,我们希望类型中的所有属性都不是必需的,只有在某些条件下才存在,我们就可以使用Partial来将已声明的类型中的所有属性标识为可选的。
```typescript
type PartialInfo = Partial
// 等价于
PartialInfo {
name?: string; // 姓名
age?: number; // 年龄
height?: string; // 身高
phone?: string; // 联系电话
email?: string; // 邮箱
}
```
### Required
与Partial相反,将所有类型改成必选项
```typescript
interface IArticle {
title?: string;
content?: string;
author?: string;
date?: string | Date;
}
type RequiredArticle = Required
// 等价于
RequiredArticle {
title: string;
content: string;
author: string;
date: string | Date;
}
```
### Omit
**Omit**类型可以从一个对象类型中 忽略某些属性
假设我们使用IInfo接口,但是不想要其中phone和email字段,就可以采用Omit,具体如下
```typescript
interface IInfo {
name: string; // 姓名
age: number; // 年龄
height: string; // 身高
phone: string; // 联系电话
email: string; // 邮箱
}
type OmitPhoneEmailInfo = Omit<IInfo, 'phone' | 'email'>
// 类型如下
OmitPhoneEmailInfo {
name: string; // 姓名
age: number; // 年龄
height: string; // 身高
}
```
### Pick
Pick类型可以从一个对象类型中 取出某些属性
假设我们使用IInfo接口,某些场景下,我们只需要其中的name和age属性,其他都不需要,当然我们可以重新再写个接口,但是这样在将来维护起来并不好,因为我们后面所有用的属性接口都应该是由IInfo接口推导出来的,那么就可以使用Pick来解决
```typescript
interface IInfo {
name: string; // 姓名
age: number; // 年龄
height: string; // 身高
phone: string; // 联系电话
email: string; // 邮箱
}
type PickNameAgeInfo = Pick<IInfo, 'name' | 'age'>
// 类型如下
PickNameAgeInfo {
name: string; // 姓名
age: number; // 年龄
}
```
# 编译器及插件推荐
## 编译器
**vscode**
## 插件
vscode插件推荐
Prettier: TypeScript格式化代码插件
# 常见问题
## 1、?(问号)操作符
### 三元运算
const a = uname**?** uname : '未知';
// 表示当uname为true时a的值是uname,否则a的值是‘未知’
### 函数参数
function getUser(user: string, **field?**: string) { }
// 这里的 ?表示这个参数 field 是一个可选参数
### 类或接口成员变量
class A {
name**?**: string
}
interface B {
name**?**: string
}
// 这里的?表示这个name属性有可能不存在
### 安全链式调用中
// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示
return new Error().stack.split('\n');
// 出错 TS2532: Object is possibly 'undefined'.
// 正确写法,可以添加?操作符,当stack属性存在时,调用 stack.split。若stack不存在,则返回空
return new Error().stack?.split('\n');
// 以上代码等同以下代码
const err = new Error();
return err.stack && err.stack.split('\n');
## 2、!(感叹号)操作符
### 一元运算符
// ! 就是将之后的结果取反,比如:
const a = !isNumber(input);
### 成员变量
interface B {
name?: string
}
class A implemented B {
name!: string
}
// 因为接口B里面name被定义为可空的值,但是实际情况是不为空的,那么我们就可以通过在class里面使用!,重新强调了name这个不为空值
### 强制链式调用
// Error对象定义的stack是可选参数,如果这样写的话编译器会提示
new Error().stack.split('\n');
// 出错 TS2532: Object is possibly 'undefined'.
// 如果确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在
new Error().stack!.split('\n');
## 3、联合类型
有些时候我们需要给变量设置多种类型,智能赋值制定类型,赋值其它类型会报错,那么就需要使用到联合类型,示例:
var val: string | number
val = 12
console.log("数字为 "+ val) //输出结果:数字为 12
val = "Runoob"
console.log("字符串为 " + val) //输出结果:字符串为 Runoob
此外,联合类型也可以用用来声明数组,例如:
var arr: number[] | string[];
arr = [1,2,4]
arr = ["Runoob","Google","Taobao"]
## 4、type 与 interface 区别
**interface**:接口,TS 设计出来主要用于定义【对象类型】,可以对【对象】的形状进行描述。
**type** :类型别名,为类型创建一个新名称。它并不是一个类型,只是一个别名。
类型别名可以起到类似接口的作用。但是,有一些细微的差别。主要区别在于 type 一旦定义就不能再添加新的属性,而 interface 总是可扩展的。
**二者的语法不通**
```typescript
// interface 声明的类型检查
interface IUser {
name: String
sayHello: () => void
sayHi(): void
}
// 类型别名声明的类型检查
type UserType = {
name: String
sayHello: () => void
sayHi(): void
}
```
**接口可以重复声明**,TS会将它们合并
```typescript
interface IUser {
name: string;
}
interface IUser {
age: number;
}
const user: IUser = {
name: 'bugcao',
age: 20
}
console.log(user) //{name: 'bugcao', age: 20}
```
如果是**type**,重复声明会报错
```typescript
type IUser {
name: string;
}
type IUser {
age: number;
}
// 报错信息:标识符“IUser”重复。ts(2300)
```
type 可以定义**基本类型**别名,如:type StringType = string
```typescript
type StringType = string;
let s: StringType;
s = "Hello";
console.log(s);
s = 123 // 报错信息:不能将类型“number”分配给类型“string”。ts(2322)
```
type 可以声明**联合类型**,如:type paramType = number | string
```typescript
type ParamType = string | number;
let param:ParamType;
param = "123"; //ok
param = 123; //ok
param = true // false
```
type 可以声明**元组类型**,如:type arrType = [string, string, number]
```typescript
type ArrType = [string, string, number];
let arr: ArrType;
arr = ['111','222',333]; //ok
arr = ['111']; // 不ok,报错信息:不能将类型“[string]”分配给类型“ArrType”。源具有 1 个元素,但目标需要 3 个。ts(2322)
arr = ['111','222','333']; //不ok,报错信息:不能将类型“string”分配给类型“number”。ts(2322)
```
**综上所述**
1、如果需要被 extends 或者 implements, 则尽量使用**接口**。
2、如果需要使用联合类型或者元组类型,使用**类型别名**。
3、如果是定义对象或函数,则都可以。
4、如果实在不想选择的话,能用**接口**实现,用**接口**,如果不能就用**类型别名**。