經過Redux 架構理解咱們瞭解到 Redux 架構的 store、action、reducers 這些基本概念和工做流程。咱們也知道了 Redux 這種架構模式能夠和其餘的前端庫組合使用,而 React-redux 正是把 Redux 這種架構模式和 React.js 結合起來的一個庫。html
在 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
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')
)
還記得咱們的第二個問題嗎?組件由於 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()
自動生成。
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 這個高階組件來實現展現組件和容器組件的鏈接的。