Ах, вы столкнулись с проблемой, которая раздражала кого-то достаточно, чтобы подать проблему с памятным заголовком "модификаторы только для чтения - это шутка" (которая с тех пор изменилась на что-тоболее нейтральным).Эта проблема отслеживается по адресу Microsoft / TypeScript # 13347 , но, похоже, в этом нет особого движения.Сейчас нам просто нужно учитывать тот факт, что свойства readonly
не влияют на присваиваемость.
Итак, какие обходные пути возможны?
Самое чистое - отказаться от readonly
свойств и вместо этого использовать какое-то отображение, которое превращает объект в нечто, из чего вы действительно можете только читать, через что-то вроде функций-получателей.Например, если свойства только для чтения заменяются функциями, которые возвращают желаемое значение:
function readonly<T extends object>(x: T): { readonly [K in keyof T]: () => T[K] } {
const ret = {} as { [K in keyof T]: () => T[K] };
(Object.keys(x) as Array<keyof T>).forEach(k => ret[k] = () => x[k]);
return ret;
}
const user = readonly({
firstName: "Joe",
lastName: "Bob",
});
const mutableUser: User = user; // error, user is wrong shape
// reading from a readonly thing is a bit annoying
const firstName = user.firstName();
const lastName = user.lastName();
// but you can't write to it
user.firstName = "Foo" // doesn't even make sense, "Foo" is not a function
user.firstName = () => "Foo" // doesn't work because readonly
Или аналогично, если объект только для чтения предоставляет только одну функцию получения:
function readonly<T extends object>(x: T): { get<K extends keyof T>(k: K): T[K] } {
return { get<K extends keyof T>(k: K) { return x[k] } };
}
const user = readonly({
firstName: "Joe",
lastName: "Bob",
});
const mutableUser: User = user; // error, user is wrong shape
// reading from a readonly thing is a bit annoying
const firstName = user.get("firstName");
const lastName = user.get("lastName");
// but you can't write to it
user.firstName = "Foo" // doesn't even make sense, firstName not a property
Это раздражаетиспользовать, но определенно поддерживает дух готовности к чтению (readonlity? ?♂️), и вы не можете случайно написать что-то для чтения.
Другой обходной путь - запустить вспомогательную функцию, которая будет принимать только изменяемыезначения, такие как @ TitianCernicova-Dragomir имеет рекомендуемое .Возможно, вот так:
type IfEquals<T, U, Y = unknown, N = never> =
(<V>() => V extends T ? 1 : 2) extends
(<V>() => V extends U ? 1 : 2) ? Y : N;
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type IsMutable<T, Y=unknown, N=never> = IfEquals<T, Mutable<T>, Y, N>
const readonly = <T>(x: T): Readonly<T> => x;
const mutable = <T>(
x: T & IsMutable<T, unknown, ["OOPS", T, "has readonly properties"]>
): Mutable<T> => x;
const readonlyUser = readonly({
firstName: "Joe",
lastName: "Bob",
});
const mutableUser = mutable(
{ firstName: "Bob", lastName: "Joe" }
); // okay
const fails: User = mutable(readonlyUser); // error, can't turn readonly to mutable
// msg includes ["OOPS", Readonly<{ firstName: string; lastName: string; }>
// , "has readonly properties"]
const works = readonly(mutableUser); //okay, can turn mutable to readonly
Здесь функция readonly
примет любое значение типа T
и вернет Readonly<T>
, но функция mutable
будет принимать только значения, которые уже изменяемы,Не забывайте вызывать mutable()
для любого значения, которое вы ожидаете изменить.Это довольно подвержено ошибкам, поэтому я не очень рекомендую этот метод.
Я также поэкспериментировал с идеей создать фальшивый тип Readonly<T>
, который изменил бы T
таким образом, чтобычтобы отличить его структурно от T
, но он был таким же громоздким, как метод getter-function.Проблема заключается в том, что, предполагая, что вы хотите иметь возможность присваивать изменяемые значения переменным только для чтения, но не хотите назначать изменяемые значения только переменным, модификатор только для чтения должен расширять тип T
, а не сужать его.Это ограничивает параметры до Readonly<T> = {[K in keyof T]: T[K] | Something}
или Readonly<T> = T | Something
.Но в каждом случае становится действительно трудно читать свойства только для чтения, так как вам приходится сужать типы обратно.Если вам нужен шаблон каждый раз, когда вы читаете свойство, вы также можете использовать функцию получения.Итак, забудьте об этом.
Подводя итог: я думаю, что метод функции getter - это, вероятно, ваш лучший выбор, если вы действительно хотите применить свойства, которые не могут быть записаны.Или, может быть, вы просто должны отказаться от readonly
модификаторов, поскольку они, в конце концов, шутка ?.Надеюсь, это поможет.Удачи!