Почему машинописный текст не создает объекты должным образом?

возьмите следующий код

type PersonKeys = "name" | "age"
type PersonR = Record<PersonKeys, string>

const test = (human: Partial<PersonR>): PersonR => {
    return {name: "", age: "", widen: true} // // fails perfect can't widen
}

const yolo = (k: PersonKeys) => {
    return test(CreatePair(k, new Date()));  // fails perfect, can't take anything but string
}

// Disgusting solution
const CreatePair = <T extends (number | string), A>(key: T, value: A): Record<T, A> => {
    return {[key]: value} as any;
} 

Проблема проста, я хочу, чтобы все, что возвращает PersonR, не могло расширяться, чтобы иметь возможность принимать свойства не на PersonR.

Однако я также хочу, чтобы все, что требует Partial of PersonR, принимало только VALID partial, что означает {[K as keyof Person]: string}, а не {[K as keyof Person]: Date}

Это кажется невозможным без взлома функции CreatePair.

Можете ли вы заставить оба комментария по-прежнему терпеть неудачу, не используя CreatePair? почему использование объекта вместо CreatePair со значением даты не приводит к сбою?


person Shanon Jackson    schedule 20.09.2018    source источник
comment
Мне непонятно, где именно вы хотите получить ошибку, а вы ее не получаете. Так ли это test({[k]: new Date()});?   -  person Titian Cernicova-Dragomir    schedule 20.09.2018
comment
да, для меня этот код в вашем комментарии должен потерпеть неудачу, потому что iv'e указанный PersonR может иметь только строковые типы   -  person Shanon Jackson    schedule 24.09.2018


Ответы (2)


Проблема проста, я хочу, чтобы все, что возвращает PersonR, не могло расширяться, чтобы иметь возможность принимать свойства не на PersonR.

TypeScript структурный. Вы всегда можете добавить недвижимость. например ваш пример widen может быть довольно легко назначен:

type PersonKeys = "name" | "age"
type PersonR = Record<PersonKeys, string>

const test = {name: "", age: "", widen: true};
const fail: PersonR = test; // PASS

Единственные случаи, когда это ошибка, - это прямое присвоение (что и делает ваш оператор return).

person basarat    schedule 20.09.2018
comment
Спасибо, но это не решает мою проблему. Мне не нравится расширение машинописных текстов, это приводит к ошибкам, если я перейду через Object.keys (неудачно), это теперь приведет к ошибкам. Я бы не сказал, что все мое приложение не допускает расширения, но я бы сказал, что 99% из них защищены. Мне нужен способ не расширяться и не провалиться на Дате - person Shanon Jackson; 20.09.2018

Насколько я понимаю ваш вопрос, вы хотите убедиться, что при вызове test аргумент будет иметь только ключи, указанные в PersonR. Итак, все они должны потерпеть неудачу:

test({ [k]: new Date() }); // has a string indexer, because of the computed property  
let o = { name: "", age: "", widen: true };
test(o); // not a direct assigment of an object literal, so this is allowed

Не существует общего переключателя компилятора, который бы глобально не допускал таких случаев. Можно утверждать, что в принципе такого переключателя быть не может, поскольку в обоих случаях тип аргумента является структурным подтипом типа параметра, поэтому это должно быть разрешено правилами ООП. Странной проверкой с точки зрения ООП является проверка лишних свойств, поскольку в этом случае мы не можем назначить структурный подтип его супертипу (в основном в этом случае мы говорим, что let base:BaseType = new DerivedType() является ошибкой).

При этом в реальном мире лишние свойства могут быть признаком ошибки (отсюда и проверка лишних свойств, реализованная в компиляторе). Хотя мы не можем запретить лишние свойства глобально, мы можем запретить аргументу иметь лишние свойства, используя условный тип. Мы используем параметр универсального типа для функции, чтобы получить фактический тип аргумента, и если этот тип аргумента имеет лишние свойства (или string индексаторы), мы добавляем тип строкового литерала к типу параметра, который вызовет ошибку:

type PersonKeys = "name" | "age"
type PersonR = Record<PersonKeys, string>

type StrictPropertyCheck<T, TExpected, TError> = Exclude<keyof T, keyof TExpected> extends never ? {} : TError;
const test = <T extends Partial<PersonR>>(human: T & StrictPropertyCheck<T, PersonR, "No extra properties!">): PersonR => {
    return {name: "", age: "", widen: true} // // fails perfect can't widen
}

const yolo = (k: PersonKeys) => {

    test({ [k]: new Date() }); // //error  
    let o = { name: "", age: "", widen: true };
    test(o); // error
}
person Titian Cernicova-Dragomir    schedule 20.09.2018
comment
Здравствуйте, Тициан, я согласен с тем, что вы упомянули о структурной типизации, однако есть некоторые вещи, которые все еще меня смущают. 1: если вы измените new Date () на строку в приведенном выше фрагменте кода, он все равно не работает. 2: Если PersonR - это запись со строковыми типами (ТОЛЬКО), почему она может даже принимать дату? 3: Почему, когда литерал объекта создается с помощью динамического k, который является ключом PersonR, результирующий тип вывода объекта - [k: string]: sometype, а не .... [k: PersonKeys]: sometype - person Shanon Jackson; 24.09.2018