【React自学笔记01】来喽~react新手村打卡

一、React简介

1.1 什么是React

用于动态构建用户界面的 JavaScript 库(只关注于视图)

1.2 为什么学React

原生JavaScript的缺点:

  1. 原生JavaScript操作dom繁琐,效率低(DOM-API操作UI)

  2. 使用JavaScript每次操作dom都要进行浏览器重绘、重排

  3. JavaScript没有组件化编码方案,模块化仅仅是对js进行了拆分,复用率低

React的特点:

  1. 组件化模式,声明式编码

    • 相比于之前的命令式编码,提高了开发效率和组件复用率
  2. 在react native 中使用React进行移动端开发

  3. 使用虚拟DOM+Diffing算法

    • 原生js操作真实dom,每次数据发生变化时,都会重新渲染数据

    • 操作虚拟dom存放内存之后再映射成页面上的真实dom。当数据发生改变时,新生成的虚拟dom会和之前的虚拟dom比较,如果一样则不再生成新的真实dom,直接用旧的真实dom。

    • DOM Diffing算法, 最小化页面重绘

1.3 React基本使用

1.3.1 相关js库

  1. react.js:React核心库。

  2. react-dom.js:提供操作DOM的react扩展库,支持react操作dom。

  3. babel.min.js:解析JSX语法代码转为JS代码的库。

1.3.2 虚拟dom的两种创建方式

  1. 使用jsx创建虚拟dom
<body>
  <!--定义了一个容器-->
  <div id="test"></div>
  <!--tips1:核心库和扩展库顺序不能颠倒-->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
  
  <!--tips2:type类型必须为babel,用于将jsx编译为js-->
  <script type="text/babel">
  	//step1:创建虚拟dom  注意jsx中变量可以是标签
    const VDom = <h1>hello,react</h1>
    //step2:将虚拟dom渲染到页面 ReactDOM是关键字
    // param1:渲染哪个虚拟dom,param2:渲染到哪个容器中
    ReactDOM.render(VDom, document.getElementById('test'))
  </script>
</body>
  1. 使用js创建虚拟dom(不推荐)
const VDom = React.createElement('h1', { id: 'title' }, 'hello react~')
ReactDOM.render(VDom, document.getElementById('test'))

总结:

  1. jsx的存在简化了虚拟dom的写法,当需要多层标签时只需要外面包一个括号即可进行标签的缩进

  2. jsx的写法本质就是js写法的语法糖,更加便捷

  3. 关于虚拟dom:

    • 本质是一个Object对象,属性少

    • 虚拟dom最终会被React转换为真实dom

1.4 jsx语法规则

  1. 定义虚拟dom时不要写引号

  2. 标签中混入js表达式时要用{}

  3. 样式类名指令依然写在标签中,但是不要写class,写className

  4. 内联样式要用style={{key:value}}形式写

    (1)外层{}代表js表达式

    (2)内层{}代表对象

  5. 根标签只能有一个

  6. 标签必须闭合

  7. 标签首字母

    (1)若是小写字母,必须是html中存在的标签,若html中无该标签对应的同名元素,报错;

    (2)若是大写字母,react就去渲染对应的组件,若组件未定义,报错

  8. jsx中注释的写法

<script type="text/babel">
    const mId = 'aTguiGU'
    const mData = 'hello react'
    const VDOM = (
      <div>
        <div id={mId} className="title">
          <span style={{ color: 'white' }}>{mData}</span>
        </div>
        <input type="text"></input>
      </div>
    )
    ReactDOM.render(VDOM, document.getElementById('test'))
  </script>

注意:

  1. 区分js表达式和js语句(代码)
  • 表达式:一个表达式产生一个值,可以放在任何一个需要的地方

    • 判断技巧在表达式左侧加const variable=是否成立
    • a
    • a+b
    • demo(1)函数调用表达式
    • arr.map()
    • function test(){}
  • 语句(代码):

    • if(){}
    • for(){}
    • switch()
  1. 循环渲染的方法[类似于vue中的v-for逻辑]
<script type="text/babel">
    const data = ['Angular', 'Vue', 'React']
    const VDOM = (
      <div>
        <h1>前端js框架</h1>
        <ul>
          {
            data.map((item, i) => {
              return <li key={i}>{item}</li>
            })
          }
        </ul>
      </div>
    )
    ReactDOM.render(VDOM, document.getElementById('test'))
  </script>

1.5 组件与模块

1.5.1 模块及模块化

  1. 理解模块:把一个庞大的js工程拆成一个js,一个功能就是一个js文件

  2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂

  3. 作用:复用js,简化js编写,提高js运行效率

  4. 模块化:当应用的js都以模块来编写,该应用就是模块化应用

1.5.2 组件及组件化

  1. 理解组件:所有实现某一效果功能的资源(html、css、image等)、js全部打包为一个组件,就像拼乐高一样

  2. 为什么用:一个界面的功能更复杂

  3. 作用:复用编码、简化项目编码、提高运行效率

  4. 组件化:当应用是以多组件的方式实现,这个应用就是一个组件化应用

——————————————记录于2022.05.11——————————————

二、React面向组件编程

2.1 组件的两种定义方式

2.1.1 函数式组件

📌定义步骤:

  1. 定义函数式组件:

    • function定义的函数组件名首字母大写MyComponent

    • babel开启严格模式禁止自定义函数中的this指向window,this指向undefined

    • 返回值return 要渲染的内容

  2. 渲染组件到页面,引用自定义组件标签

  • ReactDOM.render(<自定义组件名MyComponent/>,渲染到哪个容器中)

  • 标签要写成闭合样式

💕注意:执行了第二步之后发生了什么?

  • 1)React解析组件标签,找到MyComponent组件

  • 2)发现该组件是函数定义的组件,随后调用该函数,将返回的虚拟dom转为真实dom,呈现在页面中。

<script type="text/babel">
    function MyComponent() {
      return <h1>我是函数式组件</h1>
    }
    ReactDOM.render(<MyComponent />, document.getElementById('test'))
</script>

2.1.2 类式组件

📌定义步骤:

  1. 定义类式组件:

    • 类名首字母大写MyComponent,继承自React.Component{}

    • 重写父类函数render(){},返回值 return 要渲染到页面的内容

      🎐render放在MyComponent的原型对象上,提供给实例使用

      🎐render中的this是MyComponent(组件)的实例对象

    • babel开启严格模式禁止自定义函数中的this指向window,this指向undefined

  2. 渲染组件到页面,引用自定义组件标签

  • ReactDOM.render(<自定义组件名MyComponent/>,渲染到哪个容器中)

  • 标签要写成闭合样式

💕注意:执行第二步之后发生了什么?

  • 1)React解析组件标签,找到MyComponent组件

  • 2)发现组件是类式组件,随后new出来该类的实例,并通过该实例调用到原型上的render方法

  • 3)将render返回的虚拟dom转为真实dom,呈现在页面

<script type="text/babel">
    class MyComponent extends React.Component {
      render() {
        return <h1>我是函数式组件</h1>
      }
    }
    ReactDOM.render(<MyComponent />, document.getElementById('test'))
</script>

————————————记录于2022.05.12————————————

2.2 组件实例的三大核心属性

2.2.1 state属性

1. 案例分析

点击组件实现组件实例属性中状态发生改变

📌 借助构造器初始化state

  • 在类中利用构造器接受props参数,并将props传递给父类构造器

  • 定义state的状态(初始化状态):要将state定义为一个对象{key:value}的形式

class Weather extends React.Component {
      constructor(props) {
        super(props)
        this.state = { isHot: false }
      }
      render() {
        return <h1>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
      }
    }
    ReactDOM.render(<Weather />, document.getElementById('test'))

📌 react中如何绑定事件:(注意原生js中绑定事件的三种方式)

  • onClick={函数名} *注意{}是指js表达式;函数名后面不能加(),因为加了()代表调用函数,将函数的返回值赋值给onClick,这是不对的 不加()就是代表当函数触发后才调用

  • 定义函数 function 函数名(){}

class Weather extends React.Component {
      constructor(props) {
        super(props)
        this.state = { isHot: false }
      }
      render() {
        return <h1 onClick={changeWeather} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
      }
    }
    ReactDOM.render(<Weather />, document.getElementById('test'))
    function changeWeather() {
      console.log('click');
      //接下来的逻辑是:要拿到Weather的实例对象,来改变该实例的state状态属性
    }

📌 如何获取到类的实例对象中的属性

以上代码存在的问题:

  • 代码结构混乱,组件内部调用了外部函数,造成了this实例对象的丢失,需要再定义变量用来缓存this

    • 要将所有关于组件的行为都统一放到组件中

    • 也就是说,要把定义的该函数放到组件的原型对象上,供实例使用

    • 通过Weather实例调用该方法时,该方法的this就是组件的实例对象

    • 组件内调用该方法时,使用this.方法名调用方法

    class Weather extends React.Component {
          constructor(props) {
            super(props)
            this.state = { isHot: false }
          }
          render() {
            return <h1 onClick={this.changeWeather} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
          }
          changeWeather() {
            this.state.isHot = !this.state.isHot
          }
        }
        ReactDOM.render(<Weather />, document.getElementById('test'))
    
  • 基于以上思路,仍存在问题

    • 构造器中的this一定是当前类的实例对象

    • render()也是放到类的原型上,供实例使用,this指向实例对象

    • 但是自定义方法changeWeather中的this此时发生this丢失,其值等于 undefined

      ❓原因:

      function demo(){
        console.log(this);
      }
      demo()//window
      
      function demo(){
        'use strict'
        console.log(this)
      }
      demo();//undefined
      

      直接调用的方法,this指向window;严格模式下方法中的this是undefined

      const p1=new Person('tom',18)
      p1.speak ()//通过实例调用speak方法
      const x=p1.speak
      x()//直接调用 undefined
      

      x()属于直接调用,类中定义的方法已经在局部自动开启了严格模式,所以this是undefined

      所以onClick={this.changeWeather}并没有调用该方法,只是通过类的实例对象沿着原型链找到了这个方法,然后把这个方法交给onClick,将changeWeather作为onClick的回调。当点击事件发生,从堆里直接调用方法,并不是实例对象调用的。

      💕 总结:

      1. changeWeather放在Weather的原型对象上,供实例使用

      2. 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用

      3. 类中的方法默认开启局部严格模式,所以changeWeather中的this是undefined

      ⭐ 解决:

      • 因为构造器中的this一定是当前类的实例对象,

      • 又changeWeather放在Weather的原型对象上,供实例使用(顺着原型找到原型对象上的changeWeather)

      • 在构造器中this.changeWeather.bind(this),bind()函数生成一个新函数,同时将类中的changeWeather方法的this改为传来的this(即Weather的实例对象),然后将这个函数放到了实例的自身(this),起名叫changeWeather->由此得到实例对象身上的方法this.changeWeather

      • 所以onClick={this.changeWeather}调用的这个函数是实例对象自身上的changeWeather,而不是原型上的changeWeather ,(拿着原型上的方法生成一个新的方法挂在自身)

      • 注意:.bind方法返回一个新函数

      class Weather extends React.Component {
            constructor(props) {
              super(props)
              this.state = { isHot: false }
              this.changeWeather=this.changeWeather.bind(this)
            }
            render() {
              return <h1 onClick={this.changeWeather} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
            }
            changeWeather() {
              console.log(this);
            }
          }
          ReactDOM.render(<Weather />, document.getElementById('test'))
      

📌 实现组件状态更改

  • 注意:状态(state)不可直接更改 this.state.isHot=!this.state.isHot (错误)

  • 要借助一个内置API更改,必须通过setState进行更新

  • 更新的 数据 会和原来state中的不同名数据合并,同名数据替换

  • 调用次数问题:

    • 构造器调用一次;
    • render函数调用1+n次,1是初始化渲染,n是状态发生更新的次数,每更新一次数据,都要重新渲染一次页面;
    • changeWeather点几次调用几次
<script type="text/babel">
    class Weather extends React.Component {
      constructor(props) {
        super(props)
        this.state = { isHot: false }
        this.newChange = this.changeWeather.bind(this)
      }
      render() {
        return <h1 onClick={this.newChange} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
      }
      changeWeather() {
        // console.log(this);
        const isHot = this.state.isHot
        this.setState({ isHot: !isHot })
      }
    }
    ReactDOM.render(<Weather />, document.getElementById('test'))

  </script>

2. 代码简写形式

  • 类中可以直接写赋值语句,给实例对象添加一个属性

  • 箭头函数自身没有this,但是在箭头函数中使用this,找的就是外层函数的this作为箭头函数的this->就是组件的实例对象

  • 组件中的自定义方法大部分都是作为事件的回调。将自定义方法写成 赋值语句+箭头函数 的形式

class Weather extends React.Component {
      constructor(props) {
        super(props)
      }
      state = { isHot: false }
      render() {
        return <h1 onClick={this.changeWeather} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
      }
      changeWeather = () => {
        const isHot = this.state.isHot
        this.setState({ isHot: !isHot })
      }
    }
    ReactDOM.render(<Weather />, document.getElementById('test'))

💕 总结state:

  1. state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)

  2. 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)

——————————————记录于2022.05.13——————————————

2.2.2 props属性

1. 基本使用

案例分析:自定义用来显示一个人员信息的组件

  class Person extends React.Component {
      render() {
        const { name, gender, age } = this.props
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{gender}</li>
            <li>年龄:{age}</li>
          </ul>
        )
      }
    }
ReactDOM.render(<Person name="tom" age="13" gender="男" />, document.getElementById('test'))

💕 总结:

  1. 通过标签属性从组件外向组件内传递变化的数据

  2. 标签属性必须是key="value"形式 将Value的值默认为字符串形式

  3. 如果key={value} value值则为传值本身的数据类型

2. 批量传递props(标签属性)

const p={ name:"tom" ,age:"13", gender:"男"}
ReactDOM.render(<Person  {...p}/>, document.getElementById('test'))

💕 总结展开运算符的用法:

  1. 展开数组(...arr1)

  2. 连接数组[...arr1,...arr2]

  3. 函数中使用作为形参参数接收(...numbers)

  4. 构造字面量对象时复制对象

  5. 合并

💕 注意:

  1. babel+react可以展开对象{...object},但仅仅适用于标签属性的传递

3. 对props进行限制

从React16开始,有了新写法:使用prop-types库进限制(需要引入prop-types库)

  1. 引入prop-types,用于对组件标签属性进行限制

  2. 对标签属性进行类型、必要性的限制

    在组件身上添加限制规则:组件名.propTypes React底层会找到他(小写p)

  3. 指定默认标签属性值

  class Person extends React.Component {
      render() {
        const { name, gender, age } = this.props
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{gender}</li>
            <li>年龄:{age}</li>
          </ul>
        )
      }
    }
    Person.propTypes = {
      name: PropTypes.string.isRequired,
      gender: PropTypes.string,
      age: PropTypes.number,
      speak: PropTypes.func
    }
    Person.defaultProps = {
      gender: '未知',
      age: 18
    }
    ReactDOM.render(<Person name="jack" age={12} gender="男" />, document.getElementById('test1'))
    const p = { name: "tom", age: 13, gender: "男" }
    ReactDOM.render(<Person  {...p} />, document.getElementById('test2'))

💕 注意:props是只读的

4. props简写形式

  • 组件中能够完成所有关于组件的东西:对状态初始化、对标签属性的限制、默认值的指定

  • 之前我们说类中可以直接写赋值语句,给实例对象添加一个属性,但是在属性前面加一个static,就是给类本身加一个属性(注意千万不要加到render里)

  class Person extends React.Component {
      static propTypes = {
        name: PropTypes.string.isRequired,
        gender: PropTypes.string,
        age: PropTypes.number,
        speak: PropTypes.func
      }
      static defaultProps = {
        gender: '未知',
        age: 18
      }
      render() {
        const { name, gender, age } = this.props
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{gender}</li>
            <li>年龄:{age}</li>
          </ul>
        )
      }
    }
    ReactDOM.render(<Person name="jack" age={12} gender="男" />, document.getElementById('test1'))
    const p = { name: "tom", age: 13, gender: "男" }
    ReactDOM.render(<Person  {...p} />, document.getElementById('test2'))

5. props和构造器比较

📌 在React中构造器仅用于以下两种情况:

  • 通过给this.state赋值对象来初始化内部state(state属性的基本使用案例中用过)

  • 为事件处理函数绑定实例

📌 props在构造器中的存在意义:

  • 类中构造器不是必须有的,可以省略

  • 但是如果构造器不省略,那么props就必须接收props,且作为参数传入super中

  • 构造器中是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props

  • 类中构造器能省略就省略

6. 函数式组件使用props

在函数式组件中,不能使用state和ref,但是可以使用props

函数式组件对数据的限定规则只能写到函数外面

function Person(props) {
      return (
        <ul>
          <li>姓名:{props.name}</li>
          <li>性别:{props.gender}</li>
          <li>年龄:{props.age}</li>
        </ul>
      )
    }
	Person.propTypes = {
      name: PropTypes.string.isRequired,
      gender: PropTypes.string,
      age: PropTypes.number
    }
    Person.defaultProps = {
      gender: '未知',
      age: 18
    }	
    ReactDOM.render(<Person name="Kitty" age={18} gender="女" />, document.getElementById('test'))

💕 总结:

  1. 每个组件对象都会有props(properties的简写)属性

  2. 组件标签的所有属性都保存在props中

  3. 作用:通过标签属性从组件外向组件内传递变化的数据

  4. 内部读取某个属性值:render函数中this.props.name

  5. 扩展属性: 将对象的所有属性通过props传递 <Person {...person}/>

——————————————记录于2022.05.14——————————————

2.2.3 ref属性

1. 字符串形式的ref

  • 组件内的标签可以定义ref属性来标识自己,相当于原生js里的id </input ref="input1"/>

  • 组件实例中{ref:{input1:input}}

  • 拿到真实的节点标识 this.refs.input1

  • 相当于双向数据绑定 input1.value 就是input输入框中的值

案例分析:自定义组件,功能说明如下:

  1. 点击按钮,提示第一个输入框中的值

  2. 当第2个输入框失去焦点时,提示这个输入框中的值

<script type="text/babel">
    class Demo extends React.Component {
      showData = () => {
        alert(this.refs.input1.value)
      }
      showData2 = () => {
        alert(this.refs.input2.value)
      }
      render() {
        return (
          <div>
            <input ref="input1" placeholder="点击按钮提示数据"></input>
            <button onClick={this.showData}>弹框</button>
            <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
          </div>
        )
      }
    }
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

问题:效率不高,不推荐使用,未来可能移除

2. 回调形式的ref

什么是回调:自定义的、自己未调用、别人调用了

参数是ref当前所处的节点

内联函数的形式:(用的比较多)

<script type="text/babel">
    class Demo extends React.Component {
      showData = () => {
        // console.log(this);
        const { input1 } = this;
        alert(input1.value)
      }
      showData2 = () => {
        alert(this.input2.value)
      }
      render() {
        return (
          <div>
            <input ref={currentNode => this.input1 = currentNode} placeholder="点击按钮提示数据"></input>
            <button onClick={this.showData}>弹框</button>
            <input ref={cNode => this.input2 = cNode} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
          </div>
        )
      }
    }
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

类绑定函数形式:

  • 把内联形式中的逻辑部分直接抽离出来,挂载在实例上形成一个函数

  • 大多数情况下无关紧要

3. createRef形式

  • React.createRef调用后可以返回 一个容器,该容器可以存储被ref所标识的节点

  • 该容器专人专用,后放进去的东西会把之前的内容替换掉

  • 当前React最推荐的一种形式

<script type="text/babel">
    class Demo extends React.Component {
      myRef = React.createRef();
      showData = () => {
        alert(this.myRef.current.value);
      }
      render() {
        return (
          <div>
            <input ref={this.myRef} type="text" />
            <button onClick={this.showData}>点击按钮显示输入数据</button>
          </div>
        )
      }
    }
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

2.2.4 事件处理

  1. 通过onXXX属性指定事件处理函数(注意大小写)

    • React使用的是自定义(合成)时间,而不是使用的原生DOM事件——为了更好的兼容性

    • React中的时间是通过时间委托方式处理的——为了高效

  2. 通过event.target得到发生时间的DOM元素对象

    • 发生事件的dom元素和要操作的dom元素是同一个,要省略ref,采用event.target

    • 不要过度使用ref

    showData2=(event)=>{
      alert(event.target.value);
    }
    
    <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
    

2.3 收集表单数据

2.3.1 非受控组件

非受控组件:页面中所有输入类dom,现用现取

案例:在表单中收入用户名密码,点击登录提示输入信息

  • 表单提交默认引发页面跳转,阻止表单的默认行为

  • 使用ref逻辑来进行双向数据绑定

<script type="text/babel">
    class Login extends React.Component {
      getInfo = (event) => {
        event.preventDefault()
        const { username, password } = this;
        alert(`用户名:${username.value} 密码:${password.value}`)
      }
      render() {
        return (
          <form  onSubmit={this.getInfo}>
            用户名:<input ref={(c) => { this.username = c }} type="text" name="username" />
            密码:<input ref={(c) => { this.password = c }} type="password" name="password" />
            <button>登录</button>
          </form>
        )
      }
    }
    ReactDOM.render(<Login />, document.getElementById('test'))
  </script>

2.3.2 受控组件

  • 输入类dom都可以绑定onchange事件,只要改变数据就会触发onchange指定的回调

  • 受控组件:页面中所有输入类dom,随着输入可以把数据维护到状态中去,需要用的时候直接从状态中取出来。类似于vue中的双向数据绑定
    尽量使用受控制组件

  <script type="text/babel">
    class Login extends React.Component {
      state = {
        username: '',
        password: ''
      }

      saveUsername = (event) => {
        const username = this.username
        this.setState({ username: event.target.value })
      }
      savePassword = (event) => {
        const password = this.password
        this.setState({ password: event.target.value })
      }

      getInfo = (e) => {
        e.preventDefault()
        const { username, password } = this.state
        alert(`用户名:${username} 密码:${password}`)
      }

      render() {
        return (
          <form onSubmit={this.getInfo}>
            用户名:<input onChange={this.saveUsername} type="text" name="username" />
            密码:<input type="password" onChange={this.savePassword} name="password" />
            <button>登录</button>
          </form>
        )
      }
    }
    ReactDOM.render(<Login />, document.getElementById('test'))
  </script>

——————————————记录于2022.05.15——————————————

2.3.3 函数柯里化

  • 上述代码存在冗余问题,如果要实现注册功能,输入的input有多个(用户名、密码、邮箱、电话等),就需要更多的函数完成向state中存值的逻辑。就出现了将相同的逻辑合并成一个函数的 需求。

  • 但是 如果写成onChange={this.saveFormData('username')},saveFormData()带了括号表示将saveFormData函数的返回值undefined作为回调

  • 所以,可以在saveFormData中添加函数返回值,将一个函数作为onChange的回调

  • saveFormData就是一个高阶函数

// 第一种:不传参数,将name属性作为key值
saveFormData = (e) => {
        const dataType = e.target.name;
        this.setState({ [dataType]: e.target.value })
      }
      <input onChange={this.saveFormData} type="text" name="username" />
//第二种:传参,将一个函数作为回调
      saveFormData = (dataType) => {
        return (event) => {
          this.setState({ [dataType]: event.target.value });
        }
      }
      <input onChange={this.saveFormData('username')} type="text" name="username" />

💕 注意:对象相关的知识

在对象中,dataType是一个变量,调用dataType并解析dataType里面的数据时,要使用[dataType]

💕 总结:

  1. 高阶函数:如果一个函数符合下面两个规范中的一个,该函数就是高阶函数
  • 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
  • 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
  • 常见的高阶函数:Promise、定时器setTimeout、arr.map
  1. 函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function sum(a, b, c) {
  return a + b + c
}

function sumNum(a) {
  return (b) => {
    return (c) => {
      return a + b + c
    }
  }
}
const result = sumNum(1)(2)(3)
console.log(result);

2.4 组件的生命周期

render调用时机:初始化渲染、状态更新之后

componentDidMount调用时机:组件挂载完毕

componentWillUnmount:组件将要卸载

2.4.1 引出生命周期

案例分析:文字自动渐变效果,点击按钮卸载组件

<script type="text/babel">
    class Life extends React.Component {
      state = { opacity: 1 }
      death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
      }
      componentDidMount() {
        this.timer = setInterval(() => {
          let { opacity } = this.state;
          opacity -= 0.1;
          if (opacity <= 0) opacity = 1;
          this.setState({ opacity: opacity })
        }, 200)
      }
      render() {
        return (
          <div>
            <h1 style={{ opacity: this.state.opacity }}>这里是渐变文字效果</h1>
            <button onClick={this.death}>点击消失</button>
          </div>
        )
      }
      componentWillUnmount() {
        clearInterval(this.timer)
      }
    }
    ReactDOM.render(<Life />, document.getElementById('test'))
  </script>
  1. 渐变效果采用设置文字透明度实现,自动渐变要求设置定时器完成,每隔200毫秒透明度降低0.1

  2. 定时器不能放到render函数中,因为render函数调用1+n次,在初次渲染时调用一次,随后更新数据的次数等于后续调用次数,如果把定时器放到render函数中,就会出现死循环调用的情况

  3. 将定时器定义到 和render同级的componentDidMount中,组件挂载时调用且只调用一次。

  4. 将定时改变的透明度设置为标签的透明度

  5. 点击按钮销毁组件:使用ReactDOM.unmountComponentAtNode函数销毁

  6. 销毁后发生报错,报错提示如下。组件销毁后定时器仍在工作,所以要在销毁组件之前,清除定时器。将定义好的定时器赋值给timer并挂载到实例上 ,用clearInterval清除定时器。

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

2.4.2 生命周期(旧)

1. 初始化阶段: 由ReactDOM.render()触发---初次渲染 挂载流程:

constructor-componentWillMount-render-componentDidMount

2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

  • 更新状态里的数据调用this.setState-->shouldComponentUpdate(不写底层默认值为true,相当于控制组件的阀门,如果为false则不继续)-->componentWillUpdate-->render-->componentDidUpdata

  • 不想对状态做出修改,只想更新一下:强制更新this.forceUpdate-->跳过阀门不受阀门控制直接进入componentWillUpdate-->render-->componentDidUpdate

  • 父组件调用了setState,父组件的状态更改引发父组件的render调用,在父组件的render中写了子组件标签触发,引起在子组件中依次输出componentWillReceiveProps-->shouldComponentUpdate-->componentWillUpdate-->render-->componentDidUpdata

点击换车,改变A组件state中的carName,将更改的车名显示在B组件中(A是父组件、B是子组件)

<script type="text/babel">
    class A extends React.Component {
      state = { carName: '奔驰' }
      changeCar = () => {
        let carName = this.state
        this.setState({ carName: '奥迪' })
      }
      render() {
        return (
          <div>
            <h1>我是A组件</h1>
            <button onClick={this.changeCar}>换车</button>
            <B carName={this.state.carName} />
          </div>
        )
      }
    }
    class B extends React.Component {
      render() {
        const { carName } = this.props
        return (
          <div>我是B组件,我接受到的车是{carName}</div>
        )
      }
    }
    ReactDOM.render(<A />, document.getElementById('test'))
  </script>

3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

componentWillUnmount

💕 常用钩子函数总结:

  1. 页面一上来就如何---->componentDidMount,一般在这个钩子中做一些初始化的事
  • 例如:开启定时器、发送网络请求、订阅消息
  1. componentWillUnmount做一些收尾工作
  • 例如:关闭定时器、取消订阅消息
  1. render必须使用的一个

——————————————记录于2022.05.16——————————————

2.4.3 生命周期(新)

1. 初始化阶段: 由ReactDOM.render()触发---初次渲染

  1. constructor()

  2. getDerivedStateFromProps横跨挂载和更新

  3. render()

  4. componentDidMount()

2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

  1. getDerivedStateFromProps

  2. shouldComponentUpdate()

  3. render()

  4. getSnapshotBeforeUpdate

  5. componentDidUpdate()

3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount()

2.5 虚拟DOM和DOM Diffing算法

2.5.1 重点面试题

1)react/vue 中的key有什么作用?(key的内部原理是什么)

2)为什么遍历列表时,key最好不要用index?

1. 虚拟dom中key的作用

  • 简单地说:key是虚拟dom对象的标识,在更新显示时key起着重要作用

  • 详细地说:当状态中的数据发生改变时,react会根据新数据生成新的虚拟dom,随后react进行新虚拟dom与旧虚拟dom的diff比较,比较规则如下:

    • 旧虚拟dom中找到了与新虚拟dom相同的key:

      (1)若虚拟dom中内容没变,直接使用之前的真实dom

      (2)若虚拟dom中内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom

    • 旧虚拟dom中未找到与新虚拟dom相同的key

      根据数据创建新的真实DOM,随后渲染到页面

    注:最小粒度是标签、节点

2. 用index作为key可能会引发的问题:

​ 🎈 1.若对数据进行:逆序添加、逆序删除等破坏顺序操作

​ 会产生没有必要的真实DOM更新=>页面效果没问题,但效率低

​ 🎈 2.如果结构中还包含输入类的DOM

​ 会产生错误DOM更新=>界面有问题

​ 🎈 3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示

​ 仅用于渲染列表用于展示,使用index作为key是没有问题的

3. 开发中如何选择key?

  • 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值

  • 如果确定知识简单的展示数据,用index也是可以的

——————————————2022.05.17 本章笔记已完成——————————————

posted @   Lu西西  阅读(153)  评论(1编辑  收藏  举报
相关博文:
点击右上角即可分享
微信分享提示