Отключить разрешение присваивать типы только для чтения типам, не предназначенным для чтения - PullRequest
0 голосов
/ 21 ноября 2018

Я искал только для чтения в машинописи.К сожалению, это не работает, как я надеюсь, что будет.Например, см. Код ниже:

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

Возможно ли каким-либо образом использовать тип только для чтения таким образом, чтобы не было возможности присвоить его другому типу, который не доступен только для чтения?Если это не так, я могу решить это любым другим способом?

1 Ответ

0 голосов
/ 21 ноября 2018

Ах, вы столкнулись с проблемой, которая раздражала кого-то достаточно, чтобы подать проблему с памятным заголовком "модификаторы только для чтения - это шутка" (которая с тех пор изменилась на что-тоболее нейтральным).Эта проблема отслеживается по адресу 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 модификаторов, поскольку они, в конце концов, шутка ?.Надеюсь, это поможет.Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...