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)
posted @ 2020-03-18 12:00  Ever-Lose  阅读(1182)  评论(0编辑  收藏  举报