如何更新深度嵌套的对象(类型缩小无法按预期工作,keyof编译错误)



我正在编写一个函数来更新一个深度嵌套的对象。目标是能够键入类似copyWith(person, 'address' ,'street', 'zip', 98443)的内容来生成深度嵌套属性与原始属性不同的副本。我认为这是一些函数库中的镜头功能。这有点奏效。请参阅下面的测试。但是,如果我为输入对象添加一个类型定义——我喜欢这样做,因为它可以帮助我确保创建一个有效的源对象——那么它就不再编译了,我也不知道为什么。在我试图调用copyWith的代码中,作为输入传递给copyWith的变量的类型不应该缩小到具有custom设置的signedIn路径吗?有没有什么方法可以在不必为狭窄的路径显式创建和使用类型的情况下实现这一点?

it('can clone object with a deep change', () => {
const copyWith = <
T,
A extends keyof T,
B extends keyof T[A],
C extends keyof T[A][B],
V
>(
i: T,
a: A,
b: B,
c: C,
value: V
) => ({ ...i, [a]: { ...i[a], [b]: { ...i[a][b], [c]: value } } });
type Privacy = { mode: 'public' } | { mode: 'private' };
type Settings =
| { settings: 'default' }
| { settings: 'custom'; privacy: Privacy };
type User =
| { state: 'unauthorized' }
| { state: 'signedIn'; settings: Settings };
// =========================================================
// If add :User type definition, the test no longer compiles
// =========================================================
const user = {
state: 'signedIn',
settings: { settings: 'custom', privacy: { mode: 'private' } },
};
if (user.state === 'signedIn' && user.settings.settings === 'custom') {
const copied = copyWith(user, 'settings', 'privacy', 'mode', 'public');
assert.equal(copied.settings.privacy.mode, 'public');
} else {
throw new Error('This path should not have been taken.');
}
});

你认为有区别的工会应该在这里工作是正确的。不幸的是,它不适用于嵌套对象(希望现在还没有(。您必须使用顶部并集来确定整个类型结构:

const copyWith = <
T,
A extends keyof T,
B extends keyof T[A],
C extends keyof T[A][B],
V
>(
i: T,
a: A,
b: B,
c: C,
value: V
) => ({ ...i, [a]: { ...i[a], [b]: { ...i[a][b], [c]: value } } });
type Privacy = { mode: 'public' | 'private' };
type User =
| { state: 'unauthorized' }
| { 
state: 'signedIn'; 
settings: { settings: 'default' } 
}
| { 
state: 'signedIn'; 
settings: { settings: 'custom'; privacy: Privacy } 
}
// =========================================================
// If add :User type definition, the test no longer compiles
// =========================================================
const user: User = {
state: 'signedIn',
settings: { settings: 'custom', privacy: { mode: 'private' } },
};
if (user.state === 'signedIn' && user.settings.settings === 'custom') {
const copied = copyWith(user, 'settings', 'privacy', 'mode', 'public');
assert.equal(copied.settings.privacy.mode, 'public');
} else {
throw new Error('This path should not have been taken.');
}

TS游乐场

此外。。。隐私类型可以简单一点;(

最新更新