react-redux 實現原理


摘自:https://juejin.im/post/5def4831e51d45584b585000?utm_source=gold_browser_extension

redux 簡單實現,一個簡單的訂閱發布機制。

// reducer.js
const initialState = {
  count: 0
}
export function reducer(state = initialState, action) {
  switch (action.type) {
    case 'plus':
      return {
        ...state,
        count: state.count + 1
      }
    case 'subtract':
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return initialState
  }
}
// createStore.js
import { reducer } from './reducer'
export const createStore = (reducer) => {
  let currentState = {}
  let observers = []             //觀察者隊列        
  function getState() {
    return currentState
  }
  function dispatch(action) {
    currentState = reducer(currentState, action)
    observers.forEach(fn => fn())
  }
  function subscribe(fn) {
    observers.push(fn)
  }
  dispatch({ type: '@@REDUX_INIT' })  //初始化store數據        
  return { getState, subscribe, dispatch }
}

const store = createStore(reducer)       //創建store
store.subscribe(() => { console.log('組件1收到store的通知') })
store.subscribe(() => { console.log('組件2收到store的通知') })
store.dispatch({ type: 'plus' })         //執行dispatch,觸發store的通知

react-redux

若用 redux,一個組件如果想從store存取公用狀態,需要進行四步操作:

  • import 引入 store
  • getState 獲取狀態
  • dispatch 修改狀態
  • subscribe 訂閱更新

代碼相對冗余,我們想要合並一些重復的操作,而 react-redux 就提供了一種合並操作的方案:react-redux 提供 Provider 和 connect 兩個 API

  • Provider 將 store 放進 this.context 里,省去了 import 這一步
  • connect 將 getState、dispatch 合並進了this.props,並自動訂閱更新,簡化了另外三步。

Provider

import React from 'react'
import PropTypes from 'prop-types'
export class Provider extends React.Component {
  // 需要聲明靜態屬性childContextTypes來指定context對象的屬性,是context的固定寫法  
  static childContextTypes = {
    store: PropTypes.object
  }

  // 實現getChildContext方法,返回context對象,也是固定寫法  
  getChildContext() {
    return { store: this.store }
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }

  // 渲染被Provider包裹的組件  
  render() {
    return this.props.children
  }
}

connect

connect的調用方式:connect(mapStateToProps, mapDispatchToProps)(App)

export function connect(mapStateToProps, mapDispatchToProps) {
  return function (Component) {
    class Connect extends React.Component {
      componentDidMount() {
        //從context獲取store並訂閱更新          
        this.context.store.subscribe(this.handleStoreChange.bind(this));
      }
      handleStoreChange() {
        // 觸發更新          
        // 觸發的方法有多種,這里為了簡潔起見,直接forceUpdate強制更新,讀者也可以通過setState來觸發子組件更新          
        this.forceUpdate()
      }
      render() {
        return (
          <Component
            // 傳入該組件的props,需要由connect這個高階組件原樣傳回原組件              
            {...this.props}
            // 根據mapStateToProps把state掛到this.props上              
            {...mapStateToProps(this.context.store.getState())}
            // 根據mapDispatchToProps把dispatch(action)掛到this.props上              
            {...mapDispatchToProps(this.context.store.dispatch)}
          />
        )
      }
    }
    //接收context的固定寫法      
    Connect.contextTypes = {
      store: PropTypes.object
    }
    return Connect
  }
}

這種實現其實更便於實現裝飾器模式。

完整例子

寫一個計數器,點擊按鈕就派發一個 dispatch,讓 store 中的 count 加一,頁面上顯示這個 count

// store.js
export const createStore = (reducer) => {
  let currentState = {}
  let observers = []             //觀察者隊列    
  function getState() {
    return currentState
  }
  function dispatch(action) {
    currentState = reducer(currentState, action)
    observers.forEach(fn => fn())
  }
  function subscribe(fn) {
    observers.push(fn)
  }
  dispatch({ type: '@@REDUX_INIT' }) //初始化store數據    
  return { getState, subscribe, dispatch }
}
//reducer.js
const initialState = {
  count: 0
}

export function reducer(state = initialState, action) {
  switch (action.type) {
    case 'plus':
      return {
        ...state,
        count: state.count + 1
      }
    case 'subtract':
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return initialState
  }
}
//react-redux.js
//react-redux.js
import React from 'react'
import PropTypes from 'prop-types'
export class Provider extends React.Component {
  // 需要聲明靜態屬性childContextTypes來指定context對象的屬性,是context的固定寫法  
  static childContextTypes = {
    store: PropTypes.object
  }

  // 實現getChildContext方法,返回context對象,也是固定寫法  
  getChildContext() {
    return { store: this.store }
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }

  // 渲染被Provider包裹的組件  
  render() {
    return this.props.children
  }
}

export function connect(mapStateToProps, mapDispatchToProps) {
  return function (Component) {
    class Connect extends React.Component {
      componentDidMount() {          //從context獲取store並訂閱更新          
        this.context.store.subscribe(this.handleStoreChange.bind(this));
      }
      handleStoreChange() {
        // 觸發更新          
        // 觸發的方法有多種,這里為了簡潔起見,直接forceUpdate強制更新,讀者也可以通過setState來觸發子組件更新          
        this.forceUpdate()
      }
      render() {
        return (
          <Component
            // 傳入該組件的props,需要由connect這個高階組件原樣傳回原組件              
            {...this.props}
            // 根據mapStateToProps把state掛到this.props上              
            {...mapStateToProps(this.context.store.getState())}
            // 根據mapDispatchToProps把dispatch(action)掛到this.props上              
            {...mapDispatchToProps(this.context.store.dispatch)}
          />
        )
      }
    }

    //接收context的固定寫法      
    Connect.contextTypes = {
      store: PropTypes.object
    }
    return Connect
  }
}
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { Provider } from './react-redux'
import { createStore } from './store'
import { reducer } from './reducer'

ReactDOM.render(
  <Provider store={createStore(reducer)}>
    <App />
  </Provider>,
  document.getElementById('root')
);
//App.js
import React from 'react'
import { connect } from './react-redux'

const addCountAction = {
  type: 'plus'
}

const mapStateToProps = state => {
  return {
    count: state.count
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addCount: () => {
      dispatch(addCountAction)
    }
  }
}

class App extends React.Component {
  render() {
    return (
      <div className="App">
        {this.props.count}
        <button onClick={() => this.props.addCount()}>增加</button>
      </div>
    );
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM