? ? ? ? 目前在做项目的技术栈是 react+typescript,之前只知道 ts 是 js 的扩展,增加了类型检查,但是没有仔细的学过,纯纯看别人代码上手 anyscript(这很难评...)。趁着最近空闲,就学习一下 ts 的基础知识,此篇笔记只包含基础知识,内容并不深入,仅供熟悉 js 的 ts 新手参考。
? ? ? ? 这边提供 tsc 和 ts-node 两种方式:(1)tsc 是一个编译器,把 ts 编译为 js。只编译。(2)ts-node 是一个执行环境,把 ts 编译为 js ,然后在node上运行。即:编译+执行。
????????(1)下载 Node.js 并安装(不详细赘述,百度就行)
????????(2)使用 npm 全局安装 typescript:npm i -g typescript
????????(3)检查 tsc 编译器是否安装成功:tsc -v
????????如果出现版本号则安装成功;如果报错“tsc不是内部命令”,则需要添加环境变量,具体步骤参考:typeScript安装及TypeScript tsc 不是内部或外部命令,也不是可运行的程序或批处理文件解决办法_安装tsc(程序)-CSDN博客
????????(4)创建一个 ts 文件并用 tsc 编译器进行编译,进入?ts 文件所在目录的命令行,执行命令:tsc?xxx.ts
????????(1)下载 Node.js 并安装(不详细赘述,百度就行)
????????(2)使用 npm 全局安装 typescript:npm i -g typescript
? ? ? ? (3)安装 ts-node:npm install -g ts-node
? ? ? ? (4)ts-node需要依赖 tslib 和 @types/node 两个包:npm install tslib @types/node -g?
? ? ? ? (4)创建一个 ts 文件 xxx.ts ,并执行命令:ts-node xxx.ts
let 变量: 类型
let 变量: 类型 = 值
function fn(参数: 类型, 参数: 类型): 类型 {
}
2.1 数组类型 array
//====== 语法 ====== //(1)类型+方括号 let arr1: number[] = [1, 2, 3, 4, 5]; //(2)使用数组泛型 Array<elemType> let arr2: Array<number> = [1, 2, 3, 4, 5];
2.2 对象类型 object
? ? ? ? 对象有很多种类,{}是对象,函数也是对象,因此直接定义类型为 object 是没有意义的。于对象而言,我们希望能够指定它是“怎样”的对象,而非“它是对象”。
(1)基本用法
? ? ? ? 赋值的时候,定义的变量与对象的属性必须完全一致,不能多也不能少。
let person: { name: string, age: number }; person = { name: 'Tom', age: 18, }
(2)设置可选属性
// 1. 设置可选属性:sex属性可有可无 let person1: { name: string, age: number, sex?: string }; person1 = { name: 'Tom', age: 18, sex: 'male' }
(3)设置任意属性
// 2. 设置任意属性:除name属性必须存在,其他属性可任意添加 let person2: {name: string, [propName: string]: any} person2 = { name: 'Tom', age: 18, sex: 'male', }
????????一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集。
let person3: { name: string, age: number, // 以下语句报错:类型“number”的属性“age”不能赋给“string”索引类型“string” [propName: string]: string } // 以下定义正确: let person3: { name: string, age: number, [propName: string]: number | string }
2.3 函数 function
?????????在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):
// 函数声明(Function Declaration) function sum(x, y) { return x + y; } // 函数表达式(Function Expression) let mySum = function (x, y) { return x + y; }; // ES6 -> 箭头函数 let mySum2 = (x, y) => { return x + y } let mySum3 = (x, y) => x + y
????????一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,如下:
// 函数声明(Function Declaration) function sum(x: number, y: number): number { return x + y; } // 函数表达式(Function Expression) // 以下类型定义是可以通过编译的 // 但代码实际上只对等号右侧的匿名函数进行了类型定义,而等号 /** * 以下类型定义是可以通过编译的,但代码实际上只对等号右侧的匿名函数进行了类型定义, * 而等号左边 mySum 的类型是类型推论而来,我们也可以手动添加类型,如 mySum4; * 以下的箭头函数同理; */ let mySum = function (x: number, y: number): number { return x + y; }; let mySum4: (x: number, y: number) => number = function (x: number, y: number): number { return x + y; }; // ES6 -> 箭头函数 let mySum2: (x: number, y: number) => number = (x: number, y: number): number => { return x + y }
2.4 元组 tuple?
? ? ? ? 固定长度的数组,可为每一个元素都指定类型。
let tom: [string, number] = ['Tom', 25];
2.5 枚举? enum
????????枚举(Enum)类型用于取值被限定在一定范围内的场景。
????????枚举使用?
enum
?关键字来定义,枚举成员会被赋值为从?0
?开始递增的数字。当然也可以手动赋值,但是如果未手动赋值的枚举项与手动赋值的重复了,ts 并不会检查出来而导致出错,因此使用时要注意不要出现重复的情况。????????例如我们定义性别:female为0,male为1。存储数字比存储字母更合适,但是我们又想直观的看到我们取的选项是什么,新增的枚举类是一个不错的表示方式。(目前在我做的项目中,固定的枚举值一般通过定义对象的方式,而动态的枚举值通过后端接口返回的数据渲染,这个类型我还没用到过)。
// 简单的例子 enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; enum Person {'Tom', 'Judy', 'Jay'} const beToDuty: {day: Days, name: Person} = { day: Days.Mon, // 1 name: Person.Tom // 0 } // 给枚举项手动赋值,未手动赋值的枚举项会接着上一个枚举项递增 // Sun-7 Tue-2 Mon-1 Wed-2 Thu-3 Fri-4 Sat-5 enum Days {Sun = 7, Tue = 2, Mon = 1, Wed, Thu, Fri, Sat};
Tip1:any 和 unknown 类型的区别
(1)any 类型可以赋值给任意类型。
(2)unknown 类型的变量,不能直接赋值给其他变量。若要赋值,可使用“类型断言”。
let a: string; let b: any; let c: unknown; a = 'test'; b = 1; c = 1; a = b; // any 类型可以赋值给任意类型而不进行检查,不安全 a = c; // 报错:不能将类型“unknown”分配给类型“string” // 类型断言 a = <string>c a = c as string
Tip2:类型断言
????????类型断言(Type Assertion)可以用来手动指定一个值的类型。
? ? ? ? 类型断言只是为了“欺骗”编译器,使其在编译阶段不报错,它并没有对变量进行类型转换。如若使用不当,依旧可能在运行时报错。
? ? ? ? 语法:(1)值 as 类型? (2)<类型>值。【在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用(1)。】
Tip3:void 与 never 的区别
(1)JavaScript 没有空值(?
void
?)的概念,在 TypeScript 中,可以用?void
?表示没有任何返回值的函数(或者返回 undefined 与 null,但这没什么意义),如下:function fn1(): void { // return // return undefined // return null // vscode中会报错:不能将类型“null”分配给类型“void” }
(2)
never
?表示永远不会返回结果。通常情况下,never
?用于指示出现异常或无限循环的函数,或者表明函数总是会抛出异常或终止程序的运行。// 抛出异常 function throwError(message: string): never { throw new Error(message); } // 无限循环 function infiniteLoop(): never { while (true) { } }
????????如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
????????当变量的声明和赋值同时进行时,ts 编译器会自动判断变量的类型,此时可以省略掉类型声明。如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查。
// 报错
let myFavoriteNumber = 'seven'; // 推断成 string 类型
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
let myFavoriteNumber; // 推断成 any 类型
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
????????联合类型(Union Types)表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number; // 可以为 string 或 number 类型
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
let sex: 'male' | 'female'; // 字面量类型,sex 只能取“male”和“female”
sex = 'male';
sex = 'female';
sex = 'other' // 报错:不能将类型“"other"”分配给类型“"male" | "female"”
? ? ? ? 我目前项目的技术栈是?react+ts,都是用的函数式编程,比较少接触到类,主要是想学习一下接口。但确实是比较重要的概念还是需要学习一下。
? ? ? ? 面向对象是程序中一个非常重要的思想,简而言之就是程序之中所有的操作都需要通过对象来完成,在程序中一切皆是对象。例如:
? ? ? ? 一切操作都要通过对象,这就是所谓的面向对象。
? ? ? ? 对象是什么?计算机程序的本质就是对现实事物的抽象,我们可以将具体的事物,比如人、汽车等等抽象为一个对象。程序也是对事物的抽象,一个事物到了程序中就变成一个对象。
? ? ? ? 程序中所有的对象都被分成两个部分:数据和功能。例如人这个对象包含:[ 数据:姓名、年龄、身高等等 ]、[ 功能:说话、吃饭等等 ]。数据在对象中被称为属性,而功能被称为方法。
参考:https://ts.xcatliu.com/advanced/class.html
ES6中的类:ES6 入门教程?
new
?生成;Cat
?和?Dog
?都继承自?Animal
,但是分别实现了自己的?eat
?方法。此时针对某一个实例,我们无需了解它是?Cat
?还是?Dog
,就可以直接调用?eat
?方法,程序会自动判断出来应该如何执行?eat;
public
?表示公有属性或方法;// 基本语法 —— 定义类
class 类名 {
属性名: 类型;
constructor(参数: 类型) {
this.属性名 = 参数;
}
方法名() {
...
}
}
// 一个简单的 Person 类示例
class PersonTS {
// 定义属性类型
name: string;
age: number;
static className: string = 'PersonTS' // 类属性
// constructor -> 构造函数,在创建对象实例时调用
constructor(name: string, age: number) {
// 实例属性
this.name = name;
this.age = age;
}
// 实例方法
printAge(){
// 在方法中可以通过 this 来表示当前调用方法的对象
console.log(`${this.name}今年${this.age}岁了!`);
}
// 类方法
static sayHi(){
console.log('hi.');
}
}
const person1 = new PersonTS('Tom', 18);
const person2 = new PersonTS('John', 20);
console.log('person1: ', person1.name, person1.age); // person1: Tom 18
person1.printAge() // Tom今年18岁了!
console.log('person2: ', person2.name, person2.age); // person2: John 20
person2.printAge() // John今年20岁了!
console.log('PersonTS: ', PersonTS.className); // PersonTS: PersonTS
PersonTS.sayHi() // hi.
实例属性与方法:
????????实例属性只能通过 constructor 构造函数中的?
this.xxx
?来定义,每次 new 一个对象时,都会调用类中的?constructor 构造函数,上述代码将 name 与 age 值传入以获得不同实例对象。实例的属性值需要在实例对象上取,例如?person1.name, person1.age。? ? ? ? 实例方法直接定义在类中,可以通过 this 使用当前实例对象的实例属性值。例如printAge。
静态属性与方法:
????????使用?
static
?修饰符修饰的属性和方法称为静态属性(ES7)与静态方法,它们不需要实例化,而是直接通过类来调用。例如?PersonTS.className、PersonTS.sayHi()。
? ? ? ? 假设我们创建了 Dog 类与 Cat 类,代码如下。我们发现两个类中存在共同的属性(name、age)与方法,此时我们考虑将相同的部分提取出来共用,这就可以用到“类的继承”。
// 分别实现 Dog 类与 Cat 类
class Dog {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi() {
console.log('汪汪汪!');
}
}
class Cat {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi() {
console.log('喵喵喵~');
}
}
const dog = new Dog('旺财', 3);
const cat = new Cat('咪咪', 2);
? ? ? ? 我们使用 class Dog extends Animal?来实现继承。此时,Animal?被称为父类,Dog 被称为子类,子类将会拥有父类所有的方法和属性。
super
?关键字:使用?extends
?关键字实现继承,子类中使用?super
?关键字来调用父类的构造函数和方法。如果在子类中使用构造函数,在子类构造函数中必须调用父类的构造函数。// Animal —— 父类
class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi() {
console.log('动物的叫声...');
}
}
class Dog extends Animal {
sex: string;
constructor(name: string, age: number, sex: string) {
// 继承父类的 name 与 age 属性
super(name, age);
// Dog 子类中新增的属性
this.sex = sex;
}
// 重写父类方法
sayHi() {
console.log('汪汪汪!');
}
// 增加子类独有的方法
run() {
console.log('run...');
}
}
class Cat extends Animal {
sayHi() {
console.log('喵喵喵~');
}
}
const dog = new Dog('旺财', 3, 'male');
const cat = new Cat('咪咪', 2);
console.log('dog: ', dog.name, dog.sex); // dog: 旺财 male
dog.sayHi() // 汪汪汪!
dog.run() // run...
console.log('cat: ', cat.name, cat.age); // cat: 咪咪 2
cat.sayHi() // 喵喵喵~
????????abstract
?用于定义抽象类和其中的抽象方法。
? ? ? ? (1)抽象类不允许被实例化。当我们希望某个类不能通过 new 创建实例,只用于继承时,可以使用抽象类。
abstract class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi() {
console.log('动物的叫声...');
}
}
const dog = new Animal('旺财', 3) //报错:无法创建抽象类的实例。
????????(2)抽象类中的抽象方法必须被子类实现。在上述代码中,我们能够发现 sayHi 这个方法并没有实际的作用,因为不同的动物会有不用的叫声,因此这类方法必须在子类中被重写,我们可以将其定义为抽象方法。
abstract class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
abstract sayHi(): void
}
????????在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。在程序设计里面,接口起到一种限制和规范的作用。基本语法如下:
interface Person {
name: string;
age?: number; // 可选属性:此属性可有可无
readonly id: number; // 只读属性
[propName: string]: any; // 任意属性
}
const person: Person = {
name: 'Tom',
sex: 'male',
id: 5
}
????????实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用?implements
?关键字来实现。这个特性大大提高了面向对象的灵活性。
? ? ? ? 可以直接看这篇笔记,写了类实现接口、接口继承接口、接口继承类,可以直接参考:类与接口 · TypeScript 入门教程
????????函数本质上是一个特殊的对象,我们也可以用接口来定义函数的参数和返回值。
// 定义函数接口
interface func {
(num: number, str: string): boolean
}
let func1: func = function (num: number, str: string) {
return num === 1
}
let func2: func = (num: number, str: string) => num === 1 // 箭头函数
????????可索引接口是一种描述数组和对象的接口,用来规范它们的索引类型和索引值类型等信息。
//对数组的约束
interface interArray {
[index: number]: string
}
var arr: interArray = ['a', 'b']
? ? ? ? 在使用 react 进行组件编写时,总会看到如下形式的传参,在不清楚具体参数的时候,一般会写 React.FC<any>,但是这样就没有意义了,使用接口可以在一定程度上对传参进行限制,防止一些不必要的错误,在调用组件时也可以给编写者提示需要传入什么参数。
interface Props {
arg1: number
arg2: string
arg3?: boolean
onFunc: (x: number, y: number) => void
onFunc2: (values: string) => number
[propName: string]: any
}
const TestComponents: React.FC<Props> = ({ arg1, arg2, arg3, onFunc, onFunc2 }: Props) => {
return (
<>
测试组件
属性有:{arg1}、{arg2}、{arg3}
<button onClick={() => onFunc(1, 2)}>点击事件1</button>
<button onClick={() => onFunc2('dd')}>点击事件2</button>
</>
)
}
export default TestComponents
接口使用?interface
?关键字声明,抽象类使用?abstract
?声明(类和成员);
多继承:在接口中,一个类可以实现多个接口。这意味着一个类可以具备不同接口定义的属性和方法。而在抽象类中,一个类只能继承一个抽象类,由于JavaScript并不支持多继承,因此抽象类只能实现单一继承;
默认实现:抽象类可以包含方法的实现细节,子类可以选择性地覆盖这些方法。接口不能包含实现细节,它只提供了属性和方法的定义,需要由实现接口的类来提供具体实现;
? ? ? ? 什么是类型别名?类型别名用来给一个类型起个新名字,使用 type 创建类型别名,类型别名不仅可以用来表示基本类型,还可以用来表示对象类型、联合类型、元组和交集。
type stringName = string; //基本类型
type combineName = string | number | boolean; // 联合类型
type Person = { // 对象类型
name: string;
age: number;
}
区别:
类型别名可以表示多种类型,interface 限于描述对象类型;
类型别名不能重复声明,interface 可以重复声明,最终实现是所有同名的并集;
参考:typescript中接口(interface)和类型别名(type)的区别 - 掘金、详解TypeScript中type与interface的区别_javascript技巧_脚本之家