在 Typescript 中使用 useContext 传递状态 [英] Passing state with useContext in Typescript
问题描述
我正在尝试使用 useContext 钩子将 state 和 setState 传递给子组件,但是当我尝试在提供程序的 value 参数中传递 [state, setState] 时出现 ts 错误.我的代码如下:
<预><代码>导出接口 IProviderProps {孩子?:有;}常量初始状态 = {状态:对象,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): 预期的类型来自属性value",它在类型IntrinsicAttributes &"上声明.ProviderProps<{ state: ObjectConstructor;setState: () =>空白;}>'
如何设置初始状态以允许我传递 state 和 useState 变量?
TypeScript 从给 createContext
的 initialState
推断 AppContext
类型.
AppContext.Provider
需要一个与上述类型匹配的 value
道具.所以createContext
实例化的类型决定了上下文的形状,消费组件可以使用.
出了什么问题?
initialState
得到以下推断类型:
{ state: ObjectConstructor;setState: () =>空白;}
将 Object
传递给 state
意味着,您期望的是 ObjectConstructor
- 并不是您真正想要的.使用 setState: () =>{}
,组件无法使用 state
参数调用此函数.另请注意,useState
初始值当前包含在附加数组 [{...}]
中.
总而言之,[state, setState]
参数与 AppContext.Provider
值属性不兼容.
解决方案
让我们假设,您想要的状态形状如下所示:type AppContextState = { isMenuOpen: boolean;isSideOpen: 布尔值 }//省略围绕上下文值的附加数组
然后用适当的类型的初始状态是(游乐场):
//将 `initialState` 重命名为 `appCtxDefaultValue` 以使其更加简洁const appCtxDefaultValue = {状态: { isMenuOpen: false, isSideOpen: false },setState: (state: AppContextState) =>{}//noop 默认回调};export const AppContext = createContext(appCtxDefaultValue);export const AppProvider = (props: IProviderProps) =>{const [state, setState] = useState(appCtxDefaultValue.state);返回 (//memoize `value` 以优化性能,如果 AppProvider 经常重新渲染<AppContext.Provider value={{ state, setState }}>{props.children}</AppContext.Provider>);};
与自己的上下文值A型更明确的变体(游乐场):
import { Dispatch, SetStateAction,/* and others */} from "react";类型 AppContextValue = {状态:AppContextState;//输入,当你从 `useState` 悬停在 `setState` 上时得到setState: Dispatch>;};const appCtxDefaultValue: AppContextValue = {/* ... */};
讨论替代方案
删除上下文默认值完全(游乐场)
export const AppContext = React.createContext(未定义);export const AppProvider = (props: IProviderProps) =>{const [state, setState] = useState({ isMenuOpen: false, isSideOpen: false });//... 其他渲染逻辑};
为了防止客户端现在必须检查 undefined
,请提供自定义 Hook:
function useAppContext() {const ctxValue = useContext(AppContext)if (ctxValue === undefined) throw new Error(要设置的预期上下文值")return ctxValue//现在输入 AppContextValue//或提供域方法而不是整个上下文以获得更好的封装}const 客户端 = () =>{const ctxVal = useAppContext()//ctxVal 已定义,无需检查!}
切换到 useReducer
和/或自定义 useAppContext
Hook
考虑将 useState
替换为 useReducer
并将 dispatch
函数向下传递给组件.这将提供更好的封装,因为状态操作逻辑现在集中在一个纯 reducer 中,子组件不能通过 setState
直接操作它.
将 UI 逻辑与域逻辑分开的另一个非常好的替代方法是提供自定义的 useAppContext
Hook,而不是使用 useContext(AppContext)
- 参见前面的示例.现在 useAppContext
可以提供更窄的 API,而无需发布整个上下文.
I'm trying to use the useContext hook to pass state and setState to a child component but I'm getting a ts error when I try and pass [state, setState] in the value argument of the provider. My code is as follows:
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>;
};
I'm getting an error on the value variable about the initialState
I'm setting.
index.d.ts(290, 9): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps<{ state: ObjectConstructor; setState: () => void; }>'
What do I set the initial state as to allow me to pass the state and useState variables?
TypeScript infers the AppContext
type from initialState
given to createContext
.
AppContext.Provider
expects a value
prop, that matches above type. So the type instantiated by createContext
determines the context shape, consuming components can use.
What went wrong?
initialState
gets following inferred type:
{ state: ObjectConstructor; setState: () => void; }
Passing Object
to state
means, you expect an ObjectConstructor
- not really what you want. With setState: () => {}
, components are not able to invoke this function with a state
argument. Also note, useState
initial value is currently wrapped in an additional array [{...}]
.
In summary, [state, setState]
argument is incompatible to AppContext.Provider
value prop.
Solution
Let's assume, your desired state shape looks like:type AppContextState = { isMenuOpen: boolean; isSideOpen: boolean }
// omitting additional array wrapped around context value
Then an initial state with proper types is (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>
);
};
A more explicit variant with own context value type (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 = {/* ... */};
Discussing alternatives
Drop context default value completely (playground)
export const AppContext = React.createContext<AppContextValue | undefined>(undefined);
export const AppProvider = (props: IProviderProps) => {
const [state, setState] = useState({ isMenuOpen: false, isSideOpen: false });
// ... other render logic
};
To prevent, that a client now has to check for undefined
, provide a custom 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!
}
Switch to useReducer
and/or custom useAppContext
Hook
Consider to replace useState
by useReducer
and pass the dispatch
function down to components. This will provide better encapsulation, as the state manipulation logic is now centralized in a pure reducer and child components cannot manipulate it directly anymore via setState
.
Another very good alternative to separate UI Logic from domain logic is to provide a custom useAppContext
Hook instead of using useContext(AppContext)
- see previous example. Now useAppContext
can provide a more narrow API without publishing your whole context.
这篇关于在 Typescript 中使用 useContext 传递状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!