实现一个react-redux

react-redux是配合react库管理数据的一种方式,下面试redux的简单实现。

参考http://huziketang.mangojuice.top/books/react/lesson30

import React, {Component} from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
import './index.css'


/**
 * connect函数是高阶组件,用于包装纯组件并返回一个组件。使store中的数据可以传入到被包装组件,和响应store中数据的更新
 * connect接收两个方法,一个指定被包装组件需要通过props传入的参数数据
 * 一个指定被包装组件需要通过props传入的回调方法
 * connect方法返回的方法接收被包装的组件
 */
const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    };
    constructor(){
      super()
      this.state = {
        allProps: {}
      }
    }
    componentWillMount(){
      const {store} = this.context
      this._updateProps()
      store.subscribe(() => {
        this._updateProps()
      })
    }
    _updateProps(){
      const {store} = this.context
      let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {}
      let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : {}
      this.setState(
        {
          allProps: {
            ...stateProps,
            ...dispatchProps,
            ...this.props
          }
        }
      )
    }
    render(){
      return <WrappedComponent {...this.state.allProps}/>
    }
  }
  return Connect;
}

class Header extends Component{
  static propTypes  = {
    themeColor: PropTypes.string.isRequired
  };
  render(){
    return (
      <h1 style={{color: this.props.themeColor}}>React.js xiaoshu</h1>
    )
  }
}
const mapStateToPropsHeader = (state) => {
  return {
    themeColor: state.themeColor
  }
}
let CHeader = connect(mapStateToPropsHeader)(Header)


class ThemeSwitch extends Component{
  static propTypes = {
    themeColor: PropTypes.string,
    onSwitchColor: PropTypes.func
  }
  handleSwitchColor(color){
    if(this.props.onSwitchColor){
      this.props.onSwitchColor(color)
    }
  }
  render(){
    return (
      <div>
        <button 
          style={{color: this.props.themeColor}}
          onClick = {this.handleSwitchColor.bind(this, "red")}
        >Red</button>
        <button
         style={{color: this.props.themeColor}}
         onClick = {this.handleSwitchColor.bind(this, "blue")}
        >Blue</button>
      </div>
    )
  }
}
const mapStateToPropsThemeSwitch = (state) => {
  return {
    themeColor: state.themeColor
  }
}
const mapDispatchToPropsThemeSwitch = (dispatch) => {
  return {
    onSwitchColor: (color) => {
      dispatch({ type: 'CHANGE_COLOR', themeColor: color})
    }
  }
}
let CThemeSwitch = connect(mapStateToPropsThemeSwitch, mapDispatchToPropsThemeSwitch)(ThemeSwitch)

class Content extends Component {
  render () {
    return (
      <div>
        <p style = {{color: this.props.themeColor}}>React.js 小书</p>
        <CThemeSwitch />
      </div>
    )
  }
}
const mapStateToPropsContent = (state) => {
  return {
    themeColor: state.themeColor,
  }
}
let CContent = connect(mapStateToPropsContent)(Content);


/**
 * Procider是store的载体,通过context提供了让所有子组件访问store中数据的能力
 */
class Provider extends Component {
  // propTypes 定义了通过创建组件时传入的参数类型,这个功能是通过导入'prop-types'包提供的
  // 如果传入了不是指定类型的参数会报错
  static propTypes = {
    store: PropTypes.object.isRequired,   // isRequired 强制此参数必须传入
    children: PropTypes.any
  }
  // 如果提供了getChildContex方法,则必须提供childContextTypes作为context的声明和验证
  static childContextTypes = {
    store: PropTypes.object
  }
  /**
   * getChildContext方法返回context对象,所有子组件都可以访问context
   * 子组件想要获取context中的内容,就必须写contextTypes来声明和验证要获取的属性的类型
   * static contextTypes = {
   *   themeColor: PropTypes.string
   * };
   */
  getChildContext () {
    return {
      store: this.props.store
    }
  }
  render () {
    return (
      <div>{this.props.children}</div>
    )
  }
}

/**
 * createStroe接收一个producer函数并返回store对象
 * store对象提供三个方法,getState dispatch subscribe
 * getState() 获取储存在store中的数据
 * dispatch(action) 修改数据(依据action的type属性修改数据)
 * subscribe(listener) 订阅事件,当调用store.dispatch时会收到事件
 */
function createStore(producer){
  let state = null;
  const listeners = [];
  const subscribe = (listener) => listeners.push(listener);
  const getState = () => state;
  const dispatch = (action) => {
    state = producer(state, action);
    listeners.forEach((listener) => listener());
  };
  dispatch({});
  return {
    getState,
    dispatch,
    subscribe,
  };
}
/**
 * 作为参数传入createStore中的reducer函数
 * reducer接收数据和action作为参数并返回修改后的数据
 */
function themeProducer(state, action){
  if(!state){
    return {
      themeColor: 'red'
    };
  }
  switch(action.type){
    case 'CHANGE_COLOR':
      return {
        ...state,
        themeColor: action.themeColor,
      };
    default:
      return state;
  }
}

const store = createStore(themeProducer);

class Index extends Component{
  render(){
    return (
      <div>
        <CHeader/>
        <CContent/>
      </div>
    )
  }
}

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