面试题 97. React 两种路由模式的区别?hash和history?
参考回答:
1: hash路由 hash模式是通过改变锚点(#)来更新页面URL,并不会触发页面重新加载,我们可以通过window.onhashchange监听到hash的改变,从而处理路由hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
2: history路由 history模式是通过调用window.history对象上go、back、forward去操作浏览器的历史记录栈来实现页面的无刷新跳转。
hash 模式:
# 后⾯ hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新⻚
⾯。同时通过监听 hashchange 事件可以知道 hash 发⽣了哪些变化,然后根据 hash 变化来实现
更新⻚⾯部分内容的操作。
history 模式:
history 模式的实现,主要是 HTML5 标准发布的两个 API, pushState 和 replaceState ,这
两个 API 可以在改变 url,但是不会发送请求。这样就可以监听 url 变化来实现更新⻚⾯部分内容
的操作。
面试题 98. 解释useEffect的第二个参数传不同值的区别?
参考回答:
useEffect 拥有两个参数,第一个参数作为回调函数会在浏览器布局和绘制完成后调用,因此它不会阻碍浏览器的渲染进程。第二个参数是一个数组:
1 当数组存在并有值时,如果数组中的任何值发生更改,则每次渲染后都会触发回调。
2 当它不存在时,每次渲染后都会触发回调。
3 当它是一个空列表时,回调只会被触发一次,类似于componentDidMount
面试题 99. 简述reducer是纯函数吗?说明其原因
参考回答:
reducer必须是一个纯函数,Redux只通过比较新旧两个对象的存储位置来比较新旧两个对象是否相同。如果你在reducer内部直接修改旧的state对象的属性值,那么新的state和旧的state将都指向同一个对象。因此Redux认为没有任何改变,返回的state将为旧的state
面试题 100. 简述对 Redux 中间件的理解?常用的中间件有哪些?实现原理?
参考回答:
中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的
当action发出之后,reducer立即算出state,整个过程是一个同步的操作,那么如果需要支持异步操作,或者支持错误处理、日志监控,这个过程就可以用上中间件
Redux中,中间件就是放在就是在dispatch过程,在分发action进行拦截处理其本质上一个函数,对store.dispatch方法进行了改造,在发出 Action和执行 Reducer这两步之间,添加了其他功能
有很多优秀的redux中间件,如:
redux-thunk:用于异步操作
redux-logger:用于日志记录
面试题 101. 简述componentWillReceiveProps的调用时机?
参考回答:
componentWillReceiveProps这个生命周期在16.3之后已经被废弃,在组件接受到一个新的props时被调用,当组件初始化render的时候不会被调用
面试题 102. 执行两次setState的时候会render几次?会不会立即触发?
参考回答:
只执行一次,不会立即触发,因为react中有批处理机制,React会把setState的调用合并为一个来执行,也就是说,当执行setState的时候,state中的数据并不会马上更新,会按照先进先出,按顺序进行执行,但是在 Ajax、setTimeout 等异步方法中,每 setState 一次,就会 re-render 一次
面试题 103. 简述React.memo()和React.PureComponent组件异同 ?
参考回答:
异:React.memo()是函数组件,React.PureComponent是类组件。
同:都是对接收的props参数进行浅比较,解决组件在运行时的效率问题,优化组件的重渲染行为。
useMemo()是定义一段函数逻辑是否重复执行。
若第二个参数为空数组,则只会在渲染组件时执行一次,传入的属性值的更新也不会有作用。 所以useMemo()的第二个参数,数组中需要传入依赖的参数。
面试题 104. React 什么是 Reselect 以及它是如何工作的 ?
参考回答:
*Reselect*是一个**选择器库**(用于 Redux ),它使用*memoization*概念。它最初编写用于计算类似 Redux 的应用程序状态的派生数据,但它不能绑定到任何体系结构或库。
Reselect 保留最后一次调用的最后输入/输出的副本,并仅在其中一个输入发生更改时重新计算结果。如果连续两次提供相同的输入,则 Reselect 将返回缓存的输出。它的 memoization 和缓存是完全可定制的
面试题 105. 在React中如何防范XSS攻击?
参考回答:
dangerouslySetInnerHTML
dangerouslySetInnerHTML 是 React 解析含有 HTML 标记内容的一种方式,也是原生 DOM 元素 innerHTML 的替代方案
面试题 106. 简述点(…)在 React 的作用 ?
参考回答:
扩展运算符或者叫展开操作符,对于创建具有现有对象的大多数属性的新对象非常方便,在更新state时经常这么用
面试题 107. 简述什么是prop drilling,如何避免?
参考回答:
在构建 React 应用程序时,在多层嵌套组件来使用另一个嵌套组件提供的数据。最简单的方法是将一个 prop 从每个组件一层层的传递下去,从源组件传递到深层嵌套组件,这叫做prop drilling。
prop drilling的主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护。
为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过Consumer组件或useContext Hook 使用上下文数据
面试题 108. 简述什么是 React Fiber?
参考回答:
Fiber 是 React 16 中新的协调引擎或重新实现核心算法。它的主要目标是支持虚拟DOM的增量渲染。React Fiber 的目标是提高其在动画、布局、手势、暂停、中止或重用等方面的适用性,并为不同类型的更新分配优先级,以及新的并发原语。
React Fiber 的目标是增强其在动画、布局和手势等领域的适用性。它的主要特性是增量渲染:能够将渲染工作分割成块,并将其分散到多个帧中
面试题 109. 如何在 React 的 Props上应用验证?
参考回答:
当应用程序在开发模式下运行时,React 将自动检查咱们在组件上设置的所有 props,以确保它们具有正确的数据类型。对于不正确的类型,开发模式下会在控制台中生成警告消息,而在生产模式中由于性能影响而禁用它。强制的 props 用 isRequired定义的。
下面是一组预定义的 prop 类型:
React.PropTypes.string
React.PropTypes.number
React.PropTypes.func
React.PropTypes.node
React.PropTypes.bool
面试题 110. React 中使用构造函数和 getInitialState 有什么区别?
参考回答:
构造函数和getInitialState之间的区别就是ES6和ES5本身的区别。在使用ES6类时,应该在构造函数中初始化state,并在使用React.createClass时定义getInitialState方法
面试题 111. 解释Hooks会取代 render props 和高阶组件吗?
参考回答:
render props和高阶组件仅渲染一个子组件。React团队认为,Hooks 是服务此用例的更简单方法。
这两种模式仍然有一席之地(例如,一个虚拟的 scroller 组件可能有一个 renderItem prop,或者一个可视化的容器组件可能有它自己的 DOM 结构)。但在大多数情况下,Hooks 就足够了,可以帮助减少树中的嵌套
面试题 112. 如何避免React 组件的重新渲染?
参考回答:
React 中最常见的问题之一是组件不必要地重新渲染。React 提供了两个方法,在这些情况下非常有用:
React.memo():这可以防止不必要地重新渲染函数组件
PureComponent:这可以防止不必要地重新渲染类组件
这两种方法都依赖于对传递给组件的props的浅比较,如果 props 没有改变,那么组件将不会重新渲染。虽然这两种工具都非常有用,但是浅比较会带来额外的性能损失,因此如果使用不当,这两种方法都会对性能产生负面影响。
通过使用 React Profiler,可以在使用这些方法前后对性能进行测量,从而确保通过进行给定的更改来实际改进性能。
面试题 113. 请简述当调用setState时,React render 是如何工作的?
参考回答:
1 虚拟 DOM 渲染:当render方法被调用时,它返回一个新的组件的虚拟 DOM 结构。当调用setState()时,render会被再次调用,因为默认情况下shouldComponentUpdate总是返回true,所以默认情况下 React 是没有优化的。
2 原生 DOM 渲染:React 只会在虚拟DOM中修改真实DOM节点,而且修改的次数非常少——这是很棒的React特性,它优化了真实DOM的变化,使React变得更快
面试题 114. 解释如何避免在React重新绑定实例?
参考回答:
1.将事件处理程序定义为内联箭头函数
class SubmitButton extends React.Component {
constructor(props) {
super(props);
this.state = {
isFormSubmitted: false
}
;
}
render() {
return (
() => {
this.setState( {
isFormSubmitted: true
}
);
}
}
>Submit
)
}
}
2.使用箭头函数来定义方法:
class SubmitButton extends React.Component {
state = {
isFormSubmitted: false
}
handleSubmit = () => {
this.setState( {
isFormSubmitted: true
}
);
}
render() {
return (
this.handleSubmit
}
>Submit
)
}
}
3.使用带有 Hooks 的函数组件
const SubmitButton = () => {
const [isFormSubmitted, setIsFormSubmitted] = useState(false);
return (
() => {
setIsFormSubmitted(true);
}
}
>Submit
)
}
;
面试题 115. 简述React 中的 useState() 是什么?
参考回答:
useState 是一个内置的 React Hook。useState(0) 返回一个元组,其中第一个参数count是计数器的当前状态,setCounter 提供更新计数器状态的方法。
咱们可以在任何地方使用setCounter方法更新计数状态-在这种情况下,咱们在setCount函数内部使用它可以做更多的事情,使用 Hooks,能够使咱们的代码保持更多功能,还可以避免过多使用基于类的组件
定义state的数据,参数是初始化的数据,返回值两个值1. 初始化值,2. 修改的方法
useState中修改的方法异步
借助于useEffect 进行数据的监听
可以自己定义Hooks的方法, 方法内部可以把逻辑返回
面试题 116. Component, Element, Instance 之间有什么区别和联系?
参考回答:
**元素:**一个元素element是一个普通对象(plain object),描述了对于一个DOM节点或者其他组件component,你想让它在屏幕上呈现成什么样子。元素element可以在它的属性props中包含其他元素(译注:用于形成元素树)。创建一个React元素element成本很低。元素element创建之后是不可变的。
**组件:**一个组件component可以通过多种方式声明。可以是带有一个render()方法的类,简单点也可以定义为一个函数。这两种情况下,它都把属性props作为输入,把返回的一棵元素树作为输出。
**实例:**一个实例instance是你在所写的组件类component class中使用关键字this所指向的东西(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。
函数式组件(Functional component)根本没有实例instance。类组件(Class component)有实例instance,但是永远也不需要直接创建一个组件的实例,因为React帮我们做了这些。
面试题 117. 简述React.createClass和extends Component的区别有哪些?
参考回答:
React.createClass和extends Component的bai区别主要在于:
(1)语法区别
createClass本质上是一个工厂函数,extends的方式更加接近最新的ES6规范的class写法。两种方式在语法上的差别主要体现在方法的定义和静态属性的声明上。
createClass方式的方法定义使用逗号,隔开,因为creatClass本质上是一个函数,传递给它的是一个Object;而class的方式定义方法时务必谨记不要使用逗号隔开,这是ES6 class的语法规范。
(2)propType 和 getDefaultProps
React.createClass:通过proTypes对象和getDefaultProps()方法来设置和获取props.
React.Component:通过设置两个属性propTypes和defaultProps
(3)状态的区别
React.createClass:通过getInitialState()方法返回一个包含初始值的对象
React.Component:通过constructor设置初始状态
(4)this区别
React.createClass:会正确绑定this
React.Component:由于使用了 ES6,这里会有些微不同,属性并不会自动绑定到 React 类的实例上。
(5)Mixins
React.createClass:使用 React.createClass 的话,可以在创建组件时添加一个叫做 mixins 的属性,并将可供混合的类的集合以数组的形式赋给 mixins。
如果使用 ES6 的方式来创建组件,那么 React mixins 的特性将不能被使用了。
面试题 118. 哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?
参考回答:
(1)哪些方法会触发 react 重新渲染?
setState()方法被调用
setState 是 React 中最常用的命令,通常情况下,执行 setState 会触发 render。但是这里有个点值得关注,执行 setState 的时候不一定会重新渲染。当 setState 传入 null 时,并不会触发 render。
class App extends React.Component {
state = {
a: 1
};
render() {
console.log("render");
return (
{this.state.a}
onClick={() => {
this.setState({ a: 1 }); // 这里并没有改变 a 的值
}}
>
Click me
this.setState(null)}>setState null
);
}
}
父组件重新渲染
只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render
(2)重新渲染 render 会做些什么?
会对新旧 VNode 进行对比,也就是我们所说的Diff算法。
对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面
遍历差异对象,根据差异的类型,根据对应对规则更新VNode
React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM厉害的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能.
面试题 119. React如何判断什么时候重新渲染组件?
参考回答:
组件状态的改变可以因为props的改变,或者直接通过setState方法改变。组件获得新的状态,然后React决定是否应该重新渲染组件。只要组件的state发生变化,React就会对组件进行重新渲染。这是因为React中的shouldComponentUpdate方法默认返回true,这就是导致每次更新都重新渲染的原因。
当React将要渲染组件时会执行shouldComponentUpdate方法来看它是否返回true(组件应该更新,也就是重新渲染)。所以需要重写shouldComponentUpdate方法让它根据情况返回true或者false来告诉React什么时候重新渲染什么时候跳过重新渲染
面试题 120. 简述对React中Fragment的理解,它的使用场景是什么?
参考回答:
在React中,组件返回的元素只能有一个根元素。为了不添加多余的DOM节点,我们可以使用Fragment标签来包裹所有的元素,Fragment标签不会渲染出任何元素。React官方对Fragment的解释:
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
import React, { Component, Fragment } from 'react'
// 一般形式
render() {
return (
);
}
// 也可以写成以下形式
render() {
return (
<>
);
}
面试题 121. React中可以在render访问refs吗?
参考回答:
{this.state.title}
{
this.spanRef.current ? '有值' : '无值'
}
不可以,render 阶段 DOM 还没有生成,无法获取 DOM。DOM 的获取需要在 pre-commit 阶段和 commit 阶段
面试题 122. 简述React的插槽(Portals)的理解?
参考回答:
插槽:将一个React元素渲染到指定的Dom容器中
ReactDOM.createPortal(React元素, 真实的DOM容器),该函数返回一个React元素
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。
第二个参数(container)是一个 DOM 元素。
import React, { Component } from "react";
import ReactDOM from "react-dom";
export default class Portals extends Component {
render() {
return (
onClick={() => {
console.log("rooter click");
}}
>
我想出现在root中
);
}
}
function Test() {
return ReactDOM.createPortal(
,
//
我想出现在container中
,
document.getElementById("container")
);
}
function ChildA() {
return
我是childA
;
}
面试题 123. 简述对React-Intl 的理解,它的工作原理?
参考回答:
React-intl是雅虎的语言国际化开源项目FormatJS的一部分,通过其提供的组件和API可以与ReactJS绑定。
React-intl提供了两种使用方法,一种是引用React组件,另一种是直接调取API,官方更加推荐在React项目中使用前者,只有在无法使用React组件的地方,才应该调用框架提供的API。它提供了一系列的React组件,包括数字格式化、字符串格式化、日期格式化等。
在React-intl中,可以配置不同的语言包,他的工作原理就是根据需要,在语言包之间进行切换
面试题 124. React 并发模式是如何执行的?
参考回答:
React 中的并发,并不是指同一时刻同时在做多件事情。因为 js 本身就是单线程的(同一时间只能执行一件事情),而且还要跟 UI 渲染竞争主线程。若一个很耗时的任务占据了线程,那么后续的执行内容都会被阻塞。为了避免这种情况,React 就利用 fiber 结构和时间切片的机制,将一个大任务分解成多个小任务,然后按照任务的优先级和线程的占用情况,对任务进行调度。
对于每个更新,为其分配一个优先级 lane,用于区分其紧急程度。
通过 Fiber 结构将不紧急的更新拆分成多段更新,并通过宏任务的方式将其合理分配到浏览器的帧当中。这样就能使得紧急任务能够插入进来。
高优先级的更新会打断低优先级的更新,等高优先级更新完成后,再开始低优先级更新
面试题 125. React setState 调⽤之后发⽣了什么?是同步还是异步?
参考回答:
(1)React中setState后发⽣了什么
在代码中调⽤setState函数之后,React 会将传⼊的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation)。经过调和过程,React 会以相对⾼效的⽅式根据新的状态构建 React 元素树并且着⼿重新渲染整个UI界⾯。
在 React 得到元素树之后,React 会⾃动计算出新的树与⽼树的节点差异,然后根据差异对界⾯进⾏最⼩化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发⽣了改变以及应该如何改变,这就保证了按需更新,⽽不是全部重新渲染。
如果在短时间内频繁setState。React会将state的改变压⼊栈中,在合适的时机,批量更新state和视图,达到提⾼性能的效果。
(2)setState 是同步还是异步的
假如所有setState是同步的,意味着每执⾏⼀次setState时(有可能⼀个同步代码中,多次
setState),都重新vnode diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把⼀个同步代码中的多个setState合并成⼀次组件更新。所以默认是异步的,但是在⼀些情况下是同步的。
setState 并不是单纯同步/异步的,它的表现会因调⽤场景的不同⽽不同。在源码中,通过isBatchingUpdates 来判断setState 是先存进 state 队列还是直接更新,如果值为 true 则执⾏异步操作,为 false 则直接更新。
异步: 在 React 可以控制的地⽅,就为 true,⽐如在 React ⽣命周期事件和合成事件中,都会⾛合并操作,延迟更新的策略。
同步: 在 React ⽆法控制的地⽅,⽐如原⽣事件,具体就是在 addEventListener 、setTimeout、setInterval 等事件中,就只能同步更新。
⼀般认为,做异步设计是为了性能优化、减少渲染次数:
setState设计为异步,可以显著的提升性能。如果每次调⽤ setState都进⾏⼀次更新,那么意味着render函数会被频繁调⽤,界⾯重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进⾏批量更新;
如果同步更新了state,但是还没有执⾏render函数,那么state和props不能保持同步。state和props不能保持⼀致性,会在开发中产⽣很多的问题;
面试题 126. 简述super()和super(props)有什么区别?
参考回答:
在ES6中,通过extends关键字实现类的继承,super关键字实现调用父类,super代替的是父类的构建函数,使用super(xx)相当于调用sup.prototype.constructor.call(this.xx),如果在子类中不使用super关键字,则会引发报错
super()就是将父类中的this对象继承给子类的,没有super()子类就得不到this对象
在React中,类组件是基于es6的规范实现的,继承React.Component,因此如果用到constructor就必须写super()才初始化this,在调用super()的时候,我们一般都需要传入props作为参数,如果不传进去,React内部也会将其定义在组件实例中,所以无论有没有constructor,在render中this.props都是可以使用的,这是React自动附带的,但是也不建议使用super()代替super(props),因为在React会在类组件构造函数生成实例后再给this.props赋值,所以在不传递props在super的情况下,调用this.props为undefined,而传入props的则都能正常访问,确保了 this.props 在构造函数执行完毕之前已被赋值,更符合逻辑
总结
在React中,类组件基于ES6,所以在constructor中必须使用super
在调用super过程,无论是否传入props,React内部都会将porps赋值给组件实例porps属性中
如果只调用了super(),那么this.props在super()和构造函数结束之间仍是undefined
面试题 127. 简述React中组件间过渡动画如何实现?
参考回答:
在react中,react-transition-group是一种很好的解决方案,其为元素添加enter,enter-active,exit,exit-active这一系列勾子,可以帮助我们方便的实现组件的入场和离场动画
其主要提供了三个主要的组件:
CSSTransition:在前端开发中,结合 CSS 来完成过渡动画效果
SwitchTransition:两个组件显示和隐藏切换时,使用该组件
TransitionGroup:将多个动画组件包裹在其中,一般用于列表中元素的动画
面试题 128. 简述如何Redux 中的异步请求 ?
参考回答:
在 Redux 的应用中,我们经常需要进行异步请求,如获取数据、发送请求等。然而,Redux 原生并不支持异步请求,因为 Redux 中的数据流是单向的,由 View、Action、Reducer 三个部分组成。Action 触发 Reducer 更新 State,从而触发 View 重新渲染。如果 Action 中包含异步请求,就需要将这个请求和处理请求的数据的过程放在远程服务器上。因此,Redux 本身并不处理异步请求,它需要依赖其他的库来支持异步操作。
常见的处理异步请求的库是 Redux-thunk 和 Redux-saga。其中,Redux-thunk 是 Redux 官方推荐的一个异步操作的中间件。它允许我们在 Action 中编写异步代码,使得 Action 可以返回一个函数而不是一个对象。在函数中,我们可以发起异步请求、处理异步请求的数据,然后使用 Dispatch 将数据传递给 Reducer 更新 State。而 Redux-saga 则是一个更加强大的异步操作库,它使用了 ES6 中的 Generator 函数来实现异步操作。相对于 Redux-thunk,Redux-saga 提供了更多的功能,比如取消异步操作、自动重试等