常见的 React 面试问题以及经过审查、雄辩的答案以供练习
如果你想在 2021 年找到一份出色的 React 工作,那么这篇文章就是为你准备的。
我和出色的@Cassidoo 🎉一起在 Scrimba 的新React 面试问题模块背后为您带来了这篇文章。
在该课程中,Cassidoo 利用她在 Netlify(以及之前在 CodePen)工作的专业经验,分享了 26 个可能的 React 面试问题和示例答案。
您正在阅读的是一份 4500 字的史诗级 React 面试题及示例答案。您可以将其作为快速参考,或作为练习大声练习答案。我还在 Scrimba 博客上附上了一份React 面试题 PDF 版本,方便您下载打印 😎。
这里,我列出了相同的问题,并附上经过审核的答案,供你参考。以此为灵感,写出雄辩而自信的答案,让你未来的雇主惊叹不已🤩。
对于每个问题,我的目标是强调:
- 🔑 你的回答中需要提及的关键点
- 📝 如果你发现自己的知识存在差距,可以去哪里了解更多信息
- ⛔️ 在某些情况下,我还会提到常见的错误答案,让你不惜一切代价避免
不用多说,以下是问题(按照它们在模块中出现的顺序列出,以便您想一起使用这些资源):
React DOM
虚拟DOM和真实DOM有什么区别?
DOM 将 HTML 文档表示为树结构,其中每个节点代表文档的一部分(例如,元素、元素属性或文本):
使用原始 JavaScript 和 DOM API,您可以访问任何您喜欢的元素(例如,使用 document.getElementById)并直接更新它。
当你这样做时,浏览器会遍历 DOM 并重新渲染每个节点——即使该节点自上次渲染以来没有发生变化。这显然效率低下 😳
想象一下这样一个场景:你只需要更新表中 10,000 条记录中的一条。渲染所有 10,000 行几乎肯定会导致帧率下降,甚至可能导致表格闪烁,影响用户体验。
这就是 React 的虚拟 DOM(VDOM)发挥作用的地方✅。
React 通过构建 DOM 的“虚拟”表示(VDOM 😉)来跟踪对真实 DOM 进行的所有更改,从而提高 UI 的性能。
每次应用程序的状态更新时,React 都会构建一个新的 VDOM 并与之前的 VDOM 进行比较,以确定在直接有效地更新 DOM 之前需要进行哪些更改:
- 🔑 这里需要重点提到的是diffing。如果你想更灵活一些,你可以用它的技术名称来描述这个过程,即reconciliation(React将新建的 VDOM 与之前的 VDOM进行协调)。
- 📝 了解更多
- React 关于 VDOM 的文档
- 从另一个角度来看,我们还建议您阅读虚拟 DOM 是纯粹的开销
- ⛔️ 一个常见的误解是 VDOM 是 React 的一个特性。这完全是错误的!VDOM 是一个早于 React 的编程概念,已被许多 UI 库采用,包括 Vue。
虚拟 DOM 和影子 DOM 一样吗?
一句话,没有。
虚拟 DOM 是 React 实现的一种编程概念,主要是为了提高渲染性能,而 Shadow DOM 是一种浏览器技术,旨在限制 Web 组件中的变量和 CSS 的作用域。
虚拟 DOM 和 Shadow DOM 的名称听起来很相似,但相似之处也仅限于此 - 它们完全不相关。
- 🔑 向面试官展示你可以批判性地思考使用哪种工具来解决哪些问题,而不是盲目地使用 React
- 📝 了解更多
- 作为奖励,你可以了解React Native 的局限性——许多团队发现“一次编写,随处运行”的想法很诱人,直到他们尝试它
React 限制
React 有哪些局限性?
任何工具都不是没有局限性的,React 也不例外。
React 的大小为 133kb,被认为是一个相对较重的依赖项。相比之下,Vue 的大小为 58kb。因此,对于小型应用来说,React 可能显得有些过大。
在文件大小上比较 React 和 Vue 感觉很公平,因为它们都是库而不是框架。
与 Angular 这样的框架相比,React 不会强制要求如何编写和构建代码,或者使用哪些库来执行数据获取等操作 - 使用 Angular,团队成员会本能地使用 Angular 的内置功能HttpClient
,而使用 React,团队需要依赖额外的数据获取库,如 Axios 或 Fetch。
由于 React 不会强制要求开发者就最佳代码结构达成一致,因此团队需要格外注意代码的一致性,以确保项目能够有序发展。这可能会导致沟通成本增加,并增加新手的学习难度。
这些都是在着手新项目时需要考虑的重要事项。一旦你决定使用 React,一个限制就是文档并不总是线性的或最新的😉。
- 🔑 向面试官展示你可以批判性地思考使用哪种工具来解决哪些问题,而不是盲目地使用 React
- 📝 了解更多
- 作为奖励,你可以了解React Native 的局限性——许多团队发现“一次编写,随处运行”的想法很诱人,直到他们尝试它
JSX
什么是 JSX?
JavaScript XML (JSX) 外观与 XML 和 HTML 类似,用于使用熟悉的语法创建元素。
JSX 是 JavaScript 的一个扩展,只有 Babel 这样的预处理器才能理解。预处理器遇到这种类似 HTML 的文本后,会将其转换为常规的函数调用React.createElement
:
- 🔑 JSX 是
React.createElement
函数的语法糖 - 📝 了解更多
- ⛔️ 虽然 JSX 类似于 HTML,但它本身并不是 HTML。如果你想回答“JSX 允许你在 JavaScript 中编写 HTML”,那可不准确。
你能不使用 JSX 来编写 React 吗?
一句话,是的。
JSX 不是 ECMAScript 规范的一部分,因此没有 Web 浏览器真正理解 JSX。
相反,JSX 是 JavaScript 语言的扩展,只有 Babel 等预处理器才能理解。
当预处理器遇到一些 JSX 代码时,它会将类似 HTML 的语法转换为常规的旧函数调用React.createElement
:
React.createElement
就像 React.component 或 React.useRef 一样,它是 React 公共顶级 API 的一部分。如果你愿意,你可以在自己的代码中调用 React.createElement ✊
- 🔑 JSX 是
React.createElement
函数的语法糖,意味着你可以直接调用React.createElement
(但这并不一定意味着你应该这样做)- 这个 StackOverflow 问题的答案揭示了你所需要了解的有关 JSX 和 Babel 的魔力
道具
如何将值从父级传递给子级?
将值作为 prop 传递!
- 🔑 通常你只需要说这些👌
- 📝 了解更多:
如何将值从子级传递给父级?
要将值从子组件传递到其父组件,父组件必须首先提供一个函数,供子组件使用该值进行调用。自定义表单组件就是一个例子。
想象一个自定义表单组件来选择一种名为的语言SelectLanguage
。
当选择语言时,我们希望将该值传递回父级进行处理。
为此,SelectLanguage
子组件需要接受一个回调函数作为 prop,然后使用值调用该函数。这类函数的可能名称是onLanguageSelect
。
- 🔑 将一个函数 prop 传递给子组件,子组件可以调用该 prop。在你的答案中传达这一点的最佳方式是使用一个类似
SelectLanguage
组件 props 的示例。 - 📝 了解更多:
- 我们特意借用了StackOverflow 答案
SelectLanguage
中的示例组件,以便您可以阅读更多内容
- 我们特意借用了StackOverflow 答案
什么是支柱钻井?
Prop 钻取是将 props 从某个对象传递FirstComponent
到另一对象SecondComponent
,而后者实际上并不需要数据,而只是将其传递给另一个对象ThirdComponent
,甚至可能更远。
Prop 钻孔有时被称为线程,如果不是反模式的话,它被认为是一种滑坡。
想象一下,如果一个 prop 深入到 5 层、10 层甚至更多层(!),代码很快就会变得难以理解。当你需要跨多个组件共享数据(例如语言环境偏好、主题偏好或用户数据)时,就会出现这种陷阱。
虽然 prop 钻探本质上并不坏,但通常有更有说服力和可维护的解决方案可供探索,例如创建复合组件✳️或使用 React Context,然而,这些解决方案并非没有局限性。
- 🔑 当你传递一个 prop 到超过两个组件的深度,而第二个组件实际上并不需要数据(它只是传递它)时,就会发生 Prop 钻探
- 📝 了解更多
- Kent C. Dodds对什么是螺旋桨钻井、为什么螺旋桨钻井不好以及如何避免常见的问题提出了客观的看法
- ✳️ 奖金
- 如果复合组件听起来很有趣,但你不确定它们到底是什么,那就加入前端开发人员职业道路,享受 Cassidoo关于构建可重复使用的 React 的 3 小时互动模块,她在其中详细讨论了复合组件
子组件可以修改自己的 props 吗?
不行。
组件可以更新自己的状态,但不能更新自己的道具。
可以这样理解:Props 属于父组件,而不是子组件——子组件无权修改不属于它的值。因此,Props 是只读的。
尝试修改 props 要么会导致明显的问题,要么更糟的是,会让你的 React 应用处于一种微妙的不稳定状态。React
规定,要更新 UI,就必须更新 state。
- 🔑 React 需要你将 props 视为只读(即使有方法可以弄乱它们)
- 📝 了解更多
- 这个StackOverflow 答案使用示例代码来说明如果弄乱子组件的 props 会发生什么
- 虽然子组件无法更新自身的 props,但如果父组件通过 state 改变 props,这些 props 的值也会随之改变。尽管标题耸人听闻(也可能令人困惑),但这篇 FreeCodeCamp 文章展示了一个熟悉的这种模式的例子。
状态和生命周期
props 和 state 之间有什么区别?
Props 本质上是初始化子组件时使用的选项。这些选项(如果你愿意的话)属于父组件,并且接收它们的子组件不能对其进行更新。
另一方面,状态属于组件并由组件管理。
状态始终以默认值初始化,并且该值会在组件的整个生命周期内随着用户输入或网络响应等事件而发生变化。当状态发生变化时,组件会通过重新渲染进行响应。
State 是可选的,这意味着有些组件有 props 但没有 state。这样的组件被称为无状态组件。
- 🔑
props
并且state
相似之处在于它们都包含影响渲染输出的信息,但是,props 被传递给组件(类似于函数参数),而状态在组件内进行管理(类似于在函数内声明的变量)。 - 📝 了解更多
类组件中的状态与功能组件中的状态有何不同?
类组件中的状态属于类实例(this),而功能组件中的状态由 React 在渲染之间保留并在每次调用时调用。
在类组件中,初始状态在组件的构造函数中设置,然后分别使用 this.state 和 this.setState() 访问或设置。
在功能组件中,使用 useState Hook 来管理状态。useState 接受一个参数作为其初始状态,然后返回当前状态和一个以对的形式更新状态的函数。
- 🔑 类组件中的状态属于类实例 (this),并在构造函数中与类一起初始化。在函数式组件中,每次渲染组件时都会调用 useState Hook,并返回 React 底层记住的状态。
- 📝 了解更多
- 加入前端开发人员职业道路,享受 Cassidoo关于构建可重复使用的 React 的 3 小时互动模块,该模块扩展了此答案
- 我们非常喜欢Twilio 的这篇关于React 中的函数组件与类组件的文章- 其中关于状态处理的部分尤其相关
什么是组件生命周期?
React 组件有 4 个不同的“生命”阶段:
- 🌱 首先,组件被初始化并挂载到 DOM 上
- 🌲 随着时间的推移,组件会更新
- 🍂 最终,组件被卸载或从 DOM 中移除
使用类组件中的生命周期方法或功能组件中的 useEffect Hook,我们可以在组件生命周期的特定时间运行代码。
例如,在一个类组件中,我们可能会实现componentDidMount
并编写代码来建立一个新的 Web Socket 连接。随着实时 Web Socket 数据的不断传入,状态会更新,进而render
运行生命周期方法来更新 UI。当不再需要该组件时,我们通过实现 来关闭 Web Socket 连接componentWillUnmount
。
- 🔑 React 组件有多个生命周期方法,你可以重写这些方法,以便在组件生命周期的特定时间运行代码。了解所有函数并非坏事,但更重要的是能够解释清楚每个函数的具体使用时机。有些生命周期方法并不常用,所以你不太可能有使用经验。如果没有必要,不要让面试官走这条路。
- 📝 了解更多
如何在功能组件中更新生命周期?
使用useEffect
钩子!
您可以将useEffect
Hook 视为componentDidMount
、componentDidUpdate
和 的componentWillUnmount
组合。
- 🔑 使用
useEffect
- 📝 了解更多
效果
useEffect 接受哪些参数?
useEffect
接受两个参数。
第一个参数是一个名为的函数effect
,它赋予了useEffect
Hook 其名称。
第二个参数是一个可选数组,名为dependencies
,用于控制effect
函数的具体运行时间。可以将其视为函数引用并依赖的dependencies
变量(通常是状态变量) 。effect
如果你选择不指定 any dependencies
,React 将默认在组件首次挂载时以及每次渲染完成后运行该效果。大多数情况下,这是不必要的,最好只在发生更改时运行该效果。
这就是可选dependencies
参数出现的地方✅。
当dependencies
存在时,React 会将的当前值dependencies
与上一次渲染中使用的值进行比较。只有当发生变化effect
时才会运行✊dependencies
如果您希望效果仅运行一次(类似于componentDidMount
),您可以向传递一个空数组([]
)dependencies
。
- 🔑 该
useEffect
函数接受一个effect
函数和一个可选列表dependencies
- 📝 了解更多
该函数何时useEffect
运行?
useEffect 何时运行完全取决于依赖项数组参数:
- 如果传递一个空数组(
[]
),则效果在组件挂载时运行(类似于componentDidMount) - 如果传递状态变量数组(
[var]
),则效果会在组件安装时运行,并且这些变量的值随时发生变化 - 如果省略依赖项参数,则效果将在组件安装时以及每次状态改变时运行
这就是全部内容!
- 🔑 这就是全部内容!
- 📝 了解更多
useEffect
该函数的返回值是什么?
该useEffect
函数接受两个参数——一个effect
函数和一个可选dependencies
数组。
该effect
函数要么不返回任何内容(undefined
),要么返回一个我们可以调用的函数cleanup
。
此cleanup
函数在组件从 UI 中移除之前执行,以防止内存泄漏(类似于componentWillUnmount
)。
此外,如果一个组件渲染多次(通常会这样做),则effect
在执行下一个之前会清理前一个组件effect
。
- 🔑 返回一个
cleanup
函数(类似于componentWillUnmount
)并且可以在每个效果之后运行 - 📝 了解更多
参考文献
refs 和状态变量之间有什么区别?
refs 和状态变量都提供了一种在渲染之间保留值的方法;但是,只有状态变量会触发重新渲染。
虽然 refs 传统上(现在仍然)用于直接访问 DOM 元素(例如,与第三方 DOM 库集成时),但在功能组件中使用 refs 来在渲染之间保留值已变得越来越普遍,当值更新时不应触发重新渲染。
出于这个原因,在类组件中没有太多理由使用 refs,因为将这些值存储在属于类实例的字段中更自然,并且无论如何都会在渲染之间持久化。
- 🔑 两者都会在渲染之间保留值,但只有状态变量会导致组件重新渲染
- 📝 了解更多
- 加入前端开发人员职业道路,享受 Cassidoo关于构建可重复使用 React 的 3 小时互动模块,其中包括8 个关于 refs 的视频
什么时候是使用 ref 的最佳时间?
仅在必要时使用 ref!
Refs 主要以两种方式使用。
refs 的一个用途是直接访问 DOM 元素并对其进行操作 - 例如实现第三方 DOM 库。另一个例子是触发命令式动画。
refs 的第二种用途是在功能组件中,有时它们是一个很好的选择,可以在渲染之间保留值,而不会在值发生变化时触发组件重新渲染。
对于 React 新手来说,refs 通常会让他们感到很熟悉,因为他们习惯于自由地编写命令式代码。正因如此,初学者往往会过度使用 refs。我们更了解。我们知道,为了最大限度地利用 React,我们必须以 React 的方式思考,并且理想情况下,用状态和组件层次结构来控制应用程序的每一部分。React 文档将 refs 描述为“逃生舱”,这是有充分理由的!
- 🔑 仅在必要时使用 refs,以避免破坏封装
- 📝 了解更多
更新函数组件中的 ref 的正确方法是什么?
使用useRef
钩子!
- 🔑 这就是全部内容!
- 📝 了解更多
语境
上下文 API 和 prop 钻孔之间有什么区别?
在 React 中,您可以通过 props 明确地将数据从父组件传递到子组件。
如果需要数据的子组件恰好嵌套很深,我们有时会使用 prop-drilling,但这可能会带来滑坡效应。这种情况通常发生在数据跨多个组件共享时——例如语言环境偏好、主题偏好或用户数据(例如身份验证状态)。
相反,Context API 为我们提供了一个中央数据存储,我们可以隐式访问它以使用来自任何组件的数据,而无需显式地将其作为 prop 请求。
Context API 的隐式特性允许使用更简洁、更易于管理的代码,但如果值被意外更新,也可能导致“陷阱!”,因为不太容易跟踪该值并了解其在何处被线性修改。
- 🔑 Prop-drilling 是显式的,因此比较冗长,但至少你知道最终会得到什么。Context API 是隐式的,因此比较简洁,但如果使用不当,可能会导致不必要的重新渲染。
- 📝 了解更多
什么时候不应该使用上下文 API?
Context API 的主要缺点是,每次 context 发生变化时,所有使用该值的组件都会重新渲染。这可能会对性能造成负面影响。
因此,您应该仅将 Context 用于不经常更新的数据,例如主题偏好。
- 🔑 这就是全部内容!
- 📝 了解更多
其他(但很重要!)问题
什么是Fragment
?
Fragment
是一个新引入的组件,它支持从组件的渲染方法返回多个子项,而无需多余的 div 元素。
您可以使用 React 的顶级 API ( React.Fragment
) 或使用 JSX 语法糖 ( <>
) 来引用它。
- 🔑 我们不必
div
从组件的渲染方法返回一个,而是可以返回一个Fragment
- 📝 了解更多
- 如果你需要回答“为什么是片段?”,那么这个 dev.to 帖子就是一个
- 官方文档还涉及动机和 JSX 语法糖
何时应该创建基于类的组件而不是函数组件?
在 React 的世界里,创建 React 组件有两种方式。一种是使用派生自的类React.Component
,另一种是使用带有 Hooks 的函数式组件。
在 2018 年 Hooks 出现之前,不可能用函数式组件替代基于类的组件 - 主要是因为如果不编写类,就无法在渲染之间设置状态和记住值。
有了 Hooks,类和函数组件通常可以互换,随着我们进入新的一年,趋势很明显:函数组件正在兴起,而且理由充分📈。
功能组件释放了钩子的所有优点,包括易于使用、可测试性和更清晰的代码。
在撰写本文时,尚无与(不常见的)、和生命周期方法等效的 Hook getSnapshotBeforeUpdate
,getDerivedStateFromError
但componentDidCatch
它们“很快”会推出。
- 🔑 类组件和函数组件通常可以互换。为了保持一致性,请选择代码库中现有的组件。对于新项目,除非你需要 Hooks 尚不支持的生命周期方法,否则请使用 Hooks。
- 📝 了解更多
什么是高阶组件?
高阶组件(HOC)是一个接受组件并返回新的、修改后的组件的函数。
虽然 HOC 与 React 相关联,但它们并不是 React 的功能,而是一种受函数式编程模式(称为高阶函数)启发的模式,通过这种模式,您还可以将函数传递给函数。
您可以编写自定义 HOC 或从库中导入。React Sortable
HOC 就是一个开源 HOC 示例,通过它您可以传递一个列表组件(基于),并获得一个增强的排序和拖放功能。ul
ul
- 🔑 这里的关键是回忆一下你在自己的项目中使用 HOC 的时间,并描述为什么它是适合这项工作的模式
- 📝 了解更多
- 加入前端开发者职业道路,享受我们对 HOC 的高级交互式介绍
- 这个开源仓库展示了许多 HOC 的不同示例
什么是门户?
React 通常有一个挂载点——你传递给 ReactDOM.render 的 HTML 元素。React 从这里开始,以层级结构向页面添加新元素。
有时,你需要打破这种层级结构。
想象一下,一个小小的“关于”组件,带有一个按钮来打开模态框。由于模态框会“溢出”到容器之外,这不仅感觉不自然,而且实现起来也很棘手,因为“关于”组件可能已经overflow: hidden
设置了 ,或者故意设置了z-index
。
这就是门户发挥作用的地方✅。
门户和createPortal
函数为您提供了一种在附加挂载点(除了传递给的主要挂载点ReactDOM.render
)中渲染子项的方法。
您不太可能在自己的项目中使用 Portal 读取或编写代码。
门户主要用于当父组件具有overflow: hidden
或z-index
,但您需要子组件在视觉上“突破”其容器时。
示例包括模式、工具提示和对话框;但是,我们通常使用第三方组件来实现这些功能,这意味着我们不太可能需要自己编写 Portal 代码。
- 🔑 Portal 提供了一种一流的方法,将子组件渲染到存在于父组件 DOM 层次结构之外的 DOM 节点中
- 📝 了解更多
什么是不受控组件和受控组件?
受控组件是一种输入组件input
,例如textarea
或 ,select
其值由 React 控制。
相反,不受控制的组件管理其自己的状态 - 该组件不受 React 控制,因此是“不受控制的”。
想象一下,如果你把一个东西扔到textarea
页面上并开始打字。
您输入的任何内容都会textarea
自动存储在 中,并可通过其value
属性访问。尽管 React 可以使用 访问该值ref
,但 React 并不控制此处的值。这是一个不受控组件的示例。
要在 React 中控制此组件,您需要订阅textarea
sonChange
事件并更新状态变量(例如,名为input
)作为响应。
现在 React 负责管理 textareas 的值,你也必须负责设置 textareas 的 value 属性。这样,就可以通过更新状态来更新 textareas 的内容。很容易想象一个叫做 clearTextArea 的函数,它会将输入状态变量设置为空字符串,从而清除 textarea。
- 🔑 “受控组件”和“非受控组件”这两个名称的含义过于宽泛。更具体的名称应该是“受控输入组件”和“非受控输入组件”。缩小你的答案,专注于输入组件,才能确保你能够清晰地回答这个问题。
- 📝 了解更多