(Next.js) Thunk 不会触发 reducer

(Next.js) Thunk does not trigger reducer

我使用 next.js (https://github.com/zeit/next.js/tree/canary/examples/with-redux) 提供的 with-redux 示例在 next.js 中设置了 React-Redux。此外,我已经设置了 thunk,因为大多数 redux 调用在未来将是异步的(现在它只是初始状态,将被更改)。

当我现在尝试使用 thunk 调度函数时,reducer 从未被触发。

现在我终于明白如何完成这项工作了。 (注意:这是我第一次尝试使用 next 设置 redux,直到现在我只通过具有清晰路由结构的客户端应用程序完成)

menuStatusActions.js

import * as types from "./actionTypes";

export function changeMenu(id) {
  return { type: types.MENU_CHANGE, id };
}

export function changeMenuStatus(id) {
  return function(dispatch) {
    debugger;
    return dispatch(changeMenu(id));
  };
}

menuStatusReducer.js

import * as types from "../actions/actionTypes";
import initialState from "./initialState";

export default function menuStatusReducer(
  state = initialState.menuState,
  action
) {
  switch (action.type) {
    case types.MENU_CHANGE:
      return Object.assign([], state, {
        [action.id - 1]: !state[action.id - 1]
      });
    default:
      return state;
  }
}

configureStore.js

import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunkMiddleware from "redux-thunk";
import reduxImmutableStateInvariant from "redux-immutable-state-invariant";
import rootReducer from "./reducers";
import inState from "./reducers/initialState";

export default function initializeStore(initialState = inState) {
  return createStore(
    rootReducer,
    initialState,
    composeWithDevTools(
      applyMiddleware(thunkMiddleware, reduxImmutableStateInvariant())
    )
  );
}

with-redux-store.js(从上面的 next.js github 复制 [并改编])

/* eslint-disable no-param-reassign */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable react/prop-types */
/* eslint-disable no-underscore-dangle */
/* eslint-disable-next-line no-param-reassign */

import React from "react";
import initializeStore from "./redux/configureStore";

const isServer = typeof window === "undefined";
const __NEXT_REDUX_STORE__ = "__NEXT_REDUX_STORE__";

function getOrCreateStore(initialState) {
  // Always make a new store if server, otherwise state is shared between requests
  if (isServer) {
    return initializeStore(initialState);
  }

  // Create store if unavailable on the client and set it on the window object
  if (!window[__NEXT_REDUX_STORE__]) {
    window[__NEXT_REDUX_STORE__] = initializeStore(initialState);
  }
  return window[__NEXT_REDUX_STORE__];
}

export default App => {
  return class AppWithRedux extends React.Component {
    static async getInitialProps(appContext) {
      // Get or Create the store with `undefined` as initialState
      // This allows you to set a custom default initialState
      const reduxStore = getOrCreateStore();

      // Provide the store to getInitialProps of pages

      appContext.ctx.reduxStore = reduxStore;

      let appProps = {};
      if (typeof App.getInitialProps === "function") {
        appProps = await App.getInitialProps(appContext);
      }

      return {
        ...appProps,
        initialReduxState: reduxStore.getState()
      };
    }

    constructor(props) {
      super(props);
      this.reduxStore = getOrCreateStore(props.initialReduxState);
    }

    render() {
      return <App {...this.props} reduxStore={this.reduxStore} />;
    }
  };
};

_app.js(从上面的 next.js 示例 github 复制)

import App from "next/app";
import React from "react";
import { Provider } from "react-redux";
import withReduxStore from "../src/with-redux-store";


class MyApp extends App {
  render() {
    const { Component, pageProps, reduxStore } = this.props;
    return (
      <Provider store={reduxStore}>
        <Component {...pageProps} />
      </Provider>
    );
  }
}

export default withReduxStore(MyApp);

调用商店的组件(所有页面中使用的顶层组件)

import React from "react";
import { connect } from "react-redux";
import { makeStyles } from "@material-ui/core/styles";
import Drawer from "@material-ui/core/Drawer";
import CssBaseline from "@material-ui/core/CssBaseline";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import List from "@material-ui/core/List";
import Typography from "@material-ui/core/Typography";
import Divider from "@material-ui/core/Divider";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import { Collapse } from "@material-ui/core";

import PropTypes from "prop-types";

import Link from "next/link";

import sideMenuItems from "./sideMenuItems";
import { changeMenuStatus } from "../redux/actions/menuStatusActions";

const drawerWidth = 240;

const useStyles = makeStyles(theme => ({
  root: {
    display: "flex"
  },
  appBar: {
    width: `calc(100% - ${drawerWidth}px)`,
    marginLeft: drawerWidth
  },
  drawer: {
    width: drawerWidth,
    flexShrink: 0
  },
  drawerPaper: {
    width: drawerWidth
  },
  toolbar: theme.mixins.toolbar,
  content: {
    flexGrow: 1,
    backgroundColor: theme.palette.background.default,
    padding: theme.spacing(3)
  }
}));

function Layout({ title, menuState, changeMenuStatusAction }) {
  const classes = useStyles();
  const open = menuState;

  return (
    <div className={classes.root}>
      <CssBaseline />
      <AppBar position="fixed" className={classes.appBar}>
        <Toolbar>
          <Typography variant="h6" noWrap>
            {title}
          </Typography>
        </Toolbar>
      </AppBar>
      <Drawer
        variant="permanent"
        classes={{
          paper: classes.drawerPaper
        }}
        anchor="left"
      >
        <div className={classes.toolbar} />
        <Divider />
        <List>
          {sideMenuItems.map(item => (
            <>
              <ListItem
                key={`item${item.id}`}
                button
                onClick={() => changeMenuStatusAction(item.id)}
              >
                <ListItemText primary={item.title} />
              </ListItem>
              <Collapse
                key={`collapse${item.id}`}
                in={open[item.id - 1]}
                timeout="auto"
              >
                <List component="div" disablePadding key={`List${item.id}`}>
                  {item.children.map(childItem => (
                    <Link
                      key={`Link${childItem.id}`}
                      href={`${item.href}${childItem.href}`}
                    >
                      <ListItem button key={`ListItem${childItem.id}`}>
                        <ListItemText secondary={childItem.title} />
                      </ListItem>
                    </Link>
                  ))}
                </List>
              </Collapse>
            </>
          ))}
        </List>
      </Drawer>
      <main className={classes.content}>
        <div className={classes.toolbar} />
        Test 1234!
      </main>
    </div>
  );
}

Layout.propTypes = {
  title: PropTypes.string.isRequired,
  // eslint-disable-next-line react/forbid-prop-types
  menuState: PropTypes.any.isRequired,
  changeMenuStatusAction: PropTypes.func.isRequired
};

function mapStateToProps(state) {
  return {
    menuState: state.menuState
  };
}

const mapDispatchToProps = dispatch => ({
  changeMenuStatusAction: dispatch(changeMenuStatus)
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Layout);

结果没有错误,但本质上什么也没有发生。试图在 thunk 中添加一个 "debugger" 但它从未被触发。

它必须是异步的才能工作吗?据我了解,thunkMiddleware 不会关心传递的函数是否同步。

非常感谢所有预先提供的帮助!

mapDispatchToProps 应该是一个输入为 dispatch 的函数,然后你必须用 dispatch 包装你的动作。将您的 mapDispatchToProps 更改为以下

const mapDispatchToProps = (dispatch) => ({
  changeMenuStatusAction: (id) => dispatch(changeMenuStatus(id))
});

然后你可以使用changeMenuStatusAction作为道具,用它来触发你的动作。希望对您有所帮助:).