Разбор
Используя необязательный знак + вместе с модификаторами типа, мы можем создавать более явные и читабельные объявления типов. Также можем использовать знак - (минус) для удаления необязательных объявлений из свойств ?.
Например: у нас есть interface; можем использовать модификаторы перебора типов, чтобы сделать все его свойства доступными только для чтения readonly.
interface ICar {
name: string;
age: number;
}
type ReadonlyCar = {
readonly [K in keyof ICar]: ICar[K];
};Подобный тип может быть полезен, например, для state приложения Redux, потому что state должно быть неизменным.
После создания объекта ни одно из его свойств не должно изменяться. Модификаторы перебора типов удобны тем, что позволяют расширять существующие типы и применять изменения сразу ко всем их свойствам.
Теперь, если мы объявим две модели машины car: первый объект изменчив, другой - только для чтения; затем попробуем изменить их данные, то заметим, что во втором случае у нас будет ошибка.
const car: ICar = {
name: "Mercedes",
age: 2
};
const readOnlyCar: ReadonlyCar = {
name: "BMW",
age: 5
};
car.age = 8;
readOnlyCar.age = 10; // Cannot assign to 'age' because it is a read-only propertyВ случае с readOnlyCar.age, TypeScript говорит нам, что age только для чтения - Cannot assign to 'age' because it is a read-only property.
И это нормально, ведь мы указали, что все его свойства только для чтения. Статус readonly (только для чтения) - это не единственное, что мы можем изменить в модификаторах перебора типов.
Мы можем указать, что все свойства не обязательны через ?.
type ReadonlyCar = {
readonly [K in keyof ICar]?: ICar[K];
};Также, можем указать что все свойства - строки, или сделать каждое свойство как объединение их исходного типа и строки через вертикальную черту |. Вариантов много.
type ReadonlyCar = {
readonly [K in keyof ICar]?: ICar[K] | string;
};Однако, с синтаксисом readonly [K in keyof ICar]: ICar[K]; мы можем только добавлять новые элементы в существующие типы. Можем добавить флаг readonly, или ?.
Если у оригинального типа есть свойство которое необязательно, например:
interface ICar {
name: string;
age: number;
color?: string;
}Мы можем убрать флаг необязательно - ?. Начиная с TypeScript 2.8, стало возможным добавлять знак минус - перед символом, который хотим удалить.
type ReadonlyCar = {
readonly [K in keyof ICar]-?: ICar[K];
};Как только мы добавили знак минус, TypeScript тут же начал выдавать ошибку в const readOnlyCar. Это потому что мы внезапно пропустили обязательное свойство в этом новом объекте. Как только добавим новое поле color, ошибка исчезнет.
const readOnlyCar: ReadonlyCar = {
name: "BMW",
age: 5,
color: "black"
};Поскольку у нас есть гибкость со знаком -, чтобы удалять флаги из наших типов, знак + также был добавлен к этой функции. Мы можем более четко сказать, что именно добавляем и что удаляем.
type ReadonlyCar = {
+readonly [K in keyof ICar]-?: ICar[K];
};Теперь другим разработчикам, читающим этот тип, стало понятнее, что мы берем оригинальный интерфейс ICar, удаляем все необязательные модификаторы -? и добавляем флаг +readonly для всех свойств.
Модификаторы перебора типа полезны, если:
- есть интерфейс, который невозможно изменить напрямую (например, из библиотеки);
- есть интерфейс, который хотим продолжать использовать для некоторых целей, и создать его небольшую вариацию (с использованием модификаторов) для использования в других целях.
В обоих случаях модификаторы перебора типа следуют форме исходного интерфейса: если он изменится, они просто применят свои правила к новой форме.