一、什么是React的生命周期

在 React 中,组件的声明周期是指组件在被创建、更新和销毁的过程中所经历的一系列阶段。这些阶段允许你在不同的时间点执行特定的代码,以便管理组件的状态、数据和用户界面。

在早期版本的 React 中,存在三个主要的声明周期阶段:Mounting(挂载)、Updating(更新)和Unmounting(卸载)。然而,随着 React 的发展,声明周期方法已经逐渐被推荐为不建议使用,取而代之的是使用 React Hooks。不过,了解声明周期方法仍然有助于理解 React 组件的工作原理。

二、传统生命周期

React 生命周期-LMLPHP

2.1、挂载(Mounting)

Mounting 阶段(组件被创建并添加到 DOM)

  1. constructor(): 组件的构造函数,在组件创建时调用。在这里你可以初始化组件的状态。
  2. static getDerivedStateFromProps(): 当组件的 props 发生变化时调用,返回一个对象来更新组件的状态。
  3. render(): 渲染方法,返回组件的 JSX 或子组件。
  4. componentDidMount(): 在组件成功渲染到 DOM 后调用,通常用于执行异步操作,如数据获取。

2.2、更新(Updating)

Updating 阶段(组件的状态或 props 发生变化)

  1. static getDerivedStateFromProps(): 同上述 Mounting 阶段的用法,用于根据新的 props 更新组件的状态。
  2. shouldComponentUpdate(): 决定组件是否需要重新渲染,默认返回 true。可以用于性能优化,避免不必要的渲染。
  3. render(): 同上述 Mounting 阶段的用法,用于重新渲染组件的 JSX 或子组件。
  4. getSnapshotBeforeUpdate(): 在组件更新前获取一个快照,通常用于捕获一些 DOM 信息,比如滚动位置。
  5. componentDidUpdate(): 在组件更新后调用,通常用于处理更新后的操作。

2.3、卸载(Unmounting)

Unmounting 阶段(组件从 DOM 中移除)

  1. componentWillUnmount(): 在组件即将从 DOM 中移除时调用,通常用于清理操作、取消订阅等。

需要注意的是,随着 React 16.3 版本的引入,一些声明周期方法已经被弃用,或者在某些情况下不再推荐使用。而且,使用 React Hooks(如 useStateuseEffect 等)可以更灵活地处理组件的状态和生命周期等操作。

因此,如果你正在学习或使用 React,建议查阅最新的官方文档,以了解更详细和最新的信息。

2.4、API

2.4.1、render

2.4.1.1、Updating 阶段,render调用完还有可能不调用真实DOM么

在 React 的更新阶段中,render 方法的调用是必然会触发的,因为它负责生成组件的虚拟 DOM(Virtual DOM)表示。然而,是否会导致真实 DOM 的更新,取决于 React 在更新过程中的比对算法。

React 在更新组件时会生成新的虚拟 DOM,并与之前的虚拟 DOM 进行比较,以确定需要进行的实际 DOM 更新操作的最小集合。这个过程被称为 “reconciliation”(协调),它能够有效地减少对真实 DOM 的操作,从而提高性能。

如果在比对过程中,React 发现生成的新虚拟 DOM 与之前的虚拟 DOM 没有变化,那么就不会触发对真实 DOM 的更新操作。这也是 React 的一大优势,因为它可以避免不必要的 DOM 操作,从而提升应用的性能。

需要注意的是,尽管 render 方法的调用是必然会发生的,但是实际的 DOM 操作可能并不会每次都发生,这取决于虚拟 DOM 的比对结果。这也是为什么 React 推崇使用虚拟 DOM 来管理真实 DOM,以提高应用的性能和响应性。

2.4.1.2、Updating 阶段,render 调用后,componentDidUpdate是否比被调用

不,render 方法执行完毕不一定会立即执行 componentDidUpdate 方法。在 React 组件的更新过程中,render 方法和 componentDidUpdate 方法是在不同的时间点触发的。

以下是更新过程中 render 方法和 componentDidUpdate 方法的执行顺序:

  1. 当组件的状态或 props 发生变化,触发更新时,首先会调用 render 方法来生成新的虚拟 DOM 表示。

  2. 接着,React 会比较新的虚拟 DOM 与之前的虚拟 DOM,确定是否需要更新真实 DOM。

  3. 如果更新需要被执行,React 会执行实际的 DOM 操作。

  4. 最后,在真实 DOM 更新完成后,React 会调用 componentDidUpdate 方法,允许你在组件更新后进行任何必要的操作。

所以,render 方法执行完毕并不保证立即触发 componentDidUpdatecomponentDidUpdate 方法会在整个更新过程的最后阶段被调用,即在真实 DOM 更新完成后。如果你在 componentDidUpdate 中执行的操作依赖于最新的 DOM 状态,那么确保你在这个方法中能够获取到正确的 DOM 信息。

2.4.2、getSnapshotBeforeUpdate 是在比较虚拟DOM的时候被调用么

是的,getSnapshotBeforeUpdate 方法是在 React 比较虚拟 DOM 并在实际更新 DOM 之前被调用的一个生命周期方法。它的主要用途是在更新之前捕获一些 DOM 信息,以便在更新后恢复这些信息。

具体的执行顺序如下:

  1. 组件的状态或 props 发生变化,触发更新。

  2. getSnapshotBeforeUpdate 方法被调用。在这个方法中,你可以访问到之前的虚拟 DOM 和之前的 props。

  3. React 根据新的虚拟 DOM 进行比较,并确定需要进行的实际 DOM 更新操作的最小集合。

  4. 如果有实际的 DOM 更新需要执行,React 会进行更新操作。

  5. 更新操作完成后,componentDidUpdate 方法会被调用,此时你可以处理更新后的 DOM 以及执行其他操作。

getSnapshotBeforeUpdate 方法中,你可以返回一个值,这个值会作为第三个参数传递给 componentDidUpdate 方法。通常情况下,getSnapshotBeforeUpdate 用于捕获一些 DOM 信息,例如滚动位置,在 componentDidUpdate 中恢复这些信息,以确保用户体验的连续性。

需要注意的是,getSnapshotBeforeUpdate 并不是必需的方法,你可以根据实际需要来选择是否使用它。在大部分情况下,你可能会使用更灵活的 useEffect 和其他 Hooks 来处理更新后的操作,而不必过多地依赖于传统的生命周期方法。

三、React Hooks

3.1、什么是Hooks

React Hooks 是 React 16.8 版本引入的一组函数,用于在函数组件中添加状态管理、副作用处理以及其他 React 特性,从而使函数组件具有类组件的功能。Hooks 可以让你在不编写类组件的情况下,更方便地使用 React 的各种特性。

在使用类组件时,你需要关注生命周期方法和处理复杂的逻辑。而使用 Hooks,你可以将相关的逻辑代码分解成更小、更易于管理的部分,从而使组件更易于阅读、测试和维护。

一些常用的 React Hooks 包括:

  1. useState: 用于在函数组件中添加状态管理,允许你在组件中存储和更新状态数据。

  2. useEffect: 用于处理副作用,例如数据获取、订阅和取消订阅等。可以在组件渲染后执行一些操作,类似于类组件的 componentDidMountcomponentDidUpdate

  3. useContext: 用于访问 React 的上下文(Context),可以在组件树中跨层级传递数据,避免了多层级嵌套传递 props。

  4. useReducer: 类似于 useState,但更适用于管理复杂的状态逻辑,使用 reducer 函数来处理状态的更新。

  5. useCallbackuseMemo: 用于性能优化,分别用于优化函数的引用和记忆计算结果。

  6. useRef: 用于在函数组件中创建 ref,可以用来获取 DOM 元素的引用或保存任意可变值。

  7. 自定义 Hooks:你也可以根据需要编写自己的 Hooks,将一些逻辑抽象成可重用的函数。

使用 React Hooks 可以让你更自由地组织代码,避免了类组件中的一些限制,例如必须使用 this 关键字、无法共享逻辑等。它使函数组件在处理状态、副作用和其他 React 特性时变得更加强大和灵活。

3.2、Hooks方案横向对比老方案

3.2.1、挂载

constructor()

import React, { useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0); // 使用useState初始化状态

  const increment = () => {
    setCount(count + 1); // 更新状态
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default MyComponent;

getDerivedStateFromProps

getDerivedStateFromProps 方法可以在函数组件中使用 useStateuseEffect 来替代,尽管替代方案可能会略微不同,但可以实现类似的功能。

getDerivedStateFromProps 主要用于在接收到新的 props 时更新组件的状态。在函数组件中,可以通过监听 props 的变化,然后在 useEffect 中更新状态来达到相似的效果。下面是一个示例:

import React, { useState, useEffect } from 'react';

function MyComponent(props) {
  const [stateFromProps, setStateFromProps] = useState(null);

  useEffect(() => {
    // 在 props 变化时更新状态
    setStateFromProps(props.someProp);
  }, [props.someProp]); // 监听 props.someProp 变化

  return (
    <div>
      <p>State from props: {stateFromProps}</p>
    </div>
  );
}

export default MyComponent;

在上面的示例中,通过 useState 来管理从 props 衍生的状态,并使用 useEffect 来监听 props.someProp 的变化,然后在变化时更新状态。这种方式类似于 getDerivedStateFromProps 的功能,但是遵循了 React Hooks 的设计模式。

需要注意的是,getDerivedStateFromProps 和 Hooks 替代方案之间可能存在一些细微的差异,因为 getDerivedStateFromProps 是在每次渲染之前都会调用的,而 useEffect 是在渲染之后执行的。根据实际情况,你可能需要调整逻辑来适应 Hooks 的模式。

render

在 React 函数组件中,render 方法的替代是函数组件的主体部分。函数组件的主体部分就是组件的函数体,它返回要渲染的 JSX 元素或其他 React 组件。

例如,下面是一个简单的函数组件,其中的函数体部分就是对应于类组件中的 render 方法:

import React from 'react';

function MyComponent(props) {
  return (
    <div>
      <p>Hello, {props.name}!</p>
    </div>
  );
}

export default MyComponent;

在这个函数组件中,函数体部分就是类似于 render 方法的功能,它返回了一个包含 JSX 元素的 div。函数组件的函数体就是整个组件的渲染逻辑,所以不需要像类组件中那样明确定义 render 方法。

总之,函数组件的整个函数体就是替代了类组件中的 render 方法,用于定义组件的渲染逻辑。

componentDidMount

在 React Hooks 中,componentDidMount 生命周期方法可以被 useEffect Hook 替代。useEffect 用于处理副作用操作,包括在组件挂载后执行的操作。

具体地说,你可以在 useEffect 的函数参数中编写需要在组件挂载后执行的逻辑,使其在组件渲染后触发。以下是使用 useEffect 替代 componentDidMount 的示例:

传统的类组件中的 componentDidMount

class MyComponent extends React.Component {
  componentDidMount() {
    console.log('Component did mount');
    // 在组件挂载后执行的操作
  }

  render() {
    return <div>My Component</div>;
  }
}

使用 useEffect 替代的函数组件:

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    console.log('Component did mount');
    // 在组件挂载后执行的操作
  }, []); // 空数组表示只在挂载时执行一次

  return <div>My Component</div>;
}

在上面的例子中,useEffect 的函数参数中包含了在组件挂载后需要执行的逻辑。通过将一个空数组作为第二个参数传递给 useEffect,确保这个副作用仅在组件挂载时执行一次,模拟了 componentDidMount 的行为。

需要注意,如果你在 useEffect 的第二个参数中传递一个非空数组,那么 useEffect 会在每次指定的依赖发生变化时执行,类似于 componentDidUpdate。如果你不传递第二个参数,useEffect 会在每次组件渲染后都执行,类似于同时具有 componentDidMountcomponentDidUpdate 的行为。

3.2.2、更新

getDerivedStateFromProps

shouldComponentUpdate

在 React Hooks 中,shouldComponentUpdate 生命周期方法可以通过 React.memouseMemo Hooks 进行类似的优化。这些方法可以帮助你避免不必要的组件重新渲染。

  1. React.memo: React.memo 是一个高阶组件,用于对函数组件进行浅层比较的性能优化。它可以用来避免组件在接收相同的 props 时进行重新渲染。这类似于 shouldComponentUpdate 的功能。
import React from 'react';

const MyComponent = React.memo(function MyComponent(props) {
  // 组件的渲染逻辑
});

export default MyComponent;
  1. useMemo: useMemo Hook 可以在渲染期间对值进行缓存,以避免不必要的计算。你可以使用 useMemo 来返回一个经过计算的值,使得在依赖项不变时不会重复计算。这可以用来优化组件内部的重新渲染。
import React, { useMemo } from 'react';

function MyComponent(props) {
  const expensiveValue = useMemo(() => {
    // 计算逻辑
    return computeExpensiveValue(props);
  }, [props.someProp]); // 监听 props.someProp 变化

  // 组件的渲染逻辑
}

虽然 React.memouseMemo 可以优化组件的重新渲染,但需要注意的是,这些方法与 shouldComponentUpdate 并不完全等价。shouldComponentUpdate 可以返回布尔值来明确控制是否应该进行渲染,而 React.memouseMemo 是基于比较机制来决定是否重新渲染。在使用这些方法时,确保理解它们的工作原理以及适用场景。

render

getSnapshotBeforeUpdate

在 React Hooks 中,getSnapshotBeforeUpdate 生命周期方法的功能没有直接的 Hooks 替代方案,因为它主要用于在组件更新前获取 DOM 信息,以及在更新后恢复这些信息。然而,你可以通过组合使用多个 Hooks 来实现类似的功能。

通常情况下,getSnapshotBeforeUpdate 用于在更新前获取一些 DOM 信息,然后在 componentDidUpdate 中使用这些信息。在函数组件中,你可以通过以下方式来模拟这个过程:

  1. 使用 useRef Hook 来获取 DOM 引用,以及在更新前保存 DOM 信息。

  2. useEffect 中进行 DOM 操作,获取更新前的 DOM 信息。

  3. 使用 useEffect 的清理函数来恢复更新后的 DOM 信息。

下面是一个示例,展示如何通过组合使用 Hooks 来模拟 getSnapshotBeforeUpdate 的功能:

import React, { useRef, useEffect } from 'react';

function MyComponent(props) {
  const myElementRef = useRef(null); // 创建 DOM 引用

  useEffect(() => {
    // 更新前的 DOM 信息
    const prevScrollTop = myElementRef.current.scrollTop;
    
    // 进行 DOM 操作
    myElementRef.current.scrollTop = 100; // 假设更新操作

    // 在更新后恢复 DOM 信息
    return () => {
      myElementRef.current.scrollTop = prevScrollTop;
    };
  }, [props.someProp]); // 监听 props.someProp 变化

  return (
    <div ref={myElementRef}>
      {/* 组件的渲染逻辑 */}
    </div>
  );
}

export default MyComponent;

在上面的示例中,我们使用 useRef 来创建一个 DOM 引用,并在 useEffect 中获取更新前的 DOM 信息。然后,在清理函数中,我们恢复了更新后的 DOM 信息。这种方式模拟了 getSnapshotBeforeUpdate 的功能,尽管有一些差异和复杂性。

需要注意,虽然可以通过多个 Hooks 来模拟类似的功能,但在实际应用中,你可能需要权衡使用 Hooks 的复杂性和是否真正需要模拟这个生命周期方法的功能。

componentDidUpdate

在 React Hooks 中,componentDidUpdate 生命周期方法可以通过使用 useEffect Hook 来实现类似的功能。useEffect 可以用于处理在组件更新后执行的操作。

要模拟 componentDidUpdate 的行为,你可以使用 useEffect 并在其函数参数中进行逻辑处理。需要注意的是,useEffect 默认在每次组件渲染后都会执行,类似于同时具有 componentDidMountcomponentDidUpdate 的功能。

以下是如何使用 useEffect 来模拟 componentDidUpdate

import React, { useState, useEffect } from 'react';

function MyComponent(props) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Component did update');
    // 在组件更新后执行的操作
  });

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default MyComponent;

在上面的示例中,useEffect 的函数参数中包含了需要在组件更新后执行的操作。这个 useEffect 会在每次组件渲染后都触发,类似于 componentDidUpdate

如果你只想在特定的 props 或状态变化时执行特定的操作,可以将相应的依赖项传递给 useEffect,这样它只会在这些依赖项变化时触发。例如:

useEffect(() => {
  console.log('Component did update');
  // 在 count 或 props.someProp 变化时执行的操作
}, [count, props.someProp]);

需要注意,useEffect 在默认情况下会在组件的初次渲染和每次更新后都执行,因此在使用时要根据实际需求和逻辑来选择合适的依赖项,以及在何时执行特定的操作。

3.2.3、卸载

componentWillUnmount

在 React Hooks 中,componentWillUnmount 生命周期方法可以通过使用 useEffect Hook 来实现类似的功能。useEffect 可以用于在组件卸载时执行清理操作。

要模拟 componentWillUnmount 的行为,你可以使用 useEffect 并在其函数参数中返回一个清理函数。这个清理函数会在组件卸载时执行,用于执行一些清理操作,比如取消订阅、清除定时器等。

以下是如何使用 useEffect 来模拟 componentWillUnmount

import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Component did mount');

    // 清理函数,会在组件卸载时执行
    return () => {
      console.log('Component will unmount');
      // 在组件卸载时执行的清理操作
    };
  }, []); // 空数组表示只在挂载时执行一次

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default MyComponent;

在上面的示例中,useEffect 的函数参数中返回了一个清理函数。这个清理函数会在组件卸载时执行,用于执行一些在卸载时必要的清理操作。使用空数组作为 useEffect 的第二个参数,确保这个清理函数只在挂载时执行一次。

通过在 useEffect 中返回清理函数,你可以在函数组件中模拟 componentWillUnmount,在组件卸载时进行资源的清理和释放,确保不会产生内存泄漏等问题。

3.3、Hooks API

3.3.1、所有方法

在 React Hooks 中,有一些常用的 Hooks 方法,用于在函数组件中添加和管理状态、副作用、上下文等。以下是一些常用的 React Hooks 方法:

  1. useState: 用于在函数组件中添加和管理状态。它返回一个状态值和一个更新该状态的函数。
const [count, setCount] = useState(0);
  1. useEffect: 用于处理副作用操作,如数据获取、订阅、DOM 操作等。可以模拟 componentDidMountcomponentDidUpdatecomponentWillUnmount
useEffect(() => {
  // 副作用操作
}, [dependencies]);
  1. useContext: 用于在组件中访问 React 上下文。允许你在嵌套组件中共享数据。
const contextValue = useContext(MyContext);
  1. useReducer: 类似于 Redux 中的 reducer,用于管理复杂状态的更新逻辑。
const [state, dispatch] = useReducer(reducer, initialState);
  1. useMemo: 用于在渲染期间对值进行缓存,避免不必要的计算。
const memoizedValue = useMemo(() => computeExpensiveValue(dep), [dep]);
  1. useCallback: 用于缓存函数,避免在每次渲染时重新创建函数。
const memoizedCallback = useCallback(() => {
  // 回调函数逻辑
}, [dependencies]);
  1. useRef: 用于创建可变的 ref 对象,可以在组件渲染之间存储持久值。
const myRef = useRef(initialValue);
  1. useLayoutEffect: 类似于 useEffect,但在浏览器 layout 之后同步执行,可用于 DOM 操作。
useLayoutEffect(() => {
  // 在浏览器 layout 之后执行的操作
}, [dependencies]);
  1. useImperativeHandle: 用于在父组件中访问子组件的实例方法。
useImperativeHandle(ref, () => ({
  // 子组件实例方法
}));
  1. useDebugValue: 用于为自定义 Hook 提供调试标签。
useDebugValue(value);

这只是一些常见的 React Hooks 方法,实际上 React 还提供了其他一些 Hooks,可以根据你的需求选择适合的 Hooks 来管理组件的状态、副作用和逻辑。

3.3.2、useEffect

useEffect 是一个非常强大的 Hook,可以用来模拟 componentDidMountcomponentDidUpdatecomponentWillUnmount 的功能。通过合理地设置 useEffect 的依赖项和清理函数,你可以在函数组件中实现这些生命周期方法的功能。

下面是如何使用 useEffect 来模拟这些生命周期方法:

  1. 模拟 componentDidMount:

useEffect 在组件初次渲染时就会执行,因此可以在其中模拟 componentDidMount。将空依赖数组传递给 useEffect,确保它只在初次渲染时执行。

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    console.log('Component did mount');
    // 在组件挂载后执行的操作
  }, []); // 空依赖数组,只在初次渲染时执行

  // 组件的渲染逻辑
}
  1. 模拟 componentDidUpdate:

useEffect 的默认行为就是在每次组件渲染后都会执行。通过在依赖数组中传入需要监听的状态或 props,你可以模拟 componentDidUpdate。在 useEffect 的函数参数中,可以执行更新后的操作。

import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Component did update');
    // 在组件更新后执行的操作
  }, [count]); // 监听 count 变化

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}
  1. 模拟 componentWillUnmount:

useEffect 的清理函数可以模拟 componentWillUnmount。在返回的清理函数中,可以执行一些在组件卸载前必要的清理操作,比如取消订阅或释放资源。

import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Component did mount');

    return () => {
      console.log('Component will unmount');
      // 在组件卸载前执行的清理操作
    };
  }, []); // 空依赖数组,只在初次渲染时执行

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

通过使用 useEffect 和适当设置依赖项,你可以在函数组件中模拟这些生命周期方法的功能。请根据具体的需求,灵活使用 useEffect 和其他 Hooks 来处理组件的各种状态和副作用。

四、转

chatgpt

08-26 08:23