英文:
RxJS: Ensure Optional Object Property is Defined
问题
I need a RxJS operator that can check if a property on an object exists, and then change the type to correctly reflect this.
已经有一个可以对发射的值执行此操作的运算符:
const notUndefined = <T>() =>
filter<T | undefined, T>(
(x): x is T extends undefined ? never : T => x !== undefined
);
但我还需要能够对其属性之一执行此操作,可以像这样使用:
interface MyInterface {
myKey?: string;
}
of<MyInterface>({ myKey: "Some value" }).pipe(
notUndefinedKey("myKey"),
map((x) => x.myKey)
// ^? (property) myKey: string
)
of<MyInterface>({ myKey: "Some value" }).pipe(
map((x) => x.myKey)
// ^? (property) MyInterface.myKey?: string | undefined
)
我有一个初步可行的版本,但似乎有点多余,似乎也会从对象中删除方法:
import { OperatorFunction, filter, of, map } from "rxjs";
// TypeScript 类型辅助程序
type DefinedFields<TType> = {
[TKey in keyof TType]-?: DefinedFields<NonNullable<TType[TKey]>>;
};
type WithDefinedKey<TType, TKey extends keyof TType> = Omit<TType, TKey> & DefinedFields<Pick<TType, TKey>>;
type OperatorToDefinedKey<TType, TKey extends keyof TType> = OperatorFunction<
TType | undefined,
WithDefinedKey<TType, TKey>
>;
// 运算符
export const notUndefinedKey = <TType, TKey extends keyof TType>(
key: TKey
): OperatorToDefinedKey<TType, TKey> =>
filter<TType>((x) => x?.[key] !== undefined) as OperatorToDefinedKey<
TType,
TKey
>;
有没有更好的方法来实现这个?也许还可以保留对象上的原始接口和方法?
英文:
I need a RxJS operator that can check that if a property on an object exists, and then change the type to correctly reflect this.
I already have an operator that will do this for an emitted value:
const notUndefined = <T>() =>
filter<T | undefined, T>(
(x): x is T extends undefined ? never : T => x !== undefined
);
But I also need to be able to do it for one of its properties that can be used something like this:
interface MyInterface {
myKey?: string;
}
of<MyInterface>({ myKey: "Some value" }).pipe(
notUndefinedKey("myKey"),
map((x) => x.myKey)
// ^? (property) myKey: string
)
of<MyInterface>({ myKey: "Some value" }).pipe(
map((x) => x.myKey)
// ^? (property) MyInterface.myKey?: string | undefined
)
I've got a first working version, but it seems a bit excessive and seems to also remove methods from objects:
import { OperatorFunction, filter, of, map } from "rxjs";
// Typescript types helper
type DefinedFields<TType> = {
[TKey in keyof TType]-?: DefinedFields<NonNullable<TType[TKey]>>;
};
type WithDefinedKey<TType, TKey extends keyof TType> = Omit<TType, TKey> & DefinedFields<Pick<TType, TKey>>;
type OperatorToDefinedKey<TType, TKey extends keyof TType> = OperatorFunction<
TType | undefined,
WithDefinedKey<TType, TKey>
>;
// Operator
export const notUndefinedKey = <TType, TKey extends keyof TType>(
key: TKey
): OperatorToDefinedKey<TType, TKey> =>
filter<TType>((x) => x?.[key] !== undefined) as OperatorToDefinedKey<
TType,
TKey
>;
Is there a nice way to do this? One that maybe also keeps more of the original interface like methods on an object?
答案1
得分: 2
你可以简化这个函数,因为我认为声明所有这些类型对你来说并没有太大帮助。下面的函数返回一个与传入的类型相同的值,且属性 key 的值肯定已定义。
function notUndefinedKey<T, U extends keyof T>(key: U): OperatorFunction<T, T & { [K in U]-?: T[U] }> {
return filter((x): x is T & { [K in U]-?: T[U] } => x[key] != null);
}
示例用法
from([{}, { ignoreMe: 'Nope' }, { myKey: 'I am defined' }] as MyInterface[]).pipe(
notUndefinedKey('myKey'),
).subscribe(x => console.log(x.myKey));
// 输出: I am defined
替代方法
如果你真的想要类型定义(也许因为语法看起来有点混乱),你可以这样做:
type NotUndefinedKeys<T, U extends keyof T> = T & { [K in U]-?: T[U] }
function notUndefinedKey<T, U extends keyof T>(key: U): OperatorFunction<T, NotUndefinedKeys<T, U>> {
return filter((x): x is NotUndefinedKeys<T, U> => x[key] != null);
}
你也可以使这个函数适用于多个键,这可能是最灵活的用法,而且不需要太多更改。使用这个操作符不需要任何更改 - 你可以传递单个键或多个键。操作符的更改非常简单:接受类型的展开操作符,更改过滤函数的工作方式,TypeScript 就会发挥其魔力。
function notUndefinedKeys<T, U extends keyof T>(...keys: U[]): OperatorFunction<T, T & { [K in U]-?: T[U] }> {
return filter((x): x is T & { [K in U]-?: T[U] } => keys.every(k => x[k] != null));
}
英文:
You can simplify this function quite a lot as I don't think it does much for you to declare all those types. The function below returns a value that is the same type as what is passed and the value of the property key is definitely defined.
function notUndefinedKey<T, U extends keyof T>(key: U): OperatorFunction<T, T & { [K in U]-?: T[U] }> {
return filter((x): x is T & { [K in U]-?: T[U] } => x[key] != null);
}
Example Usage
from([{}, { ignoreMe: 'Nope'}, {myKey: 'I am defined'}] as MyInterface[]).pipe(
notUndefinedKey ('myKey'),
).subscribe(x => console.log(x.myKey));
// output: I am defined
Alternatives
If you really want the types defined (maybe because the syntax looks a bit confusing), you can do the following:
type NotUndefinedKeys<T, U extends keyof T> = T & { [K in U]-?: T[U] }
function notUndefinedKey<T, U extends keyof T>(key: U): OperatorFunction<T, NotUndefinedKeys<T, U>> {
return filter((x): x is NotUndefinedKeys<T, U> => x[key] != null);
}
You could also have this work with multiple keys, which might be the most flexible usage and doesn't require much in the way of changes. Using the operator doesn't change at all - you can pass a single key or multiple keys. The operator change is very simple: accept a spread operator of types, change how the filter function works, and TypeScript works its magic.
function notUndefinedKeys<T, U extends keyof T>(...keys: U[]): OperatorFunction<T, T & { [K in U]-?: T[U] }> {
return filter((x): x is T & { [K in U]-?: T[U] } => keys.every(k => x[k] != null));
}
答案2
得分: 0
以下是您要翻译的内容的翻译部分:
只是供参考,结合了不同的评论和答案,我得到了这个:
```ts
type NeverUndefined<T> = T extends undefined ? never : T;
type WithNeverUndefined<T, K extends keyof T> = T & {
[P in K]-?: NeverUndefined<T[P]>;
};
export const notUndefinedKey = <T, K extends keyof T>(key: K):
OperatorFunction<T | undefined, WithNeverUndefined<T, K>> =>
filter((x): x is WithNeverUndefined<T, K> => x?.[key] !== undefined);
// 从 @daniel-gimenez 那里得到的多键版本
export const notUndefinedKeys = <T, K extends keyof T>(...keys: [K, ...K[]]):
OperatorFunction<T | undefined, WithNeverUndefined<T, K>> =>
filter((x): x is WithNeverUndefined<T, K> => keys.every(key => x?.[key] !== undefined));
它确保对象和属性都不是 undefined。
它还适用于具有 undefined 的可选属性和类型联合:
interface MyInterface {
myFirstKey?: string;
mySecondKey: string | undefined;
myThirdKey?: string | undefined;
}
of<MyInterface>(
{
myFirstKey: "Some value",
mySecondKey: "Some value",
myThirdKey: "Some value",
}
).pipe(
notUndefinedKeys("myFirstKey", "mySecondKey", "myThirdKey"),
tap((x) => x.myFirstKey),
// ^? (property) MyInterface.myFirstKey: string
tap((x) => x.mySecondKey),
// ^? (property) MyInterface.mySecondKey: string
tap((x) => x.myThirdKey),
// ^? (property) MyInterface.myThirdKey: string
)
它还保留了对象上的方法等。
最后,这是 notUndefined
的更新版本:
export const notUndefined = <T>(): OperatorFunction<T | undefined, NeverUndefined<T>> =>
filter((x): x is NeverUndefined<T> => x !== undefined);
<details>
<summary>英文:</summary>
Just for reference, combining the different comments and answers, I've landed on this:
```ts
type NeverUndefined<T> = T extends undefined ? never : T;
type WithNeverUndefined<T, K extends keyof T> = T & {
[P in K]-?: NeverUndefined<T[P]>;
};
export const notUndefinedKey = <T, K extends keyof T>(key: K):
OperatorFunction<T | undefined, WithNeverUndefined<T, K>> =>
filter((x): x is WithNeverUndefined<T, K> => x?.[key] !== undefined);
// And the multi-key version from @daniel-gimenez
export const notUndefinedKeys = <T, K extends keyof T>(...keys: [K, ...K[]]):
OperatorFunction<T | undefined, WithNeverUndefined<T, K>> =>
filter((x): x is WithNeverUndefined<T, K> => keys.every(key => x?.[key] !== undefined));
It ensures that neither the object nor the property is undefined.
It also works with optional and type unions with undefined:
interface MyInterface {
myFirstKey?: string;
mySecondKey: string | undefined;
myThirdKey?: string | undefined;
}
of<MyInterface>(
{
myFirstKey: "Some value",
mySecondKey: "Some value",
myThirdKey: "Some value",
}
).pipe(
notUndefinedKeys("myFirstKey", "mySecondKey", "myThirdKey"),
tap((x) => x.myFirstKey),
// ^? (property) MyInterface.myFirstKey: string
tap((x) => x.mySecondKey),
// ^? (property) MyInterface.mySecondKey: string
tap((x) => x.myThirdKey),
// ^? (property) MyInterface.myThirdKey: string
)
It also preserves methods and such on the object.
And finally, an updated version of notUndefined
:
export const notUndefined = <T>(): OperatorFunction<T | undefined, NeverUndefined<T>> =>
filter((x): x is NeverUndefined<T> => x !== undefined);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论