React 面试题整理

320 阅读12分钟

1.什么是 React?

React 是一个开源用于构建用户页面,特别是单页应用的 JavaScript 库,主要专注于 Web 和移动应用的视图层。

2.React 的主要特性是什么?

  • 使用虚拟 DOM 代替真实 DOM, 减少 DOM 操作开销
  • 遵循单向数据流/数据绑定
  • 使用可复用/可组合的 UI 组件来开发视图
  • 支持服务端渲染

3.state 和 props 之间的不同是什么

prop 和 state 都是普通的 JS 对象。 尽管它们两者都具有影响渲染输出的信息,但它们在组件方面的功能不同。 props 被传递组件就像给函数传参,而 state 是在组件内进行管理类似于在函数中声明的变量。

4.什么是受控组件

受控组件是指控制表单中后续用户输入的输入元素的组件,每个状态的变化都用相应的处理函数。

5.什么是非受控组件

非受控组件是在内部存储其自身状态的组件,在需要时使用 ref 查询 DOM 以查找其当前值,和传统的 HTML 差不多

6.什么是高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

HOC 的用例:

  • 代码,逻辑复用和抽象
  • 渲染劫持
  • 状态抽象和操作
  • props 操作

7.组件生命周期方法

挂载

当组件实例被创建并插入DOM的时候,其生命周期被调用顺序如下:

  1. constructor(props) - 初始化state和为事件处理函数绑定实例;
  2. static getDerivedStateFromProps(props, state) - 当state的值在任何时候都取决于props时使用,返回一个对象来更新state,返回null则不更新;
  3. render() - 渲染React组件;
  4. componentDidMount() - 组件挂载后调用,一个生命周期内仅一次;

更新

当组件的props或state发生变化时会触发更新。组件更新的生命周期顺序如下:

  1. static getDerivedStateFromProps(props, state);
  2. shouldComponentUpdate(nextProps, nextState) - 根据更新后的state或props判断是否重新渲染DOM,返回值为布尔值,常用来性能优化;
  3. render();
  4. getSnapshotBeforeUpdate(prevProps, prevState) - 是的组件能在发生改变之前从DOM中捕获一些信息(如滚动位置),返回值作为componentDidUpdate的第三个参数;
  5. componentDidUpdate(prevProps, prevState, snapshot) - state或props更新后调用

卸载

componentWillUnmount() - 组件销毁或卸载时调用;

image.png

8.什么是 fragments

这是 React 中的常见模式,用于组件返回多个元素, Fragments 使您可以将子项列表分组,而无需向 DOM 添加额外的节点.

9.什么是 React Fiber?

Fiber 是 React 16 中新的协调引擎或重新实现核心算法。它的主要目标是支持虚拟DOM的增量渲染。React Fiber 的目标是提高其在动画、布局、手势、暂停、中止或重用等方面的适用性,并为不同类型的更新分配优先级,以及新的并发原语。

React Fiber 的目标是增强其在动画、布局和手势等领域的适用性。它的主要特性是增量渲染:能够将渲染工作分割成块,并将其分散到多个帧中。

10.什么是 context

Context 提供了一种在组件树传递数据的方法,而不必在每个级别手动传递 props。

const {Provider, Consumer} = React.createContext(defaultValue)

11.props 里面传递的 children 是啥

Children 是一个 props(this.prop.children),它允许将组件作为数据传递给其他组件,就像使用的任何其他 props 一样。放在组件之间的组件将作为子组件传递给该组件。

React API 中有许多方法可用于此 prop。 这些包括

  • React.Children.map
  • React.Children.forEach
  • React.Children.count
  • React.Children.only
  • React.Children.toArray

12.在构造函数中 props 作为参数传入 super 是做什么

在调用 super() 方法之前,子类构造函数不能使用 this 引用。这同样适用于 ES6 子类。将 props 参数传递给super() 调用的主要原因是为了 this.props 能在子构造函数使用。

13.refs 有什么用

Refs 提供了一种访问在render方法中创建的 DOM 节点或者 React 元素的方法。Refs 应该谨慎使用,如下场景使用 Refs 比较适合:

  • 处理焦点、文本选择或者媒体的控制
  • 触发必要的动画
  • 集成第三方 DOM 库

14.什么是 React Hooks?

Hooks是 React 16.8 中的新添加内容。它们允许在不编写类的情况下使用state和其他 React 特性。

useState hook示例如下:

import { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

15.使用 React Hook 需要遵循哪些规则?

  • 只在最顶层使用 Hook,不要在循环,条件或嵌套函数中调用 Hook
  • 只在 React 函数中调用 Hook,不要在普通的 JavaScript 函数中调用 Hook。

16.React中setState后发生了什么

在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个UI界面。

在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。

如果在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果。

17.setState到底是异步还是同步?

  • setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。

  • setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

18.React中的setState批量更新的过程是什么?

调用 setState 时,组件的 state 并不会立即改变, setState 只是把要修改的 state 放入一个队列, React 会优化真正的执行时机,并出于性能原因,会将 React 事件处理程序中的多次React 事件处理程序中的多次 setState 的状态修改合并成一次状态修改。 最终更新只产生一次组件及其子组件的重新渲染,这对于大型应用程序中的性能提升至关重要。

19.React 的事件机制

React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。

除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。

20.React Hooks 解决了哪些问题?

React Hooks 主要解决了以下问题:

(1)在组件之间复用状态逻辑很难

React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)解决此类问题可以使用 render props 和 高阶组件。但是这类方案需要重新组织组件结构,这可能会很麻烦,并且会使代码难以理解。由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。

可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使我们在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。

(2)复杂组件变得难以理解

在组件中,每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。

在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。

为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

(3)难以理解的 class

除了代码复用和代码管理会遇到困难外,class 是学习 React 的一大屏障。我们必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。

21.useEffect 与 useLayoutEffect 的区别

useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。

22.React 中的状态提升是什么

当多个组件需要共享同一个的被更改数据时,建议将共享状态提升到最接近的公共父组件,也就是说,如果两个子组件共享来自其父组件的相同数据,则将 state 移到父组件,而不是在两个子组件都各自保持私有数据。

23.在React中如何避免不必要的render?

父组件渲染导致子组件渲染,子组件并没有发生任何改变,这时候就可以从避免无谓的渲染,具体实现的方式有如下:

  • shouldComponentUpdate
  • PureComponent
  • React.memo

shouldComponentUpdate

通过shouldComponentUpdate生命周期函数来比对 state和 props,确定是否要重新渲染

默认情况下返回true表示重新渲染,如果不希望组件重新渲染,返回 false 即可

PureComponent

shouldComponentUpdate原理基本一致,通过对 props 和 state的浅比较结果来实现 shouldComponentUpdate

React.memo

React.memo用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent十分类似。但不同的是, React.memo 只能用于函数组件

24.React diff的原理是什么?

reactdiff算法主要遵循三个层级的策略,分别是tree层级、conponent 层级、element 层级。

tree层级

忽略节点跨层级操作场景,提升比对效率。即两棵树只对同一层次的节点进行比较,如果发现节点已经不存在了,则该节点及其子节点会被完全删除掉,不会用于进一步的比较,这就提升了比对效率。

conponent层级

如果是同一个类的组件,则会继续往下diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的

element层级

对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识。通过标记 key 的方式,React 可以直接移动 DOM 节点,降低内耗。

25.React 性能优化

  • 渲染列表时加Key
  • 懒加载组件
  • 事件绑定方式
  • 合理使用 shouldComponentUpdate、PureComponent 和 memo
  • 合理使用 ImmutableJS
  • webpack层面优化
  • 前端通用性能优化,如图片懒加载
  • 使用SSR
  • 使用 React Fragments 避免额外标记