一、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.thKeys = [];//表头索引,表头和表体关联的依据
this.maxRowspan = 1;//表头最大跨行数
if(props.giveParentInstance){
props.giveParentInstance(this)//子组件向父组件传值-之三-给函数传参并执行
}
}
//表头跨行跨栏的总思路
//(1)处理跨行的时候,上面决定下面,要看上面有几个单行
//(2)处理跨栏的时候,下面决定上面,要看下面有几个单栏
//(3)多次使用递归
//一、以下2个函数,解决“表头跨行”问题
//1、获取表头“最大跨行数”
getMaxRowspan(columns,initRowspan){
var that=this;
columns.forEach(function(item,index) {
if(item.children){
initRowspan+=1;
if(that.maxRowspan<initRowspan) that.maxRowspan=initRowspan;
that.getMaxRowspan(item.children,initRowspan)//使用递归
}else{
that.thKeys.push(item.thKey)//收集最后1行表头的“key”
}
});
}
//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,itemChildren){
var that=this;
itemChildren.forEach(function(itemChild) {
//以下处理item栏的某个子级
var itemChildChildren=itemChild.children;
if(itemChildChildren){//item栏的某个子级,如果还存在子级
that.addThisColumnColspan(item,itemChildChildren);//使用递归
//以下,给item栏的所有子级,添加“每栏跨栏数”
that.addEachColumnColspan(itemChildren); //此处可能重复添加
}else{//item栏的某个子级,如果不存在子级
if(item.thisColspan){
item.thisColspan+=1;
}else{
item.thisColspan=1;
}
itemChild.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} thKey={item.thKey||''}>{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.thKeys.length;i++){
var indexIn=that.thKeys[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.thKeys = [];
this.getMaxRowspan(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: '',
thKey: 'checkQC',
},
{
title: '序号',
thKey: 'order',
},
{
title: '个人信息',
children: [
{
title: '姓名',
thKey: 'name'
},
{
title: '年龄',
thKey: 'age'
},
],
},
{
title: '学校',
children: [
{
title: '年级',
children: [
{
title: '班级',
children: [
{
title: '科目',
thKey: 'cross1'
},
{
title: '科目',
thKey: 'cross2'
},
],
},
{
title: '班级',
children: [
{
title: '科目',
thKey: 'cross3'
},
{
title: '科目',
thKey: 'cross4'
},
],
},
],
},
{
title: '年级',
children: [
{
title: '班级',
children: [
{
title: '科目',
thKey: 'cross5'
},
{
title: '科目',
thKey: 'cross6'
},
],
},
{
title: '班级',
children: [
{
title: '科目',
thKey: 'cross7'
},
{
title: '科目',
thKey: 'cross8'
},
],
},
],
},
],
},
{
title: '省级',
children: [
{
title: '市级',
children: [
{
title: '县级',
children: [
{
title: '乡级',
children: [
{
title: '村级',
thKey: 'village1',
},
{
title: '村级',
thKey: 'village2',
},
],
},
{
title: '乡级',
children: [
{
title: '村级',
thKey: 'village3',
},
{
title: '村级',
thKey: 'village4',
},
],
},
],
},
{
title: '县级',
children: [
{
title: '乡级',
children: [
{
title: '村级',
thKey: 'village5',
},
{
title: '村级',
thKey: 'village6',
},
],
},
{
title: '乡级',
children: [
{
title: '村级',
thKey: 'village7',
},
{
title: '村级',
thKey: 'village8',
},
],
},
],
},
],
},
{
title: '市级',
children: [
{
title: '县级',
children: [
{
title: '乡级',
children: [
{
title: '村级',
thKey: 'village9',
},
{
title: '村级',
thKey: 'village10',
},
],
},
{
title: '乡级',
children: [
{
title: '村级',
thKey: 'village11',
},
{
title: '村级',
thKey: 'village12',
},
],
},
],
},
{
title: '县级',
children: [
{
title: '乡级',
children: [
{
title: '村级',
thKey: 'village13',
},
{
title: '村级',
thKey: 'village14',
},
],
},
{
title: '乡级',
children: [
{
title: '村级',
children: [
{
title: '组级',
thKey: 'team1'
},
{
title: '组级',
thKey: 'team2'
},
],
},
{
title: '村级',
children: [
{
title: '组级',
thKey: 'team3'
},
{
title: '组级',
thKey: '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;//1/2、把子组件的实例赋值给父组件的childRef属性。!!!
}
clickParentAllPages(dataSource){
this.childRef.clickChildAllPages(dataSource)//2/2、在父组件里调用子组件的方法。
}
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={{'padding':'14px 0 4px 0'}}>
<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() {
//applyMiddleware为Redux的store增强功能,将redux-thunk中间件应用到store中!!!
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) {
//1/2,下面是关于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
// 2/2,下面是关于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];
var nextStateForKey = reducer(previousStateForKey, action);
//关于重新渲染!!!
//previousStateForKey可能为undefined
//可能执行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) {
// //下面action匹配成功,就算值没有改变,但state的引用改变了;此时
// //previousStateForKey !== nextStateForKey,hasChanged为true,Redux认为状态已更新
// //除非组件做了shouldComponentUpdate或React.memo优化,否则触发重新渲染
// 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的引用就不改变了
// 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,不同组件之间共享数据!!!
附、redux-thunk,允许编写返回函数而不是直接创建action对象,可以在action中执行异步操作(如API请求),并在操作完成后dispatch相应的action
1、reduxThunk核心代码
function reduxThunk(_ref) { //middleware的工厂函数,接收{dispatch,getState}(即middlewareAPI)
var dispatch = _ref.dispatch; //解构出原始的store.dispatch
var getState = _ref.getState; //解构出store.getState
return function (next) { //返回的函数接收`next`(即链中下一个middleware的dispatch或原始的store.dispatch)
return function (action) { //返回改造后的dispatch函数
if (typeof action === 'function') { //如果action是函数(thunk action)
return action(dispatch, getState); //执行该函数,并注入dispatch和getState作为参数。action的执行结果也是dispatch的执行结果
}
return next(action); //否则,直接交给下一个middleware(或最终reducer)
};
};
}
2、reduxThunk使用示例
(1)示例1
export function fetchUser(userId) {
return function(dispatch) {
//发送请求前,dispatch一个加载中的action
dispatch({ type: 'FETCH_USER_REQUEST' });
getData(`https://api.example.com/users/${userId}`).
then(function(user) {
//异步操作成功后,dispatch成功的action
dispatch({
type: 'FETCH_USER_SUCCESS',
payload: user
});
}).catch(function(error) {
//异步操作失败后,dispatch失败的action
dispatch({
type: 'FETCH_USER_FAILURE',
payload: error.message
});
});
};
}
//在组件中调用
this.props.dispatch(fetchUser(123));
(2)示例2
import { useDispatch } from 'react-redux';
function UserProfile({ userId }) {
const dispatch = useDispatch();
const handleLoadUser = () => {
dispatch( //C处,A处的返回值“透传”至此处
(dispatch, getState) => { //B处,B处的返回值也是C处的返回值。左侧的函数就是上面的action
return getInfo(userId) //A处,A处的返回值也是B处的返回值。根据源码,action的执行结果也是dispatch的执行结果
.then(data => {
dispatch({ type: 'USER_INFO_SUCCESS', payload: data });
return data; //将结果传递给外部
});
}
).then(userData => {
console.log('获取用户数据成功:', userData);
});
};
return (
<button onClick={handleLoadUser}>
加载用户信息
</button>
);
}
3、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);
};
};
}
}
])
}
);
4、请给我一个示例项目,要求用到redux,react-redux,redux-thunk
(1)actions/userActions.js //生成action
import {
FETCH_USERS_REQUEST,
FETCH_USERS_SUCCESS,
FETCH_USERS_FAILURE,
SELECT_USER,
CLEAR_SELECTED_USER
} from '../reducers/userReducer';
// 同步 action 创建函数
export const selectUser = (user) => {
return {
type: SELECT_USER,
payload: user
};
};
export const clearSelectedUser = () => {
return {
type: CLEAR_SELECTED_USER
};
};
//异步action创建函数(使用thunk)
export const fetchUsers = () => {
return (dispatch) => {
dispatch({ type: FETCH_USERS_REQUEST });
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json()) //先解析JSON
.then(data => { //再处理解析后的数据
dispatch({
type: FETCH_USERS_SUCCESS,
payload: data
});
})
.catch(error => {
dispatch({
type: FETCH_USERS_FAILURE,
payload: error.message || '未知错误'
});
});
};
};
(2)components/UserList.js //调用action
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers, selectUser } from '../actions/userActions';
const UserList = () => {
const dispatch = useDispatch();
const { items, loading, error } = useSelector(state => state.users);
// 组件挂载时获取用户数据
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
if (loading) {
return <div className="loading">Loading users...</div>;
}
if (error) {
return <div className="error">Error: {error}</div>;
}
return (
<div className="user-list">
<h2>User List</h2>
<ul>
{items.map(user => (
<li
key={user.id}
onClick={() => dispatch(selectUser(user))} //改变状态3/3
className="user-item"
>
{user.name} ({user.email})
</li>
))}
</ul>
</div>
);
};
export default UserList;
(3)reducers/userReducer.js //改变状态!!!
//初始状态
const initialState = {
items: [],
loading: false,
error: null,
selectedUser: null
};
//定义 action 类型常量
export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';
export const SELECT_USER = 'SELECT_USER';
export const CLEAR_SELECTED_USER = 'CLEAR_SELECTED_USER';
//reducer 函数
const userReducer = (state = initialState, action) => { //改变状态1/3
switch (action.type) {
case FETCH_USERS_REQUEST:
return {
...state,
loading: true,
error: null
};
case FETCH_USERS_SUCCESS:
return {
...state,
loading: false,
items: action.payload,
error: null
};
case FETCH_USERS_FAILURE:
return {
...state,
loading: false,
error: action.payload
};
case SELECT_USER:
return {
...state,
selectedUser: action.payload
};
case CLEAR_SELECTED_USER:
return {
...state,
selectedUser: null
};
default:
return state;
}
};
export default userReducer;
(4)App.css(略)
(5)App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
import './App.css';
const App = () => {
return (
<Provider store={store}>
<div className="app-container">
<h1>Redux Thunk User App</h1>
<div className="app-content">
<UserList/>
<UserDetail/>
</div>
</div>
</Provider>
);
};
export default App;
(6)store.js
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import userReducer from './reducers/userReducer';
//组合多个reducer(如果有多个的话)
const rootReducer = combineReducers({
users: userReducer //改变状态2/3
});
//创建store,应用thunk中间件,并集成Redux DevTools
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
四、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,
});
}
modelShow() {
this.showModal(); //1/2、主动调用时this指向清晰
}
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>
<button onClick={ this.modelShow.bind(this) }>组件内部函数的调用</button>
//2/2、如果没有bind(this),上面的this应做如下解释!!!
//this.modelShow指向函数的引用,其内部的this指向调用该函数的对象
//在严格模式下,不会默认绑定到全局对象,this是undefined(默认)
//在非严格模式下,函数是独立调用,this是全局对象
</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自动生成,接收数据和派发行为
六、React.js基础!!!
1、React的缺点和优点
(1)优点:
A、单向数据流,使数据流向清晰,便于调试和维护
a、父组件通过props向子组件传值,子组件通过回调函数与父组件通信,实现数据反向传递
b、注意:子组件不应直接修改父组件传递的props,这违反单向数据流原则
B、使用虚拟DOM提升性能,减少对真实DOM的直接操作,降低浏览器重绘重排开销
C、采用JSX语法,允许在JavaScript中声明式描述UI,使代码结构更清晰,开发效率更高
(2)缺点:
A、React、Vue、Angular本身专注于视图层(V),并非完整的MVC/MVVM框架,需要结合其他库来构建完整应用
B、通常需要配合路由库(如React-Router、Vue Router、Angular Router)和状态管理库(如Redux、Pinia、NgRx)等,才能构建复杂应用
2、React组件3个核心部分
(1)3个核心部分:属性(props)、状态(state)以及生命周期方法
(2)当组件接收的props发生变化或自身state改变时,会触发相应的生命周期方法,最终重新渲染UI
(3)整个过程符合组件的核心职责,即根据属性更新和状态改变来管理自身行为和视图展示
(4)以上内容参考《深入React技术栈》第18和30页
3、React生命周期(基于React16.3及以上版本)
(1)挂载阶段
A、获取默认属性:旧写法使用getDefaultProps(),React16.3及以上通过类的static defaultProps定义
B、初始化状态:旧写法使用getInitialState(),React16.3及以上在constructor中初始化state
C、getDerivedStateFromProps(props,state):静态方法,在调用render方法之前调用,根据props更新state,返回新的state对象或null
D、render:渲染UI,此处调用setState会导致死循环,应避免
E、componentDidMount:组件挂载到DOM后调用,可在此处发起异步请求、订阅事件等,调用setState会触发重新渲染
(2)更新阶段
A、getDerivedStateFromProps(props,state):在更新时也会被调用,功能同挂载阶段
B、shouldComponentUpdate(nextProps,nextState):决定组件是否重新渲染,返回布尔值,此处调用setState会导致死循环
C、getSnapshotBeforeUpdate(prevProps,prevState):在render之后、DOM更新之前调用,返回值会作为参数传递给componentDidUpdate
D、render:同挂载阶段,避免在此处调用setState
E、componentDidUpdate(prevProps,prevState,snapshot):DOM更新后调用,可在此处根据前后props/state差异执行操作,可条件性调用setState
(3)卸载阶段
A、componentWillUnmount:组件卸载前调用,用于清理定时器、取消订阅等,此时修改state不会触发重新渲染
4、重要概念
(1)纯函数:指在执行过程中不依赖外部状态且不产生副作用的函数,相同的输入始终会得到相同的输出,不会修改函数外部的变量或状态
(2)副作用函数:在执行时
A、修改外部变量或状态(如全局变量、DOM、数据库等)
B、依赖外部可变状态(如读取全局变量、API数据等)
C、执行异步操作(如网络请求、定时器等)
(3)DOM改变与页面渲染
A、操作真实DOM,如修改样式、结构,会触发浏览器的重排(Reflow)和重绘(Repaint),频繁操作会非常耗时
B、操作虚拟DOM,先在内存中计算DOM树差异,最后批量更新真实DOM,可减少重排重绘次数,降低性能开销
C、虚拟DOM是堆内存中的一个JS对象,包含很多虚拟节点(VNode,VirtualNode),用于描述真实DOM的结构和属性
(4)key属性:用于标识列表中的节点,帮助Diff算法更高效地识别节点的新增、删除和移动,优化虚拟DOM更新;不建议使用index作为key
A、用index做key时,新增或删除节点会导致后续节点的index发生变化
B、可能使节点复用错误的key,导致DOM元素与数据不匹配,产生渲染错误或状态异常
(5)状态提升(将共享状态提升到最近的共同父组件,通过父组件的函数作为属性传给子组件实现状态共享)
A、在父组件中定义状态(可在constructor或类属性中)
B、在父组件的方法中执行this.setState({})更新状态
C、把父组件的方法作为属性(如fromParent)传给子组件
D、在子组件的方法中通过this.props.fromParent(参数)调用父组件方法(如this.props.fromParent(e.target.value))
E、触发子组件的事件时,执行子组件的方法,调用父组件方法以改变父组件的状态,状态变化会向下传递给相关子组件
(6)ref(在标签中设置,用于访问DOM元素或组件实例)
A、示例1,值为字符串(官方不推荐),通过ref获取DOM中的值:
<inputtype="text"ref="thisRef"/>
getInputValue(){
console.log(this.refs.thisRef.value)
}
B、示例2,值为createRef创建的实例(官方推荐),通过该实例的current属性获取DOM实例:
this.thisRef=React.createRef();
<input type="text" ref={this.thisRef}/>
getInputValue(){
console.log(this.thisRef.current.value)
}
C、示例3,值为函数,通过自定义函数的参数获取DOM实例,函数执行的时机为:
a、组件被挂载后,回调函数被立即执行,回调函数的参数为该DOM元素或组件的具体实例。
b、组件被卸载或者原有的ref属性本身发生变化时,回调也会被立即执行,此时回调函数参数为null,以避免内存泄漏。
<input type="text" ref={(ref)=>this.thisRef=ref}/>
getInputValue(){
console.log(this.thisRef.value)
}
(7)非受控组件(非约束性组件):表单元素的状态由DOM自身管理,通过ref获取值
<input type="text" defaultValue="a"/>//用户输入A->input中显示A
(8)受控组件(约束性组件):表单元素的状态由React组件的state管理,通过onChange事件同步值
<input type="text" value={this.state.name} onChange={this.handleChange}/>
handleChange=(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,该函数返回一个对象表示要修改的state;
b、第2个参数函数在state更新完成且组件重新渲染后触发,参数为更新后的state(实际使用中通常通过this.state获取)
(2)this.setState调用后,发生的过程:
A、React将参数指定的状态更新合并到组件的状态中
B、触发组件重新渲染,构建新的虚拟DOM树
C、通过Diff算法计算新老虚拟DOM树的节点差异
D、根据差异对真实DOM进行最小化更新,完成界面渲染
(3)this.setState的渲染时机:
A、React会根据批处理机制决定何时执行渲染,通常会将多个setState调用合并后一次性执行渲染
B、若在某些生命周期方法(如render、shouldComponentUpdate等)中调用setState,可能会导致无限渲染循环
(4)this.setState的同步与异步特性:
A、异步更新场景:
a、在React合成事件中(如onChange、onClick、onTouchMove等)
b、在React生命周期方法中(如componentDidMount、componentDidUpdate等)
c、React出于性能优化,会将多次setState调用合并,批量更新状态并只执行一次渲染
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},()=>{
console.log(this.state.count)//20,状态更新并渲染后执行
})
componentDidUpdate(){
console.log(this.state.count)//20,组件更新后触发
}
componentDidMount(){
console.log(this.state.count)//1,初始渲染后的值
}
B、同步更新场景:
a、在React控制之外的事件中,setState是同步更新的
b、例如原生DOM事件、setTimeout/setInterval回调、Promise.then/catch、async/await等异步操作中
state={count:1}
setTimeout(()=>{
this.setState({count:this.state.count+1})
console.log(this.state.count)//2,同步获取更新后的值
},0)
6、跨多级组件传参原理,以react-redux源码定义组件Provider为例
(1)定义组件Provider及getChildContext(基于React旧版contextAPI)
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
//需通过childContextTypes声明提供的context类型(React.PropTypes在v15.5后已迁移至prop-types库)
Provider.childContextTypes = {
store: PropTypes.object.isRequired
}
(2)在入口文件中调用组件Provider,并传入数据
import {Provider} from 'react-redux'
import {createStore} from 'redux'
import allStates from './reducers'
import App from './components/App'
const store=createStore(allStates);
//原理:Provider作为顶层组件,通过context将store传递给所有后代组件
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
(3)后代组件获取数据(基于React旧版contextAPI)
//注意:React官方已不推荐使用旧版contextAPI,现多使用useContext等新版API或react-redux的connect/hooks
class VisibleTodoList extends Component{
componentDidMount(){
const{
store
}=this.context;
this.unsubscribe=store.subscribe(()=>
this.forceUpdate()
);
}
render(){
const{
store
}=this.context;
const state=store.getState();
//...
}
}
//需通过contextTypes声明需要获取的context类型,否则无法访问this.context
VisibleTodoList.contextTypes={
store:PropTypes.object.isRequired
}
7、表单
附、数据
this.state = {
name:'',
sex:'',
city:'',
citys:[
'北京','上海','深圳'
],
hobby:[
{
'title':"睡觉",
'checked':true
},
{
'title':"吃饭",
'checked':false
},
{
'title':"敲代码",
'checked':true
}
],
};
(1)输入框示例
//需为事件处理函数绑定this(可在constructor中绑定或使用箭头函数定义方法)
handelName(event){
this.setState({
name:event.target.value
})
}
<input type="text" value={this.state.name} onChange={this.handelName.bind(this)}/>
(2)单选框示例
//使用严格相等运算符===,并为事件处理函数绑定this
changeSex(event){
this.setState({
sex:event.target.value
})
}
<input type="radio" value="男" checked={this.state.sex === "男"} onChange={this.changeSex.bind(this)}/>
<input type="radio" value="女" checked={this.state.sex === "女"} onChange={this.changeSex.bind(this)}/>
(3)多选框示例
changeHobby(key){
//遵循不可变数据原则,创建新数组而非直接修改原state
const hobby = [...this.state.hobby];
hobby[key].checked = !hobby[key].checked;
this.setState({
hobby: hobby
})
}
{
this.state.hobby.map((value, key) => { //使用箭头函数绑定this,或传入this作为map的第二个参数
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.bind(this)}>
{
this.state.citys.map((value, key) => { //使用箭头函数绑定this
return <option key={key} value={value}>{value}</option> //为option添加value属性,确保选中值正确同步
})
}
</select>
七、React.js响应式原理的4次重大演进!!!
附、(1)Derived,/diˈraivd/,派生的
(2)来源,https://github.com/facebook/react/releases
(3)所有版本简介,https://github.com/facebook/react/blob/main/CHANGELOG.md#1702-march-22-2021
(4)reactdom版本,https://cdn.bootcdn.net/ajax/libs/react-dom/16.6.0/cjs/react-dom.development.js
1、React15.6及以前,Stack Reconciler(栈协调器)
(1)原理:栈协调器。通过JS递归调用栈,实现同步、不可中断的组件树渲染
A、采用递归方式对新旧虚拟DOM树进行深度优先遍历,逐层比对节点差异(如节点类型、属性、子节点等)
B、遍历过程中同步计算需要更新的DOM操作(如新增、删除、修改节点),并立即执行这些操作,将虚拟DOM变化映射到真实DOM
C、依赖JS调用栈管理遍历过程,一旦开始遍历就无法中断,直到完成整个树的比对和更新
(2)优点:实现简单,逻辑直观
A、实现简单直观,递归逻辑易于理解和维护,适合早期React对基础渲染能力的需求
B、同步执行DOM更新,保证了渲染过程的即时性和顺序性,在小型应用或简单组件树中性能表现稳定
C、无需复杂的任务调度和状态保存机制,内存占用较低
(3)缺点:递归不可中断,大列表渲染易阻塞主线程,导致卡顿
A、递归过程不可中断,若组件树层级较深或节点数量大,会长期占用JS主线程,导致页面卡顿(无法响应用户输入、动画停滞等)
B、不支持任务优先级区分,所有更新操作同等对待,无法优先处理用户交互等紧急任务
C、面对大型应用时性能瓶颈明显,难以满足复杂UI交互的流畅性需求
(4)主要版本
A、React,0.3.0版,2013年05月29日
B、React,0.14.8版,2016年03月29日
C、React,15.0.0版,2016年04月07日
D、React,15.3.0版,2016年08月26日,出现纯函数组件,PureComponent
E、React,15.6.2版,2017年09月25日。这是React15的最终维护版本
2、React16.0及以后,Fiber Reconciler(纤维协调器)架构引入
(1)原理:纤维协调器。通过纤维树链表和优先级调度系统,实现可中断、可继续且带优先级的异步渲染
A、将虚拟DOM树拆分为独立的“纤维节点”(工作单元),每个节点对应一个组件或DOM元素
包含类型、属性、状态及与其他节点的关系(父、子、兄弟指针)
B、采用循环遍历替代递归,通过指针跳转遍历纤维链表(单链表结构),遍历过程可随时中断、暂停或继续
C、引入“双缓存机制”,维护当前渲染树(current Fiber)和待更新树(workInProgress Fiber),更新完成后通过指针切换提交结果
D、配合调度器(Scheduler)实现优先级管理,根据任务类型(如用户输入、动画、数据更新)分配优先级,高优先级任务可中断低优先级任务
(2)优点:可暂停/继续渲染,避免主线程阻塞,提升流畅度
A、支持可中断渲染,避免长时间占用主线程,解决了Stack Reconciler导致的页面卡顿问题,提升大型应用响应速度
B、实现优先级调度,优先处理用户交互、动画等关键任务,保证核心操作的即时性
C、循环遍历替代递归,减少对调用栈的依赖,降低内存占用,提高大型组件树的处理效率
D、为后续并发模式、Suspense等新特性奠定基础,增强了React的扩展性
(3)缺点:实现复杂,需处理优先级排序和中断状态保存
A、架构复杂度显著提升,纤维节点的链表结构、双缓存机制及调度逻辑增加了源码理解和维护难度
B、小型应用中可能存在性能overhead(额外开销),因为调度和状态保存机制在简单场景下显得冗余
C、对开发者编写组件的方式有潜在约束(如避免在渲染过程中产生副作用),否则可能导致状态不一致
(4)React16.0.0版(2017年09月26日)弃用和新增功能
A、新增Fiber Reconciler(纤维协调器),作为核心协调引擎,支持可中断渲染
B、新增ReactDOM.createPortal(),允许将子节点渲染到DOM树的任意位置
C、新增对自定义DOM属性的支持,未识别的属性会直接传递给DOM元素
D、改进错误处理,引入componentDidCatch 生命周期方法,用于捕获子组件错误
E、弃用ReactDOM.findDOMNode()在严格模式下的使用
F、弃用unstable_handleError生命周期方法,改用componentDidCatch
(5)React16.3.0版(2018年03月29日)弃用和新增功能
A、新增createContext()、useContext(实验性),优化上下文管理
B、新增getDerivedStateFromProps生命周期方法,替代componentWillReceiveProps
C、新增getSnapshotBeforeUpdate生命周期方法,用于在DOM更新前捕获状态
D、弃用componentWillMount、componentWillReceiveProps、componentWillUpdate(计划在未来版本移除,使用时会警告)
E、新增React.memo,用于函数组件的记忆化优化
(6)React16.4.0版(2018年06月24日)弃用和新增功能
A、修复getDerivedStateFromProps的触发时机问题,确保在父组件更新时始终调用
B、新增Pointer Events支持,统一触摸和鼠标事件处理
C、弃用ReactDOM.unstable_batchedUpdates的不稳定API,后续将提供稳定替代方案
D、优化服务器端渲染性能,减少不必要的检查
(7)React16.6.0版(2018年10月23日)弃用和新增功能
A、新增React.lazy,支持组件的动态导入(代码分割)
B、新增React.Suspense,用于在组件加载完成前显示 fallback UI
C、新增static getDerivedStateFromError生命周期方法,用于捕获子组件渲染错误
D、新增useCallback和useMemo Hooks(稳定版),优化性能
E、弃用React.unstable_AsyncMode,为后续并发模式做准备
F、新增React.memo的第二个参数(比较函数),自定义props比较逻辑
3、React16.8及以后,Hooks(钩子)推出
(1)原理:钩子。通过纤维状态链表和JS闭包,实现函数组件内的状态与副作用管理
A、实现基于函数组件的闭包特性,且依赖Fiber Reconciler(纤维协调器)的链表遍历(每个纤维节点对应一个Hooks链表)
在组件每次渲染时通过链表结构维护Hooks的调用顺序和状态,确保每次渲染时Hooks能正确关联到对应的状态
B、每个Hook(如useState、useEffect)在内部通过指针访问和更新React内部维护的Hooks链表,记录状态值、依赖项、副作用函数等信息
C、强制要求Hooks只能在函数组件顶层或自定义Hooks中调用,不能在条件语句、循环或嵌套函数中使用,避免状态关联错误
D、状态更新时,React会重新执行函数组件,Hooks依据调用顺序读取最新状态,并触发依赖变化的副作用
(2)优点:解决类组件冗余问题,让函数组件复用状态逻辑更简洁
A、让函数组件具备状态管理、生命周期等类组件能力,统一了React组件编写模式,更符合函数式编程思想
B、解决了类组件逻辑复用的痛点(如高阶组件嵌套过深),通过自定义Hooks可将逻辑抽离为独立函数,复用更简洁
C、聚合相关逻辑(如数据请求、事件监听),避免类组件中逻辑分散在不同生命周期的问题,代码可读性和维护性更好
D、消除类组件中this指向的困扰,简化代码编写,降低新手学习门槛
E、轻量化设计,相比类组件减少了模板代码,使组件结构更紧凑
(3)缺点:依赖调用顺序,复杂场景易出现闭包陷阱
A、依赖调用顺序,若在条件语句中使用Hooks会导致状态错乱,增加了代码规范约束成本
B、闭包特性可能导致新手难以理解“捕获值”行为(如在定时器中访问旧状态),容易引发bugs
C、复杂组件中Hooks过多时,可能导致逻辑分散(“Hooks地狱”),需要合理拆分自定义Hooks来缓解
D、部分场景下调试难度增加,由于状态和逻辑分散在函数作用域中,不如类组件的this.state集中查看方便
E、对第三方库适配有一定成本,部分旧库需改造才能更好地支持Hooks用法
(4)React,16.8.0版(2019年02月06日)弃用和新增功能
A、正式发布Hooks特性,成为稳定API
a、常用,useState、useReducer、useContext、useEffect、useMemo、useCallback、useRef、自定义Hook
b、不常用,useImperativeHandle、useLayoutEffect、useDebugValue
B、未新增其他重大功能
(5)React,17.0.0版(2020年10月20日)弃用和新增功能
A、事件委托机制调整,事件委托到从document改到渲染根节点(如ReactDOM.render的容器节点),减少不同React版本嵌套时的事件冲突
B、移除event.persist()的必要性,合成事件在事件处理函数执行后不再被立即回收,简化事件处理逻辑
C、新增对act测试API的改进,更好地模拟组件渲染和更新过程,提升测试稳定性。
D、弃用ReactDOM.unstable_createPortal,改用稳定的ReactDOM.createPortal(此前已逐步过渡)
E、移除部分过时的内部API(如ReactDOM.findDOMNode在严格模式下的警告进一步强化,虽未完全移除,但不推荐使用)
F、官方声明React17不添加新特性,主要聚焦于内部架构升级和兼容性优化,为后续版本铺路
4、React18.0及以后,Concurrent Rendering(并发渲染)
(1)原理:通过时间切片和优先级调度系统,实现可中断的并发渲染与任务抢占
A、基于Fiber Reconciler(纤维协调器)将渲染工作拆分为独立任务单元,每个单元可暂停、继续或放弃,实现渲染过程的可中断性
B、引入优先级调度机制,按任务类型(如用户输入、动画、数据更新)分配优先级,高优先级任务可中断低优先级任务
C、采用双缓存机制维护current(当前渲染)和workInProgress(待更新)两套纤维树
渲染阶段仅操作workInProgress树,完成后通过指针切换提交结果
D、分离渲染与提交阶段,渲染阶段(计算差异)可中断,提交阶段(更新DOM)不可中断,保证DOM操作的原子性
(2)优点:高优先级任务(如输入)可打断低优先级任务,提升交互响应速度
A、提升应用响应速度,避免用户输入、动画等高频交互被低优先级任务阻塞,减少页面卡顿
B、支持低优先级任务延迟处理,通过startTransition等API标记非紧急更新,优先保障核心交互流畅性
C、优化复杂场景体验,如快速切换筛选条件时,可放弃未完成的旧渲染,避免界面闪烁或显示过时内容
D、为自动批处理、Suspense数据加载等新特性提供底层支持,简化性能优化逻辑
(3)缺点:实现复杂度高,需处理并发模式下的状态一致性问题
A、架构复杂度增加,开发者需理解优先级调度、中断、继续等机制,否则易因不当使用导致状态不一致
B、部分第三方库存在兼容性问题,需适配并发模式下的渲染特性(如避免依赖同步渲染假设)
C、简单应用可能有轻微性能开销,调度和任务管理逻辑在小型组件树中收益有限,甚至增加额外计算成本
D、调试难度提升,中断和继续过程可能使组件生命周期或Hooks执行时机复杂化,难以追踪状态变化链路
(4)React18.0.0版(2022年03月29日)弃用和新增功能
A、新增并发渲染(Concurrent Rendering)机制,作为默认渲染模式,支持可中断、优先级调度的渲染过程
B、新增createRoot API,替代ReactDOM.render,作为并发模式的入口,支持自动批处理更新
C、新增startTransition和useTransitionAPI,用于标记低优先级更新,避免阻塞高优先级交互
D、新增自动批处理(Automatic Batching)功能,合并多个状态更新为一次渲染,减少不必要的重绘
E、新增useDeferredValue Hook,为值提供延迟更新能力,优化用户交互体验
F、强化Suspense功能,支持在服务器端渲染(SSR)中使用,配合流式渲染提升首屏加载速度
G、弃用ReactDOM.unstable_createRoot等不稳定API,统一使用稳定的createRoot
H、弃用ReactDOM.render的回调函数参数,建议通过useEffect实现类似逻辑
(5)React18.3.0版(2024年3月21日)弃用和新增功能
A、新增useActionState Hook,简化表单提交等异步操作的状态管理,自动处理加载和错误状态
B、新增use Hook,支持在组件中直接消费Promise和Context,简化异步数据处理逻辑
C、新增React.compile实验性API,用于预编译组件以提升运行时性能(暂为实验特性)
D、强化开发环境警告,对不安全的生命周期用法(如componentWillMount)提供更明确的迁移指引
E、弃用ReactDOM.findDOMNode,官方推荐使用ref直接访问DOM元素,该API将在未来版本移除
F、修复多个并发模式下的边缘案例,提升Suspense与服务器端渲染的兼容性
G、优化useEffect清理函数的执行时机,确保在组件卸载时更可靠地释放资源
(6)Concurrent Rendering(并发渲染)的作用
总、让React能够"同时"处理多个渲染任务,并根据优先级灵活调整执行顺序,让React应用在复杂交互场景下更"流畅"和"智能"
A、实现非阻塞渲染,避免页面卡顿
a、在并发渲染模式下,React可以将渲染工作分解为小单元,在执行过程中随时暂停、继续或放弃
b、当遇到高优先级任务(如用户输入、动画帧更新)时,
React会暂停当前低优先级的渲染工作(如列表渲染、数据加载后的UI更新),优先处理高优先级任务,确保用户操作能得到即时响应
c、任务优先级降低后,再继续之前的渲染工作,避免了因长时间占用主线程导致的页面卡顿(如输入框打字延迟、动画掉帧)
B、支持"可中断的更新",优化用户体验
a、在并发模式下,组件渲染过程(虚拟DOM比对、计算差异)是"可中断"的,且中间状态不会同步到真实DOM
b、用户快速切换筛选条件时,React可以放弃上一次未完成的渲染,直接处理最新的筛选请求
避免界面出现"过时的中间状态"(如短暂显示错误的列表内容)
c、只有当渲染完全完成后,才会一次性将最终结果更新到DOM,确保用户看到的始终是一致、正确的界面
C、为新特性提供底层支持,并发渲染是React18诸多新特性的基础
a、Automatic Batching:自动批量处理多个状态更新,减少不必要的渲染次数
b、Transitions:通过startTransition标记低优先级更新,确保高优先级操作(如输入)不被阻塞
c、Suspense for Data Fetching:更可靠地实现数据加载时的占位UI,配合并发渲染实现无缝切换
5、React响应式原理概要!!!
(1)React15.6及以前,栈协调器。通过JS递归调用栈,实现同步、不可中断的组件树渲染
(2)React16.0及以后,纤维协调器。通过纤维树链表和优先级调度系统,实现可中断、可继续且带优先级的异步渲染
(3)React16.8及以后,钩子。通过纤维状态链表和JS闭包,实现函数组件内的状态与副作用管理
(4)React18.0及以后,并发渲染。通过时间切片和优先级调度系统,实现可中断的并发渲染与任务抢占
6、DOM变化监听工具MutationObserver的原理
(1)说明
A、来源,是浏览器提供的原生微任务API
B、作用,用于监听DOM树单个节点或节点列表的变化,如节点增删、属性修改、子节点变化等
C、执行时机,在DOM变化完成后,其回调函数加入微任务队列,待同步代码执行完毕后统一执行,避免频繁触发
(2)示例
//选择目标节点
const targetNode = document.getElementById('target');
//配置监听选项
const config = {
childList: true, //监听子节点变化
attributes: true, //监听属性变化
subtree: true //监听后代节点
};
//创建观察者实例
const observer = new MutationObserver((mutationsList)=>{
console.log('2、微任务:DOM变化监听回调执行');
for(let mutation of mutationsList){
if(mutation.type==='childList'){
console.log('子节点发生变化');
}else if(mutation.type==='attributes'){
console.log(`属性${mutation.attributeName}发生变化`);
}
}
});
//开始监听目标节点
observer.observe(targetNode,config);
console.log('1、同步代码:开始修改DOM');
//触发DOM变化(会被观察者捕获)
targetNode.appendChild(document.createElement('div'));
targetNode.setAttribute('data-test','123');
console.log('1、同步代码:DOM修改完成');
//停止监听(按需调用)
//observer.disconnect();
//1、同步代码:开始修改DOM
//1、同步代码:DOM修改完成
//2、微任务:DOM变化监听回调执行
7、微任务队列操作工具queueMicrotask的原理!!!
(1)说明
A、来源,是浏览器提供的原生微任务API
B、作用,直接向微任务队列添加一个微任务,功能类似于Promise.resolve().then()
C、执行时机,在同步代码执行后立即执行,优先级与Promise.then、MutationObserver相同
(2)示例
//添加第一个微任务
queueMicrotask(()=>{
console.log('1、微任务1:第一个微任务执行');
//在微任务中添加新的微任务
queueMicrotask(()=>{
console.log('3、微任务3:在第一个微任务中添加的微任务');
});
});
//同步代码执行
console.log('1、同步代码:开始执行');
//添加第二个微任务
queueMicrotask(()=>{
console.log('2、微任务2:第二个微任务执行');
});
console.log('1、同步代码:执行结束');
//1、同步代码:开始执行
//1、同步代码:执行结束
//1、微任务1:第一个微任务执行
//2、微任务2:第二个微任务执行
//3、微任务3:在第一个微任务中添加的微任务
8、跨上下文双向通信工具MessageChannel的原理
(1)说明
A、来源,是浏览器提供的原生宏任务API
B、作用,在消息安全隔离的基础上,实现跨上下文双向通信
C、执行时机,其消息回调作为宏任务执行
(2)示例
//创建两个消息通道,分别处理高/低优先级任务
const highChannel = new MessageChannel();
const lowChannel = new MessageChannel();
//任务队列
const highQueue = [];
const lowQueue = [];
highChannel.port2.onmessage = () => {
while (highQueue.length > 0) {
console.log('2、执行高优先级任务');
const task = highQueue.shift();
task();
}
};
lowChannel.port2.onmessage = () => {
if (highQueue.length === 0) {
while (lowQueue.length > 0) {
console.log('3、高优先级任务执行完毕,开始执行低优先级任务');
const task = lowQueue.shift();
task();
}
} else {
console.log('1、高优先级队列非空,推迟低优先级任务到下一个宏任务');
lowChannel.port1.postMessage(null);
}
};
//调度函数
const schedule = {
high(task) {
highQueue.push(task);
highChannel.port1.postMessage(null);
},
low(task) {
lowQueue.push(task);
lowChannel.port1.postMessage(null);
}
};
console.log('宏任务同步添加开始');
schedule.low(() => console.log('3-1、执行低优先级任务'));
schedule.high(() => console.log('2-1、执行高优先级任务'));
schedule.high(() => console.log('2-2、执行高优先级任务'));
console.log('宏任务同步添加结束,开始执行宏任务');
//宏任务同步添加开始
//宏任务同步添加结束,开始执行宏任务
//1、高优先级队列非空,推迟低优先级任务到下一个宏任务
//2、执行高优先级任务
//2-1、执行高优先级任务
//2、执行高优先级任务
//2-2、执行高优先级任务
//3、高优先级任务执行完毕,开始执行低优先级任务
//3-1、执行低优先级任务
八、Hooks钩子详解!!!
附、React组件的缺点与解决方案
(1)(有状态)类组件的缺点,每个组件都要继承React实例
(2)(无状态)纯函数组件的缺点,不能状态管理、生命周期处理
(3)Hooks的作用,提供状态管理、生命周期处理
A、主要Hooks:useState、useReducer、useContext、useEffect、useMemo、useCallback、useRef、自定义Hook
B、函数组件没有实例化对象,没有this,因此里面的Hook没有this指向的问题
1、useState,单个状态管理。1次管理1个状态
(1)参数:初始值
(2)返回值:数组,包含2个元素,第1个为当前值,第2个为更新状态值的函数
(3)面临问题与解决方案
A、问题:函数组件没有内置的状态维护机制,只能依赖props渲染
B、解决:useState提供状态管理能力,通过返回的更新函数触发组件重新渲染,解决了函数组件无法维护状态的问题
(4)示例
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;
2、useReducer,复杂状态管理。1次管理1到多个状态。Reducer /riˈdjuːsə/ 减速器
(1)参数:共2个,第1个为reducer函数,第2个为状态的初始值
(2)返回值:1个数组,第1项为当前的状态值,第2项为dispatch函数
(3)面临问题与解决方案
A、问题:状态更新是异步的,直接读取当前状态可能获取到的是旧值
B、解决:useReducer的reducer函数会接收最新的状态作为参数,确保计算新状态时基于最新值
(4)示例
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、问题:纯函数组件的跨组件状态共享需层层传递props,易形成“props透传”
B、解决:useContext结合createContext可实现跨层级组件直接访问共享状态,无需手动透传
(4)示例
A、父组件定义 //stateProvider.js
import React, { createContext, useContext, useReducer } from 'react';
const StateContext = createContext(); //1、创建
const DispatchContext = createContext();
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
console.error('Unknown action type:', action.type);
return state;
}
}
function StateProvider({ children }) {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<StateContext.Provider value={state}> //2、赋值
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
function useStateContext() {
return useContext(StateContext); //3、获取
}
function useDispatchContext() {
return useContext(DispatchContext);
}
export { StateProvider, useStateContext, useDispatchContext }; //4、导出
B、子组件定义
a、Counter.js
import React from 'react';
import { useStateContext, useDispatchContext } from './stateProvider'; //5、引入
function Counter() {
const state = useStateContext();
const dispatch = useDispatchContext();
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
export default Counter;
b、Display.js
import { useStateContext, useDispatchContext } from './stateProvider';
function Display() {
const { count } = useStateContext();
return <div>Current count: {count}</div>;
}
export default Display;
C、使用父子组件,app.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { StateProvider } from './stateProvider';
import Counter from './Counter';
import Display from './Display';
function App() {
return (
<StateProvider>
<Counter />
<Display />
</StateProvider>
);
}
const rootElement = document.getElementById('root');
if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);
} else {
console.error('Root element with id "root" not found.');
}
4、useEffect,副作用操作处理
(1)参数:2个
A、第1个参数是(异步)函数
a、执行时机
第2个参数缺失时,组件每次渲染和更新后
第2个参数空数组[]时,组件挂载后,等效于componentDidMount
第2个参数为为非空数组[dep1, dep2]时,首次挂载和依赖项发生变化后
b、返回值,是一个清理函数(可选)
执行时机,1、组件卸载时,等效于componentWillUnmount,2、在下一次副作用执行前
作用,清理副作用,等效于componentWillUnmount
B、第2个参数是依赖数组(可选),通过依赖项的变化控制副作用的执行
(2)返回值:undefined
(3)面临问题与解决方案
A、问题:纯函数组件无法处理副作用(如数据请求、订阅等)。副作用函数,运行时影响其他变量的函数
B、解决:useEffect允许在函数组件中执行副作用操作,并可模拟类组件的生命周期,控制执行时机(挂载、更新、卸载阶段),允许
a、在函数组件中执行副作用操作(如数据请求、事件监听)
b、并通过依赖数组控制执行时机,
c、同时支持返回清理函数以避免内存泄漏
(4)示例
A、定时器
import { useEffect } from 'react'
function Counter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
useEffect(
function(){
const id = setInterval(function(){
setCount(function(count){
return count + step
});
},1000);
return function(){
clearInterval(id);
}
}, [step]
)
return (
<div>
<h1>{count}</h1>
<input value={step} onChange={e => setStep(Number(e.target.value))} />
</div>
);
}
B、异步请求
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
getData('https://api.example.com/data')
.then(responseData => {
if (isMounted) {
setData(responseData);
}
})
return () => {
isMounted = false;
};
}, []);
if (loading) {
return <div>数据加载中...</div>;
}
return (
<div>
<h3>获取到的数据:</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default DataFetcher;
5、useMemo,缓存计算结果
(1)参数
A、第1个参数:一个函数,返回需要缓存的值
B、第2个参数:依赖项数组,只有当数组中的任意一项发生变化时,才会重新执行第一个参数
(2)返回值:经过缓存的计算结果。在依赖项未发生变化时,多次渲染会返回同一个值;当依赖项变化时,会重新计算并返回新的结果
(3)面临问题与解决方案
A、问题:纯函数组件频繁渲染时,复杂计算会重复执行
B、解决:缓存计算昂贵的函数结果,仅在依赖项变化时重新计算
(4)示例
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ numbers }) {
//模拟一个昂贵的计算(比如大量数据处理)
const calculateSum = () => {
console.log('执行了计算...'); //观察计算是否执行
let sum = 0;
//故意增加计算复杂度
for (let i = 0; i < numbers.length; i++) {
for (let j = 0; j < 10000000; j++) {
sum += numbers[i] * j * 0.0000001; //模拟复杂计算
}
}
return sum.toFixed(2);
};
//使用useMemo缓存计算结果,只有当numbers变化时才重新计算
const result = useMemo(calculateSum, [numbers]);
return <div>计算结果: {result}</div>;
}
function Parent() {
const [count, setCount] = useState(0);
const [numbers] = useState([1, 2, 3, 4]); //固定的数组
return (
<div>
<button onClick={() => setCount(count + 1)}>
点击次数: {count}
</button>
<ExpensiveCalculation numbers={numbers} />
</div>
);
}
export default Parent;
6、useCallback,缓存回调函数
(1)参数:
A、第1个参数:需要缓存的回调函数本身
B、第2个参数:依赖项数组,当数组中的值发生变化时,才会重新创建回调函数;若为空数组,则只在组件初次渲染时创建一次
(2)返回值:经过缓存的回调函数。在依赖项未发生变化时,多次渲染会返回同一个函数引用;当依赖项变化时,会返回新的函数引用
(3)面临问题与解决方案
A、问题:纯函数组件频繁渲染时,内部函数会重复创建新的引用
B、解决:缓存函数本身,仅在依赖项变化时返回新的函数引用
(4)示例
import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
//缓存回调函数,仅当name变化时才重新创建
const handleClick = useCallback(() => {
console.log(`Hello, ${name}!`);
}, [name]); //依赖项为name
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
<ChildComponent onClick={handleClick} />
</div>
);
}
7、useRef,DOM元素访问与持久化值存储
(1)参数,初始值
(2)返回值
A、{current: initialValue}
B、在整个生命周期内一直存在
(3)面临问题与解决方案
A、问题:纯函数组件无法访问DOM元素或保存跨渲染周期的持久化值
B、解决:useRef提供容器存储DOM引用或持久化值,其变化不会触发组件重新渲染,可以跨渲染周期保存数据
(4)示例
import{ useRef,useEffect} from "react";
const RefComponent = () => {
let inputRef = useRef(initialValue);
useEffect(() => {
inputRef.current.focus();
})
return (
<input type="text" ref={inputRef}/>
)
}
8、自定义Hook,状态逻辑复用封装
(1)面临问题与解决方案
A、问题:纯函数组件难以复用状态逻辑,需通过高阶组件或render props等复杂模式
B、解决:自定义Hook可抽离复用逻辑,通过组合内置Hook实现跨组件状态逻辑共享
(2)示例
import { useState } from 'react';
//自定义Hook本身(处理表单输入状态)
function useForm(initialValues) {
//存储表单所有字段的值
const [values, setValues] = useState(initialValues);
//处理输入变化的通用方法
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({
...prev,
[name]: value
}));
};
//重置表单到初始状态
const resetForm = () => {
setValues(initialValues);
};
//返回需要暴露给组件的状态和方法
return {
values,
handleChange,
resetForm
};
}
//使用自定义Hook(在组件中)
function LoginForm() {
//调用自定义Hook,初始化表单字段
const { values, handleChange, resetForm } = useForm({
username: '',
password: ''
});
const handleSubmit = (e) => {
e.preventDefault();
//提交表单数据
console.log('提交表单:', values);
//可以在这里添加API调用等逻辑
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>用户名:</label>
<input
type="text"
name="username"
value={values.username}
onChange={handleChange}
/>
</div>
<div>
<label>密码:</label>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
/>
</div>
<button type="submit">登录</button>
<button type="button" onClick={resetForm}>重置</button>
</form>
);
}
export default LoginForm;
9、汇总
(1)useState面临问题与解决方案
A、问题:函数组件没有内置的状态维护机制,只能依赖props渲染
B、解决:useState提供状态管理能力,通过返回的更新函数触发组件重新渲染,解决了函数组件无法维护状态的问题
(2)useReducer面临问题与解决方案
A、问题:状态更新是异步的,直接读取当前状态可能获取到的是旧值
B、解决:useReducer的reducer函数会接收最新的状态作为参数,确保计算新状态时基于最新值
(3)useContext面临问题与解决方案
A、问题:纯函数组件的跨组件状态共享需层层传递props,易形成“props透传”
B、解决:useContext结合createContext可实现跨层级组件直接访问共享状态,无需手动透传
(4)useEffect面临问题与解决方案
A、问题:纯函数组件无法处理副作用 (如数据请求、订阅等)。副作用函数,运行时影响其他变量的函数
B、解决:useEffect允许在函数组件中执行副作用操作,并可模拟类组件的生命周期,控制执行时机 (挂载、更新、卸载阶段),允许
a、在函数组件中执行副作用操作 (如数据请求、事件监听)
b、并通过依赖数组控制执行时机,
c、同时支持返回清理函数以避免内存泄漏
(5)useMemo面临问题与解决方案
A、问题:纯函数组件频繁渲染时,复杂计算会重复执行
B、解决:缓存计算昂贵的函数结果,仅在依赖项变化时重新计算
(6)useCallback面临问题与解决方案
A、问题:纯函数组件频繁渲染时,内部函数会重复创建新的引用
B、解决:缓存函数本身,仅在依赖项变化时返回新的函数引用
(7)useRef面临问题与解决方案
A、问题:纯函数组件无法访问DOM元素或保存跨渲染周期的持久化值
B、解决:useRef提供容器存储DOM引用或持久化值,其变化不会触发组件重新渲染,可以跨渲染周期保存数据
(8)自定义Hook面临问题与解决方案
A、问题:纯函数组件难以复用状态逻辑,需通过高阶组件或render props等复杂模式
B、解决:自定义Hook可抽离复用逻辑,通过组合内置Hook实现跨组件状态逻辑共享
九、优化方案!!!
1、错误边界(Error Boundaries)
(1)作用:捕获子组件树中的JS错误,防止整个应用崩溃,并渲染备用UI
(2)核心方法:
A、static getDerivedStateFromError(error):更新状态以显示错误UI(渲染阶段调用)
B、componentDidCatch(error, errorInfo):记录错误信息(commit阶段调用,可用于日志上报)
(3)示例代码:
import React from 'react';
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
//更新状态,标记发生错误
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
//记录错误信息
componentDidCatch(error, errorInfo) {
console.error('捕获到错误:', error, errorInfo);
//可在此处发送错误日志到服务端
}
render() {
if (this.state.hasError) {
//错误状态下的备用UI
return (
<div className="error-boundary">
<h2>出错了</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false })}>
尝试恢复
</button>
</div>
);
}
//正常状态下渲染子组件
return this.props.children;
}
}
//使用方式
function App() {
return (
<ErrorBoundary>
<RiskyComponent /> {/* 可能出错的组件 */}
</ErrorBoundary>
);
}
2、懒加载(Suspense与Lazy)
(1)作用:实现代码分割,减少初始加载体积,提高首屏加载速度
(2)核心API:
A、React.Suspense:为懒加载组件提供加载状态(必须配合lazy使用)
B、React.lazy:动态导入组件,返回一个懒加载组件
(3)示例代码
import React, { Suspense, lazy } from 'react';
//懒加载组件(仅在需要时才会加载该组件的代码)
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<div>
<h1>主内容</h1>
{/* Suspense必须包裹懒加载组件,fallback为加载时显示的UI */}
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
//LazyComponent.js(被懒加载的组件)
export default function LazyComponent() {
return <p>这是懒加载的组件内容</p>;
}
3、缓存
(1)useMemo:缓存“计算结果”,避免依赖未变化时的重复计算
import React, { useMemo, useState } from 'react';
function ExpensiveComponent({ list }) {
//缓存计算结果,仅当list变化时重新计算
const sortedList = useMemo(() => {
console.log('执行排序(高开销操作)');
return [...list].sort((a, b) => b - a); //模拟昂贵计算
}, [list]); //依赖项为list
return (
<ul>
{sortedList.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
function Parent() {
const [list] = useState([3, 1, 4, 2]);
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ExpensiveComponent list={list} />
</div>
);
}
(2)useCallback:缓存“回调函数”的引用
import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
//缓存回调函数,仅当name变化时才重新创建
const handleClick = useCallback(() => {
console.log(`Hello, ${name}!`);
}, [name]); //依赖项为name
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
<ChildComponent onClick={handleClick} />
</div>
);
}
(3)React.memo:缓存“函数组件”的渲染,避免props未变化时的重复渲染
A、第1个参数:函数组件,接收props,返回React元素
B、第2个参数(可选):自定义比较函数 (prevProps, nextProps) => boolean
C、返回值:记忆化的组件(仅在满足条件时重新渲染)
D、渲染条件:
a、默认行为(无第2个参数):用(Object.is)浅比较props,变化时重新渲染
b、自定义比较函数时(有第2个参数):返回false时重新渲染,返回true时跳过渲染
import React, { useState } from 'react';
const ShowNumber = React.memo(({ number }) => {
console.log('ShowNumber 渲染了');
return <div>当前数字: {number}</div>;
});
//父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
return (
<div>
{/* 点击按钮会改变count,导致父组件重渲染 */}
<button onClick={() => setCount(count + 1)}>
增加数字: {count}
</button>
{/* 输入框改变text,也会导致父组件重渲染 */}
<input
placeholder="输入文字"
value={text}
onChange={(e) => setText(e.target.value)}
/>
{/* 子组件只依赖number属性 */}
<ShowNumber number={count} />
</div>
);
};
export default Parent;
4、状态更新
(1)useTransition:标记为非紧急,将状态更新标记为低优先级任务
import React, { useState, useTransition } from 'react';
function SimpleTransition() {
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();
//处理输入(紧急更新)
const handleChange = (e) => {
setInput(e.target.value); //立即更新输入框
//标记列表更新为非紧急(可中断)
startTransition(() => {
//模拟耗时计算(生成大量数据)
const newList = Array(10000).fill(0).map((_, i) =>
`${e.target.value}-${i}`
);
setList(newList);
});
};
return (
<div>
<input
type="text"
value={input}
onChange={handleChange}
placeholder="输入内容..."
/>
{isPending && <p>加载中...</p>}
<ul style={{ maxHeight: '300px', overflow: 'auto' }}>
{list.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
);
}
export default SimpleTransition;
(2)useDeferredValue:标记为延迟,原始值优先更新,副本延迟更新
说明:间接降低依赖副本的渲染逻辑和副作用的执行频率
import React, { useState, useDeferredValue } from 'react';
function SearchBox() {
const [input, setInput] = useState('');
//创建延迟值:当input快速变化时,deferredInput会延迟更新
const deferredInput = useDeferredValue(input);
//模拟耗时的列表渲染
const renderList = () => {
// 模拟计算延迟
const start = Date.now();
while (Date.now() - start < 50) {}
return Array(2000).fill(0).map((_, i) => (
<div key={i}>{deferredInput ? `${deferredInput}-结果${i}` : `结果${i}`}</div>
));
};
return (
<div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="输入搜索内容..."
/>
<div style={{ marginTop: 20, maxHeight: 300, overflow: 'auto' }}>
{renderList()}
</div>
</div>
);
}
export default SearchBox;
5、虚拟列表(长列表优化)
(1)作用:仅渲染可视区域内的列表项,大幅提升长列表性能
(2)示例(基于react-window库)
import React from 'react';
import { FixedSizeList } from 'react-window';
//长列表数据(10万条)
const bigList = Array.from({ length: 100000 }, (_, i) => `Item ${i + 1}`);
//列表项组件
const Row = ({ index, style }) => (
<div style={style} className="list-item">
{bigList[index]}
</div>
);
function VirtualizedList() {
return (
<div>
<h2>虚拟列表(仅渲染可视区域项)</h2>
<FixedSizeList
height={500} //列表总高度
width="100%" //列表总宽度
itemCount={bigList.length} //总项数
itemSize={50} //每项高度
>
{Row}
</FixedSizeList>
</div>
);
}
十、权限控制-路由级和组件级
附、请给react添加权限,要求:
A、有公共权限列表
B、登录后,根据登录者的身份,再返回一个权限列表
C、把上面两个列表合并为一个总表
D、用React-Router展示权限
E、既可以路由级控制,又可以组件级控制
F、请用es6语法,不要用ts语法
1、权限上下文(PermissionContext.js)
import React, { createContext, useContext, useState } from 'react';
const PUBLIC_PERMISSIONS = [//公共权限列表(所有登录用户都有的权限)
'dashboard:view',
'profile:view',
'settings:basic'
];
const PermissionContext = createContext();//1、创建
export const PermissionProvider = ({ children }) => {
const [userPermissions, setUserPermissions] = useState([]);
const allPermissions = [...PUBLIC_PERMISSIONS, ...userPermissions];//合并公共权限和用户专属权限
const checkPermission = (required) => {//检查权限
if (Array.isArray(required)) {
return required.some(perm => allPermissions.includes(perm));
}
return allPermissions.includes(required);
};
return (
<PermissionContext.Provider//2、赋值
value={{
permissions: allPermissions,
setUserPermissions,
checkPermission
}}
>
{children}
</PermissionContext.Provider>
);
};
export const usePermissions = () => useContext(PermissionContext);//3、获取
2、模拟权限服务(authService.js)
const ROLE_PERMISSIONS = {//不同角色对应的权限
admin: ['user:manage', 'settings:admin', 'reports:view'],
editor: ['content:edit', 'reports:view'],
user: ['content:view'],
guest: []
};
export const login = async (username, password) => {//模拟登录API
return new Promise((resolve) => {
setTimeout(() => {//根据用户名决定角色(实际项目中应由后端返回)
let role = 'user';
if (username.includes('admin')) role = 'admin';
if (username.includes('editor')) role = 'editor';
resolve({
token: 'mock-token',
role,
permissions: ROLE_PERMISSIONS[role]
});
}, 500);
});
};
3、登录组件(Login.js)
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { usePermissions } from './PermissionContext';
import { login } from './authService';
const Login = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const { setUserPermissions } = usePermissions();
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const { permissions } = await login(username, password);
setUserPermissions(permissions);
navigate('/dashboard');
} catch (error) {
alert('登录失败');
}
};
return (
<div style={{ maxWidth: '400px', margin: '0 auto', padding: '20px' }}>
<h2>登录</h2>
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px' }}>用户名</label>
<input
style={{ width: '100%', padding: '8px' }}
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="请输入用户名"
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px' }}>密码</label>
<input
style={{ width: '100%', padding: '8px' }}
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="请输入密码"
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '10px',
backgroundColor: '#4CAF50',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
登录
</button>
</form>
</div>
);
};
export default Login;
4、权限守卫组件(PermissionGuard.js)
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { usePermissions } from './PermissionContext';
//路由级权限控制
export const RoutePermissionGuard = ({ required, redirectTo = '/no-permission' }) => {
const { checkPermission } = usePermissions();
if (!checkPermission(required)) {
return <Navigate to={redirectTo} replace />;
}
return <Outlet />;
};
//组件级权限控制
export const ComponentPermissionGuard = ({ children, required, fallback = null }) => {
const { checkPermission } = usePermissions();
return checkPermission(required) ? children : fallback;
};
5、主应用和路由配置(App.js)
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { PermissionProvider } from './PermissionContext';
import Login from './Login';
import { RoutePermissionGuard } from './PermissionGuard';
//页面组件
const Dashboard = () => <h1>控制面板</h1>;
const AdminPanel = () => <h1>管理员面板</h1>;
const Profile = () => <h1>个人资料</h1>;
const Reports = () => <h1>报表中心</h1>;
const NoPermission = () => <h1>无权限访问</h1>;
const Home = () => <h1>首页</h1>;
function App() {
return (
<PermissionProvider>
<BrowserRouter>
<Routes>
{/* 公开路由 */}
<Route path="/login" element={<Login />} />
<Route path="/no-permission" element={<NoPermission />} />
{/* 需要登录的路由 */}
<Route element={<RoutePermissionGuard required="dashboard:view" />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
<Route element={<RoutePermissionGuard required="profile:view" />}>
<Route path="/profile" element={<Profile />} />
</Route>
{/* 需要管理员权限的路由 */}
<Route element={<RoutePermissionGuard required={['user:manage', 'admin:access']} />}>
<Route path="/admin" element={<AdminPanel />} />
</Route>
{/* 需要编辑权限的路由 */}
<Route element={<RoutePermissionGuard required="reports:view" />}>
<Route path="/reports" element={<Reports />} />
</Route>
{/* 默认路由 */}
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
</PermissionProvider>
);
}
export default App;
6、组件级权限控制示例(AdminPanel.js)
import React from 'react';
import { ComponentPermissionGuard } from './PermissionGuard';
const AdminPanel = () => {
return (
<div>
<h1>管理员面板</h1>
<ComponentPermissionGuard required="user:manage">
<div style={{ padding: '10px', margin: '10px 0', backgroundColor: '#f0f0f0' }}>
<h3>用户管理</h3>
<p>只有有 user:manage 权限的用户可以看到这个区域</p>
<button>添加用户</button>
<button>删除用户</button>
</div>
</ComponentPermissionGuard>
<ComponentPermissionGuard required="settings:admin">
<div style={{ padding: '10px', margin: '10px 0', backgroundColor: '#f0f0f0' }}>
<h3>系统设置</h3>
<p>只有有 settings:admin 权限的用户可以看到这个区域</p>
<button>修改配置</button>
</div>
</ComponentPermissionGuard>
<ComponentPermissionGuard
required={['reports:view', 'content:edit']}
fallback={<p style={{ color: 'red' }}>您没有查看报表和编辑内容的权限</p>}
>
<div style={{ padding: '10px', margin: '10px 0', backgroundColor: '#f0f0f0' }}>
<h3>高级功能</h3>
<p>需要同时有 reports:view 和 content:edit 权限</p>
<button>生成报表</button>
<button>编辑内容</button>
</div>
</ComponentPermissionGuard>
</div>
);
};
export default AdminPanel;
7、使用权限钩子示例(UserProfile.js)
import React from 'react';
import { usePermissions } from './PermissionContext';
const UserProfile = () => {
const { checkPermission } = usePermissions();
return (
<div>
<h2>用户资料</h2>
{checkPermission('profile:edit') ? (
<button>编辑资料</button>
) : (
<p>您没有编辑资料的权限</p>
)}
{checkPermission(['profile:view', 'profile:export']) && (
<button>导出资料</button>
)}
</div>
);
};
export default UserProfile;
十一、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源码外框(用到了递归)
1、外框本身
/**
* React v15.6.0
*/
(function (allFn) {
if (typeof exports === "object" && typeof module !== "undefined") {
module.exports = allFn()
/*
typeof exports === "object",检查 exports是否存在(exports是Node.js的模块导出对象)
typeof module !== "undefined",检查 module是否存在(module是Node.js的模块对象)
module.exports = allFn(),如果满足条件,说明当前是Node.js环境,使用module.exports导出模块
*/
} else if (typeof define === "function" && define.amd) {
define([], allFn)
/*
typeof define === "function",检查define是否存在(define是AMD模块定义函数)
define.amd,检查define是否是AMD规范的实现(如RequireJS)
define([],allFn),如果满足条件,说明当前是AMD环境,使用define注册模块
*/
} else {
var tempGlobal;
if (typeof window !== "undefined") { //检查window是否存在,代表浏览器环境。
tempGlobal = window
} else if (typeof global !== "undefined") { //检查global是否存在(某些非标准环境可能没有module,但有global)
tempGlobal = global
} else if (typeof self !== "undefined") { //检查self是否存在(self在Web Worker中指向全局对象)
tempGlobal = self
} else { //如果以上都不存在,回退到this(严格模式下可能是undefined,非严格模式是全局对象)
tempGlobal = this
}
tempGlobal.React = allFn() //最终将模块挂载到全局对象(如window.React或global.React)
}
})(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上
});
2、外框模拟
(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是进一步简化后用于配置的唯一值
十六、Web开发技术之SSR、RSC、BFF
1、SSR
(1)说明
A、SSR,Server-Side Rendering,即“服务器端渲染”
B、SSR,是指在服务器端生成完整的HTML页面,再发送到客户端的技术
(2)核心流程
A、服务器端渲染页面:服务器接收请求,动态生成HTML
B、发送HTML到客户端:客户端直接渲染完整的HTML
C、客户端水合(Hydration):React等框架将静态HTML与JS绑定,使页面变得可交互
(3)优缺点
A、优点,SEO友好、首屏加载快
B、缺点,服务器负载高、响应时间可能较长
(4)技术实现(以React + Next.js为例)
A、自动SSR支持:
a、在Next.js中,默认不启用SSR,需通过定义getServerSideProps函数为页面开启SSR(每次请求时在服务器端渲染页面)
b、若不定义该函数,页面默认采用静态生成(SSG),在构建时生成静态HTML
B、数据获取
a、通过getServerSideProps函数在服务器端获取数据,数据会作为props传递给页面组件
b、客户端交互(如按钮点击)可通过fetch调用Next.js的API路由(如/api/submit)与服务器通信
C、示例
import { useState } from 'react';
//服务器端数据获取(SSR),每次请求时执行
export async function getServerSideProps(context) {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } }; //数据传递给页面组件
}
function Page({ data }) {
const [clientData, setClientData] = useState(null);
//客户端点击事件:向服务器发送数据
const handleClick = async () => {
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Hello from client!' })
});
const result = await response.json();
setClientData(result);
} catch (error) {
console.error('Error sending data:', error);
}
};
return (
<div>
<div>Server Data: {data}</div>
<button onClick={handleClick}>提交</button>
{clientData && <div>服务器响应: {clientData.message}</div>}
</div>
);
}
export default Page; //Next.js中默认导出的组件会作为页面渲染
(5)技术实现(以 React + Remix 为例)
A、自动 SSR 支持:
a、在 Remix 中,所有页面默认采用 SSR(服务器端渲染),
b、每次请求都会经过服务器处理,自动完成数据获取、HTML 生成和客户端激活,无需额外配置。
B、数据获取:
a、通过 loader 函数在服务器端获取页面初始数据,客户端通过 useLoaderData 钩子获取;
b、表单提交等交互通过 action 函数在服务器端处理
C、示例
//app/routes/index.js
import { useState } from 'react';
import { json, useLoaderData } from '@remix-run/react';
//服务器端数据加载(替代Next.js的getServerSideProps)
export async function loader() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return json({ serverData: data }); //数据传递给客户端
}
//服务器端处理表单提交(替代Next.js的API路由)
export async function action({ request }) {
const formData = await request.formData();
const payload = { message: formData.get('message') };
//可执行数据库操作或其他业务逻辑
return json({ message: `服务器收到: ${payload.message}` });
}
//页面组件
export default function IndexPage() {
const { serverData } = useLoaderData(); //获取服务器端数据
const [clientData, setClientData] = useState(null);
//客户端提交逻辑
const handleSubmit = async (event) => {
event.preventDefault();
try {
const response = await fetch('/', {
method: 'POST',
body: new FormData(event.currentTarget)
});
if (response.ok) {
const data = await response.json();
setClientData(data);
}
} catch (error) {
console.error('Error:', error);
}
};
return (
<div>
<div>Server Data: {serverData}</div>
<form onSubmit={handleSubmit}>
<input type="hidden" name="message" value="Hello from client!" />
<button type="submit">提交</button>
</form>
{clientData && <div>服务器响应: {clientData.message}</div>}
</div>
);
}
2、RSC(React Server Components)用法
(1)说明
A、RSC,React Server Components,即“React服务器组件”
B、RSC,React18引入的组件模型,允许组件在服务器端运行,且无需发送JS到客户端
(2)核心概念
A、默认服务端组件,组件默认在服务器端运行,不包含客户端 JS
B、客户端组件标记,使用'use client'标记需要交互的组件
C、混合渲染,服务端组件可与客户端组件自由组合
(3)优缺点
A、优点,极致性能(减少客户端 JS)、简化数据获取、安全隔离
B、缺点,学习曲线陡峭、调试复杂、不适用于纯客户端交互场景
(4)技术实现(以Next.js13+App Router为例)
A、服务端组件(默认)
//app/products/[id]/page.js
export default async function ProductPage({ params }) {
//直接在服务器端获取数据(无需客户端请求)
const product = await fetchProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<ClientRating productId={product.id} /> {/* 嵌套客户端组件 */}
</div>
);
}
B、客户端组件
//app/components/ClientRating.js
'use client'; //标记为客户端组件
import { useState } from 'react';
export default function ClientRating({ productId }) {
const [rating, setRating] = useState(0);
const handleRate = async () => {
try {
await fetch(`/api/products/${productId}/rating`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rating: rating + 1 }),
});
setRating(rating + 1);
} catch (err) {
console.error('评分提交失败:', err);
}
};
return (
<div>
<button onClick={handleRate}>提交评分</button>
<p>Rating: {rating}</p>
</div>
);
}
(5)技术实现(以 Remix 为例)
A、服务端组件(默认)
//app/routes/products.$id.js
import { json, useLoaderData } from '@remix-run/react';
import { fetchProduct } from '~/utils/api'; //假设这是你的API工具
//服务器端数据加载
export async function loader({ params }) {
const product = await fetchProduct(params.id);
return json({ product });
}
//服务端组件(默认无需'use client')
export default function ProductPage() {
const { product } = useLoaderData();
return (
<div>
<h1>{product.name}</h1>
<ClientRating productId={product.id} />{/* 嵌套客户端组件 */}
</div>
);
}
B、客户端组件
//app/components/ClientRating.js
'use client'; //标记为客户端组件
import { useState } from 'react';
export default function ClientRating({ productId }) {
const [rating, setRating] = useState(0);
const handleRate = async () => {
try {
//使用Remix的action提交数据
await fetch(`/products/${productId}/rating`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rating: rating + 1 }),
});
setRating(rating + 1);
} catch (err) {
console.error('评分提交失败:', err);
}
};
return (
<div>
<button onClick={handleRate}>提交评分</button>
<p>Rating: {rating}</p>
</div>
);
}
C、服务端 API 处理(对应客户端 fetch 请求)
//app/routes/products.$id.rating.js
import { json } from '@remix-run/react';
//处理评分提交的action
export async function action({ params, request }) {
const { rating } = await request.json();
//更新评分逻辑(数据库操作等)
await updateProductRating(params.id, rating);
return json({ success: true });
}
3、BFF
(1)说明
A、BFF,Backend For Frontend,即“为前端服务的后端”
B、BFF,是一种架构模式,本质是位于前端应用与后端服务之间的中间层
(2)核心功能
A、数据聚合,将多个后端接口的数据整合为前端所需的格式,减少前端的请求次数
B、数据转换与适配,根据不同前端的需求调整数据格式(如字段重命名、结构扁平化),避免后端为适配前端修改接口
C、请求转发与代理,处理跨域、认证(如携带 Token)等问题,前端只需调用 BFF 接口,无需关心后端服务的具体地址和权限细节
D、简单业务逻辑处理,承担一些轻量的业务逻辑(如数据过滤、格式校验),减轻前端或后端的负担
(3)适用场景
A、多端应用(Web、移动端、小程序等),需要针对不同端定制数据
B、前端与后端技术栈差异大,或后端接口设计难以直接满足前端需求
C、复杂页面需要聚合多个后端服务的数据
(4)注意事项
A、避免过度设计,BFF层应专注于“适配”而非“业务逻辑”,复杂业务仍需放在后端服务中,否则会导致BFF层臃肿
B、性能考量,BFF层增加了一次转发,需注意优化(如缓存、异步处理),避免成为性能瓶颈
C、团队协作,BFF层通常由前端团队开发(更了解前端需求),或前后端协作维护
(5)示例
A、前端代码
async function getUserDashboard(userId) {
try {
//调用BFF聚合API
const response = await fetch(`http://localhost:3000/api/user-with-orders/${userId}`);
const data = await response.json();
//直接使用聚合好的数据渲染页面
renderUserDashboard(data);
} catch (error) {
console.error('Error fetching dashboard data:', error);
showErrorToast('Failed to load dashboard');
}
}
async function searchProducts(category, page = 1, limit = 10) {
try {
//调用BFF产品API
const response = await fetch(`http://localhost:3000/api/products?category=${category}&page=${page}&limit=${limit}`);
const products = await response.json();
//使用格式化好的产品数据渲染列表
renderProductList(products);
} catch (error) {
console.error('Error searching products:', error);
showErrorToast('Failed to load products');
}
}
B、BFF代码
const express = require('express');
const axios = require('axios');
const app = express();
const PORT = 3000;
//模拟用户服务API
const USER_SERVICE_URL = 'https://api.example.com/users';
//模拟订单服务API
const ORDER_SERVICE_URL = 'https://api.example.com/orders';
//模拟产品服务API
const PRODUCT_SERVICE_URL = 'https://api.example.com/products';
//中间件:解析JSON请求体
app.use(express.json());
//前端专用API:获取用户及其订单信息
app.get('/api/user-with-orders/:userId', async (req, res) => {
try {
const userId = req.params.userId;
//1.从用户服务获取用户信息
const userResponse = await axios.get(`${USER_SERVICE_URL}/${userId}`);
const user = userResponse.data;
//2.从订单服务获取用户订单
const ordersResponse = await axios.get(`${ORDER_SERVICE_URL}?userId=${userId}`);
const orders = ordersResponse.data;
//3.为每个订单中的产品ID获取产品详情
const productsPromises = orders.map(order =>
axios.get(`${PRODUCT_SERVICE_URL}/${order.productId}`)
);
const productsResponses = await Promise.all(productsPromises);
const products = productsResponses.map(res => res.data);
//4.聚合数据,为前端提供友好的格式
const formattedOrders = orders.map((order, index) => ({
id: order.id,
date: order.date,
status: order.status,
product: products[index]
}));
const result = {
user,
orders: formattedOrders
};
res.json(result);
} catch (error) {
console.error('Error fetching data:', error.message);
res.status(500).json({ error: 'Failed to fetch data' });
}
});
//前端专用API:获取产品列表(带分页和筛选)
app.get('/api/products', async (req, res) => {
try {
//从查询参数获取分页和筛选条件
const { page = 1, limit = 10, category } = req.query;
//构建请求参数
const params = {
page,
limit,
...(category && { category })
};
//调用产品服务获取数据
const productsResponse = await axios.get(PRODUCT_SERVICE_URL, { params });
//可以在这里对产品数据进行转换或增强
const enhancedProducts = productsResponse.data.map(product => ({
...product,
//为前端添加一些额外的格式化信息
formattedPrice: `$${product.price.toFixed(2)}`
}));
res.json(enhancedProducts);
} catch (error) {
console.error('Error fetching products:', error.message);
res.status(500).json({ error: 'Failed to fetch products' });
}
});
//启动BFF服务
app.listen(PORT, () => {
console.log(`BFF server running on port ${PORT}`);
});