04_React面向组件编程(生命周期、Diffing算法)

前言

React采用组件化、声明式编码,通过将不同的组件组合成复杂的UI界面。React 应用程序的组成部分:元素和组件。元素是构成 React 应用的最小砖块。组件是由元素构成的。使用 React 构建的应用只有一个根 DOM 节点。React 将以 小写字母 开头的组件视为原生 DOM 标签不是原生的标签,组件名称必须以大写字母开头

生命周期就是指一个对象的生老病死。每个组件都包含 “生命周期方法”,React会在特定的阶段执行这些方法。生命周期方法也被称为 “生命周期回调函数” 、“生命周期钩子函数” 、“生命周期函数” 或“生命周期钩子”。

组件第一次被渲染到 DOM 中的时,被称为“挂载(mount)”。当组件的 props 或 state 发生变化时会触发更新。React DOM 会将元素和它的子元素与它们之前的状态进行比较,通过React的Diffing 算法来减少重复渲染的次数。组件被删除的时候,称为“卸载(unmount)”。

一、生命周期

1.1  生命周期图谱

1.2  生命周期方法

1.2.1  挂载

  • constructor(props)------构造器 执行了1

在 React 中,构造函数仅用于以下两种情况:

  1.  通过给 this.state 赋值对象来初始化内部 state
  2. 为事件处理函数绑定实例

在事件系统中被执行时,是直接调用,会出现 this 没指向类组件实例的情况undefined,此时可以在构造函数中绑定 this。bind 生成新的函数,这里 this 这里是 MyComponent实例对象

class MyComponent extends React.Component {
            constructor(props) {
                super(props)
                this.state = { isHot: true, wind: '微风' }
                this.change = this.change.bind(this)
                //console.log(this.props);
            }

            render () {
                console.log(this)
                const { isHot, wind } = this.state
                return <h2 onClick={this.change}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h2>
            }
            change () {
                const changeHot = this.state.isHot
                this.setState({
                    isHot: !changeHot
                })
            }
        }

        //渲染组件
        ReactDOM.render(<MyComponent />, document.getElementById('test'))

注意:

  1. 如果不初始化 state 或不进行方法绑定,构造器可省略。
  2. 有构造器,则一定要super(props);
  • static getDerivedStateFromProps(props, state)  

常用于组件初始化或更新时将 props 映射到 state

注意:

  1. 不能将他定义给实例用,将会被忽略。请将他定义成静态方法
  2. 必须要有返回值,返回一个状态对象更新 state,返回 null 则不更新任何内容
  3. 此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props
  4. 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用
  • render()------ 组件初始化和页面 state 或 props 发生变化时执行 (1+n次)

render函数里面可以编写JSX,转化成createElement这种形式,用于生成虚拟DOM,最终转化成真实DOM。会根据props,state值构建并返回一个新的对象,若状态更新则会继续调用。

  1. createElement
  2. cloneElement
  3. 遍历 children
  • componentDidMount()------挂载阶段已完成

只调一次,组件一挂载到页面,就调用

1.2.2  更新

  • static getDerivedStateFromProps(props, state)

  • shouldComponentUpdate(nextProps,nextState) ------控制组件更新的阀门

根据返回值判断是否跳过更新,在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。

  • render()

  • getSnapshotBeforeUpdate(preProps, preState)------将要更新前,相当于快照

  1. 可以返回任何类型的,并将其返回值传递给给componentDidUpdate的snapshot
  2. 一般用于处理滚动位置
<!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>getSnapshotBeforeUpdate</title>
    <style>
        #list {
            width: 200px;
            height: 150px;
            background-color: skyblue;
            overflow: auto;
        }

        .news {
            height: 30px;
        }
    </style>
</head>

<body>
    <div id="test"></div>
    <script src="../js/17.0.1/react.development.js"></script>
    <script src="../js/17.0.1/react-dom.development.js"></script>
    <script src="../js/17.0.1/babel.min.js"></script>
    <script type="text/babel">
        //scrollTop 向上,不累计
        //scrollHeight 30*8=240
        class NewsList extends React.Component {
            state = { newArr: [] }
            componentDidMount () {
                this.timer = setInterval(() => {
                    const { newArr } = this.state
                    //模拟一条新闻
                    const news = '新闻' + (newArr.length + 1)
                    //更新状态
                    this.setState({
                        newArr: [news, ...newArr]
                    })
                }, 1000)

            }
            getSnapshotBeforeUpdate () {
                if (this.state.newArr.length >= 20) {
                    clearInterval(this.timer)
                }
                return this.refs.newlist.scrollHeight
            }

            componentDidUpdate (preProps, preState, height) {
                this.refs.newlist.scrollTop += this.refs.newlist.scrollHeight - height
            }

            render () {
                return (
                    <div id="list" ref="newlist">
                        {
                            this.state.newArr.map((n, index) => {
                                return <div key={index} className="news">{n}</div>
                            })
                        }
                    </div>
                )

            }
        }
        ReactDOM.render(<NewsList />, document.getElementById('test'))
    </script>
</body>

</html>
  • componentDidUpdate(preProps, preState,snapshot)------更新完成

会在更新后会被立即调用。首次渲染不会执行此方法。

注意:

  1. 如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递.
  2. 如果shoundComponentUpdate返回值为 false,则不会调用 componentDidUpdate()

1.2.3  卸载

  • componentWillUnmount()------将要卸载

主要用于清除 timer,取消网络请求或清除创建的订阅等。

<!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>生命周期</title>
    </head>

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

        <script src="../js/16.8.4/react.development.js"></script>
        <script src="../js/16.8.4/react-dom.development.js"></script>
        <script src="../js/16.8.4/babel.min.js"></script>

        <script type="text/babel">
            class Life extends React.Component {
                state = { opacity: 1 }
                clean = () => {
                    //卸载组件
                    ReactDOM.unmountComponentAtNode(
                        document.getElementById('test')
                    )

                    //can't perform a React state update on an unmounted component. 不能在已经卸载的组件内执行react里的状态的更新
                    //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.
                }
                
                //render 1+n次调用
                //初始化渲染、状态更新之后
                render() {
                    console.log('render')
                    return (
                        <div>
                            <h3 style={{ opacity: this.state.opacity }}>
                                生命周期
                            </h3>
                            <button onClick={this.clean}>点击</button>
                        </div>
                    )
                }

                //只调一次,组件一挂载到页面,就调用
                //组件完成挂载后
                componentDidMount() {
                    this.timer = setInterval(() => {
                        let { opacity } = this.state
                        opacity -= 0.1
                        if (opacity <= 0) {
                            opacity = 1
                        }
                        //设置新的透明度
                        this.setState({ opacity })
                    }, 200)
                }

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

            ReactDOM.render(<Life />, document.getElementById('test'))
        </script>
    </body>
</html>

即将废弃的钩子

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

16.4之后需要加上UNSAFE_前缀才能使用

二、Diffing算法

React是基于虚拟 DOM的。通过使用虚拟DOM,完成组件的渲染和更新。当状态中的数据发生变化时,react会根据 新数据  生成 新的虚拟DOM。随后React进行新旧虚拟DOM 的diff比较,进行局部更新。

2.1  react/vue中的key有什么作用?

key是虚拟DOM对象的标识,用于显示于更新。

2.2  Diffing算法

1.  旧虚拟DOM中找到了与新虚拟DOM相同的key

  • 若虚拟DOM中内容没变, 直接使用之前的真实DOM
  • 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM

2.  旧虚拟DOM中未找到与新虚拟DOM相同的key

  • 根据数据创建新的真实DOM,随后渲染到到页面

2.3  为什么遍历列表时,key最好不要用index?

用index作为key可能会引发的问题

  • 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生重复的真实DOM更新,效率低。
  • 如果结构中还包含输入类的DOM,会产生错误DOM更新

所以使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

才不吃胡萝卜嘞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值