在类实例中跟踪状态

共7个回答, 标签: typescript

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

理想的 API:

函数 f (x: 加载错误) {
如果 (x.isLoading () {
} 如果 (x.isError () {
} 其他 {
(X.data);//打字稿知道 x.data 类型为 “number”
}
}

我正在努力解决的主要问题是创建isLoading and isError打字稿可以理解的方法。

我试图在实际的类结构上写一些用户定义的类型警卫 ("" this is { ... }"):

类 Foo {
公共值: string | null;
公共 hasValue (): 这是 {value: string} {
返回此值!= = null;
}
}

新的 foo ();
如果 (foo.hasValue () {
//OK
Foo.value.toString ();
} 其他 {
(Foo.value);//打字稿不明白这只能是 null
}

然而,这并不起作用,因为打字稿 “忘记” 了else条款。

我的一个硬性要求是使用上课为了这个,因为我不想isLoading(instance) or isError(instance) methods but rather instance.isLoading() and instance.isError()

第1个答案

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

创建可能的状态类型

类型状态= ErrorState | SuccessState| 加载状态;

类型错误状态 = {状态: “错误”; 错误: 未知};
类型成功状态= {状态: “成功”; 数据: T};
类型 LoadingState = {状态: “加载”};

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

创建包含状态的 Foo 类

我想在这里,你想调用某种公共类型的保护方法isSuccess, isLoading,isError它检查类实例状态,并可以使用 if/else 缩小真正分支中的状态类型。你可以做到这一点的创造型警卫回报这种类型的多态性包含您的缩小状态的断言。

//T 是成功状态的可能数据类型
Foo 类{
构造函数 (公共只读当前状态: State) {}

IsLoading (): 这是 {readonly current state: LoadingState} {
返回此。当前状态。状态 = “加载”;
}

IsSuccess (): 这是 {只读当前状态: SuccessState} {
返回此。当前状态。状态 = “成功”;
}

IsError (): 这是 {只读当前状态: 错误状态} {
返回此。当前状态。状态 = “错误”;
}
}

我们来测试一下:

Const 实例 = new Foo ({状态: “成功”,数据: 42});
如果 (实例.isSuccess () {
//工作,(属性) 数据: 数字
实例。当前状态。数据;
}

[操场](https://www.typescriptlang.org/play/#code/C4TwDgpgBAysCGwIB4AqA KBeKBRATvgPb5yLQA sArgMa0QDOjZSamVAMkfACYCWAOwDmrCAG4AUKEh5CJMdigBvKIwTBqjAFxQARBHn494qIeL5d1QQGtBRAO6CoAXykzoMOg2Zj2S1XVELV09Rm8mRhMoXkR4XVRXd3Bobj4hUQ1oHECNEP0AGx4BET0kyUkAekqoRP5GKGAAC2gwImZ ACMC6FiERpSoIgAzNQjmNSzJWgL4CYAxIiI0JWs7R0FMZUkoKFoiQXV8OmASAAowam7 Wih8CD4DgpA96kIIQWAxXT8MAEoVC4Krt6mkSsIzn9dM16lBYapaG97p9vlAwRlFC4VDtdncIJp8M4YYwAHSI94orIkoKaBpYemFYoZEw4oE4 peeiRSHQpqw GvClfLI-cYsLL LHbXF4glEvmk8nI4Xkal5OkMsJilm7NkgxgECw8xoKuENBFIj4qpC6Q0KLKubEy 5yk31MmWymqmlabCa8wkHWuSRs-aHYBw8PwQQMJSCCAOKCLIhnXLBHT6cJc5h6AA0MTiugALAAmVx-KT8UZnIRBGMQEkcsWQgHSqDVKAOEg2Rj5i7ESD4UAAvrxKCCagAW06hnZUfrHqFYhJo9MIaAA)

限制

交易来了: 只有当你声明了你的类成员时,你才能这样做currentState公众号修饰符 (打字稿限制)!如果你已经申报了私人,您不能为此目的使用这样的类型保护。另一种选择是返回可选状态:

Foo 类{
...
想要成功 (): SuccessState| Null {
返回此。当前状态。状态 = “成功”?当前状态: null;
}
...
}

//测试一下
Const 实例 = new Foo ({状态: “成功”,数据: 42});
状态 = const。想要成功的 ()
如果 (状态!= = null) {
//工作,(属性) 数据: 数字
状态数据
}

[操场](https://www.typescriptlang.org/play/#code/C4TwDgpgBAysCGwIB4AqA KBeKBRATvgPb5yLQA sArgMa0QDOjZSamVAMkfACYCWAOwDmrCAG4AUKEh5CJMdigBvKIwTBqjAFxQARBHn494qIeL5d1QQGtBRAO6CoAXykzoMOg2Zj2S1XVELV09Rm8mRhMoXkR4XVRXd3Bobj4hUQ1oHECNEP0AGx4BET0kyUlaAvhmKAAxIiI0JWs7R0FMZUkoKFoiQXV8OmASAAowfH4AN3IofAg foKQXupCCEFgMV0-DABKFRcKnuEIYDSS4VG93QuMxSpBagKClW6eubO152AAC35GAA6WhreabMSAoKaRjYLA4PRFdKlKAAfigfwBwNBGy2WSguieLykPSO71OWwizGuO0pLCy-kez1eXQ n00 B -yBIPW4KykLyMLh8PC9EiZTRGO52L5swJTOJrmOUHJBAs1LkFgeUEJzPePXm7M5mJ5YNx5AFwSFcP05hIEvRXKxvPNSHxOoV7yOpL6A2AUCEQUEDCUgggDnqjVGuStoVFPiiABoYnFdAAWABMrj2Ul96jUeJwgYQwYggPJXjFVL2kn4ADMoKModAAIQ23UHFlQAD03agDhINkYyfGxEg FAB1iCHlAFsAEaGd7NwHT CSI5AA)

注意事项

第2个答案

我喜欢用”歧视工会(或 “标记工会”)。 像这样的事情:

类 LoadingFoo {
状态: “加载”;
}
类错误 {
状态: “错误”;
错误: 任何;
}
类 SuccessFoo{
状态: “成功”;
值: T | 未定义;
}

Foo 类型= LoadingFoo | ErrorFoo | SuccessFoo;

让吧: Foo;
如果 (bar.status = = '成功') {
值;//OK
Bar.error;//error
} 如果 (bar.status = = 'error') {
值;//错误
Bar.error;//OK
} 其他 {
值;//错误
Bar.error;//error
}

你可以在行动中看到它[现场演示](http://www.typescriptlang.org/play/?strictPropertyInitialization=false#code/MYGwhgzhAEAyD2YAmBLAdgcwGL3tA3gFDQnQQAuY5ArhAFzQDkIiqmjA3IQL6GiQwAogCdh8YTjxFSZSjXpMApqPGdipZWOEMwaAJ5de-KNADK1YMEVRJAHgAqAPgLqSFKrQaMIFq1DUyAG5gINSKDPbQAD7Q1GhIigBm6IpIhoSE5HoADorQdk7QALxwrOjYuNHQIlqSVeaW1hAFjlyEIIrk0ABGYNr5uLZo1AC23crOJfjc0JCxaADWaPAA7mizMHbDYxNcKInQABS9wgB07vLFRSXevk2MAJQuMienwaGKHNAA9N-QAPIAaVcPT6p004i v2gEOEPBhIAgeX2R1eF1oVxusMez1Ir3eYShf1hINesKJAOBM0UiLy0jxYIJnx xJUcJeYPJLJhbJ4QA).

第3个答案

正如我们在其他答案中看到的那样,有很多解决方案。

从阅读问题开始,你的要求是:

  1. 用一个类来表示一个州的联合
  2. 使用类成员将范围缩小到一个状态
  3. 在两个分支上正确缩小工作范围。

最大的问题是 3。缩小将通过与类型保护断言的类型相交来处理真正的分支。Else 分支通过从变量类型中排除断言类型来工作。如果类型是联合,编译器可以排除联合的所有匹配成分,这将会非常有效,但是在这里我们有一个类, 因此,没有可以排除的成分,我们只剩下原来的Foo类型。

IMO 最简单的解决方案是将类的实例类型与实际类解耦。我们可以键入一个构造函数,以返回具有适当状态联合的联合。这将允许排除机制在其他分支上按预期工作:

类 _ Foo {
公共值: string | null = null;
公共 hasValue (): 这是 {value: string} {
返回此值!= = null;
}
}

New Foo: new () => _ Foo &
({Value: string} | {value: null})
= _ Foo;


新的 foo ();
如果 (foo.hasValue () {
//OK
Foo.value.toString ();
} 其他 {
(Foo.value);//打字稿不明白这只能是 null
}

我们可以混合几个州:

类 _ Foo {
公共值: string | null = null;
公共错误: 字符串 | null = null;
公共进程: 字符串 | null = null
公共 isError (): 这是 {error: string} {//我们只需要指定足够的状态来选择所需的状态,下面只有一个状态有 error: string
返回此错误!= = null;
}
公共 isLoading (): 这是 {progress: string} {//我们只需要指定足够的状态来选择所需的状态,下面只有一个状态有 progress: string
返回此。值 = = null & & 此。进度!= = null;
}
}

New Foo: new () => _ Foo & (
| {Value: string,error: null,progress: null}//不再加载
| {Value: null,error: null,progress: string}//loading
| {Value: null,error: string,progress: null})
= _ Foo;


新的 foo ();
如果 (foo.isError () {
我们有一个错误
Foo.progress/null
值/null
错误。big ()//string
} 如果 (foo.isLoading () {
//仍在加载
Foo.progress/string
值/null
错误/null
} 其他 {
没有错误,没有加载我们有一个值
大 ()//字符串
错误/null
Foo.progress/null
}
第4个答案

我不确定你描述的用例 (考虑再次措辞问题以获得更多的澄清),但是如果你试图使用状态枚举这样你就可以避免任何空检查,并始终保持正确的有效设置状态,这对你来说会更好。

下面是我根据我认为你想要的功能做的一个例子。

  • 打字:
枚举 FoobarStatus {
加载 = “加载”,
Error = 'error',
成功 = 成功
}

接口 IFoobar {
状态: FoobarStatus,
IsLoading: () => 布尔,
IsError: () => 布尔,
IsSuccess: () => 布尔,
}
  • 班级:
Foobar 班{
Private _ status: FoobarStatus = FoobarStatus.loading;
构造函数 () {
_ Status = FoobarStatus.loading;
}
获取状态 (): FoobarStatus {
返回此状态。
}

设置状态 (状态: FoobarStatus) {
_ Status = status;
}

IsLoading (): 布尔 {
返回 (此状态 = = FoobarStatus.loading);
}

IsError (): 布尔 {
返回 (此状态 = = FoobarStatus.error);
}

IsSuccess (): 布尔 {
返回 (此状态 = = FoobarStatus。成功);
}
}
  • Console.logs () "的帮助函数
功能报告 (foobar: IFoobar): void {
控制台。日志 (“-- 报告 --”);
控制台。日志 (“状态:”,foobar。状态);
控制台。日志 (“isLoading:”,foobar。isLoading ());
控制台。日志 (“isError:”,foobar。isError ());
控制台。日志 (“issuess:”,foobar。isSuccess ());
控制台。日志 (“完成”);
}
  • 使用 foobar:
Const foobar = 新 Foobar();
报告 (foobar);
状态 = FoobarStatus。成功;
报告 (foobar);

第5个答案

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

  • 成功: 获得了价值,现在可以获得了
  • 加载: 我们正在获取值
  • 失败: 无法获取值 (错误)
型 AsyncValue= 成功| 装载| 失败;

然后,您可以使用自定义警卫定义所有这些类型:

班级成功{
只读值: T;

构造函数 (值: T) {
值 = 值;
}

IsSuccess (此: AsyncValue): 这是成功{
返回 true;
}

IsLoading (此: AsyncValue): 这是装货{
返回 false;
}

IsError (这: AsyncValue): 这是失败{
返回 false;
}
}

类加载{
只读加载 = true;

IsSuccess (此: AsyncValue): 这是成功{
返回 false;
}

IsLoading (此: AsyncValue): 这是装货{
返回 true;
}

IsError (这: AsyncValue): 这是失败{
返回 false;
}
}

类故障{
只读错误: 错误;

构造函数 (错误: 错误) {
错误 = 错误;
}

IsSuccess (此: AsyncValue): 这是成功{
返回 false;
}

IsLoading (此: AsyncValue): 这是装货{
返回 false;
}

IsError (这: AsyncValue): 这是失败{
返回 true;
}
}

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

功能做点什么 (val: AsyncValue) {
如果 (val.isLoading () {
//只能加载
} 如果 (val.isError () {
只能是错误的
Val.error
} 其他 {
//只能是成功类型
值/这是一个数字
}
}

其中一个可以调用:

新的成功(123)
做点什么 (新加载 ())
新故障 (新错误 (“未找到”))
第6个答案

一个断言将一种类型细化为它的一种子类型,不能被 “否定” 来推断另一种子类型。

在您试图完善的代码中type A into its subtype type B but if other subtypes of type A是可能的,那么否定就不起作用了 ([见操场](http://www.typescriptlang.org/play/#code/C4TwDgpgBAglC8UDeUBuBDANgVwgLigGdgAnASwDsBzKAHygu00ygF8AoUSKAIQWTRZcBYuWpt2ncNADC-FBhz4GTFhy7QAIvMFKRpSjXqNmAGiIB7ALYRgAC0MBRTIWXoKICQHovUAHQBkgAmEADGmOgk0ABm2BShwGQWFFB26IQAakIQABQAHgQwAJQEeVBkhLzsIeGR0KHJxFAFsJJk0VA5aZnZ UVFyOxQw81 irhQPkQG1OysUBAu0EhDI3lj2ZO ooZ0Ksxz7EA))。

类型 A = {value: string | null}
类型 B = {值: string}

类型 C = {值: null}
类型 D = {value: string | null,无论如何: any}
//..

声明函数 hasValue (x: A): x 是 B
声明 const x: A

如果 (hasValue (x)) {
值/字符串
} 其他 {
值/字符串 | null
}

一个简单的解决方法是直接在value而不是整个对象 (见操场)。

类型 A = {value: string | null}
类型 B = {值: string}

声明函数 isNullValue (x: A ['value'): x 为 null

如果 (isNullValue (x.value) {
值/null
} 其他 {
值/字符串
}
第7个答案

我试着做一个优雅的解决方案。


首先,我们定义了

接口加载 {
装: 真;
}

接口错误 {
错误: 错误;
}

接口成功{
数据: D;
}

类型状态= 加载 | 错误 | 成功;

请注意,您也可以使用type的字段标记的联合根据你的喜好


接下来我们定义函数接口

接口加载错误{
IsLoading (): 这是加载;

IsError (): 这是错误的;

这就是成功。;
}

所有的this is are type predicates wich will narrow the type of the object to the targeted type. When you'll call isLoading, if the function return true, then the object is now considered as a Loading对象。


现在,我们定义了最终的加载错误类型。

类型加载错误类型= 加载错误 fn& 州;

所以,一个LoadingErrorType contains all our functions, and is also a State, which can be Loading, Error, OR Success


检查功能

函数 f(X: 加载错误类型) {

如果 (x.isLoading () {
//只能访问 x.loading (true)
X.装载
} 如果 (x.isError () {
//只能访问 x.error (错误)
X.错误
} 其他 {
//只能访问 x.data (D)
X.数据
}
}

现在x可以完美推断,你可以访问想要的房产 (和功能),仅此而已!


类实现的例子

类加载错误实现加载错误 fn{
静态构建(): 加载错误类型{
将新的加载错误 () 作为加载错误类型返回;
}

加载: 布尔;
错误?: 错误;
数据: D;

私有构造函数 () {
这个装 = true;
}

IsLoading (): 这是装 {
返回此。加载;
}

IsError (): 这是错误 {
回来!错误!!
}

这就是成功。{
回来!!这个数据;
}
}

你的课要实现LoadingErrorFn,并实现所有功能。 接口不需要字段声明,所以选择你想要的结构。

build static function is a workaround for an issue: class cannot implement types, so LoadingError cannot implement LoadingErrorType. But this is a critic requirement for our f function (for the else推理部分)。所以我要演一场戏


测试时间

Load加载错误: 加载错误类型= LoadingError.build ();

(加载错误);//OK

我希望它有所帮助,任何建议都值得赞赏!

相关问题

在类实例中跟踪状态 如何解决: “找不到规则 '@ typescript-eslint/一致类型断言' 的定义” 如何将月份值从一个日期对象复制到另一个日期对象?[复制]