1. 程式人生 > >react深入 - 手寫實現react-redux api

react深入 - 手寫實現react-redux api

簡介:簡單實現react-redux基礎api

react-redux api回顧

<Provider store>
把store放在context裡,所有子元件可以直接拿到store資料


使元件層級中的 connect() 方法都能夠獲得 Redux store
根元件應該巢狀在 &lt;Provider&gt; 中

ReactDOM.render(
  &lt;Provider store={store}&gt;
    &lt;MyRootComponent /&gt;
  &lt;/Provider&gt;,
  rootEl
)

ReactDOM.render(
  &lt;Provider store={store}&gt;
    &lt;Router history={history}&gt;
      &lt;Route path="/" component={App}&gt;
        &lt;Route path="foo" component={Foo}/&gt;
        &lt;Route path="bar" component={Bar}/&gt;
      &lt;/Route&gt;
    &lt;/Router&gt;
  &lt;/Provider&gt;,
  document.getElementById('root')
)

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
連結元件和資料,把redux中的資料放到元件的屬性中

[mapStateToProps(state, [ownProps]): stateProps] (Function)


如果定義該引數,元件將會監聽 Redux store 的變化。任何時候,只要 Redux store 發生改變,mapStateToProps 函式就會被呼叫。該回調函式必須返回一個純物件,這個物件會與元件的 props 合併

如果你省略了這個引數,你的元件將不會監聽 Redux store
ownProps,則該引數的值為傳遞到元件的 props,而且只要元件接收到新的 props,mapStateToProps 也會被呼叫,被重新計算
mapStateToProps 函式的第一個引數是整個Redux store的state,它返回一個要作為 props 傳遞的物件。它通常被稱作 selector (選擇器)。 可以使用reselect去有效地組合選擇器和計算衍生資料.
注意:如果定義一個包含強制性引數函式(這個函式的長度為 1)時,ownProps 不會傳到 mapStateToProps


const mapStateToProps = (state, ownProps) =&gt; {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function)


Object: 它的每個鍵名也是對應 UI 元件的同名引數,鍵值應該是一個函式(action creator),會被當作 Action creator ,返回的 Action 會由 Redux 自動發出,

Function: 會得到dispatch和ownProps(容器元件的props物件)兩個引數(此時可能用到Redux 的輔助函式 bindActionCreators())

省略這個 mapDispatchToProps 引數,預設情況下,dispatch 會注入到你的元件 props 中,你可以this.props.dispatch呼叫
指定了該回調函式中第二個引數 ownProps,該引數的值為傳遞到元件的 props,而且只要元件接收到新 props,mapDispatchToProps 也會被呼叫

eg:


connect(mapStateToProps, {
  hideAdPanel,
  pushAdData,
})(AdPanel)

function mapDispatchToProps(dispatch) {
  return {
    todoActions: bindActionCreators(todoActionCreators, dispatch),
    counterActions: bindActionCreators(counterActionCreators, dispatch)
  }
}

知識點補充 - React高階元件(Higher-Order Components)

高階元件就是一個函式,且該函式接受一個元件作為引數,並返回一個新的元件
高階元件就是一個沒有副作用的純函式

使用場景:兩個元件大部分程式碼都是重複的+且更好的封閉性,不需要關注資料的獲取


import React, {Component} from 'react'

class Welcome extends Component {
    constructor(props) {
        super(props);
        this.state = {
            username: ''
        }
    }

    componentWillMount() {
        let username = localStorage.getItem('username');
        this.setState({
            username: username
        })
    }

    render() {
        return (
            &lt;div&gt;welcome {this.state.username}&lt;/div&gt;
        )
    }
}

export default Welcome;

import React, {Component} from 'react'

class Goodbye extends Component {
    constructor(props) {
        super(props);
        this.state = {
            username: ''
        }
    }

    componentWillMount() {
        let username = localStorage.getItem('username');
        this.setState({
            username: username
        })
    }

    render() {
        return (
            &lt;div&gt;goodbye {this.state.username}&lt;/div&gt;
        )
    }
}

export default Goodbye;

welcome和goodbye元件相似,只能獲取的資料不一樣,用高階元件,提取公共部分


import React, {Component} from 'react'

export default (WrappedComponent) =&gt; {
    class NewComponent extends Component {
        constructor() {
            super();
            this.state = {
                username: ''
            }
        }

        componentWillMount() {
            let username = localStorage.getItem('username');
            this.setState({
                username: username
            })
        }

        render() {
            return &lt;WrappedComponent username={this.state.username}/&gt;
        }
    }

    return NewComponent
}

簡化welcome和goodbye

import React, {Component} from 'react';
import wrapWithUsername from 'wrapWithUsername';

class Welcome extends Component {

    render() {
        return (
            &lt;div&gt;welcome {this.props.username}&lt;/div&gt;
        )
    }
}

Welcome = wrapWithUsername(Welcome);

export default Welcome;

此時,理解react-redux 的connect就好理解了


ConnectedComment = connect(mapStateToProps, mapDispatchToProps)(Component);

// connect是一個返回函式的函式(就是個高階函式)
const enhance = connect(mapStateToProps, mapDispatchToProps);
// 返回的函式就是一個高階元件,該高階元件返回一個與Redux store
// 關聯起來的新元件
const ConnectedComment = enhance(Component);

provider實現


import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import { counter } from './index.redux'
// import { Provider } from 'react-redux'
// 換成自己的Provider實現
import { Provider } from './self-react-redux'
import App from './App'

const store = createStore(counter, compose(
  applyMiddleware(thunk),
  window.devToolsExtension ? window.devToolsExtension() : f =&gt; f
))
ReactDOM.render(
  (
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;
  ),
  document.getElementById('root'))

./self-react-redux


import React from 'react'
import PropTypes from 'prop-types'

export function connect(){
}

class Provider extends React.Component{
  static childContextTypes = {
    store: PropTypes.object
  }
  getChildContext() {
    return { store: this.store }
  }
  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }
  render(){
    return this.props.children
  }
}

connect實現

demo


import React from 'react'
// import { connect } from 'react-redux'
import { connect } from './self-react-redux'
import { addGun, removeGun, addGunAsync } from './index.redux'

@connect(
    // 你要state什麼屬性放到props裡
    state=&gt;({num:state.counter}),
    // 你要什麼方法,放到props裡,自動dispatch
    { addGun, removeGun, addGunAsync }
)
class App extends React.Component{
    render(){
        return (
            &lt;div&gt;
                &lt;h1&gt;現在有機槍{this.props.num}把&lt;/h1&gt;
                &lt;button onClick={this.props.addGun}&gt;申請武器&lt;/button&gt;                
                &lt;button onClick={this.props.removeGun}&gt;上交武器&lt;/button&gt;                
                &lt;button onClick={this.props.addGunAsync}&gt;拖兩天再給&lt;/button&gt;                
            &lt;/div&gt;
        )
    }
}


export default App

./self-react-redux.js




// 高階元件的寫法
export function connect(maoStateToProps, mapStateToProps) {
  return function(WrapComponent) {
    return class ConnectComponent extends React.Component{
 
    }
  }
}


import React from 'react'
import PropTypes from 'prop-types'
import { bindActionCreator } from './self-redux'
// 使用簡寫形式
// connect負責連結元件,給到redux裡的資料放在元件的屬性裡
// 1. 負責接收一個元件,把state裡的一些資料放進去,返回一個元件
// 2. 資料變化的時候,能通知元件
export const connect = (
  mapStateToProps = state =&gt; state,
  mapDispatchToProps ={}
) =&gt; (WrapComponent) =&gt; {
  return class ConnectComponent extends React.Component {
    static contextTypes = {
      store: PropTypes.object
    }
    constructor(props, context){
      super(props, context)
      this.state = {
        props: {}
      }
    } 
    // 2 實現了mapStateToProps
    componentDidMount() {
      const { store } = this.context
      store.subscribe(() =&gt; this.update())
      this.update()
    }
    update() {
      const { store } = this.context
      // store.getState()這就是為什麼mapStateToProps函式裡面能拿到state 
      const stateProps = mapStateToProps(store.getState())
      // 方法不能直接給,因為需要dispatch
        /**
          function addGun() {
            return { type: ADD_GUN }
          }
          直接執行addGun() 毫無意義
          要 addGun = () =&gt; store.dispatch(addGun()) 才有意義,其實就是把actionCreator包了一層
          bindActionCreators在手寫redux api實現了
         */
      const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
      // 注意state的順序問題會覆蓋
      this.setState({
        props: {
          ...this.state.props,
          ...stateProps,
          ...dispatchProps,
        }
      })
    }
    // 1
    render() {
      return &lt;WrapComponent {...this.state.props}&gt;&lt;/WrapComponent&gt;
    }
  }
}

./self-redux.js


// creators: {addGun, removeGun, addGunAsync}
// creators[v]:addGun(引數)
// 返回:(引數) =&gt; dispatch(addGun(引數))
 function bindActionCreator(creator, dispatch) {
   return (...args) =&gt; dispatch(creator(...args))
 }

 export function bindActionCreators(creators, dispatch) {
   let bound = {}
   Object.keys(creators).forEach( v =&gt; {
     let creator = creators[v]
     bound[v] = bindActionCreator(creator, dispatch)
   })
   return bound
 }
// 簡寫
 export function bindActionCreators(creators, dispatch) {
  return Object.keys(creators).reduce((ret, item) =&gt; {
     ret[item] =  bindActionCreator(creators[item], dispatch)
     return ret
   }, {})
 }

原文地址:https://segmentfault.com/a/1190000016759675