React 生命周期(新旧)及 案例

Posted YuLong~W

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React 生命周期(新旧)及 案例相关的知识,希望对你有一定的参考价值。

生命周期

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列钩子函数(生命周期回调函数), 会在特定的时刻调用。
  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() =====> 常用
      一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

示例:说明组件生命周期的先后顺序

//创建组件
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'))

控制台打印的结果:

出现新的生命周期缘由

旧的生命周期十分完整,基本可以捕捉到组件更新的每一个state/props/ref,没有什从逻辑上的毛病。但是官方react打算在17版本推出新的Async Rendering,提出一种可被打断的生命周期,而可以被打断的阶段正是实际dom挂载之前的虚拟dom构建阶段,也就是要被去掉的三个生命周期。

生命周期一旦被打断,下次恢复的时候又会再跑一次之前的生命周期,因此componentWillMount,componentWillReceiveProps, componentWillUpdate都不能保证只在挂载/拿到props/状态变化的时候刷新一次了,所以这三个方法被标记为不安全

  • 旧的: componentWillMount,componentWillReceiveProps,componentWillUpdate

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

  • 新的: static getDerivedStateFromProps,getSnapshotBeforeUpdate

新生命周期

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

三个阶段:

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

控制台打印结果:

具体方法详解


1、constructor(props): 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前 调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。

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

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

在 constructor() 函数中不要调用 setState() 方法。如果组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始化state:


2、render(): 是 class 组件中唯一必须实现的方法

当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:

  • React 元素。通常通过 JSX 创建。例如,<div /> 会被 React 渲染为 DOM 节点,<MyComponent />会被 React 渲染为自定义组件,无论是<div /> 还是 <MyComponent />均为 React 元素。
  • 数组或fragments。 使得 render 方法可以返回多个元素。欲了解更多详细信息,请参阅fragments 文档。
  • Portals。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关 portals 的文档
  • 字符串或数值类型。它们在DOM 中会被渲染为文本节点
  • 布尔类型或 null。什么都不渲染。(主要用于支持返回 test && <Child /> 的模式,其中
    test 为布尔类型)

注意: render() 函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互

如需与浏览器进行交互,请在 componentDidMount()其他生命周期方法 中执行你的操作。保持 render() 为纯函数,可以使组件更容易使用、维护。


3、componentDidMount(): 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。

这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅

可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。

注意: 请谨慎使用该模式,因为它会导致性能问题。通常,应该在 constructor() 中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,可以使用此方式处理


4、componentDidUpdate(prevProps, prevState, snapshot): 会在更新后会被立即调用。首次渲染不会执行此方法。当组件更新后,可以在此处对 DOM 进行操作。

如果对更新前后的 props 进行了比较,也可以选择在此处进行网络请求

也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。


5、componentWillUnmount(): 会在组件卸载及销毁之前直接调用

在此方法中执行必要的 清理操作,例如,清除timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。


生命周期示例——不断获取数据但滚轮的位置固定

getSnapshotBeforeUpdate():在更新之前获取快照

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

生命周期示例——时钟

const s2={
    color:'red'
}
class Clock extends React.Component{
    constructor(props){
        super(props);
        this.state={
            show:true,
            date:new Date(),
            text:'隐藏'
        }
        this.handleShow=this.handleShow.bind(this);
    }
    //定义挂载函数(钩子函数)
    componentDidMount(){
        this.timerID=setInterval(()=>{this.tick(),1000}); //创建定时器,每隔一秒钟调用一次函数
    }
    //定义tick函数 更新状态机中的date
    tick(){
        this.setState({
            date:new Date()
        })
    }
    //定义事件响应函数:用来更新状态机中的show和text(决定显示在按钮上的文本)
    handleShow(){
        this.setState({
            show:!this.state.show,
            text:!this.state.show?'隐藏':'显示'
        })
        // this.setState(state=>{
        //     show:!state.show;
        //     text:!state.show?'隐藏':'显示'
        // })
    }
    //组件卸载函数(钩子函数):清除定时器
    componentWillUnmount(){
        clearInterval(this.timerID);
    }
    render(){
        let isShow=this.state.show;
        let element;
        if(isShow){
            element=<h2 style={ s2 }>{ this.state.date.toLocaleTimeString() }</h2>
        }else{
            element=null;
        }
        return(
            <div>
                <button onClick={ this.handleShow }>{this.state.text}计时器</button>    
                <br/><br/>
                { element }
            </div>
        )
    }
    
}
ReactDOM.render(<Clock/>,document.getElementById('myClock'));

以上是关于React 生命周期(新旧)及 案例的主要内容,如果未能解决你的问题,请参考以下文章

React 类组件的一些基本概念

React 面向组件编程(下)

React -- 组件的生命周期 (这一篇就够了!!!)

第三篇:为什么 React 16 要更改组件的生命周期?(下)

第三篇:为什么 React 16 要更改组件的生命周期?(下)

理解片段事务期间片段的生命周期方法调用