问题描述
我一直在查看TypeScrip中的只读类型。遗憾的是,它并没有像我希望的那样工作。例如,请参见下面的代码:
interface User{
firstName: string;
lastName: string;
}
const user: Readonly<User> = {
firstName: "Joe",
lastName: "Bob",
};
const mutableUser: User = user; //Is it possible to disallow this?
user.firstName = "Foo" //Throws error as expected
mutableUser.firstName ="Bar"//This works
有没有可能以某种方式使用只读类型,而不允许将其分配给另一个非只读类型?如果不是,我可以用其他方法解决它吗?推荐答案
啊,您遇到了一个问题,这个问题让一些人非常恼火,以至于提交了一个issue,标题令人难忘"readonly modifiers are a joke"(后来更改为更中性的名称)。这个问题正在Microsoft/TypeScript#13347上被跟踪,但似乎没有太多进展。目前,我们只需处理readonly
属性不影响可分配性的事实。
那么,有哪些可行的解决方法?
最干净的方法是放弃readonly
属性,而是使用某种类型的映射,通过类似于getter函数的方式将对象转换为您真正可以读取的对象。例如,如果只读属性替换为返回所需值的函数:
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
或类似地,如果只读对象仅公开单个getter函数:
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
它的使用很烦人,但肯定会增强可读性(可读性?🤷♂️)的精神,而且您不可能意外地写入只读内容。
另一种解决方法是运行帮助器函数,该函数将只接受可变的值,因为@TitianCernicova-Dragomir具有suggested。可能是这样的:
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
以区分structurally和T
,但它和getter-函数方法一样麻烦。问题是,假设您希望能够将可变值赋给只读变量,但又想阻止将只读值赋给可变变量,那么只读修饰符需要扩大T
的类型,而不是缩小它的范围。这将选项限制为类似Readonly<T> = {[K in keyof T]: T[K] | Something}
或Readonly<T> = T | Something
的内容。但在每种情况下,实际读取只读属性都变得非常困难,因为您必须缩小类型的范围。如果每次读取属性时都需要样板,那么不妨使用一个getter函数。所以,忘了这一点吧。
总结:如果您真的想强制使用无法编写的属性,我认为getter函数方法可能是您最好的选择。或者,您可能应该放弃readonly
修饰符,因为它们毕竟是一个笑话🤡。希望这能帮上忙。祝你好运!
这篇关于禁用允许将只读类型分配给非只读类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!