zoukankan      html  css  js  c++  java
  • 《深入浅出React和Redux》(2)

    ### Redux是Flux理念的一种实现。
    关于Flux理念可以通过类比MVC模式来做简单理解。
    MVC模式中,用户请求先到达Controller,由Controller调用Model获得数据,然后把数据交给View,按照这种模式,MVC应该也是一个controller->model->view的单向数据流,但是,在实际应用中,由于种种原因,往往会让view直接操作Model,随着应用的演进、逻辑变得越来约复杂,view与model之间的关系就会变得错综复杂、难以维护。**在MVC中让View和Model直接对话就是灾难**。
    Flux理念可以简单看作是对MVC添加了更加严格的数据流限制。
    
    Flux框架随React一同被Fackbook推出,但在Dan Abramov创建了Redux后,Redux已经替代了Flux。
    
    ### 基本原则
    Flux的基本原则是“单向数据流”, Redux在此基础上强调三个基本原则:
    - 唯一数据源(Single Source of Truth),应用的状态数据应该只存储在唯一的一个Store上,它是树形结构,每个组件往往只是用树形对象上一部分的数据,而如何设计Store上状态的结构,就是Redux应用的核心问题。
    
    - 保持状态只读(State is read-only),不能直接修改store状态,必须要通过派发action对象的方式来进行。
    
    - 数据改变只能通过纯函数完成(Changesare made with pure functions)。
    
    这个纯函数就是Reducer, Dan Abramov说过,Redux名字的含义就是Reducer+Flux。Reducer是一个很多语言都支持的函数,下面是js中数组的reduce函数的用法:
    

    [1,2,3,4].reduce(function reducer(accumulation, item){
    return accumulation + item;
    }, 0)

    reduce函数的第一个参数是reducer, 这个函数把数组所有元素依次做“规约”,对每个元素都调用一次reducer,通过reducer函数完成规约所有元素的功能。
    
    在Redux中,reducer的函数签名为:
    

    reducer(state, action)

    它根据state和action的值产生一个新的state对象返回,reducer是纯函数,只负责计算状态,不会去存储状态。
    
    ### Redux的使用
    使用create-react-app创建的模版中不包含redux依赖,首先需要执行```npm install redux```安装。
    
    MVC中标准的单向数据流为controller->model->view的,对应地,在React配合Redux后,要触发view的更新,需要发出一个action,然后reducer根据action来更新store的状态,最后让view根据store中最新的数据来更新。
    
    #### Action
    Redux应用习惯上把action类型和action构造函数分成两个文件定义,两者的内容类似这样:
    **ActionTypes.js**
    

    export const INCREMENT="increment";
    export const DECREMENT="decrement";

    
    **Actions.js**
    这里的每个action构造函数都返回一个action对象
    

    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;

    store的创建要调用Redux库提供的createStore函数:
    - 第一个参数为reducer,它负责更新更新状态
    - 第二个参数是状态的初始值
    - 还有第三个参数为Store Enhancer,后面再了解
    
    确定Store状态,是设计好Redux应用的关键,其主要原则是:**避免冗余的数据**;
    
    #### 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;
    }
    }

    Reducer的主要结构就是if-else或switch语句,根据action.type来执行对应的reduce操作。
    ```...state```为扩展操作符语法,将state字段扩展开赋值给一个新的对象,再根据counterCaption修改对应的字段,这种简化写法等同于:
    

    const newState = Object.assign({}, state);
    newState[counterCaption]++;
    return newState;

    
    扩展操作符语法(spread operator)并不是ES6语法,但因其语法简单而被广泛使用,babel会负责解决兼容性问题。
    
    reducer是纯函数,不会修改原有的state,而是操作新复制的state。
    
    ### View
    view代码举例:
    

    import { Component } from 'react';
    import PropTypes from 'prop-types';
    import store from '../Store.js';
    import * as Actions from '../Actions.js'

    class Counter extends Component {
    constructor(props) {
    super(props);
    /* bind methods*/

    this.state = this.getOwnState();
    

    }

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

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

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

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

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

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

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

    return (
      <div>
        <button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
        <button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
        <span>{caption} count:{value}</span>
      </div>
    );
    

    }
    }

    
    - ```store.getState()```获取store上存储的所有状态,当前组件只需要获取自身相关的状态信息;
    - componentDidMount中,通过store.subscribe监听store状态的变化;
    store状态变化时,触发onChange,在这里会调用```this.setState```触发view更新;
    - 此外还要在componentWillUnmount中取消对store状态变化的监听;
    - 点击button时,通过store.dispatch分发一个action,这个action由Actions.js中的某个action构造函数产生。
    
    ### 容器组件和展示组件
    从前文的Redux示例代码可以发现,在Redux框架下,一个React组件基本上就是要完成以下两个功能:
    - 与store相关的职责:
      - 读取并监听Store状态的改变;
      - 当Store状态变化时,更新组件状态,驱动组件重新渲染;
      - 当需要更新Store状态时,派发action对象;
    - 根据当前props和state,渲染出用户界面。
    
    于是考虑将组件一分为二:
    - 容器组件:承担store相关职责
    - 展示组件:只关注页面渲染
    
    展示组件没有状态,是一个纯函数,只需要根据props来渲染,不需要包含state;
    
    ### 组件Context
    但是如果每个组件都拆分为容器组件和展示组件后,由于容器组件要与store打交道,那么每个容器组件都需要导入store,最好能只在顶层的组件导入一次,子组件就都可以用了,不过子组件使用时不是使用props层层传递,而是借助React提供的Context(上下文环境)。
    
    要使用Context需要上下级组件之间的配合,首先来看顶层组件。
    在此创建一个Provider组件作为一个通用的Context提供者
    

    import { Component } from 'react';
    import PropTypes from 'prop-types';

    class Provider extends Component {
    getChildContext() {
    return {
    store: this.props.store
    };
    }

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

    Provider.childContextTypes = {
    store: PropTypes.object
    }

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

    export default Provider;

    这个组件作为父组件使用:
    

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

    ReactDOM.render(


    ,
    document.getElementById('root')
    );

    顶层的Provider组件,通过```Provider.childContextTypes```来宣称自己支持context,并且提供```getChildContext```函数来返回代表Context的对象,Context对象中存储的值实际上是index.js导入的store,作为props传递给了Provider。这样就实现了只在index.js导入store一次。
    Provider组件还会通过```this.props.children```把子组件渲染出来。
    
    然后它子孙组件,只要宣称自己需要这个context,就可以通过this.context访问到这个共同的环境对象。就像这样:
    

    SummaryContainer.contextTypes = {
    store: PropTypes.object
    }

    另外,构造函数也要传递context:
    

    constructor(props, context) {
    super(props, context);
    ···
    }

    这里可以进一步简化为:
    

    constructor() {
    super(...arguments);
    ···
    }

    
    ### React-Redux
    以上过程从两个方面改进了React应用:
    - 把组件拆分为容器组件和展示组件
    - 所有组件都通过Context来访问store
    
    实际上使用react-redux库可以代替上面的大部分工作了,不过通过前面一步步的改进过程可以了解其中的原理。
    
    引入react-redux后,代码相比之前会更加简洁。
    首先在index.js修改为从react-redux导入Provider:
    

    import {Provider} from 'react-redux';

    在子组件中,容器组件部分也交给react-redux来创建,我们只需编写展示组件部分,并定义好mapStateToProps和mapDispatchToProps。
    之前在容器组件有定义getOwnState方法,将store上的状态转化为展示组件的props
    

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

    mapStateToProps也是同样的作用:
    

    function mapStateToProps(state, ownProps) {
    return {
    value: state[ownProps.caption]
    }
    }

    容器组件还定义了点击是分发action的函数,
    

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

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

    mapDispatchToProps的也是同样的作用,将dispatch传递给展示组件的props,供其主动触发:
    

    function mapDispatchToProps(dispatch, ownProps) {
    return {
    onIncrement: () => {
    dispatch(Actions.increment(ownProps.caption));
    },
    onDecrement: () => {
    dispatch(Actions.decrement(ownProps.caption));
    }
    }
    }

    
    mapStateToProps和mapDispatchToProps会作为参数传递给react-redux的connect
    

    export default connect(mapStateToProps, mapDispatchToProps)(Counter);

    connect函数的运行结果还是一个函数,然后把展示组件Counter作为参数调用这个函数,这个过程就类似于之前的容器组件嵌套展示组件。
    
    ### 参考书籍
    《深入浅出React和Redux》 程墨
    
  • 相关阅读:
    第七章习题G题
    第二周习题O题
    P4735 最大异或和
    P3008 [USACO11JAN]道路和飞机Roads and Planes
    P4009 汽车加油行驶问题
    P1073 最优贸易
    P2260 [清华集训2012]模积和
    P2865 [USACO06NOV]路障Roadblocks
    P1821 [USACO07FEB]银牛派对Silver Cow Party
    P4180 【模板】严格次小生成树[BJWC2010]
  • 原文地址:https://www.cnblogs.com/zhixin9001/p/14390841.html
Copyright © 2011-2022 走看看