禁用允许将只读类型分配给非只读类型

时间:2018-11-21 13:17:38

标签: typescript immutability

我一直在看打字稿中的只读类型。可悲的是,它没有像我希望的那样起作用。例如,请参见下面的代码:

import csv

f1 = open("record.csv", "r")
reader = csv.DictReader(f1, delimiter='\t') #DictReader let you read the csv header

f2 = open("workingfile.csv", "r")
reader2 = csv.DictReader(f2, delimiter='\t')

f3 = csv.writer(open("newfile.csv", "w"))

record = [] #create a list to append values from first file
workingfile = [] #create a list to append values from second file

for row in reader:
    record.append(row['DOE']) #take values from DOE column
print(values)


for row in reader2:
    workingfile.append(row['Day_of_exp']) #take values from Day_of_exp column
print(values2)

for v in workingfile:
    if v in record:
        f3.writerow([v])
        print(v)

f1.close()
f2.close()

是否可以通过某种方式以不允许将其分配给非只读类型的方式使用只读类型?如果不是,我可以通过其他方式解决吗?

2 个答案:

答案 0 :(得分:1)

嗯,您遇到了一个问题,该问题使某人感到恼火,无法提交带有令人难忘的标题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

使用起来很烦人,但是绝对可以增强只读性(readonlity?‍♂️)的精神,并且您不能不小心写只读内容。


另一种解决方法是运行一个辅助函数,该函数仅接受可变值,因为@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>类型的想法,该类型以structurallyT区分的方式修改了T,但是和吸气功能方法一样麻烦。问题是,假设您希望能够将可变值分配给只读变量,但又希望避免将可变值分配给可变变量,则readonly修饰符需要加宽T的类型,而不是使其变窄。这将选项限制为Readonly<T> = {[K in keyof T]: T[K] | Something}Readonly<T> = T | Something之类的东西。但是在每种情况下,实际上都很难读取只读属性,因为您必须缩小类型的范围。如果每次读取属性都需要样板,则最好使用getter函数。所以,算了吧。


总结:如果您确实想强制执行无法编写的属性,那么我认为getter函数方法可能是最好的选择。或者,也许您应该放弃readonly修饰符,因为它们毕竟是个玩笑。希望能有所帮助。祝你好运!

答案 1 :(得分:0)

这是我的绝招。

我向 Mutable 类型添加了一个符号属性,以便无法为其分配非 Mutable 值。该符号有意不从具有 Mutable 定义的文件中导出,因此不通过 Mutable 函数就无法创建有效的 makeMutable 对象。

在文件 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;

如果将可变值分配给只读变量,则存在潜在的错误源,因为可以访问原始可变值的代码可能会意外地改变它。但我认为这是一个正交问题。