Как определить тип как список ключей объекта, определенных как [ключ: строка] структура ключа / значения — TypeScript

#typescript #typescript-typings #typescript2.0 #typescript-generics

#typescript #typescript-typings #typescript2.0 #typescript-generics

Вопрос:

Я хотел бы определить интерфейс в TypeScript следующим образом, но я понятия не имею, как это сделать:

 export interface SomeInterface {
  testProp:{
  [key: string]: {
    prop1: string;
    prop2?: string;
    prop3?: string;
    ....

  };
}
  requiredProps: ???? // <- here i would like to define type to be array of used keys
}
 

например, объект:

  const value = {
    testProp: {
      orange: {
        prop1: 'test1',
      },
      kiwi: {
        prop1: 'random text',
      },
      lemon: {
        prop1: 'text',
      },
    },
   requiredProps: [] // posible items in array "lemon", "kiwi", "orange"
  };
 

Я попытался определить requiredProps как requiredProps: [keyof Pick<SomeInterface,"testProp"] , но безуспешно

Ответ №1:

Не существует определенного типа, соответствующего тому, что вы пытаетесь сделать. Однако вы можете представить его как универсальный тип. Например:

 export interface SomeInterface<K extends PropertyKey, R extends K> {
    testProp: Record<K, {
        prop1: string;
        prop2?: string;
        prop3?: string;
    }>;
    requiredProps: R[]
}
 

Это будет ограничивать вещи так, как вы хотите, но для того, чтобы создать значение SomeInterface типа, вам нужно указать параметры K и R . Вы можете заставить компилятор выводить их с помощью вспомогательной функции:

 const asSomeInterface = <K extends PropertyKey, R extends K>(x: SomeInterface<K, R>) => x;
 

И используйте его так:

 const value = asSomeInterface({
    testProp: {
        orange: {
            prop1: 'test1',
        },
        kiwi: {
            prop1: 'random text',
        },
        lemon: {
            prop1: 'text',
        },
    },
    requiredProps: ["orange", "kiwi"]
});
 

и вы можете увидеть желаемую ошибку при добавлении элемента requiredProps , который не является ключом testProp :

 asSomeInterface({
    testProp: {
        a: { prop1: "" }, b: { prop1: "" }, c: { prop1: "" }
    },
    requiredProps: ["a", "b", "c", "d"] // error!
    // --------------------------> ~~~~
    // Type '"d"' is not assignable to type '"a" | "b" | "c"'
})
 

Создание SomeInterface<K, R> универсального типа является более сложным в том смысле, что любые значения или функции, которые имеют с ними дело, должны содержать дополнительные параметры типа. Вы можете рассмотреть возможность использования общего кода только для кода, доступного внешним пользователям, ввод которых не гарантируется корректным, а затем внутренне расширить тип до неродовой версии, которая менее безопасна, но ее легче передавать:

 // code seen by outside users, enforces constraint
function externalFunction<K extends string, R extends K>(
  someInterface: SomeInterface<K, R>
) {
    internalFunction(someInterface)
}

// code not exposed outside, widens to non-generic version
type SomeWiderInterface = SomeInterface<string, string>
const someWiderValue: SomeWiderInterface = value; // accepted
function internalFunction(someWiderInterface: SomeWiderInterface) {
    // do stuff
}
 

Игровая площадка ссылка на код