使用Typescript中的--strictNullChecks从DB或API接收异步数据的变量的类型?
我的React+Typescript项目遇到了一些问题 每当我必须键入一个以使用Typescript中的--strictNullChecks从DB或API接收异步数据的变量的类型?,typescript,asynchronous,typescript-typings,type-assertion,Typescript,Asynchronous,Typescript Typings,Type Assertion,我的React+Typescript项目遇到了一些问题 每当我必须键入一个以null开头并将接收某种类型的async数据的变量时,就会发生这种情况 例如:我有一个管理员用来创建和编辑帖子的AdminBlogPostPage 我有一个变量,用于保存blogPost数据 但是当该页面第一次呈现时,仍然没有任何可用数据,因为帖子将被加载async 目前,我是这样输入的(我使用的是--strictNullChecks): blogPost状态从null开始,一旦加载,它就会变成blogPost 它很好用
null
开头并将接收某种类型的async
数据的变量时,就会发生这种情况
例如:我有一个管理员用来创建和编辑帖子的AdminBlogPostPage
我有一个变量,用于保存blogPost
数据
但是当该页面第一次呈现时,仍然没有任何可用数据,因为帖子将被加载async
目前,我是这样输入的(我使用的是--strictNullChecks
):
blogPost
状态从null
开始,一旦加载,它就会变成blogPost
它很好用。但是从这一点开始,管理员对blogPost
对象所做的一切都变得有点痛苦,因为Typescript总是“担心”blogPost可能是null
在这些情况下,我通常使用Redux,因此在我的reducer
中,我经常要做类型断言,让Typescript清楚地知道,当该操作发生时,blogPost
将是blogPost
而不是null
例如:
/* ############################# */
/* #### BLOGPOST PROPERTIES #### */
/* ############################# */
UPDATE_CATEGORY(state, action:UPDATE_CATEGORY) {
const blogPost = state.blogPost as TYPES.BLOGPOST; // TYPE ASSERTION HERE
const { value } = action.payload;
blogPost.category = value;
},
UPDATE_BOOLEAN_PROPERTY(state, action: UPDATE_BOOLEAN_PROPERTY) {
const blogPost = state.blogPost as TYPES.BLOGPOST; // TYPE ASSERTION HERE
const { name, value } = action.payload;
blogPost[name] = value;
},
UPDATE_STRING_PROPERTY(state, action: UPDATE_STRING_PROPERTY) {
const blogPost = state.blogPost as TYPES.BLOGPOST; // TYPE ASSERTION HERE
const { name, value } = action.payload;
blogPost[name] = value;
},
UPDATE_PRODUCT_CATEGORY(state, action: UPDATE_PRODUCT_CATEGORY) {
const blogPost = state.blogPost as TYPES.BLOGPOST; // TYPE ASSERTION HERE
const { value } = action.payload;
blogPost.productCategory = value;
},
人们通常如何处理async
数据的类型?在数据到达之前从null
开始是否值得?还是最好从一个有效的blogPost:blogPost
存根开始作为初始状态,这样Typescript就会知道blogPost
始终是一个blogPost
,我就不必再处理这种重复的类型断言了
注意:即使我决定从有效的
blogPost:blogPost
开始,我仍然需要从数据库加载数据。将不使用初始状态。它将允许我使用blogPost:blogPost
而不是null | blogPost
这通常是一个令人讨厌的问题。这可能不足以以你喜欢的方式解决你的问题,但我将把我的答案留在这里,希望它能帮助任何人
根据我的经验,我一直采用以下两种方法之一处理此问题(其中一种依赖于框架):
- 使用更简洁的编译时类型断言,例如
代码>(而不是显式强制转换)在绝对确定值为非空/非未定义的情况下。例如,尽管运行时
可能为空,但这将编译:state.blogPost
这断言state.blogPost![name] = value state.blogPost!.property = value
值为非null,应该可以正常编译。请注意,此运算符在编译期间被擦除,并且仅用于编译时类型检查。当确认值的类型可能为空时,应使用该运算符,但在运行时不应使用该运算符。如果该值在运行时为空,那么它当然会通过一个错误返回。此外,在某些特定的场景中也可能有用state.blogPost
- 对于某些React框架,例如(SSR+CSR),可以从API异步获取呈现页面的初始属性,并且在使用props解析
承诺之前,页面不会完成其呈现。例如,这涵盖了这个场景 这是使用Next.js的特定示例,但其基本思想是在注入组件属性和呈现之前获取博客文章,并且在运行时和编译时应为非null
与您的问题无关,请记住您使用方括号表示法
x[y]
的用例以及任何安全隐患。请参阅这篇GitHub文章,解释附带情况下的潜在安全问题:我将创建一个单独的类型来表示尚未从后端/数据库加载数据的状态,而不是使用null
type UnloadedBlogPost {
stateKind: "unloaded";
}
type LoadedBlogPost {
stateKind: "loaded";
blogPost: BLOG_POST;
}
type BlogPostState = UnloadedBlogPost | LoadedBlogPost;
这将允许您在博客文章加载之前显式表示UI的状态,如果您想添加微调器或其他加载指示器,这可能会很有用。您的还原程序仍然需要检查日志是否已加载,但如果日志未加载,则提前退出非常简单:
UPDATE_CATEGORY(state, action:UPDATE_CATEGORY) {
// assuming state.blogPostState is of type BlogPostState...
if (state.blogPostState.stateKind === "unloaded") {
return state;
}
// state.blogPostState is narrowed to type LoadedBlogPost, state.blogPostState.blogPost can be accessed
},
作为补充说明,在使用Redux时,您通常希望简化程序是纯函数,返回新的状态对象,而不是改变现有状态。请参阅。问题在于类型脚本是正确的。在应用程序中,有时状态将为
null
如果在API调用完成之前尝试访问状态,会发生什么?Typescript保护您不受此影响
您需要对此进行大量检查,但这可能是添加加载器的好机会
type BLOGPOST = {name: string};
type ADMIN_BLOGPOST_STATE = {
blogPost: null | BLOGPOST
}
const state:ADMIN_BLOGPOST_STATE = {
blogPost: null
}
// .. api calls eventually set state.blogPost to some valid value
if (state.blogPost === null) {
return <Loader/>
} else {
return <BlogPost id={state.blogPost.id} title={state.blogPost.title}/>
}
这样做的好处是,一开始就将断言放在一个地方
不利的一面是,您需要特别小心如何访问您的状态。你只能靠自己。我没有选择正确的答案,因为它们都帮助我了解了这一点 不管怎样,我最后做的是:
type ADMIN_BLOGPOST_STATE = {
blogPost: BLOGPOST
}
- 我已经用一个
布尔值来控制UI状态。因此,在数据到达之前,用户看到的唯一界面是加载
null
值,并为blogPost
添加了一个空白的valid值作为初始状态。我所说的blank是指它没有任何内容,但是它拥有所有需要的属性,并且它完全实现了BLOGPOST
类型
然后,我的类型变成如下:
type ADMIN_BLOGPOST_STATE = {
blogPost: BLOGPOST
}
这不仅帮助我摆脱了reducer中的类型断言,而且还允许我
type ADMIN_BLOGPOST_STATE = {
blogPost: BLOGPOST
}
export function useAdminBlogPostProperty<K extends keyof TYPES.BLOGPOST>(propName: K): TYPES.BLOGPOST[K] {
return useSelector((state: ROOT_STATE) => state.ADMIN_BLOGPOST.blogPost[propName]);
}
type ADMIN_BLOGPOST_STATE = {
blogPost: BLOGPOST
}
// THIS FUNCTION ALLOWS TO PASS NULL AND STILL OBEY
// THE STATE CONTRACT. BECAUSE WE KNOW FOR SURE THAT
// BECAUSE INITIAL STATE WILL NEVER BE ACCESSED
export const initialNullState = <T extends unknown>() : T => {
return null as T;
};
const getInitialState = (): PAGES.ADMIN_BLOGPOST => ({
status: { loading: true, error: null, notFound: false },
blogPost: initialNullState(),
});