React 高频面试题

507 阅读16分钟

JSX

1. 什么是 JSX?

JSX 就像是在 JavaScript 代码里写 HTML 一样。它把 HTML 元素和 JavaScript 的表达式混合在一块儿,这样可以很方便地描述界面的结构。比如你可以在 JavaScript 函数里直接写一个<div>Hello</div>这样的东西,这就是 JSX。

2. 为什么推荐在 React 中使用 JSX?

  • 它让代码看起来很直观,就像写普通的 HTML 一样简单,但是又能结合 JavaScript 的强大功能。你可以很容易地在界面里展示数据、添加交互等。
  • 能更好地体现组件化的思想。你可以把界面拆分成一个个小的组件,每个组件都可以用 JSX 来描述它的结构,这样代码的可读性和可维护性都提高了。

3. 如何在 JSX 中条件渲染?

可以使用 JavaScript 的条件表达式,比如三元表达式。例如:{isShow? <Component /> : null},根据 isShow 的值来决定是否渲染<Component />。 也可以在函数内部使用 if - else 语句,然后返回不同的 JSX 结构。

4. 如何在 JSX 中循环控制?

可以使用数组的 map 方法。比如有一个数组 data,你可以这样写:{data.map((item) => <li>{item}</li>)},这样就会根据数组的元素个数生成相应数量的<li>元素。

5. 为什么 JSX 中 class 变成了 className?

因为在 JavaScript 里 class 是一个关键字,用来定义类。所以在 JSX 里不能再用 class 来表示 HTML 元素的类名了,就用 className 来代替,这样就不会和 JavaScript 的关键字冲突了。

组件

6. 什么是 React 组件?

React 组件就像是一个可以重复使用的小零件,它包含了界面的一部分,以及这部分界面相关的逻辑。你可以把它想象成乐高积木中的一块,通过组合不同的组件来搭建出整个复杂的界面。

7. React 组件分成哪几类?

主要分为类组件和函数组件。类组件是基于 ES6 的类来定义的,有自己的生命周期方法。函数组件就是一个普通的 JavaScript 函数,接收输入然后返回一个 React 元素。

8. 类组件和函数组件的区别是?

  • 定义方式:类组件通过 class 关键字定义,并且需要继承 React.Component。函数组件就是一个简单的函数。
  • 状态管理:类组件可以在内部使用 this.state 来管理状态,通过 this.setState 来更新状态。函数组件在没有 hooks 之前是无状态的,现在可以使用 React hooks 来管理状态。
  • 生命周期:类组件有完整的生命周期方法,比如 componentDidMount、componentWillUnmount 等。函数组件没有这些生命周期方法,但可以通过 React hooks 模拟一些生命周期相关的操作。

9. 受控组件和非受控组件的区别是?

  • 数据管理方式:在受控组件中,表单元素的值是由 React 组件的状态控制的,每次表单元素的值改变,都会触发一个事件,在事件处理函数中更新组件的状态。而非受控组件的值是由 DOM 本身管理的,React 组件不直接控制表单元素的值。
  • 数据获取方式:对于受控组件,可以直接从组件的状态中获取表单元素的值。对于非受控组件,需要通过引用 DOM 元素来获取表单元素的值。

10. 什么是高阶组件?

高阶组件就像是一个函数,它接收一个 React 组件作为输入,然后返回一个新的 React 组件。这个新的组件可以在原组件的基础上添加一些额外的功能,比如添加权限验证、数据获取等功能。

11. 什么是 Pure Components?

Pure Components 是 React 中的一种优化机制。当组件的属性和状态没有发生变化时,它会阻止组件重新渲染,从而提高性能。它是通过浅比较来判断属性和状态是否发生变化的。

12. 展示组件和容器组件的区别是?

  • 职责:展示组件主要负责界面的展示,它接收输入的属性,然后根据这些属性渲染出界面。容器组件主要负责数据的获取和处理,它把获取到的数据传递给展示组件。
  • 与数据的关联:展示组件不关心数据是怎么来的,它只负责展示数据。容器组件需要和数据源进行交互,比如从服务器获取数据或者修改数据。

13. 如何设计一个 React 组件?

  • 明确组件的职责:确定这个组件是负责展示数据、处理用户交互还是获取数据等。
  • 定义组件的接口:确定组件接收哪些属性,以及向外暴露哪些方法或者事件。
  • 考虑组件的复用性:尽量使组件具有通用性,能够在不同的场景下使用。
  • 处理组件的状态:根据组件的需求,合理地使用状态管理,确保组件的状态是可预测和可维护的。

属性

14. 使用 key 属性有哪些注意事项?

  • key 应该在一个列表中的每个元素上是唯一的,这样 React 可以根据 key 来高效地识别哪些元素被添加、删除或重新排序。
  • 不要使用随机生成的值或者索引作为 key,除非你确定列表元素不会重新排序或者插入新元素。因为如果使用索引作为 key,当列表元素的顺序发生变化时,React 可能会错误地复用组件,导致一些意外的结果。
  • key 只应该在数组的直接子元素上使用,而不是在内部的元素上。

15. 如何在 React 中进行静态类型检查?

可以使用一些静态类型检查工具,比如 TypeScript 或者 Flow。这些工具允许你为组件的属性和状态定义类型,在开发过程中可以提前发现类型不匹配的错误。例如在使用 TypeScript 时,你可以为组件定义接口来指定属性的类型。

16. 如何限制某个属性是必须的?

在使用 TypeScript 或者 Flow 进行类型检查时,可以在组件的属性类型定义中标记某个属性为必选。在 JavaScript 中本身没有直接的机制来强制某个属性是必须的,但是可以在组件内部通过判断属性是否存在来进行一些处理,如果不存在则抛出错误或者给出警告。

17. 如何设置属性的默认值?

在函数组件中,可以使用函数的默认参数来设置属性的默认值。例如:

function MyComponent(props = { name: 'default' }) {
    // 组件逻辑
}

在类组件中,可以在组件类内部定义 static defaultProps 属性来设置属性的默认值。例如:

class MyComponent extends React.Component {
    static defaultProps = { name: 'default' };
    // 组件逻辑
}

通信

18. React 父子组件通信有哪些方法?

  • 属性传递:父组件通过属性把数据传递给子组件,子组件通过 props 接收并使用这些数据。这是最常见的方式。
  • 回调函数传递:父组件传递一个回调函数给子组件,子组件在特定事件发生时调用这个回调函数,把数据传递回父组件。

19. 为什么 React 是单向数据流?

单向数据流可以让数据的流动更加清晰和可预测。从父组件到子组件的数据传递,使得数据的来源和变化路径一目了然。这样在调试和理解组件之间的交互时更加容易,也有助于避免复杂的双向数据绑定可能带来的数据不一致和难以追踪的问题。

20. 什么是 Context?

Context 是一种在 React 组件树中传递数据的方式,它可以让数据在组件树中跨越多个层级进行传递,而不需要通过层层传递属性的方式。例如,当有一些全局的数据,像用户信息、主题信息等,需要在多个不同层级的组件中使用时,就可以使用 Context 来传递这些数据。

21. 什么是 ContextType?

ContextType 是一种在类组件中使用 Context 的简便方法。它允许类组件直接通过 this.context 来获取 Context 中的值,而不需要使用 Context.Consumer 组件来订阅 Context。

渲染

22. 如何优化不必要的渲染?

  • 使用 React.memo(针对函数组件)或者 React.PureComponent(针对类组件)。它们通过浅比较属性和状态来决定组件是否需要重新渲染,当数据没有变化时可以避免不必要的渲染。
  • 在类组件中重写 shouldComponentUpdate 方法,根据特定的条件判断是否需要重新渲染组件。

23. React 为什么要引入基于 Fiber 协调器的异步渲染?

在复杂的应用中,当大量的更新同时发生时,传统的同步渲染方式可能会导致界面卡顿,因为 JavaScript 是单线程的,长时间的同步渲染会阻塞主线程。基于 Fiber 协调器的异步渲染可以把渲染工作分成多个小的任务,根据任务的优先级暂停或者继续渲染,这样可以在渲染过程中保持界面的响应性,提高用户体验。

生命周期

24. React 组件有哪些生命周期方法?

挂载阶段

  • constructor:组件构造函数,用于初始化组件的状态和绑定事件处理程序等。
  • componentDidMount:在组件挂载到 DOM 后立即调用。

更新阶段

  • shouldComponentUpdate:在组件接收新的属性或者状态更新时,决定组件是否需要重新渲染。
  • componentDidUpdate:在组件更新后调用。

卸载阶段

  • componentWillUnmount:在组件从 DOM 中移除之前调用。

25. 异步数据请求应在哪些生命周期里调用?

在类组件中,通常在 componentDidMount 中进行异步数据请求。因为在组件挂载后进行请求可以确保组件已经在 DOM 中,并且可以在获取数据后直接更新组件。在某些情况下,也可以在 componentDidUpdate 中进行请求,但需要注意避免无限循环的请求。 在函数组件中,可以使用 useEffect 钩子来进行异步数据请求,通常在组件挂载和依赖项变化时执行请求。

26. useEffect、useLayoutEffect 与生命周期的对应关系是?

  • useEffect:类似于 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。它在组件挂载后执行,在组件更新后如果依赖项发生变化也会执行,并且在组件卸载时可以进行清理操作。
  • useLayoutEffect:与 useEffect 类似,但它在所有的 DOM 变更之后同步调用。可以看作是 componentDidMount 和 componentDidUpdate 在布局阶段的版本。

事件处理

27. React 和 DOM 事件处理的区别是?

  • 事件绑定方式:在原生 DOM 事件处理中,事件直接绑定在 DOM 元素上,例如 document.getElementById('myButton').addEventListener('click', function() {})。而在 React 中,是在组件的 JSX 中绑定事件,如<button onClick={this.handleClick}>Click me</button>
  • 事件对象:原生 DOM 事件会提供原生的事件对象,包含很多与浏览器相关的属性和方法。React 则提供了合成事件对象,它是对原生事件对象的封装,在不同浏览器下具有一致性。

28. 什么是 React 合成事件?

React 合成事件是 React 对原生 DOM 事件的封装。它抹平了不同浏览器之间的差异,让开发者可以以统一的方式处理事件。例如,无论在哪个浏览器环境中,事件对象的属性和方法都具有相同的行为和表现形式。并且合成事件在事件冒泡阶段被触发,而不是在捕获阶段。

Hook

29. 什么是 State Hook?

State Hook 即 useState,它用于在函数组件中添加状态。通过调用 useState 可以声明一个状态变量,并获得更新该状态变量的函数。例如:const [count, setCount] = useState(0);

30. 什么是 Effect Hook?

Effect Hook 即 useEffect,它用于在函数组件中执行副作用操作,比如数据获取、订阅、手动修改 DOM 等。例如:useEffect(() => { console.log('Component mounted or updated'); }, []);

31. 如何清除 Effect Hook 的副作用?

useEffect 中可以返回一个函数,这个返回的函数会在组件卸载或者下次执行 useEffect 之前执行,用于清除上一次执行产生的副作用。例如:

useEffect(() => {
    const timer = setInterval(() => {
        // 一些操作
    }, 1000);
    return () => {
        clearInterval(timer);
    };
}, []);

32. useReducer 和 useState 的区别是?

  • useState 适用于简单的状态管理,它通过提供的函数来更新状态。例如:const [count, setCount] = useState(0);
  • useReducer 适用于复杂的状态管理场景,它接收一个 reducer 函数和初始状态,通过 dispatch 函数来触发状态更新,更适合管理包含多个子值的状态对象。例如:
const initialState = {count: 0};
const [state, dispatch] = useReducer(reducer, initialState);

33. useLayoutEffect 和 useEffect 的区别是?

  • useEffect 是异步执行的,它会在浏览器渲染完成之后执行。
  • useLayoutEffect 是同步执行的,它会在所有的 DOM 变更之后同步调用,在浏览器进行绘制之前执行,适合需要在 DOM 更新后立即执行的操作,比如读取 DOM 布局信息。

测试

34. 如何将组件渲染成 JS 对象?

可以使用 React Testing Library 中的 render()函数将组件渲染成一个包含 DOM 元素等信息的对象。例如:

import { render } from '@testing-library/react';
const { container } = render(<MyComponent />);

35. 如何模拟 DOM 环境?

Jest 本身在 Node.js 环境中运行,它使用 jsdom 库来模拟 DOM 环境。通常不需要额外的配置,但可以根据需要对 jsdom 进行一些自定义设置。

原理

36. 什么是 Virtual DOM?

Virtual DOM(虚拟 DOM)是一种编程概念,它是真实 DOM 结构在内存中的一种轻量级表示。在 React 中,当组件的状态或属性发生变化时,React 会先在虚拟 DOM 上进行操作(如创建、更新、删除节点等),而不是直接操作真实的 DOM。通过对比前后两个虚拟 DOM 树的差异,找出最小的变更集,然后将这些变更应用到真实 DOM 上,这样可以减少对真实 DOM 的操作次数,提高性能。

37. 什么是 React Diff,对比 Vue Diff?

React Diff

  • React 的 Diff 算法主要是基于两个假设:不同类型的元素会产生不同的树;开发人员可以通过设置key属性来告知 React 哪些元素在不同的渲染之间是稳定的。在对比时,React 会对新旧虚拟 DOM 树进行深度优先遍历,分层比较节点,当发现节点类型不同时,直接替换整个子树,当节点类型相同且有key时,会进行更细致的比较和更新。

Vue Diff

  • Vue 的 Diff 算法在比较节点时,同样会优先判断节点类型是否相同。在处理列表时也依赖key属性。不过 Vue 的 Diff 算法在某些细节上与 React 有所不同,例如在静态节点和动态节点的处理上,Vue 会对静态节点进行优化,尽量减少对静态节点的重复比较。

38. 什么是 React Concurrent 模式?

React Concurrent 模式是 React 中的一种新的渲染模式。它允许 React 应用在渲染过程中中断、暂停或者恢复工作,使得应用在处理复杂的渲染任务或者在资源有限的情况下能够保持响应性。例如,当用户在界面上进行交互操作时,React 可以暂停不重要的渲染工作,优先响应用户交互,从而提高用户体验。

39. 什么是 Suspense?

Suspense 是 React 中的一个特性,它主要用于处理异步操作,比如异步加载组件或者数据。它允许组件在等待异步操作完成时显示一个 fallback 状态(如加载提示),当异步操作完成后,再渲染出实际的内容。这使得异步操作在 React 组件中的处理更加优雅和直观。

40. React 如何定义任务的优先级?

React 根据任务的类型和场景来定义优先级。例如,与用户交互相关的任务(如点击按钮后的响应)通常具有较高的优先级,而一些后台的数据加载或者不太紧急的更新任务可能具有较低的优先级。React 内部使用不同的优先级级别(如立即执行、高优先级、低优先级等)来标记任务,在调度任务时会根据这些优先级来决定任务的执行顺序。

Redux

41. Redux 的核心原则是?

  • 单一数据源:整个应用的状态被存储在一棵单一的状态树中,使得状态的管理更加集中和可预测。
  • 状态只读:不能直接修改状态,必须通过触发 action 来描述对状态的更改。
  • 使用纯函数修改状态:通过 reducer 函数来处理 action 并更新状态,reducer 是纯函数,它接收旧的状态和 action 作为输入,输出新的状态。

42. 如何判断项目需要引入 Redux?

  • 全局状态共享:当多个组件需要共享大量的状态信息,且这些状态的更新需要在多个组件中同步时。
  • 复杂的状态管理:如果应用的状态变化复杂,涉及到多个不同部分的状态交互和更新。
  • 可预测的状态变化:需要确保状态的变化是可预测、可追溯的,以便于调试和维护。

Next.js

43. 为什么 Next.js 要重新设计一套路由?

  • 与服务器端渲染集成:Next.js 的路由是为了更好地与服务器端渲染配合,能够根据不同的请求路径在服务器端动态生成页面,而传统的 React Router 主要是基于客户端渲染。
  • 简化开发流程:它提供了更简洁的路由定义方式,比如基于文件系统的路由,开发者可以通过文件结构来直观地定义路由,减少了配置的复杂性。

44. Next.js 中获取数据有哪些方法?

  • getStaticProps:用于在静态生成(SSG)时获取数据,在构建时运行,可以获取数据并将其作为 props 传递给页面组件。
  • getServerSideProps:用于在服务器端渲染(SSR)时获取数据,在每个请求时运行,同样将数据作为 props 传递给页面组件。
  • 直接在组件内部获取数据(如使用 useEffect 等):这种方式适用于在客户端获取数据,但需要注意数据加载的时机和性能影响。