Shorter way to mapDispatchToProps using React, Red

2019-07-22 15:03发布

问题:

I'm trying to figure out how to reduce the amount of boilerplate when using React, Redux and TypeScript together. It might be that you can't in this case, but wanted to see if anyone has ideas.

I currently have a component that dispatches an action that toggles a menu, alternating between showing and hiding it. To do this I've defined my class something like this (omitting code related to state, focusing on the dispatching of action):

import {Action, toggleMenu} from "../../actions/index";    

interface IConnectedDispatch {
  toggleMenu: (isActive: boolean) => Action;
}

class HeaderMenu extends React.Component<IOwnProps & IConnectedState & IConnectedDispatch, any> {

  constructor(props: IOwnProps & IConnectedState & IConnectedDispatch) {
    super(props);
    this.toggleMenuState = this.toggleMenuState.bind(this);
  }

  public render() {        
    return (
      <button className={buttonClass} onClick={this.props.toggleMenu(this.props.isActive)} type="button">
      </button>
    );
  }
}

const mapDispatchToProps = (dispatch: redux.Dispatch<Store.All>): IConnectedDispatch => ({
  toggleMenu: (isActive: boolean) => dispatch(toggleMenu(isActive))});

The action is defined as

export function toggleMenu(isActive: boolean): Dispatch<Action> {
  return (dispatch: Dispatch<Action>) => {
    dispatch({
      isActive,
      type: "TOGGLE_MENU",
    });
  };
}

It feels like it should be possible to reduce the amount of code required to accomplish my goal here. Being new to React, Redux and TypeScript I fail to see exactly how though. Specifically it feels very repetitive to write the action name, toggleMenu, over and over. For example twice in this part:

const mapDispatchToProps = (dispatch: redux.Dispatch<Store.All>): IConnectedDispatch => ({
  toggleMenu: (isActive: boolean) => dispatch(toggleMenu(isActive))});

Any advice is appreciated!

回答1:

There is a shorter way. You can simplify a lot of your code.

Action - Original

export function toggleMenu(isActive: boolean): Dispatch<Action> {
  return (dispatch: Dispatch<Action>) => {
    dispatch({
      isActive,
      type: "TOGGLE_MENU",
    });
  };
}

Action - Reduced

export const toggleMenu = (isActive: boolean) => ({
  isActive,
  type: "TOGGLE_MENU"
})

Properties Interface - Original

interface IConnectedDispatch {
  toggleMenu: (isActive: boolean) => Action;
}

Properties Interface - Reduced

import { toggleMenu } from "./actions"
interface IConnectedDispatch {
  toggleMenu: typeof toggleMenu
}

MapDispatch - Original

const mapDispatchToProps = (dispatch: redux.Dispatch<Store.All>): IConnectedDispatch => ({
  toggleMenu: (isActive: boolean) => dispatch(toggleMenu(isActive))});

MapDispatch - Reduced

const mapDispatchToProps = { 
  toggleMenu 
};

I can recommend this library typescript-fsa. It helps reduce a lot of boilerplate created by actions, especially async ones.



回答2:

mapDispatchToProps will accept an object of action creators instead of a function and automatically bind all them all to dispatch.

From the docs:

If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props

This allows you to rewrite it as:

const mapDispatchToProps = { 
  toggleMenu 
};

Note: I'm not sure what you would need to type this to (if at all) in typescript.