React学习(三)----- 组件的生命周期

1、生命周期的引入

1、组件从创建到死亡它会经历一些特定的阶段。

2、React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。

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

2、React生命周期(旧)

  • 图解:

    

  • 初始化阶段:由ReactDOM.render()触发 ----- 初次渲染(顺序为
    constructor(构造器) ----- 
    componentWillMount (组件将要挂载的钩子)----- 
    render(初次渲染) ----- 
    componentDidMount(组件挂载完毕的钩子:常用的钩子一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息等
  • 更新阶段:由组件内部this.setSate()或父组件重新render触发
    • 组件内部的setState正常更新触发(顺序为
      shouldComponentUpdate(控制组件更新的 “阀门” ,这个钩子如果不写默认返回true,如果写了必须有返回值,返回false时无法更新页面)-----
      componentWillUpdate(组件将要更新的钩子)----- 
      render(初次渲染) ----- 
      componentDidUpdate(oldProps,oldState)(组件更新完毕的钩子)
    • forceUpdate强制更新触发(顺序为
      componentWillUpdate(组件将要更新的钩子)----- 
      render(初次渲染) ----- 
      componentDidUpdate(oldProps,oldState)(组件更新完毕的钩子)
      
      
       <button onClick={this.force}>不更改状态中的任何数据强制更新</button>  

      //
      强制更新 force = () =>{ this.forceUpdate() }
    • 父组件重新render触发(顺序为
      componentWillReceiveProps(组件将要接收新的props的钩子  首次渲染不执行,这是一个坑,所以好多人呼吁应该重新命名为 "componentWillReceiveNewProps")-----
      shouldComponentUpdate(控制组件更新的 “阀门” ,这个钩子如果不写默认返回true,如果写了必须有返回值,返回false时无法更新页面)-----
      componentWillUpdate(组件将要更新的钩子)----- 
      render(初次渲染) ----- 
      componentDidUpdate(oldProps,oldState)(组件更新完毕的钩子)
  • 卸载组件:由ReactDOM.unmountComponentAtNode()触发(顺序为
    componentWillUnmount(组件将要卸载的钩子:常用的钩子一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息等
  •  父子组件生命周期钩子函数的执行顺序
    • 父组件
      class Parent extends React.Component{
                // 构造器
                constructor(props){
                    super(props)
                    console.log('Parent-constructor')
                    this.state = {
                        age:55
                    }
                }
                // 组件将要挂载的钩子
                componentWillMount(){
                    console.log('Parent-componentWillMount')
                }
                // 组件挂载完毕的钩子
                componentDidMount(){
                    console.log('Parent-componentDidMount')
                }
                componentWillReceiveProps(){
                  console.log('Parent-componentWillReceiveProps')
                }
                // 相当于一个 “阀门” ,这个钩子如果不写默认返回true,如果写了
                shouldComponentUpdate(){
                    console.log('Parent-shouldComponentUpdate')
                    return true
                }
                // 组件将要更新的钩子
                componentWillUpdate(){
                    console.log('Parent-componentWillUpdate')
                }
                // 组件更新完毕的钩子
                componentDidUpdate(){
                    console.log('Parent-componentDidUpdate')
                }
                // 组件将要卸载的钩子
                componentWillUnmount(){
                    console.log('Parent-componentWillUnmount')
                }
                // 组件渲染
                render(){
                  console.log('Parent-render')
                  let {age} = this.state
                  return (
                    <div>
                      <div style={{width:'300px',height:'200px',border:'1px solid #ccc'}}>
                        <h2>我是爸爸,我今年{age}岁了!</h2>
                        <button onClick={this.handlerChange}>点我修改年龄</button>  
                      </div>
                      <Children {...this.state}/>
                    </div>
                  )
                }
                handlerChange = () => {
                  let {age} = this.state;
                  this.setState({
                    age : age + 1
                  })
                }
              }
      View Code

      子组件

       class Children extends React.Component{
                // 构造器
                constructor(props){
                    super(props)
                    console.log('Children-constructor')
                    this.state = {
                      
                    }
                }
                // 组件将要挂载的钩子
                componentWillMount(){
                    console.log('Children-componentWillMount')
                }
                // 组件挂载完毕的钩子
                componentDidMount(){
                    console.log('Children-componentDidMount')
                }
                // 组件将要接收新的props的钩子 首次渲染不执行
                componentWillReceiveProps(props){
                  console.log('Children-componentWillReceiveProps',props)
                }
                // 相当于一个 “阀门” ,这个钩子如果不写默认返回true,如果写了
                shouldComponentUpdate(){
                    console.log('Children-shouldComponentUpdate')
                    return true
                }
                // 组件将要更新的钩子
                componentWillUpdate(){
                    console.log('Children-componentWillUpdate')
                }
                // 组件更新完毕的钩子
                componentDidUpdate(){
                    console.log('Children-componentDidUpdate')
                }
                // 组件将要卸载的钩子
                componentWillUnmount(){
                    console.log('Children-componentWillUnmount')
                }
                // 组件渲染
                render(){
                  console.log('Children-render')
                  let {age} = this.props
                  return (
                    <div style={{width:'300px',height:'200px',border:'1px solid #ccc',marginTop:'10px'}}>
                      <h2>我是女儿~~~我的爸爸今年{age}了~~~</h2>
                    </div>
                  )
                }
              }
      View Code

      渲染

      // 2、将组件渲染到页面上
      ReactDOM.render(<Parent />,document.getElementById('box'))
    • 加载渲染的时候
      Parent-constructor -----> Parent-componentWillMount -----> Parent-render -----> Children-constructor ----->  Children-componentWillMount -----> Children-render -----> Children-componentDidMount -----> Parent-componentDidMount
    • 父组件更改传递给子组件的props时
      Parent-shouldComponentUpdate -----> Parent-componentWillUpdate -----> Parent-render -----> Children-componentWillReceiveProps  -----> Children-shouldComponentUpdate -----> Children-componentWillUpdate -----> Children-render -----> Children-componentDidUpdate -----> Parent-componentDidUpdate
    • 子组件更新state的时候
      Children-shouldComponentUpdate -----> Children-componentWillUpdate -----> Children-render -----> Children-componentDidUpdate
  • 生命周期钩子函数的执行次数
    1)react生命周期函数中有哪些生命周期函数只会执行一次???
        constructor -----> componentWillMount -----> componentDidMount -----> componentWillUnMount
    2)react生命周期函数中有哪些生命周期函数会执行多次?
        componentWillRecevieProps -----> shouldComponentUpdate -----> componentWillUpdate -----> render -----> componentDidUpdate
    3)key值变化时执行的生命周期函数
        componentWillUnmount -----> componentWillMount -----> render -----> componentDidMount

3、React生命周期(新)

  • 新旧生命周期钩子函数的对比
    • 引入的js文件版本为:17.0.2
      https://cdn.bootcdn.net/ajax/libs/react/17.0.2/umd/react.development.js
      https://cdn.bootcdn.net/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js
    • 有三个钩子函数即将废弃,会出现警告,解决方法是
      1)UNSAFE_componentWillMount
      2)UNSAFE_componentWillUpdate
      3)UNSAFE_componentWillReceiveProps

    • 新增了两个钩子函数
      • getDerivedStateFromProps(获取一种派生状态 ----- 即让state的值在任何时候都取决于props)没啥意义
        // 若state的值在任何时候都取决于props,那么可以使用,而且不可以被修改了
        static getDerivedStateFromProps(props,state){
            console.log('MyComponent-getDerivedStateFromProps',props,state)
            return props
        }
      • getSnapshotBeforeUpdate(在更新之前获取“快照”,
        // 可以在组件发生更改之前,从 DOM 中捕获一些信息(例如,滚动位置),
        // 此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。
        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <style>
            .list{
              width: 200px;
              height: 150px;
              background-color: aquamarine;
              overflow: auto;
            }
            .news{
              height: 30px;
            }
          </style>
        </head>
        <body>
           <!-- 容器 -->
            <div id="box"></div>
        
            <!-- 引入React的核心库 -->
            <script src="../js/react.development.js"></script>
            <!-- 引入React的扩展库,用来渲染虚拟DOM -->
            <script src="../js/react-dom.development.js"></script>
            <!-- 引入babel 将jsx转换为浏览器可以识别的js文件 并且自动开启严格模式-->
            <script src="../js/babel.min.js"></script>
            <!-- 引入propTypes 对组件标签属性的类型、必要性、默认值进行控制 -->
            <script src="../js/prop-types.js"></script>
        
            <script type="text/babel">
                // 1、创建组件
                class NewsList extends React.Component{
                    // 定义在类MyComponent上
                    static propTypes = {
        
                    }
                    static defaultProps = {
        
                    }
                    // 构造器
                    constructor(props){
                        console.log('MyComponent-constructor')
                        super(props)
                        this.state = {
                          newsList :[]
                        }
                    }
                    componentDidMount(){
                      this.timer = setInterval(()=>{
                        // 获取原列表
                        let {newsList} = this.state;
                        // 模拟一条新闻
                        const news = '新闻' + (newsList.length + 1)
                        // 更新状态
                        this.setState({
                          newsList : [news,...newsList]
                        })
                      },500)
                    }
                    getSnapshotBeforeUpdate(){
                      console.log('getSnapshotBeforeUpdate')
                      const {list} = this.refs;
                      return list.scrollHeight
                    }
                    componentDidUpdate(oldProps,oldState,snapshotValue){
                      console.log('componentDidUpdate',oldProps,oldState,snapshotValue)
                      this.refs.list.scrollTop += this.refs.list.scrollHeight - snapshotValue
                    }
                    render(){
                      let {newsList} = this.state
                      return (
                        <div className="list" ref="list">
                          {
                            newsList.map((el,index)=>{
                              return <div className="news" key={index}>{el}</div>
                            })
                          }
                        </div>
                      )
                    }
                }
                // 2、将组件渲染到页面上
                ReactDOM.render(<NewsList />,document.getElementById("box"))
            </script>
        </body>
        </html>
        View Code
    • 初始化阶段:由ReactDOM.render()触发 ----- 初次渲染(顺序为:
      constructor -----
      getDerivedStateFromProps -----
      render -----
      componentDidMount(组件挂载完毕的钩子:常用的钩子一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息等
    • 更新阶段
      • 组件内部的setState正常更新触发(顺序为
        getDerivedStateFromProps -----
        shouldComponentUpdate(控制组件更新的 “阀门” ,这个钩子如果不写默认返回true,如果写了必须有返回值,返回false时无法更新页面)-----
        render(初次渲染) ----- 
        getSnapshotBeforeUpdate ----- 
        componentDidUpdate(oldProps,oldState,snapshotValue)(组件更新完毕的钩子)
      • forceUpdate强制更新触发(顺序为
        render(初次渲染) ----- 
        getSnapshotBeforeUpdate ----- 
        componentDidUpdate(oldProps,oldState,snapshotValue)(组件更新完毕的钩子)
      • 父组件重新render触发(顺序为
        getDerivedStateFromProps -----
        shouldComponentUpdate(控制组件更新的 “阀门” ,这个钩子如果不写默认返回true,如果写了必须有返回值,返回false时无法更新页面)-----
        render(初次渲染) ----- 
        getSnapshotBeforeUpdate ----- 
        componentDidUpdate(oldProps,oldState,snapshotValue)(组件更新完毕的钩子)
    • 销毁阶段:由ReactDOM.unmountComponentAtNode()触发(顺序为
      componentWillUnmount(组件将要卸载的钩子常用的钩子一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息等
  • 图解

         

4、补充知识(DOM的Diff算法)

  •  index作为key可能产生的问题
    • 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
      • 示例
        <!--
         * @Descripttion: 
         * @version: 
         * @Author: 北栀女孩儿
         * @Date: 2021-08-30 09:28:15
         * @LastEditTime: 2021-09-01 09:26:30
        -->
        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
        </head>
        <body>
          <!-- 容器 -->
            <div id="box"></div>
        
            <!-- 引入React的核心库 -->
            <script src="./js/react.development.js"></script>
            <!-- 引入React的扩展库,用来渲染虚拟DOM -->
            <script src="./js/react-dom.development.js"></script>
            <!-- 引入babel,用来将jsx转换为浏览器可以识别的文件 -->
            <script src="./js/babel.min.js"></script>
            <!-- 引入propTypes,对标签属性的类型、必要性以及默认值进行控制 -->
            <script src="./js/prop-types.js"></script>
        
            <script type="text/babel">
            /*
                经典面试题:
                  1) vue/react中的key有什么作用???(key 的内部原理是什么???)
                  2) 为什么遍历列表时,key最好不要用index
                1.虚拟 DOM 中key的作用
                  1) 简单的说:key是虚拟 DOM 对象的标识,在更新显示时key起着极其重要的作用;
                  2) 复杂的说:当状态中的数据发生变化时,React会根据【新数据】生成【新的虚拟 DOM 】
                              随后React进行【新虚拟 DOM 】与【旧虚拟 DOM 】的diff比较,比较规则如下:
                              
                            a) 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的key:
                              (1)若虚拟 DOM 中内容没变,直接使用之前的真实DOM; 
                              (2)若虚拟 DOM 中内容变了,则生成新的真实DOM ,随后替换掉页面中之前的真实DOM
                            b) 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的key:根据数据创建新的真实DOM ,随后渲染到页面上
                
        
                2、用index作为key可能引发的问题???
                    1) 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
                    2) 如果结构中还包含输入类的DOM :会产生错误DOM更新 ===> 界面有问题
                    3) 注意:如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
        
        
                3、开发中如何选择key???
                    1) 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
                    2) 如果确定只是简单的展示数据,用index也是可以的
            */
                // 1、创建组件
                class Person extends React.Component{
                    // 对标签属性的类型、必要性进行控制
                    static propTypes = {
        
                    }
                    // 对标签属性的默认值进行控制
                    static defaultProps = {
        
                    }
                    // 初始化数据
                    state = {
                       persons : [
                         {
                           id:'1',
                           name:'jerry',
                           age:'18'
                         },
                         {
                           id:'2',
                           name:'tom',
                           age:'22'
                         }
                       ]
                    }
                    componentDidMount(){
                      
                    }
                    render(){
                        let {persons} = this.state
                        {/*
                           慢动作回放:使用index索引值作为key
                              初始数据:
                                  {id:'1',name:'jerry',age:'18'},{id:'2',name:'tom',age:'22'}
                              初始的虚拟DOM:
                                  <li key=0>名字:jerry ----- 年龄:18</li>
                                  <li key=1>名字:tom----- 年龄:22</li>
                              更新后的数据:
                                  {id:'3',name:'wxh',age:'25'},{id:'1',name:'jerry',age:'18'},{id:'2',name:'tom',age:'22'}
                              新的虚拟DOM:
                                  <li key=0>名字:wxh ----- 年龄:25</li>
                                  <li key=1>名字:jerry ----- 年龄:18</li>
                                  <li key=2>名字:tom----- 年龄:22</li>
                              
                        
                        */}
                        return(
                            <div>
                              <h1>使用index作为key</h1>
                              <h2>展示人员信息</h2>
                              <button onClick={this.handlerClick}>添加一个人:wxh</button>
                              <ul>
                                {
                                  persons.map((el,index)=>{
                                    return <li key={index}>名字:{el.name} ----- 年龄:{el.age}</li>
                                  })
                                }
                              </ul>
                              <hr />
                              <h1>使用id作为key</h1>
                              <h2>展示人员信息</h2>
                              <button onClick={this.handlerClick}>添加一个人:wxh</button>
                              <ul>
                                {
                                  persons.map((el,index)=>{
                                    return <li key={el.id}>名字:{el.name} ----- 年龄:{el.age}</li>
                                  })
                                }
                              </ul>
                            </div>
                        )
                    }
                    handlerClick = () => {
                      let person = {
                        id:'3',
                        name:'wxh',
                        age:'25'
                      }
                      this.setState({
                        persons : [person,...this.state.persons]
                      })
                      console.log(this.state.persons)
                    }
                }
                // 2、将组件渲染到页面上
                ReactDOM.render(<Person />,document.getElementById("box"))
            </script>
        </body>
        </html>
        View Code
      • 慢动作回放
        慢动作回放:使用index索引值作为key
              初始数据:
                  {id:'1',name:'jerry',age:'18'},{id:'2',name:'tom',age:'22'}
              初始的虚拟DOM:
                  <li key=0>名字:jerry ----- 年龄:18</li>
                  <li key=1>名字:tom----- 年龄:22</li>
              更新后的数据:
                  {id:'3',name:'wxh',age:'25'},{id:'1',name:'jerry',age:'18'},{id:'2',name:'tom',age:'22'}
              新的虚拟DOM:
                  <li key=0>名字:wxh ----- 年龄:25</li>
                  <li key=1>名字:jerry ----- 年龄:18</li>
                  <li key=2>名字:tom----- 年龄:22</li>
    • 如果结构中还包含输入类的DOM :会产生错误DOM更新 ===> 界面有问题
      • 示例
        View Code

        将前面显示的数据填入input之后,页面效果为:

 

                              

                               点击按钮加一个人,页面效果为:

                             

      •   慢动作回放
        慢动作回放:使用index索引值作为key
              初始数据:
                  {id:'1',name:'jerry',age:'18'},{id:'2',name:'tom',age:'22'}
              初始的虚拟DOM:
                  <li key=0>名字:jerry ----- 年龄:18<input type="text" /></li>
                  <li key=1>名字:tom----- 年龄:22<input type="text" /></li>
              更新后的数据:
                  {id:'3',name:'wxh',age:'25'},{id:'1',name:'jerry',age:'18'},{id:'2',name:'tom',age:'22'}
              新的虚拟DOM:
                  <li key=0>名字:wxh ----- 年龄:25<input type="text" /></li>
                  <li key=1>名字:jerry ----- 年龄:18<input type="text" /></li>
                  <li key=2>名字:tom----- 年龄:22<input type="text" /></li>
  • id作为key
    • 慢动作回放
      慢动作回放:使用index索引值作为key
            初始数据:
                {id:'1',name:'jerry',age:'18'},{id:'2',name:'tom',age:'22'}
            初始的虚拟DOM:
                <li key=1>名字:jerry ----- 年龄:18</li>
                <li key=2>名字:tom----- 年龄:22</li>
            更新后的数据:
                {id:'3',name:'wxh',age:'25'},{id:'1',name:'jerry',age:'18'},{id:'2',name:'tom',age:'22'}
            新的虚拟DOM:
                <li key=3>名字:wxh ----- 年龄:25</li>
                <li key=1>名字:jerry ----- 年龄:18</li>
                <li key=2>名字:tom----- 年龄:22</li>
                             

 

posted @ 2021-09-01 10:17  北栀女孩儿  阅读(171)  评论(0编辑  收藏  举报