React 面试突击系列一:速成复习5道题

176 阅读8分钟

前言

在本系列中,我们将为你提供五道常见的 React 面试题,并提供详细的答案和解释。由于专注于提供快速面试前的准备,不会深入讲解原理。让我们一起开始高效的面试复习吧!

👇下面是我们即将学习的 5 道题:

  1. 说说 React 生命周期有哪些不同阶段?
  2. 说说对 React refs 的理解以及应用场景?
  3. 说说对受控组件和非受控组件的理解以及应用场景?
  4. 说说对 React Hooks 的理解以及它解决了什么问题?
  5. 说说 React JSX 转换成真实 DOM 的过程?

说说 React 生命周期有哪些不同阶段?

React 生命周期主要分为三个阶段:挂载阶段(Mounting)更新阶段(Updating)卸载阶段(Unmounting)

挂载阶段(Mounting)

  • constructor():这是构造函数,用于初始化组件的状态(state)和绑定事件处理函数。示例:

    constructor(props) {
      super(props);
      this.state = { count: 0 };
      this.handleClick = this.handleClick.bind(this);
    }
    
  • static getDerivedStateFromProps(props, state):静态方法,在组件创建和接收新的 props 时调用,用于根据 props 更新 state。

    static getDerivedStateFromProps(nextProps, prevState) {
      // 如果新的 props 中包含新的 initialValue,则更新 state
      if (nextProps.initialValue !== prevState.value) {
        return {
          value: nextProps.initialValue
        };
      }
      return null; // 不进行 state 更新
    }
    
  • render()render方法是必须的,它负责渲染组件的 UI 结构,通常返回 React 元素(JSX)。

    render() {
      return <div>{this.state.count}</div>;
    }
    
  • componentDidMount():组件挂载完成后调用,通常用于执行异步操作、数据获取、DOM 操作等。

    componentDidMount() {
      // 发起数据请求或其他操作
      fetchData().then(data => {
        this.setState({ data });
      });
    }
    

更新阶段(Updating)

  • static getDerivedStateFromProps(props, state):同挂载阶段,用于根据新的 props 更新 state

  • shouldComponentUpdate(nextProps, nextState):该方法用于控制组件是否需要重新渲染,返回true表示需要重新渲染,返回false表示不需要。

    shouldComponentUpdate(nextProps, nextState) {
      // 根据条件决定是否重新渲染
      return nextProps.someProp !== this.props.someProp;
    }
    
  • render():同挂载阶段的render方法,用于渲染更新后的 UI。

  • getSnapshotBeforeUpdate(prevProps, prevState):在更新前获取更新前的 props 和 state,通常与 componentDidUpdate 一起使用。

    getSnapshotBeforeUpdate(prevProps, prevState) {
      // 捕获当前滚动位置
      if (prevProps.list.length < this.props.list.length) {
        const list = this.listRef.current;
        return list.scrollHeight - list.scrollTop;
      }
      return null;
    }
    
    componentDidUpdate(prevProps, prevState, snapshot) {
      // 如果存在快照,恢复滚动位置
      if (snapshot !== null) {
        const list = this.listRef.current;
        list.scrollTop = list.scrollHeight - snapshot;
      }
    }
    
  • componentDidUpdate(prevProps, prevState):组件更新完成后调用,通常用于处理更新后的 DOM 操作、数据同步等。

    componentDidUpdate(prevProps, prevState) {
      if (prevProps.someProp !== this.props.someProp) {
        // 执行一些操作
      }
    }
    

卸载阶段(Unmounting)

  • componentWillUnmount():组件即将卸载时调用,用于清理定时器、取消订阅等资源回收。示例:

    componentWillUnmount() {
      // 清理资源
      clearInterval(this.timer);
    }
    

需要注意的是,这里介绍的是新版本的 React 生命周期。

React 16.3 版本以后引入了一些新的生命周期方法,如 getDerivedStateFromProps 和 getSnapshotBeforeUpdate,同时废弃了一些旧的方法,如 componentWillMount、componentWillReceiveProps 和 componentWillUpdate。

说说对 React refs 的理解以及应用场景?

理解 ref

当你希望组件“记住”某些信息,但又不想让这些信息触发新的渲染时,你可以使用 ref 。

通过 ref,你可以在 React 中访问 DOM 元素或 React 组件实例。

何时使用 ref

通常,当你的组件需要“跳出” React 并与外部 API 通信时,你会用到 ref

访问 DOM 元素

最常见的用例是访问和操作 DOM 元素,比如获取输入框的值、聚焦输入框、手动触发动画等。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myInputRef = React.createRef();
  }

  focusInput = () => {
    this.myInputRef.current.focus();
  };

  render() {
    return (
      <div>
        <input type="text" ref={this.myInputRef} />
        <button onClick={this.focusInput}>Focus Input</button>
      </div>
    );
  }
}

存储 timeout ID

你可以在需要时访问 Timeout ID,以便在组件卸载或其他操作中清除 Timeout。这是一个确保定时任务被正确管理的常见模式

import React, { Component } from "react";

class MyComponent extends Component {
  constructor(props) {
    super(props);
    // 创建一个 Ref 来存储 Timeout ID
    this.timeoutRef = React.createRef();
  }

  // 在组件挂载后设置一个 Timeout,并将 Timeout ID 存储在 Ref 中
  componentDidMount() {
    this.timeoutRef.current = setTimeout(() => {
      console.log("Timeout complete");
      // 清除 Timeout
      clearTimeout(this.timeoutRef.current);
    }, 2000); // 2 秒后触发 Timeout
  }

  componentWillUnmount() {
    // 组件卸载前确保清除 Timeout
    clearTimeout(this.timeoutRef.current);
  }

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

export default MyComponent;

总结:如果你的组件需要存储一些值,但不影响渲染逻辑,请选择 ref。

说说对受控组件和非受控组件的理解以及应用场景?

受控组件(Controlled Components)

  • 定义: 受控组件是由 React 组件的状态(state)控制的组件。这意味着输入元素的值(如 <input><textarea> 的值)以及其他用户界面元素的状态都受 React 组件的 state 控制。
  • 应用场景: 受控组件通常用于需要对用户输入进行严格控制或实时响应输入变化的情况。它们适用于需要在输入时执行验证、格式化或操作输入数据的场景。

示例:

class ControlledInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = { inputValue: "" };
  }

  handleChange = (event) => {
    this.setState({ inputValue: event.target.value });
  };

  render() {
    return (
      <input
        type="text"
        value={this.state.inputValue}
        onChange={this.handleChange}
      />
    );
  }
}

非受控组件(Uncontrolled Components)

  • 定义: 非受控组件是不受 React 组件 state 控制的组件。在非受控组件中,DOM 元素的值由 DOM 自身管理,而不是通过 React 的 state 控制。
  • 应用场景: 非受控组件通常用于集成第三方库或处理不需要实时控制的情况。它们适用于那些不需要对输入进行操作的简单表单元素。

示例:

class UncontrolledInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建一个 Ref 来引用 DOM 元素
    this.inputRef = React.createRef();
  }

  handleSubmit = (event) => {
    // 通过 Ref 获取输入框的值
    console.log("A name was submitted: " + this.inputRef.current.value);
    event.preventDefault();
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="text" ref={this.inputRef} />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

在大多数情况下,推荐使用受控组件来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。

总的来说,受控组件提供更多的控制和可预测性,但通常需要更多的代码来管理状态。非受控组件在某些情况下可以简化代码,但失去了 React 状态管理的好处。

说说对 React Hooks 的理解以及它解决了什么问题?

React Hooks 的理解

React Hooks 是一组函数,用于在函数组件中添加状态管理和生命周期。它们的名字以 "use" 开头,如 useStateuseEffectuseContext 等。

解决了什么问题

  1. 状态管理: 以前,只有类组件才能拥有本地状态,而函数组件则通常用于无状态的 UI 渲染。Hooks 引入了 useState,使函数组件能够轻松地管理局部状态,从而更好地处理交互和数据。

    import React, { useState } from "react";
    
    function Counter() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      );
    }
    
  2. 副作用处理: 以前,副作用代码(如数据获取、订阅和手动 DOM 操作)需要在类组件的生命周期方法中编写,而 Hooks 引入了 useEffect,允许函数组件处理副作用。

    import React, { useState, useEffect } from "react";
    
    function Example() {
      const [data, setData] = useState(null);
    
      useEffect(() => {
        fetchData().then((result) => {
          setData(result);
        });
      }, []);
    
      return <div>{data ? <p>Data: {data}</p> : <p>Loading...</p>}</div>;
    }
    
  3. 逻辑复用: Hooks 允许将组件之间的共享逻辑抽象成可重用的自定义 Hook,从而减少了重复代码的编写。

    import { useState, useEffect } from "react";
    
    function useCounter(initialValue) {
      const [count, setCount] = useState(initialValue);
    
      useEffect(() => {
        document.title = `Count: ${count}`;
      }, [count]);
    
      return [count, setCount];
    }
    
    // 在多个组件中重用自定义 Hook
    function Counter1() {
      const [count, setCount] = useCounter(0);
      // ...
    }
    
    function Counter2() {
      const [count, setCount] = useCounter(10);
      // ...
    }
    

React Hooks 的引入使 React 更加灵活和易于维护,同时提供了一种更直观的方式来管理组件的状态和副作用。这使得函数组件在功能上与类组件更加接近,让 React 开发更加便捷和高效。

说说 React JSX 转换成真实 DOM 的过程?

当你在 React 中编写 JSX 代码时,它需要经过一系列的转换步骤才能最终渲染成真实的 DOM 元素。以下是 React JSX 转换成真实 DOM 的过程:

  1. 编写 JSX 代码: 首先,你会在你的 React 组件中编写 JSX 代码,它看起来像是在 JavaScript 中嵌套 HTML 标签。

    const element = <h1>Hello, React!</h1>;
    
  2. Babel 转换: JSX 代码不是浏览器可以直接理解的 JavaScript 代码,所以它需要通过 Babel 或类似的工具进行转换。Babel 将 JSX 转换成等效的 JavaScript 代码,通常使用 React.createElement 函数。

    const element = React.createElement("h1", null, "Hello, React!");
    
  3. 创建虚拟 DOM: React.createElement 函数创建了一个虚拟 DOM 元素,它是一个 JavaScript 对象,描述了要创建的元素类型、属性和子元素。

  4. 渲染虚拟 DOM: 接下来,React 将虚拟 DOM 元素渲染成真实的 DOM 元素。这个过程发生在 React 的虚拟 DOM 层,它会比较前后两次虚拟 DOM 的差异,然后只更新需要改变的部分,以提高性能。

  5. 插入到页面: 最后,React 将真实的 DOM 元素插入到页面的特定位置。

这个过程确保了你可以用声明式的方式编写组件,而 React 负责处理底层的 DOM 操作和性能优化。它使得前端开发更加高效和可维护,因为你可以专注于组件的逻辑和外观,而不用担心手动操作 DOM。

下面是一个完整的示例,展示了 JSX 到真实 DOM 的转换过程:

// JSX 代码
const element = <h1>Hello, React!</h1>;

// Babel 转换后的 JavaScript 代码
const element = React.createElement("h1", null, "Hello, React!");

// 创建虚拟 DOM 元素
const virtualDOM = {
  type: "h1",
  props: {
    children: "Hello, React!",
  },
};

// 渲染虚拟 DOM 成真实 DOM 并插入到页面
// 最终在页面上显示 "Hello, React!"

这个过程中,React 负责管理虚拟 DOM 和真实 DOM 之间的关系,以及在需要时进行高效的更新。这正是 React 提供了出色性能的原因之一。

最后

感谢你阅读我的 React 面试题突击系列的文章。

希望这些问题和答案能够为你的前端面试提供帮助,让你在面试中表现出色。

祝你在面试中好运,在前端领域取得卓越成就!