如何告知TypeScript:Store中可选值已确定非undefined?
问题
我有一个包含可选项的标准Redux Store,同时有依赖该Store的组件树,多个组件中都选取了{account}。根据业务逻辑,我已在最顶层校验account是否存在,若不存在则不渲染依赖它的组件。如何让TypeScript知晓,尽管Store中该值被定义为可选,但我能100%确定它并非undefined?
示例代码:
// store interface Account { id: number; name: string; } export interface AppState { account?: Account; } const initialState: AppState = {}; const accountSlice = createSlice({ name: "account", initialState, reducers: { setAccount(state: AppState, action: PayloadAction<Account | undefined>) { state.account = action.payload; } } }); // component const GrandChild = () => { const { account } = useSelector((state: RootState) => state, shallowEqual); return <> {account.name} </>; }; const Child = () => { const { account } = useSelector((state: RootState) => state, shallowEqual); return account ? <GrandChild /> : <>account not set</>; }; export default function App() { const dispatch = useDispatch(); useEffect(() => { // dispatch(setAccount({ id: 0, name: "john" })); }); return ( <div className="App"> <h1>Hello CodeSandbox</h1> <h2>Start editing to see some magic happen!</h2> <Child /> </div> ); }
我知道可以这样做:const { account } = useSelector((state: RootState) => state, shallowEqual) as Account;
但这种方式很不优雅,有没有更好的解决方案?
解决方案
1. 自定义类型断言函数
编写专门的断言函数,在运行时校验account存在性,同时让TypeScript确认其类型:
function assertAccountExists(account: Account | undefined): asserts account is Account { if (!account) { throw new Error("Account must exist in this context"); } } // 组件内使用 const GrandChild = () => { const { account } = useSelector((state: RootState) => state, shallowEqual); assertAccountExists(account); // 此时TypeScript会认定account为非可选的Account类型 return <> {account.name} </>; };
该方案兼具类型安全与运行时校验,能及时暴露业务逻辑漏洞,避免潜在崩溃。
2. 封装带类型保证的Selector
将断言逻辑集中到复用性强的Selector中,所有依赖account的组件直接调用:
const selectAccount = (state: RootState): Account => { const account = state.account; if (!account) { throw new Error("Account is required but not present"); } return account; }; // 组件内使用 const GrandChild = () => { const account = useSelector(selectAccount, shallowEqual); return <> {account.name} </>; };
这种方式减少重复代码,维护性更高,同时统一了account的获取逻辑。
3. 通过React Context传递已校验的Account
利用顶层组件的校验结果,将account通过Context传递给子组件,子组件直接获取非可选值:
// 创建Account Context const AccountContext = createContext<Account | undefined>(undefined); // 顶层校验组件 const Child = () => { const { account } = useSelector((state: RootState) => state, shallowEqual); if (!account) { return <>account not set</>; } return ( <AccountContext.Provider value={account}> <GrandChild /> </AccountContext.Provider> ); }; // 子组件使用Context const GrandChild = () => { const account = useContext(AccountContext); // 可选:添加运行时断言确保Context值存在 if (!account) throw new Error("Account context must be provided"); return <> {account.name} </>; };
该方案隔离了子组件与Redux的直接耦合,类型安全同时符合组件数据传递的最佳实践。
4. 非空断言操作符(谨慎使用)
若完全确定account不会为undefined,可使用TypeScript的非空断言!:
const GrandChild = () => { const { account } = useSelector((state: RootState) => state, shallowEqual); return <> {account!.name} </>; };
注意:此方法无运行时校验,一旦业务逻辑出错会直接导致运行时崩溃,仅适合场景完全可控的情况。
内容的提问来源于stack exchange,提问作者Alex Ironside
相关产品推荐
相关产品推荐

