React组件的生命周期和DOM的Diffing算法

一、引出生命周期

需求:定义组件实现以下功能

  1. 让指定的文本做显示 / 隐藏的渐变动画
  2. 从完全可见,到彻底消失,耗时 2S
  3. 点击“不活了”按钮从界面中卸载组件

screenshots.gif

	<!-- 准备好一个“容器” -->
	<div id="test"></div>
	
	<script type="text/babel">
		//创建组件
		//生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
		class Life extends React.Component{
    
    

			state = {
    
    opacity:1}

			death = ()=>{
    
    
				//卸载组件
				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);
			}

			//组件将要卸载
			componentWillUnmount(){
    
    
				//清除定时器
				clearInterval(this.timer)
			}

			//初始化渲染、状态更新之后
			render(){
    
    
				console.log('render');
				return(
					<div>
						<h2 style={
    
    {
    
    opacity:this.state.opacity}}>React学不会怎么办?</h2>
						<button onClick={
    
    this.death}>不活了</button>
					</div>
				)
			}
		}
		//渲染组件
		ReactDOM.render(<Life/>,document.getElementById('test'))
	</script>

二、生命周期的理解

  • 生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
    1. 组件从创建到死亡它会经历一些特定的阶段。
    2. React 组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
    3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作

image.png

三、生命周期(旧)

1. 生命周期流程图(旧)

react生命周期(旧).png

2. 生命周期的阶段

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

    1.constructor()
    2.componentWillMount()
    3.render()
    4.componentDidMount() ===> 常用
    一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

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

    1.shouldComponentUpdate()
    2.componentWillUpdate()
    3.render() =====> 必须使用的一个
    4.componentDidUpdate()

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

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

3. 生命周期的验证

	<!-- 准备好一个“容器” -->
	<div id="test"></div>
	
	<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})
			}

			//卸载组件按钮的回调
			death = ()=>{
    
    
				ReactDOM.unmountComponentAtNode(document.getElementById('test'))
			}

			//强制更新按钮的回调
			force = ()=>{
    
    
				this.forceUpdate()
			}

			//组件将要挂载的钩子
			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---componentWillUpdate');
			}

			//组件更新完毕的钩子
			componentDidUpdate(){
    
    
				console.log('Count---componentDidUpdate');
			}

			render(){
    
    
				console.log('Count---render');
				const {
    
    count} = this.state
				return(
					<div>
						<h2>当前求和为:{
    
    count}</h2>
						<button onClick={
    
    this.add}>点我+1</button>
						<button onClick={
    
    this.death}>卸载组件</button>
						<button onClick={
    
    this.force}>不更改任何状态中的数据,强制更新一下</button>
					</div>
				)
			}
		}
		ReactDOM.render(<Count/>,document.getElementById('test'))
	</script>

image.png

screenshots.gif

父组件的render流程

image.png

	<!-- 准备好一个“容器” -->
	<div id="test"></div>

	<script type="text/babel">
		//父组件A
		class A extends React.Component{
    
    
			//初始化状态
			state = {
    
    carName:'奔驰'}

			changeCar = ()=>{
    
    
				this.setState({
    
    carName:'奥拓'})
			}

			render(){
    
    
				return(
					<div>
						<div>我是A组件</div>
						<button onClick={
    
    this.changeCar}>换车</button>
						<B carName={
    
    this.state.carName}/>
					</div>
				)
			}
		}
		
		//子组件B
		class B extends React.Component{
    
    
			//组件将要接收新的props的钩子
			componentWillReceiveProps(props){
    
    
				console.log('B---componentWillReceiveProps',props);
			}

			//控制组件更新的“阀门”
			shouldComponentUpdate(){
    
    
				console.log('B---shouldComponentUpdate');
				return true
			}
			//组件将要更新的钩子
			componentWillUpdate(){
    
    
				console.log('B---componentWillUpdate');
			}

			//组件更新完毕的钩子
			componentDidUpdate(){
    
    
				console.log('B---componentDidUpdate');
			}

			render(){
    
    
				console.log('B---render');
				return(
					<div>我是B组件,接收到的车是:{
    
    this.props.carName}</div>
				)
			}
		}
		
		//渲染组件
		ReactDOM.render(<A/>,document.getElementById('test'))
	</script>

image.png

四、生命周期(新)

1. 生命周期流程图(新)

react生命周期(新).png

2. 生命周期的阶段

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

    1.constructor()
    2.getDerivedStateFromProps 
    3.render()
    4.componentDidMount() =====> 常用								一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

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

    1.getDerivedStateFromProps
    2.shouldComponentUpdate()
    3.render()
    4.getSnapshotBeforeUpdate
    5.componentDidUpdate()

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

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

3. 生命周期的验证

	<!-- 准备好一个“容器” -->
	<div id="test"></div>

	<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})
			}

			//卸载组件按钮的回调
			death = ()=>{
    
    
				ReactDOM.unmountComponentAtNode(document.getElementById('test'))
			}

			//强制更新按钮的回调
			force = ()=>{
    
    
				this.forceUpdate()
			}
			
			//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
			static getDerivedStateFromProps(props,state){
    
    
				console.log('getDerivedStateFromProps',props,state);
				return null
			}

			//在更新之前获取快照
			getSnapshotBeforeUpdate(){
    
    
				console.log('getSnapshotBeforeUpdate');
				return 'atguigu'
			}

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

			//组件将要卸载的钩子
			componentWillUnmount(){
    
    
				console.log('Count---componentWillUnmount');
			}

			//控制组件更新的“阀门”
			shouldComponentUpdate(){
    
    
				console.log('Count---shouldComponentUpdate');
				return true
			}

			//组件更新完毕的钩子
			componentDidUpdate(preProps,preState,snapshotValue){
    
    
				console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
			}
			
			render(){
    
    
				console.log('Count---render');
				const {
    
    count} = this.state
				return(
					<div>
						<h2>当前求和为:{
    
    count}</h2>
						<button onClick={
    
    this.add}>点我+1</button>
						<button onClick={
    
    this.death}>卸载组件</button>
						<button onClick={
    
    this.force}>不更改任何状态中的数据,强制更新一下</button>
					</div>
				)
			}
		}
		
		//渲染组件
		ReactDOM.render(<Count count={
    
    199}/>,document.getElementById('test'))
	</script>

image.png

4. getSnapshotBeforeUpdate的使用场景

screenshots.gif

	<style>
		.list{
    
    
			width: 400px;
			height: 150px;
			background-color: skyblue;
			overflow: auto;
			margin: 0  auto;
		}
		.news{
    
    
			height: 30px;
		}
	</style>
	<!-- 准备好一个“容器” -->
	<div id="test"></div>
	
	<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(preProps,preState,height){
    
    
				this.refs.list.scrollTop += this.refs.list.scrollHeight - height
			}

			render(){
    
    
				return(
					<div className="list" ref="list">
						{
    
    
							this.state.newsArr.map((n,index)=>{
    
    
								return <div key={
    
    index} className="news">{
    
    n}</div>
							})
						}
					</div>
				)
			}
		}
		ReactDOM.render(<NewsList/>,document.getElementById('test'))
	</script>

image.png

5. 重要的勾子

    1. render:初始化渲染或更新渲染调用
    2. componentDidMount:开启监听, 发送 ajax 请求
    3. componentWillUnmount:做一些收尾工作, : 清理定时器

6.即将废弃的勾子

    1. componentWillMount
    2. componentWillReceiveProps
    3. componentWillUpdate
    现在使用会出现警告,下一个大版本需要加上 UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用

五、DOM的Diffing算法

1. 验证Diffing算法

	<!-- 准备好一个“容器” -->
	<div id="test"></div>
	
	<script type="text/babel">
		class Time extends React.Component {
    
    
			state = {
    
    date: new Date()}

			componentDidMount () {
    
    
				setInterval(() => {
    
    
					this.setState({
    
    
						date: new Date()
					})
				}, 1000)
			}

			render () {
    
    
				return (
					<div>
						<h1>hello</h1>
						<input type="text"/> <br/>
						<span>
							现在是:{
    
    this.state.date.toTimeString()}
							<input type="text"/>
						</span>
					</div>
				)
			}
		}
		ReactDOM.render(<Time/>,document.getElementById('test'))
</script>

2. key的作用

经典面试题

 经典面试题:
      1). react/vue中的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也是可以的。

代码

<div id="test"></div>

<script type="text/babel">
	class Person extends React.Component{
    
    

		state = {
    
    
			persons:[
				{
    
    id:1,name:'小张',age:18},
				{
    
    id:2,name:'小李',age:19},
			]
		}

		add = ()=>{
    
    
			const {
    
    persons} = this.state
			const p = {
    
    id:persons.length+1,name:'小王',age:20}
			this.setState({
    
    persons:[p,...persons]})
		}

		render(){
    
    
			return (
				<div>
					<h2>展示人员信息</h2>
					<button onClick={
    
    this.add}>添加一个小王</button>
					<h3>使用index(索引值)作为key</h3>
					<ul>
						{
    
    
							this.state.persons.map((personObj,index)=>{
    
    
								return <li key={
    
    index}>{
    
    personObj.name}---{
    
    personObj.age}<input type="text"/></li>
							})
						}
					</ul>
					<hr/>
					<hr/>
					<h3>使用id(数据的唯一标识)作为key</h3>
					<ul>
						{
    
    
							this.state.persons.map((personObj)=>{
    
    
								return <li key={
    
    personObj.id}>{
    
    personObj.name}---{
    
    personObj.age}<input type="text"/></li>
							})
						}
					</ul>
				</div>
			)
		}
	}

	ReactDOM.render(<Person/>,document.getElementById('test'))
</script>

效果图

screenshots.gif

说明

	/* 
		慢动作回放----使用index索引值作为key

			初始数据:
					{id:1,name:'小张',age:18},
					{id:2,name:'小李',age:19},
			初始的虚拟DOM:
					<li key=0>小张---18<input type="text"/></li>
					<li key=1>小李---19<input type="text"/></li>

			更新后的数据:
					{id:3,name:'小王',age:20},
					{id:1,name:'小张',age:18},
					{id:2,name:'小李',age:19},
			更新数据后的虚拟DOM:
					<li key=0>小王---20<input type="text"/></li>
					<li key=1>小张---18<input type="text"/></li>
					<li key=2>小李---19<input type="text"/></li>

	-----------------------------------------------------------------

	慢动作回放----使用id唯一标识作为key

			初始数据:
					{id:1,name:'小张',age:18},
					{id:2,name:'小李',age:19},
			初始的虚拟DOM:
					<li key=1>小张---18<input type="text"/></li>
					<li key=2>小李---19<input type="text"/></li>

			更新后的数据:
					{id:3,name:'小王',age:20},
					{id:1,name:'小张',age:18},
					{id:2,name:'小李',age:19},
			更新数据后的虚拟DOM:
					<li key=3>小王---20<input type="text"/></li>
					<li key=1>小张---18<input type="text"/></li>
					<li key=2>小李---19<input type="text"/></li>
	 */

猜你喜欢

转载自blog.csdn.net/m0_58190023/article/details/129768714