TypeScript 从initialState 推断AppContext 类型给createContext。
AppContext.Provider 需要一个匹配上述类型的 value 属性。所以createContext实例化的类型决定了context shape,消费组件可以使用。
出了什么问题?
initialState 得到以下推断类型:
{ state: ObjectConstructor; setState: () => void; }
将Object 传递给state 意味着,您期望ObjectConstructor - 并不是您真正想要的。使用setState: () => {},组件无法使用state 参数调用此函数。另请注意,useState 初始值当前包含在一个附加数组 [{...}] 中。
总而言之,[state, setState] 参数与 AppContext.Provider 值属性不兼容。
解决方案
让我们假设,您想要的状态形状如下所示:
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,而无需发布您的整个上下文。