11、React之分页组件(含勾选、过滤、表头纵横合并、子组件向父组件传值)、redux、redux-thunk、react-router、react-redux、React基础|版本与优化、Hook钩子、组件写法、插槽、Model多层弹窗组件、react.15.6源码、ant-design-pro构成、Vue与React的异同(2800行)
一、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:1800px; 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:1800px; 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)}/>) } //一、以下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,initRowspan){ var that=this; columns.forEach(function(item,index) { if(item.children){ initRowspan+=1; if(that.maxRowspan<initRowspan) that.maxRowspan = initRowspan; that.getMaxRowspanAndDataIndexs(item.children,initRowspan) }else{ that.dataIndexs.push(item.dataIndex)//生成表头“数据索引” } }); } //2、添加表头“每栏跨行数” 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; } }); } //二、以下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([]);//1行表头对应1个空数组 } } getCenterThead(columns,initThead,index){ var that=this; //被纵、横合并的单元格(th、td)及其右侧的单元格都会向右平移1格 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)}/> } //第1次执行时,遍历出columns的最外层内容,放进initThead的第1个数组 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){ //如果columns的某项有children,那就开始第2次执行,遍历的结果放进initThead的第2个数组 that.getCenterThead(children,initThead,index+1) } }) } getLastThead(thead,initThead){ var that=this; initThead.forEach(function(item,index){ thead.push(<tr key={index}>{item}</tr>)//将每行表头放进每个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} 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>) } } } //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 style={{display:'block',display:"flex",width:"1800px",marginTop:"20px"}}> <div style={{display:"flex"}}> <button style={{marginRight:"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 style={{display:"flex", flex:1, justifyContent:"flex-end"}}> <div style={{marginRight:"15px"}}> <span>转到第</span> <input id='inputQC' key={nowPageNum==1?Math.random():'key'} type="text" style={{width:"30px",margin:"0 5px"}} ref="input" onChange={this.inputChange.bind(this)} onKeyDown={this.onKeyDown.bind(this,event)} defaultValue={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={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: 'age' }, ], }, { title: '学校', children: [ { title: '年级', children: [ { title: '班级', children: [ { title: '科目', dataIndex: 'cross1' }, { title: '科目', dataIndex: 'cross2' }, ], }, { title: '班级', children: [ { title: '科目', dataIndex: 'cross3' }, { title: '科目', dataIndex: 'cross4' }, ], }, ], }, { title: '年级', children: [ { title: '班级', children: [ { title: '科目', dataIndex: 'cross5' }, { title: '科目', dataIndex: 'cross6' }, ], }, { title: '班级', children: [ { title: '科目', dataIndex: 'cross7' }, { title: '科目', dataIndex: 'cross8' }, ], }, ], }, ], }, { title: '省级', children: [ { title: '市级', children: [ { title: '县级', children: [ { title: '乡级', children: [ { title: '村级', dataIndex: 'village1', }, { title: '村级', dataIndex: 'village2', }, ], }, { title: '乡级', children: [ { title: '村级', dataIndex: 'village3', }, { title: '村级', dataIndex: 'village4', }, ], }, ], }, { title: '县级', children: [ { title: '乡级', children: [ { title: '村级', dataIndex: 'village5', }, { title: '村级', dataIndex: 'village6', }, ], }, { title: '乡级', children: [ { title: '村级', dataIndex: 'village7', }, { title: '村级', dataIndex: 'village8', }, ], }, ], }, ], }, { title: '市级', children: [ { title: '县级', children: [ { title: '乡级', children: [ { title: '村级', dataIndex: 'village9', }, { title: '村级', dataIndex: 'village10', }, ], }, { title: '乡级', children: [ { title: '村级', dataIndex: 'village11', }, { title: '村级', dataIndex: 'village12', }, ], }, ], }, { title: '县级', children: [ { title: '乡级', children: [ { title: '村级', dataIndex: 'village13', }, { title: '村级', dataIndex: 'village14', }, ], }, { title: '乡级', children: [ { title: '村级', children: [ { title: '组级', dataIndex: 'team1' }, { title: '组级', dataIndex: 'team2' }, ], }, { title: '村级', children: [ { title: '组级', dataIndex: 'team3' }, { title: '组级', dataIndex: 'team4' }, ], }, ], }, ], }, ], }, ], } ]; }; 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), age: '19岁', }; for(var j=0;j<=40;j++){ var crossKey = 'cross' + j; var villageKey = 'village' + j; var teamKey = 'team' + j; obj[crossKey] = j%2 == 0 ? '科1':'科2'; obj[villageKey] = j%2 == 0 ? '村1' : '村2'; obj[teamKey] = j%2 == 0 ? '组1' : '组2'; } data.push(obj) }; console.log( data ); 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 style={{'border':'1px solid #cbcbcb','width':'1780px','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> <span style={{'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 style={{'paddingBottom':'4px'}}> <span style={{'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实际运用(redux.4.0.0版源码,去掉注释和空行,共413行),redux,react-redux,redux-thunk 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);此时previousStateForKey !== nextStateForKey 为false if (typeof nextStateForKey === 'undefined') { var errorMessage = getUndefinedStateErrorMessage(_key, action); throw new Error(errorMessage); } nextState[_key] = nextStateForKey; hasChanged = hasChanged || previousStateForKey !== nextStateForKey; // const defaultState = { // loading: true, // }; // export default function accountReducer(state = defaultState, action) { // switch (action.type) { // //下面给state重新赋值,state的引用就改变了;此时previousStateForKey !== nextStateForKey,hasChanged为true 。 // //vuex.js不需要这样,因为它采用了递归监听 // case "change_second_text": // return {...state, current_item: action.key} // case "change_three_text": // return {...state, current_item_three: action.key} // case "isRegister": // return {...state, loading: action.loading||false }//action匹配成功,state的引用就改变了 // //下面action匹配不成功,state的引用就不改变了 // default: // return 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实际运用,redux,react-redux,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的实际运用,redux,react-redux,redux-thunk 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组件和容器组件,即StartList和LastList (1)组件定义 import React, {Component} from 'react'; class StartList extends Component { //var Button = React.createClass({ state = { visible: false } showModal(){ //如果mapDispatchToProps缺失,那么this.props.dispatch将出现 this.props.dispatch(action); this.setState({ visible: true, }); } componentWillMount(){} componentDidUpdate(){} componentWillUnmount(){} render() { const { detail, todos, active, closeDialogue } = this.props; return ( <div> <div> <div>this.props之-容器标签-携带的内容</div> <div>{detail}</div> </div> <div> <div>this.props之-mapStateToProps-携带的内容</div> <div>{todos}</div> <div>{active}</div> </div> <div> <div>this.props之-mapDispatchToProps-携带的内容</div> <button onClick={ closeDialogue }>解构-使用</button> <button onClick={ this.props.openDialogue }>不解构-使用</button> <button onClick={ this.showModal.bind(this) }>组件内部函数的调用</button> </div> </div> ) } } //mapStateToProps,负责输入逻辑,即将state映射到UI组件的props里 function mapStateToProps (state, ownProps) {//ownProps(容器组件的属性对象) return { todos: state.todos//todos: getVisibleTodos(state.todos, state.visibilityFilter) active: ownProps.filter === state.visibilityFilter } } //mapDispatchToProps,负责输出逻辑,即将用户对UI组件的操作映射成Action //mapDispatchToProps,为对象时,有key和value,key为组件的属性,value为function,即action creator,返回值为action const mapDispatchToProps = { closeDialogue: function(ownProps){ return { type: 'dialogue', filter: ownProps.filter } } } //mapDispatchToProps,为函数时,返回值为对象,有key和value,key为组件的属性,value为function,执行过程中会dispatch action function mapDispatchToProps (dispatch, ownProps) {//ownProps(容器组件的属性对象) return { openDialogue: function(){ //1、同步请求时,此处只有下面这些代码 //2、异步请求时,此处将 ownProps 里的部分数据作为参数,向后台发送请求,获取返回值 result,在成功的回调里执行下面这些代码 dispatch({ type: 'detail', data: ownProps.result }); }, }; } const LastList = connect(mapStateToProps,mapDispatchToProps)(StartList); export default LastList (2)组件使用 <LastList detail="详情"/> (3)组件说明 A、StartList,UI组件,负责UI的呈现,即UI本身、UI接收数据和UI派发行为,由用户提供 B、LastList,容器组件,负责管理数据和业务逻辑,由React-Redux自动生成,接收数据和派发行为 六、基础 1、React的缺点和优点 (1)缺点: A、React本身只是一个V,不是一个完整的框架,不是一套完整的框架 B、需要加上React-Router和React-Redux才能成为一套完整的框架 (2)优点: A、单向数据流动 B、虚拟DOM取代物理DOM作为操作对象 C、用JSX语法取代HTML模板,在JavaScript里声明式地描述UI 2、React组件3个部分 (1)3个部分,属性(props)、状态(state)以及生命周期方法 (2)组件一旦接收到的参数(即props)或自身状态有所改变,就会执行相应的生命周期方法,最后渲染 (3)整个过程完全符合传统组件所定义的组件职责(“属性更新”与“状态改变”) (4)以上内容来自《深入React技术栈》第18和30页 3、React生命周期 (1)实例化 A、React旧写法:getDefaultProps(){return{}};//获取默认属性 //React16.3写法:getDefaultProps(){return{}}; B、React旧写法:getInitialState(){return{}}; //React16.3写法:constructor C、React旧写法:componentWillMount//执行setState会合并到初始化状态中;获取从属性生成的状态;此后生命状态会被重置为null; //React16.3写法:getDerivedStateFromProps,让组件在props变化时更新state, //该方法返回一个对象用于更新state,如果返回null则不更新任何内容 D、render//执行setState会发起updateComponent导致-死循环 E、componentDidMount//执行setState,执行“更新完成”钩子,而不是“加载完成”钩子,所以不会导致-死循环; //这是发起异步请求去API获取数据的绝佳时期 (2)存在期 A、React旧写法:componentWillReceiveProps//执行setState会合并到状态中;此后生命状态会被重置为null //React16.3写法:getDerivedStateFromProps,让组件在props变化时更新state, //该方法返回一个对象用于更新state,如果返回null则不更新任何内容 B、shouldComponentUpdate//执行setState会发起updateComponent导致-死循环 C、React旧写法:componentWillUpdate//执行setState会发起updateComponent导致-死循环 //React16.3写法:此处没有componentWillUpdate D、render//执行setState会发起updateComponent导致-死循环 //React16.3写法:此处新增getSnapshotBeforeUpdate,可以访问更新前的props和state,执行setState会发起updateComponent导致-死循环 E、componentDidUpdate//可以有条件地执行setState (3)销毁期 A、componentWillUnmount//等待页面卸载,改变state没意义 4、重要概念 (1)纯函数,运行时不影响其他变量的函数,参数相同,返回值相同 (2)副作用函数,运行时影响其他变量的函数 (3)真实DOM,在其他的情况下,用户每次操作DOM(文档对象模型),都会改变逻辑树,都会引起页面的重新渲染 (4)虚拟DOM,在react情况下,用户每次操作DOM(文档对象模型),都会改变虚拟DOM (5)虚拟DOM,Virtual DOM,堆内存的一个JS对象,包含很多虚拟节点VNode,Virtual Node (6)key属性,标识节点,让Diff算法更高效地识别节点、更新虚拟DOM;index不能做key, A、用index做key时,新增或删除节点的操作,会使一个节点使用另一节点的index, B、进而使用它的key,进而使用它的data,进而产生错误 (7)状态提声(父组件的函数作为属性传给子组件) A、在父组件的constructor中定义状态 B、在父组件的方法中执行this.setState({}) C、把父组件的方法作为属性fromParent传给子组件 D、在子组件的方法中加上this.props.fromParent(e.target.value); E、触发子组件的事件,执行子组件的方法,改变父组件的状态 (8)ref(在标签中设置) A、示例1,值为字符串,直接通过ref获取DOM中的值 <input type="text" ref="thisRef"/> getInputValue(){ cnosole.log(this.refs.thisRef.value) } B、示例2,值为内置构造函数的实例,通过该实例获取DOM实例(官方推荐) this.thisRef = createRef(); <input type="text" ref={this.thisRef} /> getInputValue(){ cnosole.log(this.refs.thisRef.value) } C、示例3,值为函数,通过自定义函数的参数获取DOM实例,函数执行的时机为: A、组件被挂载后,回调函数被立即执行,回调函数的参数为该组件的具体实例。 B、组件被卸载或者原有的ref属性本身发生变化时,回调也会被立即执行,此时回调函数参数为null,以确保内存泄露。 <input type="text" ref={getRef}/> this.thisRef = null; getRef(ref){ this.thisRef = ref } getInputValue(){ cnosole.log(this.refs.thisRef.value) } (9)非约束性组件(非受控组件) <input type="text" defaultValue="a" />//用户输入A -> input 中显示A (10)约束性组件(受控组件) <input type="text" value={this.state.name} onChange={this.handleChange} /> handleChange: function(e) { this.setState({name: e.target.value}); } //用户输入内容A>触发onChange事件>handleChange中设置state.name="A"渲染input使他的value变成A 5、setState (1)this.setState接收两种参数 A、对象+函数(可选):传入的对象浅层合并到新的state中 B、函数+函数(可选): a、第1个参数函数接受2个参数,第1个参数是当前state,第2个参数是当前props,该函数返回1个对象,是要修改的state; b、第2个参数函数在state改变后触发,参数为更新后的state (2)this.setState调用后,发生了什么? A、React将参数对象与组件状态合并 B、构建新的虚拟DOM树 C、自动计算出新老虚拟DOM树的节点差异 D、最后根据差异对界面进行最小化渲染 (3)this.setState何时渲染, A、_compositeLifeCycleState是否为null,来决定是否重新渲染 B、因此在有的生命周期里,会产生死循环 (4)this.setState何时同步何时异步? A、异步, a、由react控制的事件,如onChange、onClick、onTouchMove等处理程序, b、React出于性能考虑,并不会立马执行修改state,而是先把当前更新state对象以及后续的state对象合并到一起,多次调用会合并, c、最后一次渲染,以此来进行性能优化 state = { count: 1 } this.setState({ count: this.state.count + 1, }) this.setState({ count: this.state.count + 2, }) console.log(this.state.count)//1,不能立马获取更新后的值 this.setState({count: 20}, function(){//函数先放在队列里,等状态更改完毕后,依次传入本次的状态值并执行列队 console.log(this.state.count);//20,文本已经被改变 }) componentDidUpdate() { console.log(this.state.count); } componentDidMount(){ console.log(this.state.count) } B、同步, a、react控制之外的事件中调用setState是同步更新的 b、比如原生js绑定的事件、setTimeout/setInterval、await等 state = { count: 1, } setTimeout(function(){ this.setState({ count: this.state.count + 1, }) console.log(this.state.count)//2 }) 6、跨多级组件传参原理,以React-Redux源码定义组件Provider为例 来源,http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html (1)定义组件Provider及getChildContext class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object } (2)在入口文件中调用组件Provider,并传入数据 import { Provider } from 'react-redux' import { createStore } from 'redux' import allStates from './reducers' import App from './components/App' let store = createStore(allStates); render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ) (3)后代组件获取数据 class VisibleTodoList extends Component { componentDidMount() { const { store } = this.context; this.unsubscribe = store.subscribe(() => this.forceUpdate() ); } render() { const props = this.props; const { store } = this.context; const state = store.getState(); // ... } } VisibleTodoList.contextTypes = { store: React.PropTypes.object } 7、表单 附、数据 this.state = { name:'', sex:'', city:'', citys:[ '北京','上海','深圳' ], hobby:[ { 'title':"睡觉", 'checked':true }, { 'title':"吃饭", 'checked':false }, { 'title':"敲代码", 'checked':true } ], }; (1)输入框示例 handelName(event){ this.setState({ name:event.target.value }) } <input type="text" value={this.state.name} onChange={this.handelName}/> (2)单选框示例 changeSex(event){ this.setState({ sex:event.target.value }) } <input type="radio" value="男" checked={this.state.sex=="男"} onChange={this.changeSex}/> <input type="radio" value="女" checked={this.state.sex=="女"} onChange={this.changeSex}/> (3)多选框示例 changeHobby(key){ var hobby=this.state.hobby; hobby[key].checked=!hobby[key].checked; this.setState({ hobby:hobby }) } { this.state.hobby.map(function(value,key){ return ( <span key={key}> <input type="checkbox" checked={value.checked} onChange={this.changeHobby.bind(this,key)}/>{value.title} </span> ) }) } (4)下拉框示例 getCity(event){ this.setState({ city:event.target.value }) } { <select value={this.state.city} onChange={this.getCity}> { this.state.citys.map(function (value,key) { return <option key={key}>{value}</option> }) } </select> } 七、版本与优化 附、Derived,/diˈraivd/,派生的 来源,https://github.com/facebook/react/releases 所有版本简介,https://github.com/facebook/react/blob/main/CHANGELOG.md#1702-march-22-2021 reactdom版本,https://cdn.bootcdn.net/ajax/libs/react-dom/16.6.0/cjs/react-dom.development.js 1、React主要版本发布时间和特征 (1)React,0.3.0版,2013年05月29日 (2)React,0.14.8版,2016年03月29日 (3)React,15.0.0版,2016年04月07日 A、15.1.0版,出现错误边界,error boundaries B、15.2.3版,出现纯函数组件,PureComponent (4)React,16.0.0版,2017年09月26日, A、新增componentDidCatch(--记录错误--) B、新增纤维fiber架构, C、弃用旧虚拟DOM, D、解决了递归调用无法中断和卡顿掉帧的问题 (5)React,16.3.0版,2018年03月29日, A、沿用旧生命周期componentWillMount,componentWillReceiveProps,componentWillUpdate, B、新增新生命周期getDerivedStateFromProps,getSnapshotBeforeUpdate, C、沿用方案、新增方案只能二选一, D、组件自身state更新,shouldComponentUpdate()>render()>getSnapshotBeforeUpdate()>componentDidUpdate() E、传递过来的props更新,getDerivedStateFromProps()>shouldComponentUpdate()>render()>getSnapshotBeforeUpdate()>componentDidUpdate() (6)React,16.4.0版,2018年06月24日, A、新增Suspense(--组件--) (7)React,16.6.0版,2018年10月23日, A、新增getDerivedStateFromError(--处理错误--) B、新增React.memo()//只在props更改的时候才会重新渲染 C、新增React.lazy()//与Suspense组合使用 (8)React,16.8.0版,2019年02月06日,见本页-Hooks详解- A、新增钩子函数Hooks,可以 B、避免组件继承React实例 C、实现状态管理 D、弃用生命周期 (9)React,17.0.0版,2020年10月20日 A、并没有添加任何面向开发人员的新特性 (10)React,18.0.0版,2022年03月29日 A、新增useDeferredValue(--使用延迟--)//与Suspense组合使用 B、新增useTransition(--使用过渡--) C、18.3.1,2024年04月26日,最新版本 2、优化方案 (1)Error Boundaries,错误边界, A、componentDidCatch,记录错误 B、getDerivedStateFromError,处理错误 import React from 'react'; class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error('Uncaught error:', error, errorInfo); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } } (2)Suspense,异步加载,//--加载指示器-- A、lazy import React, {Suspense, lazy} from 'react'; //Suspend,挂起;Suspense /səˈspens/,担心 const fetchData = () => new Promise((resolve) => setTimeout(() => resolve({ name: 'John Doe' }), 1000) ); const UserDetails = lazy(() => fetchData().then(data => { return { default: UserDetailsComponent, data: data }; //组件UserDetails-异步加载-组件UserDetailsComponent })); const UserDetailsComponent = (props) => { return <h1>Hello, {props.data.name}!</h1>; }; const App = () => ( <Suspense fallback={<div>Loading...</div>}> // <UserDetails /> </Suspense> ); B、useDeferredValue,在组件更新期间, 跳过复杂组件、提高渲染效率,随后使用新值重新渲染 import { useState, Suspense, useDeferredValue } from "react"; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<div>Loading...</div>}> <SearchResults query={deferredQuery}/> </Suspense> </> ); } (3)useTransition,过渡,//--加载指示器-- 注、isPending,是否有待处理状态;处理完毕,自行修改;/pend/;推迟决定 A、在ajax里调用startTransition。//替代方案,请求前后分别给isPending赋值 import React, { useState, useTransition } from 'react'; const App = () => { const [userData, setUserData] = useState(null); const [isPending, startTransition] = useTransition(); const fetchUserData = async () => { startTransition(async () => { const data = await fetchUserDataAPI(); setUserData(data); }); }; return ( <div> <button onClick={fetchUserData}>Load User Data</button> {isPending ? <div>Loading...</div> : <div>User Data: {userData}</div>} </div> ); }; B、直接调用startTransition。//替代方案,用定时器 来源,https://blog.csdn.net/weixin_48633811/article/details/134877658 import React, { useState, useTransition } from 'react'; function Example() { const [show, setShow] = useState(false); const [isPending, startTransition] = useTransition({ timeoutMs: 1000 }); const handleClick = () => { startTransition(() => { setShow(!show); }); }; return ( <div> <button onClick={handleClick} disabled={isPending}> {show ? '隐藏' : '显示'} </button> {isPending ? '正在加载...' : null} {show ? <div>这是要显示的内容</div> : null} </div> ); } 八、Hooks详解,redux,react-redux,redux-thunk 来源,https://blog.csdn.net/weixin_45654582/article/details/121058178 附、此前React组件的缺点 (1)(有状态)类组件的缺点,每个组件都要继承React实例 (2)(无状态)纯函数组件的缺点,没法进行状态管理;没有状态、生命周期、this 附、Hook特征 (1)组件都用函数声明 (2)state都用预函数管理 (3)组件没有this和生命周期 (4)可以使用外部功能和副作用 (5)Hook函数主要有6个,useState、useReducer、useContext、useRef、useEffect、useMemo;前3个与状态相关 1、useState,状态读写钩子 附、纯函数组件没有状态,useState用于为函数组件引入state状态,并进行状态数据的读写操作 (1)参数: 初始值 (2)返回值: 数组,包含2个元素,第1个为当前值,第2个为更新状态值的函数setState, (3)示例 import React,{ useState } from "react"; const NewCount = ()=> { const [ count, setCount ] = useState(0) addCount = ()=> { let newCount = count; setCount(newCount +=1) } return ( <div> <p> { count }</p> <button onClick={ addCount }>Count++</button> </div> ) } export default NewCount; (4)用户调用setState时,参数为值或函数,比如setState(newValue),setState(value => newValue) 2、useReducer,状态管理钩子, 附、1次管理1到多个状态;Reducer /riˈdjuːsə/ 减速器 (1)参数:共2个,第1个为reducer函数,第2个为状态的初始值 (2)返回值:1个数组,第1项为当前的状态值,第2项为dispatch函数 (3)示例 import { useReducer } from "react"; const HookReducer = () => { const reducer = (state, action) => { if (action.type === 'add') { return { ...state, count: state.count + 1 } } else if (action.type === 'minus') { return { ...state, count: state.num - 1 } } else { return state } } const [state, dispatch] = useReducer(reducer, { count: 0, num: 0 }) const addCount = () => { dispatch({ type: 'add' }) } const minusCount = () => { dispatch({ type: 'minus' }) } return ( <> <p>{state.count}{state.num}</p> <button onClick = {addCount} > useReducer < /button> <button onClick = {minusCount} > minusCount < /button> </> ) } export default HookReducer; 3、useContext,状态共享钩子, 附、类似于getChildContext (1)参数: 1个对象 (2)返回值: 1个对象,即上面的参数对象 (3)示例 A、父组件定义 const AppContext = React.createContext(); const HookTest = ()=> { const [num, setNum] = useState(0); return ( <AppContext.Provider value={{ num, setNum }}> <A context={AppContext}/> //{A({context: AppContext})} </AppContext.Provider> ) } export default HookTest; B、子组件定义 import React, { useContext } from "react"; const A = (props) = > { const { num, setNum } = useContext( props.context ); const onClick = () => { setNum(num => num + 1); }; return ( <p> <span>{ name }</span> <span>{ name }</span> <button onClick={onClick}>点击</button> </p> ) } 4、useRef,引用钩子 (1)参数,初始值 (2)返回值 A、只有current属性的可变对象{current: initialValue},更新current值,不会重渲染 B、在整个生命周期内一直存在 (3)示例 import{ useRef,useEffect} from "react"; const RefComponent = () => { let inputRef = useRef(initialValue); useEffect(() => { inputRef.current.focus(); }) return ( <input type="text" ref={inputRef}/> ) } 5、useEffect,副作用钩子 附、副作用函数,运行时影响其他变量的函数 附、相当于componentDidMount、componentDidUpdate、componentWillUnmount这三个生命周期 (1)参数: 2个 A、第1个参数是异步函数 a、执行时机 第2个参数缺失时,组件每次渲染后(等同于render) 第2个参数为数组时,组件挂载后(等同于componentDidMount)或数组发生变化后 b、返回值,也是useEffect的返回值,是函数 执行时机,组件卸载时执行 作用,清理副作用 B、第2个参数是数组 (2)返回值: 是第一个参数的返回值 (3)示例 import { useEffect } from 'react' function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1); useEffect(//这不是函数的定义,是函数的调用,可以多次执行,每次执行对应一个监听数据 function(){ console.log('类似于componentDidMount,通常在此处调用api获取数据') console.log('类似于componentDidUpdate,当count发生改变时执行') const id = setInterval(function(){ setCount(function(count){ return count + step }); },1000); return function(){ clearInterval(id); console.log('类似于componentWillUnmount,通常用于清除副作用'); } }, [step] ) return ( <div> <h1>{count}</h1> <input value={step} onChange={e => setStep(Number(e.target.value))} /> </div> ); } 6、useMemo,记忆钩子 附、如果父组件状态的改变不影响子组件,那么子组件不再重新渲染 (1)父组件 import {useMemo,memo} from 'react'; const Parent = () => { const [parentState,setParentState] = useState(0); const toChildComputed = useMemo(() => { console.log("需要传入子组件的计算属性"); return 1000; },[]) return (<div> <Button onClick={() => setParentState(val => val+1)}> 点击我改变父组件中与Child组件无关的state </Button> //将父组件的计算属性传入子组件 <Child computedParams={toChildComputed}></Child> <div>) } (2)子组件,被memo保护 const Child = memo(() => { consolo.log("我被打印了就说明子组件重新构建了") return <div><div> }) 九、React组件写法 1、React15.6.1版组件写法 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React实例</title> <script src="https://lib.baomitu.com/react/15.6.1/react.js"></script> <script src="https://lib.baomitu.com/react/15.6.1/react-dom.js"></script> <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> </head> <body> <div id="example"></div> </body> </html> <script type="text/babel"> var Button = React.createClass({ // class StartList extends Component { setNewNumber(number,event) { this.setState({number: this.state.number + 1}) }, getDefaultProps() { return { name: "计数器" }; }, getInitialState() { return{number: 0}; }, render() { return ( <div> <button onClick = {this.setNewNumber.bind(null,this.state.number,event)}>点击{this.props.name}</button> <Text myNumber = {this.state.number}></Text> </div> ); } }) var Text = React.createClass({ //一、以下实例化时期 getDefaultProps() { console.log("1.getDefaultProps 获取默认属性"); return { }; }, getInitialState() { console.log("2.getInitialState 获取初始状态"); return { }; }, componentWillMount() { console.log("3.componentWillMount 此组件将要被渲染到目标组件内部"); }, componentDidMount() { console.log("5.componentWillMount 此组件已经被渲染到目标组件内部"); }, //二、以下存在时期 componentWillReceiveProps() { console.log("6.componentWillReceiveProps 此组件将要收到属性"); }, shouldComponentUpdate(newProps, newState) { console.log("7.shouldComponentUpdate 组件是否应该被更新"); return true; }, componentWillUpdate() { console.log("8.componentWillUpdate 组件将要被更新"); }, componentDidUpdate() { console.log("10.componentDidUpdate 组件已经更新完成"); }, //三、以下销毁时期 componentWillUnmount() { console.log("11.componentWillUnmount 组件将要销毁"); }, render() { console.log("4和9.render 组件将要渲染"); return ( <div> <h3>{this.props.myNumber}</h3> </div> ); } }) ReactDOM.render( <div> <Button /> </div>, document.getElementById('example') ); </script> 2、React16.4.0版组件写法 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React实例</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> </head> <body> <div id="example"></div> </body> </html> <script type="text/babel"> class Button extends React.Component { //name="计算器";state = {number: 0}; //上下写法,二选一 constructor(props) { super(props); this.name="计算器"; this.state = {number: 0}; }; setNewNumber(number,event) { this.setState({number: this.state.number + 1}) }; render() { return ( <div> <button onClick = {this.setNewNumber.bind(this,this.state.number,event)}>点击{this.name}</button> <Text myNumber = {this.state.number}></Text> </div> ); } } class Text extends React.Component { //一、以下实例化时期 constructor(props) { super(props); console.log("2.constructor 获取初始状态"); } componentWillMount() { console.log("3.componentWillMount 此组件将要被渲染到目标组件内部"); } componentDidMount() { console.log("5.componentWillMount 此组件已经被渲染到目标组件内部"); } //二、以下存在时期 componentWillReceiveProps() { console.log("6.componentWillReceiveProps 此组件将要收到属性"); } shouldComponentUpdate(newProps, newState) { console.log("7.shouldComponentUpdate 组件是否应该被更新"); return true; } componentWillUpdate() { console.log("8.componentWillUpdate 组件将要被更新"); } componentDidUpdate() { console.log("10.componentDidUpdate 组件已经更新完成"); } //三、以下销毁时期 componentWillUnmount() { console.log("11.componentWillUnmount 组件将要销毁"); } render() { console.log("4和9.render 组件将要渲染"); return ( <div> <h3>{this.props.myNumber}</h3> </div> ); } } ReactDOM.render( <div> <Button /> </div>, document.getElementById('example') ); </script> 来源 https://www.runoob.com/try/try.php?filename=try_react_life_cycle2 十、插槽 1、portal插槽 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React插槽实例</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> </head> <body> <div><span>这是器内:</span><span id="container"></span></div> <div style="margin:20px;"></div> <div><span>这是器外:</span><span id="outer"></span></div> </body> <script type="text/babel"> const container = document.getElementById('container'); const outer = document.getElementById('outer'); class Model extends React.Component { constructor(props) { super(props); this.span = document.createElement('span'); } render() { return ReactDOM.createPortal( this.props.children, this.span ); } componentDidMount() { outer.appendChild(this.span); } componentWillUnmount() { outer.removeChild(this.span); } } class Parent extends React.Component { constructor(props) { super(props); this.state = {clicks: 0}; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(state => ({ clicks: state.clicks + 1 })); } render() { return ( <span onClick={this.handleClick}> <button>器内正常内容仍在器内</button> <span style={{paddingLeft:"8px"}}>{this.state.clicks}</span> <Model> <span> <button>器内插槽内容置于器外</button> <span style={{paddingLeft:"8px"}}>器内插槽内容置于器外</span> </span> </Model> </span> ); } } ReactDOM.render(<Parent/>, container); </script> </html> 2、React.Children插槽 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React插槽实例</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> </head> <body> <div id="container"></div> <div id="outer"></div> </body> <script type="text/babel"> class Parent extends React.Component { constructor(props) { super(props); this.state = {clicks: 0}; this.handleClick = this.handleClick.bind(this); } handleClick(event) { console.log(event) this.setState(state => ({ clicks: state.clicks + 1 })); } render() { var that = this; return ( <div> <div>{this.state.clicks}</div> <div><button onClick={this.handleClick}>clicks</button></div> <ul> { React.Children.map(this.props.children,function(item,index){ if(index !=1){ return <li onClick={that.handleClick}>{item}</li> }else{ return <li onClick={that.handleClick}>{item}---{index+1}</li> } }) } </ul> </div> ); } } ReactDOM.render(<Parent> <span style={{cursor:'pointer',userSelect: 'none'}}>插槽一</span> <span style={{cursor:'pointer',userSelect: 'none'}}>插槽二</span> <span style={{cursor:'pointer',userSelect: 'none'}}>插槽三</span> </Parent>, document.getElementById('container')); </script> </html> 十一、Model多层弹窗 1、不可拖拽(含插槽) <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React弹窗实例</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> .simpleDialog { position: fixed; width: 100%; height: 100%; top: 0; left: 0; display: none; justify-content: center; align-items: center; } .simpleDialog .mask { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background: black; opacity: 0.5; } .simpleDialog .content { position: fixed; background: white; opacity: 1; display: flex; flex-direction: column; } .simpleDialog .content .title { display: flex; background: blue; color: white; padding: 10px; cursor: pointer; } .simpleDialog .content .title { display: flex; background: blue; color: white; padding: 10px; cursor: pointer; } .simpleDialog .content .conform { display: flex; justify-content: center; padding: 10px; background: blue; } </style> </head> <body> <div id="container"></div> </body> </html> <script type="text/babel"> const container = document.getElementById('container'); class Model extends React.Component { constructor(props) { super(props); } componentDidUpdate(){ var id = this.props.id||'simpleDialog'; if(this.props.isShow){ document.getElementById(id).style.display = 'flex'; }else{ document.getElementById(id).style.display = 'none'; } } close() { if(this.props.close){ this.props.close() } } open() { if(this.props.open){ this.props.open() } } render() { return ( <div> <div className="simpleDialog" id={this.props.id||'simpleDialog'}> <div className="mask"></div> <div className="content"> <div className="title"> <span>系统消息</span> </div> <div style={{width:this.props.width||'800px',height:this.props.height||'600px'}}> { React.Children.map(this.props.children,function(item,index){ return item }) } </div> <div className="conform"> <button onClick={this.close.bind(this)}>关闭</button> <button onClick={this.open.bind(this)}>打开</button> </div> </div> </div> </div> ); } } class Parent extends React.Component { constructor(props) { super(props); this.state = { outShow: false, midShow: false, inShow: false, }; } outOpen() { this.setState({ outShow: true }); } outClose() { this.setState({ outShow: false }); } midOpen() { this.setState({ midShow: true }); } midClose() { this.setState({ midShow: false }); } inOpen() { this.setState({ inShow: true, }); } inClose() { this.setState({ inShow: false, }); } render() { return ( <div> <div><button onClick={this.outOpen.bind(this)}>出现弹窗</button></div> <Model isShow={this.state.outShow} open={this.midOpen.bind(this)} close={this.outClose.bind(this)} id="simpleDialog1" width="900px" height="600px"> 这是插槽1 <Model isShow={this.state.midShow} open={this.inOpen.bind(this)} close={this.midClose.bind(this)} id="simpleDialog2" width="600px" height="400px"> 这是插槽2 <Model isShow={this.state.inShow} close={this.inClose.bind(this)} id="simpleDialog3" width="300px" height="200px"> 这是插槽3 </Model> </Model> </Model> </div> ); } } ReactDOM.render(<Parent/>, container); </script> 2、可拖拽(含插槽) <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React弹窗实例</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> <script> function drag(wholeTitleId, wholeContentId) { var wholeTitleId = wholeTitleId||'titleId'; var wholeContentId = wholeContentId||'contentId'; var oDiv = document.getElementById(wholeContentId); if(!oDiv) return; oDiv.onmousedown = down; function processThis(fn, nowThis) { return function (event) { fn.call(nowThis, event); }; } function down(event) { event = event || window.event; if (event.target.id != wholeTitleId) return; this.initOffsetLeft = this.offsetLeft; this.initOffsetTop = this.offsetTop; this.initClientX = event.clientX; this.initClientY = event.clientY; this.maxOffsetWidth = (document.documentElement.clientWidth || document.body.clientWidth) - this.offsetWidth; this.maxOffsetHeight = (document.documentElement.clientHeight || document.body.clientHeight) - this.offsetHeight; if (this.setCapture) { this.setCapture(); this.onmousemove = processThis(move, this); this.onmouseup = processThis(up, this); } else { document.onmousemove = processThis(move, this); document.onmouseup = processThis(up, this); } } function move(event) { var nowLeft = this.initOffsetLeft + (event.clientX - this.initClientX); var nowTop = this.initOffsetTop + (event.clientY - this.initClientY); this.style.left = nowLeft + 'px'; this.style.top = nowTop + 'px'; } function up() { if (this.releaseCapture) { this.releaseCapture(); this.onmousemove = null; this.onmouseup = null; } else { document.onmousemove = null; document.onmouseup = null; } } }; </script> <style> .simpleDialog { position: fixed; width: 100%; height: 100%; top: 0; left: 0; display: none; justify-content: center; align-items: center; } .simpleDialog .mask { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background: black; opacity: 0.5; } .simpleDialog .content { position: fixed; background: white; opacity: 1; display: flex; flex-direction: column; } .simpleDialog .content .title { display: flex; background: blue; color: white; padding: 10px; cursor: pointer; } .simpleDialog .content .title { display: flex; background: blue; color: white; padding: 10px; cursor: pointer; } .simpleDialog .content .conform { display: flex; justify-content: center; padding: 10px; background: blue; } </style> </head> <body> <div id="container"></div> </body> </html> <script type="text/babel"> const container = document.getElementById('container'); class Model extends React.Component { constructor(props) { super(props); } componentDidUpdate(){ var id = this.props.id||'simpleDialog'; var title = this.props.title||'title'; var content = this.props.content||'content'; if(this.props.isShow){ document.getElementById(id).style.display = 'flex'; drag(title,content) }else{ document.getElementById(id).style.display = 'none'; } } close() { if(this.props.close){ this.props.close(); var content = this.props.content; document.getElementById(content).style.cssText = "position:fixed;"; } } open() { if(this.props.open){ this.props.open() } } render() { return ( <div> <div className="simpleDialog" id={this.props.id||'simpleDialog'}> <div className="mask"></div> <div className="content" id={this.props.content||'content'}> <div className="title" id={this.props.title||'title'}> <span>系统消息</span> </div> <div style={{width:this.props.width||'800px',height:this.props.height||'400px'}}> { React.Children.map(this.props.children,function(item,index){ return item }) } </div> <div className="conform"> <button onClick={this.close.bind(this)}>关闭</button> <button onClick={this.open.bind(this)}>打开</button> </div> </div> </div> </div> ); } } class Parent extends React.Component { constructor(props) { super(props); this.state = { outShow: false, midShow: false, inShow: false, }; } outOpen() { this.setState({ outShow: true }); } outClose() { this.setState({ outShow: false }); } midOpen() { this.setState({ midShow: true }); } midClose() { this.setState({ midShow: false }); } inOpen() { this.setState({ inShow: true, }); } inClose() { this.setState({ inShow: false, }); } render() { return ( <div> <div><button onClick={this.outOpen.bind(this)}>出现弹窗</button></div> <Model isShow={this.state.outShow} open={this.midOpen.bind(this)} close={this.outClose.bind(this)} id="simpleDialog1" title="title1" content="content1" width="900px" height="600px"> 这是插槽1 <Model isShow={this.state.midShow} open={this.inOpen.bind(this)} close={this.midClose.bind(this)} id="simpleDialog2" title="title2" content="content2" width="600px" height="400px"> 这是插槽2 <Model isShow={this.state.inShow} close={this.inClose.bind(this)} id="simpleDialog3" title="title3" content="content3" width="300px" height="200px"> 这是插槽3 </Model> </Model> </Model> </div> ); } } ReactDOM.render(<Parent/>, container); </script> 十二、React.15.6.0源码外框 /** * React v15.6.0 */ (function (allFn) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = allFn() } else if (typeof define === "function" && define.amd) { define([], allFn) } else { var tempGlobal; if (typeof window !== "undefined") { tempGlobal = window } else if (typeof global !== "undefined") { tempGlobal = global } else if (typeof self !== "undefined") { tempGlobal = self } else { tempGlobal = this } tempGlobal.React = allFn() } })(function () { var define, module, exports; return (function outerFn(first, second, third) { function recursion(number) { if (!second[number]) { if (!first[number]) { var error = new Error("Cannot find module '" + number + "'"); throw error.code = "MODULE_NOT_FOUND", error } var module = second[number] = { exports: {} }; first[number][0].call(module.exports, function (key) { var value = first[number][1][key]; return recursion(value ? value : key) }, module, module.exports, outerFn, first, second, third) } return second[number].exports//在react实例化的过程中,这行代码不但因获取依赖而多次执行,而且还因获取react实例而最后执行。 } for (var number = 0; number < third.length; number++) recursion(third[number]);//fn(16)第1次执行,执行结果没有变量接收 return recursion //执行到这,整个逻辑就快结束了。前两行可以合并为一行:return recursion(third[0]),同时下面的"(48)"应当删掉。 })( { 2: [function (_dereq_, module, exports) { var thisVar = _dereq_(138) }, { "25": 25, "30": 30 }], }, { }, [16] )(16)// fn(16)第2次执行,因为n[num]为真,所以直接返回n[num].exports并被挂在g.React上 }); 附、自执行 (function (allFn) { allFn() })(function () { return (function outerFn(m) { function recursion(n) { console.log( n ); return n } recursion(m) return recursion })(1)(16) }); 十三、ant-design-pro脚手架的构成 附、Pro的底座是umi,umi是一个(基于)webpack之上的(自动化)整合工具。 附、Pro的核心是umi,umi的核心是webpack。 1、web 技术 2、Umi-前端应用框架(可整个或部分复用的软件) (1)Node.js 前端开发基础环境 (2)Webpack 前端必学必会的打包工具 (3)React Router 路由库,被dva内置 (4)proxy 反向代理工具 (5)dva 轻量级的应用框架(可整个或部分复用的软件) (6)fabric 严格但是不严苛的 lint 规则集 (7)TypeScript 带类型的 JavaScript 3、Ant Design 前端组件库 4、ProComponents 模板组件 5、useModel 简易数据流 6、编译时和运行时 (1)编译时,环境是node环境,可以使用fs,path等功能;没有使用webpack,不能使用jsx,不能引入图片 (2)运行时,编译完成,开始在浏览器环境运行,不能使用fs、path,有跨域的问题;这个环境被webpack编译过,可以写jsx,导入图片 7、Umi的插件 (1)plugin-access,权限管理 (2)plugin-analytics,统计管理 (3)plugin-antd,整合 antd UI 组件 (4)plugin-initial-state,初始化数据管理 (5)plugin-layout,配置启用 ant-design-pro 的布局 (6)plugin-locale,国际化能力 (7)plugin-model,基于 hooks 的简易数据流 (8)plugin-request,基于 umi-request 和 umi-hooks 的请求方案 8、Umi其它 (1)配置:开发配置和(浏览器)运行配置 (2)路由:配置路由和约定式路由 (3)插件:id和key,每个插件都会对应一个id和一个key,id是路径的简写,key是进一步简化后用于配置的唯一值 十四、Vue与React的异同 1、相同点: (1)用于创建UI的js库 (2)单向数据流 (3)用虚拟DOM (4)基于组件 2、不同点: (1)vue使用html模板;react使用jsx模板 (2)vue有双向绑定语法;react没有 (3)vue有computed和watch;react没有