MST

星途 面试题库

面试题:React 16+ 生命周期变更后的数据加载策略调整

React 16 之后,生命周期方法有所变更,比如废弃了一些旧的生命周期方法。请详细说明在这些变更后,对于数据加载策略你需要做出哪些调整?如何在新的生命周期体系下,保证数据加载的正确性和性能优化,特别是在处理异步数据加载时。
22.3万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

1. React 16 废弃的生命周期方法

在 React 16 之后,componentWillMountcomponentWillReceivePropscomponentWillUpdate 被标记为不安全的生命周期方法,逐渐被废弃。原因在于 React 的异步渲染机制(Fiber)可能会导致这些方法被多次调用,从而产生不一致的状态。

2. 数据加载策略调整

  • 替代 componentWillMount
    • 旧策略:在 componentWillMount 中进行数据加载,由于该方法在组件挂载前调用,此时 DOM 还未生成。
    • 新策略:使用 componentDidMount 替代。此方法在组件挂载后调用,确保 DOM 已生成,并且只会调用一次,适合进行副作用操作如数据加载。例如:
import React, { Component } from'react';

class DataLoader extends Component {
  constructor(props) {
    super(props);
    this.state = { data: null };
  }

  componentDidMount() {
    fetch('your-api-url')
    .then(response => response.json())
    .then(data => this.setState({ data }));
  }

  render() {
    const { data } = this.state;
    return (
      <div>
        {data? <p>{JSON.stringify(data)}</p> : <p>Loading...</p>}
      </div>
    );
  }
}

export default DataLoader;
  • 替代 componentWillReceiveProps
    • 旧策略:在 componentWillReceiveProps 中根据新的 props 进行数据加载或更新状态,但其会在组件接收到新 props 时就触发,无论 props 是否变化。
    • 新策略:使用 getDerivedStateFromPropscomponentDidUpdate 结合。getDerivedStateFromProps 是一个静态方法,在组件挂载及接收到新 props 时都会被调用,用于根据 props 更新 statecomponentDidUpdate 则用于处理副作用,如根据新 props 进行数据加载。例如:
import React, { Component } from'react';

class DataLoader extends Component {
  constructor(props) {
    super(props);
    this.state = { data: null };
  }

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

  componentDidUpdate(prevProps, prevState) {
    if (prevProps.id!== this.props.id) {
      fetch(`your-api-url/${this.props.id}`)
      .then(response => response.json())
      .then(data => this.setState({ data }));
    }
  }

  render() {
    const { data } = this.state;
    return (
      <div>
        {data? <p>{JSON.stringify(data)}</p> : <p>Loading...</p>}
      </div>
    );
  }
}

export default DataLoader;
  • 替代 componentWillUpdate
    • 旧策略componentWillUpdate 在组件更新前调用,可用于准备更新,但同样可能因异步渲染多次调用。
    • 新策略:使用 getSnapshotBeforeUpdatecomponentDidUpdategetSnapshotBeforeUpdate 在 DOM 更新前调用,返回值会作为 componentDidUpdate 的第三个参数,可用于获取更新前的 DOM 状态等。例如:
import React, { Component } from'react';

class ScrollingList extends Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
    this.state = { items: [] };
  }

  componentDidMount() {
    this.fetchData();
  }

  fetchData = () => {
    // 模拟异步数据获取
    setTimeout(() => {
      this.setState({ items: [...this.state.items, 'new item'] });
    }, 1000);
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevState.items.length!== this.state.items.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.listRef}>
        {this.state.items.map((item, index) => (
          <p key={index}>{item}</p>
        ))}
        <button onClick={this.fetchData}>Add Item</button>
      </div>
    );
  }
}

export default ScrollingList;

3. 保证数据加载的正确性和性能优化(异步数据加载)

  • 避免重复请求
    • 策略:在数据加载前进行检查,若数据已存在且未过期,不再重复请求。可以使用缓存机制,例如在组件内维护一个缓存变量,并设置过期时间。
    • 示例
import React, { Component } from'react';

class DataLoader extends Component {
  constructor(props) {
    super(props);
    this.state = { data: null, cache: null, cacheTime: 0 };
  }

  componentDidMount() {
    this.loadData();
  }

  loadData = () => {
    const { cache, cacheTime } = this.state;
    const now = Date.now();
    if (cache && now - cacheTime < 60 * 1000) { // 缓存 1 分钟
      this.setState({ data: cache });
      return;
    }
    fetch('your-api-url')
    .then(response => response.json())
    .then(data => {
        this.setState({ data, cache: data, cacheTime: now });
      });
  }

  render() {
    const { data } = this.state;
    return (
      <div>
        {data? <p>{JSON.stringify(data)}</p> : <p>Loading...</p>}
      </div>
    );
  }
}

export default DataLoader;
  • 处理并发请求
    • 策略:使用 AbortController 来取消未完成的请求。当组件卸载或接收到新的请求条件时,取消之前的请求,防止无效请求占用资源。
    • 示例
import React, { Component } from'react';

class DataLoader extends Component {
  constructor(props) {
    super(props);
    this.state = { data: null };
    this.controller = new AbortController();
  }

  componentDidMount() {
    this.fetchData();
  }

  componentWillUnmount() {
    this.controller.abort();
  }

  fetchData = () => {
    const { signal } = this.controller;
    fetch('your-api-url', { signal })
    .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
    .then(data => this.setState({ data }))
    .catch(error => {
        if (error.name!== 'AbortError') {
          console.error('Error fetching data:', error);
        }
      });
  }

  render() {
    const { data } = this.state;
    return (
      <div>
        {data? <p>{JSON.stringify(data)}</p> : <p>Loading...</p>}
      </div>
    );
  }
}

export default DataLoader;
  • 使用 SuspenseReact.lazy(适用于 React 16.6+)
    • 策略React.lazy 用于动态导入组件,Suspense 用于处理加载状态。可以将数据加载逻辑封装在动态导入的组件中,通过 Suspense 统一处理加载和错误状态,实现更优雅的异步渲染。
    • 示例
import React, { Suspense } from'react';

const DataComponent = React.lazy(() => import('./DataComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <DataComponent />
    </Suspense>
  );
}

export default App;

DataComponent 中进行数据加载:

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

const DataComponent = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('your-api-url')
    .then(response => response.json())
    .then(data => setData(data));
  }, []);

  if (!data) {
    return null;
  }

  return <p>{JSON.stringify(data)}</p>;
};

export default DataComponent;