问题描述
#1 我有一个对象类型的列。列可以过滤或不可过滤,如果 isFilterable
是 true
那么类型 Column
应该要求:filterType
、isTopBarFilter?
和 options
(但仅限如果 filterType
是 'SELECT'
- #2).
type Column = {
name: string;
isFilterable: boolean; // passing here false should be equal with not passing the property at all (if possible)
// below properties should exist in type only if isFilterable = true
filterType: 'SELECT' | 'TEXT' | 'DATE';
options: string[]; // this property should exist in type only if filterType = 'SELECT'
isTopBarFilter?: boolean;
};
我使用类型联合来做这种类型,它几乎可以正常工作
type FilterableColumn = {
isFilterable: true;
filterType: 'SELECT' | 'TEXT' | 'DATE';
options: string[];
isTopBarFilter?: boolean;
};
type NonFilterableColumn = {
isFilterable: false;
};
type Column = (NonFilterableColumn | FilterableColumn) & {
name: string;
};
但是:
- 正如我之前提到的 (#2),只有当
Column
是options
时,filterType
才需要'SELECT'
。我试图用类型联合来做到这一点,但它变得很奇怪:
type FilterableSelectColumn = {
filterType: 'SELECT';
options: string[];
};
type FilterableNonSelectColumn = {
filterType: 'TEXT' | 'DATE' | 'NUMBER';
};
type FilterableColumn = (FilterableSelectColumn | FilterableNonSelectColumn) & {
isFilterable: true;
isTopBarFilter?: boolean;
};
type NonFilterableColumn = {
isFilterable: false;
};
type Column = (FilterableColumn | NonFilterableColumn) & {
name: string;
};
// e.g
const col: Column = {
name: 'col2',isFilterable: false,filterType: 'SELECT',// unwanted
isTopBarFilter: false,// unwanted
options: ['option1'],// unwanted
};
如果我将 isFilterable
设置为 false,TS 不会建议不需要的属性(很好),但如果我传递这些不需要的道具也不会显示错误(不好)
有没有办法改进我的解决方案(或其他解决方案)以实现我在开头所描述的内容 (#1)?
解决方法
让我们看看分布规律如何影响您的案例中的交集和并集的解析方式。首先,以下内容:
type OuterUnionMemberA = (UnionMemberA | UnionMemberB) & IntersectedA;
相当于:
type OuterUnionMemberA = (UnionMemberA & IntersectedA) | (UnionMemberB & IntersectedA);
这反过来又引导我们到以下几点:
type ComplexType = (OuterUnionMemberA | IntersectedB) & OuterIntersected;
相当于这个复杂的联合:
type ComplexType = (UnionMemberA & IntersectedA & OuterIntersected) | (UnionMemberB & IntersectedA & OuterIntersected) | (IntersectedB & OuterIntersected);
让我们手动解析别名,看看它给我们留下了什么:
type ComplexType = {
filterType: 'SELECT';
options: string[];
isFilterable: true;
extraProp?: boolean;
name: string;
} | {
filterType: 'TEXT' | 'DATE' | 'NUMBER';
isFilterable: true;
extraProp?: boolean;
name: string;
} | {
isFilterable: false;
name: string
}
为了验证我们对它是同一类型的期望,让我们做一个相等性测试:
type isSupertype = ComplexType extends ComplexTypeUnwrapped ? true : false; //true
type isSubtype = ComplexTypeUnwrapped extends ComplexType ? true : false; //true
以上所有内容都是为了清楚说明:
- 有 2 个判别属性(
filterType
和isFilterable
); - 在这种情况下,不执行excess property check;
以上的组合被证明是 TypeScript 的一个确认的设计限制(请参阅源存储库上的 this 和 this 问题以及导致前一个问题被提出的 the question ).
但是你能做些什么呢? never
来拯救:被禁止作为属性与具有类型 never
几乎相同,因此对 isFilterable
属性进行相应的简单更改以避免使 isFilterable
第二个判别属性(为简单起见省略了额外的可选属性)应该可以解决问题:
type Column =
(
{ name: string,isFilterable:true,filterType:"SELECT",options:string[] } |
{ name: string,filterType:"TEXT"|"DATE" } |
{ name: string,isFilterable:never } |
{ name: string,isFilterable:false } //allows "name-only" case
)
const notFilterableAll: Column = { name: 'col2',isFilterable:false };
const notFilterableText: Column = { name: 'col2',filterType: "TEXT" }; //Property 'isFilterable' is missing;
const notFilterableSelect: Column = { name: "col2",filterType: "SELECT",options: [] }; //Property 'isFilterable' is missing;
const notFilterableSelectMissingOpts: Column = { name: "col2",filterType: "SELECT" }; //Type '"SELECT"' is not assignable to type '"TEXT" | "DATE"';
const selectFilterOk: Column = { name: 'col2',options: [] }; //OK
const textFilter: Column = { name: "col2",filterType: "TEXT" }; //OK
,
好的,经过几个晚上我设法做到了,我有两个解决方案:
1.
type FilterableColumn = {
isFilterable: true;
isTopBarFilter?: boolean;
} & (
| {
filterType: 'SELECT';
options: string[];
}
| {
filterType: 'TEXT' | 'DATE';
});
type NonFilterableColumn = {
isFilterable?: undefined; // same result with never
filterType?: undefined; // same result with never
};
type ColumnBaseFields = {
name: string;
};
type Column = (FilterableColumn | NonFilterableColumn) & ColumnBaseFields;
const column: Column = {
name: 'someName',isFilterable: true,filterType: 'SELECT',options: ['option'],};
它如我所愿,出现了 Typescript 错误,但错误描述不准确。我注意到 TypeScript 在同一嵌套级别上与许多联合一起工作很奇怪
因此我用嵌套过滤器选项组成了第二个解决方案
2.
type FilterSettings = (
| {
filterType: 'SELECT';
options: string[];
}
| {
filterType: 'TEXT';
}) & {
isTopBarFilter?: boolean;
};
type FilterableColumn = {
isFilterable: true;
filterSettings: FilterSettings;
};
type NonFilterableColumn = {
isFilterable?: undefined; // same result with never
};
type ColumnBaseFields = {
name: string;
};
type Column = (FilterableColumn | NonFilterableColumn) & ColumnBaseFields;
const column: Column = {
name: 'someName',filterSettings: {
filterType: 'SELECT',options: ['option']
}
};
工作正常,打字稿会准确地告诉我们何时遗漏了某个键以及何时不需要某个键。
希望对大家有所帮助