【问题标题】:What is the correct to create a interface for action object with react hooks and typescript使用反应钩子和打字稿为动作对象创建接口的正确方法是什么
【发布时间】:2020-01-27 17:57:15
【问题描述】:

我正在使用 react hooks 和 typescript。我使用useReducer() 表示全局状态。 reducer 函数的动作包含两个属性namedataname 表示事件或更改的名称,data 将是该特定名称所需的特定数据。

到目前为止,名称有四个值。如果名称为"setUserData",则data 应为IUserData(接口)。如果名称是setDialog,那么data 应该是DialogNames(包含两个字符串的类型)。如果是其他内容,则不需要数据。

//different names of dialog.
export type DialogNames = "RegisterFormDialog" | "LoginFormDialog" | "";

//type for name property in action object
type GlobalStateActionNames =
   | "startLoading"
   | "stopLoading"
   | "setUserData"
   | "setDialog";

//interface for main global state object.
export interface IGlobalState {
   loading: boolean;
   userData: IUserData;
   dialog: DialogNames;
}

interface IUserData {
   loggedIn: boolean;
   name: string;
}
//The initial global state
export const initialGlobalState: IGlobalState = {
   loading: false,
   userData: { loggedIn: false, name: "" },
   dialog: ""
};

//The reducer function which is used in `App` component.
export const GlobalStateReducer = (
   state: IGlobalState,
   { name, data }: IGlobalStateAction
): IGlobalState => {
   switch (name) {
      case "startLoading":
         return { ...state, loading: true };
      case "stopLoading":
         return { ...state, loading: false };
      case "setUserData":
         return { ...state, userData: { ...state.userData, ...data } };
      case "setDialog":
         return { ...state, dialog: data };
      default:
         return state;
   }
};

//The interface object which is passed from GlobalContext.Provider as "value"
export interface GlobalContextState {
   globalState: IGlobalState;
   dispatchGlobal: React.Dispatch<IGlobalStateAction<GlobalStateActionNames>>;
}

//intital state which is passed to `createContext`
export const initialGlobalContextState: GlobalContextState = {
   globalState: initialGlobalState,
   dispatchGlobal: function(){}
};

//The main function which set the type of data based on the generic type passed.
export interface IGlobalStateAction<
   N extends GlobalStateActionNames = GlobalStateActionNames
> {
   data?: N extends "setUserData"
      ? IUserData
      : N extends "setDialog"
      ? DialogNames
      : any;
   name: N;
}

export const GlobalContext = React.createContext(initialGlobalContextState);

我的&lt;App&gt; 组件看起来像。

const App: React.SFC = () => {
   const [globalState, dispatch] = React.useReducer(
      GlobalStateReducer,
      initialGlobalState
   );


   return (
      <GlobalContext.Provider
         value={{
            globalState,
            dispatchGlobal: dispatch
         }}
      >
         <Child></Child>
      </GlobalContext.Provider>
   );
};

上述方法很好。我必须像下面在&lt;Child&gt;中使用它

dispatchGlobal({
   name: "setUserData",
   data: { loggedIn: false }
} as IGlobalStateAction<"setUserData">);

上述方法的问题是它使代码更长一些。第二个问题是我必须导入IGlobalStateAction,因为我必须在哪里使用dispatchGlobal

有没有一种方法我只能告诉namedata 被自动分配给正确的类型或任何其他更好的方法。请引导到正确的路径。

【问题讨论】:

    标签: javascript typescript react-hooks


    【解决方案1】:

    useReducer 与打字稿一起使用有点棘手,因为正如您所提到的,reducer 的参数会根据您采取的操作而有所不同。

    我想出了一个模式,您可以使用类来实现您的操作。这允许您将类型安全参数传递到类的构造函数中,并且仍然使用类的超类作为 reducer 参数的类型。听起来可能比实际更复杂,这里有一个例子:

    interface Action<StateType> {
      execute(state: StateType): StateType;
    }
    
    // Your global state
    type MyState = {
      loading: boolean;
      message: string;
    };
    
    class SetLoadingAction implements Action<MyState> {
      // this is where you define the parameter types of the action
      constructor(private loading: boolean) {}
      execute(currentState: MyState) {
        return {
          ...currentState,
          // this is how you use the parameters
          loading: this.loading
        };
      }
    }
    

    由于状态更新逻辑现在被封装到类的execute方法中,reducer现在只有这么小:

    const myStateReducer = (state: MyState, action: Action<MyState>) => action.execute(state);
    

    使用此 reducer 的组件可能如下所示:

    const Test: FunctionComponent = () => {
      const [state, dispatch] = useReducer(myStateReducer, initialState);
    
      return (
        <div>
          Loading: {state.loading}
          <button onClick={() => dispatch(new SetLoadingAction(true))}>Set Loading to true</button>
          <button onClick={() => dispatch(new SetLoadingAction(false))}>Set Loading to false</button>
        </div>
      );
    }
    

    如果您使用这种模式,您的操作会将状态更新逻辑封装在它们的执行方法中,这(在我看来)可以更好地扩展,因为您不会得到一个带有巨大 switch-case 的 reducer。您也是完全类型安全的,因为输入参数的类型由操作的构造函数定义,reducer 可以简单地采用 Action 接口的任何实现。

    【讨论】:

    • 这是什么Command 这里Command&lt;MyState&gt;。你是说Action&lt;MyState&gt;
    • @MaheerAli 是的,很抱歉这个错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-15
    • 1970-01-01
    • 2021-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多