網站搜尋

React Hooks - 使用 TypeScript 的 createContext 和 useReducer 時,無法將物件狀態和 dispatch 傳遞給 Provider


我正在嘗試使用 contextApi 和 hooks 來實現類似 redux 的行為,但我在使用 typescript 時遇到了一些問題。

我想要提供應用程式的狀態和 dispatch,以便能從元件內部存取和修改 store。問題是,當我使用 createContext 時,我仍然無法取得 state 或 dispatch 物件,因為我只能在 React 元件內呼叫 useReducer。因此,我只能宣告 createContext 時將參數的類型設為 any,以便當時可以傳入 null,之後再傳入其他的 dispatch 和 state。

    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 };

有沒有辦法可以傳遞 state 和 dispatch,而不需要宣告類型為 any,並且不會有 TypeScript 衝突?

我不想使用 redux 來實現這一切,因為我想使用原生工具,但 redux 似乎更簡單,儘管非常相似。

我嘗試將物件 {state,dispatch} 的類型傳遞給 create context,如下所示:

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

但在這種情況下,我不再能夠導出 Context Auth,因為它隨後會在 StateProvider 函數內部被創建。

答案#1

你問題的根源在於,你試圖在物件被創建之後推斷它的類型,而不是理解將會創建哪種類型的物件。在我看來,這是一種倒退的設計。

在可能的情況下,最好避免使用 typeof。有些事情你可以用 type 或 interface 來說明,而 typeof 本身無法推斷清楚。string "myString" 可以是任何 string 值,還是它是一個字串字面量?這個 null 屬性是否只能是 null,還是它可能是 null 或其他某種類型?那其他類型會是哪一種呢?希望你明白我的意思。

dispatch 有一個已知的類型。它是一個接受 action 並返回 void 的函數。你不需要使用 typeof dispatch 來獲取它的類型。

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

泛型變數 A 代表可接受的 action 參數。你使用哪一種取決於你希望你的應用程式的型別檢查有多嚴格。你可以將它設為你特定 action 類型的聯合集合,或者設為所有 action 都會實現的一般介面。Redux 匯出了通用型別 Action 和 AnyAction,但 React 沒有,所以你必須自己定義。

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

同樣地,你應該已經知道你的 State 預期的類型/介面。如果不知道,就定義它吧!

這裡是一個完整的程式碼範例。要實現類型的地方是在你的 initialState 和 reducerAuth。如果這些類型設定正確,那麼 StateProvider 就不需要額外的類型,因為 useReducer Hook 可以推斷 state 和 dispatch 的類型。

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>
    );
};