在 React 中使用 Redux

这是一篇介绍 redux 的入门文章,欲知更多内容请查阅 官方文档css

本文会经过三种方式实现一个简单到不能呼吸的计数器小例子,先用 React 实现,再慢慢引入 Redux 的内容,来了解什么是 Redux、为何要使用 Redux 以及如何简单地使用 Redux。html

一、React 实现计数器

上面这个例子用 React 实现起来很是简单,初始化一个 creact-react-app,而后为了页面看起来更美观,在 index.html 加入 bootstrap 的 CDN,并修改 App.js 为以下内容就能够了。react

<link href="https://cdn.bootcss.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet">
复制代码
import React, { Component } from 'react';

export default class App extends Component {

  constructor(props) {
    super(props)

    this.state = {
      count: 0
    }
  }

  handleIncrement = () => {
    this.setState({
      count: this.state.count + 1
    })
  }

  handleDecrement = () => {
    this.setState({
      count: this.state.count - 1
    })
  }

  render() {
    return (
      <div className="container"> <h1 className="text-center mt-5">{this.state.count}</h1> <p className="text-center"> <button onClick={this.handleIncrement} className="btn btn-primary mr-2">Increase</button> <button onClick={this.handleDecrement} className="btn btn-danger my-2">Decrease</button> </p> </div>
    );
  }
}
复制代码

这个例子很是简单,可是咱们应该思考一下,React 是如何来改变这个数字的。git

有两个关键步骤,首先,它会从 state 中来读取初始值,而后当有点击事件发生了之后会去调用 setState 方法来改变 state 的值并从新渲染页面。这样就能看到页面上数字能够发生改变的效果了。github

那么问题来了,React 中一个组件里面维护数据只须要 state 和 setState 就能够轻松搞定。假如多个组件都须要维护这一份数据怎么办呢?npm

二、为何要使用 Redux

了解 React 组件之间如何传递数据的人都应该知道,React 传递数据是一级一级地传的。就像以下的左图,绿色组件要想把某个时候的数据传递给红色的组件那么须要向上回调两次,再向下传一次,很是之麻烦。redux

而 Redux 是怎么作的呢,Redux 有一个很是核心的部分就是 Store,Store 中管理的数据独立于 React 组件以外,若是 React 某个组件中的某个数据在某个时刻改变了(能够称之为状态改变了),就能够直接更改这个 Store 中管理的数据,这样其余组件想要拿到此时的数据直接拿就好了,不须要传来传去。bootstrap

须要说明的是,react 中有一个 context 也能够实现相似的功能,但它是一种侵入式写法,官方都不推荐,因此本文都不会提到它。app

这个过程看上去挺简单的,可是 Redux 为了作好这样一件事也是要经历一个比较复杂的过程的。dom

接下来就开启 Redux 之旅吧。

三、如何使用 Redux

安装 redux:

$ npm install --save redux
or
$ yarn add redux
复制代码

首先建立一个 src/Reducer.js

Store 一般要和 Reducer 来配合使用,Store 存数据,Reducer 是个纯函数,它接收并更新数据。

先建立一个 Reducer,为了简单,这里直接将须要的初始值写到 reducer 中的 state 中,state = 0 是给它的一个初始化数据(state 的值能够是一个对象,这里直接给一个数字),它还收接一个 action,当 action 的 type 为 'increment' 时就将 state + 1,反之减一。

虽然这里把初始值写到了 reducer 中,可是真正存储这个 state 的仍是 store,reducer 的做用是负责接收、更新并返回新的数据。

同时也这里能够看出,Reducer 怎么更新数据,得看传入的 action 的 type 值了。

export default (state = 0, action) => {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    default:
      return state;
  }
};
复制代码

而后建立 src/Store.js

有了 Reducer 以后就能够来建立咱们须要的 store 了,这个 store 是全局的,任何一个 react 组件想用它均可以引入进去。

import { createStore } from 'redux';
import Reducer from './Reducer';

const store = createStore(Reducer)

export default store;
复制代码

最后来修改咱们的 App.js

import React, { Component } from "react";
+ import store from "./Store";

export default class App extends Component {
+ onIncrement = () => {
+ store.dispatch({
+ type: "increment"
+ });
+ };

+ onDecrement = () => {
+ store.dispatch({
+ type: "decrement"
+ });
+ };

  render() {
+ store.subscribe(() => console.log("Store is changed: " + store.getState()));

    return (
      <div className="container">
+ <h1 className="text-center mt-5">{store.getState()}</h1>
        <p className="text-center">
          <button className="btn btn-primary mr-2" onClick={this.onIncrement}>
            Increase
          </button>
          <button className="btn btn-danger my-2" onClick={this.onDecrement}>
            Decrease
          </button>
        </p>
      </div>
    );
  }
}
复制代码

store.getState() 是用来获取 store 中的 state 值的。 store.subscribe() 方法是用来监听 store 中 state 值的,若是 state 被改变,它就会被触发,因此这个方法接收的是一个函数。subscribe() 方法也能够写到 componentDidMount() 里面。

以前说了,要想改变 store 中 state 的值,就要传入一个 action 的 type 值,redux 规定,这个 action 的值须要由 store 的 dispatch 方法来派发。

因此用 store.dispatch({type: 'increment'}); 这样简单的写法就轻松地给 reducer 传入了想要的值了,这个时候 state 的值就能变化了。

如今就能够验证一下上面的操做了,手动改一下 state 的值,发现页面上的数据也改变了,说明页面上的数据从 store 中成功读取了:

触发点击事件后,能够看到 state 的值成功的被改变了,说明用 store.dispatch() 来派发的这个 action 的 type 是成功的。

那么页面上的数据为何没有变化呢,这是由于 state 的值虽然被改变了,可是页面并无从新渲染,以前在用 react 来实现这个功能的时候改变 state 调用了 setState() 方法,这个方法会同时从新渲染 render()。

那么这里其实也是能够借助这个 setState() 方法的

修改 App.js

import React, { Component } from "react";
import store from "./Store";

export default class App extends Component {
+ constructor(props) {
+ super(props);

+ this.state = {
+ count: store.getState()
+ };
+ }

  onIncrement = () => {
    ...
  };

  onDecrement = () => {
    ...
  };

  render() {
+ store.subscribe(() =>
+ this.setState({
+ count: store.getState()
+ })
+ );

    return (
      ...
      );
  }
}
复制代码

这样借助 react 的 setState 方法就可让 store 中的值改变时也能同时从新渲染页面了。

一个简单的 redux 例子到这里也就完成了。

3.一、抽取 Action

上面的例子中,在 onClick 事件出触发的函数里面用了 store.dispatch() 方法来派发 Action 的 type,这个 Action 其实也能够单独抽取出来

新建 src/Action.js

export const increment = () => {
  return {
      type: "increment"
  };
};

export const decrement = () => {
  return {
      type: "decrement"
  };
};
复制代码

修改 App.js

import React, { Component } from "react";
import store from "./Store";
+import * as Action from './Action'

export default class App extends Component {
  ...

  onIncrement = () => {
+ store.dispatch(Action.increment());
  };

  onDecrement = () => {
+ store.dispatch(Action.decrement());
  };

  render() {
    ...
  }
}
复制代码

这样就把 dispatch 里面的内容单独抽取出来了,Action.js 里面的内容也就表明了用户鼠标进行的的一些动做。

这个 Action.js 是还能够作进一步抽取的,由于 type 的值是个常量,因此能够单独提取出来

新建 ActionTypes.js

export const INCREMENT = 'increment'

export const DECREMENT = 'decrement'
复制代码

而后就能够修改 Actions.js 了

+import * as ActionTypes from './ActionType';

...
复制代码

一样的 Reducer.js 的 type 也能够修改下

+import * as ActionTypes from './ActionType';

export default (state = 0, action) => {
  switch (action.type) {
+ case ActionTypes.INCREMENT:
      return state + 1;
+ case ActionTypes.DECREMENT:
      return state - 1;
    default:
      return state;
  }
};
复制代码

到这里一个包含 Action、Reducer、Store、dispatch、subscribe、view 的完整 redux 例子就实现了,这里的 view 指的是 App.js 所提供的页面也就是 React 组件。

这个时候再来看一眼题图就很很是好理解 Redux 整个工做流程了:

四、如何使用 react-redux

在 react 中使用 redux 实际上是还能够更加优雅一点的。redux 还提供了一个 react-redux 插件,须要注意的是这个插件只起到辅助做用并非用来替代 redux 的。

至于使用了它如何变得更加优雅,这个先从代码开始提及:

安装 react-redux:

$ npm install --save react-redux
or
$ yarn add react-redux
复制代码

每一个组件中要用到 store,按以前的方法须要单独引入,这里还能够换一种方式,直接在最顶层组件将它传进去,而后组件想用的时候再接收:

修改 index.js,这是个最顶层组件,将 store 在这里引入并向下传递

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
+import store from "./Store";
+import { Provider } from 'react-redux'

import registerServiceWorker from "./registerServiceWorker";

ReactDOM.render(
+ <Provider store={store}>
    <App />
+ </Provider>,
  document.getElementById("root")
);
registerServiceWorker();
复制代码

修改 App.js,在这里引入用 connect 来接收 store,而后就能够用 this.props 来使用 dispatch 了,state 则用一个函数来接收一下就可使用了。

import React, { Component } from "react";
import * as Action from "./Action";
+import { connect } from "react-redux";
-import store from "./Store";

+class App extends Component {
- constructor(props) {
- super(props);

- this.state = {
- count: store.getState()
- };
- }

  onIncrement = () => {
+ this.props.dispatch(Action.increment());
  };

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

  render() {
- store.subscribe(() =>
- this.setState({
- count: store.getState()
- })
- );

    return (
      <div className="container">
+ <h1 className="text-center mt-5">{this.props.count}</h1>
        <p className="text-center">
          <button className="btn btn-primary mr-2" onClick={this.onIncrement}>
            Increase
          </button>
          <button className="btn btn-danger my-2" onClick={this.onDecrement}>
            Decrease
          </button>
        </p>
      </div>
    );
  }
}

+const mapStateToProps = state => ({
+ count: state
+});

+export default connect(mapStateToProps)(App);
复制代码

你会意外地发现居然不须要用 setState 方法来从新渲染页面了,redux 已经帮咱们作这件事情了。你可能会对 connect()() 这种写法有疑问,它是一个柯里化函数,底层是怎么实现的本文不讨论,如今只须要知道传入什么参数,怎么用它就能够了。

4.一、处理 Action

其实使用了 react-redux 以后咱们不只不需关心如何去从新渲染页面,还不须要去手动派发 Action,直接在 connect 方法中把 Action 传进去,而后直接调用就好了

在上面的基础上再次修改 App.js:

import React, { Component } from "react";
import * as Action from "./Action";
import { connect } from "react-redux";

class App extends Component {
- onIncrement = () => {
- this.props.dispatch(Action.increment());
- };

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

  render() {
+ const { increment, decrement } = this.props;

    return (
      <div className="container">
        <h1 className="text-center mt-5">{this.props.count}</h1>
        <p className="text-center">
+ <button className="btn btn-primary mr-2" onClick={() => increment()}>
            Increase
          </button>
+ <button className="btn btn-danger my-2" onClick={() => decrement()}>
            Decrease
          </button>
        </p>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  count: state
});

+export default connect(mapStateToProps,Action)(App);
复制代码

到这里你应该就能体会到使用 react-redux 插件的便捷性了,其实它还有其余不少优势,这里再也不一一举例。

五、如何使用多个 reducer

redux 中只须要有一个全局的 store,那么若是还须要管理其它状态,可能就须要用到多个 reducer,redux 中提供了一个 combineReducers 能够来将多个 reducer 链接起来。

这里再来演示一下 combineReducers 的用法,为了尽可能少修改文件,我这里并无建立文件夹来分类管理,实际使用过程当中不一样做用的文件应该放到不一样的文件夹中。

建立文件 src/Reducer2.js

export default (state = "hello", action) => {
  switch (action.type) {
    default:
      return state;
  }
};
复制代码

建立 src/CombineReducer.js

import { combineReducers } from 'redux';

import count from './Reducer';

import hello from './Reducer2';

const rootReducer = combineReducers({
    count,
    hello
})

export default rootReducer;
复制代码

修改 Store.js

import { createStore } from 'redux';
+import rootReducer from './CombineReducer';

+const store = createStore(rootReducer)

export default store;
复制代码

修改 App.js

...
    return (
      <div className="container">
+ <h1 className="text-center mt-5">{this.props.text}{this.props.count}</h1>
        ...
      </div>
    );
  }
}

const mapStateToProps = state => ({
  count: state.count,
+ text: state.hello
});
...
复制代码

效果以下,能够在 store 中读取到相应的值:

最后,完整的代码在这里:github.com/bgrc/react-…

相关文章
相关标签/搜索