TypeScript 类型安全省略函数
TypeScript Type-safe Omit Function
我想在普通打字稿中复制 lodash 的 _.omit
函数。 omit
应该 return 一个删除了某些属性的对象,该对象通过先出现的对象参数之后的参数指定。
这是我最好的尝试:
function omit<T extends object, K extends keyof T>(obj: T, ...keys: K[]): {[k in Exclude<keyof T, K>]: T[k]} {
let ret: any = {};
let key: keyof T;
for (key in obj) {
if (!(keys.includes(key))) {
ret[key] = obj[key];
}
}
return ret;
}
这给了我这个错误:
Argument of type 'keyof T' is not assignable to parameter of type 'K'.
Type 'string | number | symbol' is not assignable to type 'K'.
Type 'string' is not assignable to type 'K'.ts(2345)
let key: keyof T
我对错误的解释是:
因为 key 是一个 keyof T
而 T
是一个对象,key 可以是 symbol
、number
或 string
.
由于我使用 for in
循环,key 只能是 string
但 includes
可能需要 number
如果我传入一个数组,例如?我认为。那么这意味着这里存在类型错误?
感谢任何关于为什么这不起作用以及如何使其起作用的见解!
如果我们将键的类型限制为字符串[],就可以了。
不过好像不太好 idea.Keys should be string |编号 |符号[];
function omit<T, K extends string>(
obj: T,
...keys: K[]
): { [k in Exclude<keyof T, K>]: T[k] } {
let ret: any = {};
Object.keys(obj)
.filter((key: K) => !keys.includes(key))
.forEach(key => {
ret[key] = obj[key];
});
return ret;
}
const result = omit({ a: 1, b: 2, c: 3 }, 'a', 'c');
// The compiler inferred result as
// {
// b: number;
// }
interface Omit {
<T extends object, K extends [...(keyof T)[]]>
(obj: T, ...keys: K): {
[K2 in Exclude<keyof T, K[number]>]: T[K2]
}
}
const omit: Omit = (obj, ...keys) => {
const ret = {} as {
[K in keyof typeof obj]: (typeof obj)[K]
};
let key: keyof typeof obj;
for (key in obj) {
if (!(keys.includes(key))) {
ret[key] = obj[key];
}
}
return ret;
};
为方便起见,我已将大部分类型输入到一个界面中。
问题是 K
被推断为 元组 ,而不是 联合 键。因此,我相应地更改了它的类型约束:
[...(keyof T)[]] // which can be broke down to:
keyof T // a union of keys of T
(keyof T)[] // an array containing keys of T
[...X] // a tuple that contains X (zero or more arrays like the described one above)
然后,我们需要将元组 K
转换为联合(以便 Exclude
它来自 keyof T
)。它是用 K[number]
完成的,我想这是不言自明的,它与 T[keyof T]
创建 T
.
值的并集相同
上面 Nurbol 接受的答案可能是更多类型的版本,但这是我在 utils-min
中所做的。
它使用 typescript 内置的 Omit 并且被设计为仅支持字符串键名。 (仍然需要放松 Set to Set,但其他一切似乎都很好)
export function omit<T extends object, K extends Extract<keyof T, string>>(obj: T, ...keys: K[]): Omit<T, K> {
let ret: any = {};
const excludeSet: Set<string> = new Set(keys);
// TS-NOTE: Set<K> makes the obj[key] type check fail. So, loosing typing here.
for (let key in obj) {
if (!excludeSet.has(key)) {
ret[key] = obj[key];
}
}
return ret;
}
Object.keys
或 for in
returns 键作为字符串并排除符号。数字键也被转换为字符串。
您需要将数字字符串键转换为数字,否则它将 return 带有字符串键的对象。
function omit<T extends Record<string | number, T['']>,
K extends [...(keyof T)[]]>(
obj: T,
...keys: K
): { [P in Exclude<keyof T, K[number]>]: T[P] } {
return (Object.keys(obj)
.map((key) => convertToNumbers(keys, key)) as Array<keyof T>)
.filter((key) => !keys.includes(key))
.reduce((agg, key) => ({ ...agg, [key]: obj[key] }), {}) as {
[P in Exclude<keyof T, K[number]>]: T[P];
};
}
function convertToNumbers(
keys: Array<string | number | symbol>,
value: string | number
): number | string {
if (!isNaN(Number(value)) && keys.some((v) => v === Number(value))) {
return Number(value);
}
return value;
}
// without converToNumbers omit({1:1,2:'2'}, 1) will return {'1':1, '2':'2'}
// Specifying a numeric string instead of a number will fail in Typescript
要包含符号,您可以使用以下代码。
function omit<T, K extends [...(keyof T)[]]>(
obj: T,
...keys: K
): { [P in Exclude<keyof T, K[number]>]: T[P] } {
return (Object.getOwnPropertySymbols(obj) as Array<keyof T>)
.concat(Object.keys(obj)
.map((key) => convertToNumbers(keys, key)) as Array<keyof T>)
.filter((key) => !keys.includes(key))
.reduce((agg, key) => ({ ...agg, [key]: obj[key] }), {}) as {
[P in Exclude<keyof T, K[number]>]: T[P];
};
}
最简单的方法:
export const omit = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> => {
keys.forEach((key) => delete obj[key])
return obj
}
作为纯函数:
export const omit = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> => {
const _ = { ...obj }
keys.forEach((key) => delete _[key])
return _
}
很遗憾,无法摆脱as any
const removeProperty = <Obj, Prop extends keyof Obj>(
obj: Obj,
prop: Prop
): Omit<Obj, Prop> => {
const { [prop]: _, ...rest } = obj;
return rest;
};
export default removeProperty;
const omit = <Obj, Prop extends keyof Obj, Props extends ReadonlyArray<Prop>>(
obj: Obj,
props: readonly [...Props]
): Omit<Obj, Props[number]> =>
props.reduce(removeProperty, obj as any);
不确定我是否明白,但我遇到了类似的问题,我想确保在省略属性时没有打错字,所以我采用了这样的解决方案:
export interface Person {
id: string;
firstName: string;
lastName: string;
password: string;
}
type LimitedDTO<K extends keyof Person> = Omit<Person, K>;
export type PersonDTO = LimitedDTO<"password" | "lastName">;
并且 tsc 不允许您省略 属性,它不存在于 Person 界面
我想在普通打字稿中复制 lodash 的 _.omit
函数。 omit
应该 return 一个删除了某些属性的对象,该对象通过先出现的对象参数之后的参数指定。
这是我最好的尝试:
function omit<T extends object, K extends keyof T>(obj: T, ...keys: K[]): {[k in Exclude<keyof T, K>]: T[k]} {
let ret: any = {};
let key: keyof T;
for (key in obj) {
if (!(keys.includes(key))) {
ret[key] = obj[key];
}
}
return ret;
}
这给了我这个错误:
Argument of type 'keyof T' is not assignable to parameter of type 'K'.
Type 'string | number | symbol' is not assignable to type 'K'.
Type 'string' is not assignable to type 'K'.ts(2345)
let key: keyof T
我对错误的解释是:
因为 key 是一个
keyof T
而T
是一个对象,key 可以是symbol
、number
或string
.由于我使用
for in
循环,key 只能是string
但includes
可能需要number
如果我传入一个数组,例如?我认为。那么这意味着这里存在类型错误?
感谢任何关于为什么这不起作用以及如何使其起作用的见解!
如果我们将键的类型限制为字符串[],就可以了。 不过好像不太好 idea.Keys should be string |编号 |符号[];
function omit<T, K extends string>(
obj: T,
...keys: K[]
): { [k in Exclude<keyof T, K>]: T[k] } {
let ret: any = {};
Object.keys(obj)
.filter((key: K) => !keys.includes(key))
.forEach(key => {
ret[key] = obj[key];
});
return ret;
}
const result = omit({ a: 1, b: 2, c: 3 }, 'a', 'c');
// The compiler inferred result as
// {
// b: number;
// }
interface Omit {
<T extends object, K extends [...(keyof T)[]]>
(obj: T, ...keys: K): {
[K2 in Exclude<keyof T, K[number]>]: T[K2]
}
}
const omit: Omit = (obj, ...keys) => {
const ret = {} as {
[K in keyof typeof obj]: (typeof obj)[K]
};
let key: keyof typeof obj;
for (key in obj) {
if (!(keys.includes(key))) {
ret[key] = obj[key];
}
}
return ret;
};
为方便起见,我已将大部分类型输入到一个界面中。
问题是 K
被推断为 元组 ,而不是 联合 键。因此,我相应地更改了它的类型约束:
[...(keyof T)[]] // which can be broke down to:
keyof T // a union of keys of T
(keyof T)[] // an array containing keys of T
[...X] // a tuple that contains X (zero or more arrays like the described one above)
然后,我们需要将元组 K
转换为联合(以便 Exclude
它来自 keyof T
)。它是用 K[number]
完成的,我想这是不言自明的,它与 T[keyof T]
创建 T
.
上面 Nurbol 接受的答案可能是更多类型的版本,但这是我在 utils-min
中所做的。
它使用 typescript 内置的 Omit 并且被设计为仅支持字符串键名。 (仍然需要放松 Set to Set,但其他一切似乎都很好)
export function omit<T extends object, K extends Extract<keyof T, string>>(obj: T, ...keys: K[]): Omit<T, K> {
let ret: any = {};
const excludeSet: Set<string> = new Set(keys);
// TS-NOTE: Set<K> makes the obj[key] type check fail. So, loosing typing here.
for (let key in obj) {
if (!excludeSet.has(key)) {
ret[key] = obj[key];
}
}
return ret;
}
Object.keys
或 for in
returns 键作为字符串并排除符号。数字键也被转换为字符串。
您需要将数字字符串键转换为数字,否则它将 return 带有字符串键的对象。
function omit<T extends Record<string | number, T['']>,
K extends [...(keyof T)[]]>(
obj: T,
...keys: K
): { [P in Exclude<keyof T, K[number]>]: T[P] } {
return (Object.keys(obj)
.map((key) => convertToNumbers(keys, key)) as Array<keyof T>)
.filter((key) => !keys.includes(key))
.reduce((agg, key) => ({ ...agg, [key]: obj[key] }), {}) as {
[P in Exclude<keyof T, K[number]>]: T[P];
};
}
function convertToNumbers(
keys: Array<string | number | symbol>,
value: string | number
): number | string {
if (!isNaN(Number(value)) && keys.some((v) => v === Number(value))) {
return Number(value);
}
return value;
}
// without converToNumbers omit({1:1,2:'2'}, 1) will return {'1':1, '2':'2'}
// Specifying a numeric string instead of a number will fail in Typescript
要包含符号,您可以使用以下代码。
function omit<T, K extends [...(keyof T)[]]>(
obj: T,
...keys: K
): { [P in Exclude<keyof T, K[number]>]: T[P] } {
return (Object.getOwnPropertySymbols(obj) as Array<keyof T>)
.concat(Object.keys(obj)
.map((key) => convertToNumbers(keys, key)) as Array<keyof T>)
.filter((key) => !keys.includes(key))
.reduce((agg, key) => ({ ...agg, [key]: obj[key] }), {}) as {
[P in Exclude<keyof T, K[number]>]: T[P];
};
}
最简单的方法:
export const omit = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> => {
keys.forEach((key) => delete obj[key])
return obj
}
作为纯函数:
export const omit = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> => {
const _ = { ...obj }
keys.forEach((key) => delete _[key])
return _
}
很遗憾,无法摆脱as any
const removeProperty = <Obj, Prop extends keyof Obj>(
obj: Obj,
prop: Prop
): Omit<Obj, Prop> => {
const { [prop]: _, ...rest } = obj;
return rest;
};
export default removeProperty;
const omit = <Obj, Prop extends keyof Obj, Props extends ReadonlyArray<Prop>>(
obj: Obj,
props: readonly [...Props]
): Omit<Obj, Props[number]> =>
props.reduce(removeProperty, obj as any);
不确定我是否明白,但我遇到了类似的问题,我想确保在省略属性时没有打错字,所以我采用了这样的解决方案:
export interface Person {
id: string;
firstName: string;
lastName: string;
password: string;
}
type LimitedDTO<K extends keyof Person> = Omit<Person, K>;
export type PersonDTO = LimitedDTO<"password" | "lastName">;
并且 tsc 不允许您省略 属性,它不存在于 Person 界面