MST

星途 面试题库

面试题:React 16+ 新生命周期在复杂异步业务逻辑场景下的深度应用

在一个实时数据交互的应用中,如在线协作绘图工具,多个用户同时操作会产生大量异步请求和数据更新。React 16引入了新的生命周期如`getDerivedStateFromProps`和`getSnapshotBeforeUpdate`。请详细阐述如何在这种复杂异步业务逻辑场景下,合理使用这些新生命周期方法以及`componentDidUpdate`来确保数据一致性、避免竞态条件,同时保证用户操作的流畅性,并给出详细的代码示例和设计模式。
13.1万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

1. getDerivedStateFromProps

  • 作用:此方法在组件挂载和更新时都会被调用,用于根据新的 props 更新 state。它是一个静态方法,不能访问 this。主要用于在 props 变化时同步更新 state,使得 stateprops 保持一致。
  • 在实时数据交互应用中的使用:在在线协作绘图工具中,当接收到来自服务器的新绘图数据(作为 props 传递进来),可以通过 getDerivedStateFromProps 来更新组件的 state,确保本地绘制状态与服务器传来的数据一致。
class DrawingComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      drawingData: []
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.newDrawingData!== prevState.drawingData) {
      return {
        drawingData: nextProps.newDrawingData
      };
    }
    return null;
  }

  render() {
    return (
      <div>
        {/* 基于 this.state.drawingData 进行绘图渲染 */}
      </div>
    );
  }
}

2. getSnapshotBeforeUpdate

  • 作用:在更新发生之前,在 render 之后立即调用,它可以访问更新前的 propsstate,并且返回的值会作为 componentDidUpdate 的第三个参数 snapshot 传入。常用于在 DOM 更新前捕获一些信息,如滚动位置等。
  • 在实时数据交互应用中的使用:在绘图工具中,如果用户在绘图区域滚动,并且希望在更新后保持滚动位置,可以使用 getSnapshotBeforeUpdate 捕获更新前的滚动位置,然后在 componentDidUpdate 中恢复滚动位置。
class DrawingComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      drawingData: []
    };
    this.drawingRef = React.createRef();
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.newDrawingData!== prevState.drawingData) {
      return {
        drawingData: nextProps.newDrawingData
      };
    }
    return null;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    const drawingArea = this.drawingRef.current;
    if (drawingArea) {
      return {
        scrollTop: drawingArea.scrollTop,
        scrollLeft: drawingArea.scrollLeft
      };
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    const drawingArea = this.drawingRef.current;
    if (drawingArea && snapshot) {
      drawingArea.scrollTop = snapshot.scrollTop;
      drawingArea.scrollLeft = snapshot.scrollLeft;
    }
  }

  render() {
    return (
      <div ref={this.drawingRef}>
        {/* 基于 this.state.drawingData 进行绘图渲染 */}
      </div>
    );
  }
}

3. componentDidUpdate

  • 作用:在组件更新后调用,可以访问更新前的 propsstate。常用于副作用操作,如数据请求、DOM 操作等。
  • 在实时数据交互应用中的使用:在绘图工具中,当用户绘制完成后,可能需要将绘制的数据发送到服务器。可以在 componentDidUpdate 中判断绘制数据是否发生变化,如果变化则发送数据到服务器。
class DrawingComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      drawingData: []
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.newDrawingData!== prevState.drawingData) {
      return {
        drawingData: nextProps.newDrawingData
      };
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.drawingData!== this.state.drawingData) {
      // 发送数据到服务器
      fetch('/api/saveDrawing', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ drawingData: this.state.drawingData })
      });
    }
  }

  render() {
    return (
      <div>
        {/* 基于 this.state.drawingData 进行绘图渲染 */}
      </div>
    );
  }
}

设计模式

  • 单向数据流模式:在 React 应用中,始终保持单向数据流,即数据从父组件通过 props 流向子组件,子组件通过回调函数通知父组件状态变化。在绘图工具中,顶层组件管理绘图数据的 state,通过 props 将数据传递给负责绘制的子组件,子组件通过回调函数通知顶层组件数据更新,顶层组件再通过 getDerivedStateFromProps 更新 state
  • 防抖(Debounce)和节流(Throttle):对于频繁触发的用户操作,如绘图时的鼠标移动事件,使用防抖或节流技术来减少不必要的更新。例如,在用户绘制线条时,使用节流确保每 100ms 才更新一次绘制数据,避免大量高频异步请求。
import throttle from 'lodash/throttle';

class DrawingComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      drawingData: []
    };
    this.handleDraw = throttle(this.handleDraw.bind(this), 100);
  }

  handleDraw(event) {
    // 更新绘制数据逻辑
    this.setState((prevState) => {
      // 根据鼠标位置等更新 drawingData
      return {
        drawingData: prevState.drawingData.concat([/* 新的绘制点 */])
      };
    });
  }

  render() {
    return (
      <div onMouseMove={this.handleDraw}>
        {/* 基于 this.state.drawingData 进行绘图渲染 */}
      </div>
    );
  }
}

通过合理使用这三个生命周期方法以及相应的设计模式,可以在复杂异步业务逻辑场景下确保数据一致性、避免竞态条件,并保证用户操作的流畅性。