# SGG-REACT 学习笔记 (基于React16)

# 不懂就问、不会就学、错了就改

# 第一个React案例

<body>
  <!-- 准备好一个容器 -->
  <div id="test"></div>
  <!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"-->
  <!-- 库的引入一定注意顺序,核心库一定是先引入 -->
  <!-- 引入react核心库 -->
  <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  <!-- 此处一定要写babel,不写默认是js -->
  <script type="text/babel">
    // 1,创建虚拟DOM
    const VDOM = <h1>Hello,React</h1>/*此处一定不能添加引号,因为不是字符串**/
    // 2,渲染虚拟DOM到页面
    ReactDOM.render(VDOM,document.getElementById('test'))
  </script>
</body>
</html>

# 使用JSX?接受JSX(JavaScript XML)

JSX更加流畅的创建虚拟DOM

  • React定义的一种类似于XML的JS扩展语法:JS+XML(JSX创建虚拟DOM是原生JS创建虚拟DOM的语法糖)

  • 本质:React.createElement(component,props,....children)方法的语法糖

  • 作用:用来简化创建虚拟DOM

    写法:var ele = <h1>hello,huge</h1>

    • 它不是字符串,也不是HTML/XML标签
    • 它最终产生的就是一个JS对象
  • 标签名任意:HTML标签或其他标签

# 补充小知识-XML

XML早期用于存储和传输数据,比如存储一个学生的信息如下:

......
<student>
	<name>huge</name>
    <age>18</age>
</student>
.......

后面大部分用JSON替代了XML

"{"name":"huge","age":18}"

JSON.parse() 把JSON字符串解析成JS里面对应的数组和对象

JSON.stringify() 把JS的数组和对象转换为JSON字符串

# 虚拟DOM

关于虚拟DOM

1.本质是Object类型的对象(一般对象)

2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性

3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上

# 基础jsx语法

  1.定义虚拟dom时,不要写引号。
  2.标签中混入js表达式时要用{}3.样式的类名指定不要用class,要用className。
  4.内联样式,要用stle={{key:value}}的形式写。(style=				   {{color:'orange',fontSize:'32px'}}5.虚拟dom必须只有一个根标签。
  6.标签必须闭合。
  7.标签首字母
     (1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
     (2).若大写字母开头,react就去渲染对应的组件,若组件中没有定义,则报错。

# 补充小知识

一定注意区分:【js语句(代码)】和【js表达式】

1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方

例如下面这些都是表达式:

- a
- a+b
- demo(1)
- arr.map()
- function test(){}

2.语句(代码)

例如下面这些都是语句(代码)

  • if(){}
  • for(){}
  • switch(){case:xxxx}

# react生成列表

  <script type="text/babel">
    // 模拟一些数据
    const data = ['Angular','React','Vue']
    // 1.创建虚拟dom
    const VDOM = (
      <div>
        <h1>react小练习--前端框架</h1>
        <ul>
          {
            data.map((item,index) => {
              return <li key={item}>{item}</li>
            })
          }
        </ul>  
      </div>
    )
    // 2.渲染虚拟dom到页面
    ReactDOM.render(VDOM,document.getElementById('test'))
  </script>
</body>

# 模块与组件 模块化与组件化

  • 模块
    • 理解:向外提供特定功能的js程序,一般就是一个js文件
    • 为什么拆成模块:随着业务逻辑增加,代码越来越复杂,拆成模块复用js,简化js编写,提高js运行效率。
  • 组件
    • 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
    • 为什么用组件:一个界面的功能更复杂,使用组件,复用代码,简化项目编码,提高运行效率。
  • 模块化:当应用的js都以模块来编写的,这个应用就是一个模块化的应用。
  • 组件化:当应用是以组件的方式实现,这个应用就是一个组件化的应用。

# React面向组件编程

# 函数式组件

  <script type="text/babel">
    // 创建函数式组件
    function Demo(){ //注意点1:首字母大写
      console.log(this); // undefined,因为babel编译后开启了严格模式
      return <h2>函数定义的组件(适用于【简单组件】的定义)</h2> //注意2:函数必须有返回值
    }
    // 渲染组件到页面   注意点3:必须写组件标签,不要写组件名字
    ReactDOM.render(<Demo></Demo>,document.getElementById('test'))
    /******
     * 执行ReactDOM.render(<...>,.....).....之后发生了什么?
     * 1,React解析组件标签,找到了Demo组件
     * 2,发现组件是使用函数定义的,随后调用该函数,将返回的虚拟dom转为真实dom,然后呈现在页面中。
     * *****/ 
  </script>

# 补充小知识--类

<script type="text/javascript">
    // 创建一个Person类
    class Person{
      // 构造器方法
      constructor(name,age){
        // 构造器中的this是谁?--- 类的实例对象
        this.name = name;
        this.age = age;
      }
      // 方法
      speak(){
        // speak方法放在哪里?--- 类的原型对象上,供实例使用
        // 通过Person实例调用speak时,speak中的this就是Person实例
        console.log(`我是${this.name},我今年${this.age}`)
      }
    }
    /**
     * 创建一个Person的实例对象
     * 要创建Person的新实例,必须使用new操作符。
     * 以这种方式调用构造函数实际上会经历以下4个步骤:
     * 1,创建一个新对象
     * 2,将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
     * 3,执行构造函数中的代码(为这个新对象添加属性)
     * 4,返回新对象
     * ***/
    const p1 = new Person('胡歌',12)
    const p2 = new Person('huggggg',19)

    console.log(p1)
    console.log(p2)
    p1.speak()
    p2.speak()
	/*........................................................................*/
    //  创建一个Student类,继承于Person类
    class Student extends Person{
      constructor(name,age,grade){
        super(name,age)
        this.grade = grade
      }
      // 重写父类继承过来的的方法
      speak(){
        console.log(`我是${this.name},今年${this.age},读${this.grade}`)
      }

      study(){
        // study方法放在哪里?---类的原型对象上,供实例使用
        // 通过Student实例调用study时,study中的this就是Student实例
        console.log('好好学习,天天向上')
      }
    }
    const t1 = new Student('zhangzhang',12,'七年级')
    console.log(t1)
    t1.speak()
    t1.study()
    /***
     * 总结:
     * 1,类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
     * 2,如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super也是必须要调用的。
     * 3,类中定义的方法,都是放在了类的原型对象上,供实例去使用。
     * **/ 

	/**
     * 类里面可以写构造器,可以写方法,还可以直接写赋值语句
     * **/ 
    class Car{
      constructor(name,price){
        this.name = name;
        this.price = price
      }
      //类中可以直接写赋值语句
      // 下面wheel=4的含义就是:给Car的实例对象添加一个属性,名为wheel值为4
      wheel = 4
    }
    const c1 = new Car('奔驰',123)
    console.log(c1)
  </script>

# 类式组件

  <script type="text/babel">
    // 1,创建类式组件
    class Demo extends React.Component{
      render(){
        //render放在哪里?--- Demo的原型对象上,供实例使用
        //render中的this是谁?--- Demo的实例对象(Demo组件实例对象)。
        console.log('render中的this:',this) 
        return <h2>类定义的组件(适用于【复杂组件】的定义)</h2>
      }
    }
    // 2,渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))
    /***
     * 执行ReactDOM.render(........) 之后,发生了什么?
     * 1.react解析组件标签,找到<Demo/>组件
     * 2.发现组件使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
     * 3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
     * **/ 
  </script>

# 状态(state)

关于状态的一些碎碎念:组件也是有状态的,组件的状态驱动页面,组件的状态里面存着数据(数据放在状态里面),数据的改变就会驱动着页面的展示。

  • state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
  • 组件被称为"状态机",通过更新组件的state来更新对应的页面显示(重新渲染组件)

<strong style="color:#F00">注意:</strong>

  • 组件中render方法中的this为组件实例对象
  • 组件自定义的方法中this为undefined,如何解决?
    • 强制绑定this:通过函数对象的bind()
    • 箭头函数
  • 状态数据,不能直接修改或更新

# state初始

<script type="text/babel">
    // 1,创建组件
    class Weather extends React.Component{
      /**
       * 构造器调用几次?
       *    页面只有一个<Weather/>组件的实例,只写了一次<Weather/>标签
       *    构造器执行了一次 
       * ***/ 
      constructor(props){
        console.log('constructor-----')
        super(props)
        // 初始化状态
        this.state = { isHot:true,wind:'台风'}
        // 解决 changeWeather this指向问题
        this.changeWeather = this.changeWeather.bind(this)
      }
      /**
       * render调用几次?
       *    1+n次
       *    1是初始化那次
       *    n是状态更新的次数
       * ***/ 
      render(){
        console.log('render---')
        // console.log(this);
        const {isHot,wind} = this.state
        return <h1 onClick={this.changeWeather}>今天{isHot ? '热':'冷'},{wind}</h1>
      }
      // changeWeather调用几次? --- 点击几次调用几次
      changeWeather(){
        /*****
         * changeWeather放在哪里? --- Weather原型对象上,供实例使用
         * 用于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
         * 类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
         * *******/
        // 获取原来isHot
        const isHot = this.state.isHot
        // 注意注意:状态必须通过setState进行更新,且更新是一种合并,不是替换(同名的直接覆盖了,不同名的保持原来)
        this.setState({isHot:!isHot})

        // 注意注意:状态(State)不可以直接更改,必须调用内置的API,下面的写法就是直接更改,是错误的写法
        // this.state.isHot = !isHot //这是错误的写法
      }
    }
    // 2.渲染页面到组件
    ReactDOM.render(<Weather/>,document.getElementById('test'))
  </script>

# state精简版

<script type="text/babel">
    // 1,创建组件
    class Weather extends React.Component{
      // 初始化状态
      state = { isHot:true,wind:'台风'}
      render(){
        const {isHot,wind} = this.state
        return <h1 onClick={this.changeWeather}>今天{isHot ? '热':'冷'},{wind}</h1>
      }
      /***
       * 自定义方法:
       * 要用赋值语句+箭头函数(没有自己的this,假如有this找自己外层的this作为自己的this) 
       * **/
      changeWeather = ()=>{
        const isHot = this.state.isHot
        this.setState({isHot:!isHot})
      }
    }
    // 2.渲染页面到组件
    ReactDOM.render(<Weather/>,document.getElementById('test'))
  </script>

# 补充小知识 -- 原生事件绑定

<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button onclick="clickBtn3()">按钮3</button>
<script>
    const btn1 = document.getElementById('btn1')
    btn1.addEventListener('click',()=>{
        alert('1--click')
    })
    const btn2 = document.getElementById('btn2')
    btn2.onclick = ()=>{
        alert('2--click')
    }
    function clickBtn3(){
        alert('3-click')
    }
</script>

# props

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

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

  • 作用

    • 通过标签属性从组件外向组件内传递变化的数据
    • 注意:组件内部不要修改props数据
  • 编码操作

    • 内部读取某个属性值:this.props.name

    • 对props中的属性值进行类型限制和必要性限制

      //使用prop-types库进行限制(需要引入prop-types库)
      Person.propTypes = {
      	name: PropTypes.string.isRequired,
      	age: PropTypes.number
      }
      
    • 默认值设置

      Person.defaultProps = {
      	age:18,
      	sex:'女'
      }
      
    • 扩展属性:将对象的所有属性通过props传递

      <Person {...person}/>
      
    • 组件类的构造函数

      constructor(props){
      	super(props)
      	console.log(props) //打印所有属性
      }
      

# props基本使用

<script type="text/babel">
    //1, 创建组件
    class Person extends React.Component{
      render(){
        const {name,age,sex} = this.props
        return (
          <ul>
            <li>姓名:{name}</li>  
            <li>年龄:{age}</li>  
            <li>性别:{sex}</li>  
          </ul>
        )
      }
    }
    //2,渲染组件到页面
    ReactDOM.render(<Person name='胡歌' age='12' sex='男'/>,document.getElementById('test1'))
    ReactDOM.render(<Person name='杨过' age='12' sex='男'/>,document.getElementById('test2'))
    ReactDOM.render(<Person name='程英' age='12' sex='女'/>,document.getElementById('test3'))
  </script>

# props批量传递

  <script type="text/babel">
    //1, 创建组件
   	...........
    //2,渲染组件到页面
    ReactDOM.render(<Person name='胡歌' age='12' sex='男'/>,document.getElementById('test1'))
    ReactDOM.render(<Person name='杨过' age='12' sex='男'/>,document.getElementById('test2'))

    const p = {name:'程英',age:23,sex:'女'}
    // ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
    // react 这里展开运算符只适用于标签属性的传递
  ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
  </script>

# 限制props

  <!-- 引入prop-types用于对组件标签属性进行限制 PropTypes-->
  <script src="../js/old/prop-types.js"></script>
  <script type="text/babel">
    //1, 创建组件
    class Person extends React.Component{
      render(){
        // props是只读的
        // this.props.name = '小龙女' 这种写法会报错,因为props是只读的。
        const {name,age,sex} = this.props
        return (
          <ul>
            <li>姓名:{name}</li>  
            <li>年龄:{age + 1}</li>  
            <li>性别:{sex}</li>  
          </ul>
        )
      }
    }
    // 对标签属性进行类型,必要性的限制
    Person.propTypes = {
      name: PropTypes.string.isRequired,//限制名字必传,而且为字符串
      sex: PropTypes.string,//限制性别为字符串
      age: PropTypes.number,//限制age为数值
      speak: PropTypes.func,//限制speak为函数(用func)
    }
    // 指定标签属性默认值
    Person.defaultProps = {
      sex:'女',
      age: 18
    }
    //2,渲染组件到页面
    ReactDOM.render(<Person name='胡歌' age={12} sex='男'/>,document.getElementById('test1'))
    ReactDOM.render(<Person name='杨过' age={19} sex='男'/>,document.getElementById('test2'))
    const p = {name:'程英',age:23,sex:'女'}
    ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
  </script>

# props简写

  <script type="text/babel">
    //1, 创建组件
    class Person extends React.Component{
      // 对标签属性进行类型,必要性的限制
      static propTypes = {
        name: PropTypes.string.isRequired,//限制名字必传,而且为字符串
        sex: PropTypes.string,//限制性别为字符串
        age: PropTypes.number,//限制age为数值
        speak: PropTypes.func,//限制speak为函数(用func)
      }
      // 指定标签属性默认值
      static defaultProps = {
        sex:'女',
        age: 18
      }   
      render(){   
        const {name,age,sex} = this.props
        return (
          <ul>
            <li>姓名:{name}</li>  
            <li>年龄:{age + 1}</li>  
            <li>性别:{sex}</li>  
          </ul>
        )
      }
    }
    //2,渲染组件到页面
 	........................
  </script>

# 函数式组件使用props

  <script type="text/babel">
    // 函数式组件
    function Person(props){
      const {name,age,sex} = props
      return (
        <ul>
          <li>{name}</li> 
          <li>{age}</li> 
          <li>{sex}</li> 
        </ul>
      )
    }
    // 对标签属性进行类型,必要性的限制
    Person.propTypes = {
      name: PropTypes.string.isRequired,//限制名字必传,而且为字符串
      sex: PropTypes.string,//限制性别为字符串
      age: PropTypes.number,//限制age为数值
    }
    // 指定标签属性默认值
    Person.defaultProps = {
      sex:'女',
      age: 18
    }
    //2,渲染组件到页面
    ReactDOM.render(<Person name='胡歌' />,document.getElementById('test1'))

# 补充小知识--解构

官方文档参考:解构赋值 (opens new window)

  <script type="text/javascript">
    let arr1 = [1,2,3,4,5,6]
    let arr2 = [7,8,9,0]
    console.log(...arr1)//展开一个数组 1 2 3 4 5 6
    let arr3 = [...arr1,...arr2] //连接数组
    console.log(arr3) //[1,2,3,4,5,6,7,8,9,0]
    // 在函数中使用 不确定传递参数的个数
    function sum(...numbers){
      return numbers.reduce((preValue,currentValue)=>{
        return preValue + currentValue
      })
    }
    console.log(sum(1,2,3,4,5))
    // 构造对象字面量时使用展开语法
    let person = {name:'huge',age:12}
    // console.log(...person) 这个是错误的写法,展开运算符不能展开对象
    let person2 = {...person}
    person.name = '杨过'
    console.log(person)
    console.log(person2)
    // 合并(重名的就覆盖,原来没有的就添加上)
    let person3 = {...person,name:'程英',address:'桃花岛'}
    console.log(person3)
  </script>

# refs

  • 组件内的标签可以定义ref属性来标识自己

# 字符串形式ref

  • 条件允许的情况下尽量不用字符串形式ref
<script type="text/babel">
    // 1,创建组件
    class Test extends React.Component{
      showTips1 = () => {
        console.log(this)
        const {input1} = this.refs
        alert(input1.value + '这些白云聚了又散,散了又聚。人生离合,亦复如此')
      }
      showTips2 = () => {
        const {input2} = this.refs
        alert(input2.value + '既见君子云胡不喜')
      }
      render(){
        return (
          <div>
            <input ref="input1" type="text" placeholder="程英说:"/>
            <button onClick={this.showTips1}>点击的那个按钮</button>
            <input onBlur={this.showTips2} ref="input2" type="text" placeholder="程英说:失去焦点提示"/>  
          </div>
        )
      }
    }
    // 2,渲染组件
    ReactDOM.render(<Test/>,document.getElementById('test'))
  </script>

# 回调函数形式ref

<script type="text/babel">
    // 1,创建组件
    class Test extends React.Component{
      showTips1 = () => {
        console.log(this)
        const {input1} = this
        alert(input1.value + '这些白云聚了又散,散了又聚。人生离合,亦复如此')
      }
      showTips2 = () => {
        const {input2} = this
        alert(input2.value + '既见君子云胡不喜')
      }
      render(){
        return (
          <div>
            <input ref={c => this.input1 = c} type="text" placeholder="程英说:"/>
            <button onClick={this.showTips1}>点击的那个按钮</button>
            <input onBlur={this.showTips2} ref={c => this.input2 = c} type="text" placeholder="程英说:失去焦点提示"/>  
          </div>
        )
      }
    }
    // 2,渲染组件
    ReactDOM.render(<Test/>,document.getElementById('test'))
  </script>

# 回调ref中回调执行次数的问题(内联回调和类绑定回调区别)

  • 内联回调和类绑定回调无关紧要
  <script type="text/babel">
    // 1,创建组件
    class Test extends React.Component{
      state = {isHot:false}
      showTips1 = () => {
        console.log(this)
        const {input1} = this
        alert(input1.value)
      }
      changeWeather = () => {
        // 获取天气状态
        const {isHot} = this.state
        // 更新状态
        this.setState({isHot:!isHot})
      }
      saveInput = c => {
        this.input1 = c;
        console.log('saveinput',c)
      }
      render(){
        const {isHot} = this.state
        return (
          <div>
            <div>天气{isHot?'热':'冷'}</div>
            {/*<input ref={c => {this.input1 = c,console.log("EEEE",c)}} type="text" placeholder="程英说:"/> 会出现更新执行两次*/}
            <input ref={this.saveInput} type="text" placeholder="程英说:"/>
            <br/>
            <br/>
            <button onClick={this.showTips1}>点击的那个按钮</button>
            <button onClick={this.changeWeather}>切换天气</button>
          </div>
        )
      }
    }
    // 2,渲染组件
    ReactDOM.render(<Test/>,document.getElementById('test'))
  </script>

# createRef

  • 虽然是最复杂的一种,但是是React最推荐的一种
  <script type="text/babel">
    // 1,创建组件
    class Test extends React.Component{
      /***
       * React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
       * ***/ 
      myRef1 = React.createRef();
      myRef2 = React.createRef();
      showTips1 = () => {
        alert(this.myRef1.current.value)
      }
      showTips2 = () => {
        alert(this.myRef2.current.value)
      }
      render(){
        return (
          <div>
            <input ref={this.myRef1} type="text" placeholder="程英说:"/>
            <button onClick={this.showTips1}>点击的那个按钮</button>
            <input onBlur={this.showTips2} ref={this.myRef2} type="text" placeholder="失去焦点数据"/>
          </div>
        )
      }
    }
    // 2,渲染组件
    ReactDOM.render(<Test/>,document.getElementById('test'))
  </script>

# 事件处理

  <script type="text/babel">
    // 1,创建组件
    class Test extends React.Component{
      /***
       * 1,通过onXxx属性指定事件处理函数(注意大小写)
       *    ①React使用的是自定义(合成)事件,而不是使用原生DOM事件----为了更好的兼容性
       *    ②React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)---- 为了高效
       * 2,通过event.target得到发生事件的DOM元素对象 -- 不要过度使用ref 
       * ***/ 
      myRef1 = React.createRef();
      showTips1 = () => {
        alert(this.myRef1.current.value)
      }
      showTips2 = (event) => {
        alert(event.target.value)
      }
      render(){
        return (
          <div>
            <input ref={this.myRef1} type="text" placeholder="程英说:"/>
            <button onClick={this.showTips1}>点击的那个按钮</button>
            <input onBlur={this.showTips2}  type="text" placeholder="失去焦点数据"/>
          </div>
        )
      }
    }
    // 2,渲染组件
    ReactDOM.render(<Test/>,document.getElementById('test'))
  </script>

# 收集表单数据

# 非受控组件(现用现取)

  <script type="text/babel">
    // 1 创建组件
    class Login extends React.Component{
      handleLogin = (event) => {
        event.preventDefault(); //阻止表单提交
        const {username,password} = this
        alert(`用户名:${username.value},密码:${password.value}`)
      }
      render(){
        return (
          <form onSubmit={this.handleLogin}>
            用户名:<input ref={c => this.username = c} type="text" name="username"/>
            密码:<input ref={c => this.password = c}  type="password" name="password"/>
            <button>登录</button>
          </form>
        )
      }
    }
    // 2 渲染组件
    ReactDOM.render(<Login/>,document.getElementById('test'))
  </script>

# 受控组件

  • 受控组件优势就是能够省略ref
  <script type="text/babel">
    // 1 创建组件
    class Login extends React.Component{
      // 初始化状态
      state = {
        username: '',
        password: ''
      } 
      // 保存用户名到状态中
      saveUsername = (event) => {
        this.setState({username: event.target.value})
      }
      // 保存密码到状态中
      savePassword = () => {
        this.setState({password: event.target.value})
      }
      handleLogin = (event) => {
        event.preventDefault(); //阻止表单提交
        const {username,password} = this.state
        alert(`用户名:${username},密码:${password}`)
      }
      render(){
        return (
          <form onSubmit={this.handleLogin}>
            用户名:<input onChange={this.saveUsername} type="text" name="username"/>
            密码:<input onChange={this.savePassword} type="password" name="password"/>
            <button>登录</button>
          </form>
        )
      }
    }
    // 2 渲染组件
    ReactDOM.render(<Login/>,document.getElementById('test'))
  </script>

# 高阶函数_函数柯理化

  • 高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数
    • 1.若A函数,接收的参数是一个函数,那么A就可以称为高阶函数。
    • 2.若A函数,调用的返回值依然是一个函数,那么A就可以称为高阶函数。
    • 常见的高阶函数:Promise,setTimeout,arr.map()等等
  • 函数的柯理化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

# 使用柯理化

<script type="text/babel">
    // 1 创建组件
    class Login extends React.Component{
      // 初始化状态
      state = {
        username: '',
        password: ''
      } 
      // 保存表单数据到状态中
      saveFormData = (dataType) => {
        return event => {
          this.setState({[dataType]:event.target.value})
        }
      }
      handleLogin = (event) => {
        event.preventDefault(); //阻止表单提交
        const {username,password} = this.state
        alert(`用户名:${username},密码:${password}`)
      }
      render(){
        return (
          <form onSubmit={this.handleLogin}>
            用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
            密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
            <button>登录</button>
          </form>
        )
      }
    }
    // 2 渲染组件
    ReactDOM.render(<Login/>,document.getElementById('test'))
  </script>

# 不用柯理化实现

  <script type="text/babel">
    // 1 创建组件
    class Login extends React.Component{
      // 初始化状态
      state = {
        username: '',
        password: ''
      } 
      // 保存表单数据到状态中
      saveFormData = (dataType,event) => {
        this.setState({[dataType]:event.target.value})
      }
      handleLogin = (event) => {
        event.preventDefault(); //阻止表单提交
        const {username,password} = this.state
        alert(`用户名:${username},密码:${password}`)
      }
      render(){
        return (
          <form onSubmit={this.handleLogin}>
            用户名:<input onChange={event => this.saveFormData('username',event)} type="text" name="username"/>
            密码:<input onChange={event => this.saveFormData('password',event)} type="password" name="password"/>
            <button>登录</button>
          </form>
        )
      }
    }
    // 2 渲染组件
    ReactDOM.render(<Login/>,document.getElementById('test'))
  </script>

# 生命周期

1,组件对象从创建到死亡经历特定阶段。

2,React组件对象包含一系列钩子(React会在合适时间点勾出来执行一下)函数(生命周期回调函数),在特定时刻调用。

3,我们定义组件时候,在特定的生命周期回调函数中做特定的工作。

  • 重要的钩子

    • render: 初始化渲染或者更新渲染调用
    • componentDidMount: 开启监听,发送ajax请求
    • componentWillUnmount: 做一些收尾工作,如:清理定时器
  • 即将废弃的钩子

    • componentWillMount

    • componentWillReceiveProps

    • componentWillUpdate

    旧版本(16)使用会出现警告,后续版本可能需要加上UNSAFE_前缀才能使用,以后可能会彻底废弃,不建议使用。(这三个钩子较少使用)

# 生命周期(旧版本)

# 生命周期(旧)三阶段
  • 初始化阶段:由ReactDOM.render()触发---初次渲染

    1,constructor()

    2,componentWillMount()

    3,render() 【必须】

    4,componentDidMount() 【常用】,一般在这个钩子中做一些初始化的事情,例如:开启定时器,发送网络请求,订阅消息

  • 更新阶段:由组件内部this.setState()或者父组件更新render触发

    1,shouldComponentUpdate()

    2,componentWillUpdate()

    3,render() 【必须】

    4,componentDidUpdate()

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

    1,componentWillUnmount()【常用】,一般在这个钩子中做一些收尾的事情,例如:关闭定时器,取消订阅消息

# 生命周期(旧)---componentDidMount
  <script type="text/babel">
    // 1 创建组件
    // 生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
    class Life extends React.Component {
      state = { opacity:0.1 }
      study = () => {
        // 清除定时器
        clearInterval(this.timer)
        // 卸载组件
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
      }
      // 组件挂载完毕调用
      componentDidMount(){
        console.log('componentDidMount---')
        // 定时器
        this.timer = setInterval(() => {
          // 获取原状态
          let { opacity } = this.state
          // 减小0.1
          opacity -= 0.1
          if(opacity <= 0) opacity = 1
          // 设置新的透明
          this.setState({opacity})
        },200)
      }
      // render调用时机:初始化渲染,状态更新之后
      render(){
        console.log('render---')
        return(
          <div>
            <h2 style={{ opacity:this.state.opacity }} >
              React学习
            </h2>
            <button onClick={this.study}>不懂就问不会就学(练习生命周期--卸载组件)</button>
          </div>
        )
      }
    }
    // 2 渲染组件
    ReactDOM.render(<Life/>,document.getElementById('test'))
  </script>

⚠️定时器没有清除的bug

react-dom.development.js:82 Warning: 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. in Life

# 生命周期(旧)---componentWillUnmount
.......
study = () => {
    // 卸载组件
    ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
........
 // 组件将要卸载
 componentWillUnmount(){
     //清除定时器
     clearInterval(this.timer)
 }
.........
# 生命周期(旧)--- 挂载时
<script type="text/babel">
    // 创建组件
    class Count extends React.Component{
      // 构造器
      constructor(props){
        console.log('Count-构造器-constructor')
        super(props)
        // 初始化状态
        this.state = { count:0 }
      }

      //加1按钮回调
      add = () => {
        // 获取原状态值
        const { count } = this.state
        // 更新状态
        this.setState({count:count+1})
      }

      // 组件将要挂载的钩子
      componentWillMount(){
        console.log('Count-组件将要挂载的钩子-componentWillMount')
      }
      //组件挂载完毕的钩子 
      componentDidMount(){
        console.log('Count-组件挂载完毕的钩子-componentDidMount')
      }

      render(){
        console.log('Count-render')
        const {count} =  this.state
        return (
          <div>
            <h6>Sum: {count}</h6>  
            <button onClick={this.add}>+1</button>
          </div>
        )
      }
    }
    // 渲染组件
    ReactDOM.render(<Count/>,document.getElementById('test'))
  </script>
# 生命周期(旧)--- setState()出发
  • 如上图生命周期(旧)setState()开始经过的钩子函数:

    setState > shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate

  • shouldComponentUpdate:

    控制组件更新的阀门

    ​ 如果不写shouldComponentUpdate这个钩子函数,底层也会给补一个返回值,而且返回值为true

    ​ 如果写了这个钩子函数就以已经写的为主

    ​ return true 阀门就是开启的 setState出发的流程就都是可以走下去的

    ​ return false 阀门就是关闭的,从shouldComponentUpdate之后的钩子流程不再走下去(也就是说componentWillUpdate,render,componentDidUpdate这些钩子函数都不走了,所以页面也没法更新了)

    如果shouldComponentUpdate钩子函数不写返回值会报错:

    ⚠️ Warning: Count.shouldComponentUpdate(): Returned undefined instead of a boolean value. Make sure to return true or false. in Count

  • setState()整体流程代码

    <script type="text/babel">
        // 创建组件
        class Count extends React.Component{
          // 构造器
          constructor(props){
            console.log('Count-构造器-constructor')
            super(props)
            // 初始化状态
            this.state = { count:0 }
          }
          //加1按钮回调
          add = () => {
            // 获取原状态值
            const { count } = this.state
            // 更新状态
            this.setState({count:count+1})
          }
          // 卸载组件按钮的回调函数
          removeComponent = ()=> {
            ReactDOM.unmountComponentAtNode(document.getElementById('test'))
          }
          // 组件将要挂载的钩子
          componentWillMount(){
            console.log('Count-组件将要挂载的钩子-componentWillMount')
          }
          //组件挂载完毕的钩子 
          componentDidMount(){
            console.log('Count-组件挂载完毕的钩子-componentDidMount')
          }
          //组件将要卸载的钩子
          componentWillUnmount(){
            console.log('Count-组件将要卸载的钩子-componentWillUnmount')
          }
    	  // 控制组件更新的[阀门]
          shouldComponentUpdate(){
            console.log('Count-控制组件更新的[阀门]-shouldComponentUpdate');
            return true
          }
          //组件将要更新的钩子
          componentWillUpdate(){
            console.log('Count-组件将要更新的钩子-componentWilUpdate');
          }
          // 组件更新完毕的钩子
          componentDidUpdate(){
            console.log('Count-组件更新完毕的钩子-componentDidUpdate');
          }
          render(){
            console.log('Count-render')
            const {count} =  this.state
            return (
              <div>
                <h6>Sum: {count}</h6>  
                <button onClick={this.add}>+1</button>
                <button onClick={this.removeComponent}>卸载</button>
              </div>
            )
          }
        }
        // 渲染组件
        ReactDOM.render(<Count/>,document.getElementById('test'))
      </script>
    
# 生命周期(旧)--- forceUpdate()出发
  • 如上图生命周期(旧)forceUpdate()开始经过的钩子函数:

    forceUpdate > componentWillUpdate > render > componentDidUpdate

  • forceUpdate整体流程代码(省略部分和setState部分一样)

    ....
    //这个不受【shouldComponentUpdate】阀门的控制
    forceComponent = ()=> {
    	this.forceUpdate()
    }
    ........
        return (
            <div>
                <h6>Sum: {count}</h6>  
                <button onClick={this.add}>+1</button>
                <button onClick={this.removeComponent}>卸载</button>
                <button onClick={this.forceComponent}>不更改任何状态中的数据,强制更新一下</button>
            </div>
        )
    ........
    
# 生命周期(旧)--- 父组件render出发
 <script type="text/babel">
   /***
     * 父组件--Farther
     * **/ 
    class Farther extends React.Component{
      // 初始化状态
      state = {fatherTxt:'老骥伏枥志在千里'}
      changeFatherTxt = ()=>{
        this.setState({fatherTxt:'烈士暮年壮心不已'})
      }
      render(){
        return(
          <div> 
            <h2>我是父组件--Father--我不展示文字-让子组件展示</h2>
            <button onClick={this.changeFatherTxt}>改变文字</button>
            <Son fatherTxt={this.state.fatherTxt}/>  
          </div>
        )
      }
    }
    /**
     * 子组件--Son
     * */ 
    class Son extends React.Component{
      /**
       * 组件将要接受新的props的钩子
       * 这个【钩子函数】有个小坑
       * 第一次不能触发这个函数
       * 参数:props
       * **/
      componentWillReceiveProps(props){
        console.log('Son--第二次触发才会调用--componentWillReceiveProps',props);
      }
      //控制组件更新的[阀门] 
      shouldComponentUpdate(){
        console.log('Son-控制组件更新的[阀门]-shouldComponentUpdate');
        return true
      }
      //组件将要更新的钩子
      componentWillUpdate(){
        console.log('Son-组件将要更新的钩子-componentWilUpdate');
      }
      //组件更新完毕的钩子
      componentDidUpdate(){
        console.log('Son-组件更新完毕的钩子-componentDidUpdate');
      } 
      render(){
        console.log('Son-去更新-render');
        return(
          <div>
            <h3>我是子组件--Son--接收到的父组件信息是:{this.props.fatherTxt}</h3> 
          </div>
        )
      }
    }
    // 渲染组件
    ReactDOM.render(<Farther/>,document.getElementById('test'))
  </script>

# 生命周期(新版本)

挂载时,新的生命周期流程图中虽然没有了componentWillMount但是在相同位置出现了getDerivedStateFromProps

更新时,新的生命周期中没有了componentWillReceivePropsgetDerivedStateFromProps横跨了挂载时和更新时,componentWillUpdate没有了,render之后新增了getSnapshotBeforeUpdate

# 生命周期(新)三阶段
  • 初始化阶段:由ReactDOM.render()触发---初次渲染

    1,constructor()

    2,getDerivedStateFromProps

    3,render() 【必须】

    4,componentDidMount() 【常用】,一般在这个钩子中做一些初始化的事情,例如:开启定时器,发送网络请求,订阅消息

  • 更新阶段:由组件内部this.setState()或者父组件更新render触发

    1,getDerivedStateFromProps

    2,shouldComponentUpdate()

    3,render() 【必须】

    4 , getSnapshotBeforeUpdate

    5,componentDidUpdate()

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

    1,componentWillUnmount()【常用】,一般在这个钩子中做一些收尾的事情,例如:关闭定时器,取消订阅消息

# 生命周期(新)--- getDerivedStateFromProps
<script type="text/babel">
.....
 /**
   * 若state的值在任何时候都取决于props,那么可以使用
   * 无论是初始化或者修改都是不起作用的,完全听props指挥,适用于一些非常特殊的情况,使用场景罕见。
   * 派生状态,会导致代码冗余,组件难以维护,了解即可。
   * 从生命周期(新)流程图上可以看出来getDerivedStateFromProps横跨挂载和更新
   * 参数:(props,state)
   * **/
   static getDerivedStateFromProps(props,state){
       console.log('Count-state的值在任何时候都取决于props-		 getDerivedStateFromProps',props,state)
        return props
      }
..........
// 渲染组件
ReactDOM.render(<Count name='胡歌'/>,document.getElementById('test'))
</script>
# 生命周期(新)--- getSnapshotBeforeUpdate
.......
  /**
   * getSnapshotBeforeUpdate在最新一次渲染输出(提交到DOM节点)之前调用。
   * 此生命周期的任何返回值将作为参数传递给componentDidUpdate()(因为该函数的下游是componentDidUpdate()函数)。
    * 该方法不常用,但是可能出现在UI处理中,如需要以特殊方式处理滚动位置的聊天线程等。
    * ***/ 
      getSnapshotBeforeUpdate(prevProps,prevState){
        console.log('Count-getSnapshotBeforeUpdate');
        return '随便写个快照值'
      }
  ......
  // 组件更新完毕的钩子
      componentDidUpdate(prevProps,prevState,snapshotValue){
        console.log('Count-组件更新完毕的钩子-componentDidUpdate',prevProps,prevState,snapshotValue);
      } 
....
# getSnapshotBeforeUpdate使用场景
 <style type="text/css">
    .list{
      width: 900px;
      height: 180px;
      background-color: orangered;
      overflow: auto;
      color: #fff;
      text-align: center;
    }
    .news{
      height: 30px;
    }
  </style>
 <script type="text/babel">
    // 创建组件
    class NewsList extends React.Component{
      state = {newsArr:[]}
      componentDidMount(){
        setInterval(() => {
          // 获取原状态
          const {newsArr} = this.state
          // 模拟一条新闻
          const news = '胡歌将出演苏轼一角'+ (newsArr.length + 1)
          // 更新状态
          this.setState({newsArr:[news,...newsArr]})
        }, 1000);
      }
      //返回最新的新闻还没有渲染出来时盒子的高度
      getSnapshotBeforeUpdate(){
        return this.refs.list.scrollHeight 
      }
      componentDidUpdate(prevProps,prevState,snapshotHeight){
        this.refs.list.scrollTop += this.refs.list.scrollHeight - snapshotHeight
      }
      render(){
        return(
          <div className="list" ref="list">
            {
              this.state.newsArr.map((newsItem,index) => {
                return <div key={index} className="news">{newsItem}</div>
              })
            }
          </div>
        )
      }
    }
    // 渲染组件
    ReactDOM.render(<NewsList/>,document.getElementById('test'))
  </script>
# 重要的钩子
  • render:初始化渲染或者更新渲染调用
  • componentDidMount: 开启监听,发送ajax请求
  • componentWillUnmount:做一些收尾的工作,如:清理定时器
# 废弃的钩子
  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

# DOM的Diffing算法

# 虚拟DOM中key的作用

    /********
     * 经典面试题
     * 1),react/vue中的key有什么作用?(key的内部原理是什么)
     * 2),为什么遍历列表的时候,key最好不要用index?
     * 
     * 1,虚拟DOM中key的作用:
     *   - 简单说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
     *   - 详细说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
     *   1-a,旧虚拟DOM中找到了与新虚拟DOM相同的key:
     *    1-a-1:若虚拟DOM中内容没有变,直接使用之前的真实DOM,
     *    1-a-2:若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
     *   1-b,旧虚拟DOM中未找到与新虚拟DOM相同的key
     *     1-b-1:根据数据创建新的真实DOM,随后渲染到页面
     * 2,用index作为key可能会引发的问题:
     *   2-1:若对数据进行:逆序添加,逆序删除等破坏顺序的操作:会产生没必要的真实DOM更新(界面的展示没有问题,但是效率低)
     *   2-2:若结构中还包含输入类的DOM:会产生错误DOM更新(界面展示也有问题)
     *   2-3:注意:如果不存在对数据的逆序添加,逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
     * 3,开发中如何选择key?
     *   3-1:最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等唯一值。
     *  3-2:如果确定只是简单的数据展示,用index也是可以的。 
     * *******/