ts如何根据参数推断类型

// 不用 @ts-ignore 不用函数重载方式 能行吗?

interface IFish {
    swim(): number;
}
interface IBird {
    fly(): string;
}

type FishOrBirdOrNever<T> = T extends IFish
    ? ReturnType<typeof fish.swim> :
    T extends IBird
    ? ReturnType<typeof bird.fly>
    : undefined; 

function isFish(pet: IFish | IBird): pet is IFish {
    return (pet as IFish).swim !== undefined;
}
function isBird(pet: IFish | IBird): pet is IBird {
    return (pet as IBird).fly !== undefined;
}
function getPetAction<T extends IFish | IBird>(pet: T): FishOrBirdOrNever<T> {
    const result = isFish(pet) ? pet.swim() : isBird(pet) ? pet.fly() : undefined;
    // 不用 @ts-ignore 不用函数重载
    return result; // 报错 这是为什么 (不用上面两种方式 又要怎么解决呢)
    /*
    * Type 'string | number' is not assignable to type 'FishOrBirdOrNever<T>'.
    * Type 'string' is not assignable to type 'FishOrBirdOrNever<T>'.
    */
}


const fish: IFish = {
    swim() {
        return 100;
    }
};
const bird: IBird = {
    fly() {
        return "Can't fly";
    }
};
const p1 = getPetAction(fish); // 根据参数推断类型
const p2 = getPetAction(bird);
console.log(p1);
console.log(p2);
阅读 5.7k
2 个回答

https://codepen.io/pantao/pen...

只是给出一个可行的编写方案,单纯看上面楼主的代码,感觉 TypeScript 的使用方法应该不太对吧。

interface IPet {
  name?: string;
}

interface IPetAction {
  (): number | string;
}

interface IFish extends IPet {
  swim: IPetAction;
}

interface IBird extends IPet {
  fly: IPetAction;
}

class Fish implements IFish {
  name?: string;
  swim() {
    return 1;
  }
}

class Bird implements IBird {
  name?: string;
  fly() {
    return "to";
  }
}

const getPetAction = (pet: IPet): (IPetAction | undefined) => {
  if (pet instanceof Fish) {
    return pet.swim;
  }
  if (pet instanceof Bird) {
    return pet.fly;
  }
};

const fish = new Fish();
const bird = new Bird();

const action = getPetAction(fish);

if (action) {
  action();
}

当你写到这一行的时候, 返回类型其实就已经确定了,所以很容易想到问题其实是出在result和声明的返回类型不匹配

function getPetAction<T extends IFish | IBird>(pet: T): FishOrBirdOrNever<T>

那么为什么会不匹配,以及该怎样去匹配呢?

我们先分析返回类型,已知泛型参数T实现了IFishIBird,那么返回值必然为numberstring类型,由是可以断定,这个undefined不该存在。

那么很容易就会写出如下代码

function getPetAction<T extends IFish | IBird>(pet: T): FishOrBirdOrNever<T> {
    const result = isFish(pet) ? pet.swim() : pet.fly()

然后你会发现这还是不行,为什么,到这里result是什么类型?

答案是number | string,但我们预先声明的返回类型是什么呢?是根据泛型参数计算出的一个确切类型numberstring,这与number | string是不同的,所以理所当然会报错。

那么该怎样将result的类型纠正过来呢?有如下代码

更新

改是改不过来了,any大法好

let res    // any
if (isFish(pet)) {
    res = pet.swim()
}
else res = pet.fly()
return res;    // 某些编辑器会自作聪明把这里推导为string | number,还是会报错

于是我尝试了这样去写,离谱的是第一个if里类型推导成了,第二个却嗝屁了

if (isFish(pet)) {
    let result : FishOrBirdOrNever<typeof pet> = pet.swim()
    return result
}
if (isBird(pet)) {
    // Type 'string' is not assignable to type 'FishOrBirdOrNever<T & IBird>'.
    let result : FishOrBirdOrNever<typeof pet> = pet.fly()
    return result
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进