假设我有以下界面:
interface A extends Entity {
propA: string;
propB: number;
propC: boolean;
}
我可以通过以下方式构建过滤器来查询此接口背后的对象:
const filters: Filter<A>[] = [
{ op: 'eq', field: 'propA', value: 'test' },
{ op: 'in', field: 'propB', value: [1,2,3] },
{ op: 'exists', field: 'propC' }
]
op
的所有值对所有字段和字段类型都有效。但是上面的两个示例有一个额外的 value
属性。此属性对于 op
的某些值是必需的,并且取决于值,应该是单个标量或标量数组。
到目前为止我已经了解了它,但这就是我遇到的问题:value
的类型必须与我们过滤的对象的属性类型相匹配。
所以这应该是有效的:
// single value operators
const filter: Filter<A> = { op: 'eq', field: 'propA', value: 'test' };
const filter: Filter<A> = { op: 'noteq', field: 'propB', value: 5 };
const filter: Filter<A> = { op: 'eq', field: 'propC', value: true };
// multi value operators
const filter: Filter<A> = { op: 'in', field: 'propA', value: ['test1', 'test2'] };
const filter: Filter<A> = { op: 'in', field: 'propB', value: [5, 6, 7] };
const filter: Filter<A> = { op: 'notIn', field: 'propC', value: [true] };
// no value operators
const filter: Filter<A> = { op: 'exists', field: 'propC' };
但这不是:
const filter: Filter<A> = { op: 'eq', field: 'propA', value: 5 }; // A.propA is string, not number
const filter: Filter<A> = { op: 'noteq', field: 'propB', value: true }; // A.propB is number, not boolean
const filter: Filter<A> = { op: 'in', field: 'propA', value: 'test' }; // in operator expects array (of strings, in this case)
const filter: Filter<A> = { op: 'in', field: 'propA', value: [5, 6, 7] }; // A.propA is string, operator is in, so value should be an array of strings, not of numbers
const filter: Filter<A> = { op: 'exists', field: 'propC', value: true }; // exists operator does not expect a value
我该怎么做?
这是我到目前为止的代码:
export interface Entity {
id: number;
isActive: boolean;
}
interface ComparisonFilter {
op: 'eq'|'noteq'|'gt'|'gte'|'lt'|'lte'|'beginsWith'|'endsWith'|'contains';
value: ???;
}
interface NoncomparisonFilter {
op: 'exist'|'notExist';
}
interface ListFilter {
op: 'in'|'notIn';
value: ???[];
}
type FieldFilter<T extends Entity> = (
ComparisonFilter |
NoncomparisonFilter |
ListFilter
) & { field: keyof T };
interface GroupingFilter<T extends Entity> {
op: 'AND'|'OR';
items: Filter<T>[];
}
export type Filter<T extends Entity> = FieldFilter<T> | GroupingFilter<T>;
答案 0 :(得分:0)
您可以创建允许类型的联合
interface A {
propA: string;
propB: number;
propC: boolean;
}
export interface Entity {
id: number;
isActive: boolean;
}
type WithoutValue = 'exist'
interface ComparisonFilter {
op: 'ne' | 'eq' | 'noteq' | 'gt' | 'gte' | 'lt' | 'lte' | 'beginsWith' | 'endsWith' | 'contains';
}
interface NoncomparisonFilter {
op: WithoutValue | 'notExist';
}
interface ListFilter {
op: 'in' | 'notIn';
}
type ApplyAll<T, Filters extends { op: string }> = {
op: Exclude<Filters['op'], WithoutValue>;
} & Enchance<T> | { op: WithoutValue, field: keyof T }
type AllFilters =
| ComparisonFilter
| NoncomparisonFilter
| ListFilter;
type FieldFilter<T> = ApplyAll<T, AllFilters>
type Values<T> = T[keyof T]
type Enchance<T> = Values<{
[P in keyof T]: { field: P, value: T[P] | Array<T[P]> }
}>
interface GroupingFilter<T> {
op: 'AND' | 'OR';
items: Filter<T>[];
}
export type Filter<T> = FieldFilter<T> | GroupingFilter<T>;
/**
* Valid
*/
// single value operators
const filter1: Filter<A> = { op: 'eq', field: 'propA', value: 'test' };
const filter2: Filter<A> = { op: 'ne', field: 'propB', value: 5 };
const filter3: Filter<A> = { op: 'eq', field: 'propC', value: true };
// multi value operators
const filter4: Filter<A> = { op: 'in', field: 'propA', value: ['test1', 'test2'] };
const filter5: Filter<A> = { op: 'in', field: 'propB', value: [5, 6, 7] };
const filter6: Filter<A> = { op: 'notIn', field: 'propC', value: [true] };
// no value operators
const filter7: Filter<A> = { op: 'exist', field: 'propC' };
// Invalid
const filter_: Filter<A> = { op: 'eq', field: 'propA', value: 5 }; // A.propA is string, not number
const filter__: Filter<A> = { op: 'ne', field: 'propB', value: true }; // A.propB is number, not boolean
const filter___: Filter<A> = { op: 'in', field: 'propA', value: 'test' }; // in operator expects array (of strings, in this case)
const filter____: Filter<A> = { op: 'in', field: 'propA', value: [5, 6, 7] }; // A.propA is string, operator is in, so value should be an array of strings, not of numbers
const filter_____: Filter<A> = { op: 'exist', field: 'propC', value: true }; // exists operator does not expect a value
您可以在我的 blog
中找到有关此模式的更多说明