帶你實現 react-redux

前言

以前咱們實現了 redux 的功能,此次咱們來實現一下配合 redux 開發中常常會用到的一個庫—— react-redux。本文不會詳細介紹 react-redux 的使用,另外須要瞭解 Context API, 再看此文就很容易理解了。能夠看看我以前寫的幾篇文章javascript

react-redux 基本使用

用個簡單的加減數字做例子, 把代碼貼出來:java

  • redux.js
import { createStore } from 'redux'

// actions
const ADD_NUM = 'ADD_NUM'
const DESC_NUM = 'DESC_NUM'

// action creators
export function addNumAction() {
  return {
    type: ADD_NUM,
  }
}

export function reduceNumAction() {
  return {
    type: DESC_NUM,
  }
}

const defaultState = {
  num: 0,
}

// reducer
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case ADD_NUM:
      return { num: state.num + 1 }
    case DESC_NUM:
      return { num: state.num - 1 }
    default:
      return state
  }
}

const store = createStore(reducer)

export default store
複製代碼

index.jsreact

import React from 'react'
import ReactDOM from 'react-dom'
import Demo from './Demo'
import { Provider } from 'react-redux'
import store from './redux.js'

ReactDOM.render(
  <Provider store={store}> <Demo /> </Provider>,
  document.getElementById('root')
)
複製代碼

Demo.jsredux

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addNumAction, reduceNumAction } from './redux.js'

class Demo extends Component {
  render() {
    console.log(this.props)
    return (
      <div> <p>{this.props.num}</p> <button onClick={this.props.addNum}>增長1</button> <button onClick={this.props.reduceNum}>減小1</button> </div>
    )
  }
}

const mapStateToProps = state => {
  return {
    num: state.num,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addNum() {
      const action = addNumAction()
      dispatch(action)
    },
    reduceNum() {
      const action = reduceNumAction()
      dispatch(action)
    },
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Demo)
複製代碼

就能夠實現 num 的增減:bash

其實一個簡單的 react-redux, 主要也就是實現 connect 和 Provider 的基本功能

  • connect:能夠把 state 和 dispatch 綁定到 react 組件,使得組件能夠訪問到 redux 的數據
  • Provider:提供的是一個頂層容器的做用,實現 store 的上下文傳遞

使用舊版 Context API 實現

實現Provider

首先咱們看它的用法,就知道它不是一個函數,而是一個組件:dom

<Provider store={store}>
    <Demo />
</Provider>
複製代碼

React 的 Context API 提供了一種經過組件樹傳遞數據的方法,無需在每一個級別手動傳遞 props 屬性。ide

Provider 的實現比較簡單,核心就是把 store 放到 context 裏面,全部的子元素能夠直接取到 store函數

export class Provider extends Component {
  static childContextTypes = {
    store: PropTypes.object
  }

  constructor(props) {
    super(props)
    this.store = props.store // 也就是 Provider 組件從外部傳入的 store
  }

  getChildContext() {
    return { store: this.store }
  }

  render() {
    return this.props.children // 中間包的組件傳入了 context,其他原封不動返回組件
  }
}
複製代碼

還有個地方你們知道就好,兩種寫法同樣的,對context type 的約束post

export class Provider extends Component {
  // 不用 static 來聲明...
}
Provider.childContextTypes = {
   store: PropTypes.object  
}
複製代碼

實現 connect

connect用法ui

export default connect(mapStateToProps, mapDispatchToProps)(Demo)
複製代碼

connect 是一個高階組件,就是以組件做爲參數,返回一個組件。

connect 負責鏈接組件,給到 redux 的數據放到組件的屬性裏

  1. 負責接收一個組件,把 state 的一些數據放進去,返回一個組件
  2. 數據變化的時候,可以通知組件(須要進行監聽)
export function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrapComponent) {
    return class ConnectComponent extends Component {
      static contextTypes = { 
        store: PropTypes.object,
      }

      constructor(props) {
        super(props)
        this.state = {
          props: {},
        }
      }

      componentDidMount() {
        const { store } = this.context
        store.subscribe(() => this.update()) // 監聽更新事件
        this.update()
      }

      update() {
        const { store } = this.context
        let stateToProps = mapStateToProps(store.getState()) // 傳入 redux 的 state
        let dispatchToProps = mapDispatchToProps(store.dispatch) // 傳入 redux 的 dispatch

        this.setState({
          props: {
            ...this.state.props,
            ...stateToProps,
            ...dispatchToProps,
          },
        })
      }

      render() {
        return <WrapComponent {...this.state.props} /> } } } } 複製代碼

這樣 connect 就實現了,但還有一個問題,像上面的例子,咱們其實能夠直接傳入 action creators, 而不用本身定義函數傳入 dispatch

export default connect(mapStateToProps, { addNumAction, reduceNumAction })(Demo)
複製代碼

調用的時候:

<button onClick={this.props.addNumAction}>增長1</button>
<button onClick={this.props.reduceNumAction}>減小1</button>
複製代碼

那它的 dispatch 哪裏來的,實際上是用了 redux 的 bindActionCreators 函數,在我介紹 redux 的文章有提到,它做用是將 actionCreator 轉化成 dispatch 形式,即

{ addNumAction }  =>  (...args) => dispatch(addNumAction(args))
複製代碼

因此咱們須要再更改 connect 函數,同時,此次咱們用箭頭函數的形式簡化代碼

import { bindActionCreators } from 'redux'

export const connect = (mapStateToProps, mapDispatchToProps) => WrapComponent => {
  return class ConnectComponent extends Component {
    static contextTypes = {
      store: PropTypes.object,
    }

    constructor(props) {
      super(props)
      this.state = {
        props: {},
      }
    }

    componentDidMount() {
      const { store } = this.context
      store.subscribe(() => this.update())
      this.update()
    }

    update() {
      const { store } = this.context
      let stateToProps = mapStateToProps(store.getState())
      let dispatchToProps
      if (typeof mapDispatchToProps === 'function') {
        dispatchToProps = mapDispatchToProps(store.dispatch)
      } else {
        // 傳遞了一個 actionCreator 對象過來
        dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch)
      }

      this.setState({
        props: {
          ...this.state.props,
          ...stateToProps,
          ...dispatchToProps,
        },
      })
    }

    render() {
      return <WrapComponent {...this.state.props} /> } } } 複製代碼

以上,咱們實現了最基本版的 react-redux,而後接下來,咱們用新版的 Context API 再寫一次

使用新版 Context API 實現

import React, { Component } from 'react'
import { bindActionCreators } from 'redux'

const StoreContext = React.createContext(null)

// 此次 Provider 採起更簡潔的形式寫
export class Provider extends Component {
  render() {
    return (
      <StoreContext.Provider value={this.props.store}>
        {this.props.children}
      </StoreContext.Provider>
    )
  }
}

export function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrapComponent) {
    class ConnectComponent extends Component {
      constructor(props) {
        super(props)
        this.state = {
          props: {},
        }
      }

      componentDidMount() {
        const { store } = this.props
        store.subscribe(() => this.update())
        this.update()
      }

      update() {
        const { store } = this.props
        let stateToProps = mapStateToProps(store.getState())
        let dispatchToProps
        if (typeof mapDispatchToProps === 'function') {
          dispatchToProps = mapDispatchToProps(store.dispatch)
        } else {
          // 傳遞了一個 actionCreator 對象過來
          dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch)
        }

        this.setState({
          props: {
            ...this.state.props,
            ...stateToProps,
            ...dispatchToProps,
          },
        })
      }

      render() {
        return <WrapComponent {...this.state.props} />
      }
    }
    
    return () => (
      <StoreContext.Consumer>
        {value => <ConnectComponent store={value} />}
      </StoreContext.Consumer>
    )
  }
}
複製代碼
相關文章
相關標籤/搜索