[Next] 四.在next中引入redux

添加 redux

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

新建./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()
  };
}

新建./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"
};

新建./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 createSagaMiddleware from "redux-saga";

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

const bindMiddleware = middleware => {
  if (process.env.NODE_ENV !== "production") {
    const { composeWithDevTools } = require("redux-devtools-extension");
    return composeWithDevTools(applyMiddleware(...middleware));
  }
  return applyMiddleware(...middleware);
};

function configureStore(initialState = exampleInitialState) {
  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(rootReducer, initialState, bindMiddleware([sagaMiddleware]));

  store.sagaTask = sagaMiddleware.run(rootSaga);

  return store;
}

export default configureStore;

使用 redux-sage 实现异步函数

新建./redux/saga.js

import { all, call, delay, put, take, takeLatest } from "redux-saga/effects";
import es6promise from "es6-promise";
import "isomorphic-unfetch";
import { actionTypes } from "./actionTypes";
import { failure, loadDataSuccess, tickClock } from "./actions";

es6promise.polyfill();

function* runClockSaga() {
  yield take(actionTypes.START_CLOCK);
  while (true) {
    yield put(tickClock(false));
    yield delay(1000);
  }
}

function* loadDataSaga() {
  try {
    const res = yield fetch("https://jsonplaceholder.typicode.com/users");
    const data = yield res.json();
    yield put(loadDataSuccess(data));
  } catch (err) {
    yield put(failure(err));
  }
}

function* rootSaga() {
  yield all([call(runClockSaga), takeLatest(actionTypes.LOAD_DATA, loadDataSaga)]);
}

export default rootSaga;

修改 pages/_app.js 文件

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 withReduxSaga from "next-redux-saga";
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)(withReduxSaga(MyApp));

react-redux 中 mapDispatchToProps 的几种方式

首先需要先明白什么是 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342