一、react之表格和分页组件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>react之表格和分页组件之React16.4.0版</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<style>
table{
border-collapse: collapse;
border: 1px solid #cbcbcb;
width:1000px;
background:#ffffff;
text-align:center;
}
table td,table th {
padding: 5px;
border: 1px solid #cbcbcb;
font-weight:100
}
div button{
color:gray;
margin-right:5px
}
.div{
color:red;
width:1000px;
padding:10px 0;
}
</style>
</head>
<body>
<div class="div">
<div>表格组件的功能:</div>
<div>(1)带过滤条件、</div>
<div>(2)表头可以合并、</div>
<div>(3)排序(暂不实现)、</div>
<div>(4)表体可以嵌套表格(暂不实现)、</div>
<div>(5)3种勾选(选择一项、选择一页、选择所有页)、</div>
<div>(6)翻页记忆、</div>
<div>(7)分页。</div>
</div>
<div ></div>
</body>
<script type="text/babel">
const container = document.getElementById('container');
function TwoImg(props) {
var checkImg = {
yes: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAA+UlEQVQoFZWSMU4DMRBF/584G7QSRcIxuAZKEykNEiUVHVTQRaKh4AIcgAvQpkukVDlBOAYNSGSlXXuwpViyYYFdS9aMZ/6bsezh5HZ3T2KhqkfosEhWqnjkyd1u3xWKdQMsfaEAB0Zilf8swfdU0w0klmpGpz1BvpbHcklbPf8Okts0CfJtWBTz/Yc++Jc8S3PZVQfKGwiuvMD6XYsMzm1dT/1jXKdQ8E0asHRrAzOzbC6UGINWHPQp1UQ/6wjF2LpmJSKfhti4Bi8+lhWP4I+gAqV1uqSi8j9WRuF3m3eMWVUJBeKxzUoYn7bEX7HDyPmB7QEHbRjyL+/+VnuXDUFOAAAAAElFTkSuQmCC',
no: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAAbklEQVQoFWM8c+ZMLQMDQxUQcwAxMeAHUFEbC5CoYmNj02ZmZn5FjK6/f/+K/fr16ypIIwdIk7a29hdiNF69ehWkjIOJGMXY1IxqxBYqULEhFDiglPMDlIygKQKPryBSILUgPSCNbaC0B6RJSuQAbowizhJuOsAAAAAASUVORK5CYII=',
};
return (<img src={props.isTrue?checkImg.yes:checkImg.no} onClick={props.clickImg.bind(this)}/>)
}
//一、以下TablePage是表格组件,约280行
class TablePage extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.initThead = [];
this.dataIndexs = [];//表头索引,表头和表体关联的依据
this.maxRowspan = 1;//表头最大跨行数
if(props.giveParentInstance){
props.giveParentInstance(this)//子组件向父组件传值-之三-给函数传参并执行
}
}
//表头跨行跨栏的总思路
//(1)处理跨行的时候,上面决定下面,要看上面有几个单行
//(2)处理跨栏的时候,下面决定上面,要看下面有几个单栏
//一、以下2个函数解决“表头跨行”问题
//1、获取表头“最大跨行数”
getMaxRowspanAndDataIndexs(columns,thisRowspan){
var that=this;
columns.forEach(function(item,index) {
if(item.children){
thisRowspan+=1;
if(thisRowspan>that.maxRowspan) that.maxRowspan = thisRowspan;
that.getMaxRowspanAndDataIndexs(item.children,thisRowspan)
}else{
that.dataIndexs.push(item.dataIndex)//生成表头“数据索引”
}
});
}
//2、添加表头“每栏跨行数”
addEachColumnRowspan(columns,thisRowspan){
var that=this;
columns.forEach(function(item,index) {
if(item.children){
item.thisRowspan=1;
that.addEachColumnRowspan(item.children,thisRowspan-1)
}else{
item.thisRowspan=thisRowspan;
}
});
}
//二、以下2个函数解决“表头跨栏”问题
//3、添加表头“每栏跨栏数”
addEachColumnColspan(columns){
var that=this;
columns.forEach(function(item) {
if(item.thisColspan)return;
if(item.children){
that.addThisColumnColspan(item,item.children)
}else{
item.thisColspan=1;
}
});
}
//4、当某栏有多个子栏时,添加“该栏及其子栏的跨栏数”
addThisColumnColspan(item,children){
var that=this;
children.forEach(function(child) {
var thisChildren= child.children;
if(thisChildren){//item的子级还有子级
//下面添加item栏的跨栏数
that.addThisColumnColspan(item,thisChildren);
//下面添加item栏子栏的跨栏数
that.addEachColumnColspan(children);
}else{
if(item.thisColspan){
item.thisColspan+=1;
}else{
item.thisColspan=1;
}
child.thisColspan=1;
}
});
}
getInitThead(){
for(var i=0;i<this.maxRowspan;i++){
this.initThead.push([]);
}
}
getCenterThead(columns,initThead,index){
var that=this;
columns.forEach(function(item,indexIn){
var itemTitle;
if(item.title){
itemTitle=item.title;
}else{
itemTitle=<TwoImg isTrue={that.props.checkSource.isSelectNowPage} clickImg={that.clickThisPage.bind(that,that.props.dataSource)}/>
}
initThead[index].push(<th key={indexIn+Math.random()} rowSpan={item.thisRowspan} colSpan={item.thisColspan} dataindex={item.dataIndex||''}>{itemTitle}</th>)
var children=item.children;
if(children){
that.getCenterThead(children,initThead,index+1)
}
})
}
getLastThead(thead,initThead){
var that=this;
initThead.forEach(function(item,index){
thead.push(<tr key={index}>{item}</tr>)
})
}
getTbody(dataSource,trBody){
var that=this;
dataSource.forEach(function(tr,index){
var trSingle=[];
for(var i=0;i<that.dataIndexs.length;i++){
var indexIn=that.dataIndexs[i];
var td;
if(indexIn === 'checkQC'){
td = <TwoImg isTrue={tr.state} clickImg={that.clickSingleItem.bind(that,tr,dataSource)}/>
}else{
td = tr[indexIn];
}
trSingle.push(<td key={indexIn}>{td}</td>)
}
trBody.push(<tr key={index}>{trSingle}</tr>);
});
}
componentWillUpdate(nextProps) {
this.signCheckbox(nextProps)
}
setAllState(){
this.props.checkboxClick({
allIncludedIds: this.props.checkSource.allIncludedIds,
allExcludedIds: this.props.checkSource.allExcludedIds,
isSelectNowPage: this.props.checkSource.isSelectNowPage,
isSelectAllPages: this.props.checkSource.isSelectAllPages,
textAllPages: this.props.checkSource.textAllPages,
})
}
clickChildAllPages(itemArray) {//所有页所有条目复选框被点击时执行的函数
if(this.props.checkSource.isSelectAllPages){
if(this.props.checkSource.allExcludedIds.length>0){
this.props.checkSource.isSelectAllPages = true;
this.props.checkSource.isSelectNowPage = true;
this.props.checkSource.textAllPages= '已启用,无排除项!';
itemArray.forEach(function (item) {
item.state = true;
});
}else if(this.props.checkSource.allExcludedIds.length==0){
this.props.checkSource.isSelectAllPages = false;
this.props.checkSource.isSelectNowPage = false;
this.props.checkSource.textAllPages= '未启用,无选择项!';
itemArray.forEach(function (item) {
item.state = false;
});
}
}else{
this.props.checkSource.isSelectAllPages = true;
this.props.checkSource.isSelectNowPage = true;
this.props.checkSource.textAllPages= '已启用,无排除项!';
itemArray.forEach(function (item) {
item.state = true;
});
}
this.props.checkSource.allExcludedIds = [];
this.props.checkSource.allIncludedIds = [];
this.setAllState()
}
clickThisPage(itemArray) {//当前页所有条目复选框被点击时执行的函数
//onClick={this.clickThisPage.bind(this,params.tableDatas)
var that = this;
this.props.checkSource.isSelectNowPage = !this.props.checkSource.isSelectNowPage;
itemArray.forEach(function (item) {
item.state = that.props.checkSource.isSelectNowPage;
if (item.state) {
that.delID(item[that.props.idKey], that.props.checkSource.allExcludedIds);
that.addID(item[that.props.idKey], that.props.checkSource.allIncludedIds);
} else {
that.delID(item[that.props.idKey], that.props.checkSource.allIncludedIds);
that.addID(item[that.props.idKey], that.props.checkSource.allExcludedIds);
}
});
if(this.props.checkSource.isSelectAllPages){
if(this.props.checkSource.isSelectNowPage && this.props.checkSource.allExcludedIds.length === 0){
this.props.checkSource.textAllPages = '已启用,无排除项!';
}else{
this.props.checkSource.textAllPages = '已启用,已排除'+ this.props.checkSource.allExcludedIds.length + '项!排除项的ID为:' + this.props.checkSource.allExcludedIds;
}
}else{
if(!this.props.checkSource.isSelectNowPage && this.props.checkSource.allIncludedIds.length === 0){
this.props.checkSource.textAllPages='未启用,无选择项!';
}else{
this.props.checkSource.textAllPages = '未启用,已选择' + this.props.checkSource.allIncludedIds.length + '项!选择项的ID为:' + this.props.checkSource.allIncludedIds;
}
}
this.setAllState()
}
clickSingleItem(item, itemArray) {//当前页单个条目复选框被点击时执行的函数
var that = this;
item.state = !item.state;
if (item.state) {
this.props.checkSource.isSelectNowPage = true;
this.addID(item[this.props.idKey], this.props.checkSource.allIncludedIds);
this.delID(item[this.props.idKey], this.props.checkSource.allExcludedIds);
itemArray.forEach(function (item) {
if (!item.state) {
that.props.checkSource.isSelectNowPage = false;
}
});
} else {
this.props.checkSource.isSelectNowPage = false;
this.addID(item[this.props.idKey], this.props.checkSource.allExcludedIds);
this.delID(item[this.props.idKey], this.props.checkSource.allIncludedIds);
}
if(this.props.checkSource.isSelectAllPages){
if(this.props.checkSource.isSelectNowPage && this.props.checkSource.allExcludedIds.length === 0){
this.props.checkSource.textAllPages = '已启用,无排除项!';
}else{
this.props.checkSource.textAllPages = '已启用,已排除'+ this.props.checkSource.allExcludedIds.length + '项!排除项的ID为:' + this.props.checkSource.allExcludedIds;
}
}else{
if(!this.props.checkSource.isSelectNowPage && this.props.checkSource.allIncludedIds.length === 0){
this.props.checkSource.textAllPages='未启用,无选择项!';
}else{
this.props.checkSource.textAllPages = '未启用,已选择' + this.props.checkSource.allIncludedIds.length + '项!选择项的ID为:' + this.props.checkSource.allIncludedIds;
}
}
this.setAllState()
}
signCheckbox(nextProps) {//标注当前页被选中的条目,在翻页成功后执行。
var that = this;
if(nextProps.checkSource.isSelectAllPages){
nextProps.checkSource.isSelectNowPage = true;
nextProps.dataSource.forEach(function (item) {
var thisID = item[nextProps.idKey];
var index = nextProps.checkSource.allExcludedIds.indexOf(thisID);
if (index > -1) {
item.state = false;
nextProps.checkSource.isSelectNowPage = false;
} else {
item.state = true;
}
});
}else{
nextProps.checkSource.isSelectNowPage = true;
nextProps.dataSource.forEach(function (item) {
var thisID = item[nextProps.idKey];
var index = nextProps.checkSource.allIncludedIds.indexOf(thisID);
if (index === -1) {
item.state = false;
nextProps.checkSource.isSelectNowPage = false;
} else {
item.state = true;
}
});
}
this.state.isSelectNowPage=nextProps.checkSource.isSelectNowPage;
}
addID(id, idArray) {
var index = idArray.indexOf(id);
if (index === -1) {
idArray.push(id);//如果当前页的单项既有勾选又有非勾选,这时勾选当前页,需要这个判断,以免重复添加
}
}
delID(id, idArray) {
var index = idArray.indexOf(id);
if (index > -1) {
idArray.splice(index, 1)
}
}
render() {
var that=this;
var thead=[];
var tbody=[];
var trBody=[];
var columns=this.props.columns;
var dataSource=this.props.dataSource;
this.initThead = [];
this.dataIndexs = [];
this.getMaxRowspanAndDataIndexs(columns,1);
this.addEachColumnRowspan(columns,this.maxRowspan);
this.addEachColumnColspan(columns);
this.getInitThead();
this.getCenterThead(columns,this.initThead,0);
this.getLastThead(thead,this.initThead);
this.getTbody(dataSource,trBody);
return (
<div>
<table>
<thead>
{thead}
</thead>
<tbody>
{trBody}
</tbody>
</table>
</div>
)
}
}
//二、以下DevidePage是分页组件,约150行
class DevidePage extends React.Component {
constructor(props) {
super(props);
this.state = { };
}
componentDidMount(){
document.getElementById("inputQC").addEventListener("keydown", this.onKeyDown.bind(this))
}
componentDidUpdate(){
document.getElementById("inputQC").addEventListener("keydown", this.onKeyDown.bind(this))
}
componentWillUnmount(){
document.getElementById("inputQC").removeEventListener("keydown", this.onKeyDown.bind(this))
}
inputChange() {
var value = parseInt(this.refs.input.value);
var allPagesNum = this.props.divideSource.allPagesNum;
if(value < allPagesNum && value > 1) {
this.props.divideSource.inputValue = value
}else if(value >= allPagesNum) {
this.props.divideSource.inputValue = allPagesNum
}else{//包含 value <= 1和value=其它非数字字符
this.props.divideSource.inputValue = 1
}
}
clickButton(value){
var nowPageNum = null;
if(value === 'front'){
this.props.divideSource.nowPageNum--;
nowPageNum = this.props.divideSource.nowPageNum
}else if(value === 'back'){
this.props.divideSource.nowPageNum++;
nowPageNum = this.props.divideSource.nowPageNum
}else if(value === 'leap'){
this.inputChange();
nowPageNum = this.props.divideSource.inputValue
}else{
nowPageNum = value
}
this.refs.input.value = nowPageNum;
this.props.divideClick(nowPageNum,this.props.divideSource.eachPageItemsNum);
}
onKeyDown(event){
if(event.key === 'Enter'){
this.inputChange();
this.refs.input.value = this.props.divideSource.inputValue;
this.props.divideClick(this.props.divideSource.inputValue,this.props.divideSource.eachPageItemsNum);
}
}
pageNumLeap(){
var eachPageItemsNum = this.refs.select.value;
this.props.divideSource.eachPageItemsNum = eachPageItemsNum;
this.props.divideClick(1,eachPageItemsNum);
}
render() {
var numButton=[];
var divideSource = this.props.divideSource;
//1、以下处理与分页相关的数字
var nowPageNum = divideSource.nowPageNum;
var allPagesNum = divideSource.allPagesNum;
var inputValue = divideSource.inputValue;
var eachPageItemsNum = divideSource.eachPageItemsNum;
var allItemsNum = divideSource.allItemsNum;
//2、以下是分页组件本身
if (allPagesNum >= 1 && allPagesNum <= 10) {
for (var i = 1; i <= allPagesNum; i++) {
numButton.push(<button key={i} red'}:{color:'gray'}} onClick={this.clickButton.bind(this,i)}>{i}</button>)
}
} else if (allPagesNum >= 11) {
if (nowPageNum > 8) {
numButton.push(<button key={1} onClick={this.clickButton.bind(this,1)}>{1}</button>);
numButton.push(<button key={2} onClick={this.clickButton.bind(this,2)}>{2}</button>);
numButton.push(<button key={3} onClick={this.clickButton.bind(this,3)}>{3}</button>);
numButton.push(<button key={'front'} disabled>{'...'}</button>);
numButton.push(<button key={nowPageNum-2} onClick={this.clickButton.bind(this,nowPageNum-2)}>{nowPageNum-2}</button>);
numButton.push(<button key={nowPageNum-1} onClick={this.clickButton.bind(this,nowPageNum-1)}>{nowPageNum-1}</button>);
numButton.push(<button key={nowPageNum} red'}} onClick={this.clickButton.bind(this,nowPageNum)}>{nowPageNum}</button>);
} else {
for (i = 1; i <= nowPageNum; i++) {
numButton.push(<button key={i} red'}:{color:'gray'}} onClick={this.clickButton.bind(this,i)}>{i}</button>)
}
}
// 以上当前页的左边,以下当前页的右边
if (allPagesNum - nowPageNum >= 7) {
numButton.push(<button key={nowPageNum+1} onClick={this.clickButton.bind(this,nowPageNum+1)}>{nowPageNum+1}</button>);
numButton.push(<button key={nowPageNum+2} onClick={this.clickButton.bind(this,nowPageNum+2)}>{nowPageNum+2}</button>);
numButton.push(<button key={'back'} disabled>{'...'}</button>);
numButton.push(<button key={allPagesNum-2} onClick={this.clickButton.bind(this,allPagesNum-2)}>{allPagesNum-2}</button>);
numButton.push(<button key={allPagesNum-1} onClick={this.clickButton.bind(this,allPagesNum-1)}>{allPagesNum-1}</button>);
numButton.push(<button key={allPagesNum} onClick={this.clickButton.bind(this,allPagesNum)}>{allPagesNum}</button>);
} else {
for (var i = nowPageNum + 1; i <= allPagesNum; i++) {
numButton.push(<button key={i} onClick={this.clickButton.bind(this,i)}>{i}</button>)
}
}
}
//3、以下处理每页显示条数
var selectOption=[];
var numOptions=this.props.numOptions;
if(!numOptions){numOptions=[10,20,30,40,50]};
for(var i=0;i<numOptions.length;i++){
selectOption.push(<option value={numOptions[i]} key={i} >{numOptions[i]}</option>)
}
//4、以下处理最右侧说明文字
var textTemp=this.props.text||{};
var text = {
unit: textTemp.unit || "条,",
frontMoreText: textTemp.frontMoreText || "",
totalText: textTemp.totalText || "共",
totalUnit: textTemp.totalUnit || "项",
backMoreText: textTemp.backMoreText || "",
};
//5、以下渲染分页组件
return (
<div block',display:"flex",width:"1000px",marginTop:"20px"}}>
<div flex"}}>
<button 5px"}} disabled={nowPageNum===1} onClick={this.clickButton.bind(this,'front')}>上一页</button>
<div>{ numButton }</div>
<button disabled={nowPageNum===allPagesNum} onClick={this.clickButton.bind(this,'back')}>下一页</button>
</div>
<div flex", flex:1, justifyContent:"flex-end"}}>
<div 15px"}}>
<span>转到第</span>
<input key={nowPageNum==1?Math.random():'key'} type="text" 30px",margin:"0 5px"}} ref="input" onChange={this.inputChange.bind(this)} onKeyDown={this.onKeyDown.bind(this,event)} defaultValue={inputValue}/>
<span>页</span>
<button 0 5px"}} onClick={this.clickButton.bind(this,'leap')}>Go</button>
</div>
<div>
<span>每页显示</span>
<select 0 5px"}} ref="select" defaultValue={eachPageItemsNum||10} onChange={this.pageNumLeap.bind(this)}>
{ selectOption }
</select>
<span>{text.unit}</span>
</div>
<div>
<span>{text.frontMoreText}</span>
<span>{text.totalText}</span>
<span>{allItemsNum||0}</span>
<span>{text.totalUnit}</span>
<span>{text.backMoreText}</span>
</div>
</div>
</div>
)
}
}
//三、以下WholePage是页面组件,根据自己的独特需求写
class WholePage extends React.Component {
constructor(props) {
super(props);
this.state = {
filter: {
input:'',
select:1
},
dataSource: [],
divideSource:{
nowPageNum: 1,
allPagesNum: 1,
allItemsNum: 1,
eachPageItemsNum: 10,
inputValue:1,
},
checkSource:{
allIncludedIds: [],
allExcludedIds: [],
isSelectAllPages: false,
isSelectNowPage: false,
textAllPages: '未启用,无选择项!',
},
};
this.columns = [
{
title: '',
dataIndex: 'checkQC',
},
{
title: '序号',
dataIndex: 'order',
},
{
title: '个人信息',
children: [
{
title: '姓名',
dataIndex: 'name'
},
{
title: '性别',
dataIndex: 'sex'
},
{
title: '年龄',
dataIndex: 'age'
},
],
},
{
title: '学校以下(学习)',
children: [
{
title: '学校',
dataIndex: 'school'
},
{
title: '年级以下',
children: [
{
title: '年级',
dataIndex: 'grade'
},
{
title: '班级以下',
children: [
{
title: '班级',
dataIndex: 'class'
},
{
title: '科目以下',
children: [
{
title: '科目1',
dataIndex: 'cross1'
},
{
title: '科目2',
dataIndex: 'cross2'
},
],
},
],
},
],
},
],
},
{
title: '省级以下(地址)',
children: [
{
title: '省级',
dataIndex: 'province'
},
{
title: '市级以下',
children: [
{
title: '市级',
dataIndex: 'city'
},
{
title: '县级以下',
children: [
{
title: '县级',
dataIndex: 'coutry'
},
{
title: '乡级以下',
children: [
{
title: '乡级',
dataIndex: 'town'
},
{
title: '村级以下',
children: [
{
title: '村级',
dataIndex: 'village'
},
{
title: '组级',
dataIndex: 'team'
},
],
},
],
},
],
},
],
},
],
}
];
};
componentWillMount() {
this.divideClick(1,this.state.divideSource.eachPageItemsNum)
}
divideClick(nowPageNum,eachPageItemsNum) {
var data=[];
var allItemsNum = 193;
var nowPageNum = nowPageNum||1;
var eachPageItemsNum = eachPageItemsNum||10;
var allPagesNum = Math.ceil(allItemsNum/eachPageItemsNum);
for(var i=0;i<allItemsNum;i++){
var obj={
id:'id'+(i+1),
order:i+1,
name:'姓名'+(i+1),
sex:['男','女'][i%2],
age:(i+11)+'岁',
province:'第'+(i+11)+'省',
city:(i+11)+'市',
coutry:(i+11)+'县',
town:(i+11)+'乡',
village:(i+11)+'村',
team:(i+11)+'组',
school:(i+11)+'学校',
grade:(i+11)+'年级',
class:(i+11)+'班',
cross1:(i+11)+'科',
cross2:(i+11)+'科',
};
data.push(obj)
};
var dataSource = data.slice((nowPageNum-1)*eachPageItemsNum,nowPageNum*eachPageItemsNum);
this.setState({
dataSource: dataSource,
divideSource: {
nowPageNum: nowPageNum,
allPagesNum: allPagesNum,
allItemsNum: allItemsNum,
eachPageItemsNum: eachPageItemsNum,
inputValue: nowPageNum,
},
})
}
checkboxClick(object) {
this.setState({
allIncludedIds: object.allIncludedIds,
allExcludedIds: object.allExcludedIds,
isSelectAllPages: object.isSelectAllPages,
isSelectNowPage: object.isSelectNowPage,
textAllPages: object.textAllPages,
})
}
getChildInstance(that){//子组件向父组件传值-之一-在父组件定义函数
this.childRef=that;//把子组件的实例赋值给父组件的childRef属性。非常重要!!!
}
clickParentAllPages(dataSource){
this.childRef.clickChildAllPages(dataSource)//在父组件里调用子组件的方法。非常重要!!!
}
changeValue(key,event){
this.setState({
filter:{...this.state.filter,[key]:event.target.value}
})
}
render() {
var {dataSource,divideSource,checkSource,filter}={...this.state};
return (
<div>
<div>以下是过滤示例</div>
<div border':'1px solid #cbcbcb','width':'980px','padding':'10px','margin':'6px 0'}}>
<div display':'flex'}}>
<div width':'212px'}}>
<label paddingRight':'4px'}}>输入框示例</label>
<input type='text' placeholder='请输入' onChange={this.changeValue.bind(this,'input')} border':'1px solid #cbcbcb','width':'100px'}}/>
</div>
<div width':'174px'}}>
<label paddingRight':'4px'}}>下拉框示例</label>
<select onChange={this.changeValue.bind(this,'select')}>
<option value={1}>1分钟</option>
<option value={5}>5分钟</option>
<option value={10}>10分钟</option>
<option value={30}>30分钟</option>
<option value={60}>60分钟</option>
</select>
</div>
<div width':'500px'}}>
<span>过滤条件为:{JSON.stringify(this.state.filter)}</span>
<span padding':'0 10px'}}></span>
<button onClick={this.divideClick.bind(this,3,10)}>过滤</button>
<button onClick={this.divideClick.bind(this,1,10)}>刷新</button>
</div>
</div>
</div>
<div paddingBottom':'4px'}}>
<span paddingRight':'10px'}}><TwoImg isTrue={checkSource.isSelectAllPages && checkSource.allExcludedIds.length===0} clickImg={this.clickParentAllPages.bind(this,dataSource)}/></span>
<span>{checkSource.textAllPages}</span>
</div>
<TablePage
idKey='id'
columns={this.columns}
dataSource={dataSource}
checkSource={checkSource}
checkboxClick={this.checkboxClick.bind(this)}
giveParentInstance={this.getChildInstance.bind(this)}//子组件向父组件传值-之二-将函数传给子组件
/>
<DevidePage
divideSource={divideSource}
divideClick={this.divideClick.bind(this)}
/>
</div>
)
}
}
ReactDOM.render(<WholePage/>, container);
</script>
</html>
二、redux实际运用
1、参数的定义
function functionA(createStore3) {//7、接收functionB的返回值createStore3
return function createStore4(reducer0, preloadedState0, enhancer0) {//8、返回值为createStore4
//9、实际执行createStore4(reducer, preloadedState),此处加工参数reducer, preloadedState,传给下面的createStore3
var store3=createStore3(reducer1, preloadedState1, enhancer1);
//16、此处对createStore3的返回值store3进行加工,下面return的是createStore4的返回值,也是最终的返回值
return {
dispatch: dispatch3,
subscribe: subscribe3,
getState: getState3,
replaceReducer: replaceReducer3
}
}
};
function functionB(createStore2) {//5、接收functionC的返回值createStore2
return function createStore3(reducer1, preloadedState1, enhancer1) {//6、返回值为createStore3
//10、此处加工参数,传给下面的createStore2
var store2=createStore2(reducer2, preloadedState2, enhancer2);
//15、此处对createStore2的返回值store2进行加工,下面return的是createStore3的返回值
return {
dispatch: dispatch2,
subscribe: subscribe2,
getState: getState2,
replaceReducer: replaceReducer2
}
}
};
function functionC(createStore1) {//3、接收functionD的返回值createStore1
return function createStore2(reducer2, preloadedState2, enhancer2) {//4、返回值为createStore2
//11、此处加工参数,传给下面的createStore1
var store1=createStore1(reducer3, preloadedState3, enhancer3);
//14、此处对createStore1的返回值store1进行加工,下面return的是createStore2的返回值
return {
dispatch: dispatch1,
subscribe: subscribe1,
getState: getState1,
replaceReducer: replaceReducer1
}
}
};
function functionD(createStore0) {//1、实际执行functionD(createStore)
return function createStore1(reducer3, preloadedState3, enhancer3) {//2、返回值为createStore1
//12、此处加工参数,传给下面的createStore0
var store0=createStore0(reducer4, preloadedState4, enhancer4);
//13、此处对createStore0的返回值store0进行加工,下面return的是createStore1的返回值
return {
dispatch: dispatch0,
subscribe: subscribe0,
getState: getState0,
replaceReducer: replaceReducer0
}
}
};
2、createStore函数的定义与执行
(1)定义
function createStore(reducer, preloadedState, enhancer) {
return enhancer(createStore)(reducer, preloadedState);
}
(2)执行
createStore(
rootReducer,
preloadedState,
compose(arrayFunction)
)
3、compose的定义与执行
(1)定义
var arrayFunction = [functionA, functionB, functionC, functionD];
function compose(arrayFunction) {
return arrayFunction.reduce(function (total, next) {//reduce用作高阶函数,compose其它函数
// reduce第1次执行时,total是functionA,next是functionB,执行结果为functionOne
// function functionOne() {
// return functionA(functionB.apply(undefined, arguments));
// }
// reduce第2次执行时,total是functionOne,next是functionC,执行结果为functionTwo
// function functionTwo() {
// return functionOne(functionC.apply(undefined, arguments));
// }
// reduce第3次执行时,total是functionTwo,next是functionD,执行结果为functionThree
// function functionThree() {
// return functionTwo(functionD.apply(undefined, arguments));
// }
// reduce将最后一次执行结果functionThree暴露出去
return function () {
return total(next.apply(undefined, arguments));
};
})
}
(2)compose(arrayFunction)执行,返回functionThree
4、enhancer(createStore)(reducer, preloadedState)执行,就是functionThree(createStore)(reducer, preloadedState)执行
(1)enhancer(createStore)执行,就是functionThree(createStore)执行,最终返回createStore4
//第1次执行时,functionThree(createStore0),functionD(createStore0),返回createStore1
//第2次执行时,functionTwo(createStore1),functionC(createStore1),返回createStore2
//第3次执行时,functionOne(createStore2),functionB(createStore2),返回createStore3
//第4次执行时,functionA(createStore3),返回createStore4
(2)createStore4(reducer, preloadedState)执行
//1、给createStore4传参并执行,进而给createStore3、createStore2、createStore1、createStore0传参并执行
//2、给createStore0的返回值加工,进而给createStore1、createStore2、createStore3、createStore4的返回值加工,生成最终store
5、createStore实际运用(真实案例)
import { createStore, applyMiddleware, compose } from 'redux';
import reduxThunk from 'redux-thunk';//从UI组件直接dispatch action。它的主要思想是扩展action,使得action从只能是一个对象变成还可以是一个函数。
import rootReducer from 'reducers/index';
import DevTools from 'containers/DevTools';
export default function configureStore(preloadedState) {
const store = createStore(
rootReducer,
preloadedState,
compose(
applyMiddleware(reduxThunk),
DevTools.instrument()
)
)
return store
}
6、相关源码解析
(1)createStore
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, preloadedState); }
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
function getState() {
return currentState;
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
}
if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
subscribe: function subscribe(observer) {
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.');
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return { unsubscribe: unsubscribe };
}
}, _ref[result] = function () {
return this;
}, _ref;
}
dispatch({ type: ActionTypes.INIT });
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[result] = observable, _ref2;
}
(2)compose
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(function (a, b) {//reduce将最后一次计算结果暴露出去
return function () {
return a(b.apply(undefined, arguments));
};
});
}
(3)applyMiddleware
function applyMiddleware() {//这是一个加工dispatch的中间件
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function (reducer, preloadedState, enhancer) {
var store = createStore(reducer, preloadedState, enhancer);
var _dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch(action) {//此处为什么不是dispatch:store.dispatch
return _dispatch(action);
}
};
chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);//return reduxThunk(_ref)
});
_dispatch = compose.apply(undefined, chain)(store.dispatch);//_dispatch = (function (next){})(store.dispatch) ,这是dispatch的新定义。
return _extends({}, store, {//store里的dispatch,被这里的dispatch覆盖
dispatch: _dispatch
});
};
};
}
(4)combineReducers
function combineReducers(reducers) {
//下面是关于reducers的定义,它的key如fn1、fn2、fn3、fn4后来也成了state的key。此论依据“非常重要!”前面的代码
// finalReducers = reducers = {
// fn1 : function(){ return { key1 : "value1" } },
// fn2 : function(){ return { key2 : "value2" } },
// fn3 : function(){ return { key3 : "value3" } },
// fn4 : function(){ return { key4 : "value4" } },
// }
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
{
if (typeof reducers[key] === 'undefined') {
warning('No reducer provided for key "' + key + '"');
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers);
var unexpectedKeyCache = void 0;
{
unexpectedKeyCache = {};
}
var shapeAssertionError = void 0;
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
return function combination() {//这个返回值就是createStore(reducer, preloadedState, enhancer)中的reducer
// 下面是关于state的定义,它的key如fn1、fn2、fn3、fn4来自于reducers的key。此论依据“非常重要!”前面的代码
// nextState = state = { } = {
// fn1 : { key1 : "value1" },
// fn2 : { key2 : "value2" },
// fn3 : { key3 : "value3" },
// fn4 : { key4 : "value4" }
// }
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var action = arguments[1];
if (shapeAssertionError) {
throw shapeAssertionError;
}
{
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
if (warningMessage) {
warning(warningMessage);
}
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];//非常重要!previousStateForKey可能为undefined
var nextStateForKey = reducer(previousStateForKey, action);//非常重要!可能执行reducer(undefined,action),即执行reducer(defaultState,action)。
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(_key, action);
throw new Error(errorMessage);
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
// const defaultState = {
// loading: true,
// };
// export default function accountReducer(state = defaultState, action) {
// switch (action.type) {
// case "isRegister":
// //redux.js纯函数执行后,需要给state重新赋值,否则hasChanged不会为true,vuex.js不需要这样,因为它采用了递归监听
// return {...state, loading: action.loading||false }//action匹配成功,state的引用就改变了
// default:
// return state;//action匹配不成功,state的引用就不改变了
// }
// }
}
//遍历结束,一次性返回结果。
return hasChanged ? nextState : state;
};
}
(5)其它
function componentWillMount() {
const current = [
{ location: "222", url: "/" },
{ location: "333", url: "/carDetail" },
];
this.props.dispatch(menuChange(current))
}
export const menuChange = function (key) {
return function (dispatch) {
dispatch({
type: "menu_change",
key
})
}
}
三、redux-thunk实际运用
1、reduxThunk核心代码
//从UI组件直接dispatch action。它的主要思想是扩展action,使得action从只能是一个对象变成还可以是一个函数。
function reduxThunk(_ref) {//middleware(middlewareAPI)
var dispatch = _ref.dispatch;
var getState = _ref.getState;
return function (next) {//即return function(store.dispatch);next是dispatch的旧定义,即applyMiddleware函数的chain数组的下一项的执行结果。
return function (action) {//返回的函数是dispatch的新定义;其中action是参数,原本只能是对象,经改造后还可以是函数。
if (typeof action === 'function') {
return action(dispatch, getState);//把旧的dispatch、getState传进自定义函数参数里
}
return next(action);
};
};
}
2、reduxThunk全部代码
(function webpackUniversalModuleDefinition(root, factory) {
if (typeof exports === 'object' && typeof module === 'object')
module.exports = factory();//common.js模块下执行,factory()的执行结果为null
else if (typeof define === 'function' && define.amd)
define([], factory);//require.js异步模块下执行
else if (typeof exports === 'object')
exports["ReduxThunk"] = factory();
else
root["ReduxThunk"] = factory();
})(
this,
function () {
return (function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId])
return installedModules[moduleId].exports;
var module = installedModules[moduleId] = {
exports: {},
id: moduleId,
loaded: false
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.loaded = true;
return module.exports;
}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.p = "";
return __webpack_require__(0);
})([function (module, exports, __webpack_require__) {
module.exports = __webpack_require__(1);
},function (module, exports) {
'use strict';
exports.__esModule = true;
exports['default'] = reduxThunk;//这是最终的注入
function reduxThunk(_ref) {
var dispatch = _ref.dispatch;
var getState = _ref.getState;
return function (next) {// return function (store.dispatch)
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
};
}
}
])
}
);
四、React-Router3、4版本的区别
来源:https://zhuanlan.zhihu.com/p/28585911
1、V3版用法
const PrimaryLayout = props =>
<div className="primary-layout">
<header>Our React Router 3 App</header>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/user">User</Link>
</li>
</ul>
<main>
{props.children}
</main>
</div>;
const App = () =>
<Router history={browserHistory}>
<Route component={PrimaryLayout}>
<Route path="/home" component={HomePage} />
<Route path="/user" component={UsersPage} />
</Route>
</Router>;
render(<App/>, document.getElementById("root"));
2、v4版用法
const PrimaryLayout = () =>
<div className="primary-layout">
<header>Our React Router 4 App</header>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/User">User</Link>
</li>
</ul>
<main>
<Route path="/home" exact component={HomePage} />
<Route path="/user" component={UsersPage} />
</main>
</div>;
const App = () =>
<BrowserRouter>
<PrimaryLayout />
</BrowserRouter>;
render(<App/>, document.getElementById("root"));
五、react-redux的实际运用
1、react-redux把redux状态和React-Router路由连起来
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route component={PrimaryLayout}>
<IndexRoute component={HomePage} />//在IndexRoute的所有兄弟路由都没有激活时,该组件才显示。
<Route path="/user" component={UsersPage} />
</Route>
</Router>
</Provider>,
document.getElementById('app')
);
2、React-Redux 将所有组件分成两大类:UI 组件 和 容器组件
(1)UI 组件:负责 UI 的呈现,即 UI本身、UI接收数据 和 UI派发行为,由用户提供。
(2)容器组件:负责管理数据和业务逻辑,由 React-Redux 自动生成。
3、UI组件StartList的定义
class StartList extends Component {
render() {
const { detail,close } = this.props;
//openDialogue 和 closeDialogue没有被解构赋值
//this.props包含3部分:
//(1)mapStateToProps遍历的内容
//(2)mapDispatchToProps遍历的内容,如果该项缺失,那么将被dispatch取代
//(3)容器标签携带的内容
return (
<div>
<div>页面内容</div>
<button onClick={ this.props.openDialogue }>{detail}</button>
</div>
<div>
<div>弹窗内容</div>
<button onClick={ this.props.closeDialogue }>{close}</button>
</div>
)
}
}
4、mapStateToProps 方法定义。负责输入逻辑,即将 state 映射到 UI 组件的 props 里,
function mapStateToProps (state){
return {
todos: state.todos//todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
5、mapDispatchToProps 方法定义。负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
(1)为对象时,有key和value,key为组件的属性,value为function,即action creator,返回值为action
const mapDispatchToProps = {
closeDialogue: function(ownProps){
return {
type: 'dialogue',
filter: ownProps.filter
}
}
}
const mapDispatchToProps = {
closeDialogue: (ownProps) => {
type: 'dialogue',
filter: ownProps.filter
};
}
(2)为函数时,返回值为对象,有key和value,key为组件的属性,value为function,执行过程中会dispatch action
function mapDispatchToProps (dispatch, ownProps) {//ownProps(容器组件的props对象)
return {
closeDialogue: function(){
dispatch({
type: 'dialogue',
isShow: false
});
},
openDialogue: function(){
//1、同步请求时,此处只有下面这些代码
//2、异步请求时,此处将 ownProps 里的部分数据作为参数,向后台发送请求,获取返回值 result,在成功的回调里执行下面这些代码
dispatch({
type: 'dialogue',
isShow: true
});
dispatch({
type: 'detail',
data: result
});
},
};
}
6、容器组件LastList的定义。接收数据和派发行为,即把 mapStateToProps 和 mapDispatchToProps 传给 StartList 。
const LastList = connect(
mapStateToProps,
mapDispatchToProps
)(StartList)
<LastList detail="详情" close="关闭"/>
//附:简单实例
class ProjectNumber extends Component {
state = {
visible: false
};
showModal(){
const projectId = this.props.record.id;
this.props.dispatch(getCarInfo(projectId));
this.setState({
visible: true,
});
};
render() {
return (
<div>
<span onClick={this.showModal.bind(this)}>{this.props.record}</span>
<Modal visible={this.state.visible}></Modal>
</div>
);
}
}