TypeScriptAppContext
从initialState
给定到推断类型createContext
。
AppContext.Provider
需要一个value
与上述类型匹配的props。所以实例化的类型createContext
决定了上下文形状,消费组件可以使用。
什么地方出了错?
initialState
获得以下推断类型:
{ state: ObjectConstructor; setState: () => void; }
传递Object
到state
意味着,您期望ObjectConstructor
- 不是您真正想要的。使用setState: () => {}
,组件无法使用state
参数调用此函数。另请注意,useState
初始值当前包含在附加数组中[{...}]
。
总之,[state, setState]
argument 与AppContext.Provider
value prop不兼容。
解决方案
让我们假设,您想要的状态形状如下所示:
type AppContextState = { isMenuOpen: boolean; isSideOpen: boolean }
// omitting additional array wrapped around context value
然后具有适当类型的初始状态是(playground):
// renamed `initialState` to `appCtxDefaultValue` to be a bit more concise
const appCtxDefaultValue = {
state: { isMenuOpen: false, isSideOpen: false },
setState: (state: AppContextState) => {} // noop default callback
};
export const AppContext = createContext(appCtxDefaultValue);
export const AppProvider = (props: IProviderProps) => {
const [state, setState] = useState(appCtxDefaultValue.state);
return (
// memoize `value` to optimize performance, if AppProvider is re-rendered often
<AppContext.Provider value={{ state, setState }}>
{props.children}
</AppContext.Provider>
);
};
具有自己的上下文值类型(playground)的更明确的变体:
import { Dispatch, SetStateAction, /* and others */ } from "react";
type AppContextValue = {
state: AppContextState;
// type, you get when hovering over `setState` from `useState`
setState: Dispatch<SetStateAction<AppContextValue>>;
};
const appCtxDefaultValue: AppContextValue = {/* ... */};
讨论替代方案
完全删除上下文默认值(操场)
export const AppContext = React.createContext<AppContextValue | undefined>(undefined);
export const AppProvider = (props: IProviderProps) => {
const [state, setState] = useState({ isMenuOpen: false, isSideOpen: false });
// ... other render logic
};
为了防止客户端现在必须检查undefined
,请提供自定义 Hook:
function useAppContext() {
const ctxValue = useContext(AppContext)
if (ctxValue === undefined) throw new Error("Expected context value to be set")
return ctxValue // now type AppContextValue
// or provide domain methods instead of whole context for better encapsulation
}
const Client = () => {
const ctxVal = useAppContext() // ctxVal is defined, no check necessary!
}
切换到useReducer
和/或自定义useAppContext
Hook
考虑到更换useState
的useReducer
并通过dispatch
函数到组件。这将提供更好的封装,因为状态操作逻辑现在集中在一个纯 reducer 中,子组件不能再通过setState
.
将 UI 逻辑与域逻辑分开的另一个非常好的替代方法是提供自定义useAppContext
Hook 而不是使用useContext(AppContext)
- 参见前面的示例。现在useAppContext
可以在不发布整个上下文的情况下提供更窄的 API。