【问题标题】:Declare and initialize a Dictionary in Typescript在 Typescript 中声明和初始化字典
【发布时间】:2013-03-30 10:22:34
【问题描述】:

给定以下代码

interface IPerson {
   firstName: string;
   lastName: string;
}

var persons: { [id: string]: IPerson; } = {
   "p1": { firstName: "F1", lastName: "L1" },
   "p2": { firstName: "F2" }
};

为什么初始化没有被拒绝?毕竟,第二个对象没有“lastName”属性。

【问题讨论】:

  • 注意:这已经被修复(不确定哪个确切的 TS 版本)。正如你所料,我在 VS 中遇到了这些错误:Index signatures are incompatible.Type '{ firstName: string; }' is not assignable to type 'IPerson'.Property 'lastName' is missing in type '{ firstName: string; }'.
  • 您能否更新这篇文章:标题与问题和接受的答案不一致!
  • 允许我们在 typescript 中制作类似字典的概念在官方 typescript 手册中被称为“可索引类型”(参见 Indexable Types )。由于我花了一段时间才发现这一点,因此我想通过提供此功能的“官方名称”来为所有搜索官方文档的人指明正确的方向。

标签: dictionary initialization typescript


【解决方案1】:

Typescript 在您的情况下失败,因为它希望所有字段都存在。使用 Record 和 Partial 实用程序类型来解决它。

Record<string, Partial<IPerson>>

interface IPerson {
   firstName: string;
   lastName: string;
}

var persons: Record<string, Partial<IPerson>> = {
   "p1": { firstName: "F1", lastName: "L1" },
   "p2": { firstName: "F2" }
};

解释。

  1. 记录类型创建字典/哈希图。
  2. 部分类型表示可能缺少某些字段。

备用。

如果您希望姓氏可选,您可以附加一个 ? Typescript 会知道它是可选的。

lastName?: string;

https://www.typescriptlang.org/docs/handbook/utility-types.html

【讨论】:

    【解决方案2】:

    这是受@dmck 启发的更通用的字典实现

        interface IDictionary<T> {
          add(key: string, value: T): void;
          remove(key: string): void;
          containsKey(key: string): boolean;
          keys(): string[];
          values(): T[];
        }
    
        class Dictionary<T> implements IDictionary<T> {
    
          _keys: string[] = [];
          _values: T[] = [];
    
          constructor(init?: { key: string; value: T; }[]) {
            if (init) {
              for (var x = 0; x < init.length; x++) {
                this[init[x].key] = init[x].value;
                this._keys.push(init[x].key);
                this._values.push(init[x].value);
              }
            }
          }
    
          add(key: string, value: T) {
            this[key] = value;
            this._keys.push(key);
            this._values.push(value);
          }
    
          remove(key: string) {
            var index = this._keys.indexOf(key, 0);
            this._keys.splice(index, 1);
            this._values.splice(index, 1);
    
            delete this[key];
          }
    
          keys(): string[] {
            return this._keys;
          }
    
          values(): T[] {
            return this._values;
          }
    
          containsKey(key: string) {
            if (typeof this[key] === "undefined") {
              return false;
            }
    
            return true;
          }
    
          toLookup(): IDictionary<T> {
            return this;
          }
        }
    

    【讨论】:

      【解决方案3】:

      如果您想忽略某个属性,请通过添加问号将其标记为可选:

      interface IPerson {
          firstName: string;
          lastName?: string;
      }
      

      【讨论】:

      • 问题的重点是为什么给定的代码在编译时没有提供姓氏……
      【解决方案4】:

      要在打字稿中使用字典对象,您可以使用如下界面:

      interface Dictionary<T> {
          [Key: string]: T;
      }
      

      并且,将其用于您的类属性类型。

      export class SearchParameters {
          SearchFor: Dictionary<string> = {};
      }
      

      使用和初始化这个类,

      getUsers(): Observable<any> {
              var searchParams = new SearchParameters();
              searchParams.SearchFor['userId'] = '1';
              searchParams.SearchFor['userName'] = 'xyz';
      
              return this.http.post(searchParams, 'users/search')
                  .map(res => {
                      return res;
                  })
                  .catch(this.handleError.bind(this));
          }
      

      【讨论】:

        【解决方案5】:

        我同意 thomaux 的观点,即初始化类型检查错误是一个 TypeScript 错误。但是,我仍然想找到一种方法,通过正确的类型检查在单个语句中声明和初始化 Dictionary。此实现更长,但它添加了额外的功能,例如containsKey(key: string)remove(key: string) 方法。我怀疑一旦泛型在 0.9 版本中可用,这可能会被简化。

        首先我们声明基本 Dictionary 类和接口。索引器需要该接口,因为类无法实现它们。

        interface IDictionary {
            add(key: string, value: any): void;
            remove(key: string): void;
            containsKey(key: string): bool;
            keys(): string[];
            values(): any[];
        }
        
        class Dictionary {
        
            _keys: string[] = new string[];
            _values: any[] = new any[];
        
            constructor(init: { key: string; value: any; }[]) {
        
                for (var x = 0; x < init.length; x++) {
                    this[init[x].key] = init[x].value;
                    this._keys.push(init[x].key);
                    this._values.push(init[x].value);
                }
            }
        
            add(key: string, value: any) {
                this[key] = value;
                this._keys.push(key);
                this._values.push(value);
            }
        
            remove(key: string) {
                var index = this._keys.indexOf(key, 0);
                this._keys.splice(index, 1);
                this._values.splice(index, 1);
        
                delete this[key];
            }
        
            keys(): string[] {
                return this._keys;
            }
        
            values(): any[] {
                return this._values;
            }
        
            containsKey(key: string) {
                if (typeof this[key] === "undefined") {
                    return false;
                }
        
                return true;
            }
        
            toLookup(): IDictionary {
                return this;
            }
        }
        

        现在我们声明 Person 特定类型和 Dictionary/Dictionary 接口。在 PersonDictionary 中说明我们如何覆盖 values()toLookup() 以返回正确的类型。

        interface IPerson {
            firstName: string;
            lastName: string;
        }
        
        interface IPersonDictionary extends IDictionary {
            [index: string]: IPerson;
            values(): IPerson[];
        }
        
        class PersonDictionary extends Dictionary {
            constructor(init: { key: string; value: IPerson; }[]) {
                super(init);
            }
        
            values(): IPerson[]{
                return this._values;
            }
        
            toLookup(): IPersonDictionary {
                return this;
            }
        }
        

        这里是一个简单的初始化和使用示例:

        var persons = new PersonDictionary([
            { key: "p1", value: { firstName: "F1", lastName: "L2" } },
            { key: "p2", value: { firstName: "F2", lastName: "L2" } },
            { key: "p3", value: { firstName: "F3", lastName: "L3" } }
        ]).toLookup();
        
        
        alert(persons["p1"].firstName + " " + persons["p1"].lastName);
        // alert: F1 L2
        
        persons.remove("p2");
        
        if (!persons.containsKey("p2")) {
            alert("Key no longer exists");
            // alert: Key no longer exists
        }
        
        alert(persons.keys().join(", "));
        // alert: p1, p3
        

        【讨论】:

        • 非常有用的示例代码。 “interface IDictionary”包含一个小错字,因为其中引用了 IPerson。
        • 也可以很好地实现元素计数
        • @dmck 声明 containsKey(key: string): bool; 不适用于 TypeScript 1.5.0-beta。应该改为containsKey(key: string): boolean;
        • 为什么不删除泛型类型? Dictionary,则无需创建 PersonDictionary 类。你这样声明: var people = new Dictionary();
        • 我有效地使用了这样一个通用字典。我在这里找到它:fabiolandoni.ch/…
        【解决方案6】:

        编辑:此问题已在最新的 TS 版本中得到修复。引用@Simon_Weaver 对 OP 帖子的评论:

        注意:这已被修复(不确定哪个确切的 TS 版本)。一世 如您所料,在 VS 中得到这些错误:Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.


        显然,在声明时传递初始数据时这不起作用。 我猜这是 TypeScript 中的一个错误,所以你应该在项目站点提出一个错误。

        您可以通过在声明和初始化中拆分示例来使用类型化字典,例如:

        var persons: { [id: string] : IPerson; } = {};
        persons["p1"] = { firstName: "F1", lastName: "L1" };
        persons["p2"] = { firstName: "F2" }; // will result in an error
        

        【讨论】:

        • 为什么需要id 符号?好像没必要。
        • 使用id符号,你可以声明字典的键的类型应该是什么。使用上述声明,您无法执行以下操作:persons[1] = { firstName: 'F1', lastName: 'L1' }
        • id 符号可以命名为您喜欢的任何名称,并且旨在使代码更易于阅读。例如{ [username: string] : IPerson; }
        • 第一行IPerson后面是否需要分号?看来不用那个分号我可以编译没有任何错误。
        • @DamnVegetables 可以省略,因为这种情况下的接口只列出一个属性。当定义多个属性时,需要对它们进行分隔。在 v1.6 之前,这必须是分号。从 v1.6 开始,您还可以选择使用逗号。我更喜欢使用分号,因为这也是行尾分隔符,但选择纯粹是个人的。有关更多背景信息,请参阅此 SO 帖子:stackoverflow.com/questions/27994253/…
        猜你喜欢
        • 2011-10-09
        • 2017-09-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-12
        • 2013-10-22
        • 1970-01-01
        相关资源
        最近更新 更多