带有省略类型的 TypeScript 中的函数链接

Function chaining in TypeScript with Omit types

假设我想在 TypeScript 中实现类型化函数链,但在这种情况下,调用函数会从 return 类型中删除该函数。例如:

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

interface Chainable {
  execute: () => Promise<void>;
}

interface Chain1 extends Chainable {
  chain1?: () => Omit<this, 'chain1'>;
}

interface Chain2 extends Chainable {
  chain2?: () => Omit<this, 'chain2'>;
}

let chain: Chain1 & Chain2 = {
  execute: () => null,
  chain1: () => {
    delete chain.chain1;
    return chain;
  },
  chain2: () => {
    delete chain.chain2;
    return chain;
  }
};

chain.chain1().chain2().execute(); // Using the function chain

当我调用 chain.chain1() 时,我会得到 Pick<Chain1 & Chain2, "execute" | "chain2" 作为 return 类型,这很好,因为它阻止我调用 chain1 两次。

但是,一旦我将它与 chain2 函数链接起来,return 类型就变成了 Pick<Chain1 & Chain2, "chain1" | "execute"。这将允许我再次调用 chain1,这是我试图阻止的。理想情况下,编译器会抱怨 Property 'chain1' does not exist on type:

chain.chain1().chain2().chain1(); // I want this to return a compiler error :(

我这样做的方式正确吗?是否可以在 TypeScript 中逐步将多个 Omit 类型组合在一起,以便 return 类型连续省略属性?

我认为 this 的类型是在首次检查函数时确定的,之后的任何时候都不会重新评估。因此 chain2 的第二次调用的 this 仍然是原始的 this 而不是 chain1 的 return 类型。我不确定这是预期的行为还是错误,您可能需要检查 GitHub 是否存在类似问题。

一种解决方法是使用将绑定到 this 的泛型类型参数为任何给定函数捕获 this。这将确保通过函数链的正确类型流。一个小问题是使用箭头函数无法进行输入,您需要使用常规函数并访问 this:

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

interface Chainable {
    execute: () => Promise<void>;
}

interface Chain1 extends Chainable {
    chain1?: <T extends Chain1>(this: T) => Omit<T, 'chain1'>;
}

interface Chain2 extends Chainable {
    chain2?: <T extends Chain2>(this: T) => Omit<T, 'chain2'>;
}

let chain: Chain1 & Chain2 = {
    execute: () => null,
    chain1: function () {
        delete this.chain1;
        return this;
    },
    chain2: function ()  {
        delete this.chain2;
        return this;
    }
};

chain.chain1().chain2().execute(); // Using the function chain
chain.chain1().chain2().chain1().execute(); // error