• [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

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法训练 拦截导弹
    Java实现 蓝桥杯VIP 算法训练 拦截导弹
    Java实现 蓝桥杯VIP 算法训练 回文数
    Java实现 蓝桥杯VIP 算法训练 回文数
    Java实现 蓝桥杯VIP 算法训练 集合运算
    好看的游戏soul calibur
    程序员的出路(要非常专一门技术才行,超过80%的同行,或者积累自己的类库和产品)
    Qt的版本历史
    红魔城传说:血色交响曲 (2009)(东方系列游戏,实在是太美了)
    QString和char字符数组之间的转换(QTextCodec.toUnicode方法,特别注意的问题)
  • 原文地址:https://www.cnblogs.com/mybilibili/p/11725443.html
Copyright © 2020-2023  润新知