`keyof` сообщил об ошибке компиляции при использовании универсального типа

#typescript

#typescript

Вопрос:

Сначала у меня есть определение типа, отображающее все свойства в число с помощью keyof

 type Numeric<T> = {
    [K in keyof T]: number
}
  

Ниже приведен класс, который я буду использовать.

 class Entity {
    aNumber: number;
}
  

Ниже приведена функция, которая принимает аргумент универсального типа и локальную переменную с типом Numberic<T> . Но когда я назначил { aNumber: 1 } , это выдало ошибку компиляции.

 const fn = <T extends Entity>() => {
    const n: Numeric<T> = {
//        ^
//        Type '{ aNumber: number; }' is not 
//        assignable to type 'Numeric<T>'
        aNumber: 1
    };
};
  

Я не знаю, почему { aNumber: number; } не может быть назначен Numeric<T> , поскольку аргумент типа T должен быть расширен с Entity и он должен содержать ключ с именем aNumber . Это означает, что aNumber должен быть ключ типа T и должен иметь возможность присваиваться Numeric<T> .

Ответ №1:

Сообщение об ошибке вводит в заблуждение. Однако есть ошибка, и это то, что ловит TypeScript. Действительно, нет ничего плохого в Entity :

 type Numeric<T> = {
    [K in keyof T]: number
}
interface Entity {
    aNumber: number
}

// No error
const n: Numeric<Entity> = {
    aNumber: 1
};
  

Однако, когда вы говорите T extends Entity , он открывает его для значений, отличных от числа, например

 type Numeric<T> = {
    [K in keyof T]: number
}
interface Entity {
    aNumber: number
}

// No error
const n: Numeric<Entity> = {
    aNumber: 1
};

interface X extends Entity {
    notANumber: string
}
// Error. Thank you TypeScript
const o: Numeric<X> = {
    aNumber: 1
};
  

Отсюда и ошибка при использовании T extends Entity в Numeric .

Комментарии:

1. Нет, я не думаю, что ваше объяснение правильное. Я вижу ошибку в части TS, но не в вашем случае. Проверьте мой ответ.

Ответ №2:

Ограничение, налагаемое <T extends Entity> , следует рассматривать как минимальное требование, которому T должно соответствовать. Это означает, что « T должно содержать по крайней мере aNumber: number пару».

Давайте посмотрим const n: T . Это означает, что «n должно содержать, по крайней мере, любые пары ключ-значение, которые находятся в T «.

Теперь мы знаем, T имеет aNumber: number пару, но помните, что это всего лишь минимальное требование. T также может быть { aNumber: number; aString: string } . Вот почему это также приведет к ошибке.

 // if you understand this:
const n: { aNumber: number; aString: string } = { aNumber: 42 }  // error, of course
// you see why this is an error:
const n: T = { aNumber: 42 }  // also error
  

Вы никогда не сможете сказать, что именно T . keyof T все в порядке, потому что мы точно знаем хотя бы об одном из T ключей.

 const k: keyof T = "aNumber"
  

Чтобы доказать мою точку зрения, давайте рассмотрим нестандартный случай. Возьмем, к примеру, код @basarat, здесь X нет genreic.

 type Numeric<T> = {
    [K in keyof T]: number
}

type OptionalNumeric<T> = {
    [K in keyof T]?: number
}

interface Entity {
    aNumber: number
}

interface X extends Entity {
    notANumber: string
}

// Error, because `notANumber` is missing
const o: Numeric<X> = { aNumber: 1 };
// Correct
const o1: Numeric<X> = { aNumber: 1, notANumber: 2 };
// Also correct, because all keys are optional.
const o2: OptionalNumeric<X> = { aNumber: 1 };
  

Примечание сбоку. Выше следует объяснить ваш случай. Тем не менее, я считаю, что в TS есть ошибка.

Я думаю, что если вы используете OptionalNumeric в своем исходном случае, то то, что вы хотите, должно сработать. Но, оказывается, это не так. Должен быть дефект, когда задействованы параметры универсального типа.

Комментарии:

1. ошибка все еще существует, хотя я использовал type Numeric<T> = { [K in keyof T]?: number } . Но если я передаю аргумент типа в Numeric<T> , он работает хорошо, например const n: Numeric<Entity> . Но я использую общий аргумент, так как то, что я сделал в примере const fn = <T extends Entity>() => { const n: Numeric<T> = { aNumber: 1 }; }; , не удалось.

2. @ShaunXu Я говорю следующее: а) то, что вы пытаетесь сделать, неправильно; б) необязательный ключ должен работать, но это не так, и его следует считать ошибкой. У меня есть только объяснение, а не решение.

3. Обходной путь здесь (при условии, что мы используем OptionalNumeric ) заключается в присвоении n типу OptionalNumeric<T amp; Entity> вместо OptionalNumeric<T> . Однако я не знаю, известна ли проблема с OptionalNumeric<T> .

4. Я думал, что это связано с Microsoft / TypeScript # 13442 , но я думаю, что это не так.