React-redux: React.js 和 Redux 架构的结合

经过Redux 架构理解咱们了解到 Redux 架构的 store、action、reducers 这些基本概念和工做流程。咱们也知道了 Redux 这种架构模式能够和其余的前端库组合使用,而 React-redux 正是把 Redux 这种架构模式和 React.js 结合起来的一个库。html

Context

在 React 应用中,数据是经过 props 属性自上而下进行传递的。若是咱们应用中的有不少组件须要共用同一个数据状态,能够经过状态提高的思路,将共同状态提高到它们的公共父组件上面。可是咱们知道这样作是很是繁琐的,并且代码也是难以维护的。这时会考虑使用 Context,Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。也就是说在一个组件若是设置了 context,那么它的子组件均可以直接访问到里面的内容,而不用经过中间组件逐级传递,就像一个全局变量同样。前端

在 App -> Toolbar -> ThemedButton 使用 props 属性传递 theme,Toolbar 做为中间组件将 theme 从 App 组件 传递给 ThemedButton 组件。react

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // Toolbar 组件接受一个额外的“theme”属性,而后传递给 ThemedButton 组件。
  // 若是应用中每个单独的按钮都须要知道 theme 的值,这会是件很麻烦的事,
  // 由于必须将这个值层层传递全部组件。
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

使用 context,就能够避免经过中间元素传递 props 了redux

// Context 可让咱们无须明确地传遍每个组件,就能将值深刻传递进组件树。
// 为当前的 theme 建立一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给如下的组件树。
    // 不管多深,任何组件都能读取这个值。
    // 在这个例子中,咱们将 “dark” 做为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件不再必指明往下传递 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,而后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

虽然解决了状态传递的问题却引入了 2 个新的问题。性能优化

1. 咱们引入的 context 就像全局变量同样,里面的数据能够被子组件随意更改,可能会致使程序不可预测的运行。架构

2. context 极大地加强了组件之间的耦合性,使得组件的复用性变差,好比 ThemedButton 组件由于依赖了 context 的数据致使复用性变差。app

咱们知道,redux 不正是提供了管理共享状态的能力嘛,咱们只要经过 redux 来管理 context 就能够啦,第一个问题就能够解决了。dom

 

Provider 组件

React-Redux 提供 Provider 组件,利用了 react 的 context 特性,将 store 放在了 context 里面,使得该组件下面的全部组件都能直接访问到 store。大体实现以下:ide

class Provider extends Component {
  // getChildContext 这个方法就是设置 context 的过程,它返回的对象就是 context,全部的子组件均可以访问到这个对象
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object
}

那么咱们能够这么使用,将 Provider 组件做为根组件将咱们的应用包裹起来,那么整个应用的组件均可以访问到里面的数据了函数

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import { Provider } from 'react-redux'; import { createStore } from 'redux'; import todoApp from './reducers'; import App from './components/App'; const store = createStore(todoApp); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )

 

展现(Dumb Components)组件和容器(Smart Components)组件

还记得咱们的第二个问题吗?组件由于 context 的侵入而变得不可复用。React-Redux 为了解决这个问题,将全部组件分红两大类:展现组件和容器组件。

展现组件

展现组件有几个特征

1. 组件只负责 UI 的展现,没有任何业务逻辑

2. 组件没有状态,即不使用 this.state

3. 组件的数据只由 props 决定

4. 组件不使用任何 Redux 的 API

 

展现组件就和纯函数同样,返回结果只依赖于它的参数,而且在执行过程里面没有反作用,让人以为很是的靠谱,能够放心的使用。

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Title extends Component {
  static propTypes = {
    title: PropTypes.string
  }

  render () {
    return (
      <h1>{ this.props.title }</h1>
    )
  }
}

像这个 Title 组件就是一个展现组件,组件的结果彻底由外部传入的 title 属性决定。

容器组件

容器组件的特征则相反

1. 组件负责管理数据和业务逻辑,不负责 UI 展现

2. 组件带有内部状态

3. 组件的数据从 Redux state 获取

4. 使用 Redux 的 API

 

你能够直接使用 store.subscribe() 来手写容器组件,可是不建议这么作,由于这样没法使用 React-redux 带来的性能优化。

React-redux 规定,全部的展现组件都由用户提供,容器组件则是由 React-Redux 的 connect() 自动生成。

 

高阶组件 Connect 

React-redux 提供 connect 方法,能够将咱们定义的展现组件生成容器组件。connect 函数接受一个展现组件参数,最后会返回另外一个容器组件回来。因此 connect 实际上是一个高阶组件(高阶组件就是一个函数,传给它一个组件,它返回一个新的组件)。

import { connect } from 'react-redux';
import Header from '../components/Header';

export default connect()(Header);

上面代码中,Header 就是一个展现组件,通过 connect 处理后变成了容器组件,最后把它导出成模块。这个容器组件没有定义任何的业务逻辑,全部不能作任何事情。咱们能够经过 mapStateToProps 和 mapDispatchToProps 来定义咱们的业务逻辑。

import { connect } from 'react-redux';
import Title from '../components/Title';

const mapStateToProps = (state) => {
  return {
    title: state.title
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onChangeColor: (color) => {
      dispatch({ type: 'CHANGE_COLOR', color });
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Title);

mapStateToProps 告诉 connect 咱们要取 state 里的 title 数据,最终 title 数据会以 props 的方式传入 Title 这个展现组件。

mapStateToProps 会订阅 Store,每当 state 更新的时候,就会自动执行,从新计算展现组件的参数,从而触发展现组件的从新渲染。

mapDispatchToProps 告诉 connect 咱们须要 dispatch action,最终 onChangeColor 会以 props 回调函数的方式传入 Title 这个展现组件。

 

Connect 组件大概的实现以下

export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor () {
      super()
      this.state = {
        allProps: {}
      }
    }

    componentWillMount () {
      const { store } = this.context
      this._updateProps()
      store.subscribe(() => this._updateProps())
    }

    _updateProps () {
      const { store } = this.context
      let stateProps = mapStateToProps
        ? mapStateToProps(store.getState(), this.props) // 将 Store 的 state 和容器组件的 state 传入 mapStateToProps
        : {} // 判断 mapStateToProps 是否传入
      let dispatchProps = mapDispatchToProps
        ? mapDispatchToProps(store.dispatch, this.props) // 将 dispatch 方法和容器组件的 state 传入 mapDispatchToProps
        : {} // 判断 mapDispatchToProps 是否传入
      this.setState({
        allProps: {
          ...stateProps,
          ...dispatchProps,
          ...this.props
        }
      })
    }

    render () {
      // 将 state.allProps 展开以容器组件的 props 传入
      return <WrappedComponent {...this.state.allProps} />
    }
  }
  return Connect
}

 

小结

至此,咱们就很清楚了,原来 React-redux 就是经过 Context 结合 Redux 来实现 React 应用的状态管理,经过 Connect 这个高阶组件来实现展现组件和容器组件的链接的。

相关文章
相关标签/搜索