【问题标题】:Unable to pass objects state and dispatch to provider when using createContext and useReducer with typescript使用带有 typescript 的 createContext 和 useReducer 时无法将对象状态和分派传递给提供者
【发布时间】:2021-04-26 10:05:42
【问题描述】:

我正在尝试使用contextApi 和钩子来实现类似于 redux 的行为,但我在使用 typescript 时遇到了一些问题。

我想提供我的应用程序的状态和调度,以便能够从组件内访问和修改存储。问题是,当我使用 createContext 时,我手边仍然没有 statedispatch 对象,因为我只能在 React 组件内调用 useReducer。因此,我只能调用 createContext 声明参数的类型为 any ,以便在此时传递 null 并稍后传递另一个调度和状态。

    import React, { createContext, useReducer } from 'react';
    import reducerAuth, { initialState } from './reducerAuth';
    const ContextAuth = createContext<any>(null);
    const StateProvider = (
      { children }:{children:JSX.Element},
    ) => {
      const { Provider } = ContextAuth;
      const [state, dispatch] = useReducer(reducerAuth, initialState);
      return (<Provider value={{ state, dispatch }}>{children}</Provider>);
    };
    export { ContextAuth, StateProvider };

有没有一种方法可以让我通过 statedispatch 而不必声明类型 any 并且不会发生打字稿冲突?

我不想使用redux 来实现这一切,因为我想使用原生工具,但 redux 似乎更容易,尽管非常相似。

我绑定了对象{state,dispatch}的类型来创建上下文,方法如下:

    const [state, dispatch] = useReducer(reducerAuth, initialState);
      type UseReducer = {
        state: typeof state,
        dispatch: typeof dispatch,
      }
      const ContextAuth = createContext<UseReducer | null>(null);

但在这种情况下,我不再能够导出 Context Auth,因为它是在 StateProvider 函数中创建的。

【问题讨论】:

  • 您可能在某处有一个类型/接口来定义状态对象,对吧?只需在创建上下文时使用它:const ContextAuth = createContext&lt;MyStateType&gt;(undefined);
  • 我编辑了这个问题来解释我在尝试这个时遇到的问题。

标签: typescript react-hooks typescript-typings context-api


【解决方案1】:

问题的根源在于您试图在创建对象后推断其类型,而不是了解将创建的对象类型。在我看来,这是一个倒退的设计。

尽可能避免使用typeof。有些事情你可以用typeinterface 来说明,typeof 不可能自行推断。 string "myString" 可以是任何 string 值,还是字符串文字?这个null 属性只能是null,还是null 或其他类型?那会是什么其他类型?希望你明白这一点。

dispatch 具有已知类型。它是一个执行操作并返回void 的函数。你不需要使用typeof dispatch 来获取它的类型。

type Dispatch<A> = (value: A) => void;

泛型变量A 表示可接受的action 参数。您使用的内容取决于您希望应用程序的类型严格程度。您可以将其设置为特定操作类型的联合,或者设置为您的所有操作都将完成的通用界面。 Redux 导出通用类型 ActionAnyAction 但 React 没有,所以你必须自己定义。

interface MyAction {
  type: string;
  payload: any;
}
type MyDispatch = Dispatch<MyAction>

同样,您应该已经知道State 的预期类型/接口。如果没有,请定义它!

这是一个完整的代码示例。实现这些类型的地方在您的initialStatereducerAuth 上。如果输入正确,则StateProvider 不需要任何额外输入,因为useReducer 挂钩可以推断statedispatch 的类型。

import React, { createContext, useReducer } from 'react';

interface MyState {
    // some actual data here
}

// initialState must fulfill the `MyState` interface
const initialState: MyState = {};

// just an example, you can define this type however you want
interface MyAction {
    type: string;
    payload: any;
}

// reducer declaration depends on the types for `MyState` and `MyAction`
const reducerAuth = (state: MyState, action: MyAction): MyState => {
    // actual reducer here
    return state;
};

// these are the values in your context object
interface MyContext {
    state: MyState;
    dispatch: React.Dispatch<MyAction>
}

const ContextAuth = createContext<MyContext | null>(null);

const StateProvider = ({ children }: { children: JSX.Element }) => {
    const { Provider } = ContextAuth;
    const [state, dispatch] = useReducer(reducerAuth, initialState);
    return (
        <Provider value={{ state, dispatch }}>
            {children}
        </Provider>
    );
};

【讨论】:

  • 这个答案太棒了!但它也产生了另一个小问题。现在,typescript 无法再通过解构识别 {state, dispatch},如您所见:const { state, dispatch } = useContext(ContextAuth); Typescript 说它在&lt; Context | null &gt; 类型中找不到{ state, dispatch }。 State 和 dispatch 确实存在于 Context 中,但显然不存在于 null 中。常量上下文 = useContext(ContextAuth); ``` if (Context !== null) { const { state, dispatch } = Context; } ``` 我可以用这个 sn-p 解决这个问题,但我发现它有点麻烦。有什么建议吗?
  • 我找到了更好的方法,简单用了:const { state, dispatch } = useContext(ContextAuth) as Context 太感谢了!!!
  • 我昨天实际上回答了另一个问题,我展示了如何为包含状态(从您的初始状态)和调度(虚拟函数)的上下文设置默认值:stackoverflow.com/a/65866029/10431574
猜你喜欢
  • 2020-11-14
  • 2020-03-16
  • 1970-01-01
  • 1970-01-01
  • 2019-06-25
  • 2015-08-02
  • 1970-01-01
  • 1970-01-01
  • 2019-10-27
相关资源
最近更新 更多