【问题标题】:Passing state with useContext in Typescript在 Typescript 中使用 useContext 传递状态
【发布时间】:2020-01-31 06:07:50
【问题描述】:

我正在尝试使用 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 &amp; ProviderProps&lt;{ state: ObjectConstructor; setState: () =&gt; void; }&gt;'

如何设置初始状态以允许我传递 state 和 useState 变量?

【问题讨论】:

  • 试试value={{state, setState}}
  • 不高兴。 AppContext.tsx(8, 3): The expected type comes from property 'state' which is declared here on type '{ state: ObjectConstructor; setState: () =&gt; void; }

标签: reactjs typescript react-hooks


【解决方案1】:

TypeScript 从initialState 推断AppContext 类型给createContext

AppContext.Provider 需要一个匹配上述类型的 value 属性。所以createContext实例化的类型决定了context shape,消费组件可以使用。

出了什么问题?

initialState 得到以下推断类型:

{ state: ObjectConstructor; setState: () => void; }

Object 传递给state 意味着,您期望ObjectConstructor - 并不是您真正想要的。使用setState: () =&gt; {},组件无法使用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 = {/* ... */};

讨论替代方案

完全删除上下文默认值 (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
};
为了防止,客户端现在必须检查 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,而无需发布您的整个上下文。

【讨论】:

  • 非常感谢!这真的很有帮助。我使用了第一个示例,当我掌握 Dispatch 时,第二个可能会派上用场。
  • 嗨,我也遇到了这个错误。但在您的回复中,我不清楚如何使用 AppProvider。因为这里没有给出值。组件如何从 AppProvider 获取 state 和 setState 值?任何帮助将不胜感激。
  • @g00golplex AppProvider 可以像这样与MyComp 一起使用(第一个示例的继续):const MyComp = () =&gt; { const {state,setState} = useContext(AppContext); ... } const App = () =&gt; &lt;AppProvider&gt; &lt;MyComp /&gt; &lt;/AppProvider&gt;
  • 嗨,Ford04!非常感谢您的反馈!我真的需要时间来实现它(是的,我是 React 和 TypeScript 的 n00b)我终于让它工作了!
  • 你是神人,你的解释很中肯!
猜你喜欢
  • 2023-03-05
  • 2021-10-14
  • 2022-01-08
  • 1970-01-01
  • 2022-07-22
  • 1970-01-01
  • 1970-01-01
  • 2020-09-24
  • 2023-02-23
相关资源
最近更新 更多