【React】03-React面向组件编程2

2.6. 组件的生命周期

2.6.1. 效果

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

  1. 让指定的文本做显示 / 隐藏的渐变动画
  2. 从完全可见,到彻底消失,耗时2S
  3. 点击“不活了”按钮从界面中卸载组件
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入三个库 -->
<script type="text/babel">
  
    class Demo 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(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>

2.6.2. 理解

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

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

生命周期的三个阶段(旧)

  1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
    1.constructor()
    2.componentWillMount()
    3.render()
    4.componentDidMount()
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
    1.shouldComponentUpdate()=
    2.componentWillUpdate()
    3.render()
    4.componentDidUpdate()
  3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
    1.componentWillUnmount()
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入三个库 -->
<script type="text/babel">
    /* 
        1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
                            1.	constructor()
                            2.	componentWillMount()
                            3.	render()
                            4.	componentDidMount() =====> 常用
                                            一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
        2. 更新阶段: 由组件内部this.setSate()或父组件render触发
                            1.	shouldComponentUpdate()
                            2.	componentWillUpdate()
                            3.	render() =====> 必须使用的一个
                            4.	componentDidUpdate()
        3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
                            1.	componentWillUnmount()  =====> 常用
                                            一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
		*/
    class Demo extends React.Component{
        constructor(props){
            console.log("Count---construtor");
            super(props)
            //初始化状态
            this.state = {count:0}
        }
        //加按钮的回调
        add = () =>{
            //获取原状态
            const {count} = this.state;
            this.setState({count:count+1})
        }
        //卸载组件按钮的回调
        dath = ()=>{
            React.unmountComponentAtNode(document.getElementById('test'))
        }
        
        //强制更新的按钮
        force = ()=>{
            this.forceUpdate()
        }
        
        //组件将要挂在的钩子函数
        componentWillMount(){
            console.log('Count---componentWillMount');
        }
        
        //组件挂在完毕
        componentDidMount(){
            console.log('Count---componentDidMount');
        }
        
        //组件将要卸载的钩子
        componentWillUnmount(){
            console.log('Count---componentWillUnmount');
        }
        
        //控制组件更新的阀门,默认为true,必须要有返回值
        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>
            )
        }     
        
    }
    //父组件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(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>

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

生命周期的三个阶段(新)

  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()
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入三个库 -->
<script type="text/babel">
    /* 
       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()  =====> 常用
                                    一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
		*/
    class Demo 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(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>

2.6.5. 重要的勾子

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

2.6.6. 即将废弃的勾子

1.componentWillMount
2.componentWillReceiveProps
3.componentWillUpdate

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

2.6.7 getSnapshotBeforeUpdate

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>4_getSnapShotBeforeUpdate的使用场景</title>
	<style>
		.list{
			width: 200px;
			height: 150px;
			background-color: skyblue;
			overflow: auto;
		}
		.news{
			height: 30px;
		}
	</style>
</head>
<body>
	<!-- 准备好一个“容器” -->
	<div id="test"></div>
	
	<!-- 引入react核心库 -->
	<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
	<!-- 引入react-dom,用于支持react操作DOM -->
	<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
	<!-- 引入babel,用于将jsx转为js -->
	<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>

	<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>
</body>
</html>

2.7. 虚拟DOM与DOM Diffing算法

2.7.1. 效果

需求:验证虚拟DOM Diffing算法的存在

2.7.2. 基本原理图

经典面试题:
      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也是可以的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值