一、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; 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; 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 id="container"></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)}/>) } class TablePage extends React.Component { constructor(props) { super(props); this.state = {}; this.initThead = []; this.dataIndexs = [];//把表头每一栏最后一项的dataIndex放到这里,作为表头和表体关联的依据 this.maxRowspan = 1;//表头最大跨行数 if(props.giveParentClickAllPages){ props.giveParentClickAllPages(this) } } getDataIndexsAndMaxRowspan(columns,maxRowspan){ var that=this; columns.forEach(function(item,index) { if(item.children){ maxRowspan+=1; if(maxRowspan>that.maxRowspan) that.maxRowspan = maxRowspan; that.getDataIndexsAndMaxRowspan(item.children,maxRowspan) }else{ that.dataIndexs.push(item.dataIndex) } }); } addEachColumnRowspan(columns,maxRowspan){ var that=this; columns.forEach(function(item,index) { if(item.children){ item.thisRowspan=1; that.addEachColumnRowspan(item.children,maxRowspan-1) }else{ item.thisRowspan=maxRowspan; } }); } addEachColumnColspan(columns){ var that=this; columns.forEach(function(item) { if(item.children){ if(!item.thisColspan)that.addThisColumnColspan(item,item.children) }else{ item.thisColspan=1; } }); } addThisColumnColspan(item,children){ var that=this; children.forEach(function(child) { var thisChildren= child.children; if(thisChildren){ that.addThisColumnColspan(item,thisChildren); 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, }) } clickAllPages(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.getDataIndexsAndMaxRowspan(columns,this.maxRowspan); 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可传入属性 //1、numOptions:每页显示多少项 //2、text:总项数前后的说明文字 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 allPagesNum = this.props.divideSource.allPagesNum; var nowPageNum = this.props.divideSource.nowPageNum; if (allPagesNum >= 1 && allPagesNum <= 10) { for (var i = 1; i <= allPagesNum; i++) { numButton.push(<button key={i} style={i==nowPageNum?{color:'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} style={{color:'red'}} onClick={this.clickButton.bind(this,nowPageNum)}>{nowPageNum}</button>); } else { for (i = 1; i <= nowPageNum; i++) { numButton.push(<button key={i} style={i==nowPageNum?{color:'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>) } } } 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>) } return ( <div style={{display:'block',display:"flex","1000px",marginTop:"20px"}}> <div style={{display:"flex"}}> <button style={{marginRight:"5px"}} disabled={this.props.divideSource.nowPageNum===1} onClick={this.clickButton.bind(this,'front')}>上一页</button> <div>{ numButton }</div> <button disabled={this.props.divideSource.nowPageNum===this.props.divideSource.allPagesNum} onClick={this.clickButton.bind(this,'back')}>下一页</button> </div> <div style={{display:"flex", flex:1, justifyContent:"flex-end"}}> <div style={{marginRight:"15px"}}> <span>转到第</span> <input id='inputQC' key={this.props.divideSource.nowPageNum==1?Math.random():'key'} type="text" style={{"30px",margin:"0 5px"}} ref="input" onChange={this.inputChange.bind(this)} onKeyDown={this.onKeyDown.bind(this,event)} defaultValue={this.props.divideSource.inputValue}/> <span>页</span> <button style={{margin:"0 5px"}} onClick={this.clickButton.bind(this,'leap')}>Go</button> </div> <div> <span>每页显示</span> <select style={{margin:"0 5px"}} ref="select" defaultValue={this.props.divideSource.eachPageItemsNum||10} onChange={this.pageNumLeap.bind(this)}> { selectOption } </select> <span>{(this.props.text&&this.props.text.unit)||"条"}</span> </div> <div> <span>{this.props.text&&this.props.text.frontMoreText}</span> <span>{(this.props.text&&this.props.text.totalText)||"共"}</span> <span>{this.props.divideSource.allItemsNum||0}</span> <span>{(this.props.text&&this.props.text.totalUnit)||"项"}</span> <span>{this.props.text&&this.props.text.backMoreText}</span> </div> </div> </div> ) } } 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: '未启用,无选择项!', }, }; }; componentWillMount() { this.divideClick(1,10) } 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), age:(i+11)+'岁', street:'第'+(i+3)+'大街', building:(i+2)+'号楼', number:((i+1)+'0'+5)+'室', companyAddress:'公司地址'+(i+5), companyName:'公司名称'+(i+6), gender:(i%2===0?'男':'女') }; 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, }) } getChildClickAllPages(that){ this.childRef=that;//把子组件的实例赋值给父组件的childRef属性。非常重要!!! } clickAllPages(dataSource){ this.childRef.clickAllPages(dataSource)//在父组件里调用子组件的方法。非常重要!!! } changeValue(key,event){ this.setState({ filter:{...this.state.filter,[key]:event.target.value} }) } render() { var columns = [ { title: '', dataIndex: 'checkQC', }, { title: '序号', dataIndex: 'order', }, { title: 'Name', dataIndex: 'name', }, { title: 'Person', children: [ { title: 'Age', dataIndex: 'age' }, { title: 'Family Address', children: [ { title: 'Street', dataIndex: 'street' }, { title: 'Block', children: [ { title: 'Building', dataIndex: 'building' }, { title: 'Door No.', dataIndex: 'number' }, ], }, ], }, ], }, { title: 'Company', children: [ { title: 'Company Address', dataIndex: 'companyAddress' }, { title: 'Company Name', dataIndex: 'companyName' }, ], }, { title: 'Gender', dataIndex: 'gender' }, ]; var {dataSource,divideSource,checkSource,filter}={...this.state}; return ( <div> <div>以下是过滤示例</div> <div style={{'border':'1px solid #cbcbcb','width':'980px','padding':'10px','margin':'6px 0'}}> <div style={{'display':'flex'}}> <div style={{'width':'212px'}}> <label style={{'paddingRight':'4px'}}>输入框示例</label> <input type='text' placeholder='请输入' onChange={this.changeValue.bind(this,'input')} style={{'border':'1px solid #cbcbcb','width':'100px'}}/> </div> <div style={{'width':'174px'}}> <label style={{'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 style={{'width':'500px'}}> <span>过滤条件为:{JSON.stringify(this.state.filter)}</span> <button onClick={this.divideClick.bind(this,3,10)}>过滤</button> <button onClick={this.divideClick.bind(this,1,10)}>刷新</button> </div> </div> </div> <div style={{'paddingBottom':'4px'}}> <span style={{'paddingRight':'10px'}}><TwoImg isTrue={checkSource.isSelectAllPages && checkSource.allExcludedIds.length===0} clickImg={this.clickAllPages.bind(this,dataSource)}/></span> <span>{checkSource.textAllPages}</span> </div> <TablePage idKey='id' columns={columns} dataSource={dataSource} checkSource={checkSource} checkboxClick={this.checkboxClick.bind(this)} giveParentClickAllPages={this.getChildClickAllPages.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": // 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> ); } }