深入浅出React和Redux——阅读笔记3

第三章 从Flux到Redux

  1. MVC


    深入浅出React和Redux——阅读笔记3_第1张图片
    image.png
  2. Flux


    深入浅出React和Redux——阅读笔记3_第2张图片
    image.png

使用flux:
整体目录


深入浅出React和Redux——阅读笔记3_第3张图片
image.png

思路:story继承并扩展了EventEmitter对象,拥有了广播和添加/移除监听的功能。
view层通过story获取数据,并添加对该数据的监听,一旦接收到对应数据变化的广播,立即更新view层数据,view层不能直接改变数据,唯一的方式是通过派发action,action改变store数据并进行广播。

  • 安装:
    npm install --save flux

  • Dispatcher
    AppDispatcher.js

import {Dispatcher} from 'flux';

export default new Dispatcher();
  • action

ActionTypes.js

export const INCREMENT = 'increment';

export const DECREMENT = 'decrement';

Actions.js

import * as ActionTypes from './ActionTypes.js';
import AppDispatcher from './AppDispatcher.js';

export const increment = (counterCaption) => {
  AppDispatcher.dispatch({
    type: ActionTypes.INCREMENT,
    counterCaption: counterCaption
  });
};

export const decrement = (counterCaption) => {
  AppDispatcher.dispatch({
    type: ActionTypes.DECREMENT,
    counterCaption: counterCaption
  });
};
  • Store

CounterStore.js

import AppDispatcher from '../AppDispatcher.js';
import * as ActionTypes from '../ActionTypes.js';
import {EventEmitter} from 'events';

const CHANGE_EVENT = 'changed';

const counterValues = {
  'First': 0,
  'Second': 10,
  'Third': 30
};


const CounterStore = Object.assign({}, EventEmitter.prototype, {
  getCounterValues: function() {
    return counterValues;
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }

});

CounterStore.dispatchToken = AppDispatcher.register((action) => {
  if (action.type === ActionTypes.INCREMENT) {
    counterValues[action.counterCaption] ++;
    CounterStore.emitChange();
  } else if (action.type === ActionTypes.DECREMENT) {
    counterValues[action.counterCaption] --;
    CounterStore.emitChange();
  }
});

export default CounterStore;

SummaryStore.js

import AppDispatcher from '../AppDispatcher.js';
import * as ActionTypes from '../ActionTypes.js';
import CounterStore from './CounterStore.js';
import {EventEmitter} from 'events';

const CHANGE_EVENT = 'changed';

function computeSummary(counterValues) {
  let summary = 0;
  for (const key in counterValues) {
    if (counterValues.hasOwnProperty(key)) {
      summary += counterValues[key];
    }
  }
  return summary;
}

const SummaryStore = Object.assign({}, EventEmitter.prototype, {
  getSummary: function() {
    return computeSummary(CounterStore.getCounterValues());
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }

});


SummaryStore.dispatchToken = AppDispatcher.register((action) => {
  if ((action.type === ActionTypes.INCREMENT) ||
      (action.type === ActionTypes.DECREMENT)) {
    AppDispatcher.waitFor([CounterStore.dispatchToken]);

    SummaryStore.emitChange();
  }
});

export default SummaryStore;

  • View
    ControlPanel.js
import React, { Component } from 'react';
import Counter from './Counter.js';
import Summary from './Summary.js';

const style = {
  margin: '20px'
};

class ControlPanel extends Component {

  render() {
    return (
      

); } } export default ControlPanel;

Counter.js

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

import * as Actions from '../Actions.js';
import CounterStore from '../stores/CounterStore.js';

const buttonStyle = {
  margin: '10px'
};

class Counter extends Component {

  constructor(props) {
    super(props);

    this.onChange = this.onChange.bind(this);
    this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
    this.onClickDecrementButton = this.onClickDecrementButton.bind(this);

    this.state = {
      count: CounterStore.getCounterValues()[props.caption]
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    return (nextProps.caption !== this.props.caption) ||
           (nextState.count !== this.state.count);
  }

  componentDidMount() {
    CounterStore.addChangeListener(this.onChange);
  }

  componentWillUnmount() {
    CounterStore.removeChangeListener(this.onChange);
  }

  onChange() {
    const newCount = CounterStore.getCounterValues()[this.props.caption];
    this.setState({count: newCount});
  }

  onClickIncrementButton() {
    Actions.increment(this.props.caption);
  }

  onClickDecrementButton() {
    Actions.decrement(this.props.caption);
  }

  render() {
    const {caption} = this.props;
    return (
      
{caption} count: {this.state.count}
); } } Counter.propTypes = { caption: PropTypes.string.isRequired }; export default Counter;

Summary.js

import React, { Component } from 'react';

import SummaryStore from '../stores/SummaryStore.js';

class Summary extends Component {

  constructor(props) {
    super(props);

    this.onUpdate = this.onUpdate.bind(this);

    this.state = {
      sum: SummaryStore.getSummary()
    }
  }

  componentDidMount() {
    SummaryStore.addChangeListener(this.onUpdate);
  }

  componentWillUnmount() {
    SummaryStore.removeChangeListener(this.onUpdate);
  }

  onUpdate() {
    this.setState({
      sum: SummaryStore.getSummary()
    })
  }

  render() {
    return (
      
Total Count: {this.state.sum}
); } } export default Summary;
  1. Redux


    深入浅出React和Redux——阅读笔记3_第4张图片
    image.png

Redux实例:

  • 安装:
    npm install --save redux

  • action

ActionTypes.js

export const INCREMENT = 'increment';

export const DECREMENT = 'decrement';

Actions.js

import * as ActionTypes from './ActionTypes.js';

export const increment = (counterCaption) => {
  return {
    type: ActionTypes.INCREMENT,
    counterCaption: counterCaption
  };
};

export const decrement = (counterCaption) => {
  return {
    type: ActionTypes.DECREMENT,
    counterCaption: counterCaption
  };
};

  • store
    Store.js
import {createStore} from 'redux';
import reducer from './Reducer.js';

const initValues = {
  'First': 0,
  'Second': 10,
  'Third': 20
};

const store = createStore(reducer, initValues);

export default store;
  • reducer
    Reducer.js
import * as ActionTypes from './ActionTypes.js';

export default (state, action) => {
  const {counterCaption} = action;

  switch (action.type) {
    case ActionTypes.INCREMENT:
      return {...state, [counterCaption]: state[counterCaption] + 1};
    case ActionTypes.DECREMENT:
      return {...state, [counterCaption]: state[counterCaption] - 1};
    default:
      return state
  }
}
  • view
    ControlPanel.js
import React, { Component } from 'react';
import Counter from './Counter.js';
import Summary from './Summary.js';

const style = {
  margin: '20px'
};

class ControlPanel extends Component {
  render() {
    return (
      

); } } export default ControlPanel;

Counter.js

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

import store from '../Store.js';
import * as Actions from '../Actions.js';

const buttonStyle = {
  margin: '10px'
};

class Counter extends Component {
  constructor(props) {
    super(props);

    this.onIncrement = this.onIncrement.bind(this);
    this.onDecrement = this.onDecrement.bind(this);
    this.onChange = this.onChange.bind(this);
    this.getOwnState = this.getOwnState.bind(this);

    this.state = this.getOwnState();
  }

  getOwnState() {
    return {
      value: store.getState()[this.props.caption]
    };
  }

  onIncrement() {
    store.dispatch(Actions.increment(this.props.caption));
  }

  onDecrement() {
    store.dispatch(Actions.decrement(this.props.caption));
  }

  onChange() {
    this.setState(this.getOwnState());
  }

  shouldComponentUpdate(nextProps, nextState) {
    return (nextProps.caption !== this.props.caption) ||
      (nextState.value !== this.state.value);
  }

  componentDidMount() {
    store.subscribe(this.onChange);
  }

  componentWillUnmount() {
    store.unsubscribe(this.onChange);
  }

  render() {
    const value = this.state.value;
    const {caption} = this.props;

    return (
      
{caption} count: {value}
); } } Counter.propTypes = { caption: PropTypes.string.isRequired }; export default Counter;

Summary.js

import React, { Component } from 'react';

import store from '../Store.js';

class Summary extends Component {
  constructor(props) {
    super(props);

    this.onChange = this.onChange.bind(this);

    this.state = this.getOwnState();
  }

  onChange() {
    this.setState(this.getOwnState());
  }

  getOwnState() {
    const state = store.getState();
    let sum = 0;
    for (const key in state) {
      if (state.hasOwnProperty(key)) {
        sum += state[key];
      }
    }

    return { sum: sum };
  }

  shouldComponentUpdate(nextProps, nextState) {
    return nextState.sum !== this.state.sum;
  }

  componentDidMount() {
    store.subscribe(this.onChange);
  }

  componentWillUnmount() {
    store.unsubscribe(this.onChange);
  }

  render() {
    const sum = this.state.sum;
    return (
      
Total Count: {sum}
); } } export default Summary;
  1. 容器组件和傻瓜组件
    负责和Redux Store打交道的组件,处于外层,所以被称为容器组件(Container Component);只负责渲染界面的组件,处于内层,叫做展示组件(Presentational Component)。


    深入浅出React和Redux——阅读笔记3_第5张图片
    image.png
深入浅出React和Redux——阅读笔记3_第6张图片
image.png

改进Redux例子的view层:
Counter.js

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

import store from '../Store.js';
import * as Actions from '../Actions.js';

const buttonStyle = {
  margin: '10px'
};

class Counter extends Component {
  render() {
    const {caption, onIncrement, onDecrement, value} = this.props;

    return (
      
{caption} count: {value}
); } } Counter.propTypes = { caption: PropTypes.string.isRequired, onIncrement: PropTypes.func.isRequired, onDecrement: PropTypes.func.isRequired, value: PropTypes.number.isRequired }; class CounterContainer extends Component { constructor(props) { super(props); this.onIncrement = this.onIncrement.bind(this); this.onDecrement = this.onDecrement.bind(this); this.onChange = this.onChange.bind(this); this.getOwnState = this.getOwnState.bind(this); this.state = this.getOwnState(); } getOwnState() { return { value: store.getState()[this.props.caption] }; } onIncrement() { store.dispatch(Actions.increment(this.props.caption)); } onDecrement() { store.dispatch(Actions.decrement(this.props.caption)); } onChange() { this.setState(this.getOwnState()); } shouldComponentUpdate(nextProps, nextState) { return (nextProps.caption !== this.props.caption) || (nextState.value !== this.state.value); } componentDidMount() { store.subscribe(this.onChange); } componentWillUnmount() { store.unsubscribe(this.onChange); } render() { return } } CounterContainer.propTypes = { caption: PropTypes.string.isRequired }; export default CounterContainer;

Summary.js

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

import store from '../Store.js';

class Summary extends Component {
  render() {
    return (
      
Total Count: {this.props.sum}
); } } Summary.propTypes = { sum: PropTypes.number.isRequired }; class SummaryContainer extends Component { constructor(props) { super(props); this.onChange = this.onChange.bind(this); this.state = this.getOwnState(); } onChange() { this.setState(this.getOwnState()); } getOwnState() { const state = store.getState(); let sum = 0; for (const key in state) { if (state.hasOwnProperty(key)) { sum += state[key]; } } return { sum: sum }; } shouldComponentUpdate(nextProps, nextState) { return nextState.sum !== this.state.sum; } componentDidMount() { store.subscribe(this.onChange); } componentWillUnmount() { store.unsubscribe(this.onChange); } render() { return ( ); } } export default SummaryContainer;
深入浅出React和Redux——阅读笔记3_第7张图片
image.png

注意: 上图中的函数,还有一种惯常的写法,就是把结构赋值直接放到参数部分。

  1. 组件Context


    深入浅出React和Redux——阅读笔记3_第8张图片
    image.png
深入浅出React和Redux——阅读笔记3_第9张图片
image.png

进一步优化redux
创建一个名为Provider的组件:

import {PropTypes, Component} from 'react';

class Provider extends Component {

  getChildContext() {
    return {
      store: this.props.store
    };
  }

  render() {
    return this.props.children;
  }

}

Provider.propTypes = {
  store: PropTypes.object.isRequired
}

Provider.childContextTypes = {
  store: PropTypes.object
};

export default Provider;

改进入口文件:
index.js

import React from 'react';
import ReactDOM from 'react-dom';
import ControlPanel from './views/ControlPanel';
import './index.css';

import store from './Store.js';
import Provider from './Provider.js';

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

子组件中使用context
Counter.js

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

import * as Actions from '../Actions.js';

const buttonStyle = {
  margin: '10px'
};

class Counter extends Component {
  render() {
    const {caption, onIncrement, onDecrement, value} = this.props;

    return (
      
{caption} count: {value}
); } } Counter.propTypes = { caption: PropTypes.string.isRequired, onIncrement: PropTypes.func.isRequired, onDecrement: PropTypes.func.isRequired, value: PropTypes.number.isRequired }; class CounterContainer extends Component { // 此处构造函数需要声明context constructor(props, context) { super(props, context); this.onIncrement = this.onIncrement.bind(this); this.onDecrement = this.onDecrement.bind(this); this.onChange = this.onChange.bind(this); this.getOwnState = this.getOwnState.bind(this); this.state = this.getOwnState(); } getOwnState() { return { value: this.context.store.getState()[this.props.caption] }; } onIncrement() { this.context.store.dispatch(Actions.increment(this.props.caption)); } onDecrement() { this.context.store.dispatch(Actions.decrement(this.props.caption)); } onChange() { this.setState(this.getOwnState()); } shouldComponentUpdate(nextProps, nextState) { return (nextProps.caption !== this.props.caption) || (nextState.value !== this.state.value); } componentDidMount() { this.context.store.subscribe(this.onChange); } componentWillUnmount() { this.context.store.unsubscribe(this.onChange); } render() { return } } CounterContainer.propTypes = { caption: PropTypes.string.isRequired }; // 这里要和Provide里的值保持一致 CounterContainer.contextTypes = { store: PropTypes.object } export default CounterContainer;
  1. React-Redux


    深入浅出React和Redux——阅读笔记3_第10张图片
    image.png

利用该库重写redux例子
首相入口文件,我们不需要自己去实现Provider,直接从库里引用:
index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';

import ControlPanel from './views/ControlPanel';
import store from './Store.js';

import './index.css';

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

view层:
Counter.js

import React, { PropTypes } from 'react';
import * as Actions from '../Actions.js';
import {connect} from 'react-redux';

const buttonStyle = {
  margin: '10px'
};

function Counter({caption, onIncrement, onDecrement, value}) {
  return (
    
{caption} count: {value}
); } Counter.propTypes = { caption: PropTypes.string.isRequired, onIncrement: PropTypes.func.isRequired, onDecrement: PropTypes.func.isRequired, value: PropTypes.number.isRequired }; function mapStateToProps(state, ownProps) { return { value: state[ownProps.caption] } } function mapDispatchToProps(dispatch, ownProps) { return { onIncrement: () => { dispatch(Actions.increment(ownProps.caption)); }, onDecrement: () => { dispatch(Actions.decrement(ownProps.caption)); } } } export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Summary.js

import React, { PropTypes } from 'react';
import {connect} from 'react-redux';

function Summary({value}) {
  return (
    
Total Count: {value}
); } Summary.PropTypes = { value: PropTypes.number.isRequired }; function mapStateToProps(state) { let sum = 0; for (const key in state) { if (state.hasOwnProperty(key)) { sum += state[key]; } } return {value: sum}; } export default connect(mapStateToProps)(Summary);

你可能感兴趣的:(深入浅出React和Redux——阅读笔记3)