介绍目前常见前端面试题,React 基础原理题
参考慕课网实战课 前端框架及项目面试-聚焦 Vue3 React Webpack 总结的知识点
React vs Vue
- React 和 Vue 一样重要(特别是大厂面试),力求两者都学会
- React 和 Vue 有很多相通之处,而且正在趋于一致
- React 比 Vue 学习成本高,尤其对于初学者
React 使用
- 基本使用——常用,必须会
- 高级特性——不常用,但体现深度
- Redux 和 React-router 使用
自己去看文档不行吗?
- 行。但这种一种最低效的方式
- 文档是一个备忘录,给会用的人查阅,并不是入门教程
- 文档全面冗长且细节过多,不能突出面试考点
回顾之前的 React 面试题
- React 组件如何通讯
- JSX 本质是什么
context
是什么,有何用途?shouldComponentUpdate
的用途- 描述
redux
单项数据流 setState
是同步还是异步?(场景图,见下页)
针对上述题目
- 先自己思考,不着急解答
- 可能会涉及 React 原理(本来应用和原理就是分不开的)
- 这几道题是“开胃菜”,后面还有真题演练“大餐”
关于React 17
- 没有明显的新特性,和 React16 的使用是一致的
- 面试时也不会被重点考察
- 放心学习,需要注意的地方,视频中会重点提醒出来
React 基本使用
- 日常使用,必须掌握,面试必考(不一定全考)
- 梳理知识点,从冗长的文档中摘出考点和重点
- 考察形式不限(参考后面的面试真题),但都在范围之内
JSX 基本使用
- 变量、表达式
- class style
- 子元素和组件
代码示例用到 react-code-demo 项目的 BaseUse 组件的 JSXBaseDemo
条件判断
- if else
- 三元表达式
- 逻辑运算符 && ||
代码示例用到 react-code-demo 项目的 BaseUse 组件的 ConditionDemo
渲染列表
- map
- key
代码示例用到 react-code-demo 项目的 BaseUse 组件的 ListDemo
事件
- bind this
- 关于 event 参数
- 传递自定义参数
代码示例用到 react-code-demo 项目的 BaseUse 组件的 EventDemo
React 17 事件绑定到 root
- React 16 绑定到 document
- React 17 事件绑定到 root 组件
- 有利于多个 React 版本并存,例如微前端

表单
- 受控组件
- input textarea select 用 value
- checkbox radio 用 checked
代码示例用到 react-code-demo 项目的 BaseUse 组件的 FormDemo
组件使用
- props 传递数据
- props传递函数
- props类型检查
代码示例用到 react-code-demo 项目的 BaseUse 组件的 PropsDemo
进度:看到 7-8 React父子组件通讯 视频
React面试题
❤ React 生命周期的各个阶段
- 得分点:
constructor
、componentDidMount
、componentDidUpdate
、componentWillUnmount
、getDerivedStateFromProps
、shouldComponentUpdate
、getSnapshotBeforeUpdate
、componentWillUpdate
。 - 标准回答
- React的生命周期中常用的有:
constructor
,负责数据初始化。render
,将 jsx 转换成真实的dom节点。componentDidMount
,组件第一次渲染完成时触发。componentDidUpdate
,组件更新完成时触发。componentWillUnmount
,组件销毁和卸载时触发。
- 不常用的有:
getDerivedStateFromProps
,更新state和处理回调。shouldComponentUpdate
,用于性能优化。getSnapshotBeforeUpdate
,替代了componentWillUpdate
。
- React的生命周期中常用的有:
- 加分回答 React 的生命周期中有常用的和不常用的。
- 常用的有:
constructor()
: 完成了数据的初始化。注意:只要使用了constructor()就必须写super(),否则this指向会出错。 -render()
: render()函数会将jsx生成的dom插入到目标节点中。在每次组件更新时,react通过diff算法比较更新前和更新之后的dom节点,找到最小的有差异的dom位置并更新,花费最小的开销。componentDidMount()
: 组件第一次渲染完成,此时dom节点已经生成,在这里调用接口请求,返回数据后使用setState()更新数据后重新渲染。componentDidUpdate(prevProps,prevState)
: 组件更新完成。每次react重新渲染之后都会进入这个生命周期,可以拿到更新之前的props和state。componentWillUnmount()
: 在这个生命周期完成组件的数据销毁和卸载,移除所有的定时器和监听。
- 不常用的有:
getDerivedStateFromProps(nextProps,prevState)
: 代替老版的componentWillReceiveProps()。官方将更新state与触发回调重新分配到了componentWillReceiveProps()中,让组件整体的更新逻辑更加清晰,并且在当前生命周期中,禁止使用this.props,强制让开发者们通过比较nextProps和PrevState去保证数据的正确行为。shouldComponentUpdate()
: return true可以渲染,return false不重新渲染。为什么会出现这个SCU生命周期?主要用于性能优化。也是唯一可以控制组件渲染的生命周期,在setState之后state发生改变组件会重新渲染,在当前生命周期内return false会阻止组件的更新。因为react中父组件重新渲染导致子组件也重新渲染,这时在子组件的当前生命周期内做判断是否真的需要重新渲染。getSnapshotBeforeUpdate(prevProps, prevState)
: 代替componentWillUpdate(),核心区别在于getSnapshotBeforeUpdate()中读取到的dom元素状态是可以保证和componentDidUpdate()中的一致。
- 常用的有:
❤ React 组件间传值的方法
- 得分点 props、context、发布/订阅。
- 标准回答 React 中组件之间的传值方法有很多,按照不同的组件间关系可以把组件传值的方法分为父子组件传值,跨级组件传值和非嵌套关系组件传值。
- 父子组件常用的传值方法是当父组件给子组件传值时通过
props
,子组件向父组件传值通过回调函数来传值。 - 跨级组件常用的传值方法是
props
一层一层的传,或者使用context
对象,将父组件设置为生产者而子组件都设置对应的contextType
即可。 - 非嵌套关系组件传值的方法是使用共同的父级组件进行
props
传值,或者通过context
传值,推荐使用发布/订阅的自定义事件传值。
- 父子组件常用的传值方法是当父组件给子组件传值时通过
- 加分回答 React中组件间传值方法有很多,按照不同的组件间关系可以把组件间传值的方法分为:
- 父子组件传值:父子组件传值是最常见的应用场景也是非常简单的一种通信方式,父组件通过向子组件传递
props
,子组件得到props
之后做处理就行。而子组件向父组件传值需要通过回调函数触发,具体操作是父组件将一个函数作为props
的属性传递给子组件,子组件通过this.props.xxx()
调用父组件的函数,参数根据需要自己进行搭配即可。 - 跨级组件传值:跨级的组件之间传值无非就是重复多个父子组件传值的过程。一般跨级的传值方式有两种,分别是:
- 一层一层的传递
props
:写法很复杂,耦合程度也很高,如果两个组件之间隔了很多层,那么也会影响中间组件的性能,开销大。不过这种方法也是可以的,如果组件之间的层级不是非常多,可以考虑使用这个方法。
- 一层一层的传递
- context对象:
context
相当于一个全局的变量,是一个大的容器,可以把需要传递的数据放在这个容器中,不论嵌套多深都可以轻易的使用。具体操作是创建一个context
对象,需要输入默认值。在父组件中使用生产者标签将需要传值的所有子组件包裹起来。子组件通过指定contextType
获取到这个context
对象,直接调用this.context即可获得值。 - 非嵌套关系组件传值:就是没有任何包含关系的组件之间的传值,包括了兄弟组件。对于肺嵌套关系组件传值一般用两种方法: - 通过相同的父组件传值:子组件通过回调函数的形式将数据传给父组件,父组件直接通过属性将数据传递给子组件。
- context:利用共同的父组件
context
对象进行传值 - 通过发布/订阅进行传递:也可以说是自定义事件。重点是在子组件的
componentDidMount
生命周期通过声明一个自定义事件,然后在componentWillUnmount
生命周期组件销毁时移除自定义事件。
- 父子组件传值:父子组件传值是最常见的应用场景也是非常简单的一种通信方式,父组件通过向子组件传递
❤ React 事件绑定原理
- 得分点 非原生事件、SyntheticBaseEvent
- 标准回答
- React中event事件不是原生事件,而是对原生event进行了封装的新类SyntheticBaseEvent,模拟出DOM事件的所有功能,通过
event.nativeEvent
可以获取到原生事件。 - React17版本开始所有事件都绑定在root根组件上,之前都是绑定在document上。
- React中event和DOM事件不一样,和Vue也不一样。
- React中event事件不是原生事件,而是对原生event进行了封装的新类SyntheticBaseEvent,模拟出DOM事件的所有功能,通过
- 加分回答 React并不是将click事件绑在该div的真实DOM上,而是在root处监听所有支持的事件,当事件发生并冒泡至root处时,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。另外冒泡到 root上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticBaseEvent)。因此我们如果不想要事件冒泡的话,调用
event.stopPropagation
是无效的,而应该调用event.preventDefault
。
❤ ReactRouter 基本用法
- 得分点 路由的模式有两种、hash模式、history模式、路由的动态传参、重定向、高阶路由组件。
- 标准回答 react的路由保证了界面和URL的同步,拥有简单的API和强大的功能。react中的路由模式有两种,分别是:
hash
路由和history
路由。- 首先用析构的方法引入需要用到的路由方式,需要注意的是路由所有的配置都必须被包裹在hash路由或者history路由里面。
- 然后在路由标签内先再配置Route标签,它的参数有:path,路由匹配的路径。component,路由匹配成功之后渲染的组件。
- react中路由的跳转使用Link标签,它的参数to指路由匹配的路径,也需要引入。NavLink标签和Link的区别就是渲染成a标签之后会自带一个class属性,对应的是NavLink标签的active属性。
- react路由中有高阶路由组件
withRouter
,它和普通路由一样需要引入,主要作用是增加了路由跳转的方式,可以调用history
方法进行函数中路由的跳转。 - react中路由的动态传值是一个重点,{/:属性名}和{/属性名/值}搭配的方式进行传值,在需要接收参数的组件通过
this.props.match.params
来进行接收。react中路由的query传值是通过问号的方法将参数拼接在url之后,在需要接收参数的组件通过url.parse(this.props.location.search).query
获取参数。 - 路由的重定向需要用组件
Redirect
来完成,参数to
是目标组件。 - 路由的懒加载需要从react中引入
Suspense
和lazy
,引入组件时通过lazy(() => import())
来引入,使用Suspense
标签将Route
包裹起来即可。
- 加分回答
- react中路由模式分为
hash
路由和history
路由。- hash路由的原理是
window.onhashchange
监听,url前面会有一个#号,这个就是锚点,每个路由前面都会有 # 锚点,特点是 # 之后的数据改变不会发起请求,因此改变 hash 不会重新加载页面。 - history路由的原理是
window.history API
,在HTML5中新增了pushState
和replaceState
方法,这两个方法是在浏览器的历史记录栈上做文章,提供了对历史记录做修改的功能,虽然更改了url但是不会向服务器发起请求。history模式虽然去掉了hash模式的#锚点,但是它怕刷新,因为刷新时是真实的请求。而hash模式下,修改的是#锚点之后的信息,浏览器不会将#锚点之后的数据发送到服务器,所以没有问题。
- hash路由的原理是
- react中路由模式分为
❤ setState 是同步还是异步的
- 得分点 是同步也是异步、合成事件、生命周期函数、原生事件、定时器
- 标准回答 setState在合成事件和生命周期函数中是异步的,在原生事件和定时器中都是同步的。
- 加分回答
setState
本身不分同步或者异步,而是取决于是否处于batch update
中。组件中的所有函数在执行时临时设置一个变量isBatchingUpdates = true
,当遇到setState
时,如果isBatchingUpdates
是true
,那么setState
就是异步的,如果是false
,那么setState
就是同步的。那么什么时候isBatchingUpdates
会被设置成false呢?- 当函数运行结束时
isBatchingUpdates = false
- 当函数遇到
setTimeout
、setInterval
时isBatchingUpdates = false
- 当dom添加自定义事件时
isBatchingUpdates = false
❤ React 中 hooks 的优缺点
- 得分点 可读性强、组件层级变得更浅、不再需要考虑class组件中this指向、部分生命周期不支持
- 标准回答
- 优点:
- 代码的可读性强,在使用
hooks
之前比如发布/订阅自定义事件被挂载在componentDidMount
生命周期中,然后需要在componentWillUnmount
生命周期中将它清楚,这样就不便于开发者维护和迭代。在使用hooks
之后,通过useEffect
可以将componentDidMount
生命周期、componentDidUpdate
生命周期和componentWillUnmount
生命周期聚合起来,方便代码的维护。 - 组件层级变得更浅了,在使用hooks之前通常使用高阶组件HOC的方法来复用多个组件公共的状态,增强组建的功能,这样肯定是加大了组件渲染的开销,损失了性能。但是在hooks中可以通过自定义组件
useXxx()
的方法将多个组件之间的共享逻辑放在自定义hook中,就可以轻松的进行数据互通。 - 不再需要考虑class组件中this指向的问题,hook在函数组件中不需要通过
this.state
或者this.fn
来获取数据或者方法。
- 代码的可读性强,在使用
- 缺点:hooks的useEffect只包括了
componentDidMount
、componentDidUpdate
还有componentWillUnmount
这三个生命周期,对于getSnapshotBeforeUpdate
和componentDidCatch
等其他的生命周期没有支持。
- 优点:
- 加分回答 使用useEffect时候里面不能写太多依赖项,将各个不同的功能划分为多个useEffect模块,将各项功能拆开写,这是遵循了软件设计的“单一职责模式”。如果遇到状态不同步的情况,使用手动传递参数的形式。如果业务复杂,就使用Component代替hooks,hooks的出现并不是取代了class组件,而是在函数组件的基础上可以实现一部分的类似class组件功能。