typescript 具有泛型的递归不可变定义

bvjveswy  于 2023-01-14  发布在  TypeScript
关注(0)|答案(2)|浏览(113)
    • bounty将在5天后过期**。回答此问题可获得+100的声誉奖励。OliverRadini正在寻找来自声誉良好的来源的答案

我有一个类型定义来使一个类型不可变,我希望它能够一直确定属性的类型并使它们不可变,这个定义的代码如下(Immutable)。
我的问题是,当我尝试将它用于泛型类型时,编译器确定这些类型不重叠,因此我不能将对象强制转换为不可变的。我尝试做的是能够使用我拥有的Immutable的定义,但让它用于泛型。
如果我尝试不使用泛型,它会工作的很好。同样,如果我改变了不可变的定义(删除as T[K] extends (...args: Array<any>) => any ? never),那么类型转换将使用泛型。所以我想这是与此有关的。
我对as T[K] extends (...args: Array<any>) => any ? never的理解是,如果T的属性K是一个函数,那么就不要包含它;但是我可能错了,我最初并没有写这段代码。
下面是代码:

type Immutable<T> = T extends string | number | boolean | bigint | symbol | undefined | null
    ? Readonly<T>
    : T extends object
    ? { readonly [K in keyof T as T[K] extends (...args: Array<any>) => any ? never : K]: Immutable<T[K]> }
    : never;

interface IWithValue {
    value: number;
}

interface IWithSelected<TData> {
    selected: TData;
}

function f<T extends IWithValue>(
    g: (x: Immutable<IWithSelected<T>>) => void,
    x: IWithSelected<T>
) {
    // This doesn't work because it doesn't believe the types overlap sufficiently
    g(x as Immutable<IWithSelected<T>>);
}

function f2<T extends number>(
    g: (x: Immutable<IWithSelected<T>>) => void,
    x: IWithSelected<T>
) {
    // Another generic using a number instead makes no difference
    g(x as Immutable<IWithSelected<T>>);
}

function f3(
    g: (x: Immutable<IWithSelected<{ a: 1 }>>) => void,
    x: IWithSelected<{ a: 1 }>
) {
    // Removing the generic removes the issue
    g(x as Immutable<IWithSelected<{ a: 1 }>>);
}

function f4<T>(
    g: (state: Immutable<IWithSelected<T>>) => void,
    x: IWithSelected<T>
) {
    // This one works
    g(x as Immutable<typeof x>);
}

function f5<T>(
    g: (state: Immutable<T>) => void,
    x: T
) {
    // This one works
    g(x as Immutable<T>);
}

function f6<T>(
    g: (state: Immutable<IWithSelected<T>>) => void,
    x: IWithSelected<T>
) {
    // This one gives as error
    g(x as Immutable<T>);
}

上面的Playground链接
但是改变Immutable的定义,错误就消失了:

type Immutable<T> = T extends string | number | boolean | bigint | symbol | undefined | null
    ? Readonly<T>
    : T extends object
    ? { readonly [K in keyof T]: Immutable<T[K]> }
    : never;

interface IWithValue {
    value: number;
}

interface IWithSelected<TData> {
    selected: TData;
}

function f<T extends IWithValue>(
    g: (x: Immutable<IWithSelected<T>>) => void,
    x: IWithSelected<T>
) {
    // no error on this one
    g(x as Immutable<IWithSelected<T>>);
}

上面的Playground链接

fykwrbwg

fykwrbwg1#

在我看来,a : Treadonly_A : Immutable<T>非常不同
不能将强制转换为readonly_A。
你的类型是正确的,但是变量不正确。你需要的是将a转换为readonly_A,这不能是同一个示例。你需要的是你的对象的一个只读副本,以便传递给g函数。
这看起来很有效:

function readonlyCopy <T>(something:T) : Immutable<T>  {
    const readonlyCopy :  Immutable<T> =  JSON.parse(JSON.stringify(something))
    return readonlyCopy;
}

那么

g(readonlyCopy(x));
ggazkfy8

ggazkfy82#

好吧,我想我明白了。
查看只读定义:类型只读= {只读[T的关键字中的P]:T[P]; };
和值类型的不可变异常:

string | number | boolean | bigint | symbol | undefined | null

我已测试只读:

type ROstring = Readonly<string>; // is string 
type ROnumber = Readonly<number>; // is number 
type ROboolean = Readonly<boolean>; // is boolean 
type RObigint = Readonly<bigint>; // is bigint 
type ROsymbol = Readonly<symbol>; // is symbol 
type ROundefined = Readonly<undefined>; // is undefined 
type ROnull = Readonly<null>; // is null

所以这个宣言:

readonly [P in keyof T]: T[P];

对这些类型进行标识。不变性是如此相似,我们可以删除异常:

type Immutable<T> = { readonly [K in keyof T  as T[K] extends (...args: Array<any>) => any ? never : K]: Immutable<T[K]> }

所有演员现在都工作。(除了f6,但这一个应该崩溃)。
甚至更好的g(x)也是有效的。
游戏结束后有奖金

相关问题