输入安全性给出属性名称字符串

时间:2018-02-14 01:53:16

标签: typescript rxjs

我创建了一个运算符“pluck”,它映射一个对象并返回其属性名称与提供给pluck的字符串列表匹配的值:

const pluck = (...args: string[]) => map(val => args.reduce((props, propName) => {
  props[propName] = val[propName];
  return props;
}, {}));

pluck('name', 'id', 'login');

有没有办法实现类型安全性以确保传递给pluck操作的类型?我尝试过使用泛型:

const pluck = <T>(...args: string[]) => map<T>((val: T) => args.reduce((props, propName) => {
  props[propName] = val[propName];
  return props;
}, {}));

但是由于数组访问语法,这可能不起作用。但是,val.prop不起作用,因为T类型上的属性不存在。

此实现适用于数组:

const pluck = (arr, ...args) => arr.map(val => args.reduce((props, propName) => {
  props[propName] = val[propName];
  return props;
}, {}));

1 个答案:

答案 0 :(得分:1)

不确定第一个map示例中的pluck是什么,但数组的类型版似乎

const pluck = <T, E extends keyof T>(arr: T[], ...args: E[]) => arr.map((val: T) => args.reduce((props, propName) => {
  props[propName] = val[propName];
  return props;
}, {} as {[e in E]: T[e]}));

interface Z {
    x: number;
    y: number;
    name: string;
    id: string;
    login: string;
}

let z: Z[] = [];

let p = pluck(z, 'name', 'id', 'login'); 
// p's type is inferred as  { name: string; id: string; login: string; }[]

诀窍是引入另一个通用参数E,限制为T的一个键子集。此外,类型断言是将空对象转换为最终props返回类型所必需的。然后,可以对props[propName]进行类型检查 - props已映射类型{[e in E]: T[e]}propName已将E作为其类型。

<强>更新

对于RxJX,我可以为完全通用的pluck运算符提出最佳类型。它不好,因为它将属性类型推断为any,但至少它会检查属性名称:

import {Observable} from "rxjs/Observable";
import "rxjs/add/operator/map";
import "rxjs/add/operator/let";


import 'rxjs/add/observable/from';

import {map, filter} from "rxjs/operators";
import {OperatorFunction} from "rxjs/interfaces";


const source = Observable.from([
  { name: 'Joe', age: 30, id: 123, login: 'joe1' },
  { name: 'Frank', age: 20, id: 456, login: 'frank1956' },
  { name: 'Ryan', age: 50, id: 2, login: 'z' }
]);


const pluck = <E extends string, T extends {[e in E]: T[e]}>(...args: E[]): OperatorFunction<T, {[e in E]: T[e]}> => map((val: T) => args.reduce((props, propName) => {
  props[propName] = val[propName];
  return props;
}, {} as {[e in E]: T[e]}));


const p = pluck('name', 'id', 'login');



const d = source.let(p); // Observable<{ name: any; id: any; login: any; }>

您可以添加另一级别的函数,并明确指定输入类型作为返回实际pluck的函数的类型参数,但是:

type PluckOperatorType<T> = <E extends keyof T>(...args: E[]) => OperatorFunction<T, {[e in E]: T[e]}>;

const pluck = <T>(): PluckOperatorType<T> => <E extends keyof T>(...args: E[]) => map((val: T) => args.reduce((props, propName) => {
  props[propName] = val[propName];
  return props;
}, {} as {[e in E]: T[e]}));

interface Person {
    name: string;
    age: number;
    id: number;
    login: string;
}

const p = pluck<Person>()('name', 'id', 'login');

const d = source.let(p); // Observable<{ name: string; id: number; login: string; }>