腾讯前端经典react面试题(附答案)
Posted beifeng1996
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了腾讯前端经典react面试题(附答案)相关的知识,希望对你有一定的参考价值。
React 性能优化在哪个生命周期?它优化的原理是什么?
react的父级组件的render函数重新渲染会引起子组件的render方法的重新渲染。但是,有的时候子组件的接受父组件的数据没有变动。子组件render的执行会影响性能,这时就可以使用shouldComponentUpdate来解决这个问题。
使用方法如下:
shouldComponentUpdate(nexrProps)
if (this.props.num === nexrProps.num)
return false
return true;
shouldComponentUpdate提供了两个参数nextProps和nextState,表示下一次props和一次state的值,当函数返回false时候,render()方法不执行,组件也就不会渲染,返回true时,组件照常重渲染。此方法就是拿当前props中值和下一次props中的值进行对比,数据相等时,返回false,反之返回true。
需要注意,在进行新旧对比的时候,是浅对比,也就是说如果比较的数据时引用数据类型,只要数据的引用的地址没变,即使内容变了,也会被判定为true。
面对这个问题,可以使用如下方法进行解决:
(1)使用setState改变数据之前,先采用ES6中assgin进行拷贝,但是assgin只深拷贝的数据的第一层,所以说不是最完美的解决办法:
const o2 = Object.assign(,this.state.obj)
o2.student.count = \'00000\';
this.setState(
obj: o2,
)
(2)使用JSON.parse(JSON.stringfy())进行深拷贝,但是遇到数据为undefined和函数时就会错。
const o2 = JSON.parse(JSON.stringify(this.state.obj))
o2.student.count = \'00000\';
this.setState(
obj: o2,
)
vue 或者react 优化整体优化
- 虚拟dom
为什么虚拟 dom 会提高性能?(必考)
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
对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 />
</>
);
在哪个生命周期中你会发出Ajax请求?为什么?
Ajax请求应该写在组件创建期的第五个阶段,即 componentDidMount生命周期方法中。原因如下。
在创建期的其他阶段,组件尚未渲染完成。而在存在期的5个阶段,又不能确保生命周期方法一定会执行(如通过 shouldComponentUpdate方法优化更新等)。在销毀期,组件即将被销毁,请求数据变得无意义。因此在这些阶段发岀Ajax请求显然不是最好的选择。
在组件尚未挂载之前,Ajax请求将无法执行完毕,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount方法中,执行Ajax即可保证组件已经挂载,并且能够正常更新组件。
Hooks可以取代 render props
和高阶组件吗?
通常,render props
和高阶组件仅渲染一个子组件。React团队认为,Hooks 是服务此用例的更简单方法。
这两种模式仍然有一席之地(例如,一个虚拟的 scroller
组件可能有一个 renderItem prop
,或者一个可视化的容器组件可能有它自己的 DOM 结构)。但在大多数情况下,Hooks 就足够了,可以帮助减少树中的嵌套。
createElement过程
React.createElement(): 根据指定的第一个参数创建一个React元素
React.createElement(
type,
[props],
[...children]
)
- 第一个参数是必填,传入的是似HTML标签名称,eg: ul, li
- 第二个参数是选填,表示的是属性,eg: className
- 第三个参数是选填, 子节点,eg: 要显示的文本内容
//写法一:
var child1 = React.createElement(\'li\', null, \'one\');
var child2 = React.createElement(\'li\', null, \'two\');
var content = React.createElement(\'ul\', className: \'teststyle\' , child1, child2); // 第三个参数可以分开也可以写成一个数组
ReactDOM.render(
content,
document.getElementById(\'example\')
);
//写法二:
var child1 = React.createElement(\'li\', null, \'one\');
var child2 = React.createElement(\'li\', null, \'two\');
var content = React.createElement(\'ul\', className: \'teststyle\' , [child1, child2]);
ReactDOM.render(
content,
document.getElementById(\'example\')
);
参考 前端进阶面试题详细解答
react-router4的核心
- 路由变成了组件
- 分散到各个页面,不需要配置 比如
<link> <route></route>
这段代码有什么问题吗?
这段代码有什么问题:
this.setState((prevState, props) =>
return
streak: prevState.streak + props.count,
;
);
答案:
没有什么问题。这种方式很少被使用,咱们可以将一个函数传递给setState
,该函数接收上一个 state
的值和当前的props
,并返回一个新的状态,如果咱们需要根据以前的状态重新设置状态,推荐使用这种方式。
这三个点(...)在 React 干嘛用的?
...
在React(使用JSX)代码中做什么?它叫什么?
<Modal ...this.props title=\'Modal heading\' animation=false/>
这个叫扩展操作符号或者展开操作符,例如,如果this.props
包含a:1
和b:2
,则
<Modal ...this.props title=\'Modal heading\' animation=false>
等价于下面内容:
<Modal a=this.props.a b=this.props.b title=\'Modal heading\' animation=false>
扩展符号不仅适用于该用例,而且对于创建具有现有对象的大多数(或全部)属性的新对象非常方便,在更新state
咱们就经常这么做:
this.setState((prevState) =>
return foo: ...prevState.foo, a: "updated" ;
);
react-redux 的实现原理?
通过 redux 和 react context 配合使用,并借助高阶函数,实现了 react-redux
React 中的StrictMode(严格模式)是什么??
React 的StrictMode
是一种辅助组件,可以帮助咱们编写更好的 react 组件,可以使用<StrictMode />
包装一组组件,并且可以帮咱们以下检查:
-
验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告。
-
验证是否使用的已经废弃的方法,如果有,会在控制台给出警告。
-
通过识别潜在的风险预防一些副作用。
什么情况下使用异步组件
- 提高页面加载速度,使用
reloadable
把各个页面分别单独打包,按需加载
如果创建了类似于下面的 Icketang元素,那么该如何实现 Icketang类?
<Icketang username="雨夜清荷">(user) => (user ? <Info user=user /> : <Loading />)</Icketang>;
import React, Component from "react";
export class Icketang extends Component
//请实现你的代码
在上面的案例中,一个组件接受一个函数作为它的子组件。Icketang组件的子组件是一个函数,而不是一个常用的组件。这意味着在实现 Icketang组件时,需要将props. children作为一个函数来处理。
具体实现如下。
import React, Component from "react";
class Icketang extends Component
constructor(props)
super(props);
this.state =
user: props.user,
;
componentDidMount()
//模拟异步获取数据操作,更新状态
setTimeout(
() =>
this.setstate(
user: "有课前端网",
),
2000
);
render()
return this.props.children(this.state.user);
class Loading extends Component
render()
return <p>Loading.</p>;
class Info extends Component
render()
return <h1> this.props.user</h1>;
调用 Icketang组件,并传递给user属性数据,把 props.children作为一个函数来处理。这种模式的好处是,我们已经将父组件与子组件分离了,父组件管理状态。父组件的使用者可以决定父组件以何种形式渲染子组件。
为了演示这一点,在渲染 Icketang组件时,分别传递和不传递user属性数据来观察渲染结果。
import render from "react-dom";
render(<Icketang>(user) => (user ? <Info user=user /> : <Loading />)</Icketang>, ickt);
上述代码没有为 Icketang组件传递user属性数据,因此将首先渲染 Loading组件,当父组件的user状态数据发生改变时,我们发现Info组件可以成功地渲染出来。
render(<Icketang user="雨夜清荷">(user) => (user ? <Info user=user /> : <Loading />)</Icketang>, ickt);
上述代码为 Icketang组件传递了user属性数据,因此将直接渲染Info组件,当父组件的user状态数据发生改变时,我们发现Info组件产生了更新,在整个过程中, Loading组件都未渲染。
如何用 React构建( build)生产模式?
通常,使用 Webpack的 DefinePlugin方法将 NODE ENV设置为 production。这将剥离 propType验证和额外的警告。除此之外,还可以减少代码,因为 React使用 Uglify的dead-code来消除开发代码和注释,这将大大减少包占用的空间。
key的作用
是给每一个 vnode 的唯一 id,可以依靠 key,更准确,更快的拿到 oldVnode 中对应的 vnode 节点
<!-- 更新前 -->
<div>
<p key="ka">ka</p>
<h3 key="song">song</he>
</div>
<!-- 更新后 -->
<div>
<h3 key="song">song</h3>
<p key="ka">ka</p>
</div>
如果没有 key,React 会认为 div 的第一个子节点由 p 变成 h3,第二个子节点由 h3 变成 p,则会销毁这两个节点并重新构造。
但是当我们用 key 指明了节点前后对应关系后,React 知道 key === "ka"
的 p 更新后还在,所以可以复用该节点,只需要交换顺序。
key 是 React 用来追踪哪些列表元素被修改、被添加或者被移除的辅助标志。
在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React diff 算法中,React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重新渲染。同时,React 还需要借助 key 来判断元素与本地状态的关联关系。
调用 setState 之后发生了什么
在代码中调用 setState 函数之后,React 会将传入的参数与之前的状态进行合并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。在 React 得到元素树之后,React 会计算出新的树和老的树之间的差异,然后根据差异对界面进行最小化重新渲染。通过 diff 算法,React 能够精确制导哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
- 在 setState 的时候,React 会为当前节点创建一个 updateQueue 的更新列队。
- 然后会触发 reconciliation 过程,在这个过程中,会使用名为 Fiber 的调度算法,开始生成新的 Fiber 树, Fiber 算法的最大特点是可以做到异步可中断的执行。
- 然后 React Scheduler 会根据优先级高低,先执行优先级高的节点,具体是执行 doWork 方法。
- 在 doWork 方法中,React 会执行一遍 updateQueue 中的方法,以获得新的节点。然后对比新旧节点,为老节点打上 更新、插入、替换 等 Tag。
- 当前节点 doWork 完成后,会执行 performUnitOfWork 方法获得新节点,然后再重复上面的过程。
- 当所有节点都 doWork 完成后,会触发 commitRoot 方法,React 进入 commit 阶段。
- 在 commit 阶段中,React 会根据前面为各个节点打的 Tag,一次性更新整个 dom 元素
为什么有些react生命周期钩子被标记为UNSAFE
componentWillMount
componentWillMount生命周期发生在首次渲染前,一般使用的小伙伴大多在这里初始化数据或异步获取外部数据赋值。初始化数据,react官方建议放在constructor里面。而异步获取外部数据,渲染并不会等待数据返回后再去渲染
class Example extends React.Component
state =
value: \'\'
;
componentWillMount()
this.setState(
value: this.props.source.value
);
this.props.source.subscribe(this.handleChange);
componentWillUnmount()
this.props.source.unsubscribe(this.handleChange );
handleChange = source =>
this.setState(
value: source.value
);
;
- 试想一下,假如组件在第一次渲染的时候被中断,由于组件没有完成渲染,所以并不会执行componentWillUnmount生命周期(注:很多人经常认为componentWillMount和componentWillUnmount总是配对,但这并不是一定的。只有调用componentDidMount后,React才能保证稍后调用componentWillUnmount进行清理)。因此handleSubscriptionChange还是会在数据返回成功后被执行,这时候setState由于组件已经被移除,就会导致内存泄漏。所以建议把异步获取外部数据写在componentDidMount生命周期里,这样就能保证componentWillUnmount生命周期会在组件移除的时候被执行,避免内存泄漏的风险。
- 现在,小伙伴清楚为什么了要用
UNSAFE_componentWillMount
替换componentWillMount
了吧
componentWillReceiveProps
componentWillReceiveProps生命周期是在props更新时触发。一般用于props参数更新时同步更新state参数。但如果在componentWillReceiveProps生命周期直接调用父组件的某些有调用setState的函数,会导致程序死循环
// 如下是子组件componentWillReceiveProps里调用父组件改变state的函数示例
class Parent extends React.Component
constructor()
super();
this.state=
list: [],
selectedData:
;
changeSelectData = selectedData =>
this.setState(
selectedData
);
render()
return (
<Clild list=this.state.list changeSelectData=this.changeSelectData/>
);
...
class Child extends React.Component
constructor()
super();
this.state=
list: []
;
componentWillReceiveProps(nextProps)
this.setState(
list: nextProps.list
)
nextProps.changeSelectData(nextProps.list[0]); //默认选择第一个
...
- 如上代码,在Child组件的componentWillReceiveProps里直接调用Parent组件的changeSelectData去更新Parent组件state的selectedData值。会触发Parent组件重新渲染,而Parent组件重新渲染会触发Child组件的componentWillReceiveProps生命周期函数执行。如此就会陷入死循环。导致程序崩溃。
- 所以,React官方把componentWillReceiveProps替换为UNSAFE_componentWillReceiveProps,让小伙伴在使用这个生命周期的时候注意它会有缺陷,要注意避免,比如上面例子,Child在componentWillReceiveProps调用changeSelectData时先判断list是否有更新再确定是否要调用,就可以避免死循环。
componentWillUpdate
componentWillUpdate生命周期在视图更新前触发。一般用于视图更新前保存一些数据方便视图更新完成后赋值。 案例三:如下是列表加载更新后回到当前滚动条位置的案例
class ScrollingList extends React.Component
listRef = null;
previousScrollOffset = null;
componentWillUpdate(nextProps, nextState)
if (this.props.list.length < nextProps.list.length)
this.previousScrollOffset = this.listRef.scrollHeight - this.listRef.scrollTop;
componentDidUpdate(prevProps, prevState)
if (this.previousScrollOffset !== null)
this.listRef.scrollTop = this.listRef.scrollHeight - this.previousScrollOffset;
this.previousScrollOffset = null;
render()
return (
`<div>` /* ...contents... */`</div>`
);
setListRef = ref => this.listRef = ref; ;
- 由于componentWillUpdate和componentDidUpdate这两个生命周期函数有一定的时间差(componentWillUpdate后经过渲染、计算、再更新DOM元素,最后才调用componentDidUpdate),如果这个时间段内用户刚好拉伸了浏览器高度,那componentWillUpdate计算的previousScrollOffset就不准确了。如果在componentWillUpdate进行setState操作,会出现多次调用只更新一次的问题,把setState放在componentDidUpdate,能保证每次更新只调用一次。
- 所以,react官方建议把componentWillUpdate替换为UNSAFE_componentWillUpdate。如果真的有以上案例的需求,可以使用16.3新加入的一个周期函数getSnapshotBeforeUpdat
结论
- React意识到componentWillMount、componentWillReceiveProps和componentWillUpdate这三个生命周期函数有缺陷,比较容易导致崩溃。但是由于旧的项目已经在用以及有些老开发者习惯用这些生命周期函数,于是通过给它加UNSAFE_来提醒用它的人要注意它们的缺陷
- React加入了两个新的生命周期函数getSnapshotBeforeUpdate和getDerivedStateFromProps,目的为了即使不使用这三个生命周期函数,也能实现只有这三个生命周期能实现的功能
createElement和 cloneElement有什么区别?
createElement是JSX被转载得到的,在 React中用来创建 React元素(即虚拟DOM)的内容。cloneElement用于复制元素并传递新的 props。
概述下 React 中的事件处理逻辑
- 抹平浏览器差异,实现更好的跨平台。
- 避免垃圾回收,React 引入事件池,在事件池中获取或释放事件对象,避免频繁地去创建和销毁。
- 方便事件统一管理和事务机制。
为了解决跨浏览器兼容性问题,
React
会将浏览器原生事件(Browser Native Event
)封装为合成事件(SyntheticEvent
)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React
并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样React
在更新DOM
的时候就不需要考虑如何去处理附着在DOM
上的事件监听器,最终达到优化性能的目的
state 是怎么注入到组件的,从 reducer 到组件经历了什么样的过程
通过connect和mapStateToProps将state注入到组件中:
import connect from \'react-redux\'
import setVisibilityFilter from \'@/reducers/Todo/actions\'
import Link from \'@/containers/Todo/components/Link\'
const mapStateToProps = (state, ownProps) => (
active: ownProps.filter === state.visibilityFilter
)
const mapDispatchToProps = (dispatch, ownProps) => (
setFilter: () =>
dispatch(setVisibilityFilter(ownProps.filter))
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(Link)
上面代码中,active就是注入到Link组件中的状态。 mapStateToProps(state,ownProps)中带有两个参数,含义是∶
- state-store管理的全局状态对象,所有都组件状态数据都存储在该对象中。
- ownProps 组件通过props传入的参数。
reducer 到组件经历的过程:
- reducer对action对象处理,更新组件状态,并将新的状态值返回store。
- 通过connect(mapStateToProps,mapDispatchToProps)(Component)对组件 Component进行升级,此时将状态值从store取出并作为props参数传递到组件。
高阶组件实现源码∶
import React from \'react\'
import PropTypes from \'prop-types\'
// 高阶组件 contect
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) =>
class Connect extends React.Component
// 通过对context调用获取store
static contextTypes =
store: PropTypes.object
constructor()
super()
this.state =
allProps:
// 第一遍需初始化所有组件初始状态
componentWillMount()
const store = this.context.store
this._updateProps()
store.subscribe(() => this._updateProps()); // 加入_updateProps()至store里的监听事件列表
// 执行action后更新props,使组件可以更新至最新状态(类似于setState)
_updateProps()
const store = this.context.store;
let stateProps = mapStateToProps ?
mapStateToProps(store.getState(), this.props) : // 防止 mapStateToProps 没有传入
let dispatchProps = mapDispatchToProps ?
mapDispatchToProps(store.dispatch, this.props) :
dispatch: store.dispatch
// 防止 mapDispatchToProps 没有传入
this.setState(
allProps:
...stateProps,
...dispatchProps,
...this.props
)
render()
return <WrappedComponent ...this.state.allProps />
return Connect
以上是关于腾讯前端经典react面试题(附答案)的主要内容,如果未能解决你的问题,请参考以下文章