这个篇博客是对上一篇博客 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组件
- 修改构造函数的写法(
constructor(props,context){ super(props,context); }
) - 添加
Xxxx.contextTypes={ store:PropTypes.object}
- 修改获取store获取的方法( 把以前通过
this.props.store
改成this.context.store
获取store 对象)。 - 导入:
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;
执行的效果: