如何在 TypeScript 中将类型参数设置为“nothing”?

2023-12-21

在我们的应用程序中,我们有一个数据“包装器”模式,其中我们使用类的单个实例来表示一些可能已加载或尚未加载的数据。像这样的事情:

abstract class BaseWrapper<TData> {
    protected data?: TData;
    loadedPromise: Promise<this>;
}

然后,您可以针对特定的数据实现此功能,如下所示:

interface PersonData {
    name: string;
}

class PersonWrapper extends BaseWrapper<PersonData> {
    get name() { return this.data && this.data.name }
}

给定一个人:

const person: PersonWrapper = ...;

的类型person.name is string | undefined since this.data可能是未定义的。这很好,因为它提醒我们该对象可能未加载,并且我们需要在访问它的成员时处理这种情况。

然而,在某些情况下,我们know对象被加载,最常见的是当loadedPromise就满足了,像这样:

(await person.loadedPromise).name

这个表达式的类型仍然是string | undefined即使我们知道该对象已加载。这很烦人,我希望帮助解决这个问题。

我最好的想法是参数化BaseWrapper带有“空”类型参数。像这样的事情:

abstract class BaseWrapper<TData, TEmpty = undefined> {
    protected _data?: TData;
    get data(): TData | TEmpty {
        return this._data as (TData | TEmpty);
    }
}

然后,我们可以这样做:

class PersonWrapper<TEmpty = undefined> extends BaseWrapper<PersonData, TEmpty> {
    get name() { return this.data && this.data.name }

    loadedPromise: Promise<PersonWrapper<nothing>>;
    requireLoaded(): PersonWrapper<nothing> {
        if (this.data) return this as PersonWrapper<nothing>
        else throw new Error("Not loaded!")
    }
}

这里的发行人是“无”类型。我希望能够将类型传递到参数中,以便TData | TEmpty等于TData。这个类型应该是什么?

假设这是可能的,类型(await person.loadedPromise).name只会是string,那就太棒了。

有任何想法吗?


一种可能的解决方案是使用!后缀,它向编译器保证可选值可用:

const name: string = (await person.loadedPromise).name!

这在程序员“知道”该值已加载的非常明显的情况下有效。


(嘿,我正在重复你的“TData | TEmpty等于TData“想法。您想要一种安全且透明地封装可能的空值的类型,而不需要您处理未定义的情况,即使您知道该值已定义......)

另一个想法是:而不是具有name be string | undefined为什么不让它成为...string[]!哈哈哈,忍耐一下吧!

type Maybe<T> = T[] // I know, I know... this is weird

function of <T>(value: T): Maybe<T> {
  return value === undefined ? [] : [value]
}

abstract class BaseWrapper<TData> {
    protected data?: TData;
    loadedPromise: Promise<this>;
}

interface PersonData {
    name: string;
}

class PersonWrapper extends BaseWrapper<PersonData> {
    get name() { return of(this.data && this.data.name) }
}

const person: PersonWrapper = ...;

// these typings will be inferred I'm just putting them here to 
// emphasize the fact that these will both have the same type
const unloadedName: Maybe<string> = person.name
const loadedName: Maybe<string> = (await person.loadedPromise).name

现在,如果您想使用这些名称中的任何一个......

// reduce will either return just the name, or the default value
renderUI(unloadedName.reduce(name => name, 'defaultValue')
renderUI(loadedName.reduce(name => name, 'defaultValue')

The Maybetype 表示一个可能未定义的值,并且它有一个安全且一致的接口来访问它,无论其状态如何。 reduce 的默认值不是可选参数,因此不可能忘记它:您始终必须处理未定义的情况,这是非常透明的!如果你想做更多的处理,你可以这样做map.

unloadedName
  .map(name => name.toUpperCase)
  .map(name => leftPad(name, 3))
  .map(name => split('')) // <-- this actually changes the type
  .reduce(names => names.join('_'), 'W_A_T') // <-- now it's an array!

这意味着您可以编写类似的函数,

function doStuffWithName(name: string) { 
  // TODO implement this 

  return name
}

并这样称呼它们:

const name = person.name
  .map(doStuffWithName)
  .reduce(name => name, 'default')

renderUI(name)

并且您的辅助函数不需要知道它们正在处理可选值。由于映射的工作方式,如果该值丢失name[]并且map将应用给定的函数0次......即。安全!

如果您对像这样重载数组类型感到不舒服,您可以随时实现Maybe作为一个班级:它只需要map and reduce。你甚至可以实施map and reduce在你的包装类上(因为 Maybe 只是可能未定义数据的包装)。

这不是具有“无类型”的“特定于打字稿”的解决方案,但也许它会帮助您找到解决方案!

(注意:这很像暴露潜在的承诺,我认为这也是您可能需要考虑的事情)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 TypeScript 中将类型参数设置为“nothing”? 的相关文章

随机推荐