• React生命周期


    React生命周期

    React的生命周期从广义上分为挂载、渲染、卸载三个阶段,在React的整个生命周期中提供很多钩子函数在生命周期的不同时刻调用。

    描述

    此处描述的是使用class类组件提供的生命周期函数,每个组件都包含自己的生命周期方法,通过重写这些方法,可以在运行过程中特定的阶段执行这些方法,常用的生命周期有constructor()render()componentDidMount()componentDidUpdate()componentWillUnmount()

    挂载过程

    当组件实例被创建并插入DOM中时,其生命周期调用顺序如下:

    • constructor()
    • static getDerivedStateFromProps()
    • render()
    • componentDidMount()

    在这个阶段的componentWillMount()生命周期即将过时,在新代码中应该避免使用。

    更新过程

    当组件的propsstate发生变化时会触发更新,组件更新的生命周期调用顺序如下:

    • static getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()

    在这个阶段的componentWillUpdate()componentWillReceiveProps()生命周期即将过时,在新代码中应该避免使用。

    卸载过程

    当组件从DOM中移除时,组件更新的生命周期调用顺序如下:

    • componentWillUnmount()

    错误处理

    当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

    • static getDerivedStateFromError()
    • componentDidCatch()

    生命周期

    constructor()

    React组件挂载之前,会调用它的构造函数,如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数。在为React.Component子类实现构造函数时,应在其他语句之前前调用super(props),否则this.props在构造函数中可能会出现未定义的错误。
    通常在React中构造函数仅用于以下两种情况:

    • 通过给this.state赋值对象来初始化内部state
    • 为事件处理函数绑定实例。
    constructor(props) {
        super(props);
    }
    

    static getDerivedStateFromProps()

    getDerivedStateFromProps静态方法会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用,它应返回一个对象来更新state,如果返回null则不更新任何内容。此方法无权访问组件实例,如果确实需要,可以通过提取组件props的纯函数及class之外的状态,在getDerivedStateFromProps()和其他class方法之间重用代码。此外,不管原因是什么,都会在每次渲染前触发此方法。

    static getDerivedStateFromProps(props, state) {}
    

    render()

    render()方法是class组件中唯一必须实现的方法,render()函数应该为纯函数,这意味着在不修改组件state的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,请在componentDidMount()或其他生命周期方法中执行操作,保持render()为纯函数。当render被调用时,它会检查this.propsthis.state的变化并返回以下类型之一:

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

    componentDidMount()

    componentDidMount()会在组件挂载后(即插入DOM树后)立即调用,依赖于DOM节点的初始化应该放在这里,如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法是比较适合添加订阅的地方,如果添加了订阅,请不要忘记在componentWillUnmount()里取消订阅。
    你可以在componentDidMount()里直接调用setState(),它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前,如此保证了即使在render()两次调用的情况下,用户也不会看到中间状态,请谨慎使用该模式,因为它会导致性能问题。通常应该在constructor()中初始化state,如果你的渲染依赖于DOM节点的大小或位置,比如实现modalstooltips等情况下,你可以使用此方式处理。

    componentDidMount() {}
    

    shouldComponentUpdate()

    propsstate发生变化时,shouldComponentUpdate()会在渲染执行之前被调用,返回值默认为true,首次渲染或使用forceUpdate()时不会调用该方法。根据shouldComponentUpdate()的返回值,判断React组件的输出是否受当前stateprops更改的影响。默认行为是state每次发生变化组件都会重新渲染,大部分情况下,你应该遵循默认行为。
    此方法仅作为性能优化的方式而存在,不要企图依靠此方法来阻止渲染,因为这可能会产生bug,你应该考虑使用内置的PureComponent组件,而不是手动编写shouldComponentUpdate()PureComponent会对propsstate进行浅层比较,并减少了跳过必要更新的可能性。
    如果你一定要手动编写此函数,可以将this.propsnextProps以及this.statenextState进行比较,并返回false以告知React可以跳过更新。请注意,返回false并不会阻止子组件在state更改时重新渲染。不建议在shouldComponentUpdate()中进行深层比较或使用JSON.stringify(),这样非常影响效率,且会损害性能。目前如果shouldComponentUpdate()返回false,则不会调用UNSAFE_componentWillUpdate()render()componentDidUpdate()。后续版本React可能会将shouldComponentUpdate视为提示而不是严格的指令,并且当返回false时仍可能导致组件重新渲染。

    shouldComponentUpdate(nextProps, nextState) {}
    

    getSnapshotBeforeUpdate()

    getSnapshotBeforeUpdate()在最近一次渲染输出(提交到DOM节点)之前调用,它使得组件能在发生更改之前从DOM中捕获一些信息(例如滚动位置),此生命周期的任何返回值将作为参数传递给componentDidUpdate(),该方法应返回snapshot的值或null
    此用法并不常见,但它可能出现在UI处理中,如需要以特殊方式处理滚动位置的聊天线程等。

    getSnapshotBeforeUpdate(prevProps, prevState) {}
    

    componentDidUpdate()

    componentDidUpdate()会在更新后会被立即调用,首次渲染不会执行此方法。当组件更新后,可以在此处对DOM进行操作,如果你对更新前后的props进行了比较,也可以选择在此处进行网络请求(例如,当props未发生变化时,则不会执行网络请求。如果shouldComponentUpdate()返回值为false,则不会调用componentDidUpdate()
    你也可以在componentDidUpdate()中直接调用setState(),但请注意它必须被包裹在一个条件语句里,否则会导致死循环,因为他将无限次触发componentDidUpdate()。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。
    如果组件实现了getSnapshotBeforeUpdate()生命周期(不常用),则它的返回值将作为componentDidUpdate()的第三个参数snapshot参数传递,否则此参数将为undefined

    componentDidUpdate(prevProps, prevState, snapshot) {}
    

    componentWillUnmount()

    componentWillUnmount()会在组件卸载及销毁之前直接调用,在此方法中执行必要的清理操作,例如清除timer、取消网络请求或清除在componentDidMount()中创建的订阅等。
    componentWillUnmount()中不应调用setState(),因为该组件将永远不会重新渲染,组件实例卸载后,将永远不会再挂载它。

    componentWillUnmount() {}
    

    static getDerivedStateFromError()

    此生命周期会在后代组件抛出错误后被调用,它将抛出的错误作为参数,并返回一个值以更新stategetDerivedStateFromError()会在渲染阶段调用,因此不允许出现副作用,如遇此类情况,请改用componentDidCatch()

    static getDerivedStateFromError(error) {}
    

    componentDidCatch()

    此生命周期在后代组件抛出错误后被调用,componentDidCatch()会在提交阶段被调用,因此允许执行副作用,它应该用于记录错误之类的情况它接收两个参数:

    • error: 抛出的错误。
    • info: 带有componentStack key的对象,其中包含有关组件引发错误的栈信息。
    componentDidCatch(error, info) {}
    

    示例

    React组件的常用生命周期示例。

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="UTF-8" />
      <title>React生命周期</title>
    </head>
    
    <body>
      <div id="root"></div>
    </body>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
    
      class Clock extends React.Component {
        constructor(props) {
          super(props);
          this.state = { date: new Date() };
        }
        componentDidMount() {
          console.log("ComponentDidMount", this);
          console.log(this.props);
          console.log(this.state);
          console.log("");
          this.timer = setInterval(() => this.tick(), 1000);
        }
        componentWillUnmount() {
          console.log("ComponentWillUnmount", this);
          console.log(this.props);
          console.log(this.state);
          console.log("");
          clearInterval(this.timer);
        }
        tick() {
          this.setState({ date: new Date() });
        }
        render() {
          return (
            <div>
              <h1>{this.props.tips}</h1>
              <h2>Now: {this.state.date.toLocaleTimeString()}</h2>
            </div>
          );
        }
      }
    
      class App extends React.Component{
        constructor(props){
          super(props);
          this.state = { 
            showClock: true,
            tips: "Hello World!"
          }
        }
        componentDidUpdate(prevProps, prevState) {
          console.log("ComponentDidUpdate", this);
          console.log(this.props);
          console.log(this.state);
          console.log("");
        }
        updateTips() {
          this.setState((state, props) => ({
            tips: "React update"
          }));
        }
        changeDisplayClock() {
          this.setState((state, props) => ({
            showClock: !this.state.showClock
          }));
        }
        render() {
          return (
            <div>
              {this.state.showClock && <Clock tips={this.state.tips} />}
              <button onClick={() => this.updateTips()}>更新tips</button>
              <button onClick={() => this.changeDisplayClock()}>改变显隐</button>
            </div>
          );
        }
      }
    
      var vm = ReactDOM.render(
        <App />,
        document.getElementById("root")
      );
    </script>
    
    </html>
    

    每日一题

    https://github.com/WindrunnerMax/EveryDay
    

    参考

    https://www.jianshu.com/p/b331d0e4b398
    https://www.cnblogs.com/soyxiaobi/p/9559117.html
    https://zh-hans.reactjs.org/docs/react-component.html
    https://zh-hans.reactjs.org/docs/state-and-lifecycle.html
    https://www.runoob.com/react/react-component-life-cycle.html
    https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
    
  • 相关阅读:
    Shiro使用Redis作存储之后更新Session失败的问题
    安装JDK 9 时出现“正在进行另一Java安装”
    Rancher——新一代智障Docker调度系统
    使用Spring STOMP时ChannelInterceptor无法获取用户信息
    Docker生存要点
    多线程编程几个误区
    shiro中JdbcRealm使用salt的问题
    通过ProxyServlet实现可编程的反向代理
    关于之前的博客《SharePoint无代码工作流设计开发实例——交通费报销流程(三) 》的一些问题的解答
    SharePoint无代码工作流设计开发实例——交通费报销流程(三)
  • 原文地址:https://www.cnblogs.com/WindrunnerMax/p/14090262.html
Copyright © 2020-2023  润新知