【问题标题】:Extending (mapped) tuple types扩展(映射)元组类型
【发布时间】:2019-04-13 15:23:48
【问题描述】:

在泛型函数中使用元组类型时,如何“扩展”源类型?

假设我们要创建一个 rxjs 映射运算符,它返回源可观察值和另一个映射值:

export function mapExtended<T, R>(mapping: (input: [T]) => R): OperatorFunction<[T], [T, R]>;
export function mapExtended<T1, T2, R>(mapping: (input: [T1, T2]) => R): OperatorFunction<[T1, T2], [T1, T2, R]>;
export function mapExtended<T1, T2, T3, R>(mapping: (input: [T1, T2, T3]) => R): OperatorFunction<[T1, T2, T3], [T1, T2, T3, R]>;
export function mapExtended<R>(mapping: (input: any) => R): OperatorFunction<any, {}> {
    return (source$: Observable<any>) => source$.pipe(
        map(input => {
            const mappingResult = mapping(input);
            return [...input, mappingResult];
        }),
    );
}

这似乎可行,但未正确检测到过载。 test 的返回类型是 Observable&lt;[[number, number], number]&gt;,而不是预期的 Observable&lt;[number, number, number]&gt;

const test = combineLatest(of(1), of(2)).pipe(
    mapExtend(([s1, s2]) => s1 + s2),
);

是否有某种条件类型检查表明 T 不能是元组类型?


我尝试通过对元组的映射类型支持来完成同样的工作,但无济于事,因为我似乎无法(或者我不知道如何)“扩展”映射类型:

type MapExtended2Input<T> = { [P in keyof T]: T[P] };
function mapExtended2<T extends any[], R>(mapping: (input: MapExtended2Input<T>) => R): OperatorFunction<MapExtended2Input<T>, [MapExtended2Input<T>, R]> {
    return (source$: Observable<MapExtended2Input<T>>) => source$.pipe(
        map(input => {
            const mappingResult = mapping(input);
            const result: [MapExtended2Input<T>, R] = [input, mappingResult];
            return result;
        }),
    );
}

const test2 = combineLatest(of(1), of(2)).pipe(
    mapExtended2(([s1, s2]) => s1 + s2),
);

这里,返回类型也是Observable&lt;[[number, number], number]&gt;,这是意料之中的,但我不知道如何向映射的元组类型“添加”一个类型。相交似乎不起作用,或者我做错了。


编辑:

没有 rxjs 的所需功能的示例是:

假设我需要一个函数myFunc,它有 2 个泛型类型参数:

  • 元组类型 T,具有可变数量的元素
  • 另一种类型R

函数的结果应该是一个元组类型,包含元组类型 T 的所有元素,并附加 R 类型的参数。

例如:

myFunc<T, R>(x, y); // Should return [T, R]
myFunc<[T1, T2], R>(x, y); // Should return [T1, T2, R]
myFunc<[T1, T2, T3], R>; // Should return [T1, T2, T3, R]
// ...

【问题讨论】:

  • 什么是rxjs,ofpipe函数有什么作用?如果问题是关于特定库的,为什么没有适当地标记/命名它?
  • @m93a: rxjs的用法和问题无关,不过我已经加了tag才完整,谢谢。供您参考,rxjs 是一个非常受欢迎的库:github.com/ReactiveX/rxjs
  • 代码太复杂了。尝试用不同的东西替换像 OperatorFunctionObservable 这样不必要的类型,这样我们就可以在纯 TypeScript 中复制它。

标签: typescript rxjs


【解决方案1】:

我认为您要求的是:给定一个元组类型L(用于“列表”)和另一个类型T,生成一个新的元组类型,T 附加到L 的末尾。如果您要调用l.push(t),则对结果类型进行排序。你可以这样做:

type Push<L extends any[], T> =
  ((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ?
  { [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never

使用rest tuplesconditional type inferencemapped tuples 有点诡计......

在元组的开头预置一个类型的结果很容易产生,因为TypeScript引入了函数参数和元组类型之间的类型级对应关系,以及传播元组类型的能力进入休息参数。如果L 是像[string, number] 这样的元组。那么函数类型(...args: L)=&gt;void 表示一个函数,分别采用stringnumber 类型的两个参数。其余参数前面可以有东西:(first: boolean, ...args: L)=&gt;void 是一个接受三个参数的函数。并且,条件类型((first: boolean, ...args: L) =&gt; void) extends ((...args: infer L2)=&gt;void) ? L2 : never 生成一个新元组L2,其中boolean前置L... 的内容... 例如[boolean, string, number]

但您不想预先添加,您想要添加。好吧,通过将任何元素添加到您的列表元组 L,我们得到一个长度正确但类型错误的元组 L2。啊,但我们可以映射元组!让我们使用条件类型K extends keyof L ? L[K] : T 映射L2 的数字键K。也就是说,如果数字键KL 中("0""1" 如果L 的长度为2),那么只需使用L 中的对应类型。如果不是,它必须是最后的那个("2" 如果L 是长度2),所以使用新类型T 那里。这应该具有将T 附加到L 末尾的效果。

让我们确保它有效:

type P = Push<[1, 2, 3], 4>; // type P = [1, 2, 3, 4]

看起来不错。现在你可以像这样声明你的mapExtended()

declare function mapExtended<T extends any[], R>(
  mapping: (input: T) => R
): OperatorFunction<T, Push<T, R>>;

请注意,mapExtended()实现可能不会进行类型检查,因为编译器无法轻松验证泛型 TR 的类型是否为 OperatorFunction&lt;T, Push&lt;T, R&gt;&gt;。因此,您可能希望使用 type assertion 或单个 overload

我无法验证它的行为,因为我没有安装 rxjs。如果您需要特定于 rxjs 的帮助,我相信会有更了解它的人出现。无论如何,希望这会有所帮助。祝你好运!

【讨论】:

  • 不错!我尝试使用休息元组,但无法提出正确的解决方案。实际上,实现不进行类型检查。在试图弄清楚时,我也没有考虑如何区分多源 Observable 的元组类型或 Array 类型的单个 Observable。尽管所需操作员功能的意图很明确,但由于技术限制,我可能需要重新考虑实现。不过,您的解决方案正是我所追求的,并且肯定会帮助最终实施,谢谢!
猜你喜欢
  • 1970-01-01
  • 2022-01-22
  • 2012-11-07
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 2021-04-10
  • 2016-01-29
  • 2019-01-15
相关资源
最近更新 更多