相信不少前端開發者都據說或使用過react-redux,我曾寫過一篇關於快速理解redux的文章,雖然說是快速理解,但實際上更應該叫作複習redux吧。本文也只是講述react-redux的思想及原理,對於細節實現則不贅述。javascript
create-react-app
新建一個react項目,而後安裝第三方庫cnpm install --save prop-types
咱們先構建store,而且把它放在Index組件的context裏面,那樣Index如下的全部組件均可以使用store了。前端
class Index extends Component { static childContextTypes = { store: PropTypes.object } getChildContext () { return { store } } render () { return ( <div> <Header /> <Content /> </div> ) } }
此時咱們點擊按鈕後,store裏的數據確實改變了,但是頁面卻沒有改變,爲什麼?java
代碼以下,僅以ThemeSwitch爲例,其餘文件相似react
class ThemeSwitch extends Component { static contextTypes = { store: PropTypes.object } constructor () { super() this.state = { themeColor: '' } } componentWillMount () { const { store } = this.context this._updateThemeColor() store.subscribe(() => this._updateThemeColor()) } _updateThemeColor () { const { store } = this.context const state = store.getState() this.setState({ themeColor: state.themeColor }) } // dispatch action 去改變顏色 handleSwitchColor (color) { const { store } = this.context store.dispatch({ type: 'CHANGE_COLOR', themeColor: color }) } render () { return ( <div> <button style={{ color: this.state.themeColor }} onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button> <button style={{ color: this.state.themeColor }} onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button> </div> ) } }
到這裏,咱們已經把react-redux的骨架搭建起來了git
咱們觀察剛剛寫的組件,他們存在兩個問題npm
有大量重複邏輯redux
對context依賴過強,可複用性太低segmentfault
咱們須要高階組件幫助咱們從 context 取數據,咱們也須要寫 Dumb(純) 組件幫助咱們提升組件的複用性。因此咱們儘可能多地寫 Dumb 組件,而後用高階組件把它們包裝一層,高階組件和 context 打交道,把裏面數據取出來經過 props 傳給 Dumb 組件。app
這個高階組件被其銘文Connect,由於他能夠把 Dunb組件 和 context數據 connect 起來less
import React, { Component } from 'react' import PropTypes from 'prop-types' export const connect = (mapStateToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } render () { const { store } = this.context let stateProps = mapStateToProps(store.getState()) // {...stateProps} 意思是把這個對象裏面的屬性所有經過 `props` 方式傳遞進去 return <WrappedComponent {...stateProps} /> } } return Connect }
咱們將新建一個react-redux文件,將Connect放進去
咱們在定義 mapStateToProps函數 使它接收某參數,返回相應的數據。由於不一樣的組件須要store中不一樣的數據
import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from './react-redux' class Header extends Component { static propTypes = { themeColor: PropTypes.string } render () { return ( <h1 style={{ color: this.props.themeColor }}>React.js 小書</h1> ) } } const mapStateToProps = (state) => { return { themeColor: state.themeColor } } Header = connect(mapStateToProps)(Header) export default Header
此時,Header 刪掉了大部分關於 context 的代碼,它除了 props 什麼也不依賴,它是一個 Pure Component,而後經過 connect 取得數據。咱們不須要知道 connect 是怎麼和 context 打交道的,只要傳一個 mapStateToProps 告訴它應該怎麼取數據就能夠了。
此時,Connect還未能監聽渲染,咱們須要在Connect中建立渲染函數而且在componentWillMount中添加渲染函數
export const connect = (mapStateToProps) => (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(store.getState(), this.props) // 額外傳入 props,讓獲取數據更加靈活方便 this.setState({ allProps: { // 整合普通的 props 和從 state 生成的 props ...stateProps, ...this.props } }) } render () { return <WrappedComponent {...this.state.allProps} /> } } return Connect }
如今已經很不錯了,Header.js 和 Content.js 的代碼都大大減小了,而且這兩個組件 connect 以前都是 Dumb 組件。接下來會繼續重構 ThemeSwitch。
到目前爲止,咱們每次在更改數據時,都要用過store.dispatch修改。
爲了使組件更 Dunb(純) 咱們對Connect和ThemeSwitch增長一個mapDispatchToProps 函數,使ThemeSwitch組件擺脫對store.dispatch的依賴
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) : {} // 防止 mapStateToProps 沒有傳入 let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : {} // 防止 mapDispatchToProps 沒有傳入 this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render () { return <WrappedComponent {...this.state.allProps} /> } } return Connect }
import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from './react-redux' class ThemeSwitch extends Component { static propTypes = { themeColor: PropTypes.string, onSwitchColor: PropTypes.func } handleSwitchColor (color) { if (this.props.onSwitchColor) { this.props.onSwitchColor(color) } } render () { return ( <div> <button style={{ color: this.props.themeColor }} onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button> <button style={{ color: this.props.themeColor }} onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button> </div> ) } } const mapStateToProps = (state) => { return { themeColor: state.themeColor } } const mapDispatchToProps = (dispatch) => { return { onSwitchColor: (color) => { dispatch({ type: 'CHANGE_COLOR', themeColor: color }) } } } ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch) export default ThemeSwitch
光看 ThemeSwitch 內部,是很是清爽乾淨的,只依賴外界傳進來的 themeColor 和 onSwitchColor。可是 ThemeSwitch 內部並不知道這兩個參數其實都是咱們去 store 裏面取的,它是 Dumb 的。
咱們要把全部和context有關的東西都分離出去,如今只有Index組件是被污染的
因此,咱們在react-redux中新增Provider類,讓它包裹成爲Index的高階函數,包裹index
使組件結構圖以下
代碼以下
export class Provider extends Component { static propTypes = { store: PropTypes.object, children: PropTypes.any } static childContextTypes = { store: PropTypes.object } getChildContext () { return { store: this.props.store } } render () { return ( <div>{this.props.children}</div> ) } }
... // 頭部引入 Provider import { Provider } from './react-redux' ... // 刪除 Index 裏面全部關於 context 的代碼 class Index extends Component { render () { return ( <div> <Header /> <Content /> </div> ) } } // 把 Provider 做爲組件樹的根節點 ReactDOM.render( <Provider store={store}> <Index /> </Provider>, document.getElementById('root') )
此時,咱們就把全部關於 context 的代碼從組件裏面刪除了。
redux的思想就是有條理地,有規則地修改共享數據。而react裏恰好有 context 這個東西能夠被某組件如下的全部組件共享,爲了在react中優雅的修改context,react-redux就誕生了。它經過高階函數(Connect),純函數(mapStateToProps, mapDispatchToProps) 使咱們在編寫組件時徹底不用接觸context相關內容,只經過Connect 將 Dumb組件 和 Context數據 鏈接起來便可。
本文若是有錯,歡迎指出