TypeScript еще одна функция prop с ключом небытия - PullRequest
0 голосов
/ 15 апреля 2019

Как мне набрать prop функцию, которая возвращает тип по умолчанию, когда ключ не существует для объекта o?

type Prop = <K, O extends {}>(k: K, o: O) =>
  K extends keyof O
    ? O[K]
    : 'Nah';

/*
Argument of type 'K' is not assignable to parameter of type 'string | 
  number | symbol'.
  Type 'K' is not assignable to type 'symbol'.
*/
const p: Prop = (k, o) => o.hasOwnProperty(k) ? o[k] : 'Nah';

p('non-existence-property', { n: 1 });

/*
Type '"Nah" | O[K]' is not assignable to type 'K extends keyof O ? O[K] : "Nah"'.
  Type '"Nah"' is not assignable to type 'K extends keyof O ? O[K] : "Nah"'.
*/
const p1 = <K, O extends {}>(k: K, o: O): K extends keyof O ? O[K] : 'Nah' =>
    o.hasOwnProperty(k) ? o[k] : 'Nah';

Ответы [ 2 ]

2 голосов
/ 16 апреля 2019

Во-первых, чтобы помочь в выводе типов при вызове p(), давайте немного изменим определение Prop:

type Prop = <K extends keyof any, O extends {}>(k: K, o: O) =>
  K extends keyof O
  ? O[K]
  : 'Nah';

Я только что ограничил K, чтобы он был string | number | symbol, чтовероятно, это то, что вы имели в виду, и имеет то преимущество, что функции типа Prop будут выводить K как строковые литералы вместо string.


Основная проблема, с которой выИмеется в том, что на самом деле компилятор не может проверить, можно ли присвоить тип неразрешенному условному типу (T extends U ? X : Y, когда T или U зависят от неуказанных / неферментированных параметров универсального типа).Общие функции, возвращающие условные типы, призваны упростить жизнь вызывающей стороны ; реализатор более или менее застрял, используя утверждения типа или подобное, чтобы успокоить компилятор:

const p: Prop = (k: any, o: any) => o.hasOwnProperty(k) ? o[k] : 'Nah'; // no error

Аннотация k и o какany позволяет нам писать нашу собственную реализацию без жалоб компилятора.Конечно, это небезопасно, и мы должны быть очень осторожны, чтобы не лгать компилятору.Что, технически, у нас есть:

// missing optional keys  
const val: { a?: number } = (1 > 2) ? { a: 1 } : {};
const oops1 = p("a", val); // definitely "Nah" at runtime, 
// but number | undefined at compile time, oops!

// subtypes with unknown extra keys
interface Animal { limbs: number; }
interface Cat extends Animal { lives: number; }
const cat: Cat = { limbs: 4, lives: 9 };
const animal: Animal = cat;
const oops2 = p("lives", animal); // definitely number at runtime, 
// but "Nah" at compile time, oops!

// prototype properties 
const regex = /hey/;
const oops3 = p("exec", regex); // definitely "Nah" at runtime, 
// but function at compile time, oops!

Это все ситуации, когда ваше предположение о том, что p реализует Prop, оказывается неверным.Возможно, есть и другие.Только вы знаете, если ваши варианты использования таковы, когда эти ситуации не имеют значения.Может быть, они не имеют значения, но вы должны знать об этом.

В любом случае, я надеюсь, что это поможет вам.Удачи!

2 голосов
/ 16 апреля 2019

Исправление вашей версии

Самое простое исправление вашей версии - ограничение K с помощью extends string:

type Prop = <K extends string, O extends {}>(k: K, o: O) => /*...*/

Проблема заключается в том, что при вызове вашей функции, как prop("foo", {foo: 'value'}) параметры типа заполняются как prop<string, {foo: string}> - поскольку K является общей «строкой», она не проходит проверку keyof O, и вы получаете "Nah".

Добавление ограничения extends string приведет к тому, что вместо этого параметры будут заполнены как prop<"foo", {foo: string}>, который функционирует так, как вы ожидаете.


Однако я не думаю, что этоверсия, вероятно, то, что вы хотите.Проблема в том, что если TS не может доказать во время компиляции, что ваш ключ является частью объекта, тип будет "Nah":

// According to TS, always returns "Nah", but in reality, it might not be.
function test(obj: object, someKey: string) {
    return prop(someKey, obj);
}

Альтернатива:

В качестве альтернативы, я бы предложил это:

function prop1<O>(k: string, o: O): O[keyof O] | "Nah" {
    return o.hasOwnProperty(k) ?
        o[k as keyof O] : 'Nah'; 
}

Основное отличие от вашей версии в том, что мы не делаем здесь ничего условного: вся идея в том, что мы не знаем, если k является ключом O или нет, поэтому он не очень помогает нам выполнять условные выражения: либо он вернет значение в O (O[keyof O]), либо вернет "Nah".

Если вы знаете, во время компиляции, что k является ключом O?Вы можете добавить перегрузки к этой функции для обработки этого случая, но это становится сложным, и вам, вероятно, лучше просто сделать o[k] и вообще не использовать эту функцию.

...