简体   繁体   English

检查构建器模式中的详尽性

[英]Check for exhaustive in a builder pattern

I want to build something like this:我想构建这样的东西:

type MyValues = "good" | "bad" | "ugly"

function doSomething(values:MyValues) {
   return startBuilder(values)
          .case("good", 1)
          .case("bad", 2)
          .exhaustive() // this should give an compiler error, since I forgot the `ugly` }

I tried to figure out the tail using TypeScript's infer - but with no luck:我试图使用 TypeScript 的infer来找出tail ——但没有成功:

type Extract<T, A> = A extends T
  ? T extends infer Tail
    ? Tail
    : never
  : never;
type X = Extract<Values, 'good'>;

But this does not work.但这不起作用。 Any ideas?有任何想法吗?

Here is a playground: https://stackblitz.com/edit/typescript-mguypt这是一个游乐场: https://stackblitz.com/edit/typescript-mguypt

We'll first need a type that checks if two types are EXACTLY equal, a simple extends won't do the trick here:我们首先需要一个类型来检查两个类型是否完全相等,简单的extends不会在这里起作用:

type Equals<A, B> = (<T>() => T extends A ? true : false) extends (<T>() => T extends B ? true : false) ? true : false;

The explanation behind why this interesting-looking type works can be found here by jcalz no less. jcalz 在这里可以找到为什么这种看起来很有趣的类型有效的解释。

Then we define the Builder class:然后我们定义Builder class:

class Builder<T, SoFar extends ReadonlyArray<unknown> = []> {
    private cases = new Map<T, unknown>();
}

It takes a generic T which is what it will check its cases against, and a generic not intended to be passed by the end user.它需要一个泛型T来检查其案例,以及一个不打算由最终用户传递的泛型。 This generic SoFar stores which cases have been used so far .这个通用SoFar存储到目前为止使用了哪些案例。

So let's build the case function now:那么让我们现在构建case function:

    case<V extends T>(v: V, d: unknown): Builder<T, [...SoFar, V]> {
        this.cases.set(v, d);

        return this as Builder<T, [...SoFar, V]>;
    }

It takes something that extends T and any value, then returns the builder as Builder<T, [...SoFar, V]> , which means we add the case to the cases used up so far.它需要扩展T和任何值的东西,然后将构建器返回为Builder<T, [...SoFar, V]> ,这意味着我们将 case 添加到到目前为止用完的 case 中。

Finally in here:终于在这里:

    exhaustive(...args: Equals<T, SoFar[number]> extends true ? [] : [{ error: "Builder is not exhaustive." }]) {

    }

We check if T and SoFar[number] are exactly the same.我们检查TSoFar[number]是否完全相同。 If they are, then we have exhausted all cases.如果是,那么我们已经用尽了所有情况。 Otherwise, we say that we expect a parameter.否则,我们说我们期待一个参数。

Because you will likely call exhaustive with no parameters, calling it without using up all cases will display a nice error message for the user.因为您可能会在没有参数的情况下调用exhaustive ,所以在不用完所有情况的情况下调用它会向用户显示一条很好的错误消息。

You can even go one step further and include all the missing cases in the error message.您甚至可以更进一步 go 并在错误消息中包含所有缺失的案例。 The second playground link demonstrates this.第二个游乐场链接演示了这一点。

Playground 操场

Playground with error messages that display missing cases 带有显示缺失案例的错误消息的游乐场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM