实现react版本的vuex

254 阅读2分钟

本文开发的reacx状态库,是为了加深对状态管理库的理解,目前尚未运用商业项目中, 仅供参考。

项目目录

其余才用create-react-app创建

+ src
   App.js
   HelloWorld.js
   index.js
   reatx.js
   store.js

reatx.js

react版本的vuex状态管理库

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

const isObject = (v) => v && typeof v === 'object'

class Reatx {
  constructor (options) {
    this.state = options.state
    this._actions = options.actions
    this._mutations = options.mutations
    this._listeners = []
  }
  subscribe (listener) {
    this._listeners.push(listener)
  }
  commit (type, payload) {
    this._mutations[type].call(this, this.state, payload)
    this._listeners.forEach(listener => {
      listener()
    })
  }
  dispatch (type) {
    this._actions[type](this)
  }
}

export 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
  }
}

export const connect = (stateObj) => {
  return (App) => {
    return class WrapperApp extends React.Component {
      static contextTypes = {
        store: PropTypes.object
      }
      constructor (props, context) {
        super(props, context)
        this.state = {
          store: context.store
        }
        this.unsubscribe = context.store.subscribe(() => {
          this.setState({})
        })
        this._mappedProps = this.getProps()
      }
      componentWillUnmount () {
        this.unsubscribe()
      }
      shouldComponentUpdate () {
        let preProps = this._mappedProps
        let nextProps = this.getProps()
        console.log(preProps, nextProps)
        if (isObject(preProps) && isObject(nextProps)) {
          let nextKeys = Object.keys(nextProps) 
          for (let i = 0, len = nextKeys.length; i < len; i++) {
            let nextK = nextKeys[i]
            if (nextProps[nextK] !== preProps[nextK]) {
              this._mappedProps = nextProps
              return true
            }
          }
        }
        console.log(false)
        return false
      }
      getProps () {
        let _mappedProps = {}
        let copy = JSON.parse(JSON.stringify(this.state.store.state))
        Object.keys(stateObj).forEach(k => {
          _mappedProps[k] = stateObj[k].call(this, copy)
        })
        return _mappedProps
      }
      render(h) {
        return (
          <App { ...this.state } { ...this._mappedProps }/>
        )
      }
    }
  }
}

export default Reatx

webpack打包入口 index.js 。

  1. Vue 通过提供 beforeCreate 的 mixin 勾子,可以将store挂载在每个组件实例的 $store 属性中。
  2. react不提供这个勾子。而是通过封装Provider组件,将store存储在context中,从而使得每个组件可以获得仓库的引用。
import React from 'react'
import { render } from 'react-dom'
import App from './App'
import store from './store'
import { Provider } from './reatx'

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

根组件App.js

import React from 'react'
import HelloWorld from './HelloWorld'
class App extends React.Component {
  render () {
    return <HelloWorld />
  }
}
export default App

页面组件HelloWorld.js

  1. Vue提供computed选项,通过mapState,将store中的state映射到组件的实例中。
  2. React更加灵活,提供了高阶组件,设计一个connect高阶组件,第一个入参数相当于vuex中computed选项中的mapState,将仓库中的state映射到组件的props中。
import React from 'react'
import { connect } from './reatx'
class HelloWorld extends React.Component {
  render () {
    let { store } = this.props
    return (
      <div>
        <p>{ this.props.obj.num }</p>
        <button onClick={e => {
          store.dispatch('add')
        }}>增加</button>
        <button onClick={e => store.dispatch('delete')}>减少</button>
      </div>
    )
  }
}

export default connect({
  obj: (state) => state.obj
})(HelloWorld)

实例仓库, api与vuex保持一致。

  1. dispatch一个action
  2. 在action中触发mutation
import Reatx from './reatx'
const store = new Reatx({
  state: {
    obj: {
      num: 0
    },
    userInfo: {},
    phone: '',
    latitude: '',
    longitude: '',
    channel: ''
  },
  actions: {
    add (store) {
      store.commit('ADD')
    },
    delete (store) {
      store.commit('DELETE')
    }
  },
  mutations: {
    ADD (state, payload) {
      state.obj.num = state.obj.num + 1
    },
    DELETE (state, payload) {
      state.obj.num = state.obj.num - 1
    }
  }
})

export default store