一种可能的解决方案是使用!
后缀,它向编译器保证可选值可用:
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 Maybe
type 表示一个可能未定义的值,并且它有一个安全且一致的接口来访问它,无论其状态如何。 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 只是可能未定义数据的包装)。
这不是具有“无类型”的“特定于打字稿”的解决方案,但也许它会帮助您找到解决方案!
(注意:这很像暴露潜在的承诺,我认为这也是您可能需要考虑的事情)