React + Redux实现计算案例(优化)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012987546/article/details/79995076

这个篇博客是对上一篇博客 React + Redux实现计算案例 的进一步优化, 完成UI组件和容器组件分离将store对象存在React的context中
项目代码下载 地址

1.UI组件和容器组件分离

1.UI 组件特征

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 没有状态(即不使用this.state这个变量)
  • 所有数据都由参数(this.props)提供
  • 不使用任何 Redux 的 API

因为UI组件不含有状态,又称为”纯组件“,即它纯函数一样,纯粹由参数决定它的值。

例如,下面的是UI组件:

/*1.UI组件:只负责UI的呈现*/
class Counter extends Component{
    render(){
        var {sum,store}=this.props;
        return(
            <div style={style}>
                <CounterItem id={0} store={store}></CounterItem>
                <CounterItem id={1} store={store}></CounterItem>
                <div>
                    合计:<span>{sum}</span>
                </div>
            </div>
        )
    }
}
//or 写成下面这样
var Counter=({sum,store})=>(
    <div style={style}>
        <CounterItem id={0} store={store}></CounterItem>
        <CounterItem id={1} store={store}></CounterItem>
        <div>
            合计:<span>{sum}</span>
        </div>
    </div>
)

2.容器组件

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态
  • 使用 Redux 的 API

容器组件负责管理数据和逻辑。在使用的时候 一般使用容器组件包裹这个UI组件

例如: 上一篇博客的Counter组件将render中的代码抽取为一个组件后就成为了容器组件:

/*2.容器组件:负者管理数据和逻辑*/
class CounterContainer extends Component {

    constructor(props){
        super(props);
        this.state = this.getOwnState();

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

    }

    getOwnState() {
        var state=this.props.store.getState();
        var sum=0;
        state.forEach(function (item,index) {
            sum+=item.number;
        })
        return {
            sum:sum,
        };
    }

    render() {
        const sum =this.state.sum;
        /*容器组件包含了UI组件:容器组件负者逻辑处理UI组件负者界面呈现*/
        return (
            <Counter sum={sum} store={this.props.store}></Counter>
        );
    }

    onChange() {
        //如果store 发生了变化,重新获取store中的值
        this.setState(this.getOwnState());
    }

    componentDidMount() {
        // 监听store 的变化
        this.props.store.subscribe(this.onChange);
    }

    componentWillUnmount() {
        //取消监听store 的变化
        this.props.store.unsubscribe(this.onChange);
    }
}

export default CounterContainer;

3.优化React + Redux实现计算案例代码

1.Counter组件实现UI组件和容器组件分离

import React, { Component } from 'react';
import CounterItem from './CounterItem'

const style = {
    width:'200px',
    backgroundColor:'skyblue',
    margin: '20px'
};

/*1.UI组件:只负责UI的呈现*/
var Counter=({sum,store})=>(
    <div style={style}>
        <CounterItem id={0} store={store}></CounterItem>
        <CounterItem id={1} store={store}></CounterItem>
        <div>
            合计:<span>{sum}</span>
        </div>
    </div>
)

/*2.容器组件:负者管理数据和逻辑*/
class CounterContainer extends Component {

    constructor(props){
        super(props);
        this.state = this.getOwnState();

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

    }

    getOwnState() {
        var state=this.props.store.getState();
        var sum=0;
        state.forEach(function (item,index) {
            sum+=item.number;
        })
        return {
            sum:sum,
        };
    }

    render() {
        const sum =this.state.sum;
        /*容器组件包含了UI组件:容器组件负者逻辑处理UI组件负者界面呈现*/
        return (
            <Counter sum={sum} store={this.props.store}></Counter>
        );
    }

    onChange() {
        //如果store 发生了变化,重新获取store中的值
        this.setState(this.getOwnState());
    }

    componentDidMount() {
        // 监听store 的变化
        this.props.store.subscribe(this.onChange);
    }

    componentWillUnmount() {
        //取消监听store 的变化
        this.props.store.unsubscribe(this.onChange);
    }
}
/**默认将容器组件导出*/
export default CounterContainer;

2.CounterItem组件实现UI组件和容器组件分离

import React, { Component } from 'react';
import * as Actions  from '../action';

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

/*1.UI组件:只负责UI的呈现*/
var CounterItem=({num,addCounter,removeCounter})=>(
    <div style={style}>
        <button onClick={addCounter}> + </button>
        <span>{num}</span>
        <button onClick={removeCounter}> - </button>
    </div>
)

/*2.容器组件:负者管理数据和逻辑*/
class CounterItemContainer extends Component {
    constructor(props) {
        super(props);
        this.state=this.getOwnState();
        this.addCounter = this.addCounter.bind(this);
        this.removeCounter = this.removeCounter.bind(this);
        this.onChange = this.onChange.bind(this);
        this.getOwnState = this.getOwnState.bind(this);
    }

    getOwnState(){
        var {id,store}=this.props;
        var oldNumber=store.getState()[id].number;
        return {
            number:oldNumber,
        }
    }

    addCounter(){
        var{id,store}=this.props;
        //调用 store 来 分发 + action
        store.dispatch( Actions.addCounter(id) );
    }
    removeCounter(){
        var{id,store}=this.props;
        //调用 store 来 分发 - action
        store.dispatch( Actions.removeCounter(id) );
    }

    render() {
        var num=this.state.number;
        return (
            <CounterItem num={num}
                         addCounter={this.addCounter}
                         removeCounter={this.removeCounter}></CounterItem>
        );
    }

    onChange() {
        //如果store 发生了变化,重新获取store中的值
        this.setState(this.getOwnState());
    }

    componentDidMount() {
        // 监听store 的变化
        this.props.store.subscribe(this.onChange);
    }

    componentWillUnmount() {
        //取消监听store 的变化
        this.props.store.unsubscribe(this.onChange);
    }
}
/**默认将容器组件导出*/
export default CounterItemContainer;

React + Redux实现计算案例 的其它的代码不需要改动。仅仅对Counter组件和CounterItem组件进行UI组件和容器组件分离。

执行的效果:

2.把store对象封装到React的上下文context

React + Redux实现计算案例 这个案例中,我们可以发现子组件想要获取store对象,需要通过父亲传递下去。如果一旦嵌套的层级多了,这种做法并不友善。那有什么办法可以解决这个问题 ?? 为了解决这个问题,可以将store对象存储在React的context上下文中,这样每个组件都可以通过context来获取store对象。

1.编写Provider组件

编写一个Provider组件,它是一个通用的context提供者。Provider组件通过getChildContext()函数将返回一个context上下文对象 , 这个context对象这里只有一个store属性。

1 ) 为了使用Provider能够被认为是一个Context的提供者,必须设计Provider.childContextTypes={ store:PropTypes.object } ;

2 ) 还有如果子组件想通过this.context来获取Provider提供的context对象必须满足下面的要求:

​ 1.子组件必须设计:Xxxx.contextTypes={ store:PropTypes.object}

​ 2.子组件的构造函数必须必须用上第二参数context , 例如:

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

import {Component} from 'react';
/**这个需要额外得安装:npm install [email protected] --save */
import {PropTypes} from 'prop-types';

class Provider extends Component {

    /**
     * 返回代表:Context 对象。
     * 这个context对象只有一个store属性。
     * 并且store是外部传递过来的对象
     * */
    getChildContext() {
        return {
            store: this.props.store
        };
    }

    /**
     * 这个Provider组件把渲染的工作完全的交给子组件
     * this.props.children :拿到的是<Provider></Provider>组件里面嵌套的子组件
     */
    render() {
        return this.props.children;
    }

}

/**强制指定store的数据类型和必须项*/
Provider.propTypes = {
    store: PropTypes.object.isRequired
}

/**使用Provider能够被认为是一个Context的提供者*/
Provider.childContextTypes = {
    store: PropTypes.object
};

export default Provider;

2.使用Provider优化代码写法

修改React + Redux实现计算案例 项目的index.js文件

这里写图片描述

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import store from './demo1/store'
import Provider from './demo1/Provider'
import App from './demo1/view/Counter';

import registerServiceWorker from './registerServiceWorker';

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

3.修改Counter组件和CounterItem组件

  1. 修改构造函数的写法( constructor(props,context){ super(props,context); } )
  2. 添加Xxxx.contextTypes={ store:PropTypes.object}
  3. 修改获取store获取的方法( 把以前通过this.props.store 改成 this.context.store 获取store 对象)。
  4. 导入:import {PropTypes} from 'prop-types'; React 16 以后需要额外导入

修改后的Counter组件(写注释的地方就是修改的地方):

import React, { Component } from 'react';
import CounterItem from './CounterItem'
/*导入PropTypes*/
import {PropTypes} from 'prop-types';

const style = {
    width:'200px',
    backgroundColor:'skyblue',
    margin: '20px'
};

var Counter=({sum})=>(
    <div style={style}>
        {/*这里不需要在传递store给子组件*/}
        <CounterItem id={0}></CounterItem>
        <CounterItem id={1}></CounterItem>
        <div>
            合计:<span>{sum}</span>
        </div>
    </div>
)

class CounterContainer extends Component {
    /*添加context参数*/
    constructor(props,context){
        super(props,context);
        this.state = this.getOwnState();

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

    }

    getOwnState() {
        /*改成从this.context获取store对象*/
        var state=this.context.store.getState();
        var sum=0;
        state.forEach(function (item,index) {
            sum+=item.number;
        })
        return {
            sum:sum,
        };
    }

    render() {
        const sum =this.state.sum;
        return (
            <Counter sum={sum}></Counter>
        );
    }

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

    componentDidMount() {
        /*改成从this.context获取store对象*/
        this.context.store.subscribe(this.onChange);
    }

    componentWillUnmount() {
         /*改成从this.context获取store对象*/
        this.context.store.unsubscribe(this.onChange);
    }
}
/**给子组件添加contextTypes属性才能通过this.context来获取Provider提供的contex上下文*/
CounterContainer.contextTypes={
    store:PropTypes.object,
}
export default CounterContainer;

修改后的CounterItem组件(写注释的地方就是修改的地方):

import React, { Component } from 'react';
import * as Actions  from '../action';
/*导入PropTypes*/
import {PropTypes} from 'prop-types';
const style = {
    margin:'10px'
};

/*1.UI组件:只负责UI的呈现*/
var CounterItem=({num,addCounter,removeCounter})=>(
    <div style={style}>
        <button onClick={addCounter}> + </button>
        <span>{num}</span>
        <button onClick={removeCounter}> - </button>
    </div>
)

/*2.容器组件:负者管理数据和逻辑*/
class CounterItemContainer extends Component {
    constructor(props,context) {
        super(props,context);
        this.state=this.getOwnState();
        this.addCounter = this.addCounter.bind(this);
        this.removeCounter = this.removeCounter.bind(this);
        this.onChange = this.onChange.bind(this);
        this.getOwnState = this.getOwnState.bind(this);
    }

    getOwnState(){
        /*改成从this.context获取store对象*/
        var store=this.context.store;
        var id=this.props.id;
        var oldNumber=store.getState()[id].number;
        return {
            number:oldNumber,
        }
    }

    addCounter(){
        var store=this.context.store;
        var id=this.props.id;
        store.dispatch( Actions.addCounter(id) );
    }
    removeCounter(){
        /*改成从this.context获取store对象*/
        var store=this.context.store;
        var id=this.props.id;
        store.dispatch( Actions.removeCounter(id) );
    }

    render() {
        var num=this.state.number;
        return (
            <CounterItem num={num}
                         addCounter={this.addCounter}
                         removeCounter={this.removeCounter}></CounterItem>
        );
    }

    onChange() {
        //如果store 发生了变化,重新获取store中的值
        this.setState(this.getOwnState());
    }

    componentDidMount() {
        /*改成从this.context获取store对象*/
        this.context.store.subscribe(this.onChange);
    }

    componentWillUnmount() {
        /*改成从this.context获取store对象*/
        this.context.store.unsubscribe(this.onChange);
    }
}
/**给子组件添加contextTypes属性才能通过this.context来获取Provider提供的contex上下文*/
CounterItemContainer.contextTypes={
    store:PropTypes.object,
}
export default CounterItemContainer;

执行的效果:

猜你喜欢

转载自blog.csdn.net/u012987546/article/details/79995076