Javascript 通过动态查找关键字修改对象值TypeScript
我正在尝试编写一个函数,该函数返回另一个函数,该函数允许我修改TypeScript中对象的键。这是用于React减速器内 如果我有一个带有keysJavascript 通过动态查找关键字修改对象值TypeScript,javascript,reactjs,typescript,Javascript,Reactjs,Typescript,我正在尝试编写一个函数,该函数返回另一个函数,该函数允许我修改TypeScript中对象的键。这是用于React减速器内 如果我有一个带有keysdogs、dogIds的状态对象,以及另一个带有keyscats和catIds的状态对象,我想编写一个patchGroup(group:string),这样,如果我传入cat或dog,它将返回一个函数,允许我修改这些键 我不熟悉打字脚本,所以我尝试用字符串索引。但是,TypeScript会出错,因为我无法使用字符串索引我的其他类型 显式声明/使用类型的
dogs
、dogIds
的状态对象,以及另一个带有keyscats
和catIds
的状态对象,我想编写一个patchGroup(group:string)
,这样,如果我传入cat
或dog
,它将返回一个函数,允许我修改这些键
我不熟悉打字脚本,所以我尝试用字符串索引。但是,TypeScript会出错,因为我无法使用字符串索引我的其他类型
显式声明/使用类型的示例
例如,我为狗定义了以下状态:
interface DogsState {
dogs: Array<Dogs>;
dogIds: Array<string>;
loading: boolean;
// etc.
}
我现在想要一只猫:
接口CATSSState{
猫:阵列;
catIds:数组;
加载:布尔;
//等等。
}
稍微修改patchDog
是很简单的,我不想重复我的代码
当前尝试使用字符串(不工作)
常量patchGroup=(组:字符串)=>{
//与“patchDog”类似的逻辑`
常数补丁=(
州:DogsState | CatsState,
有效负载:DogResponse | DogCreateRequest | CatResponse | CatCreateRequest,
) => {
const groupIdx=state[`{$group}Id`].indexOf(有效负载[`${group}`][`${group}Id`]);
//字符串失败;需要以某种方式获取确切的密钥
}
返回补丁;
}
//======================状态对象=================
接口DogsState{
狗:阵列;
dogIds:数组;
加载:布尔;
//等等。
}
接口CATS状态{
猫:阵列;
catIds:数组;
加载:布尔;
//等等。
}
//======================请求=================
类型请求={
dogId:字符串;
};
类型DogCreateRequest={
狗:狗;
};
类型CatRequest={
catId:字符串;
};
类型CatCreateRequest={
猫:猫;
};
//======================回复=================
类型响应={
狗:狗;
最近更新:编号;
错误:字符串;
}
类型CatResponse={
猫:猫;
最近更新:编号;
错误:字符串;
}
//======================应用程序对象=================
接口狗{
dogId:字符串;
名称:字符串;
//……等等。
}
接口猫{
catId:字符串;
名称:字符串;
//等等。
}
到目前为止,我已经调查了:
- (仍在阅读)
“cat”
是一种文本字符串类型,并且可以将其与“dog”
区分开来,但它无法知道“cat”+“Id”
将导致字符串文本类型“catId”
。它只知道“cat”+“Id”
属于字符串类型
因此,当您使用“Cat”+“Id”
索引到Cat
时,它不知道该做什么。由于缺少字符串索引签名,它无法索引到具有通用字符串的Cat
所以这取决于你的目标是什么。如果您的目标只是抑制错误,而不担心编译器验证的安全性,那么您可以这样开始使用:
state[`{$group}Ids` as keyof typeof state]
const patchGroup = (group: string) => {
const patch = (
state: DogsState | CatsState,
payload: DogResponse | DogCreateRequest | CatResponse | CatCreateRequest,
): DogsState | CatsState => {
const groupIdx = (state[`{$group}Ids` as keyof typeof state] as any as Array<string>).
indexOf((payload[`${group}` as keyof typeof payload] as Cat | Dog)[`${group}Id` as keyof (Cat | Dog)] as any as string);
return {
...state,
loading: false,
[`{$group}s`]: [...state[`{$group}s` as keyof typeof state] as any as Array<Cat | Dog>, payload[group as keyof typeof payload]],
[`{$group}Ids`]: [...state[`{$group}Ids` as keyof typeof state] as any as Array<string>, payload[group as keyof typeof payload][`{$group}Id`]]
} as any as DogsState | CatsState;
}
return patch;
}
interface CatBundle {
ks: "cats",
id: "catId",
ids: "catIds"
obj: Cat,
req: CatRequest,
crq: CatCreateRequest,
rsp: CatResponse,
stt: CatsState
}
interface DogBundle {
ks: "dogs",
id: "dogId",
ids: "dogIds"
obj: Dog,
req: DogRequest,
crq: DogCreateRequest,
rsp: DogResponse,
stt: DogsState
}
interface Animals {
dog: DogBundle,
cat: CatBundle
}
// start implementation
const patchGroup = <K extends keyof Animals>(
k: K) => (state: Animals[K]['stt'], payload: Animals[K]['rsp'] | Animals[K]['crq']
): Animals[K]['stt'] => {
const id = k + "Id" as Animals[K]["id"];
const ks = k + "s" as Animals[K]["ks"];
const ids = k + "Ids" as Animals[K]["ids"];
但后来你遇到了一个新问题。。。您的内部功能似乎接受状态
和有效负载
,这是Cat
和狗
版本的联合。这也不是类型安全的,因为它允许您调用补丁组(“dog”)(someCatState,someCatPayload)
:
为了抑制这些合法的类型错误,您需要像这样开始到处断言:
state[`{$group}Ids` as keyof typeof state]
const patchGroup = (group: string) => {
const patch = (
state: DogsState | CatsState,
payload: DogResponse | DogCreateRequest | CatResponse | CatCreateRequest,
): DogsState | CatsState => {
const groupIdx = (state[`{$group}Ids` as keyof typeof state] as any as Array<string>).
indexOf((payload[`${group}` as keyof typeof payload] as Cat | Dog)[`${group}Id` as keyof (Cat | Dog)] as any as string);
return {
...state,
loading: false,
[`{$group}s`]: [...state[`{$group}s` as keyof typeof state] as any as Array<Cat | Dog>, payload[group as keyof typeof payload]],
[`{$group}Ids`]: [...state[`{$group}Ids` as keyof typeof state] as any as Array<string>, payload[group as keyof typeof payload][`{$group}Id`]]
} as any as DogsState | CatsState;
}
return patch;
}
interface CatBundle {
ks: "cats",
id: "catId",
ids: "catIds"
obj: Cat,
req: CatRequest,
crq: CatCreateRequest,
rsp: CatResponse,
stt: CatsState
}
interface DogBundle {
ks: "dogs",
id: "dogId",
ids: "dogIds"
obj: Dog,
req: DogRequest,
crq: DogCreateRequest,
rsp: DogResponse,
stt: DogsState
}
interface Animals {
dog: DogBundle,
cat: CatBundle
}
// start implementation
const patchGroup = <K extends keyof Animals>(
k: K) => (state: Animals[K]['stt'], payload: Animals[K]['rsp'] | Animals[K]['crq']
): Animals[K]['stt'] => {
const id = k + "Id" as Animals[K]["id"];
const ks = k + "s" as Animals[K]["ks"];
const ids = k + "Ids" as Animals[K]["ids"];
废话
从调用端恢复某种类型安全性的下一步是使patchGroup()
成为一个只允许使用所有Cat
或所有Dog
输入调用自身的函数:
interface PatchGroup {
cat: (state: CatsState, payload: CatResponse | CatCreateRequest) => CatsState,
dog: (state: DogsState, payload: DogResponse | DogCreateRequest) => DogsState
}
const patchGroup = <K extends keyof PatchGroup>(group: K): PatchGroup[K] =>
(state: any, payload: any) => {
const groupIdx = state[`{$group}Ids`].indexOf((payload[`${group}`])[`${group}Id`]);
return {
...state,
loading: false,
[`{$group}s`]: [...state[`{$group}s`], payload[group]],
[`{$group}Ids`]: [...state[`{$group}Ids`], payload[group][`{$group}Id`]]
};
}
试图让编译器验证patchGroup()
的实现是类型安全的,这可能是不值得的。这里的一个绊脚石是没有人支持我所说的。CatXXX
接口之间的关系和DogXXX
接口之间的关系很难表示为CatXXX | DogXXX
联合之间的关系。即使我告诉编译器它需要知道的关于问题中使用的字符串文本之间关系的所有信息,比如:
state[`{$group}Ids` as keyof typeof state]
const patchGroup = (group: string) => {
const patch = (
state: DogsState | CatsState,
payload: DogResponse | DogCreateRequest | CatResponse | CatCreateRequest,
): DogsState | CatsState => {
const groupIdx = (state[`{$group}Ids` as keyof typeof state] as any as Array<string>).
indexOf((payload[`${group}` as keyof typeof payload] as Cat | Dog)[`${group}Id` as keyof (Cat | Dog)] as any as string);
return {
...state,
loading: false,
[`{$group}s`]: [...state[`{$group}s` as keyof typeof state] as any as Array<Cat | Dog>, payload[group as keyof typeof payload]],
[`{$group}Ids`]: [...state[`{$group}Ids` as keyof typeof state] as any as Array<string>, payload[group as keyof typeof payload][`{$group}Id`]]
} as any as DogsState | CatsState;
}
return patch;
}
interface CatBundle {
ks: "cats",
id: "catId",
ids: "catIds"
obj: Cat,
req: CatRequest,
crq: CatCreateRequest,
rsp: CatResponse,
stt: CatsState
}
interface DogBundle {
ks: "dogs",
id: "dogId",
ids: "dogIds"
obj: Dog,
req: DogRequest,
crq: DogCreateRequest,
rsp: DogResponse,
stt: DogsState
}
interface Animals {
dog: DogBundle,
cat: CatBundle
}
// start implementation
const patchGroup = <K extends keyof Animals>(
k: K) => (state: Animals[K]['stt'], payload: Animals[K]['rsp'] | Animals[K]['crq']
): Animals[K]['stt'] => {
const id = k + "Id" as Animals[K]["id"];
const ks = k + "s" as Animals[K]["ks"];
const ids = k + "Ids" as Animals[K]["ids"];
因此,我们可能已经超出了编译器可以帮助我们的范围。通过连接属性名并使用类型联合,我能想到的最好的方法是上面的调用安全实现不安全版本
从这里开始,下一步要做的就是停止试图强迫编译器理解这种字符串连接和计算属性代码,而是重构Cat
和Dog
接口以扩展单个Animal
接口。不要给属性临时命名,如catIds
和dogIds
;相反,只需给他们相同的animalIds
名称:
interface AnimalState<A extends Animal> {
animals: Array<A>;
animalIds: Array<string>;
loading: boolean;
// etc
}
interface DogsState {
animals: Array<Dog>;
animalIds: Array<string>;
loading: boolean;
// etc.
}
interface CatsState {
animals: Array<Cat>;
animalIds: Array<string>;
loading: boolean;
// etc.
}
编译器验证此实现是安全的,并且在调用站点也是安全的:
patch(catsState, catResponse); // okay
patch(dogsState, dogResponse); // okay
patch(catsState, dogResponse); // error! DogResponse not valid
可能有一些原因导致你不能进行重构,但它是如此的好,以至于我无论如何都会尝试,或者,如果失败的话,忘记将你原来重复的代码重构到一个单一的实现;复制很烦人,但至少编译器仍在帮助您
好吧,希望这会有帮助;祝你好运
考虑编辑问题中的代码,以构成中所述的。理想情况下,有人可以将代码放入一个独立的IDE中,以便自己演示这个问题。现在我不知道
patch(catsState, catResponse); // okay
patch(dogsState, dogResponse); // okay
patch(catsState, dogResponse); // error! DogResponse not valid