【面试官系列】一道曾经卡得我 “头皮发麻” 的阿里前端(React)面试题 ~

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【面试官系列】一道曾经卡得我 “头皮发麻” 的阿里前端(React)面试题 ~

image.png
本文正在参加「金石计划」

flag:每月至少产出三篇高质量文章~

最近前 leader 找到我,让我帮他面试一个前端开发的岗位(react技术栈,3年+),在整理面试题的时候,想到几年前跳槽的时候面阿里高德时被问到的一个 “刁钻” 面试题:image.png注意!【componentWillMount】,不是 componentDidMountcomponentDidUpdatecomponentWillUnmount。大家可以先不看下文,自己考虑一下,会怎么回答这个问题。

这个之所以让我印象这么深刻,是因为当时我被这个问题问懵了。常规的 React 面试题可能会问你怎么实现 componentDidMountcomponentDidUpdatecomponentWillUnmount 之类的,而这个问题却并不按常理出牌,让你实现一个在之前 class component 时代都不怎么常用的生命周期 —— componentWillMount

个人觉得这个问题虽然角度 “刁钻”,但是不失为一道挺有水平的面试题,是因为:

  1. 需要面试者对 React 类组件的生命周期和 v16.8 以后的函数组件中官方提供的 React Hooks 执行时机有足够深入的了解才能比较好地回答这个问题;
  2. 如果是在 React Hooks 时代才入坑 React 的新晋选手恐怕会难以理解这个问题的本质;
  3. 即便是经验丰富的 React 选手,由于疏于对 componentWillMount 的使用(这个钩子确实用得也少),也可能翻车,需要有比较丰富、扎实的 React 基础知识;

那接下来,我们就尝试怎么比较好地回答这个问题吧~ 阅读此文,你将对新旧版本的 React 的生命周期以及 React Hooks 的执行时机有更深刻的理解。

一、Class Component 时代的生命周期

为了回答好上述面试题,我们首先得对 componentWillMount 这个生命周期及其执行时机有足够的了解。所以,先来回顾一下 class component 的生命周期。

我想很多人应该对下面两张图挺熟悉的吧,来自 wojtekmaj 的项目:react-lifecycle-methods-diagram

  • react v16.3

image.png

  • react v16.4image.png注意:componentWillReceiveProps()v16.4 已经被标记为不建议使用,官方建议使用新的getDerivedStateFromProps()方法代替。

React class 组件在其生命周期中经历三个阶段:挂载更新卸载

  1. 挂载阶段是在创建新组件并将其插入 DOM 时,或者换句话说,在组件生命周期开始时。这只会发生一次,通常称为“初始渲染”。
  2. 更新阶段是组件更新或重新渲染的时候。当道具更新或状态更新时,会触发此反应。这个阶段可以发生多次,这就是 React 的意义所在。
  3. 组件生命周期的最后一个阶段是卸载阶段,当组件从 DOM 中移除时。

以下是每个生命周期函数的详细描述和执行时机:

1、挂载阶段

这个阶段发生在组件被创建并插入到 DOM 中的时候。按照上图,这个阶段会执行这几个钩子函数:constructorstatic getDerivedStateFromPropscomponentWillMount/UNSAVE_componentWillMountrendercomponentDidMount

constructor()

构造函数,在组件创建时调用,用于初始化状态和绑定方法。

需要注意:如果你使用了 constructor 函数,你需要首先调用 super(props) 才能使用this关键字。


image.png

static getDerivedStateFromProps()

需要注意的是: propsstate 是完全不同的概念,一个成熟的 React 开发者最基本的是要知道组件的数据从哪里来,要往哪里去。

顾名思义,getDerivedStateFromProps 的字面意思就是:从 props 获取 衍生state。但在许多情况下,你的组件的 state 实际上是其 props 的衍生品。这个方法允许你用 任何props值 来修改 state 值

这个方法在组件挂载前调用,并且在组件每次更新时也会被调用。它的作用是根据 props 的改变来更新 state,返回一个 新的state。如果不需要更新 state,返回 null 即可。

image.png

componentWillMount/UNSAVE_componentWillMount

React v16.3 版本中将 componentWillMount, componentWillReceiveProps以及componentWillUpdate 加上了 UNSAFE_ 前缀,这些钩子将在 React 17.0 废除,如果你确实选择继续使用它,你应该使用 UNSAFE_componentWillMount()

这个生命周期函数在 render 之前调用,在此生命周期中使用 setState 不会触发额外渲染,因为你不可能在创建的时候把数据渲染出来。只能在 componentDidMount 中使用 setState 把数据塞回去,通过更新界面来展示数据。所以一般建议把网络请求的逻辑放在 componentDidMount,而不是 componentWillMount 中。

image.png

render()

render() 方法是唯一必须的钩子函数,它在 getDerivedStateFromProps 方法之后被调用,用于渲染组件的UI。

注意:不要在 render() 方法中改变 state,否则会陷入死循环,导致程序崩溃。

image.png

componentDidMount()

componentDidMount 是在挂载阶段调用的最后一个生命周期方法,组件被挂载后调用,这个方法可以用于发起网络请求或者设置定时器等异步操作。它可能在组件被渲染或挂载到DOM之后被调用。

这个方法中,你可以添加副作用,如发送网络请求或更新组件的状态,componentDidMount 中还可以订阅 Redux store。你也可以立即调用 this.setState 方法;但这将导致重新渲染,因为它启动了更新阶段,因为状态已经改变。

所以,你需要小心使用 componentDidMount,因为它可能导致不必要的重新渲染。

image.png

2、更新阶段

当组件的 props 或 state 改变时,组件会被重新渲染,此时就会进入到更新阶段。这个阶段会执行这几个钩子函数:static getDerivedFromPropsshouldComponentUpdaterendergetSnapshotBeforeUpdatecomponentDidUpdate

static getDerivedStateFromProps()

在更新阶段,第一个调用的生命周期方法是 getDerivedStateFromProps。在组件更新前被调用,和挂载阶段的作用相同,但是尽量不要在这个方法中执行副作用操作,因为这个方法会在每次更新时都被调用。

例如,一个组件的状态可能取决于其 props 的值。通过 getDerivedStateFromProps,在组件被重新渲染之前,它的 state 可以反映这些变化,并且可以显示在新更新的组件中。

shouldComponentUpdate()

shouldComponentUpdate 是专门用于性能优化的, 通常来说,只有 propsstate 变化时才需要再重新渲染。这个方法接受两个参数:nextProps 和 nextState,可以用于控制组件是否需要重新渲染,如果返回 false,组件将不会重新渲染,默认返回true。

注意,当调用 forceUpdate() 时,shouldComponentUpdate 方法被忽略。

image.png

render()

render() 方法会根据 最新的props和state 来重新渲染组件的UI,在挂载阶段已经说明,这里就不赘述了。

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate 方法让你可以访问组件更新前的 propsstate。这使你能够处理或检查 propsstate 的先前值。这是一个很少使用的方法。

例如,这个方法的一个很好的使用场景是处理 聊天APP 中的滚动位置。当用户在查看旧的信息时有一条新的信息进来,它不应该把旧的信息推到视野之外。

getSnapshotBeforeUpdate 在渲染方法之后,组件 DidUpdate 之前被调用。如果 getSnapshotBeforeUpdate 方法返回任何东西,它将被传递给 componentDidUpdate 方法作为参数:image.png

componentDidUpdate()

componentDidUpdate 方法是在更新阶段调用的最后一个生命周期方法。组件更新后被调用,可以用于处理 DOM的更新 或者 发起网络请求 等异步操作。

这个方法最多可以接受三个参数:prevPropsprevStatesnapshot(如果你调用了 getSnapshotBeforeUpdate 方法)。

下面是一个使用 componentDidUpdate 方法来实现自动保存功能的例子:

image.png

3、卸载阶段

当组件从DOM中移除时,就会进入到卸载阶段。卸载阶段只涉及一个生命周期方法:componentWillUnmount

componentWillUnmount()

组件被卸载时调用,可以用于清除定时器、取消网络请求等操作。一旦这个方法执行完毕,该组件将被销毁。

下面是一个使用 componentWillUnmount 的例子:

image.png

二、React Hooks 时代的生命周期

1、你以为的生命周期

React 16.8 之前的版本有两种组件:基于类的有状态组件和无状态的函数组件。随着 React 16.8 的发布,引入了 Hooks,使我们也能够在函数组件中操作状态。

大多数同学在学习 React Hooks 的时候应该都是如下图这样理解函数组件和类组价生命周期的对应关系,大多数文章也仅限于此了,不太会再深究。

image.png
具体的例子:

  • componentDidMountimage.png
    componentDidUpdateimage.png
    componentWillUnmountimage.png合在一起
    image.png
    2、模拟 Class Component 生命周期由于官方也没有对函数组件的生命周期做描述,这里我们就自己造点术语,以方便我们对齐类组件的生命周期。image.png
    顺便强调一下,函数组件实际上不存在所谓生命周期方法,因为在函数组件中没有这样的东西。另外,接下来的执行流程是基于 “非 StrictMode” 下的。挂载initializerendereffectimage.png
    更新initializerenderremoveEffecteffect
    image.png
    卸载removeEffectinitialize: 函数组件中没有构造函数。initialize 执行的就是初始化工作。render: 在浏览器中渲染 DOM 或者更新已经在 DOM 中渲染的数据。effect:执行一个副作用。它被定位为 componentDidMount 和 componentDidUpdate 的组合,但严格来说它不是。removeEffect:副作用被清理掉。定位像 componentWillUnmount,但严格来说不是。3、实际的 “生命周期”实际的”生命周期“之所以打引号,是因为,严格来说,React 的生命周期在类组件和函数组件中是不同的概念。官方在新的文档中也并没有函数组件生命周期的描述(我理解是官方想把大家的开发思维方式从原来的类组件切换到函数组件,嘴上说函数组件不是用来替代类组件的,但是身体却很诚实~ 哈哈哈)。把类组件的生命周期的概念强行应用到函数组件上,有点强迫症?但我觉得这是更好地理解 React 的很有效的方式。下面两幅图是国外大神基于 Dan Abramov's tweet 的灵感画出来的 React Hook LifeCycle:hook-flow作者:_你当像鸟飞往你的山链接:https://juejin.cn/post/7218942994467389498来源:稀土掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    image.pngreact-hooks-lifecycle
    react-hooks-lifecycle
    image.png
    基于上面两图的启示,绘制下图中这样一个比较容易理解的流程:image.png
    挂载正如上图中看到的,挂载阶段按照下面的顺序执行:首先 react 运行 (惰性初始化程序)第一次渲染React 更新 DOM运行 LayoutEffects浏览器绘制屏幕运行 Effects这里发生了什么? 首先是惰性初始化器,然后 React 进行第一次渲染并更新 DOM,然后 React 运行 LayoutEffects。下一个活动是浏览器屏幕绘制,最后 React 运行 Effects。更新在每次更新时,React都会从由 state 或 props 变化引起的重新渲染开始。现在就没有惰性的初始化调用了。renderReact 更新 DOM清除 LayoutEffects运行 LayoutEffects浏览器绘制屏幕清理 Effects运行 Effects注意,在渲染之后,React 清理了 LayoutEffects,使其紧接着运行。浏览器然后绘制屏幕,之后React清理 Effects 并紧接着运行它。挂载和更新之间的主要区别是:惰性初始化仅在挂载时挂载阶段不存在清理工作卸载在卸载期间,React 清理所有效果:清理 LayoutEffects清理 Effects验证为了证明上面的理论,我们可以看一个代码片段示例。在下面的代码中,我创建了父子组件。父组件有惰性初始化渲染开始日志渲染结束日志useEffects 日志useEffects 清理日志子组件有渲染开始日志渲染结束日志useEffects image.png
    你可以看到浏览器日志,是符合我们上面的流程的:image.png

    4、React Hooks 的执行时机接下来,我们来看这样一个例子:
    jfieh微信图片_20230613180842.png
    初次渲染的结果是:image.png
    搞清楚 React Hooks 的“生命周期” 和 各个 React Hooks 的执行时机之后,我们至少可以尝试使用 useRef、useState、useMemo 来模拟 componentWillMount 这个生命周期函数。三、实现 componentWillMount在类组件中,componentWillMount 被认为是 legacy 的(“遗留的”),就是要被干掉的。因为它可能会运行不止一次,而且有一个替代方法 —— constructor。从 React 16.9.0 开始,componentWillMount 被废弃, 适用 UNSAFE_componentWillMount 代替。

1、基于 useState 的实现image.png使用:image.png
2、基于 useMemo 实现

还可以使用 useMemo 来实现:

是因为 useMemo 不需要实际返回一个值,你也不需要实际使用它,但是因为它根据依赖关系缓存了一个值,而这个依赖关系只运行一次(在依赖为"[]"的情况下),而且当组件挂载时,它在其他东西之前运行一次。

但不建议这么做:这可能会使用当前实现,React文档特别说明不要这样操作。你应该将 useMemo 作为性能优化的工具,而不是作为语义保证。


image.png

3、基于 useRef 实现

useRef 是在函数组件初始渲染之前就会执行,而且它的值改变不会触发重渲染。image.png
甚至,我们还可以用来防止 useEffect 在挂载的时候执行:image.png

4、基于 useLayoutEffect 实现

useLayoutEffect 在第二个依赖值为空的情况下可以实现跟 componentWillMount 相似的作用。useLayoutEffect 会在第一次页面挂载之前运行第一个函数里的回调。虽然实际上有两个更新,但在绘制到屏幕之前它们是同步的。

image.png

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
78 9
|
2月前
|
存储 关系型数据库 MySQL
阿里面试:为什么要索引?什么是MySQL索引?底层结构是什么?
尼恩是一位资深架构师,他在自己的读者交流群中分享了关于MySQL索引的重要知识点。索引是帮助MySQL高效获取数据的数据结构,主要作用包括显著提升查询速度、降低磁盘I/O次数、优化排序与分组操作以及提升复杂查询的性能。MySQL支持多种索引类型,如主键索引、唯一索引、普通索引、全文索引和空间数据索引。索引的底层数据结构主要是B+树,它能够有效支持范围查询和顺序遍历,同时保持高效的插入、删除和查找性能。尼恩还强调了索引的优缺点,并提供了多个面试题及其解答,帮助读者在面试中脱颖而出。相关资料可在公众号【技术自由圈】获取。
|
1月前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
106 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
17天前
|
存储 NoSQL 架构师
阿里面试:聊聊 CAP 定理?哪些中间件是AP?为什么?
本文深入探讨了分布式系统中的“不可能三角”——CAP定理,即一致性(C)、可用性(A)和分区容错性(P)三者无法兼得。通过实例分析了不同场景下如何权衡CAP,并介绍了几种典型分布式中间件的CAP策略,强调了理解CAP定理对于架构设计的重要性。
47 4
|
1月前
|
存储 NoSQL 算法
阿里面试:亿级 redis 排行榜,如何设计?
本文由40岁老架构师尼恩撰写,针对近期读者在一线互联网企业面试中遇到的高频面试题进行系统化梳理,如使用ZSET排序统计、亿级用户排行榜设计等。文章详细介绍了Redis的四大统计(基数统计、二值统计、排序统计、聚合统计)原理和应用场景,重点讲解了Redis有序集合(Sorted Set)的使用方法和命令,以及如何设计社交点赞系统和游戏玩家排行榜。此外,还探讨了超高并发下Redis热key分治原理、亿级用户排行榜的范围分片设计、Redis Cluster集群持久化方式等内容。文章最后提供了大量面试真题和解决方案,帮助读者提升技术实力,顺利通过面试。
|
1月前
|
前端开发 JavaScript 开发者
使用React和Redux构建高效的前端应用
使用React和Redux构建高效的前端应用
36 1
|
1月前
|
SQL 关系型数据库 MySQL
阿里面试:1000万级大表, 如何 加索引?
45岁老架构师尼恩在其读者交流群中分享了如何在生产环境中给大表加索引的方法。文章详细介绍了两种索引构建方式:在线模式(Online DDL)和离线模式(Offline DDL),并深入探讨了 MySQL 5.6.7 之前的“影子策略”和 pt-online-schema-change 方案,以及 MySQL 5.6.7 之后的内部 Online DDL 特性。通过这些方法,可以有效地减少 DDL 操作对业务的影响,确保数据的一致性和完整性。尼恩还提供了大量面试题和解决方案,帮助读者在面试中充分展示技术实力。
|
1月前
|
前端开发 JavaScript Android开发
前端框架趋势:React Native在跨平台开发中的优势与挑战
【10月更文挑战第27天】React Native 是跨平台开发领域的佼佼者,凭借其独特的跨平台能力和高效的开发体验,成为许多开发者的首选。本文探讨了 React Native 的优势与挑战,包括跨平台开发能力、原生组件渲染、性能优化及调试复杂性等问题,并通过代码示例展示了其实际应用。
65 2
|
1月前
|
前端开发 JavaScript 开发者
React与Vue:前端框架的巅峰对决与选择策略
【10月更文挑战第23天】React与Vue:前端框架的巅峰对决与选择策略
|
1月前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
39 2