reactjs - 在 Typescript 中使用 useContext 传递状态
问题描述
我正在尝试使用 useContext 挂钩将 state 和 setState 传递给子组件,但是当我尝试在提供程序的 value 参数中传递 [state, setState] 时出现 ts 错误。我的代码如下:
export interface IProviderProps {
children?: any;
}
const initialState = {
state: Object,
setState: () => {},
};
export const AppContext = createContext(initialState);
export const AppProvider = (props: IProviderProps) => {
const [state, setState] = useState([{ isMenuOpen: false, isSideOpen: false }]);
return <AppContext.Provider value={[state, setState]}>{props.children}</AppContext.Provider>;
};
initialState
关于我正在设置的值变量,我收到错误消息。
index.d.ts(290, 9): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps<{ state: ObjectConstructor; setState: () => void; }>'
我如何设置初始状态以允许我传递 state 和 useState 变量?
解决方案
TypeScript 将AppContext
类型从initialState
given 推断为createContext
.
AppContext.Provider
需要一个value
与上述类型匹配的道具。所以实例化的类型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
然后具有适当类型的初始状态是(游乐场):
// 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>
);
};
具有自己的上下文值类型(游乐场)的更明确的变体:
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
byuseReducer
并将dispatch
函数向下传递给组件。这将提供更好的封装,因为状态操作逻辑现在集中在纯 reducer 中,子组件不能再通过setState
.
将 UI 逻辑与域逻辑分开的另一个很好的替代方法是提供自定义useAppContext
Hook 而不是使用useContext(AppContext)
- 请参阅前面的示例。现在useAppContext
可以在不发布整个上下文的情况下提供更窄的 API。
推荐阅读
- c++ - 防止 SC_METHOD 在没有事件/触发器的情况下执行
- google-data-studio - 谷歌数据工作室只嵌入一页
- kubernetes - Kubernetes Ingress 在 minikube ssh 中工作时出错
- reactjs - 使用 useState 钩子的 React 组件的状态在子组件更新之间意外重置
- java - 如何使用 Spring Boot 在一个消费者类中顺序读取 2 个 Kafka 主题?
- docker - 获取:当 njs 与 Nginx 一起使用时,未知指令“js_import”
- python - 如何获取包含字典的值和键的列表?
- javascript - 如何按属性合并两个对象?
- powerbi - power bi 如何在日期不唯一的折线图和堆积柱形图中排序日期
- node.js - sam local start-api 无法解析属性