当外部数据进入您的程序时,除非您验证了它,否则您无法真正确定它的类型。那个库的输出,那个API响应,尤其是那个用户输入……您确定它就是您认为的那样吗?
在您检查之前,给数据分配的最准确的类型应该是意味着“我实际上不知道”。1
做出假设
假设您正在从用户那里获取输入,并期望它是一个字符串。一个天真的方法是假设数据总是您期望的那样:
const getUserInput = (): string => {/*...*/}
const unsafe = () => {
const data = getUserInput()
data.toUpperCase()
}
没有警告,没有问题,对吧?并不完全……因为我们告诉类型检查器用户输入总是一个string
。这就是为什么它愿意让我们在data
上调用字符串方法。
但是,如果data
有时是undefined
(或任何不是string
的东西)怎么办?在这种情况下,这段代码在运行时将遇到一个未捕获的TypeError
,说Cannot read properties of undefined (reading 'toUpperCase')
,这可能会使您的程序处于破损状态。
这就是“Unknown”可以帮忙的地方。2
Unknown 来救援
一些语言明确包含了一个叫做“Unknown”的类型(例如TypeScript就有一个),而其他语言则有一个您可以类似对待的类型(例如Python的object
类型实际上意味着“Unknown”)。
无论您的语言给您什么,一般的方法都是相同的:
- 将“Unknown”类型分配给未经验证的数据
- 在使用它们之前明确验证数据的相关特征
- 当您的工具警告您正在做出不安全的假设时感到高兴
不做假设
让我们要求类型检查器帮助我们更加小心:
// 🤞 应该是一个字符串,但谁知道呢...
const getUserInput = (): unknown => {/*...*/}
const unsafe = () => {
const data = getUserInput()
data.toUpperCase()
// 🚨 'data' 是 'unknown' 类型
}
完美!我们想要那些类型警告。我们在我们尚未确认是string
的值上调用了一个string
方法(toUpperCase
)。这是有风险的。
为了解决警告,我们需要验证我们对data
类型的假设:
const getUserInput = (): unknown => {/*...*/}
const safe = () => {
const data = getUserInput()
if (typeof data === 'string') {
data.toUpperCase() // 确认安全
} else {
// 处理无效输入
}
}
随着您验证的每一个假设,类型检查器从其狭窄的起点(unknown
)“扩展”了对您的数据的理解,到具有更多特征的类型(例如string
)。
现在您可以确定您拥有的数据类型了。
脚注
这篇文章是对我在Reddit上收到的问题的回应,这些问题建议我们总是知道给定值的数据类型。对于任何来自程序外部的数据,我认为这种假设是有风险的!↩
这是很多人会使用“any”类型的地方,但要小心!“any”是“unknown”的反面,通过告诉类型检查器关于一个值的所有假设都是安全的,有效地禁用了类型检查。您可能不想要那个。↩