[Next] 四.在next中引入redux

添加 redux

写过 react 稍微复杂一些应用的话,应该都对 redux(mobx)有一定的了解.这次将 redux 引入到项目中

因为之前写项目的习惯,更喜欢使用 redux-thunk 改写 dispatch 进行异步请求.

redux-thunk 改写了 dispatch API,使其具备接受一个函数作为参数的能力.既然是函数,就可以执行,也就可以发起 ajax 请求了.

yarn add next-redux-wrapper react-redux redux redux-devtools-extension es6-promise redux-thunk redux-logger

创建 actions

首先,还是将 action 的常量抽离出去单独做一个文件 actionTypes.js.当前项目是刚起步,所以暂时不需要将 reducer 和 action 分成小模块.

新建./redux/actions.js

import { actionTypes } from "./actionTypes";

export function failure(error) {
  return {
    type: actionTypes.FAILURE,
    error
  };
}

export function increment() {
  return { type: actionTypes.INCREMENT };
}

export function decrement() {
  return { type: actionTypes.DECREMENT };
}

export function reset() {
  return { type: actionTypes.RESET };
}

export function loadData() {
  return { type: actionTypes.LOAD_DATA };
}

export function loadDataSuccess(data) {
  return {
    type: actionTypes.LOAD_DATA_SUCCESS,
    data
  };
}

export function startClock() {
  return { type: actionTypes.START_CLOCK };
}

export function tickClock(isServer) {
  return {
    type: actionTypes.TICK_CLOCK,
    light: !isServer,
    ts: Date.now()
  };
}

创建 action 常量文件

新建./redux/actionTypes.js

export const actionTypes = {
  FAILURE: "FAILURE",
  INCREMENT: "INCREMENT",
  DECREMENT: "DECREMENT",
  RESET: "RESET",
  LOAD_DATA: "LOAD_DATA",
  LOAD_DATA_SUCCESS: "LOAD_DATA_SUCCESS",
  START_CLOCK: "START_CLOCK",
  TICK_CLOCK: "TICK_CLOCK"
};

创建 reducer

新建./redux/reducer.js

import { actionTypes } from "./actionTypes";

export const exampleInitialState = {
  count: 0,
  error: false,
  lastUpdate: 0,
  light: false,
  placeholderData: null
};

function reducer(state = exampleInitialState, action) {
  switch (action.type) {
    case actionTypes.FAILURE:
      return {
        ...state,
        ...{ error: action.error }
      };

    case actionTypes.INCREMENT:
      return {
        ...state,
        ...{ count: state.count + 1 }
      };

    case actionTypes.DECREMENT:
      return {
        ...state,
        ...{ count: state.count - 1 }
      };

    case actionTypes.RESET:
      return {
        ...state,
        ...{ count: exampleInitialState.count }
      };

    case actionTypes.LOAD_DATA_SUCCESS:
      return {
        ...state,
        ...{ placeholderData: action.data }
      };

    case actionTypes.TICK_CLOCK:
      return {
        ...state,
        ...{ lastUpdate: action.ts, light: !!action.light }
      };

    default:
      return state;
  }
}

export default reducer;

新建./redux/store.js

import { applyMiddleware, createStore } from "redux";
import thunkMiddleware from "redux-thunk";

import rootReducer, { exampleInitialState } from "./reducer";

const bindMiddleware = middleware => {
  if (process.env.NODE_ENV !== "production") {
    const { composeWithDevTools } = require("redux-devtools-extension");
    // 开发模式打印redux信息
    const { logger } = require("redux-logger");
    middleware.push(logger);
    return composeWithDevTools(applyMiddleware(...middleware));
  }
  return applyMiddleware(...middleware);
};

function configureStore(initialState = exampleInitialState) {
  const store = createStore(rootReducer, initialState, bindMiddleware([thunkMiddleware]));
  return store;
}

export default configureStore;

修改 pages/_app.js 文件

redux 会创建一个全局的 state 包裹在 app 的最高一级,然后通过注入的形式添加到下级的各级组件中.现在就在自定义的_app.js 文件中添加 Provider

import React from "react";
import App from "next/app";
import "../assets/css/styles.less";
import { Provider } from "react-redux";
import withRedux from "next-redux-wrapper";
import createStore from "../redux/store";

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {};

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps({ ctx });
    }

    return { pageProps };
  }

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

export default withRedux(createStore)(MyApp);

react-redux 中 mapDispatchToProps 的几种方式

讲一下关于 redux 的集中注入方式,同时将 redux 如何在下级组件使用的方式展示出来.

重点知道什么是 action,什么是 action 生成器,什么又是触发 action 函数

action 是一个对象

{
    type: actionTypes.TICK_CLOCK,
    light: !isServer,
    ts: Date.now()
  }

action 生成器是一个函数

function tickClock(isServer) {
  return {
    type: actionTypes.TICK_CLOCK,
    light: !isServer,
    ts: Date.now()
  };
}

触发 action 函数

function dispatchTickClock(dispatch){
    return dispatch(tickClock(false))
}

mapDispatchToProps

[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function)

传递对象

  • 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中 dispatch 方法会将 action creator 的返回值作为参数执行,Redux 自动发出
import React, { Component } from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "../../redux/actions";
import { Button } from "antd";

class Counter extends Component {
  render() {
    const { count, increment, decrement, reset } = this.props;
    return (
      <div>
        <style jsx>{`
          div {
            padding: 0 0 20px 0;
          }
        `}</style>
        <h1>
          Count: <span>{count}</span>
        </h1>
        <Button type="primary" onClick={increment}>
          当前counter +1
        </Button>
        <Button type="primary" onClick={decrement}>
          当前counter -1
        </Button>
        <Button type="primary" onClick={reset}>
          当前counter Reset
        </Button>
      </div>
    );
  }
}

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

const mapActionCreators = {
  increment,
  decrement,
  reset
};

export default connect(
  mapStateToProps,
  mapActionCreators
)(Counter);

bindActionCreators 辅助函数

  • 如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起(提示:你也许会用到 Redux 的辅助函数 bindActionCreators()。
import React, { Component } from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "../../redux/actions";
import { Button } from "antd";
import { bindActionCreators } from "redux";

class Counter extends Component {
  render() {
    const { count, increment, decrement, reset } = this.props;
    return (
      <div>
        <style jsx>{`
          div {
            padding: 0 0 20px 0;
          }
        `}</style>
        <h1>
          Count: <span>{count}</span>
        </h1>
        <Button type="primary" onClick={increment}>
          当前counter +1
        </Button>
        <Button type="primary" onClick={decrement}>
          当前counter -1
        </Button>
        <Button type="primary" onClick={reset}>
          当前counter Reset
        </Button>
      </div>
    );
  }
}

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

function mapDispatchToProps(dispatch) {
  return {
    increment: bindActionCreators(increment, dispatch),
    decrement: bindActionCreators(decrement, dispatch),
    reset: bindActionCreators(reset, dispatch)
  };
}

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

上面的写法 mapDispatchToProps 可以将函数以不同的名称加入到 props 中,如果不需要变更名称,也可以简写

function mapDispatchToProps(dispatch) {
  return bindActionCreators({increment,decrement,reset},dispatch);
}

其中 action 生成器

export function increment() {
  return { type: actionTypes.INCREMENT };
}

export function decrement() {
  return { type: actionTypes.DECREMENT };
}

这里直接使用了 redux 的 bindActionCreators 辅助函数去绑定 action 触发函数

参数传入函数且不用 bindActionCreators 辅助函数

import React, { Component } from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "../../redux/actions";
import { Button } from "antd";

class Counter extends Component {
  render() {
    const { count, increment, decrement, reset } = this.props;
    return (
      <div>
        <style jsx>{`
          div {
            padding: 0 0 20px 0;
          }
        `}</style>
        <h1>
          Count: <span>{count}</span>
        </h1>
        <Button type="primary" onClick={increment}>
          当前counter +1
        </Button>
        <Button type="primary" onClick={decrement}>
          当前counter -1
        </Button>
        <Button type="primary" onClick={reset}>
          当前counter Reset
        </Button>
      </div>
    );
  }
}

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

function mapActionCreators(dispatch) {
  return {
    increment: () => {
      return dispatch(increment());
    },
    decrement: () => {
      return dispatch(decrement());
    },
    reset: () => {
      return dispatch(reset());
    }
  };
}

export default connect(
  mapStateToProps,
  mapActionCreators
)(Counter);

dispatch 注入组件

  • 如果你省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。
import React, { Component } from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "../../redux/actions";
import { Button } from "antd";

class Counter extends Component {
  increment = () => {
    this.props.dispatch(increment());
  };

  decrement = () => {
    this.props.dispatch(decrement());
  };

  reset = () => {
    this.props.dispatch(reset());
  };
  render() {
    const { count } = this.props;
    return (
      <div>
        <style jsx>{`
          div {
            padding: 0 0 20px 0;
          }
        `}</style>
        <h1>
          Count: <span>{count}</span>
        </h1>
        <Button type="primary" onClick={this.increment}>
          当前counter +1
        </Button>
        <Button type="primary" onClick={this.decrement}>
          当前counter -1
        </Button>
        <Button type="primary" onClick={this.reset}>
          当前counter Reset
        </Button>
      </div>
    );
  }
}

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

export default connect(mapStateToProps)(Counter);

其中 action 生成器

export function increment() {
  return { type: actionTypes.INCREMENT };
}

export function decrement() {
  return { type: actionTypes.DECREMENT };
}

这里的方法就是 increment()生成器返回一个 action,然后交由 action 触发器 dispatch 去触发

使用 redux-thunk

import React, { Component } from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "../../redux/actions";
import { Button } from "antd";

class Counter extends Component {
  render() {
    const { count, increment, decrement, reset } = this.props;
    return (
      <div>
        <style jsx>{`
          div {
            padding: 0 0 20px 0;
          }
        `}</style>
        <h1>
          Count: <span>{count}</span>
        </h1>
        <Button type="primary" onClick={increment}>
          当前counter +1
        </Button>
        <Button type="primary" onClick={decrement}>
          当前counter -1
        </Button>
        <Button type="primary" onClick={reset}>
          当前counter Reset
        </Button>
      </div>
    );
  }
}

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

const mapActionCreators = {
  increment,
  decrement,
  reset
};

export default connect(
  mapStateToProps,
  mapActionCreators
)(Counter);

其中 action 生成器需要修改为返回函数,由于返回的是一个函数,redux 可以在里面执行一些异步操作,action 也可以用来进行网络请求

export function increment() {
  return dispatch => {
    dispatch({ type: actionTypes.INCREMENT });
  };
}

export function decrement() {
  return dispatch => {
    dispatch({ type: actionTypes.DECREMENT });
  };
}

export function reset() {
  return dispatch => {
    dispatch({ type: actionTypes.RESET });
  };
}

使用装饰器

yarn add @babel/plugin-proposal-decorators --dev
yarn add babel-plugin-transform-decorators-legacy --dev

然后修改.babelrc

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    [
      "import",
      {
        "libraryName": "antd",
        "style": "less"
      }
    ]
  ]
}

将组件的代码更新

import React, { Component } from "react";
import { connect } from "react-redux";
import { increment, decrement, reset } from "../../redux/actions";
import { Button } from "antd";

@connect(
  state => ({ count: state.count }),
  dispatch => bindActionCreators({ increment, decrement, reset }, dispatch)
)
class Counter extends Component {
  render() {
    const { count, increment, decrement, reset } = this.props;
    return (
      <div>
        <style jsx>{`
          div {
            padding: 0 0 20px 0;
          }
        `}</style>
        <h1>
          Count: <span>{count}</span>
        </h1>
        <Button type="primary" onClick={increment}>
          当前counter +1
        </Button>
        <Button type="primary" onClick={decrement}>
          当前counter -1
        </Button>
        <Button type="primary" onClick={reset}>
          当前counter Reset
        </Button>
      </div>
    );
  }
}

export default Counter;

如果你之前没有使用装饰器的话,vscode 会报出一个警告,现在去除这个警告

tsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "allowJs": true,
  },
}

没有 tsconfig.json 就用 jsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

[错误解决]https://github.com/zeit/next.js/issues/5231

posted @ 2019-10-23 11:45  月山  阅读(1870)  评论(1编辑  收藏  举报