为联合类型和泛型编写巧妙的类型保护
Writing clever type guards for union type and generics
我有以下结构:
const fragmentTypes = [
'Word',
'Sentence',
] as const;
type FragmentType = typeof fragmentTypes[number];
interface IFragmentData {
type: FragmentType;
}
interface IFragment<T extends IFragmentData> {
id: string;
type: T['type'];
data: Omit<T, 'type'>;
}
interface IWordFragmentData extends IFragmentData {
type: 'Word';
word: string;
}
interface ISentenceFragmentData extends IFragmentData {
type: 'Sentence';
sentence: string;
}
type Fragment =
| IFragment<IWordFragmentData>
| IFragment<ISentenceFragmentData>;
并且知道有挑战,我经常 filter
片段。我目前的方式是通过以下类型保护:
function isFragmentType<T extends IFragmentData>(t: FragmentType) {
return (x: Fragment | IFragment<T>): x is IFragment<T> => {
return x.type === t;
};
}
console.log(isFragmentType<IWordFragmentData>('Word')({type: 'Word', id: 'test123', data: {word: 'test123'}}));
这很好用,但保留了将 IFragmentData
与错误的 FragmentType
组合的选项。例如: isFragmentType<IMarkFragmentData>('Sentence')
将是有效代码,即使 'Sentence' 将是 IMarkFragmentData
类型的错误鉴别符。
有没有更聪明的方法来编写我的类型保护甚至重组我的类型?
您的 isFragmentType()
函数的主要问题是 t
的类型根本不限于 T
。我可能会重写它,使 T
代表 type
属性,并使用 the Extract
utility type 过滤 Fragment
联合 [=17] 的成员=] 属性:
function isFragmentType<T extends Fragment['type']>(t: T) {
return (x: Fragment): x is Extract<Fragment, { type: T }> => {
return x.type === t;
};
}
您可以验证它是否按预期工作(并且您不必手动指定 T
,因为它可以从 t
的类型推断出来):
function processFragment(f: Fragment) {
if (isFragmentType("Word")(f)) {
f.data.word.toUpperCase(); // okay
} else {
f.data.sentence.toUpperCase(); // okay
}
}
仅供参考,我不确定为什么 isFragmentType()
是 curried,但看起来不需要:
function isFragmentType<T extends Fragment['type']>(
t: T, x: Fragment
): x is Extract<Fragment, { type: T }> {
return x.type === t;
}
function processFragment(f: Fragment) {
if (isFragmentType("Word", f)) {
f.data.word.toUpperCase(); // okay
} else {
f.data.sentence.toUpperCase(); // okay
}
}
我有以下结构:
const fragmentTypes = [
'Word',
'Sentence',
] as const;
type FragmentType = typeof fragmentTypes[number];
interface IFragmentData {
type: FragmentType;
}
interface IFragment<T extends IFragmentData> {
id: string;
type: T['type'];
data: Omit<T, 'type'>;
}
interface IWordFragmentData extends IFragmentData {
type: 'Word';
word: string;
}
interface ISentenceFragmentData extends IFragmentData {
type: 'Sentence';
sentence: string;
}
type Fragment =
| IFragment<IWordFragmentData>
| IFragment<ISentenceFragmentData>;
并且知道有挑战,我经常 filter
片段。我目前的方式是通过以下类型保护:
function isFragmentType<T extends IFragmentData>(t: FragmentType) {
return (x: Fragment | IFragment<T>): x is IFragment<T> => {
return x.type === t;
};
}
console.log(isFragmentType<IWordFragmentData>('Word')({type: 'Word', id: 'test123', data: {word: 'test123'}}));
这很好用,但保留了将 IFragmentData
与错误的 FragmentType
组合的选项。例如: isFragmentType<IMarkFragmentData>('Sentence')
将是有效代码,即使 'Sentence' 将是 IMarkFragmentData
类型的错误鉴别符。
有没有更聪明的方法来编写我的类型保护甚至重组我的类型?
您的 isFragmentType()
函数的主要问题是 t
的类型根本不限于 T
。我可能会重写它,使 T
代表 type
属性,并使用 the Extract
utility type 过滤 Fragment
联合 [=17] 的成员=] 属性:
function isFragmentType<T extends Fragment['type']>(t: T) {
return (x: Fragment): x is Extract<Fragment, { type: T }> => {
return x.type === t;
};
}
您可以验证它是否按预期工作(并且您不必手动指定 T
,因为它可以从 t
的类型推断出来):
function processFragment(f: Fragment) {
if (isFragmentType("Word")(f)) {
f.data.word.toUpperCase(); // okay
} else {
f.data.sentence.toUpperCase(); // okay
}
}
仅供参考,我不确定为什么 isFragmentType()
是 curried,但看起来不需要:
function isFragmentType<T extends Fragment['type']>(
t: T, x: Fragment
): x is Extract<Fragment, { type: T }> {
return x.type === t;
}
function processFragment(f: Fragment) {
if (isFragmentType("Word", f)) {
f.data.word.toUpperCase(); // okay
} else {
f.data.sentence.toUpperCase(); // okay
}
}