React搭配TypeScript使用useContext时有无Partial的替代方案
问题根源
你遇到的报错本质是Partial<contextTypes>会把接口中所有属性标记为可选,TypeScript默认所有从Context取出的属性都可能为undefined,调用函数类型的属性时就会触发「无法调用可能为undefined的对象」报错。你用Partial只是为了给createContext传初始空对象,但实际通过Provider注入的value是完整符合contextTypes定义的,这个矛盾导致了不必要的类型校验报错。
解决方案1(推荐:带运行时校验,类型安全)
我们可以抛弃Partial,封装专用的自定义Context钩子,同时实现类型安全和错误提示:
- 把Context的泛型改为
contextTypes | undefined,初始默认值设为undefined - 封装自定义的useContext钩子,在内部做非空判断,不符合条件就抛出明确错误
- 后续所有组件都用这个自定义钩子取Context,返回值自动是完整的
contextTypes类型,不需要任何非空断言
修改后的完整代码如下:
import React, { useState, createContext, useContext } from 'react'; import endpoints from '../components/endpoints/endpoints'; interface contextTypes { endpointQuery: string, setEndpointQuery: React.Dispatch<React.SetStateAction<string>>, searchInput: string, setSearchInput: React.Dispatch<React.SetStateAction<string>>, filmSearch: string | undefined, setFilmSearch: React.Dispatch<React.SetStateAction<string>>, // 注意你原代码这里漏了逗号,记得补上 pageIndex: number, setPageIndex: React.Dispatch<React.SetStateAction<number>>, resetState: () => void; } // 初始默认值设为undefined,泛型标注为联合类型 export const DisplayContext = createContext<contextTypes | undefined>(undefined); // 封装专用的自定义钩子 export function useDisplayContext() { const context = useContext(DisplayContext); // 运行时校验:如果取到undefined说明组件没有被Provider包裹 if (!context) { throw new Error("useDisplayContext 必须在 DisplayContextProvider 内部使用"); } return context; } interface Props { children: React.ReactNode; } const DisplayContextProvider = (props: Props) => { const { nowShowing } = endpoints; const [ endpointQuery, setEndpointQuery ] = useState(nowShowing); const [ searchInput, setSearchInput ] = useState(''); const [ filmSearch, setFilmSearch ] = useState(''); const [ pageIndex, setPageIndex ] = useState(1); const resetState = () => { setSearchInput(''); setFilmSearch(''); setPageIndex(1); }; const values = { endpointQuery, setEndpointQuery, pageIndex, setPageIndex, filmSearch, setFilmSearch, searchInput, setSearchInput, resetState }; return ( <DisplayContext.Provider value={values}> {props.children} </DisplayContext.Provider> ); }; export default DisplayContextProvider;
后续在组件中使用时直接调用useDisplayContext()即可,所有属性都是必选类型,不会触发undefined报错:
const { resetState, setSearchInput } = useDisplayContext(); // 直接调用不需要加! resetState();
解决方案2(极简版:适合小型项目)
如果你能100%保证所有使用该Context的组件都会被Provider包裹,永远不会用到默认值,可以直接用类型断言把初始空对象标记为符合contextTypes类型,仅修改createContext一行即可:
export const DisplayContext = createContext<contextTypes>({} as contextTypes);
这个方案不需要改其他代码,但是没有运行时校验,如果确实出现组件没被Provider包裹的情况,会直接报属性未定义的运行时错误,排查成本更高。
内容的提问来源于stack exchange,提问作者Adam Mann
相关产品推荐
相关产品推荐

