Skip to content

React 组件与生命周期

1. 什么是有状态组件和无状态组件?

答案

  1. 有状态组件(Stateful Components):

    • 包含自身的state(状态)
    • 通常是类组件,但使用useState Hook的函数组件也可以是有状态的
    • 可以处理复杂的逻辑和数据操作
  2. 无状态组件(Stateless Components):

    • 不包含自身的state
    • 通常是函数组件
    • 主要用于展示UI,接收props并渲染

示例:

jsx
// 有状态组件
class StatefulComponent extends React.Component {
  state = { count: 0 };
  render() {
    return <div>{this.state.count}</div>;
  }
}

// 无状态组件
function StatelessComponent(props) {
  return <div>{props.count}</div>;
}

2. React 组件的生命周期方法有哪些?它们的调用顺序是什么?

答案:React 类组件的生命周期方法可以分为三个阶段:

  1. 挂载阶段:

    • constructor()
    • static getDerivedStateFromProps()
    • render()
    • componentDidMount()
  2. 更新阶段:

    • static getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()
  3. 卸载阶段:

    • componentWillUnmount()

调用顺序就是上述列出的顺序。注意,在 React 16.3 之后,一些生命周期方法被标记为不安全(如 componentWillMount, componentWillReceiveProps, componentWillUpdate),应避免使用。

3. componentDidMount() 方法有什么用途?

答案:componentDidMount() 方法在组件被挂载到 DOM 后立即调用。这个方法通常用于:

  1. 执行副作用操作,如设置订阅或定时器
  2. 发起网络请求获取数据
  3. 直接操作 DOM

示例:

jsx
class MyComponent extends React.Component {
  componentDidMount() {
    // 发起 API 请求
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => this.setState({ data }));

    // 设置定时器
    this.timer = setInterval(() => {
      console.log('Timer ticking');
    }, 1000);
  }

  componentWillUnmount() {
    // 清除定时器
    clearInterval(this.timer);
  }

  render() {
    // 渲染逻辑
  }
}

4. shouldComponentUpdate() 方法的作用是什么?如何优化性能?

答案:shouldComponentUpdate() 方法允许我们手动决定是否要重新渲染组件。它在 props 或 state 发生变化时,在渲染前被调用。

作用:

  1. 性能优��:通过比较新旧 props 和 state,决定是否需要重新渲染
  2. 避免不必要的渲染

优化性能的方法:

  1. 实现浅比较:比较新旧 props 和 state 的值
  2. 使用 React.PureComponent:自动实现浅比较的 shouldComponentUpdate
  3. 使用 React.memo 高阶组件(用于函数组件)

示例:

jsx
class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 只有当 title 改变时才重新渲染
    return this.props.title !== nextProps.title;
  }

  render() {
    return <div>{this.props.title}</div>;
  }
}

// 使用 React.memo
const MyFunctionComponent = React.memo(function MyFunctionComponent(props) {
  return <div>{props.title}</div>;
});

5. 什么是错误边界(Error Boundaries)?如何实现?

答案:错误边界是 React 组件,它可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。

实现错误边界:

  1. 定义 static getDerivedStateFromError() 或 componentDidCatch() 方法
  2. 使用 state 来控制是否应该显示错误 UI

示例:

jsx
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.log('Error:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

// 使用
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

6. React 16.3 之后的生命周期变化是什么?为什么要进行这些变化?

答案:React 16.3 之后的主要生命周期变化:

  1. 引入了新的生命周期方法:

    • getDerivedStateFromProps
    • getSnapshotBeforeUpdate
  2. 废弃了一些生命周期方法(在 17.0 版本中彻底移除):

    • componentWillMount
    • componentWillReceiveProps
    • componentWillUpdate

这些变化的原因:

  1. 为即将到来的异步渲染做准备
  2. 避免开发者在即将被移除的生命周期方法中编写副作用代码
  3. 鼓励更好的编码实践,如将副作用移到 componentDidMount 中

新的生命周期方法使用示例:

jsx
class MyComponent extends React.Component {
  static getDerivedStateFromProps(props, state) {
    // 根据 props 更新 state
    return null;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 获取更新前的一些信息
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 使用 getSnapshotBeforeUpdate 的返回值
  }
}

7. 如何在函数组件中模拟生命周期方法?

答案:在函数组件中,我们可以使用 React Hooks 来模拟生命周期方法:

  1. componentDidMount: 使用 useEffect 并传入空数组作为依赖
  2. componentDidUpdate: 使用 useEffect 并传入依赖数组
  3. componentWillUnmount: 在 useEffect 中返回一个清理函数

示例:

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

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 模拟 componentDidMount
    fetchData();

    // 模拟 componentWillUnmount
    return () => {
      // 清理工作,如取消订阅
    };
  }, []); // 空数组表示只在挂载和卸载时运行

  useEffect(() => {
    // 模拟 componentDidUpdate
    console.log('data has changed:', data);
  }, [data]); // 当 data 变化时运行

  const fetchData = () => {
    // 获取数据的逻辑
  };

  return <div>{/* 组件内容 */}</div>;
}

8. React 中的 render 方法作用是什么?它应该是纯函数吗?

答案:render 方法是 class 组件中唯一必须实现的方法。它的作用是:

  1. 检查 this.props 和 this.state 的变化
  2. 返回以下类型之一:
    • React 元素(通常通过 JSX 创建)
    • 数组或 fragments
    • Portals
    • 字符串或数值类型
    • Boolean 或 null

render 方法应该是纯函数,这意味着:

  1. 不应该修改组件的 state
  2. 每次调用时,相同的输入(props 和 state)应该返回相同的输出
  3. 不应该直接与浏览器交互(应该在 componentDidMount 或其他生命周期方法中执行这些操作)

示例:

jsx
class MyComponent extends React.Component {
  render() {
    return (
      <div>
        <h1>{this.props.title}</h1>
        <p>{this.state.content}</p>
      </div>
    );
  }
}

9. 什么是 React 中的受控组件和非受控组件?

答案

  1. 受控组件:

    • 表单数据由 React 组件控制
    • 通过 props 接收当前值,并通过回调通知变化
  2. 非受控组件:

    • 表单数据由 DOM 本身处理
    • 通常使用 ref 来获取表单数据

示例:

jsx
// 受控组件
function ControlledInput() {
  const [value, setValue] = useState('');

  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

// 非受控组件
function UncontrolledInput() {
  const inputRef = useRef(null);

  function handleSubmit() {
    console.log(inputRef.current.value);
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}

10. 什么是高阶组件(HOC)?它们有什么用途?

答案:高阶组件(HOC)是一个函数,它接受一个组件作为参数并返回一个新的组件。HOC 是 React 中复用组件逻辑的高级技术。

HOC 的主要用途:

  1. 代码复用
  2. 渲染劫持
  3. State 抽象和操作
  4. Props 操作

示例:

jsx
function withLogging(WrappedComponent) {
  return class extends React.Component {
    componentDidMount() {
      console.log('Component is mounted');
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

// 使用
const EnhancedComponent = withLogging(MyComponent);

高阶组件可以用来实现横切关注点,如日志记录、权限控制,而不需要修改原组件的代码。