首页 > 解决方案 > 为联合类型和泛型编写巧妙的类型保护

问题描述

我有以下结构:

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'}}));

这工作正常,但留下了将 aIFragmentData与 wrong结合的选项FragmentType。例如 : isFragmentType<IMarkFragmentData>('Sentence')将是有效代码,即使 'Sentence' 是该IMarkFragmentData类型的错误鉴别器。

有没有更聪明的方法来编写我的类型保护甚至重组我的打字?

标签: javascripttypescriptdiscriminated-uniondiscriminator

解决方案


isFragmentType()您的函数的主要问题是类型t根本不受T. 我可能会重写它以T表示该type属性,并使用实用Extract程序类型来过滤Fragment具有该属性的成员的联合type

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
  }
}

Playground 代码链接


推荐阅读