(Next.js) Thunk 不会触发 reducer

Posted

技术标签:

【中文标题】(Next.js) Thunk 不会触发 reducer【英文标题】:(Next.js) Thunk does not trigger reducer 【发布时间】:2020-01-15 05:29:34 【问题描述】:

我使用 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% - $drawerWidthpx)`,
    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 中添加一个“调试器”,但它从未被触发。

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

非常感谢所有的帮助!

【问题讨论】:

+注意:如果我尝试通过 chrome 中的 REDUX 开发工具手动调度操作,则操作按预期工作。因此,我认为减速器按预期工作。 【参考方案1】:

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

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

然后您可以使用changeMenuStatusAction 作为道具并使用它来触发您的操作。希望它有所帮助:)。

【讨论】:

这会在操作中出现:TypeError: dispatch is not a function 错误。 thunk 被调用的地方。 -> 我想这很好,因为 thunk 被击中了。 TypeError: dispatch is not a function (anonymous function) ./src/redux/actions/menuStatusActions.js:14 我在 thunk 中添加了一个调试器。现在在这里 dispatch = 1 (这是传递给它的 id) 更新您的操作以返回调度而不是 1 我做了 - 最初是为了测试。 export function changeMenuStatus(id) return function(dispatch) debugger; return dispatch(changeMenu(id)); ;

以上是关于(Next.js) Thunk 不会触发 reducer的主要内容,如果未能解决你的问题,请参考以下文章

如何使 thunk 独立于状态形状以使其可移植?

在调用另一个动作创建者之前等待动作创建者完成 - Redux thunk

Next.js Router.push() 自动重新加载页面

Redux Thunk操作,axios返回多个值

带有 axios 返回多个值的 Redux Thunk 操作

Next.js 不会在 _app.tsx 中传递用户属性