TypeScript 通用运行时类型检查
TypeScript generic runtime type check
运行时数据是否可以指定类型进行运行时类型检查?希望使用 io-ts?
一条switch
语句创建了多个位置来添加新类型。查找像 types[runtime.type]
这样的对象属性会产生编译时类型检查错误。值可能未定义。
运行时数据:
[{label:"user", userid:1}, {label:"post", body:"lipsum"}]
类型:
type User = {
label: 'user'
userid: number
}
type Delete = {
label: 'post'
body: string
}
检查类型后,我还想在通用实现中使用数据:
function save<A>(data:A) {
mutate<A>(data)
validate<A>(data)
send<A>(data)
}
这叫做相关记录类型,在 TypeScript 社区有很多讨论。我的做法是将相关记录检查移动到运行time:
https://github.com/microsoft/TypeScript/issues/30581#issuecomment-686621101
https://repl.it/@urgent/runtime-fp-ot
第 1 步
创建一个包含所有允许属性的通用 Prop
界面。
interface Prop {
label: "user" | "post",
userid: string,
body: string
}
这确实会阻止类型特定的属性,并且您可能会发生冲突。 user.id
想成为 number
,但 post.id
想成为 string
。对我来说没什么大不了的。考虑一个特定于您的类型的不同 属性 名称,接受那里的名称,或者如果您有冒险精神,请尝试向 Props
添加维度并按类型建立索引。
第 2 步
创建标签到 运行 时间解码器的映射。在打字稿中我使用 class 所以我可以通过不同的文件扩展它:
class Decoder {
user: userDecode,
post: postDecode
}
步骤 3
创建一个接受 props 的函数,从 class 原型中查找解码器,并执行 运行time decode
(props:Props) => Decoder.prototype[props.label].decode(props as any)
在严格模式下 io-ts 需要在此处加宽 any
。您还需要检查 props.label
是否存在于 Decoder
中
第 4 步
创建与 运行 类似的函数映射。如果您在 运行time 解码之后调用它们,您就会知道 运行time 正在传递有效值。
缺点
- 比编写和管理 switch 语句要复杂得多。 TypeScript 类型自动缩小
switch
。
- 属性 碰撞。没有添加工作就没有该类型的特定属性。
- 需要手动检查类型是否存在。
switch
将忽略,并使用默认大小写
优点
- 您可以永久关闭运行时间处理。隔离,如果您需要添加对不同 运行 时间类型的支持,请不要打开该代码。
- 当创建对不同 运行 时间类型的支持时,例如
page
或 file
,像 babel 文件这样的东西可以自动导入新文件。
- 适用于版本控制和开发人员访问权限。可以打开新类型以供 public 提交。核心处理可以保持关闭。这样你就有了 DSL 的开始。
如果您的标签在所有类型中都是通用的,如果您对要处理的每种情况进行测试,则可以使用类型保护轻松处理。
对于标记为返回 x is y
的函数,如果它 returns true
,那么 编译器 知道在 true
部分 if
,变量是该类型,在 else 中,不是该类型。
const data = [{label:"user", userid:1}, {label:"post", body:"lipsum"}]
type User = {
label: 'user'
userid: number
}
function isUser(u: { label: string }): u is User {
return u.label === 'user';
}
type Delete = {
label: 'post'
body: string
}
function isDelete(d: { label: string }): d is Delete {
return d.label === 'post';
}
for (const datum of data) {
if (isUser(datum)) {
console.log(`User with userid of ${datum.userid}`);
} else if (isDelete(datum)) {
console.log(`Delete with body of ${datum.body}`);
}
}
运行时数据是否可以指定类型进行运行时类型检查?希望使用 io-ts?
一条switch
语句创建了多个位置来添加新类型。查找像 types[runtime.type]
这样的对象属性会产生编译时类型检查错误。值可能未定义。
运行时数据:
[{label:"user", userid:1}, {label:"post", body:"lipsum"}]
类型:
type User = {
label: 'user'
userid: number
}
type Delete = {
label: 'post'
body: string
}
检查类型后,我还想在通用实现中使用数据:
function save<A>(data:A) {
mutate<A>(data)
validate<A>(data)
send<A>(data)
}
这叫做相关记录类型,在 TypeScript 社区有很多讨论。我的做法是将相关记录检查移动到运行time:
https://github.com/microsoft/TypeScript/issues/30581#issuecomment-686621101
https://repl.it/@urgent/runtime-fp-ot
第 1 步
创建一个包含所有允许属性的通用 Prop
界面。
interface Prop {
label: "user" | "post",
userid: string,
body: string
}
这确实会阻止类型特定的属性,并且您可能会发生冲突。 user.id
想成为 number
,但 post.id
想成为 string
。对我来说没什么大不了的。考虑一个特定于您的类型的不同 属性 名称,接受那里的名称,或者如果您有冒险精神,请尝试向 Props
添加维度并按类型建立索引。
第 2 步
创建标签到 运行 时间解码器的映射。在打字稿中我使用 class 所以我可以通过不同的文件扩展它:
class Decoder {
user: userDecode,
post: postDecode
}
步骤 3
创建一个接受 props 的函数,从 class 原型中查找解码器,并执行 运行time decode
(props:Props) => Decoder.prototype[props.label].decode(props as any)
在严格模式下 io-ts 需要在此处加宽 any
。您还需要检查 props.label
是否存在于 Decoder
第 4 步
创建与 运行 类似的函数映射。如果您在 运行time 解码之后调用它们,您就会知道 运行time 正在传递有效值。
缺点
- 比编写和管理 switch 语句要复杂得多。 TypeScript 类型自动缩小
switch
。 - 属性 碰撞。没有添加工作就没有该类型的特定属性。
- 需要手动检查类型是否存在。
switch
将忽略,并使用默认大小写
优点
- 您可以永久关闭运行时间处理。隔离,如果您需要添加对不同 运行 时间类型的支持,请不要打开该代码。
- 当创建对不同 运行 时间类型的支持时,例如
page
或file
,像 babel 文件这样的东西可以自动导入新文件。 - 适用于版本控制和开发人员访问权限。可以打开新类型以供 public 提交。核心处理可以保持关闭。这样你就有了 DSL 的开始。
如果您的标签在所有类型中都是通用的,如果您对要处理的每种情况进行测试,则可以使用类型保护轻松处理。
对于标记为返回 x is y
的函数,如果它 returns true
,那么 编译器 知道在 true
部分 if
,变量是该类型,在 else 中,不是该类型。
const data = [{label:"user", userid:1}, {label:"post", body:"lipsum"}]
type User = {
label: 'user'
userid: number
}
function isUser(u: { label: string }): u is User {
return u.label === 'user';
}
type Delete = {
label: 'post'
body: string
}
function isDelete(d: { label: string }): d is Delete {
return d.label === 'post';
}
for (const datum of data) {
if (isUser(datum)) {
console.log(`User with userid of ${datum.userid}`);
} else if (isDelete(datum)) {
console.log(`Delete with body of ${datum.body}`);
}
}