[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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM