禁用允许将只读类型分配给非只读类型
Disable allowing assigning Readonly types to non-readonly types
我一直在看打字稿中的只读类型。可悲的是,它没有像我希望的那样工作。例如,请看下面的代码:
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 with the memorable title "readonly modifiers are a joke" (which has since changed to something more neutral). The issue is being tracked at Microsoft/TypeScript#13347,但似乎没有太多进展。现在,我们只需要处理 readonly
属性不影响可分配性的事实。
那么,有哪些可行的解决方法?
最干净的方法是放弃 readonly
属性,而是使用某种映射,通过 getter 函数之类的东西将对象变成您真正只能读取的东西。例如,如果将只读属性替换为 return 所需值的函数:
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
使用起来很烦人,但绝对体现了只读精神(readonlity?♂️),你不能不小心写入只读内容。
另一个解决方法是 运行 一个只接受可变值的辅助函数,如 @TitianCernicova-Dragomir has 。可能是这样的:
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
和return类型的任何值 Readonly<T>
,但mutable
函数将只接受类型为T
和return的值已经是可变的。您必须记住对您希望可变的任何值调用 mutable()
。这是相当容易出错的,所以我真的不推荐这种方法。
我也想过制作一个伪造的 Readonly<T>
类型的想法,它修改 T
以区分它 structurally 和 T
,但是它和 getter-function 方法一样麻烦。问题是,假设您希望能够将可变值分配给只读变量,但您希望被阻止将只读值分配给可变变量,只读修饰符需要扩大 T
的类型,而不是缩小它.这将选项限制为 Readonly<T> = {[K in keyof T]: T[K] | Something}
或 Readonly<T> = T | Something
。但是在每种情况下,实际读取只读属性都变得非常困难,因为您必须缩小类型范围。如果每次阅读 属性 时都需要样板文件,您不妨使用 getter 函数。所以,算了。
总结:如果您真的想强制执行无法写入的属性,我认为 getter 函数方法可能是您最好的选择。或者也许您应该放弃 readonly
修饰符,因为它们毕竟是个玩笑。希望有所帮助。祝你好运!
这是我的绝招。
我向 Mutable
类型添加了一个符号 属性,这样就无法为其分配非 Mutable
值。该符号有意不从具有 Mutable
定义的文件中导出,因此如果不通过 makeMutable
函数就无法创建有效的 Mutable
对象。
在文件 Mutable.ts 中:
const mutableMarker = Symbol("mutable marker");
type MutableMarker = typeof mutableMarker;
export type Mutable<T> = {
-readonly [P in keyof T]: T[P];
} & {
[mutableMarker]: MutableMarker;
};
export function makeMutable<T>(value: T): Mutable<T> {
return { ...value, [mutableMarker]: mutableMarker };
}
用法:
// Everything should be readonly by default.
interface User {
readonly firstName: string;
readonly lastName: string;
}
// This is normal.
const readonlyUser: User = {
firstName: "Joe",
lastName: "Bob",
};
// Error. Can't assign to readonly property.
readonlyUser.firstName = "";
// This creates a mutable copy, so the original object won't be modified.
const mutableUser = makeMutable(readonlyUser);
mutableUser.firstName = "";
// This is fine. A readonly variable can be assigned the mutable value.
const works: User = mutableUser;
// Error. Can't assign to readonly property.
works.firstName = "";
// Error. Property '[mutableMarker]' is missing in type 'User'.
const fails: Mutable<User> = readonlyUser;
如果您将可变值分配给只读变量,则可能会产生错误,因为可以访问原始可变值的代码可能会意外地改变它。但我认为这是一个正交问题。
我一直在看打字稿中的只读类型。可悲的是,它没有像我希望的那样工作。例如,请看下面的代码:
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 with the memorable title "readonly modifiers are a joke" (which has since changed to something more neutral). The issue is being tracked at Microsoft/TypeScript#13347,但似乎没有太多进展。现在,我们只需要处理 readonly
属性不影响可分配性的事实。
那么,有哪些可行的解决方法?
最干净的方法是放弃 readonly
属性,而是使用某种映射,通过 getter 函数之类的东西将对象变成您真正只能读取的东西。例如,如果将只读属性替换为 return 所需值的函数:
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
使用起来很烦人,但绝对体现了只读精神(readonlity?♂️),你不能不小心写入只读内容。
另一个解决方法是 运行 一个只接受可变值的辅助函数,如 @TitianCernicova-Dragomir has
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
和return类型的任何值 Readonly<T>
,但mutable
函数将只接受类型为T
和return的值已经是可变的。您必须记住对您希望可变的任何值调用 mutable()
。这是相当容易出错的,所以我真的不推荐这种方法。
我也想过制作一个伪造的 Readonly<T>
类型的想法,它修改 T
以区分它 structurally 和 T
,但是它和 getter-function 方法一样麻烦。问题是,假设您希望能够将可变值分配给只读变量,但您希望被阻止将只读值分配给可变变量,只读修饰符需要扩大 T
的类型,而不是缩小它.这将选项限制为 Readonly<T> = {[K in keyof T]: T[K] | Something}
或 Readonly<T> = T | Something
。但是在每种情况下,实际读取只读属性都变得非常困难,因为您必须缩小类型范围。如果每次阅读 属性 时都需要样板文件,您不妨使用 getter 函数。所以,算了。
总结:如果您真的想强制执行无法写入的属性,我认为 getter 函数方法可能是您最好的选择。或者也许您应该放弃 readonly
修饰符,因为它们毕竟是个玩笑。希望有所帮助。祝你好运!
这是我的绝招。
我向 Mutable
类型添加了一个符号 属性,这样就无法为其分配非 Mutable
值。该符号有意不从具有 Mutable
定义的文件中导出,因此如果不通过 makeMutable
函数就无法创建有效的 Mutable
对象。
在文件 Mutable.ts 中:
const mutableMarker = Symbol("mutable marker");
type MutableMarker = typeof mutableMarker;
export type Mutable<T> = {
-readonly [P in keyof T]: T[P];
} & {
[mutableMarker]: MutableMarker;
};
export function makeMutable<T>(value: T): Mutable<T> {
return { ...value, [mutableMarker]: mutableMarker };
}
用法:
// Everything should be readonly by default.
interface User {
readonly firstName: string;
readonly lastName: string;
}
// This is normal.
const readonlyUser: User = {
firstName: "Joe",
lastName: "Bob",
};
// Error. Can't assign to readonly property.
readonlyUser.firstName = "";
// This creates a mutable copy, so the original object won't be modified.
const mutableUser = makeMutable(readonlyUser);
mutableUser.firstName = "";
// This is fine. A readonly variable can be assigned the mutable value.
const works: User = mutableUser;
// Error. Can't assign to readonly property.
works.firstName = "";
// Error. Property '[mutableMarker]' is missing in type 'User'.
const fails: Mutable<User> = readonlyUser;
如果您将可变值分配给只读变量,则可能会产生错误,因为可以访问原始可变值的代码可能会意外地改变它。但我认为这是一个正交问题。