React-redux: React.js 和 Redux 架構的結合

經過 Redux架構理解 咱們瞭解到 Redux 架構的 storeactionreducers 這些基本概念和工做流程。咱們也知道了 Redux 這種架構模式能夠和其餘的前端庫組合使用,而 React-redux 正是把 Redux 這種架構模式和 React.js 結合起來的一個庫。前端

Context

React 應用中,數據是經過 props 屬性自上而下進行傳遞的。若是咱們應用中的有不少組件須要共用同一個數據狀態,能夠經過狀態提高的思路,將共同狀態提高到它們的公共父組件上面。可是咱們知道這樣作是很是繁瑣的,並且代碼也是難以維護的。這時會考慮使用 ContextContext 提供了一個無需爲每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法。也就是說在一個組件若是設置了 context,那麼它的子組件均可以直接訪問到裏面的內容,而不用經過中間組件逐級傳遞,就像一個全局變量同樣。react

App -> Toolbar -> ThemedButton 使用 props 屬性傳遞 themeToolbar 做爲中間組件將 themeApp 組件 傳遞給 ThemedButton 組件。redux

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,就能夠避免經過中間元素傳遞 propssegmentfault

// 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 組件,利用了 reactcontext 特性,將 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 組件做爲根組件將咱們的應用包裹起來,那麼整個應用的組件均可以訪問到裏面的數據了app

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 爲了解決這個問題,將全部組件分紅兩大類:展現組件和容器組件。dom

展現組件
展現組件有幾個特徵ide

  1. 組件只負責 UI 的展現,沒有任何業務邏輯
  2. 組件沒有狀態,即不使用 this.state
  3. 組件的數據只由 props 決定
  4. 組件不使用任何 ReduxAPI

展現組件就和純函數同樣,返回結果只依賴於它的參數,而且在執行過程裏面沒有反作用,讓人以爲很是的靠譜,能夠放心的使用。

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. 使用 ReduxAPI

你能夠直接使用 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 處理後變成了容器組件,最後把它導出成模塊。這個容器組件沒有定義任何的業務邏輯,全部不能作任何事情。咱們能夠經過 mapStateToPropsmapDispatchToProps 來定義咱們的業務邏輯。

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 這個高階組件來實現展現組件和容器組件的鏈接的。

更多精彩內容,歡迎關注微信公衆號~

相關文章
相關標籤/搜索