我正在將一個專案移植到打字稿,除了這個簡單的實用函式外,一切都很好:
function mapObject(obj, mapperFn) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => mapperFn(key, value))
);
}
它用于轉換物件的鍵和值,如下所示:
let obj = { a: 1, b: 2 };
let mappedObj = mapObject(obj, (key, value) => [
key.toUpperCase(),
value 1,
]);
mappedObj; // { A: 2, B: 3 }
編輯 為了清楚起見,輸出物件的條目值不一定與輸入物件中的條目值相同,例如,上面的示例可以將數字轉換為字串以生成{ A: '2', B: '3' },或者只是為它們中的每一個回傳 null:{ A: null, B: null }. 此外,為了簡單起見,我只考慮輸入和輸出物件中的字串鍵,因為這就是我們專案中使用它的方式。
我想mapperFn保留物件條目的值型別,支持按 key 縮小型別,就像GetEntryOf下面的例子一樣:
type GetEntryOf<T> = {
[K in keyof T]: [K, T[K]];
}[keyof T];
interface TestI {
str: string;
num: number;
}
let entry: GetEntryOf<TestI>;
if (entry[0] === 'str') {
entry[1]; // string
} else if (entry[0] === 'num') {
entry[1]; // number
} else {
entry; // never
}
這是我得到的最接近的,不幸的是我需要轉換為使用key in obj運算子:
function mapObject<K>(
obj: K,
mapperFn: (key: keyof K, value: K[typeof key]) => [string, unknown]
): Record<string, unknown> {
const newObj = {} as Record<string, unknown>;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key];
const [newKey, newValue] = mapperFn(key, value);
newObj[newKey] = newValue;
}
}
return newObj;
}
interface TestI {
str: string;
num: number;
}
let test: TestI;
mapObject(test, (key, value) => {
if (key === 'str') {
return [key, value]; // value should be string
} else if (key === 'num') {
return [key, value]; // value should be number
} else {
return [key, value]; // value should be never
}
});
如您所見,value的型別沒有正確縮小:

考慮到GetEntryOf可以做到,我認為應該可以在里面做同樣的事情mapperFn。這在當前的打字稿中甚至是可能的,如果是,那么合適的方法是什么?
注意:作業型別縮小示例基于此 SO answer。我知道并且同意同一個答案中提到的約束:“不能保證該值也沒有其他屬性”。
uj5u.com熱心網友回復:
我想你正在尋找這樣的東西:
type Values<T> = T[keyof T]
type MakeTuple<T> = Values<{
[Prop in keyof T]: { key: Prop, value: T[Prop] }
}>
type Output<T> = {
[Prop in keyof T]: [Prop, T[Prop]]
}
const mapObject = <K,>(
obj: K,
mapperFn: (params: MakeTuple<K>) => [MakeTuple<K>['key'], MakeTuple<K>['value']]
) => (Object.keys(obj) as Array<keyof K>)
.reduce((acc, key) => {
const [newKey, newValue] = mapperFn({ key, value: obj[key] });
return {
...acc,
[newKey]: newValue
}
}, {} as Output<K>)
interface TestI {
str: string;
num: number;
}
declare let test: TestI;
const result = mapObject(test, (obj) => {
// const key = params[0]
// const value = params[1]
if (obj.key === 'str') {
return [obj.key, obj.value]
} else if (obj.key === 'num') {
return [obj.key, obj.value];
} else {
throw new Error();
}
});
result.num // ['num', number]
操場
我不確定你為什么使用,Object.prototype.hasOwnProperty.call(obj, key)但你可以使用:
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
反而。你甚至可以hasOwnProperty像我在這里那樣超載
更新 您將被允許使用兩個單獨的引數
const result = mapObject(test, (key, value) => {
if (key === 'str') {
return [key, obj]
} else if (key === 'num') {
return [key, obj];
} else {
throw new Error();
}
});
當這個 PR控制流分析解構的歧視聯合將被合并
uj5u.com熱心網友回復:
根據yossarian 船長的回答,這里是將要實施的最終結果:
type Values<T> = T[keyof T];
type MakeTuple<T> = Values<{
[Prop in keyof T]: { key: Prop; value: T[Prop] };
}>;
interface MapObjectMapper<K> {
(params: MakeTuple<K>): [string, unknown];
}
export default function mapObject<K>(
obj: K,
mapperFn: MapObjectMapper<K>
): Record<string, unknown> {
return Object.fromEntries(
(Object.keys(obj) as Array<keyof K>).map((key) =>
mapperFn({ key, value: obj[key] })
)
);
}
為了清晰起見,我提取了映射器函式簽名,并替換了輸出型別[string, unknown]以說明問題中的編輯注釋(僅使用字串鍵)以及回傳值確實無法知道的事實。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/347888.html
