在另一个 属性 的基础上添加一个额外的类型 属性
Add an extra type property base on another property
#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;
};
我使用 types union 做这种类型,它几乎可以正常工作
type FilterableColumn = {
isFilterable: true;
filterType: 'SELECT' | 'TEXT' | 'DATE';
options: string[];
isTopBarFilter?: boolean;
};
type NonFilterableColumn = {
isFilterable: false;
};
type Column = (NonFilterableColumn | FilterableColumn) & {
name: string;
};
但是:
- 正如我之前提到的 (#2) 只有当
filterType
是 'SELECT'
时,Column
才需要 options
。我曾尝试使用类型联合来做到这一点,但它变得很奇怪:
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 不会提示不需要的属性(这很好)但是如果我传递这些不需要的属性也不会显示错误(这很糟糕)
- 我的解决方案也强制通过
isFilterable
,即使它是 false
,正如我上面提到的,我只想在它是 true
时通过它
有没有办法改进我的解决方案(或其他解决方案)以实现我在开头描述的内容(#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 and this issues on the source repository and the question 导致提出前一个问题)。
但是你能做些什么呢? never
拯救:作为 属性 被禁止与具有类型 never
几乎相同,因此相应地对 isFilterable
属性 进行简单更改为了避免使 isFilterable
成为第二个判别式 属性(为简单起见,省略了额外的可选 属性)应该可以解决问题:
type Column =
(
{ name: string, isFilterable:true,filterType:"SELECT",options:string[] } |
{ name: string, isFilterable:true,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', isFilterable:true, filterType: "SELECT", options: [] }; //OK
const textFilter: Column = { name: "col2", isFilterable:true, 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',
isFilterable: true,
filterSettings: {
filterType: 'SELECT',
options: ['option']
}
};
工作正常,typescript 准确地告诉我们什么时候缺少某些键以及什么时候不需要某些键。
希望对大家有所帮助
#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;
};
我使用 types union 做这种类型,它几乎可以正常工作
type FilterableColumn = {
isFilterable: true;
filterType: 'SELECT' | 'TEXT' | 'DATE';
options: string[];
isTopBarFilter?: boolean;
};
type NonFilterableColumn = {
isFilterable: false;
};
type Column = (NonFilterableColumn | FilterableColumn) & {
name: string;
};
但是:
- 正如我之前提到的 (#2) 只有当
filterType
是'SELECT'
时,Column
才需要options
。我曾尝试使用类型联合来做到这一点,但它变得很奇怪:
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 不会提示不需要的属性(这很好)但是如果我传递这些不需要的属性也不会显示错误(这很糟糕)
- 我的解决方案也强制通过
isFilterable
,即使它是false
,正如我上面提到的,我只想在它是true
时通过它
有没有办法改进我的解决方案(或其他解决方案)以实现我在开头描述的内容(#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 and this issues on the source repository and the question 导致提出前一个问题)。
但是你能做些什么呢? never
拯救:作为 属性 被禁止与具有类型 never
几乎相同,因此相应地对 isFilterable
属性 进行简单更改为了避免使 isFilterable
成为第二个判别式 属性(为简单起见,省略了额外的可选 属性)应该可以解决问题:
type Column =
(
{ name: string, isFilterable:true,filterType:"SELECT",options:string[] } |
{ name: string, isFilterable:true,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', isFilterable:true, filterType: "SELECT", options: [] }; //OK
const textFilter: Column = { name: "col2", isFilterable:true, 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',
isFilterable: true,
filterSettings: {
filterType: 'SELECT',
options: ['option']
}
};
工作正常,typescript 准确地告诉我们什么时候缺少某些键以及什么时候不需要某些键。
希望对大家有所帮助