快速理解react-redux

react-redux react和redux的結合

簡述

相信不少前端開發者都據說或使用過react-redux,我曾寫過一篇關於快速理解redux的文章,雖然說是快速理解,但實際上更應該叫作複習redux吧。本文也只是講述react-redux的思想及原理,對於細節實現則不贅述。javascript


1、初始化工程

  • 咱們先create-react-app新建一個react項目,而後安裝第三方庫cnpm install --save prop-types
  • 咱們在src目錄下新建3個文件,Header.js、Content.js、ThemeSwitch.js
  • 組件結構是這樣的
    組建結構
  • 運行起來是這樣的
    圖片描述

2、結合context和store

  • 咱們先構建store,而且把它放在Index組件的context裏面,那樣Index如下的全部組件均可以使用store了。前端

    class Index extends Component {
      static childContextTypes = {
        store: PropTypes.object
      }
    
      getChildContext () {
        return { store }
      }
    
      render () {
        return (
          <div>
            <Header />
            <Content />
          </div>
        )
      }
    }
  • 咱們修改Header,Content,ThemeSwitch,定義一個函_updateThemeColor在componentWillMount中調用
  • 在該函數中獲取Index組件context裏的store,而且將store裏的colorState這隻爲本身文本的顏色
  • 咱們再修改ThemeSwitch,使按鈕綁定事件,點擊後執行dispatch修改store裏的state
  • 此時咱們點擊按鈕後,store裏的數據確實改變了,但是頁面卻沒有改變,爲什麼?java

    • 由於咱們忽略了subscribe,使得dispatch後_updateThemeColor函數並未執行
  • 咱們分別給 Header.js、Content.js、ThemeSwitch.js 的 componentWillMount 生命週期都加上監聽數據變化從新渲染的代碼
  • 代碼以下,僅以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


3、connect 和 mapStateToProps

咱們觀察剛剛寫的組件,他們存在兩個問題npm

  • 有大量重複邏輯redux

    • 使用Connect高階組件解決
  • 對context依賴過強,可複用性太低segmentfault

    • 使用mapStateToProps解決

使用高階組件

咱們須要高階組件幫助咱們從 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。


mapDispatchToProps

到目前爲止,咱們每次在更改數據時,都要用過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 的。


5、Provider

咱們要把全部和context有關的東西都分離出去,如今只有Index組件是被污染的
因此,咱們在react-redux中新增Provider類,讓它包裹成爲Index的高階函數,包裹index
使組件結構圖以下
Provider
代碼以下

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 的代碼從組件裏面刪除了。


6、react-redux總結

redux的思想就是有條理地,有規則地修改共享數據。而react裏恰好有 context 這個東西能夠被某組件如下的全部組件共享,爲了在react中優雅的修改context,react-redux就誕生了。它經過高階函數(Connect),純函數(mapStateToProps, mapDispatchToProps) 使咱們在編寫組件時徹底不用接觸context相關內容,只經過ConnectDumb組件Context數據 鏈接起來便可。


參考

完整代碼

make-react-redux

本文若是有錯,歡迎指出

相關文章
相關標籤/搜索