【问题标题】:Is possible to tell that a function to narrow down the types and not expand?是否可以告诉一个函数来缩小类型而不是扩展?
【发布时间】:2022-06-10 22:41:50
【问题描述】:

我正在尝试编写一个工具来生成具有类型安全调用的 API 客户端,添加将负责验证输入并将其整理出来的库。

我想实现一个全局转换选项,以允许用户根据给定的类型修改响应。

假设我们有一组共享一个 Base 的类型,即

type Base<NAME extends string, T> = {
  name: NAME;
  value: T;
}

// All the possible responses given to the API
type ALL = Base<'Animal', Animal> | Base<'ZooKeeper', ZooKeeper> | Base<'Visitor', Visitor>;

我想编写一个函数来将所有Animal 转换为TaggedAnimalZooKeeper 转换为Keeper

const transformer = (value: ALL) => {
  if (value.name === 'Animal') {
     return {
         name: 'TaggedAnimal',
         value: {
            ...value.value,
            tag: 'mytag' // or something else made up of animal attributes
         }
     } as Base<'TaggedAnimal', TaggedAnimal>;
  } else if (value.name === 'ZooKeeper') {
    return {
      name: 'Keeper',
      value: {
        id: value.value.id
      }
    } as Base<'Keeper', Keeper>;
  }

  return value;
}

到目前为止一切都很好,但是当我尝试在特定 API 上使用此函数时,问题就出现了。

const getAnimal = (): Base<'Animal', Animal> => {
  // do network request, validation, etc
  return {
      name: 'Animal',
      value: {
        id: '123',
        name: 'Lion'
    }
  } as Base<'Animal', Animal>;
}

const animal = getAnimal(); // Good! type of animal: Base<'Animal', Animal>
const tanimal = transformer(animal); // :/! type of tanimal: Base<'TaggedAnimal', TaggedAnimal> | Base<'Keeper', Keeper> | Base<'Visitor', Visitor>;

我知道这是因为 transformer 需要所有类型并返回一个固定子集(由函数给出)。

当前版本的 typescript (4.7) 有什么方法可以解决这个问题吗?

我曾尝试使用泛型来缩小范围,即:

const transformer = <IN extends ALL>(value: IN) => {
    // ...
}

const tanimal = transformer(animal); // :/! type of tanimal: Base<'Animal', Animal> | Base<'TaggedAnimal', TaggedAnimal> | Base<'Keeper', Keeper>;

Playground link

【问题讨论】:

    标签: typescript


    【解决方案1】:

    你需要重载你的函数:

    interface Animal {
      id: string;
      name: string;
    }
    
    interface ZooKeeper {
      id: string;
      shift: string;
    }
    
    interface Visitor {
      id: string;
      fee: number;
    }
    
    interface TaggedAnimal extends Animal {
      tag: string;
    }
    
    interface Keeper {
      id: string;
    }
    
    type Base<NAME extends string, T> = {
      name: NAME;
      value: T;
    }
    
    // All the possible responses given to the API
    type ALL = Base<'Animal', Animal> | Base<'ZooKeeper', ZooKeeper> | Base<'Visitor', Visitor>;
    
    function transformer(value: Base<'Visitor', Visitor>): Base<'Visitor', Visitor>
    function transformer(value: Base<'ZooKeeper', ZooKeeper>): Base<'Keeper', Keeper>;
    function transformer(value: Base<'Animal', Animal>): Base<'TaggedAnimal', TaggedAnimal>
    function transformer(value: ALL) {
      if (value.name === 'Animal') {
        return {
          name: 'TaggedAnimal',
          value: {
            ...value.value,
            tag: 'mytag' // or something else made up of animal attributes
          }
        }
      } else if (value.name === 'ZooKeeper') {
        return {
          name: 'Keeper',
          value: {
            id: value.value.id
          }
        }
      }
    
      return value;
    }
    
    const getAnimal = (): Base<'Animal', Animal> => ({
      name: 'Animal',
      value: {
        id: '123',
        name: 'Lion'
      }
    })
    
    const animal = getAnimal(); // Good! type of animal: Base<'Animal', Animal>
    const tanimal = transformer(animal); // Base<"TaggedAnimal", TaggedAnimal>
    
    

    Playground

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-05
      • 2015-01-18
      • 2020-03-02
      • 2019-01-25
      • 2016-03-11
      • 1970-01-01
      • 2017-09-11
      相关资源
      最近更新 更多