跟踪类实例中的状态

Dav*_*mes 11 typescript

我想创建一个具有某些内部状态的类(可能正在加载,错误或成功)。我还想在类上有一些方法可以检查此类的状态。

理想的API:

function f(x: LoadingError<number>) {
  if (x.isLoading()) {
  } else if (x.isError()) {
  } else {
    (x.data); // TypeScript knows x.data is of type `number`
  }
}
Run Code Online (Sandbox Code Playgroud)

我一直在努力的主要事情是创建isLoadingisError方法,以便TypeScript可以理解它们。

我尝试在实际的类结构(“ " this is { ... }”)上编写一些用户定义的类型防护:

class Foo {
  public value: string | null;
  public hasValue(): this is { value: string } {
    return this.value !== null;
  }
}

const foo = new Foo();
if (foo.hasValue()) {
  // OK
  foo.value.toString();
} else {
  (foo.value); // TypeScript does NOT understand that this can only be null
}
Run Code Online (Sandbox Code Playgroud)

但是,由于TypeScript会“忘记”该else子句中的类实例的状态,因此无法使用。

我的硬性要求之一是为此使用,因为我不想拥有isLoading(instance)or isError(instance)方法,而是想要instance.isLoading()and instance.isError()

for*_*d04 7

我想创建一个具有某些内部状态的类(可能正在加载,错误或成功)

创建您可能的状态类型

type State<T> = ErrorState | SuccessState<T> | LoadingState;

type ErrorState = { status: "error"; error: unknown };
type SuccessState<T> = { status: "success"; data: T };
type LoadingState = { status: "loading" };
Run Code Online (Sandbox Code Playgroud)

我还想在类上有一些方法可以检查此类的状态。

创建包含状态的Foo类

我想在这里,你要调用某种公共型导引法的isSuccessisLoadingisError支票类实例的状态,并且可以通过使用缩小的真实分支的状态类型的if / else。您可以通过创建类型防护来做到这一点,该防护返回包含您的缩小状态的多态此类型谓词。

// T is the possible data type of success state
class Foo<T = unknown> {
  constructor(public readonly currentState: State<T>) {}

  isLoading(): this is { readonly currentState: LoadingState } {
    return this.currentState.status === "loading";
  }

  isSuccess(): this is { readonly currentState: SuccessState<T> } {
    return this.currentState.status === "success";
  }

  isError(): this is { readonly currentState: ErrorState } {
    return this.currentState.status === "error";
  }
}
Run Code Online (Sandbox Code Playgroud)

让我们测试一下:

const instance = new Foo({ status: "success", data: 42 });
if (instance.isSuccess()) {
  // works, (property) data: number
  instance.currentState.data; 
}
Run Code Online (Sandbox Code Playgroud)

操场

局限性

这很重要:只有在currentState使用public修饰符声明了类成员(TypeScript限制)后,您才能这样做!如果已将其声明为private,则不能为此目的使用此类类型防护。另一种选择是返回一个可选状态:

class Foo<T = unknown> {
... 
  getSuccess(): SuccessState<T> | null {
    return this.currentState.status === "success" ? this.currentState : null;
  }
...
}

// test it 
const instance = new Foo({ status: "success", data: 42 });
const state = instance.getSuccess()
if (state !== null) {
  // works, (property) data: number
  state.data
}
Run Code Online (Sandbox Code Playgroud)

操场

有关分支错误的旁注foo.hasValue()

const foo = new Foo();
if (foo.hasValue()) {
  // OK
  foo.value.toString();
} else {
  (foo.value); // TypeScript does NOT understand that this can only be null
}
Run Code Online (Sandbox Code Playgroud)

TypeScript不会foo.value在这里推断为null,因为这foo.hasValue()是一个自定义的Type Guard,它只会将您的类型缩小为{ value: string }true。如果条件为假,则再次采用默认类型valuestring | null)。自定义类型防护取消了TypeScript的常规分支逻辑。您可以通过简单地省略它来更改它:

if (foo.value !== null) {
  // OK
  foo.value.toString();
} else {
  (foo.value); // (property) Foo.value: null
}
Run Code Online (Sandbox Code Playgroud)

操场

如果您从类实例内部检查状态

class Foo<T = unknown> {
  ...
  // Define private custom type guard. We cannot use a polymorphic
  // this type on private attribute, so we pass in the state directly.
  private _isSuccess(state: State<T>): state is SuccessState<T> {
    return state.status === "success";
  }

  public doSomething() {
    // use type guard
    if (this._isSuccess(this.currentState)) {
      //...
    }

    // or inline it directly
    if (this.currentState.status === "success") {
      this.currentState.data;
      //...
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

操场


Fed*_*kun 2

您可以创建一个可以处理三种情况的类型:

  • 成功:值已获取并且现在可用
  • Loading:我们正在获取值
  • 失败:无法获取值(错误)
type AsyncValue<T> = Success<T> | Loading<T> | Failure<T>;
Run Code Online (Sandbox Code Playgroud)

然后您可以使用自定义防护来定义所有这些类型:

class Success<T> {
  readonly value: T;

  constructor(value: T) {
    this.value = value;
  }

  isSuccess(this: AsyncValue<T>): this is Success<T> {
    return true;
  }

  isLoading(this: AsyncValue<T>): this is Loading<T> {
    return false;
  }

  isError(this: AsyncValue<T>): this is Failure<T> {
    return false;
  }
}

class Loading<T> {
  readonly loading = true;

  isSuccess(this: AsyncValue<T>): this is Success<T> {
    return false;
  }

  isLoading(this: AsyncValue<T>): this is Loading<T> {
    return true;
  }

  isError(this: AsyncValue<T>): this is Failure<T> {
    return false;
  }
}

class Failure<T> {
  readonly error: Error;

  constructor(error: Error) {
    this.error = error;
  }

  isSuccess(this: AsyncValue<T>): this is Success<T> {
    return false;
  }

  isLoading(this: AsyncValue<T>): this is Loading<T> {
    return false;
  }

  isError(this: AsyncValue<T>): this is Failure<T> {
    return true;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以AsyncValue在代码中使用:

function doSomething(val: AsyncValue<number>) {
  if(val.isLoading()) {
    // can only be loading
  } else if (val.isError()) {
    // can only be error
    val.error
  } else {
    // can only be the success type
    val.value // this is a number
  }
}
Run Code Online (Sandbox Code Playgroud)

可以通过其中之一调用:

doSomething(new Success<number>(123))
doSomething(new Loading())
doSomething(new Failure(new Error('not found')))
Run Code Online (Sandbox Code Playgroud)