0

我有一个指令,在 Input 中可以获取字符串数组或对象数组。该指令是从 中过滤列表@Input()并发出一个新列表@Output()。在指令中,我创建了一个可以完成所有魔法的方法,但我无法正确键入@Input()和函数本身。

export class SelectFilteringDirective<T> {
  @Input() readonly optionsList: T[] = [];
  @Output() filteredList = new EventEmitter<T[]>();

  constructor() {}

  @HostListener('keyup', ['$event'])
  onKeyup(event: Event): void {
    this.filteredList.emit(
      this.filterList(
        this.optionsList,
        (event.target as HTMLInputElement).value
      )
    );
  }

  private filterList(data: T[], value: string): any {
    return typeof data[0] === 'object'
      ? [...data].filter(
          (item) => item.value.toLowerCase().indexOf(value.toLowerCase()) != -1
        )
      : [...data].filter(
          (item) => item.toLowerCase().indexOf(value.toLowerCase()) != -1
        );
  }
}

在带有对象的变体中,我收到错误:

Property 'value' does not exist on type 'T'.

但是,在带有字符串数组的变体中,出现错误:

Property 'toLowerCase' does not exist on type 'T'.
4

3 回答 3

1

如果我的问题正确,您有一个指令接收一个 @Input可以是Array字符串或Array对象的指令。基于此,这里有一个解决方案:

type KeyValue = Readonly<{
  key: string;
  value: string;
}>;
type ListItem = string | KeyValue;

@Directive({ selector: 'selectFiltering' })
export class SelectFilteringDirective {
  @Input() optionsList: readonly ListItem[] = [];
  @Output() readonly filteredList = new EventEmitter<ListItem[]>();

  @HostListener('keyup', ['$event'])
  onKeyup(event: Event): void {
    this.filteredList.emit(
      this.filterList(
        this.optionsList,
        (event.target as HTMLInputElement).value,
      )
    );
  }

  private filterList(
    list: readonly ListItem[],
    value: string
  ): ListItem[] {
    const lowerCasedValue = value.toLowerCase();
    return list.filter((item) =>
      (typeof item === 'string' ? item : item.value)
        .toLowerCase()
        .includes(lowerCasedValue)
    );
  }
}

提示

虽然这可能会解决问题,但我不太确定@Directive这是否是最正确的方法。同样,如果我的问题正确,您有一些选择,并且您想根据输入更改将它们过滤掉......如果是这种情况,我建议使用@Pipe(pure, ofc) 代替。

以防万一您想查看@Pipe版本,请执行以下操作:

演示使用@Pipe

于 2021-11-14T14:43:38.310 回答
0

您可以像这样定义输入类型:

@Input() variantOne: string[];
@Input() variantTwo: {key: string, value: string}[]; // although an interface would be better
于 2021-11-14T13:41:10.737 回答
0

你不需要任何泛型。

将您的代码更改为:

interface Item {
    readonly value: string;
}

type StringsOrObjectsArray = readonly String[] | readonly Item[];

function isItem( x: unknown ): x is Item {
    const whatIf = x as Item;
    return (
        typeof whatIf === 'object' &&
        whatIf !== null &&
        typeof whatIf.value === 'string'
    );
}

//

export class SelectFilteringDirective {

  @Input()  optionsList: StringsOrObjectsArray = [];
  @Output() filteredList                       = new EventEmitter<StringsOrObjectsArray>();

  constructor() {
  }

  @HostListener('keyup', ['$event'])
  onKeyup(event: KeyboardEvent): void {

    const inputValue = (event.target as HTMLInputElement).value;
    const oldList    = this.optionsList;
    const newList    = this.filterList( oldList, inputValue );

    this.filteredList.emit( newList );
  }

  private filterList(data: StringsOrObjectsArray, value: string): StringsOrObjectsArray {

    // Preconditions: (defensive-programming is essential in JS and TS!)
    if( !Array.isArray( data ) ) throw new Error( "Expected an array." );
    if( data.length < 1 ) throw new Error( "Expected a non-empty array." );

    const valuePattern = new RegExp( escapeRegExp( value ), 'i' );

    if( typeof data[0] === 'string' ) {

        return data.filter( str => valuePattern.test( str ) );
    }
    else if( isItem( data[0] ) ) {
        // The `isItem` type-guard is unnecessary as TypeScript *should* infer that `data` is `readonly Item[]` here.
        return data.filter( obj => valuePattern.test( obj.value ) );
    }
    else {
        throw new Error( "Unexpected array contents." );
    }
}


function escapeRegExp(str: string): string {
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
于 2021-11-14T14:58:23.250 回答