一、介绍
- 一种由微软开发的自由和开源的编程语言,简称 TS。
- TypeScript 是 JavaScript 的超集,即包含 JavaScript 的所有元素,能运行 JavaScript 的代码,并扩展了 JavaScript 的语法。
- 相比于 JavaScript,它还增加了 静态类型、类、模块、接口 和 类型注解 方面的功能,相对于 JavaScript,TypeScript 属于 强类型 语言,所以对于项目而言,会使代码更加规范,从而解决了大型项目代码的复杂性。 更易于大项目的开发
- TS 是不能被浏览器直接识别的,所以在编译的时候,TS 文件会先编译为 JS文件。
TypeScript 与 ES5、ES6+ 之间的关系
TypeScript 与 JavaScript 的区别
TypeScript | JavaScript |
---|---|
JavaScript的超集,用于解决大型项目的复杂性 | 一种脚本语言,用于创建动态网页 |
能在编译期间发现错误并纠正错误 | 只能在运行时发现错误 |
是强类型语言,支持静态类型和动态类型 | 是弱类型语言,没有静态类型 |
最终是被编译成JavaScript代码,使得浏览器可以理解 | 直接在浏览器使用 |
支持模块、泛型和接口 | 不支持模块、泛型和接口 |
二、安装编译环境
安装并查看版本
// 安装
npm install -g typescript
// 或者
yarn global add typescript
// 查看版本
tsc -v
创建配置文件
// tsc --init
{
"compilerOptions": {
/* Language and Environment */
"target": "es6",
/* Modules */
"rootDir": "./src",
/* Emit */
"outDir": "./dist",
/* Type Checking */
"strict": true,
}
}
创建一个ts文件
function greeter(person) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
编译
tsc greeter.ts // greeter.ts => greeter.js
三、数据类型
类型 | 例子 | 描述 |
---|---|---|
number | 1 50 1.5 | 任意数字 |
string | ‘hello world’ ‘你好’ | 任意字符串 |
boolean | true false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值 |
any | * | 任意类型 |
unknown | * | 类型安全的any |
void | 空值(undefined) | 没有值或undefined |
never | 没有值 | 不能是任何值 |
object | {id:1, name:“pengyuyan”} | 任意的JS对象 |
array | [1,2,3] | 任意js数组 |
tuple | [4,5] | 元素,TS新增类型,固定长度数组 |
enum | enum(A,B) | 枚举,TS中新增类型 |
基础类型
// number string boolean
var a: number = 1; // 添加类型注释来显式指定变量的类型
var a2 = 2; // // 不需要类型定义--'a2'推断为类型 'number'
let b: string = 'hello';
//字符串模板
let b1:string=`${b}` // "hello"
const c: boolean = true;
// symbol
// bigint
let f: symbol = Symbol(); // Symbol是es6/es2015才出现的类型
let f1:symbol = Symbol(); // Symbol是es6/es2015才出现的类型
console.log(f === f1) // false
let g: bigint = 10n // bigint是es2020新增语法
null 和 undefined
默认情况下它们是所有类型的子类型,即可以赋值给任意类型
let a: string = 'hello'
a = null //right
a = undefined // right
// 这两个类型只有 自己 null 和 undefined 两个类型一旦赋值上,就不能在赋值给任何其他类型
let d: null = null;
let e: undefined = undefined;
let d: string = 'pengyuyan' // error
当我们在 tsconfig.js 文件中设置 strictNullChecks 为 true 时,就不能将 null 和 undefined 赋值给除 它们自身 和 void 之外的任意类型了。
// "strictNullChecks": true
let b: string = 'hello'
b = null // error,不能将类型“null”分配给类型'string'。
let c: string | null = 'hi'
c = null // right
c = undefined // error,不能将类型'undefined'分配给类型'string | null'。
可选参数 (“strictNullChecks”: true)
// "strictNullChecks": true
function f(x: number, y?: number){
return x + (y || 0)
}
f(1, 2) // 3
f(1) // 1
f(1, undefined) // 1
f(1, null) // error,类型“null”的参数不能赋给类型“number | undefined”的参数。
可选属性 (“strictNullChecks”: true)
// "strictNullChecks": true
interface PositionInterface{
x: number
y?: number
}
let p: PositionInterface = {x: 10}
p.y = 'abc' // error,不能将类型“"abc"”分配给类型“number | undefined”。
p.y = null // error,不能将类型“null”分配给类型“number | undefined”。
p.y = undefined // right
引用类型
array
TS 中要求数组中的每一项必须是 同一个数据类型。
语法:
变量名: 数组中数据的类型[ ] = 数组值
变量名: Array<数组中数据的类型> = 数组值
const arr1: number[] = [1, 2, 3];
const arr2: Array<number> = [1, 2, 3];
const arr3: Array<string> = ['1', '2']
const arr4: Array<number> = [1, 2, '3'] // error 必须是同一个数据类型
//如果想要是数字类型或字符串类型,需要使用 |
const arr5: Array<number | string> = [1, 2, '3']
tuple(元祖)
- 元祖可以理解成一个 任意类型并且长度有限的数组 。元组中,允许一个数组中保存多个类型的数据。
- *注意:数组中的值与元组类型必须:数量、位置、类型都要对应
const arr5: [string, number] = ['1', 2];
let arr6: [string, number];
arr6[0] = 'pengyuyan';
arr6[1] = 18;
arr6[0].slice(1);
arr6[1].toFixed(2);
元组越界
ts允许向元组中使用数组的push方法插入新元素(但不允许访问)
const arr7: [string, number] = ['pengyuyan', 2];
arr7.push(3); // 正常运行
console.log(arr7); // 正常运行 ['pengyuyan',2,3]
console.log(arr7[2]); //访问新插入的元素会报错
object
// 方式一:
let obj:object;
// 或
let obj:{};
// object 表示一个js对象
let a :object;
a = {
name:"pengyuyan",
age:18
};
// 方式二
let b:{};
b = {
name:"pengyuyan",
age:18
}
// 定义对象时,需要定义出对象中有哪些属性,每一个属性的值是什么类型,指定的属性的个数,多一个属性也不行,少一个属性也不行
const obj: { age: number, name: string } = { age: 18, name: 'pengyuyan' };
// 如果想指定属性是可选的(可有可无)
const obj:{
name:string,
age?:number
}
obj = {
name:"pengyuyan"
}
function
声明的函数在调用的时候,参数个数 要和 声明时候的参数 个数 保持一致
//没有返回值的函数可以用void声明
// 参数类型定义
const f1 = (name:string,id:number): void => {
console.log("我是没有返回值的箭头函数");
};
function f2(name:string,id:number):void{
console.log("我是没有返回值的普通函数");
}
//有返回值的箭头函数声明是这样的
// 返回类型注释
const f3 = (): string => {
return "pengyuyan=>"
};
//有返回值的普通函数声明是这样的
function f4():string{
return "pengyuyan"
}
//函数表达式的双向限定
//上述f1其实只对=右侧做了限制,对左侧并没有
//完善一点,可以这样 => 用来表示函数的定义,左输入类型,需要用括号括起来,右输出类型
const f5:(name:string,id:number)=>void = (name:string,id:number): void => {
console.log("我是没有返回值的箭头函数");
};
// 函数的可选参数
// 注意可选参数要在确定参数后
function f6(name:string,id?:number):string{
return "pengyuyan"
}
//函数参数默认值
function f7(name:string,id:number=1):string{
return `${name}--${age}`
}
//此时可选参数不必一定在确定参数后,但是调用有问题
function f8(name:string,sex?:string,age:number=1):string{
return `${name}--${age}--${sex}`
}
console.log(f8('pengyuyan','male',18)) // "pengyuyan--18--male"
//剩余参数
function f9(...arr:number[]):number[]{
return arr
}
console.log(f9(1,2,3,4,5)) // [1,2,3,4,5]
特殊类型
any
可以访问它的任何属性,可以将它分配给赋予 任意类型 的值
let a; // 声明变量不赋值,等效于 let a: any;
// a 的类型不确定,ts会自动将其定义为隐式 any ,能够变成多种类型
a = 123; // 未声明类型的变量虽然一开始被识别为 any 类型,但是经过赋值后,TS 会根据赋值类型来标识变量的类型
a = "pyy"
a = true
console.log("这是a: ", a) // "这是a: ", true
// 定义的any类型,类型可以多次改变,可以给任意类型赋值, 也可以被任意类型赋值,还能赋值给其他类型的变量
let a1: any;
a1 = 123;
a1 = "pyy";
a1 = false;
a1 = a;
console.log("这是a1: ", a1) // "这是a1: ", true
let b: string = "pyy";
b = a1;
console.log("这是b: ", b) // "这是b: ", true
unknown
未知的类型,TS中所有基础类型的父类型,所有基础类型都能赋值为 unknown类型。但 **unknown ** 是无法给 ** unknown **之外 的(包括 any)其他类型变量赋值的
// 接上文any的例子
let c1: unknown;
c1 = "wyz&pyy";
c1 = c;
console.log("这是c1: ", c1) // "这是c1: ", true
c1 = b;
console.log("这是c1: ", c1) // "这是c1: ", true
unknown类型直接赋值给其他变量的方法
typeof 进行类型判断
let num1: unknown = 666;
if(typeof num1 === "number") {
let num2 = num1
}
类型断言
let num1: unknown = 666;
let num2 = num1 as number);
// 或
let num3 = <number>num1;
any 和 unkown 的区别
unknown类型会更加严格:在对 unknown类型的值执行大多数操作之前,不能直接赋值给其他变量,必须将这个 unknown类型的变量断言为具体的类型,才可以继续使用。
而在对 any类型的值执行操作之前,会绕过类型检查,直接可用。
let foo: any = 123;
console.log(foo.msg); // 符合TS的语法
let value1: unknown = foo; // OK
let value2: any = foo; // OK
let value3: string = foo; // OK
let bar: unknown = 456; // OK
console.log(bar.msg); // Error
let value4: unknown = bar; // OK
let value5: any = bar; // OK
let value6: string = bar; // Error
因为bar是一个未知类型(任何类型的数据都可以赋给 unknown 类型),所以不能确定是否有msg属性。不能通过TS语法检测;而 unkown 类型的值也不能将值赋给 any 和 unkown 之外的类型变量
总结: any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量
never 类型
在 TS中使用 never类型来表示不应该存在的值的类型,例如:
- 一个抛出异常的函数
- 一个永远不会返回的函数的返回值类型
*注意:never 类型是任何类型的子类型,可以赋值给 任意类型。但是没有类型是 never类型的子类型,即使是 any类型 也不能 赋值给 never 类型
const a:never;
a = 123 // error
a = (()=>{
throw new Error('错误')
})
const fn: ()=>never = () => {
throw new Error('error')
}
const [n,setN] = React.useState<number>(1)
const add: ()=>never = () => {
while(true){ setN(i=>i+1) }
}
Enum(枚举)
可以定义带名字的 常量
- 枚举的类型 只能 是 string 或 number
- 定义的名称不能为 关键字
按枚举成员分类
数字枚举
*注意:
- 是 数字类型
- 如果有 默认值,会影响到后面的值
- 支持 反向映射
enum Direction {
Up = 1, // 1
Down, // 2
Left, // 3
Right // 4
}
enum Direction {
Up, // 0
Down = 3, // 3
Left, // 4
Right // 5
}
// 如上,我们定义了一个数字枚举, Up使用初始化为 1。 其余的成员会从 1开始自动增长。
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 当我们不在乎成员的值的时候,这种自增长的行为是很有用处的,但是要注意每个枚举成员的值都是不同的。
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
const date: { id: number, name: string, days: Days } = { id: 1, name: '周三', days: Days.Wed }
console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true
// 如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的,但建议尽量避免
字符串枚举
在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化
*注意:
- 必须要有 默认值
- 不 支持 反向映射
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
const a: Direction = Direction.Up //a = UP
const b: Direction = Direction.Down //b = DOWN
// 由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。
// 字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。
异构枚举 (不建议)
将 数字 枚举与 字符串 枚举 混用,不建议
反向映射
除了创建一个以 属性名 做为对象成员的对象之外,数字枚举成员(字符串枚举成员没有反向映射)还具有了 反向映射,从枚举值到枚举名字
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
按声明方式
普通枚举
enum Color {Red, Green, Blue = "blue".length};
// 值由计算所得变为计算所得项 如,"blue".length 就是一个计算所得项
enum Color {Red = "red".length, Green, Blue};
// 上述代码会报错,因为Red是计算项,而Green紧接其后却无法获取初始值.
// 根据官方定义,不带初始化器的枚举要么被放在第一的位置,要么被放在使用了数字常量或其它常量初始化了的枚举后面。
常量枚举 (通过const enum 定义的枚举)
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
外部枚举
用来描述 已经存在的枚举类型,当前环境中 已经存在的对象 的,这个对象可以存在 任意 的地方,但是一定是 已声明 的。
*注意:不支持 反向映射
参考:https://stackoverflow.com/questions/28818849/how-do-the-different-enum-variants-work-in-typescript
declare enum Enum {
A = 1,
B,
C = 2
}
使用枚举
- 通过 枚举的属性 来访问 枚举成员
- 通过 枚举的名字 来访问 枚举类型
enum Response {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: Response): void {
// ...
}
respond("Princess Caroline", Response.Yes)
void
- 可以用来声明变量,但只能作用在 undefined 身上,null 也不可以。只有 tsconfig.json 中 strictNullChecks 属性设置为 false 时,null 才能赋值给 void。
- 一般用于当一个函数 没有返回值时,TS 会默认他的返回值是 void 类型
const a = ():void => {} // 等价于 const a = () => {}
const b = ():void => { return 1 } // error
const c = ():void => { return '2' } // error
const d = ():void => { return true } // error
const e = ():void => { return } // ok
const f = ():void => { return undefined } //ok
const g:void = 1 // error
const h:void = undefined // error
其他类型
联合类型 |
由两个或多个其他类型组成的类型,表示可能是这些类 型中的任何一种的值。我们将这些类型中的每一种称为联合类型的成员。
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
printId(101); // ok
printId("202"); // ok
printId({ myID: 22342 }); // error
在联合类型中,unknown 类型会吸收 任何 类型。联合类型中有 unknown,那么最终得到的都是 unknown 类型
type a = unknown | null; // unknown
type b = unknown | undefined; // unknown
type c = unknown | string; // unknown
type d = unknown | number[]; // unknown
// 如果至少一种组成类型是 any,联合类型会相当于 any
type e = unknown | any; // any
基础类型联合
let a: string | number;
a = 1; //ok
a= "a"//ok
对象类型联合
只能访问联合中 所有共同成员
interface Women{
age: number,
sex: string,
cry(): void
}
interface Man{
age: number,
sex: string,
}
declare function People(): Women | Man;
let people = People();
people.age = 18; // ok
people.cry();// error 非共同成员
交叉类型 &
多种类型的集合。每种类型都可以赋值给 unknown 类型,所以在交叉类型中包含 unknown 不会改变结果
type u1 = unknown & null; // null
type u2 = unknown & undefined; // undefined
type u3 = unknown & string; // string
type u4 = unknown & number[]; // number[]
type u5 = unknown & any; // any
interface People {
age: number
}
interface Man{
sex: string
}
const pengyuyan = (man: People & Man) => {
console.log(man.age) // 18
console.log(man.sex) // male
}
pengyuyan({age: 18,sex: 'male'})
同名基础属性合并
type obj = { a: string, c: number }
type obj2 = { b: number, c: string }
type allObj = obj & obj2
const Info: allObj = {
a: 'hello world',
b: 7,
// c 的类型既可以是 string 类型又可以是 number 类型。很明显这种类型是不存在的,所以混入后 c 的类型为 never
c: 1, // Type 'number' is not assignable to type 'never' c: never
c: 'hello', // Type 'string' is not assignable to type 'never' c: never
}
同名非基础属性合并
interface obj3 { a: number }
interface obj4 { b: string }
interface A {
x: obj3
}
interface B {
x: obj4
}
type obj5 = A & B
const Info: obj5 = {
x: {
a: 1,
b: 'hello'
}
}
console.log(Info) // { x: { "a": 1, "b": "hello" }}
四、interface 和 type 的区别
interface
- 使用 interface 可以定义一个 接口 类型,能 合并 多个类型声明至 一个类型声明。
- 接口声明只存在于 编译 阶段,在编译后生成的 JS 代码中不包含任何接口代码
interface obj1 {
a: number
b: string
}
interface fn1 {
() => void
}
type
type 除了能声明 对象、函数,还可以为 基础类型 声明 别名
type A = number; // 基本类型
// type:声明类型别名的关键字
// A:类型别名的名称
// Type:类型别名关联的具体类型
let num: test = 10;
type Ojb = {name:string} // 对象
type fun = ()=>string // 函数
type Tdata = [number,string] // 元组
type numOrStr = number | string // 联合类型
不同点
- type 在声明类型别名后,将 别名 与 类型 关联了起来,也就是说类型别名不会创建出一种新的类型,只是给已有类型命名并直接引用。而 interface 是定义了一个 接口 类型。
- type 表示 非对象 类型, 而 interface 表示 对象 类型。
- interface 可以 继承 其他的 接口、类 等 对象 类型, 严格来说 type 不 支持 继承。
- interface 可以 声明合并,而 type 不可以,这也意味着我们可以通过声明合并的方式给 interface 定义的类型进行属性扩展。
- type 可以通过 typeof 来获取实例的类型从而进行赋值操作
相同点
都可以用来定义 对象 或者 函数 的结构,而严谨的来说,type 是 引用,而 interface 是 定义
五、Class(类)
基本方法
在基本方法中有:静态属性、静态方法、成员属性、成员方法、构造器、get、set 方法
// 类定义方式如下
class obj{
// 类作用域
static myName: string = "pengyuyan" // 静态属性
name1:string = "男"
engine:string
engine3!:string //*在成员属性中,如果不给默认值,并且不使用是会报错的 。不设置默认值的时候加 ! 就不会报错 engine3!:string
// 构造函数
constructor(engine:string) {
this.engine = engine
}
// 静态方法
static staticFn = ()=>{
return '静态方法'
}
// 成员方法
fn2 = () =>{
return '成员方法'
}
// get方法
get engine2(){
return this.engine
}
// set方法
set engine2(engine2){
this.engine = engine2
}
}
const setObj = new obj('hello')
console.log(obj.myName) // "pengyuyan"
console.log(obj.staticFn()) // "静态方法"
console.log(setObj.fn2()) // "成员方法"
console.log(setObj.engine2) // "hello"
console.log(setObj.engine) // "hello"
// *set方法和get方法时的错误提示:
// error TS1056: Accessors are only available when targeting ECMAScript 5 and higher
// 解决方法:需要编译到 es5 及更高版本时可用
tsc 项目名称 -t es5
tsc Class(类).ts -t es5
// 类定义方式如下
var obj = /** @class */ (function () {
// 构造函数
function obj(engine) {
// 成员属性
this.name1 = "男";
// 成员方法
this.fn2 = function () {
return '成员方法';
};
this.engine = engine;
}
Object.defineProperty(obj.prototype, "engine2", {
// get方法
get: function () {
return this.engine;
},
// set方法
set: function (engine2) {
this.engine = engine2;
},
enumerable: false,
configurable: true
});
// 类作用域
obj.myName = "pengyuyan"; // 静态属性
// 静态方法
obj.staticFn = function () {
return '静态方法';
};
return obj;
}());
var setObj = new obj('hello');
console.log(obj.myName); // "pengyuyan"
console.log(obj.staticFn()); // "静态方法"
console.log(setObj.fn2()); // "成员方法"
console.log(setObj.engine2); // "hello"
console.log(setObj.engine); // "hello"
只读属性 readonly
- 只能在 构造函数 中初始化,并且在 TS 中,只允许将 interface、type、class上的属性标识为 readonly
- readonly 实际上只是在 编译阶段 进行代码检查。
- 被 readonly 修饰的词只能在 constructor 阶段修改,其他时刻不允许修改。
class Person {
public readonly name:string; // 字符串 只读
name2:string
constructor(name:string){
this.name = name
this.name2 = name
}
setName(name:string) {
this.name = name // Cannot assign to 'name' because it is a read-only property.
this.name2 = name; // ok
}
}
const name3 = new Person('pengyuyan')
console.log(name3) // Person: {"name": "pengyuyan", "name2": "pengyuyan"}
console.log(name3.name2) // pengyuyan
console.log(name3.name) // pengyuyan
继承 extends
- 继承之后,子类会拥有父类的一切 属性 和 方法
- 子类也可以自己定义一些方法,如 getTel() 方法
- 子类也可以写与父类相同的方法,这样执行方法的时候会执行子类的方法,叫做 方法重写( Child子类中 重写了sayName() 方法)
// 父类
class Person {
name: string
age: number
constructor(name: string, age:number){
this.name = name
this.age = age
}
getName(){
console.log(`name是:${this.name}`)
return this.name
}
setName(name: string){
console.log(`设置name为:${name}`)
this.name = name
}
sayName(){
console.log(`哈哈哈,我是${this.name}`)
}
}
// 子类
class Child extends Person {
tel: number
// 如果在子类中写了构造函数,就必须调用父类的构造函数
constructor(name: string, age: number, tel:number){
super(name, age) // super用在子类中,表示当前的父类
this.tel = tel
}
// 子类也可以自己定义一些方法
getTel(){
return this.tel
}
sayName(){
// 类的方法中super就表示父类,可以通过super.(父类的方法)调用父类的方法
super.sayName()
console.log("Child")
}
}
let res = new Child("pengyuyan", 18 , 133123456789)
res.setName('pengyuyan') // 设置name为:pengyuyan
console.log(res) // Child {."name": "pengyuyan", "age": 18, "tel": 133123456789 }
console.log(res.age) // 18
console.log(res.getTel()) // 133123456789
res.sayName()
res.getName()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qAZwCqZA-1661916600109)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/feff259796cb47029929efa7841c5e26~tplv-k3u1fbpfcp-zoom-1.image)]
修饰符
- public:类中、子类内的任何地方、外部 **都能调用 **
- protected:类中、子类内的任何地方都能调用,但 外部不能调用
- private:类中可以调用,子类内的任何地方、外部 均不可调用
class Person {
public name: string // 类中、子类内的任何地方、外部 都能调用
protected age: number // 类中、子类内的任何地方都能调用,但外部不能调用
private tel: number // 类中可以调用,子类内的任何地方、外部都不可调用
constructor(name: string, age:number, tel: number){
this.name = name
this.age = age
this.tel = tel
}
}
class Child extends Person {
constructor(name: string, age: number, tel: number) {
super(name, age, tel);
}
getName(){
console.log(`我的name是${this.name},年龄${this.age}`) // "我的name是pengyuyan,年龄18"
console.log(`电话是${this.tel}`) // 类中可以调用,子类内的任何地方、外部都不可调用
console.log(`年龄是${this.age}`) // 类中可以调用 "年龄是18"
}
}
const res = new Child('pengyuyan', 18, 133123456789)
console.log(res.name) // Domesy
console.log(res.age) // 类中、子类内的任何地方都能调用,但外部不能调用
console.log(res.tel) // 类中可以调用,子类内的任何地方、外部都不可调用
res.getName()
重写和重载
重写
继承父类之后 重写父类 的方法
class Person{
setName(name: string){
return `我的名字是${name}`
}
}
class Child extends Person{
setName(name: string){
return `我的名字是${name}`
}
}
const newInfo = new Child()
console.log(newInfo.setName('pengyuyan')) // "我的名字是pengyuyan"
重载
class double{
setNum(num: string);
setNum(num: number);
setNum(num:string | number){
if(typeof num === 'string'){
console.log(num + num)
}else{
console.log(num*2)
}
};
}
const res = new double()
res.setNum('1') // "11"
res.setNum(1) // 2
abstract
用 abstrac 关键字来声明一个 抽象类,抽象类中的方法声明的方法叫做 抽象方法。抽象类 不能被直接实例化,只能 被子类继承,而且需要重新实现抽象类中的抽象方法。abstract 还可以修饰 属性 和 存取器(get、set)
abstract class Person {
constructor(public str: string){}
// 抽象类中的方法也必须是抽象方法
abstract numMethod(id: number) :void;
}
class Child extends Person {
constructor(str: string) {
super(str);
this.str = str
}
// 抽象类中的函数
numMethod(id: number): void {
console.log(`str 是${this.str},id 是${id}`);
}
}
let a = new Person("pengyuyan") // Cannot create an instance of an abstract class
let b = new Child("pengyuyan");
b.numMethod(7) // str 是pengyuyan,id 是7
六、断言
类型断言
可以手动指定一个值的类型,不需要 ts 去判断。
*注意:在 tsx 中必须使用 值 as 类型 的语法。
// 值 as 类型
let str: any = 'pengyuyan';
let num: number = (str as string).length;
// <类型>值
let str2:any = 'pengyuyan'
let num2: number = (<string>str2).length; // react中会报错
将任何一个类型断言为 any ( 不能滥用 as any)
let obj:object = {};
obj.num = 1; // error obj并没有num属性,所以就需要将 obj 断言为any
// 改为
(obj as any).num = 1;
// *注意:不能滥用 as any
将any断言为一个具体的类型
let obj:object = {};
function fn1(key: string): any {
return (obj as any).name = key;
}
// fn1()方法执行以后返回的是any类型
interface Animal {
name:string;
fn2():void;
}
let a = fn1("pengyuyan") as Animal;
// 将其断言为Animal类型
将一个联合类型断言为其中一个类型
*注意:类型断言只是欺骗编译器,让编译器可以编译通过,但是如果强制类型转换,在 执行 的过程中会报错
interface obj1 {
name: string;
fn1(): void;
}
interface obj2 {
name: string;
fn2(): void;
}
function isObj(obj3: obj1 | obj2):string {
return obj3.name // 只能访问联合属性中共有的属性和方法
// 断言,强制类型转换会报错
return (typeof (obj3 as obj2).fn2) === 'function')
}
const obj4:obj1 = {
name:"obj1",
fn1(){
console.log("fn1xxxxx");
}
}
function fn3(animal:obj1|obj2){
(animal as obj2).fn2();
}
fn3(obj4); // animal.fn2 is not a function
将一个父类断言为更加具体的子类
//类有继承关系
class Father extends Error { //抽象的父类 Error,这样这个函数就能接受 Error 或它的子类作为参数了
num1: number = 1;
}
class Child extends Error {
num2: number = 2;
}
function fnType (error: Error) {
// 父类 Error 中没有 num1 属性,所以需要使用类型断言获取
if (typeof (error as Father).num1 === 'number') {
return true;
}
return false;
}
非空断言
在上下文中当类型检查器 无法断定类型 时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是 非 null 和 非 undefined 类型
// 忽略 undefined 和 null 类型
function fn(name: string | undefined | null) {
const youName: string = name; // Error
const myName: string = name!; // right
}
// 调用方法时忽略 undefined 类型
let myName:string = 'pengyuyan'
console.log(myName.trim()) // 为undefined时候报错
// 改为
console.log(myName!.trim())
类型断言 和 类型转换的区别
function toBoolean(something: any): boolean {
return something as boolean;
}
toBoolean(1);
// 返回值为 1
而类型转换则会影响编译结果:
function toBoolean(something: any): boolean {
return Boolean(something);
}
toBoolean(1);
// 返回值为 true
七、类型保护
function f(sn: string | null): string{
if(sn === null){
return 'null'
} else {
return sn
}
}
// #或者 使用运算符
function f(sn: string | null): string{
return sn || 'null'
}
interface Bird {
fly: boolean;
sing: () => {};
}
interface Dog {
fly: boolean;
bark: () => {};
}
function trainAnimal(animal: Bird | Dog){
if(animal.fly){
(animal as Bird).sing()
}else {
(animal as Dog).bark()
}
}
typeof
用来确定 变量的类型。
Number、String、Bigint、Symbol、Undefined、Function、Boolean
除此以外,typeof 只返回 object
const StudentId = (data: number | string | undefined ) => {
if(typeof data === "string"){
console.log(`我是字符串:${data}`)
}
if(typeof data === "number"){
console.log(`我是数字:${data}`)
}
if(typeof data === "undefined"){
console.log(data)
}
if(typeof data !== "object"){
console.log('我不是object')
}
}
StudentId('pengyuyan') // 我是字符串:pengyuyan
StudentId(100) // 我是数字:100
StudentId(undefined) // undefined
function add(first: string | number, second: string | number){
if(typeof first === 'string' || second === 'string'){
return `${first}${second}`
}else {
return false
}
}
console.log(add(1,'2')) // false
instanceof
检查一个值是否是 给定构造函数 或 类 的实例
class NumberObj{
count: number = 1
}
class NumberSecondObj{
count: number = 1
}
function addSecond (first : object | NumberObj, second: object | NumberSecondObj){
if(first instanceof NumberObj && second instanceof NumberSecondObj){
return first.count + second.count
}
return 0
}
console.log(addSecond(new NumberObj(),new NumberSecondObj())) // 2
in
interface Bird {
fly: boolean;
sing: () => {};
}
interface Dog {
fly: boolean;
bark: () => {};
}
function trainAnimalSecond(animal: Bird | Dog){
if('sing' in animal){
animal.sing()
}else {
animal.bark()
}
}
console.log({fly:true,sing:() => {}})
接口
在面向对象语言中表示 行为抽象,也可以用来描述 对象的形状。
使用 interface 关键字来定义接口
对象的形状
接口可以用来描述 对象,包括以下数据:可读属性、只读属性、任意属性
- 可读属性:当我们定义一个接口时,我们的属性可能 不全都要,这是就需要 **? ** 来解决
- 只读属性:用 readonly 修饰的属性为 只读属性,意思是指 允许定义,不允许之后进行更改
- 任意属性:可以用作 没有定义,也可以使用,比如 [data: string]: any 。比如说我们对组件进行封装,而封装的那个组件并没有导出对应的类型,然而又想让他不报错,这时就可以使用 任意属性
// 使用 interface 关键字来定义接口
interface Info {
name: string;
age: number;
bool: boolean;
realAge?: number; // 可选属性、代表属性可用或不用
readonly readType: string; //只读属性
[anyType: string]: any //任意属性
}
let res: Info = {
name: 'pengyuyan',
age: 18,
bool: true,
realAge:20, // 可选
readType: 'peng', // 只读
anyType: 2 // 任意
}
res.readType = 'Bai' // error, 只读属性不可更改
继承
interface Parent {
name: string
}
interface Child extends Parent{
id: number
}
const info: Child = {
name: 'pengyuyan',
id: 0
}
泛型
在定义 函数、接口 或 类 的时候,不预先指定具体的类型,而在 使用的时候再指定类型 的一种特性,使 函数,接口 或 类 更加通用
在函数中使用
// T:数据类型,也可以换成任意字符串。(占位符,<A>|<B> 也可以)
function test <T> (arg:T):T{
console.log(arg);
return arg;
}
test<number>(111);// 返回值是number类型的 111
test<string | boolean>('hahaha')//返回值是string类型的 hahaha
test<string | boolean>(true);//返回值是布尔类型的 true
在接口中使用
// 定义方法
interface Search {
<T,Y>(name:T,age:Y):T
}
// <泛型变量名称>(参数1: 泛型变量, 参数2: 泛型变量, ...参数n: 泛型变量) => 泛型变量
let fn:Search = function <T, Y>(name: T, id:Y):T {
console.log(name, id)
return name;
}
fn('li',11);//编译器会自动识别传入的参数,将传入的参数的类型认为是泛型指定的类型
在类中使用
class Animal<T> {
name:T;
constructor(name: T){
this.name = name;
}
action<T>(say:T) {
console.log(say)
}
}
let cat = new Animal('cat');
cat.action('mimi')
泛型约束
interface isArray {
id: number
}
function logIndex<T extends isArray>(arg: T): void {
for (let i = 0; i < arg.length; ++i) {
console.log(arg[i])
}
}
let arr = [1, 2, 3, 4]
logIndex<number>(arr) // error
logIndex<number[]>(arr) // ok
logIndex(arr) // ok
泛型工具类
Partial
将某个类型中的属性全部改为 可选项 ?
// 语法
type Partial<T> = {
[P in keyof T]?: T[P]; // keyof,即 索引类型查询操作符,我们可以将 keyof 作用于泛型 T 上来获取泛型 T 上的所有 public 属性名构成的 联合类型
};
interface User {
name: string,
age: number
}
type PartialUser = Partial<User>
// 相当于: type PartialUser = { name?: string; age?: number;}
Required
将所有属性改为 必选 的,与 Partial 相反
// 语法
type Require<T> = {
[p in keyof T]-?: T[P]
}
interface Props {
name: string,
age: number,
isMale?: boolean
}
const info: Props = {
name: 'pengyuyan',
age: 18
}
const info1: Required<Props> = {
name: 'pengyuyan',
age: 18,
isMale: true
}
Record
将给定的 对象属性名 类型 和 对象属性 类型,创建一个 新的对象类型
// 语法
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type a = 'x' | 'y';
type b = number;
type c = Record<K, T>; // {x:number; y:number}}
const d: c = { x: 0, y: 0 }
interface PageInfo {
title: string
}
type Info = 'a'|'b'|'c';
const e: Record<Info, RecordInfo> = {
a: { title: "aaa" },
b: { title: "bbb" },
c: { title: "ccc" },
};
Readonly
可以构造一个 新的类型, 并将类型参数 T 中的 所有属性 变成 只读属性
// 语法
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Info {
name: string
age: number
}
let readInfo: Readonly<Info> = {
name: 'pengyuyan',
age: 18
}
readInfo.age = 19 //error 无法分配到 "age", 是只读属性
// 或者
type T = Readonly<Info>; // {name:string; age:number}}
const a: T = { name: 'pengyuyan', age: 18 }
// a.name = 'wuyanzu' 无法分配到 "name" ,因为它是只读属性
// a.age = 20 无法分配到 "age" ,因为它是只读属性
Pick
从一个 复合类型 中,取出几个想要的 类型的组合
// 语法
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface Todo {
name: string
description: string
completed: boolean
}
type TodoPreview = Pick<Todo, 'name' | 'completed'>
// 相当于: type TodoPreview = { name: string; completed: boolean; }
const todo: TodoPreview = {
name: 'pengyuyan',
completed: false,
}
Exclude
将 某个类型中 属于另一个类型的属性** 移除 掉
// 语法
type Exclude<T, U> = T extends U ? never : T;
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
const t:T0 ='b';
ReturnType
获取 函数 T 的返回类型
// 语法
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T4 = ReturnType<any>; // any
type T5 = ReturnType<never>; // any
type T6 = ReturnType<string>; // Error
type T7 = ReturnType<Function>; // Error
NonNullable
从类型 T 中剔除 null 和 undefined 类型,并构建一个 新的类型
// 语法
type NonNullable<T> = T extends null | undefined ? never : T;
type T0 = NonNullable<string | number | null | undefined>; // string | number
Extract
和 Exclude 类型互补, 它能从类型 T 中获取所有可以赋值给类型 U 的类型
// 语法
type Extract<T, U> = T extends U ? T : never;
type T0 = Extract<"a" | "b", 'a'>; // "a"
type T1 = Extract<string | (() => void), Function>; // () => void
type T2 = Extract<"a" | "b", 'c'>; // never
欢迎留言指正
学习资源:TypeScript 入门教程 一篇让你完全够用TS的指南
学习的代码整理好会同步到 gitlab