【问题标题】:How do I create an object type that should have all the keys of an array type如何创建应该具有数组类型的所有键的对象类型
【发布时间】:2020-08-03 11:09:10
【问题描述】:

我正在尝试创建一个通用类来定义具有 crud 操作的资源。我的最终目标是拥有一个通用类实例,可用于创建 servicesreducer slicecomponents 以自动显示数据、过滤和对特定资源进行分页。 对于当前的用例,这是我要定义的

资源城市有父州和国家 CityInstance.getPath 应该接受 statecountryids 并返回 /country/1/states/1/cities

export default class Resource {
  name: string;
  path: string;
  parents: string[];

  constructor(arg: { name: string; path: string; parents: string[] }) {
    this.name = arg.name;
    this.path = arg.path;
    this.parents = arg.parents;
  }
//I want the argument parentIds to include every single element of parents and their ids
  getBaseUrl(parentIds: {[parentName:string]=> id}){
       const parentPath = this.parents.map(p=>`/${p}/${parentIds[p]}`).join("");

       return `${parentPath}/${this.path}`
  }

}


const resource = new Resource({name:"city", parents: ["countries", "states"]})
//The next call should force me to supply the object of ids  and should not allow calls without them such as this one

resource.getBaseUrl({}) // Typescript should not allow


resource.getBaseUrl({country: 1}) // Typescript should not allow. 


//This would be valid since it supplies both state and country

resource.getBaseUrl({country: 1, state:2});

我意识到 typescript 无法预测运行时值是什么,因此无法仅从代码推断类型。我曾尝试将父母创建为一种类型,但不知道如何使用它

class Resource<ResourceType,  Parents extends string[] =[] > {
    name:string
    resourceUrlName:string
    
    constructor(name:string,resourceUrlName:string){
        this.name=name
        this.resourceUrlName = resourceUrlName
    }
    //How do I specify that the indexer should be a part of parents array type
    generateBasePathForCollection(parents: {[name: keyof Parents} : number}){
      // How do I get all the members of the Parents Array

    }
}

【问题讨论】:

    标签: typescript


    【解决方案1】:

    据我所知,您不能使用数组的 来映射类型。稍微改变一下签名来帮助你怎么样?你可以做一个映射类型的对象键。

    type WithParents<T extends Record<string, number>> = {
      baseUrl?: string;
      name: string;
      parents: T;
    };
    
    class Resource<T extends Record<string, number>> {
      baseUrl?: string;
      name: string;
      parents: T;
    
      constructor(args: WithParents<T>) {
        this.baseUrl = args.baseUrl;
        this.name = args.name;
        this.parents = args.parents;
      }
    
      getBaseUrl(input: { [P in keyof T]: string | number }) {
        const parts = Object.keys(this.parents).sort(k => this.parents[k]);
        return [this.baseUrl, ...parts.map(key => `${key}/${input[key]}`)]
          .filter(Boolean)
          .join("/");
      }
    }
    
    

    这里的重要部分是类上的泛型和WithParents 类型,以及getBaseUrl 中的映射类型arg(基于类上的泛型参数)。以下是有关映射类型的文档:https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types

    然后

    const x = new Resource({ name: "string", parents: { countries: 1, state: 2 } });
    
    console.log(x.getBaseUrl({ countries: 1, state: 2 })); // countries/1/state/2 
    console.log(x.getBaseUrl({ state: 1, countries: 2 })); // countries/2/state/1 
    
    const y = new Resource({ name: "string", parents: { state: 1, countries: 2 } });
    
    console.log(y.getBaseUrl({ countries: 1, state: 2 })); // state/2/countries/1 
    console.log(y.getBaseUrl({ state: 1, countries: 2 })); // state/1/countries/2 
    

    你可以让它变得有用,比如{ countries: "number" },然后在你在那里的时候做一些验证。就目前而言,您可以将 any 值作为记录中条目的值,它会起作用。

    编辑:已更新以保持排序。由于没有使用parents 的初始值,我们将使用它们的值来表示一个顺序(从 1 开始)。

    【讨论】:

    • 对,数组没有键。数组只有可以静态类型的值,而不是动态值。
    • 唯一的问题是父母的顺序很重要。这可能产生国家/1/州/1/城市或州/1/国家/2/城市
    • 我将对此进行测试并回复您。我确实考虑过使用记录,但认为排序可能是个问题
    • 这实际上是一个非常新颖的解决方案,是的,它似乎确实有效。 repl.it/@slaith/DismalRaggedProcess#index.ts 我认为问题在于对事物顺序的依赖。也许有办法消除它
    • 避免使用any,因为它只能在URL 中使用numberstring,或者实现toString(): stringvalueOf(): number
    【解决方案2】:

    更新以保留parents

    parents 作为 parentIdsT 相同类型的对象提供。 parents 的值无关紧要,除了它们的类型。保留键的顺序。

    export default class Resource<T extends { [parentName: string]: number }> {
        name: string;
        path: string;
        parents: T;
    
        constructor({ name, path, parents }: { name: string; path: string; parents: T; }) {
            this.name = name;
            this.path = path;
            this.parents = parents;
        }
        // You want the argument parentIds to include every single element of parents and their ids
        getBaseUrl(parentIds: T) {
            const parentPath = Object.keys(this.parents).map(p => `/${p}/${parentIds[p]}`).join("");
    
            return `${parentPath}/${this.path}`
        }
    
    }
    
    
    const resource = new Resource({ name: "city", path: "dont-forget-path", parents: { country: 0, state: 0 } })
    //The next call should force me to supply the object of ids  and should not allow calls without them such as this one
    
    resource.getBaseUrl({}) // Typescript should not allow
    
    
    resource.getBaseUrl({ country: 1 }) // Typescript should not allow. 
    
    
    //This would be valid since it supplies both state and country
    
    resource.getBaseUrl({ country: 1, state: 2 });
    

    注意{[parentName:string]=&gt; id} 无效,因为=&gt; 应该是:id 必须是一个类型。

    另外,调用构造函数的时候别忘了加上path

    【讨论】:

    • 唯一的问题是父母的顺序很重要。这可能产生国家/1/州/1/城市或州/1/国家/2/城市
    猜你喜欢
    • 1970-01-01
    • 2019-12-12
    • 1970-01-01
    • 2013-09-05
    • 1970-01-01
    • 2022-08-09
    • 2021-07-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多