react-redux 是一個鏈接react和redux的庫, 方便把redux狀態庫在react中使用。javascript
先讓咱們來個裸的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
HelloWorld 已經向咱們展現了當react把狀態交給redux的處理方式。 可是這裏有這樣的問題:git
- redux侵入了每個組件,每一個組件都要引入store
- 每一個組件和redux交互的邏輯,就是constructor裏面監聽, 取對應的state。。。諸多邏輯沒法方便複用
讓咱們用react-redux的思路來思考這個問題。 react-redux須要作什麼呢?github
因此: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
前面提到的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