如何让React Context Consumer返回非undefined值,无值则报错?
类型安全的必填React Context消费方案(兼容类组件)
背景
在启用strict和strictNullChecks的TypeScript/React应用中,我使用了若干React Context。由于无法定义合理的默认值,只能允许Context值为undefined:
const MyMandatoryContext = createContext<string | undefined>(undefined);
这些Context用于存储全局核心状态(如已登录用户名、选中项、主题等),且所有需要消费值的组件都必须被对应的Provider包裹。
为了避免重复编写空值检查逻辑,我希望能实现统一的安全校验机制。
函数组件的解决方案:自定义Hook
通过自定义Hook可以轻松实现统一检查,确保获取到的Context值非空:
const useMyMandatoryContext = () => { const context = useContext(MyMandatoryContext); if (context === undefined) throw new Error("必须通过MyMandatoryContext.Provider提供值"); return context; };
使用该Hook时,TypeScript会自动推断返回值为非空的string类型,无需每次手动校验:
const MyFuncComponent: React.FC = () => { const myMandatoryValue = useMyMandatoryContext(); // 确定为string类型 return (<span>{myMandatoryValue.toUpperCase()}</span>) };
类组件的痛点
Hook无法在类组件中使用(比如遗留业务代码、ErrorBoundary这类特殊组件仍依赖类组件实现)。目前只能在类组件中手动消费Context并添加空值检查:
class MyClassComponent extends Component { render() { return ( <MyMandatoryContext.Consumer> {(myMandatoryValue) => { if (!myMandatoryValue) throw new Error("必须通过MyMandatoryContext.Provider提供值"); return <span>{myMandatoryValue.toUpperCase()}</span>; }} </MyMandatoryContext.Consumer> ); } }
但这种写法需要在每个类组件的Consumer中重复编写校验逻辑,冗余度极高。
需求:类型安全的类组件Consumer封装
有没有一种类型安全的方式,封装出一个能直接返回非空值的Consumer?我尝试修改React.Consumer类型,但没找到正确的实现方式:
const MyMandatoryContextConsumer: React.Consumer<string> = ({ children }) => { return children(val => { if (val === undefined) throw new Error("必须通过MyMandatoryContext.Provider提供值"); return ???? // 不知道此处该如何处理 }); };
完整复现代码
import "./styles.css"; import { createContext, useContext, useState, Component } from "react"; const MyMandatoryContext = createContext<string | undefined>(undefined); const useMyMandatoryContext = () => { const context = useContext(MyMandatoryContext); if (context === undefined) throw new Error("必须通过MyMandatoryContext.Provider提供值"); return context; }; const MyMandatoryContextConsumer: React.Consumer<string> = ({ children }) => { return children(val => { if (val === undefined) throw new Error("必须通过MyMandatoryContext.Provider提供值"); return ???? // 不知道此处该如何处理 }); }; const MyFuncComponent: React.FC = () => { const myMandatoryValue = useMyMandatoryContext(); // 确定为string类型 return (<span>{myMandatoryValue.toUpperCase()}</span>) }; class MyClassComponent extends Component { render() { return ( <MyMandatoryContext.Consumer> {(myMandatoryValue) => <span>{myMandatoryValue.toUpperCase()}</span>} </MyMandatoryContext.Consumer> ); } } class MyClassComponent2 extends Component { render() { return ( <MyMandatoryContext.Consumer> {(myMandatoryValue) => { if (!myMandatoryValue) throw new Error("必须通过MyMandatoryContext.Provider提供值"); return <span>{myMandatoryValue.toUpperCase()}</span>; }} </MyMandatoryContext.Consumer> ); } } export default function App() { const [message, setMessage] = useState<string | undefined>(); return ( <div className="App"> <p> <input value={message} onChange={(evt) => setMessage(evt.target.value)} /> </p> {message && ( <MyMandatoryContext.Provider value={message}> <MyFuncComponent /> <MyClassComponent /> <MyClassComponent2 /> </MyMandatoryContext.Provider> )} </div> ); }
内容的提问来源于stack exchange,提问作者Steve B
相关产品推荐
相关产品推荐

