拓展 - typescript笔记
学习 TypeScript 笔记,仅记录一些容易混淆的知识点
数据类型
void 空值
可以表示没有任何返回值的函数
function alertName(): void {
alert('My name is Tom');
}
也可以用于表示只能被设置成 null 或 undefined 的变量
let unusable: void = undefined;
Null/Undefined
null 和 undefined 是所有类型的子类,也就是说 null / undefined 类型的变量能够赋值给 number 类型的变量
let num: number = undefined;
// or
let u: undefined;
let num: number = u;
类型推论
当没有明确指定类型时,会在编译时进行推断,例如给一个变量赋值字符串,那么 ts 就会推断为 string 类型
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
联合类型
联合类型表示取值可以为多种类型中的一种
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
当 ts 无法确定一个联合类型的变量是哪个类型的时候,就会使用类型推断来确定其类型,在没有进行推断之前,只能访问联合类型的所有类型里的共有属性和方法,下例中因为 number 没有 length 方法,所以 ts 报错
function getLength(something: string | number): number {
return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
接口
在 TS 中使用 interface(接口)去定义对象的类型。
可以通过readonly
去定义只读属性,TS 会在修改只读属性时报错。
interface Person {
readonly id: number;
name: string;
}
接口之间可以互相继承:
interface Alarm {
alert(): void;
}
interface LightableAlarm extends Alarm {
lightOn(): void;
lightOff(): void;
}
有点特别的是,接口可以继承类:
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface PointInstanceType {
x: number;
y: number;
}
// 等价于 interface Point3d extends PointInstanceType
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
函数类型
函数的类型定义要区分 函数声明 和 函数表达式。
函数声明,定义好入参和返回的类型即可:
function sum(x: number, y: number): number {
return x + y;
}
函数表达式的类型定义要比函数声明来的复杂,定义同一个函数:
let sum: (x: number, y: number) => number = function (x, y) {
return x + y;
};
当然也可以使用接口去定义:
interface Sum {
(x: number, y: number): number;
}
let sum: Sum;
sum = function(x: number, y: number) {
return x + y;
}
类型别名
这是之前我一直和 interface 混淆的概念,类型别名用来给一个类型起新的名字,常用于联合类型:
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
这里用到了 type 关键字去定义类型别名,在 TS 中,定义字符串字面量类型用的也是 type 关键字:
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
断言
断言,即手动指定一个值的类型。
断言的使用有一定的限制,如果想让 A 与 B 能兼容,那么 A 能被断言成 B,B 也能被断言成 A。
举个例子,下面的 Animal 和 Cat 的关系可以看做是 Cat extends Animal,Cat 能和 Animal 兼容,所以他们能互相断言。
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
function testAnimal(animal: Animal) {
return (animal as Cat);
}
function testCat(cat: Cat) {
return (cat as Animal);
}
下面列举了3种常见的使用场景:
常遇见的一个状况就是在联合类型中,只能使用所有类型共有的属性和方法,如果需要使用某个类型的特有方法,就需要使用断言来指定类型:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function getName(animal: Cat | Fish) {
return animal.swim(); // 这里会报错
}
// 但使用断言后便不会报错
function isFish(animal: Cat | Fish) {
return (animal as Fish).swim();
}
还可用于将一个父类断言为更具体的子类:
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
const animal: Animal = {
name: 'tom'
};
let tom = animal as Cat;
当使用类型声明去定义 tom 变量时,是会报错的,因为不能将父类的实例赋值给类型为子类的变量:
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
const animal: Animal = {
name: 'tom'
};
let tom: Cat = animal; // 此处会报错
还有一种情况就是,TS 会在不存在属性或方法时进行报错,但是有时候我们确定某个方法或者属性不会出错,就可以使用断言去忽略:
(window as any).foo = 1;
枚举
枚举解决了状态判断的痛点,下面是对于状态值判断的一段代码,从代码中很难看出值1对应的状态:
function handleStatusChange(status) {
if(status == 1){
// do somthing
}else if(status == 2){
// do somthing
}else if(status == 3){
// do somthing
}
}
如果使用枚举,且如果是数字枚举,那么
enum orderStatus {
UN_PAYED, // 未支付
PAYED, // 已支付
CANCELED, // 已取消
}
那么上述的代码就可以修改为:
function handleStatusChange(status) {
if(status == orderStatus['UN_PAYED']){
// do somthing
}else if(status == orderStatus['PAYED']){
// do somthing
}else if(status == orderStatus['CANCELED']){
// do somthing
}
}
枚举又可以分为数字枚举和字符串枚举。
他们的区别就在于:数字枚举既可以用枚举名称来索引,也可以用枚举值来索引;而字符串枚举只能够通过枚举名称来索引,不能通过枚举值来反向索引。
enum orderStatus {
UN_PAYED,
PAYED,
CANCELED,
}
orderStatus.UN_PAYED // 0;
UN_PAYED[0] // "UN_PAYED";
从他们被编译的结构上,就可以看出区别:
// 数字枚举
enum orderStatus {
UN_PAYED,
PAYED,
CANCELED,
}
var orderStatus;
(function (orderStatus) {
orderStatus[orderStatus["UN_PAYED"] = 0] = "UN_PAYED";
orderStatus[orderStatus["PAYED"] = 1] = "PAYED";
orderStatus[orderStatus["CANCELED"] = 2] = "CANCELED";
})(orderStatus || (orderStatus = {}));
// 字符串枚举
enum orderStatus {
UN_PAYED = "A",
PAYED = "B",
CANCELED = "C",
}
var orderStatus;
(function (orderStatus) {
orderStatus["UN_PAYED"] = "A";
orderStatus["PAYED"] = "B";
orderStatus["CANCELED"] = "C";
})(orderStatus || (orderStatus = {}));
如果在枚举之前加上const
,就是常量枚举,不同于常规的枚举,他们在编译阶段会被删除,减少额外开销:
const enum orderStatus {
UN_PAYED,
PAYED,
CANCELED,
}
类
修饰符
TS 提供了三种修饰符:public、private 和 protected
public:修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的;
private:
- 修饰的属性或方法是私有的,不能在声明它的类的外部访问;
- 在子类中也不能访问;
- 当构造函数用 private 进行修饰时,该类不能被继承或者实例化;
protected:在子类中允许被访问的 private 属性或方法,当构造函数用 protected 进行修饰时,该类只允许被继承;
参数属性
修饰符和 readonly 还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁:
class Animal {
public name: string;
public constructor(name) {
this.name = name;
}
}
// 可以简写为
class Animal {
public constructor(public name) {
}
}
如果需要加上 readonly 只读属性关键字:
class Animal {
// public readonly name;
public constructor(public readonly name) {
// this.name = name;
}
}
类与接口
interface(接口)通过 implements 进行实现(定义):
// 共有特性
interface Alarm {
alert(): void;
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
一个类可以实现多个接口:
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
一些特殊的场景
键名
如果要去定义一个对象的键和值的类型,就需要如下操作:
interface DataInterface = {
query?: { [key: string]: number | string };
}
Promise
如果要定义 Promise 的 resolve 和 reject 的值的类型,可以在创建 Promise 时在后面跟上泛型,例如:
function foo(){
return new Promise<number>((resolve,reject)=>{
resolve(123)
});
}
定义Ref
在 react 中定义 ref 时,可以使用 RefObject<> 的形式定义,例如定义一个 inputRef:
const inputRef: RefObject<HTMLInputElement> = useRef(null);
拓展 - typescript笔记