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)(组件更新完毕的钩子)
- 组件内部的setState正常更新触发(顺序为
- 卸载组件:由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 }) } }
子组件
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> ) } }
渲染
// 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>
- getDerivedStateFromProps(获取一种派生状态 ----- 即让state的值在任何时候都取决于props)没啥意义
- 初始化阶段:由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)(组件更新完毕的钩子)
- 组件内部的setState正常更新触发(顺序为
- 销毁阶段:由ReactDOM.unmountComponentAtNode()触发(顺序为
componentWillUnmount(组件将要卸载的钩子:常用的钩子一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息等))
- 引入的js文件版本为:17.0.2
- 图解
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>
- 慢动作回放
慢动作回放:使用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之后,页面效果为:
- 示例
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
点击按钮加一个人,页面效果为:
-
-
- 慢动作回放
慢动作回放:使用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>
- 慢动作回放
北栀女孩儿