你能创建一个 Typescript 类型,它恰好具有一组属性中的一个并且可以被该组索引吗

Can you create a Typescript type that has exactly one of a set of properties AND is indexable by that set

我有以下类型:

type OrBranch = {
   or: Branch[]
}

type AndBranch = {
   and: Branch[]
}

我想要一个 Branch 类型,它可以是 OrBranchAndBranch。所以我首先尝试:

type Branch = AndBrand | OrBranch

工作很棒,除非我想做类似的事情:

let branch: Branch = ...
let andOr = 'and';   // or 'or'

let nodes = branch[andOr]

然后我发现 branch 不可索引。好的,所以我尝试使用可索引类型:

type AndOr = 'and' | 'or';
type Branch = Record<AndOr, Branch[]>;

但这需要 andor 都存在,所以在这种情况下我不能将 AndBranch 转换为 Branch。

同样

type Branch = Record<AndOr, Branch[]> | AndBranch | OrBranch

不起作用,原因相同。

虽然我可以使用类型保护来确定类型,但我有对这些对象进行操作的长函数,在这些对象中,除了 属性 之外,它们可以被同等对待。我希望通过使用 andOr 变量来消除一堆重复代码,类型保护并不能真正阻止。例如:

let retval = {} as Branch;
if (isAnd(branch)) {  // branch is a Branch parameter passed in
   (retval as AndBranch).and = [] as Branch[];
   set = (retval as AndBranch).and;
} else {
   (retval as OrBranch).or = [] as Branch[];
   set = (retval as OrBranch).or;
}

set = _.reduce(set, (all, item: Branch)=> {
   if (isAnd(branch) && isAnd(item)) 
      return _.union(all, item.and);
   else if (isOr(branch) && isOr(item)) 
      return _.union(all, item.or);
   else 
      return all;
}, [] as Branch[]);

对比

andOr = isAnd(branch) ? 'and' : 'or';
let retval = {} as Branch;
retval[andOr] = _.reduce(set, (all, item: Branch) => {
   if (item[andOr]) 
      return _.union(all, item[andOr]);
   else
      return all;
}, [] as Branch[]);

我知道有一种方法可以精确地要求 andor 之一(就像对 的回答)。但是那个类型是不可索引的。

是否可以同时获得两种效果?

使类型可索引并不能解决基本问题,即您试图在可能是 and 分支的对象上使用 属性 and(因此有 属性) 或者可能是 or 分支(因此不是)。相反,询问分支它有什么并使用它,因为这允许 TypeScript 缩小类型:

if ("and" in branch) {
    // ...use `branch.and`...
} else {
    // ...use `branch.or`...
}

如果你的代码认为它知道分支是什么,你可以将它与你的 andOr 结合起来,也许通过使用类型断言函数:

function assertIsAndBranch(branch: Branch): asserts branch is AndBranch {
    if (!("and" in branch)) {
        throw new Error(`branch is not an AndBranch`);
    }
}
// (And `assertIsOrBranch`)

然后:

if (andOr === "and") {
    assertIsAndBranch(branch);
    // ...here, TypeScript knows `branch` is an `AndBranch`...
}

重新编辑:如果您有重要代码需要在不知情的情况下处理 AndBranchandOrBranchor 中包含的项目或者关心它们是 AndBranch 还是 OrBranch 实例,我将它们重新设计为 discriminated union,其中工会的所有成员都具有相同的 items(或其他)属性:

type OrBranch = {
    type: "or";
    items: Branch[];
};

type AndBranch = {
    type: "and";
    items: Branch[];
};

这样,不关心分支类型的代码就可以使用 items。您显示的代码是:

const items = _.reduce(branch.items, (all, item) => {
    return _.union(all, item.items);
}, [] as Branch[]);
const retval = {type: branch.type, items};

Playground link

让属性做 double-duty(指示分支的类型和其中的项)就像它们在当前类型中所做的那样,这使得编写类型安全代码来处理没有 knowing/caring 什么类型的项分支真的很难。