從0實現一個tiny react-redux

從0實現一個tiny react-redux


react-redux 是一個鏈接react和redux的庫, 方便把redux狀態庫在react中使用。javascript

不用react-redux

先讓咱們來個裸的redux 和react結合的例子試試手
樣例store(本文都會以這個store爲例)html

import { createStore, applyMiddleware } from 'redux'
import logger from 'redux-logger'

function reduer(state, action) {
    switch (action.type) {
        case 'add': {
            return {
                ...state,
                count: state.count + 1
            }
        }
        default: {
            return state
        }
    }
}

const store = createStore(reduer, { count: 0 }, applyMiddleware(logger))

這個store接受一個 type爲add的action。
假設如今有一個 組件HelloWorld 監聽展現這個 store的count值。java

class HelloWorld extends Component {
    constructor(props) {
        super(props)
        store.subscribe(() => {
            this.setState({})
        })
    }
    render() {
        const state = store.getState()
        return (
            <div
                onClick={e => store.dispatch({type: 'add'})}
                style={{color: 'red'}}
            >
                {state.count}
            </div>
        )
    }
}
ReactDOM.render(<HelloWorld/>, document.getElementById("root"))

在線地址。 打開f12,當咱們點擊的時候 會發現有redux日誌輸出,而且渲染的值和store保持了一致。react

connect

HelloWorld 已經向咱們展現了當react把狀態交給redux的處理方式。 可是這裏有這樣的問題:git

  • redux侵入了每個組件,每一個組件都要引入store
  • 每一個組件和redux交互的邏輯,就是constructor裏面監聽, 取對應的state。。。諸多邏輯沒法方便複用

讓咱們用react-redux的思路來思考這個問題。 react-redux須要作什麼呢?github

  1. 組件要監聽store
  2. 從store取出對應的數據,放到組件的props上

因此:npm

class HelloWorld extends Component {
    render() {
        return (
            <div>
                <div onClick={e => store.dispatch({type: 'add'})} >{this.props.count}</div>
                <div onClick={e => store.dispatch({type: 'delete'})} >xxx</div>
            </div>
        )
    }
}


function reduxHoc(WrappedComponent, mapStateToProps) {

    return class Rh extends Component {
        constructor(props) {
            super(props)

            this.sub = store.subscribe(() => {
                this.setState({})
            })

            this._beforeProps = mapStateToProps(store.getState(), props)
        }

        componentWillUnmount() {
            this.sub()
        }

        shouldComponentUpdate() {
            const newProps = mapStateToProps(store.getState(), this.props)
            if (this._beforeProps === newProps) {
                return false
            }
            this._beforeProps = newProps
            return true
        }

        render() {
            return <WrappedComponent {...this.props} {...this._beforeProps} />
        }
    }
}

HelloWorld = reduxHoc(HelloWorld, state => state)

這裏的reduxHoc方法返回了一個React組件類,類比與「高階函數」的概念,這裏叫作「高價組件」。高階組件詳解。reduxHoc 接受2個參數WrappedComponent, mapStateToProps,分別是要被包裝的組件(這裏是HelloWorld)以及 把state映射到props的mapStateToProps。 返回的Rh組件此刻已經監聽了store的變化,而且會把從store映射過來的props 傳遞給WrappedComponent組件。
react-redux的connect 方法不只接受mapStateToProps,還接受mapDispatchToProps。更近一步,把reduxHoc改成connect吧redux

function connect(mapStateToProps, mapDispatchToProps) {
    return function (WrappedComponent) {
        return class Hoc extends Component {
           constructor(props, context) {
                super(props)
                this.unsubscribe = store.subscribe(() => {
                    this.setState({})
                })

                this.memorizeProps = this.calculateProps()
            }

            calculateProps() {
                const o1 = mapStateToProps(store.getState(), this.props)

                let o2 = null
                if(mapDispatchToProps) {
                    o2 = mapDispatchToProps(store.dispatch, this.props)
                } else {
                    o2 = {
                        dispatch: store.dispatch
                    }
                }

                return {
                    ...o1,
                    ...o2
                }
            }

            componentWillUnmount() {
                this.unsubscribe()
                this.unsubscribe = null
            }

            shouldComponentUpdate() {
                const nextProps = this.calculateProps()

                const isEqual = shallowEqual(nextProps, this.memorizeProps)
                if (isEqual) {
                    return false
                } else {
                    this.memorizeProps = nextProps
                    return true
                }
            }


            render() {
                return (
                    <WrappedComponent {...this.props} {...this.memorizeProps} />
                )
            }
        }

    }
}

function shallowEqual(objA, objB) {
  if (objA === objB) {
    return true;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  const hasOwn = Object.prototype.hasOwnProperty;
  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwn.call(objB, keysA[i]) ||
        objA[keysA[i]] !== objB[keysA[i]]) {
      return false;
    }
  }

  return true;
}

這裏的connect使用方式和 react-redux的connect是一致的了(react-redux的connect其實接受4個參數)。有一點須要注意:reduxHoc和connect的shouldComponentUpdate 都只是 淺比較了引用, 這是由於redux庫是無反作用的,因此來自redux庫的對象只要引用相同就必定徹底沒有改變, 能夠不用再次渲染。反過來講:若是store裏面的值改變了,可是頁面沒有從新渲染,說明redux的邏輯寫的有問題。app

Provider

前面提到的connect方法, 雖然如今store沒有侵入具體業務組件, 可是connect方法裏面卻用到了store。而咱們在使用react-redux這個庫的時候, 可能壓根兒就不知道store在哪裏。 或者咱們須要把store傳給這個庫,來生成connect函數。
換一個角度, react不只提供了props來傳遞數據。 還提供了context, context傳遞數據是透傳的形式, 關於conext的詳細介紹請看。 在最外層的根組件提供store, 而後全部的組件均可以經過context獲取store。ide

class Provider extends Component {

    static childContextTypes =  {
        store: PropTypes.object
    }

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

    render() {
        return Children.only(this.props.children)
    }
}

對應的connect寫法

function connect(mapStateToProps, mapDispatchToProps) {

    return function (WrappedComponent) {
        return class Hoc extends Component {
            static contextTypes = {
                store: PropTypes.object
            }

            constructor(props, context) {
                super(props)

                this.store = props.store || context.store

                this.unsubscribe = this.store.subscribe(() => {
                    this.setState({})
                })

                this.memorizeProps = this.calculateProps()
            }

            calculateProps() {
                const o1 = mapStateToProps(this.store.getState(), this.props)

                let o2 = null
                if(mapDispatchToProps) {
                    o2 = mapDispatchToProps(this.store.dispatch, this.props)
                } else {
                    o2 = {
                        dispatch: this.store.dispatch
                    }
                }

                return {
                    ...o1,
                    ...o2
                }
            }

            componentWillUnmount() {
                this.unsubscribe()
                this.unsubscribe = null
            }

            shouldComponentUpdate() {
                const nextProps = this.calculateProps()

                const isEqual = shallowEqual(nextProps, this.memorizeProps)
                if (isEqual) {
                    return false
                } else {
                    this.memorizeProps = nextProps
                    return true
                }
            }


            render() {
                return (
                    <WrappedComponent {...this.props} {...this.memorizeProps} />
                )
            }
        }

    }
}

其餘

代碼託管在git
安裝:

npm install treact-tredux
相關文章
相關標籤/搜索