前端面试,备考第 20 天—— React 基础,再进一步
Posted 前端修罗场
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端面试,备考第 20 天—— React 基础,再进一步相关的知识,希望对你有一定的参考价值。
- ⭐️ 本文首发自 前端修罗场(点击加入社区,参与学习打卡,获取奖励),是一个由资深开发者独立运行的专业技术社区,我专注 Web 技术、答疑解惑、面试辅导以及职业发展。。
- 🏆 目前就职于全球前100强知名外企,曾就职于头部互联网企业 | 清华大学出版社签约作者 | CSDN 银牌讲师 | 蓝桥云课2021年度人气作者Top2 | CSDN 博客专家 | 阿里云专家博主 | 华为云享专家
出品著作:《ElementUI 详解与实战》| 《ThreeJS 在网页中创建动画》|《PWA 渐进式Web应用开发》- 🔥 本文已收录至前端面试题库专栏: 《前端面试宝典》(点击订阅)
- 💯 此专栏文章针对准备找工作的应届生、初中高级前端工程师设计,以及想要巩固前端基础知识的开发者,文章中包含 90% 的面试考点和实际开发中需要掌握的知识,内容按点分类,环环相扣,重要的是,形成了前端知识技能树,多数同学已经通过面试拿到 offer,并提升了自己的前端知识面。
作者对重点考题做了详细解析和重点标注(建议在 PC 上阅读
),并通过图解、思维导图的方式帮你降低了学习成本,节省备考时间,尽可能快地提升。可以说目前市面上没有像这样完善的面试备考指南!- ❤️ 现在订阅专栏,私聊博主,即可享受一次免费的模拟面试、简历修改、答疑服务。拉你进前端答疑互助交流群,享受博主答疑服务和备考服务,优质文章分享。【私聊备注:前端修罗场】。
- 🚀 加入前端修罗场,从此快人一步,和一群人一起更进一步!
- 👉🏻 目前优惠中,即将恢复至原价 ~
紧接上一篇 React 基础一点通,本文将进一步讲解 React 基础与核心知识。
1. 对有状态组件和无状态组件的理解及使用场景
(1)有状态组件
特点:
- 是类组件
- 有继承
- 可以使用 this
- 可以使用 react 的生命周期
- 使用较多,容易频繁触发生命周期钩子函数,影响性能
- 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state 进行渲染。
使用场景:
- 需要使用到状态的。
- 需要使用状态操作组件的(无状态组件的也可以实现新版本 react hooks 也可实现)
总结:
类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。 当一个类组件不需要管理自身状态时,也可称为无状态组件。
(2)无状态组件
特点:
- 不依赖自身的状态 state
- 可以是类组件或者函数组件。
- 可以完全避免使用 this 关键字。(由于使用的是箭头函数事件无需绑定)
- 有更高的性能。当不需要使用生命周期钩子时,应该首先使用无状态函数组件
- 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。
使用场景:
- 组件不需要管理 state,纯展示
优点:
- 简化代码、专注于 render
- 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用
- 视图和数据的解耦分离
缺点:
- 无法使用 ref
- 无生命周期方法
- 无法控制组件的重渲染,因为无法使用
shouldComponentUpdate
方法,当组件接受到新的属性时则会重渲染
总结:
组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。 当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。比如自定义的 <Button/>
、 <Input />
等组件。
2. 对 React 中 Fragment 的理解,它的使用场景是什么?
在 React 中,组件返回的元素只能有一个根元素。为了不添加多余的 DOM 节点,我们可以使用 Fragment 标签来包裹所有的元素,Fragment 标签不会渲染出任何元素。React官方对 Fragment 的解释:
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
理解:它不会渲染任何元素。
场景:返回多个子组件,可以用它包裹。
import React, Component, Fragment from 'react'
// 一般形式
render()
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
// 也可以写成以下形式
render()
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
3. React 如何获取组件对应的 DOM 元素?
可以用 ref
来获取某个子节点的实例,然后通过当前 class 组件实例的一些特定属性来直接获取子节点实例。ref 有三种实现方法:
- 字符串格式:字符串格式,这是 React16 版本之前用得最多的,例如:
<p ref="info">span</p>
- 函数格式:ref 对应一个方法,该方法有一个参数,也就是对应的节点实例,例如:
<p ref=ele => this.info = ele></p>
- createRef方法:React 16 提供的一个API,使用
React.createRef()
来实现
4. React中可以在 render 访问 refs 吗(不可以)?为什么?
<>
<span id="name" ref=this.spanRef>this.state.title</span>
<span>
this.spanRef.current ? '有值' : '无值'
</span>
</>
不可以,render 阶段 DOM 还没有生成,无法获取 DOM。DOM 的获取需要在 pre-commit
阶段和 commit
阶段:
5. 对 React 的插槽(Portals)的理解,如何使用,有哪些使用场景(模态框)
React 官方对 Portals
的定义:
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
场景:对话框、模态框
Portals 是React 16提供的官方解决方案,使得组件可以脱离父组件层级挂载在DOM树的任何位置。通俗来讲,就是我们** render 一个组件,但这个组件的 DOM 结构并不在本组件内。**
Portals语法如下:
ReactDOM.createPortal(child, container);
- 第一个参数 child 是可渲染的 React 子项,比如元素,字符串或者片段等;
- 第二个参数 container 是一个 DOM 元素。
一般情况下,组件的render函数返回的元素会被挂载在它的父级组件上:
import DemoComponent from './DemoComponent';
render()
// DemoComponent元素会被挂载在id为parent的div的元素上
return (
<div id="parent">
<DemoComponent />
</div>
);
然而,有些元素需要被挂载在更高层级的位置。最典型的应用场景:当父组件具有 overflow: hidden
或者 z-index
的样式设置时,组件有可能被其他元素遮挡,这时就可以考虑要不要使用 Portal 使组件的挂载脱离父组件。例如:对话框,模态窗。
import DemoComponent from './DemoComponent';
render()
// react会将DemoComponent组件直接挂载在真实的 dom 节点 domNode 上,生命周期还和16版本之前相同。
return ReactDOM.createPortal(
<DemoComponent />,
domNode,
);
6. 在React中如何避免不必要的 render ?
React 基于虚拟 DOM 和高效 Diff 算法的完美配合,实现了对 DOM 最小粒度的更新。大多数情况下,React 对 DOM 的渲染效率足以业务日常。但在个别复杂业务场景下,性能问题依然会困扰我们。此时需要采取一些措施来提升运行性能,其很重要的一个方向,就是避免不必要的渲染(Render)。这里提下优化的点:
- shouldComponentUpdate 和 PureComponent
在 React 类组件中,可以利用 shouldComponentUpdate
或者 PureComponent
来减少因父组件更新而触发子组件的 render,从而达到目的。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false
即可。
- 利用高阶组件
在函数组件中,并没有 shouldComponentUpdate 这个生命周期,可以利用高阶组件,封装一个类似 PureComponet 的功能
- 使用 React.memo
React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent 十分类似,但不同的是,React.memo只能用于函数组件。
7. 对 React-Intl 的理解,它的工作原理?
React-intl 是雅虎的语言国际化开源项目 FormatJS 的一部分,通过其提供的组件和 API 可以与 ReactJS 绑定。
React-intl 提供了两种使用方法,一种是引用 React 组件,另一种是直接调取 API,官方更加推荐在 React 项目中使用前者,只有在无法使用 React 组件的地方,才应该调用框架提供的 API。它提供了一系列的 React 组件,包括数字格式化、字符串格式化、日期格式化等。
在 React-intl 中,可以配置不同的语言包,他的工作原理就是根据需要,在语言包之间进行切换。
8. 对 React context 的理解
作用域链原理。
关于 Context 的定义, React 官方也没有确定给出;我认为 Context 可以看做子组件到父组件上的作用域链。该对象上存储着所有组件的 Context 对象,可以通过 getChildContext()
获取。
在React中,数据传递一般使用 props 传递数据,维持单向数据流,这样可以让组件之间的关系变得简单且可预测,但是单项数据流在某些场景中并不适用。单纯一对的父子组件传递并无问题,但要是组件之间层层依赖深入,props 就需要层层传递。显然,这样做太繁琐了。
官方解释:Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
可以把context 当做是特定一个组件树内共享的 store,用来做数据传递。简单说就是,当你不想在组件树中通过逐层传递props 或者 state 的方式来传递数据时,可以使用 Context 来实现跨层级的组件数据传递。
JS 的代码块在执行期间,会创建一个相应的作用域链,这个作用域链记录着运行时 JS 代码块执行期间所能访问的活动对象,包括变量和函数,JS 程序通过作用域链访问到代码块内部或者外部的变量和函数。
假如以 JS 的作用域链作为类比,React 组件提供的 Context 对象其实就好比一个提供给子组件访问的作用域,而 Context 对象的属性可以看成作用域上的活动对象。由于组件的 Context 由其父节点链上_所有组件通过 __getChildContext()
返回的Context 对象组合而成,所以,组件通过 Context 是可以访问到其父组件链上__所有节点组件提供的 __Context
的属性_。
9. 为什么 React 并不推荐优先考虑使用 Context ?
- 尽管不建议在 app 中使用 context,但是独有组件而言,由于影响范围小于 app,如果可以做到高内聚,不破坏组件树之间的依赖关系,可以考虑使用 context
- 对于组件之间的数据通信或者状态管理,优先考虑使用 props 或者 state 解决,然后再考虑使用第三方的成熟库进行解决,以上的方法都不是最佳的方案的时候,在考虑 context。
- 如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
- context 的更新需要通过
setState()
触发,但是这并不是很可靠的。Context 支持跨组件的访问,但是如果中间的子组件通过一些方法不影响更新,比如 shouldComponentUpdate() 返回 false 那么不能保证 Context 的更新一定可以使用 Context 的子组件,因此,Context 的可靠性需要关注
简而言之,只要你能确保 Context 是可控的,使用 Context 并无大碍,甚至如果能够合理的应用,Context 其实可以给 React 组件开发带来很强大的体验。
10. React 中什么是受控组件和非控组件?
(1)受控组件:组件的状态可通过 state 控制,且只能通过 setState() 来更新
在使用表单来收集用户输入时,例如等元素都要绑定一个 change 事件,当表单的状态发生变化,就会触发 onChange 事件,更新组件的 state。这种组件在React中被称为受控组件,在受控组件中,组件渲染出的状态与它的 value 或 checked 属性相对应,react 通过这种方式消除了组件的局部状态,使整个状态可控。 react 官方推荐使用受控表单组件。
受控组件更新 state 的流程:
- 可以通过初始 state 中设置表单的默认值
- 每当表单的值发生变化时,调用
onChange
事件处理器 - 事件处理器通过事件对象
e
拿到改变后的状态,并更新组件的state
- 一旦通过 setState 方法更新 state,就会触发视图的重新渲染,完成表单组件的更新
受控组件缺陷:
表单元素的值都是由React组件进行管理,当有多个输入框,或者多个这种组件时,如果想同时获取到全部的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种情况,出现了非受控组件。
(2)非受控组件:没有 state 值,获取组件的值要通过 ref
。
如果一个表单组件没有 value props
(单选和复选按钮对应的是 checked props
)时,就可以称为非受控组件。在非受控组件中,可以使用一个 ref 来从 DOM 获得表单值。而不是为每个状态更新编写一个事件处理程序。
React官方的解释:
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以使用 ref 来从 DOM 节点中获取表单数据。
因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
例如,下面的代码在非受控组件中接收单个属性:
class NameForm extends React.Component
constructor(props)
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
handleSubmit(event)
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
render()
return (
<form onSubmit=this.handleSubmit>
<label>
Name:
<input type="text" ref=(input) => this.input = input />
</label>
<input type="submit" value="Submit" />
</form>
);
总结:页面中所有输入类的 DOM 如果是现用现取的称为非受控组件,而通过 setState 将输入的值维护到了 state 中,需要时再从 state 中取出,这里的数据就受到了 state 的控制,称为受控组件。
11. React 中 refs 的作用是什么?有哪些应用场景?
Refs 提供了一种方式,用于访问在 render 方法中创建的 React 元素或 DOM 节点。Refs 应该谨慎使用,如下场景使用 Refs 比较适合:
- 处理焦点、文本选择或者媒体的控制
- 触发必要的动画
- 集成第三方 DOM 库
Refs 是使用 React.createRef()
方法创建的,他通过 ref
属性附加到 React 元素上。要在整个组件中使用 Refs,需要将 ref
在构造函数中分配给其实例属性:
class MyComponent extends React.Component
constructor(props)
super(props)
this.myRef = React.createRef()
render()
return <div ref=this.myRef />
由于函数组件没有实例,因此不能在函数组件上直接使用 ref
:
function MyFunctionalComponent()
return <input />;
class Parent extends React.Component
constructor(props)
super(props);
this.textInput = React.createRef();
render()
// 这将不会工作!
return (
<MyFunctionalComponent ref=this.textInput />
);
但可以通过闭合的帮助在函数组件内部进行使用 Refs:
function CustomTextInput(props)
// 这里必须声明 textInput,这样 ref 回调才可以引用它
let textInput = null;
function handleClick()
textInput.focus();
return (
<div>
<input
type="text"
ref=(input) => textInput = input; />
<input
type="button"
value="Focus the text input"
onClick=handleClick
/>
</div>
);
注意:
- 不应该过度的使用 Refs
ref
的返回值取决于节点的类型:- 当
ref
属性被用于一个普通的 html 元素时,React.createRef()
将接收底层 DOM 元素作为他的current
属性以创建ref
。 - 当
ref
属性被用于一个自定义的类组件时,ref
对象将接收该组件已挂载的实例作为他的current
。
- 当
- 当在父组件中需要访问子组件中的
ref
时可使用传递 Refs 或回调 Refs。
12. React 中除了在构造函数中绑定 this,还有别的方式吗?
- 在构造函数中绑定 this (
super(props)
方式)
constructor(props)
super(props);
this.state=
msg:'hello world',
this.getMsg = this.getMsg.bind(this)
- 函数定义的时候使用箭头函数
constructor(props)
super(props);
this.state=
msg:'hello world',
render()
<button onClcik=()=>alert(this.state.msg)>点我</button>
- 函数调用是使用 bind 绑定 this
<button onClick=this.getMsg.bind(this)>点我</button>
13. React 组件的构造函数有什么作用?它是必须的吗?
构造函数主要用于两个目的:
- 通过将对象分配给
this.state
来初始化本地状态 - 将事件处理程序方法绑定到实例上
所以,当在 React class 中需要设置 state 的初始值或者绑定事件时,需要加上构造函数,官方 Demo:
class LikeButton extends React.Component
constructor()
super();
this.state =
liked: false
;
this.handleClick = this.handleClick.bind(this);
handleClick()
this.setState(liked: !this.state.liked);
render()
const text = this.state.liked ? 'liked' : 'haven\\'t liked';
return (
<div onClick=this.handleClick>
You text this. Click to toggle.
</div>
);
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);
构造函数用来新建父类的 this 对象;子类必须在 constructor
方法中调用 super
方法;否则新建实例时会报错;因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法;子类就得不到 this 对象。
注意:
- constructor() 必须配上
super()
, 如果要在constructor 内部使用this.props
就要 传入 props , 否则不用 - javascript 中的 bind 每次都会返回一个新的函数, 为了性能等考虑, 尽量在 constructor 中绑定事件
14. React.forwardRef 是什么?它有什么作用?
React.forwardRef
会创建一个 React 组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:
- 转发 refs 到 DOM 组件
- 在高阶组件中转发 refs
15. 类组件与函数组件有什么异同?
相同点:
组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终呈现效果上都是完全一致的。
我们甚至可以将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(虽然并不推荐这种重构行为)。从使用者的角度而言,很难从使用体验上区分两者,而且在现代浏览器中,闭包和类的性能只在极端场景下才会有明显的差别。所以,基本可认为两者作为组件是完全一致的。
不同点:
- 它们在开发时的心智模型上却存在巨大的差异。类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是
immutable
、没有副作用、引用透明等特点。 - 之前,在使用场景上,如果存在需要使用生命周期的组件,那么主推类组件;设计模式上,如果需要使用继承,那么主推类组件。但现在由于 React Hooks 的推出,生命周期概念的淡出,函数组件可以完全取代类组件。其次继承并不是组件最佳的设计模式,官方更推崇“组合优于继承”的设计概念,所以类组件在这方面的优势也在淡出。
- 性能优化上,类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能。
- 从上手程度而言,类组件更容易上手,从未来趋势上看,由于React Hooks 的推出,函数组件成了社区未来主推的方案。
- 类组件在未来时间切片与并发模式中,由于生命周期带来的复杂度,并不易于优化。而函数组件本身轻量简单,且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的未来发展。
以上是关于前端面试,备考第 20 天—— React 基础,再进一步的主要内容,如果未能解决你的问题,请参考以下文章
前端面试,备考第 21 天—— React 怎么做数据管理,原理是什么
前端面试,备考第 13 天 - 执行上下文 | 作用域链 | 闭包
前端面试,备考第 15 天 - 异步编程:Promise | Async/Await | 定时器 | 扩展