在TypseScript中引入Redux给action加类型验证的时候我们考虑对于同一个请求的三个action的type是不是可以直接将类型设置为string。
例如:
export interface ActionType {
type: string;
}
export interface RequestManufacturerSuccess extends ActionType {
payloadUsAs: Manufacturer[];
}
export interface RequestManufacturerFailed extends ActionType {
payloadUsAs: any;
}
export type RequestManufacturerAction =
ActionType | RequestManufacturerSuccess | RequestManufacturerFailed;
但是我们在reducer中发现如果给action的type直接限定类型为string时,reducer无法通过不同type找到对应的不同的actionType,代码如下:
const initState: ManufacturerState = { manufacturers: [], error: null, loading: false };
function reducer(state = initState, action: RequestManufacturerAction): ManufacturerState {
switch (action.type) {
case REQUEST_MANUFACTURERS:
return { ...state, loading: true };
case REQUEST_MANUFACTURERS_SUCCESS:
// 会提示 property action.payload does not exist on type ActionType
return { ...state, manufacturers: action.payload, loading: false };
case REQUEST_MANUFACTURERS_FAILED:
// 会提示 property action.payload does not exist on type ActionType
return { ...state, error: action.payload, loading: false };
default:
return state;
}
}
这里就涉及到了TypeScript union type的概念和特性了:
联合类型概念:
union type基本形式:使用 | 分隔不同类型
export type pad = number | string
表示如果一个变量或参数或函数是pad类型则它可以为number类型或者string类型;
export type RequestManufacturerAction =
ActionType | RequestManufacturerSuccess | RequestManufacturerFailed;
同理,上面的代码表示如果一个变量或参数或函数是RequestManufacturerAction类型则它可以为ActionType类型或者RequestManufacturerSuccess类型或RequestManufacturerFailed类型。
但是在reducer中使用这个联合类型时,我们想通过传入的action的不同type让它自动识别对应到不同的actionType使得我们能够获取该类型的属性时,它无法做到这一点。
实际上,如果我们使用了union type,我们在使用该联合类型的参数时,只能使用联合类型中所有类型的公共部分。例如:
//types.ts
export interface Bird {
type: string;
fly: () => string;
layEggs: () => string;
}
export interface Fish {
type: string;
swim: () => string;
layEggs: () => string;
}
export type SmallPet = Bird | Fish;
//index.ts
import {Bird, Fish, SmallPet} from "./types";
function distinguishPet(smallPet: SmallPet) {
switch (smallPet.type) {
case 'bird':
return smallPet.fly; //errors
case 'fish':
return smallPet.swim; //errors
default:
return smallPet.layEggs; //okay
}
}
联合类型在这里看来似乎有点复杂,如果我们有A | B这样的类型,我们只能确定A和B共有的部分,在这个例子中,Bird 有一个fly成员,但是我们无法确定Bird | Fish类型中有fly成员。如果变量确实为Fish类型,那么在运行时smallPet.fly会失败。
Type Guard 和 Differentiating Types
我们可以通过Type Guard 和 Differentiating Types的方式去确保union type的各种情况,使其可以使用不同类型的属性:
类型断言
为了使上面的情况可以工作,我们可以使用类型断言的方式:
function distinguishPet(smallPet: SmallPet) {
switch (smallPet.type) {
case 'bird':
return (smallPet as Bird).fly;
case 'fish':
if ("swim" in smallPet) {
return (smallPet as Fish).swim;
}
}
}
自定义 Type Guards
Type Predicates
定义一个 type guard,我们只需要定义一个返回类型为type predicate的函数
例如:
function distinguishPet(smallPet: SmallPet) {
switch (smallPet.type) {
case 'bird':
if (isBird(smallPet)) {
return smallPet.fly;
}
case 'fish':
if (isFish(smallPet)) {
return smallPet.swim;
}
default:
return smallPet.layEggs;
}
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function isBird(pet: Fish | Bird): pet is Bird {
return (pet as Bird).fly !== undefined;
}
不论何时isFish被调用,TypeScript会进一步缩小变量类型的范围到一个比较精确的类型。
'in' 操作符
in 操作符在这里作为缩小类型的表达式
function distinguishPet(smallPet: SmallPet) {
switch (smallPet.type) {
case 'bird':
if ("fly" in smallPet) {
return smallPet.fly;
}
case 'fish':
if ("swim" in smallPet) {
return smallPet.swim;
}
default:
return smallPet.layEggs;
}
}
typeof type guards
const BIRD = 'BIRD';
const FISH = 'FISH';
export interface Bird {
type: typeof BIRD;
fly: () => string;
layEggs: () => string;
}
export interface Fish {
type: typeof FISH;
swim: () => string;
layEggs: () => string;
}
export type SmallPet = Bird | Fish;
import {SmallPet} from "./types";
function distinguishPet(smallPet: SmallPet) {
switch (smallPet.type) {
case 'BIRD':
return smallPet.fly;
case 'FISH':
return smallPet.swim;
default:
const {layEggs} = smallPet;
return layEggs;
}
}
在这里TypeScript会把typeof识别为它自己的一个type guards.
instanceof type guards
export interface Tree{
getTreeNumber(): string;
}
import {Tree} from "./types";
export class TreesIncrement implements Tree{
constructor(private treeNumber: number){}
getTreeNumber(){
return Array(this.treeNumber+1).join(" ");
}
}
export class TreesNumber implements Tree{
constructor(private treeNumber: string){}
getTreeNumber(){
return this.treeNumber;
}
}
function getRandomTreeNumber() {
return Math.random() < 0.5 ? new TreesIncrement(4) : new TreesNumber("3");
}
let tree: Tree = getRandomTreeNumber();
if(tree instanceof TreesIncrement){
console.log(tree);
}
if(tree instanceof TreesNumber){
console.log(tree);
}
instanceof 右边需要是一个contructor函数。instanceof type guards 是一个使用其contructor函数实现精确type的方法。
参考链接:https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types