[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 (
      
        
      
    );
  }
}

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 (
      

Count: {count}

); } } 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 (
      

Count: {count}

); } } 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 (
      

Count: {count}

); } } 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 (
      

Count: {count}

); } } 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 (
      

Count: {count}

); } } 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 (
      

Count: {count}

); } } 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

你可能感兴趣的:([Next] 四.在next中引入redux)