React进阶

1.安装React开发调试工具(React Developer Tools):对React项目进行调试

  • Google -> 更多工具 -> 扩展程序 -> 左下角打开Chrome网上应用店 -> 搜索React -> React Developer Tools -> 添加至Chrome

  • React Developer Tools调试工具颜色:

    • 灰色: 没有用React开发

    • 红色: React项目,线下版本

    • 黑色: React项目,线上版本(相对本地代码有压缩,精悍一点~)

  • React Developer Tools工具作用:

    • 查看地点:控制栏 -> React菜单

    • 作用一:查看React组件结构

    • 作用二:查看右侧的Props、State等进行调试

    • PropTypes与DefaultProps

  • PropTypes:做属性接收的强校验,限制父组件传递给子组件属性的类型

    • 引入PropTypes

      import PropTypes from 'prop-types';
    • 使用PropTypes进行校验

      import React,{Component} from 'react';
      import PropTypes from 'prop-types';
      class TodoItem extends Component{
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);//为了性能考虑,改变函数的this指向通常在constructor中定义
      }
      render(){
          const { content,test } = this.props; //结构赋值
          return(
              <div onClick={this.handleClick}>
                  {test} - {content}
              </div>
          )
      }
      handleClick(){
          const { deleteItem,index } =this.props;//结构赋值
          deleteItem(index);
      }
      }
      //对TodoItem组件对类型进行校验
      TodoItem.propTypes = {
      content:PropTypes.oneOfType([PropTypes.string, PropTypes.number]), //表示content属性必须是String类型或Number类型中的一个
      deleteItem:PropTypes.func,//表示deleteItem属性必须是function类型
      index:PropTypes.number,//表示index属性必须是number类型
      test:PropTypes.string.isRequired //isRequired表示test必须要从父组件传递过来,且类型是string类型,如果没传则报出警告
      }
      
      //如果父组件没有向子组件传递对应的属性,则在这里定义属性的默认值
      TodoItem.defaultProps = {
      test: 'helloworld'
      } 
      export default TodoItem;

      (1)使用组件名.propTypes表示对组件属性进行校验

      (2)使用属性名:PropTypes.类型表示属性的类型必须是设定的类型

      (3)使用属性名:PropTypes.类型.isRequired表示属性的类型必须是设定的类型,且必须由父组件传递过来

      (4)使用属性名:PropTypes.oneOfType([PropTypes.string,PropTypes.number])表示属性的类型必须是string或number中的一个

      (5)…等等验证方式请参见相关文档,如:PropTypes.instanceOf(object)/PropTyps.oneOf(['News','Photos'])/PropTypes.arrayOf(PropTypes.number)...

    • 如果传值类型与PropTypes不同,则会在控制台给出警告,但对开发并不影响,用来查看对应的相关错误信息。

    • 对于个别属性父类没有传递但在子类却要使用,则使用组件名.defaultProps={属性名:属性值}的方式进行设定。【具体可参见上方代码示例】

    • Props,State与render函数(解决数据发生变化,页面就要重新渲染的原理)

  • render函数在页面初始化的时候会先被执行一次,当state或者props发生变化时,render函数会重新执行,从而重新渲染页面,实现联动

  • 当父组件的render函数被执行时,他的子组件的render函数都将被重新执行

2.虚拟DOM

  • 版本一:基本DOM生成流程

    • 定义state数据
    • 定义JSX模板
    • 数据+模板 结合,生成真实的DOM来显示
    • 当state发生改变
    • 数据+模板 结合,生成真实的DOM,替换原来的DOM
    • 缺陷:
      • 第一次生成了一个完整的DOM片段
      • 第二次生成了一个完整的DOM片段
      • 第二次的DOM替换第一次的DOM,非常耗性能
  • 版本二:改良DOM生成流程

    • 定义state数据
    • 定义JSX模板
    • 数据+模板 结合,生成真实的DOM来显示
    • 当state发生改变
    • 数据+模板 结合,生成真实的DOM,并不直接替换原始的DOM
    • 新的DOM(js底层的DocumentFragment)和原始的DOM做比对,找差异
    • 找出input框发生的变化
    • 只用新的DOM中的input元素,替换掉老的DOM中的input元素
    • 缺陷:
      • 性能的提升并不明显
  • 版本三:React的虚拟DOM

    • 定义state数据

    • 定义JSX模板

    • 数据+模板 结合,生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)【js创建js对象损耗很少的性能;但如果js创建jsDOM则有很大的损耗】

      Eg.['div',{id:'abc'},['span',{},'hello world']]

    • 用虚拟DOM的结构,生成真实的DOM来显示

      Eg.<div id="abc"><span>hello world</span></div>

    • state发生变化

    • 数据+模板 生成新的虚拟DOM(极大的提升了性能)

      Eg.['div',{id:'abc'},['span',{},'bye bye']]

    • 比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中的内容(比较虚拟DOM即比较js对象,极大的提升了性能)

    • 直接操作DOM改变span中的内容

    • 优点:

      • 减少了对真实DOM的创建与真实DOM的对比
      • 利用js对象比较代替DOM比较

3.深入虚拟DOM底层原理

  • React生成DOM流程
    • JSX => React.createElement => 虚拟DOM(JS对象) => 真实的DOM
    • return <div>item</div>等同于return React.createElement('div',{},'item')。React.createElement是更偏向于底层的接口,提供了一个类似于js对象的内容传递给createElement方法,将该对象变成虚拟DOM,再被转换成真实的DOM。
  • 虚拟DOM带来的好处:
    • 性能提升了(将DOM比对变成JS对象的比对)
    • 使得跨端应用得以实现(React Native)
      • 虚拟DOM是JS对象在原生应用和网页应用里都可以被识别
      • 利用虚拟DOM转化成原生组件

4. 虚拟DOM中的Diff算法(Difference,原始虚拟DOM与新的虚拟DOM的差异比对的算法)

  • setState

    • 异步方法,提升React性能

    • 设计成一步函数的初衷:假设连续调用三次setState变更三组数据,时间间隔非常小,React将三次SetState合并成一个SetState,只做成一次虚拟DOM的比对,更新一次DOM,省去性能耗费。

这里写图片描述

  • Diff算法

    • Diff算法 是虚拟DOM进行比对时用到的算法,采用同级比对的概念。

      这里写图片描述

      • 比较流程:先比第一层、再比第二层…
      • 如果在一层不一致,下面不会继续比,将原始页面的虚拟DOM下面所有虚拟DOM替换
      • 优点:
      • 同级比较,算法简单从而带来比对的速度快
    • key值

      • 引入key值的原因:提高虚拟DOM比对的性能

      • 例子:

      ​ 假设有一个数组,数组上面包含五个数据,在页面第一次渲染时将5个数据映射成五个虚拟DOM节点,即虚拟DOM树。接着向数组中添加一个数据,生成新的虚拟DOM树,将虚拟DOM进行比对,没有key值没法确认节点之间的关系,要想进行比对需要进行两层循环。

      ​ 如果添加key值,虚拟DOM的比对则根据key值做关联,一致则可以复用。

      ​ 前提:key值在前一个虚拟DOM上和后一个虚拟DOM上相同。故,在循环中key值不要是index,没法保证在原始虚拟DOM书上和新的虚拟DOM树上的key值一致。

      这里写图片描述

      ​ 如在todolist中,若以index为key, 添加a、b、c三个元素,key值分别是0、1、2;当删除a后,b和c的key值分别是0、1,则无法建立关系,key值失去存在意义。

      ​ 若以item为key则a->a,b->b,c->c;删除a后,b->b,c->c,可以建立关系。

      • key值要保持稳定,在项目开发时能不使用index作为key值就不使用index作为key值

      • 与Diff算法的关系:同层比对和key值比对都是Diff算法中的一部分…

5.React中ref的使用

  • 作用:帮助在React中直接获取DOM元素,尽量不要使用ref

  • 应用实例:

    //TodoList.js中的部分代码
    render(){
            //JSX模板语法 => js对象 => 真实的DOM
            return(
                <Fragment>
                    <div>
                        <label htmlFor="insertArea">输入内容</label>
                        <input 
                            id="insertArea"
                            className="input"
                            value={this.state.inputValue}
                            onChange={this.handleInputChange}
                            ref={(input)=>{this.testinput=input}}
                            />
                        <button
                            onClick={this.handleBtnClick}>提交</button>
                    </div>
                    <ul>
                        {this.getTodoItem()}
                    </ul>
                </Fragment>
            )
        }
    
    handleInputChange(){
            console.log(this.testinput);    //e.target表示input框对应的DOM节点
            this.setState((preState)=>({ //preState表示修改前的state
                inputValue : this.testinput.value
            }));
    }
    • 取DOM元素的方法
      • 利用函数调用后的参数e,e.target获取对应备操作的DOM元素
      • 利用标签的ref属性,ref的属性值为函数,函数中的参数input为当前元素的DOM,将该DOM元素作为this.testinput的值,函数调用时,输出this.testinput即会找到之前赋值时的DOM元素
  • 与setState合用时的坑

    • 坑的出现:

      • 实例代码
      import React ,{ Component , Fragment}from 'react';
      import TodoItem from './TodoItem';
      import './style.css';
      class TodoList extends Component{
      constructor(props) {
        super(props); //调用Component的构造函数
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
        this.state = {
          inputValue:'', //input中的内容
          list:[] //列表中的每一项
        };
      }
      render(){
          //JSX模板语法 => js对象 => 真实的DOM
          return(
              <Fragment>
                  <div>
                      <label htmlFor="insertArea">输入内容</label>
                      <input 
                          id="insertArea"
                          className="input"
                          value={this.state.inputValue}
                          onChange={this.handleInputChange}
                          ref={(input)=>{this.testinput=input}}
                          />
                      <button
                          onClick={this.handleBtnClick}>提交</button>
                  </div>
                  <ul ref={(ul)=>{this.ul=ul}}>
                      {this.getTodoItem()}
                  </ul>
              </Fragment>
          )
      }
      getTodoItem(){
          return(
              this.state.list.map((item,index)=>
              {
                  return (//循环组件应包含key值,key值应该放在return的最外层元素上
                          <TodoItem 
                              key={index}
                              content={item}
                              index={index}
                              deleteItem={this.handleItemDelete}/>
                      )
              })
          )   
      }
      handleInputChange(){
          console.log(this.testinput);    //e.target表示input框对应的DOM节点
          this.setState((preState)=>({ //preState表示修改前的state
              inputValue : this.testinput.value
          }));
      }
      handleBtnClick(){
          this.setState((preState)=>({//preState表示修改前的state,也就是this.state
              list:[...preState.list,preState.inputValue],//...this.state.list  es6的展开运算符
              inputValue:''
          }))
          console.log(this.ul.querySelectorAll('div').length);
      }
      handleItemDelete(index){
          //console.log(index);
          //array.splice(index,howmany,item1,.....,itemX) 
          //index下标,howmany表示删除多少个,item1...itemX表示要添加的元素
          this.setState((preState)=>{
              const list = [...preState.list];//拷贝一份list数组
              list.splice(index,1);
              return {list}
          })
      }
      
      //注意:箭头函数this.setState(()=>({...}))表示返回...是一个对象
      //                   this.setState(()=>{...})表示返回...是一个函数,需要些return
      }
      export default TodoList;

      上述代码中,利用ul的ref属性将this.ul设置成ul的DOM元素,并在handleBtnClick()中打印该DOM下的所有的div元素的个数。但输入内容并提交后,页面渲染出一个新的DOM元素,但控制台打印的却是0;再添加后,ul下共有两个div,但控制台打印的却是1。如下所示:

      这里写图片描述

    • 解决坑:

      • 问题原因:setState是异步函数,即使console.log()在声明时在setState下面,但由于setState的异步,导致先执行console.log再执行setState

      • 爬坑:setState的第二个参数为一个回调函数,将想在setState执行后进行的操作,以箭头函数的形式放到第二个参数中即可。只需对handleBtnClick函数做以下修改:

      handleBtnClick(){
              this.setState((preState)=>({//preState表示修改前的state,也就是this.state
                  list:[...preState.list,preState.inputValue],//...this.state.list  es6的展开运算符
                  inputValue:''
              }),()=>{
                  console.log(this.ul.querySelectorAll('div').length);
              })
      
          }

6.React生命周期函数[针对于组件而言的,每一个组件都有生命周期函数,生命周期函数又称为生命周期钩子]

React生命周期函数

  • 什么是生命周期函数?

    生命周期函数指在某一个时刻组件会自动调用执行的函数

  • 生命周期过程

    • Initialization初始化过程
      • 执行constructor()函数,初始化数据,如props、state【constructor()不算做React生命周期函数,而是ES6的规定初始化函数】
    • Mounting挂载(挂载指的是组件第一次被放到页面上的时候)
      • componentWillMount 在组件即将被挂载到页面的时刻执行(只在挂载的时候执行,即第一次放到页面上的时候执行)
      • render 在页面被挂载的时刻执行
      • componentDidMount 在组件被挂载到页面之后自动执行(只在挂载的时候执行,即第一次放到页面上的时候执行)
    • Updation组件更新(发生在props或states发生变化的时刻)
      • props
        • componentWillReceiveProps在组件要从父组件接受参数,且只要父组件的render函数被重新执行了,子组件的这个生命周期函数会被执行(即这个组件第一次存在于父组件中,不会执行;如果这个组件之前已经存在于父组件中,才会执行)
        • shouldComponentUpdate在组件被更新之前被执行,返回boolean类型的返回结果
          • return true表示需要更改组件,继续调用componentWillUpdate方法
          • return false表示不需要更改组件,不继续调用其他生命周期方法
        • componentWillUpdate在组件更新之前被自动执行,在shouldComponentUpdate之后执行,如果shouldComponentUpdate返回true才执行,如果返回false此函数则不会被执行
        • render执行页面渲染
        • componentDidUpdate在组件更新完成之后被自动执行
      • states
        • shouldComponentUpdate在组件被更新之前被执行,返回boolean类型的返回结果
          • return true表示需要更改组件,继续调用componentWillUpdate方法
          • return false表示不需要更改组件,不继续调用其他生命周期方法
        • componentWillUpdate在组件更新之前被自动执行,在shouldComponentUpdate之后执行,如果shouldComponentUpdate返回true才执行,如果返回false此函数则不会被执行
        • render执行页面渲染
        • componentDidUpdate在组件更新完成之后被自动执行
    • Unmounting取消挂载
      • componentWillUnmount当这个组件即将被从页面中剔除的时候会被执行

7.生命周期函数的使用场景

  • 除render()函数,其他的生命周期函数都可以重写

    • 原因:继承Component,Component默认内置了其他所有的生命周期函数,除render函数
  • 当父组件的render函数被执行,子组件的render函数也会被执行

    • 上述操作消耗组件性能,故可以对子组件的shouldComponentUpdate()函数进行如下修改

      shouldComponentUpdate(nextProps,nextState){
          //nextProps和nextState表示当组件被更新时,nextProps表示props会变成什么样,nextState表示state会变成什么样
          //利用组件内数据是否变化的比对,从而决定是否执行updation相关函数,避免多余的渲染
          if(nextProps.content !== this.props.content){
              return true;
          }else{
              return false;
          }
          //作用:避免一个组件做无谓的render()操作
      }
  • ajax请求放到componentDidMount()中,保证请求只会被执行一次

    • 使用ajax请求

      • 借助axios扩展工具发送ajax请求:yarn add axios

      • 应用实例

      //引入axios
      import axios from 'axios';
      //使用axios
      componentDidMount(){
              axios.get('/api/todolist')
                  .then(()=>{alert("succ")}) //发送成功的回调
                  .catch(()=>{alert("error")}) //发送失败的回调
          }
    • 使用Charles实现本地数据的模拟mock

  • 安装Charles:https://www.charlesproxy.com/latest-release/download.do直接下载安装即可

  • Charles是抓包工具(同Fiddler),实现本地mock的原理是:拦截指定请求,返回模拟数据。

  • 实现本地数据的模拟:

    • 本地创建json文件

      eg.json文件中的内容
      ["Jack","Jerry","Sam"]
    • 开启Charles -> Tools -> Map Local -> 选择Encable Map Local -> Add 添加路径并将Local path选择本地json文件 -> OK

      即完成本地模拟数据的操作

    • 显示请求内容

      componentDidMount(){
          axios.get('/api/todolist')
              .then((res)=>{
                  this.changeList(res.data);
              })
              .catch(()=>{alert("error")})
      }
      
      changeList(data){
          this.setState((preState)=>({
              list:[...preState.list,...data]
          }))
      }
      //由于请求的地址有响应数据,且响应数据中的data对象为要返回的数据信息,则将data中的数据添加到list中从而更新页面内容
    • 总结:整个请求流程是通过axios发送ajax请求访问指定url,使用charles工具拦截url并返回json数据,通过then方法获取回调成功的数据并显示。

8.React的CSS过渡动画

  • 应用实例:

    //App.js
    import React, { Component , Fragment} from 'react';
    import './App.css';
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.handleToggle = this.handleToggle.bind(this);
        this.state = {
          show:true
        };
      }
    
      render() {
        return (
          <Fragment>
            <div className={this.state.show?'show':'hide'}>hello</div>
            <button onClick={this.handleToggle}>toggle</button>
          </Fragment>
        );
      }
    
      handleToggle(){
        this.setState((PreState)=>({
          show:!PreState.show
        }))
      }
    }
    
    export default App;
    
    //App.css
    .show{
      opacity: 1;
      transition: all 1s ease-in;  
    }
    
    .hide{
      opacity: 0;
      transition: all 1s ease-in;  
    }

    分析:对button设定监听事件,当点击时切换state中show的值为相反,从而切换div组件的class样式值,通过设置css中opacity:0为透明(即隐藏),并结合transition属性实现过渡样式(all 1s ease-in的含义是:所有样式的切换都用1s做过度)

9.React中CSS的动画效果

  • CSS3的动画效果是指通过@keyframes定义css动画

    //App.js
    import React, { Component , Fragment} from 'react';
    import './App.css';
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.handleToggle = this.handleToggle.bind(this);
        this.state = {
          show:true
        };
      }
    
      render() {
        return (
          <Fragment>
            <div className={this.state.show?'show':'hide'}>hello</div>
            <button onClick={this.handleToggle}>toggle</button>
          </Fragment>
        );
      }
    
      handleToggle(){
        this.setState((PreState)=>({
          show:!PreState.show
        }))
      }
    }
    
    export default App;
    
    //App.css
    .show{
      animation: show-item 2s ease-in forwards;
    }
    
    .hide{
      animation:hide-item 2s ease-in forwards;
    }
    
    @keyframes hide-item{
      0% {
        opacity: 1;
        color: red;
      }
      50%{
        opacity: 0.5;
        color: green;
      }
      100%{
        opacity: 0;
        color: blue;
      }
    }
    
    @keyframes show-item{
      0% {
        opacity: 0;
        color: red;
      }
      50%{
        opacity: 0.5;
        color: green;
      }
      100%{
        opacity: 1;
        color: blue;
      }
    }

    分析:CSS3的动画效果是指通过@keyframes定义css动画(定义简单动画),利用animation属性引用,使用hide-item的动画效果,动画时长2s,动画曲线ease-in,设置最终效果forward即停留在最后一帧的样式(如果不设置则显示至0帧),从而实现使用对应自定义的动画效果。

10.使用react-transition-group实现动画(react第三方动画模块)

  • 安装react-transition-group模块

    
    #npm
    
    npm install react-transition-group --save
    
    #yarn
    
    yarn add react-transition-group
  • react-transition-group中的CSSTransition是动画组件(实现复杂的动画效果),自动进行class的增加和移除工作

    //App.js
    import React, { Component , Fragment} from 'react';
    import {CSSTransition} from 'react-transition-group';
    import './App.css';
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.handleToggle = this.handleToggle.bind(this);
        this.state = {
          show:true
        };
      }
    
      render() {
        return (
          <Fragment>
            <CSSTransition
              in={this.state.show}
              timeout={300}
              classNames="fade"
              unmountOnExit
              onEntered={(el)=>{el.style.color='blue'}}
              appear={true}
              >
              <div>hello</div>
            </CSSTransition>
            <button onClick={this.handleToggle}>toggle</button>
          </Fragment>
        );
      }
    
      handleToggle(){
        this.setState((PreState)=>({
          show:!PreState.show
        }))
      }
    }
    
    export default App;
    
    //App.css
    .fade-enter{
      opacity:0;
    }
    
    .fade-enter-active{
      opacity: 1;
      transition: opacity 1s ease-in;
    }
    
    .fade-enter-done{
      opacity: 1;
      color: red;
    }
    
    .fade-exit{
      opacity:1;
    }
    
    .fade-exit-active{
      opacity: 1;
      transition: opacity 1s ease-in;
    }
    
    .fade-exit-done{
      opacity: 0;
    }
    
    
    .fade-appear{
      opacity: 0;
      color: yellow;
    }
    
    .fade-appear-active{
      opacity: 1;
      color: gray;
      transition: opacity 2s ease-in;
    }
    • in:当前状态,用于确定是出场动画(in的值从false变成true)还是入场动画(in的值从true变成false)
    • timeout:动画持续时间(单位:毫秒)
    • 入场动画css
      • fade-enter:in的值从false变成ture时第一个时刻执行,CSSTransition会向其中元素添加fade-enter的css样式
      • fade-enter-active:入场动画执行的第二个时刻到入场动画执行完成期间,被CSSTransition包裹的元素使用的是fade-enter-active的css样式
      • fade-enter-down:整个入场动画执行完成之后,被CSSTransition包裹的元素将使用fade-enter-down的css样式
    • 出场动画css
      • fade-exit:出场动画执行的第一个时刻
      • fade-exit-active:出场动画执行的第二个时刻到出场动画执行完成
      • fade-exit-down:出场动画执行完成之后
    • className值是指定css中”-“前面的内容【即css的样式为fade-enter】,故className属性值应该是fade,从而让组件找到对应的css样式
    • unmountOnExit:执行完出场动画后,移除内部DOM元素
    • 钩子属性(即在某一时刻会执行的函数)利用js去执行相关操作
      • onEnter(入场动画执行的第一帧的时候触发),函数第一个参数为内部元素
      • onEntering(入场动画执行的第二帧到动画结束的期间触发),函数第一个参数为内部元素
      • onEntered(入场动画结束之后执行的函数),函数第一个参数为内部元素
      • onExit(出场动画执行的第一帧),函数第一个参数为内部元素
      • onExiting(出场动画执行的第二帧到动画结束的期间触发),函数第一个参数为内部元素
      • onExited(出场动画结束之后执行的函数),函数第一个参数为内部元素
      • 等…
    • appear:值为true表示内部元素第一次渲染到页面时也要动画效果;默认false
      • 使用fade-appear样式:第一次渲染时出场动画执行的第一时刻
      • 使用fade-appear-active样式:第一次渲染时出场动画执行的第二时刻到出场动画执行完成
    • 更多:Transition组件是CSSTransition更底层的组件,如果有CSSTransition无法实现的功能,则可以考虑使用Transition组件进行动画效果的实现。

11.使用react-transition-group实现多个元素的动画效果(使用TransitionGroup搭配CSSTransition使用)

  • 使用react-transition-group中的TransitionGroup组件,包裹要进行动画效果的一组组件
  • TransitionGroup组件中的多个组件外应使用CSSTransition包裹要添加动画的元素,此时in的属性可以去掉,因为并不是做切换入场出场而是添加元素的操作
  • 应用实例:【实现点击按钮添加元素动画】

    //App.js
    import React, { Component , Fragment} from 'react';
    import {CSSTransition,TransitionGroup} from 'react-transition-group';
    import './App.css';
    
    class App extends Component {
    constructor(props) {
      super(props);
      this.handleAddItem = this.handleAddItem.bind(this);
      this.state = {
        list:[]
      };
    }
    
    render() {
      return (
        <Fragment>
        <TransitionGroup>
            {
              this.state.list.map((item,index)=>{
                return(
                  <CSSTransition
                    key={index}
                    timeout={300}
                    classNames="fade"
                    unmountOnExit
                    onEntered={(el)=>{el.style.color='blue'}}
                    appear={true}
                    >
                    <div>item</div>
                  </CSSTransition>
                )
              })
            }
          </TransitionGroup>
          <button onClick={this.handleAddItem}>toggle</button>
        </Fragment>
      );
    }
    
    handleAddItem(){
      this.setState((PreState)=>({
        list:[...PreState.list,'item']
      }))
    }
    }
    
    export default App;
    //App.css同上方代码

猜你喜欢

转载自blog.csdn.net/qq_34829447/article/details/81482095