12、React之分页组件(含勾选、过滤、表头纵横合并递归、子组件向父组件传值)、不同组件之间共享数据redux、redux-thunk、react-redux、react-router、基础、响应式原理、Hooks钩子详解、优化方案、权限控制、组件写法、插槽、多层弹窗组件、react.15.6源码、脚手架构成、Web开发技术之SSR|RSC|BFF(4200行)

一、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}`);
      });

  

posted @ 2020-10-26 23:42  WEB前端工程师_钱成  阅读(10030)  评论(0)    收藏  举报