React-redux: React.js 和 Redux 架构的连系,Redux 架构明白

通过Redux 架构明白我们领会到 Redux 架构的 store、action、reducers 这些基本概念和事情流程。我们也知道了 Redux 这种架构模式可以和其他的前端库组合使用,而 React-redux 正是把 Redux 这种架构模式和 React.js 连系起来的一个库。

Context

在 React 应用中,数据是通过 props 属性自上而下举行通报的。若是我们应用中的有许多组件需要共用同一个数据状态,可以通过状态提升的思绪,将配合状态提升到它们的公共父组件上面。然则我们知道这样做是异常繁琐的,而且代码也是难以维护的。这时会思量使用 Context,Context 提供了一个无需为每层组件手动添加 props,就能在组件树间举行数据通报的方式。也就是说在一个组件若是设置了 context,那么它的子组件都可以直接接见到内里的内容,而不用通过中心组件逐级通报,就像一个全局变量一样。

在 App -> Toolbar -> ThemedButton 使用 props 属性通报 theme,Toolbar 作为中心组件将 theme 从 App 组件 通报给 ThemedButton 组件。

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 了

// 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 的数据导致复用性变差。

我们知道,redux 不正是提供了治理共享状态的能力嘛,我们只要通过 redux 来治理 context 就可以啦,第一个问题就可以解决了。

 

Provider 组件

React-Redux 提供 Provider 组件,利用了 react 的 context 特征,将 store 放在了 context 内里,使得该组件下面的所有组件都能直接接见到 store。大致实现如下:

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 这个高阶组件来实现展示组件和容器组件的毗邻的。

 

原创文章,作者:28rg新闻网,如若转载,请注明出处:https://www.28rg.com/archives/1102.html