接口
接口继承
interface IPerson {
name: string;
age: number;
}
interface IStudent extends IPerson {
grade: number;
}
let student: IStudent = {
name: 'pidancode',
age: 18,
grade: 90
};
类型断言
- "尖括号" 语法(Angle Bracket Syntax)
- "as" 语法
let someValue: any = 'hello';
let strLength: number = (<string>someValue).length;
let someValue: any = 'hello';
let strLength: number = (someValue as string).length;
const buttonElement = document.getElementById('myButton') as HTMLButtonElement;
buttonElement.disabled = true;
枚举
enum Direction {
Up,
Down,
Left,
Right
}
let direction: Direction = Direction.Up;
console.log(direction); // 输出: 0
direction = Direction.Right;
console.log(direction); // 输出: 3
// 通过枚举成员的值来访问枚举常量
let dirName: string = Direction[1];
console.log(dirName); // 输出: "Down"
typeof
在 TypeScript 中,typeof 是一个类型操作符(Type Operator),用于获取给定表达式的类型。这个操作符可以 用在类型注解、类型声明或泛型中。
const numValue = 42;
// 类型注解
let numType: typeof numValue;
numType = 10; // 错误
// 类型声明
type NumberType = typeof numValue;
let num: NumberType;
num = 10; // 错误
// 泛型中的 typeof
function printValue<T>(value: T) {
// 使用 typeof 获取 value 的类型并打印
const valueType: typeof value = value;
console.log(valueType);
}
printValue(42); // 输出:42
printValue('hello'); // 输出:"hello"
类型兼容性说明
TS 采用的是结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状。
也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。
解释:
class Point {
x: number;
y: number;
}
class Point2D {
x: number;
y: number;
}
const p: Point = new Point2D();
class Point3D {
x: number;
y: number;
z: number;
}
let p3: Point2D = new Point3D();
- Point 和 Point2D 是两个名称不同的类
- 变量 p 的类型被显示标注为 Point 类型,但是,它的值却是 Point2D 的实例,并且没有类型错误。
- 因为 TS 是结构化类型系统,只检查 Point 和 Point2D 的结构是否相同(相同,都具有 X 和 y 两个属性,属 性类型也相同)
- 但是,如果在 NominalType System 中(比如,C#、Java 等),它们是不同的类,类型无法兼容。
对象的类型兼容
注意:在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法并不准确。
更准确的说法: 对于对象类型来说,y 的成员至少与 X 相同,则 x 兼容 y(成员多的可以赋值给少的)。
class Point {
x: number;
y: number;
}
class Point3D {
x: number;
y: number;
z: number;
}
const p: Point = new Point3D();
解释:
- Point3D 的成员至少与 Point 相同,则 Point 兼容 Point3D
- 所以,成员多的 Point3D 可以赋值给成员少的 Point。
接口之间的兼容性
除了 class 之外,TS 中的其他类型也存在相互兼容的情况,包括:接口兼容性、函数兼容性等。
接口之间的兼容性,类似于 class。并且,class 和 interface 之间也可以兼容。
interface Point {
x: number;
y: number;
}
interface Point2D {
x: number;
y: number;
}
let p1: Point;
let p2: Point2D = p1;
interface Point3D {
x: number;
y: number;
z: number;
}
let p3: Point3D;
p2 = p3;
泛型
创建泛型函数:
function id<Type>(v: Type): Type {
return v;
}
id<number>(10);
id(10);
解释:
- 语法:在函数名称的后面添加
<>
(尖括号),尖括号中添加类型变量,比如此处的 Type。 - 类型变量 Type,是一种特殊类型的变量,它处理类型而不是值。
- 该类型变量相当于一个类型容器,能够捕获用户提供的类型
- 因为 Type 是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型。
- 类型变量 Type,可以是任意合法的变量名称。
泛型约束
泛型约束:默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性.
interface ILength {
length: number;
}
function fn<Type extends ILength>(value: Type): Type {
return value;
}
fn<string>('abc');
解释: Type 可以代表任意类型,无法保证一定存在 length 属性,比如 number 类型就没有 length。此时,就需 要为泛型添加约束来收缩类型(缩窄类型取值范围)。
多个泛型变量
泛型的类型变量可以有多个,并且类型变量之间还可以约束。
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let prop = getProp({ name: 'abc', age: 18 }, 'name');
console.log('prop', prop);
keyof 关键字接收一个对象类型,生成其键名称 (可能是字符串或数字)的联合类型。
泛型接口
interface IdFunc<Type> {
id: (value: Type) => Type;
ids: () => Type[];
}
let obj: IdFunc<number> = {
id(value) {
return value;
},
ids() {
return [1, 2, 3];
}
};
数组是泛型的接口:
const strs = ['a', 'b', 'c'];
strs.forEach;
/* Array<string>.forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any): void */
interface IState {
count: number;
}
interface IProps {
message: string;
}
class C4 extends React.Component<IState, IProps> {
state: IState = {
count: 10
};
}
泛型工具类
Partial
Partial<Type>
用来构造 (创建)一个类型,将 Type 的所有属性设置为可选。
interface Prop {
id: string;
children: number[];
}
type partialProps = Partial<Prop>;
// 解释:构造出来的新类型 PartialProps 结构和 Props相同,但所有属性都变为可选的
let p1: Prop = {
id: '',
children: []
};
let p2: partialProps = {
id: ''
};
Readonly
Readonly<Type>
用来构造一个类型,将 Type 的所有属性都设置为 readonly (只读)
type ReadonlyProps = Readonly<Prop>;
let p3: ReadonlyProps = {
id: '1',
children: []
};
// p3.id = '1';
Pick
Pick<Type, Keys>
从 Type 中选择一组属性来构造新类型
interface Props {
id: string;
title: string;
children: number[];
}
type PickProps = Pick<Props, 'id' | 'title'>;
- Pick 工具类型有两个类型变量:1 表示选择谁的属性 2 表示选择哪几个属性
- 其中第二个类型变量,如果只选择一个则只传入该属性名即可。
- 第二个类型变量传入的属性只能是第一个类型变量中存在的属性
- 构造出来的新类型 PickProps,只有 id 和 title 两个属性类型。
Record
Record<Keys,Type>
构造一个对象类型,属性键为 Keys,属性类型为 Type。
type RecordObj = Record<'a' | 'b' | 'c', string[]>;
let obj2: RecordObj = {
a: ['1'],
b: ['1'],
c: ['1']
};
解释:
- Record 工具类型有两个类型变量:1 表示对象有哪些属性 2 表示对象属性的类型。
- 构建的新对象类型 Recordobi 表示:这个对象有三个属性分别为
a/b/c
,属性值的类型都是 string[].
React 中的常用类型
前提说明: 现在,基于 class 组件来讲解 React+TS 的使用。
在不使用 TS 时,可以使用 prop-types 库,为 React 组件提 供类型检查。
说明: TS 项目中,推荐使用 TypeScript
实现组件类型校验 (代替 PropTypes)。
不管是 React 还是 Vue,只要是支持 TS 的库,都提供了很多类型,来满足该库对类型的需求。注意:
- React 项目是通过
@types/react
、@types/react-dom
类型声明包,来提供类型的。 - 这些包 CRA 已帮我们安装好(
react-app-env.d.ts
),直接用即可。
参考资料: React 文档-静态类型检查、[`React
- TS
备忘单](
React + TS` 备忘单)
React 是组件化开发模式,React 开发主要任务就是写组件,两种组件:函数组件、class 组件
函数组件
- 函数组件,主要包括以下内容:
- 组件的类型
- 组件的属性 (props)
- 组件属性的默认值 (defaultProps)
- 事件绑定和事件对象
组件的类型及其属性:
import React, { FC } from 'react';
type Props = { name: string; age?: number };
const Hello: FC<Props> = ({ name, age }) => {
return (
<>
{name}- {age}
</>
);
};
<Hello name='jack' />;
实际上,还可以简化为:
type Props = { name: string; age?: number };
const Hello = ({ name, age }: Props) => {
return (
<>
{name}- {age}
</>
);
};
组件属性的默认值:
const Hello: FC<Props> = ({ name, age }) => {
return (
<>
{name}- {age}
</>
);
};
Hello.defaultProps = {
age: 18
};
实际上,还可以简化为:
type Props = { name: string; age?: number };
const Hello = ({ name, age = 18 }: Props) => {
return (
<>
{name}- {age}
</>
);
};
事件绑定和事件对象
// 按钮
const Btn = () => {
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {};
return <button onClick={onClick}>点击</button>;
};
// 文本框
const Ipt = () => {
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {};
return <input onChange={onChange}>点击</input>;
};
class 组件
在 TypeScript 中,类(class)中可以使用以下四种访问修饰符来控制类成员(属性和方法)的可见性和访问权 限:
- public:默认的修饰符,所有类成员都是公共的,可以在类的内部和外部访问。
- private:私有成员,只能在类的内部访问,外部无法访问。
- protected:受保护成员,类的内部和派生类(继承类)中可以访问,外部无法问。
- readonly:只读成员,表示该成员的值在初始化后不能再被修改。
class Person {
public name: string; // 默认为 public
private age: number;
protected address: string;
readonly id: number;
constructor(name: string, age: number, address: string, id: number) {
this.name = name;
this.age = age;
this.address = address;
this.id = id;
}
public displayInfo(): void {
console.log(`Name: ${this.name}, Age: ${this.age}, Address: ${this.address}`);
}
private getAge(): number {
return this.age;
}
protected getId(): number {
return this.id;
}
}
class Employee extends Person {
public department: string;
constructor(name: string, age: number, address: string, id: number, department: string) {
super(name, age, address, id);
this.department = department;
}
public displayEmployeeInfo(): void {
console.log(
`Name: ${this.name}, Age: ${this.age}, Address: ${this.address}, Department: ${this.department}`
);
}
}
const person = new Person('Alice', 30, '123 Main St', 1);
console.log(person.name); // 可以访问,输出:Alice
console.log(person.age); // 错误,age 是 private,无法访问
console.log(person.address); // 错误,address 是 protected,无法在类外部访问
console.log(person.id); // 可以访问,输出:1
person.displayInfo(); // 可以调用,输出:Name: Alice, Age: 30, Address: 123 Main St
person.getAge(); // 错误,getAge 是 private,无法在类外部调用
person.getId(); // 错误,getId 是 protected,无法在类外部调用
const employee = new Employee('Bob', 25, '456 Broadway', 2, 'HR');
console.log(employee.name); // 可以访问,输出:Bob
console.log(employee.department); // 可以访问,输出:HR
employee.displayEmployeeInfo(); // 可以调用,输出:Name: Bob, Age: 25, Address: 456 Broadway, Department: HR
employee.displayInfo(); // 可以调用,输出:Name: Bob, Age: 25, Address: 456 Broadway