前端高频react面试题整理

createElement和 cloneElement有什么区别?

createElement是JSX被转载得到的,在 React中用来创建 React元素(即虚拟DOM)的内容。cloneElement用于复制元素并传递新的 props。

区分状态和 props

条件StateProps
1. 从父组件中接收初始值YesYes
2. 父组件可以改变值NoYes
3. 在组件中设置默认值YesYes
4. 在组件的内部变化YesNo
5. 设置子组件的初始值YesYes
6. 在子组件的内部更改NoYes

React-Router 4怎样在路由变化时重新渲染同一个组件?

当路由变化时,即组件的props发生了变化,会调用componentWillReceiveProps等生命周期钩子。那需要做的只是: 当路由改变时,根据路由,也去请求数据:

class NewsList extends Component {
 componentDidMount () {
 this.fetchData(this.props.location);
 }
 fetchData(location) {
 const type = location.pathname.replace('/', '') || 'top'
 this.props.dispatch(fetchListData(type))
 }
 componentWillReceiveProps(nextProps) {
 if (nextProps.location.pathname != this.props.location.pathname) {
 this.fetchData(nextProps.location);
 } 
 }
 render () {
 ...
 }
}

利用生命周期componentWillReceiveProps,进行重新render的预处理操作。

Redux 请求中间件如何处理并发

使用redux-Saga redux-saga是一个管理redux应用异步操作的中间件,用于代替 redux-thunk 的。它通过创建 Sagas 将所有异步操作逻辑存放在一个地方进行集中处理,以此将react中的同步操作与异步操作区分开来,以便于后期的管理与维护。 redux-saga如何处理并发:

  • takeEvery

可以让多个 saga 任务并行被 fork 执行。

import {
 fork,
 take
} from "redux-saga/effects"
const takeEvery = (pattern, saga, ...args) => fork(function*() {
 while (true) {
 const action = yield take(pattern)
 yield fork(saga, ...args.concat(action))
 }
})
  • takeLatest

takeLatest 不允许多个 saga 任务并行地执行。一旦接收到新的发起的 action,它就会取消前面所有 fork 过的任务(如果这些任务还在执行的话)。
在处理 AJAX 请求的时候,如果只希望获取最后那个请求的响应, takeLatest 就会非常有用。

import {
 cancel,
 fork,
 take
} from "redux-saga/effects"
const takeLatest = (pattern, saga, ...args) => fork(function*() {
 let lastTask
 while (true) {
 const action = yield take(pattern)
 if (lastTask) {
 yield cancel(lastTask) // 如果任务已经结束,则 cancel 为空操作
 }
 lastTask = yield fork(saga, ...args.concat(action))
 }
})

对虚拟 DOM 的理解?虚拟 DOM 主要做了什么?虚拟 DOM 本身是什么?

从本质上来说,Virtual Dom是一个JavaScript对象,通过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。

虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对DOM的描述。它设计的最初目的,就是更好的跨平台,比如node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟dom,因为虚拟dom本身是js对象。 在代码渲染到页面之前,vue或者react会把代码转换成一个对象(虚拟DOM)。以对象的形式来描述真实dom结构,最终渲染到页面。在每次数据发生变化前,虚拟dom都会缓存一份,变化之时,现在的虚拟dom会与缓存的虚拟dom进行比较。在vue或者react内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。

另外现代前端框架的一个基本要求就是无须手动操作DOM,一方面是因为手动操作DOM无法保证程序性能,多人协作的项目中如果review不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作可以大大提高开发效率。

为什么要用 Virtual DOM:

(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能

下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶

  • 真实DOM∶ 生成HTML字符串+ 重建所有的DOM元素
  • Virtual DOM∶ 生成vNode+ DOMDiff+必要的DOM更新

Virtual DOM的更新DOM的准备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,我依然可以给你提供过得去的性能。 (2)跨平台 Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp等。

React 16中新生命周期有哪些

关于 React16 开始应用的新生命周期: 可以看出,React16 自上而下地对生命周期做了另一种维度的解读:

  • Render 阶段:用于计算一些必要的状态信息。这个阶段可能会被 React 暂停,这一点和 React16 引入的 Fiber 架构(我们后面会重点讲解)是有关的;
  • Pre-commit阶段:所谓“commit”,这里指的是“更新真正的 DOM 节点”这个动作。所谓 Pre-commit,就是说我在这个阶段其实还并没有去更新真实的 DOM,不过 DOM 信息已经是可以读取的了;
  • Commit 阶段:在这一步,React 会完成真实 DOM 的更新工作。Commit 阶段,我们可以拿到真实 DOM(包括 refs)。

与此同时,新的生命周期在流程方面,仍然遵循“挂载”、“更新”、“卸载”这三个广义的划分方式。它们分别对应到:

  • 挂载过程:

    • constructor
    • getDerivedStateFromProps
    • render
    • componentDidMount
  • 更新过程:

    • getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  • 卸载过程:

    • componentWillUnmount

参考 前端进阶面试题详细解答

你理解“在React中,一切都是组件”这句话。

组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。

React Hooks在平时开发中需要注意的问题和原因

(1)不要在循环,条件或嵌套函数中调用Hook,必须始终在 React函数的顶层使用Hook

这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。

(2)使用useState时候,使用push,pop,splice等直接更改数组对象的坑

使用push直接更改数组无法获取到新值,应该采用析构方式,但是在class里面不会有这个问题。代码示例:

function Indicatorfilter() {
 let [num,setNums] = useState([0,1,2,3])
 const test = () => {
 // 这里坑是直接采用push去更新num
 // setNums(num)是无法更新num的
 // 必须使用num = [...num ,1]
 num.push(1)
 // num = [...num ,1]
 setNums(num)
 }
return (
 <div className='filter'>
 <div onClick={test}>测试</div>
 <div>
 {num.map((item,index) => ( <div key={index}>{item}</div>
 ))} </div>
 </div>
 )
}
class Indicatorfilter extends React.Component<any,any>{
 constructor(props:any){
 super(props)
 this.state = {
 nums:[1,2,3]
 }
 this.test = this.test.bind(this)
 }
 test(){
 // class采用同样的方式是没有问题的
 this.state.nums.push(1)
 this.setState({
 nums: this.state.nums
 })
 }
 render(){
 let {nums} = this.state
 return(
 <div>
 <div onClick={this.test}>测试</div>
 <div>
 {nums.map((item:any,index:number) => ( <div key={index}>{item}</div>
 ))} </div>
 </div>
 )
 }
}

(3)useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect

TableDeail是一个公共组件,在调用它的父组件里面,我们通过set改变columns的值,以为传递给TableDeail 的 columns是最新的值,所以tabColumn每次也是最新的值,但是实际tabColumn是最开始的值,不会随着columns的更新而更新:

const TableDeail = ({ columns,}:TableData) => {
 const [tabColumn, setTabColumn] = useState(columns) 
}
// 正确的做法是通过useEffect改变这个值
const TableDeail = ({ columns,}:TableData) => {
 const [tabColumn, setTabColumn] = useState(columns) 
 useEffect(() =>{setTabColumn(columns)},[columns])
}

(4)善用useCallback

父组件传递给子组件事件句柄时,如果我们没有任何参数变动可能会选用useMemo。但是每一次父组件渲染子组件即使没变化也会跟着渲染一次。

(5)不要滥用useContext

可以使用基于 useContext 封装的状态管理工具。

React.Component 和 React.PureComponent 的区别

PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。

在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行 shouldComponentUpdate。

不过,pureComponent中的 shouldComponentUpdate() 进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和或状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一般会用在一些纯展示组件上。

使用pureComponent的好处:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,达到提升性能的目的。这是因为react自动做了一层浅比较。

React 事件机制

<div onClick={this.handleClick.bind(this)}>点我</div>

React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。

除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。 JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。

另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault

实现合成事件的目的如下:

  • 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
  • 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

Redux 和 Vuex 有什么区别,它们的共同思想

(1)Redux 和 Vuex区别

  • Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可
  • Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
  • Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)

通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易;

(2)共同思想

  • 单—的数据源
  • 变化可以预测

本质上∶ redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案。

React Hooks 解决了哪些问题?

React Hooks 主要解决了以下问题:

(1)在组件之间复用状态逻辑很难

React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)解决此类问题可以使用 render props 和 高阶组件。但是这类方案需要重新组织组件结构,这可能会很麻烦,并且会使代码难以理解。由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。

可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使我们在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。

(2)复杂组件变得难以理解

在组件中,每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。

在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。

为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

(3)难以理解的 class

除了代码复用和代码管理会遇到困难外,class 是学习 React 的一大屏障。我们必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。

为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术

react16版本的reconciliation阶段和commit阶段是什么

  • reconciliation阶段包含的主要工作是对current tree 和 new tree 做diff计算,找出变化部分。进行遍历、对比等是可以中断,歇一会儿接着再来。
  • commit阶段是对上一阶段获取到的变化部分应用到真实的DOM树中,是一系列的DOM操作。不仅要维护更复杂的DOM状态,而且中断后再继续,会对用户体验造成影响。在普遍的应用场景下,此阶段的耗时比diff计算等耗时相对短。

react 实现一个全局的 dialog

import React, { Component } from 'react';
import { is, fromJS } from 'immutable';
import ReactDOM from 'react-dom';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import './dialog.css';
let defaultState = {
 alertStatus:false,
 alertTip:"提示",
 closeDialog:function(){},
 childs:''
}
class Dialog extends Component{
 state = {
 ...defaultState
 };
 // css动画组件设置为目标组件
 FirstChild = props => {
 const childrenArray = React.Children.toArray(props.children);
 return childrenArray[0] || null;
 }
 //打开弹窗
 open =(options)=>{
 options = options || {};
 options.alertStatus = true;
 var props = options.props || {};
 var childs = this.renderChildren(props,options.childrens) || '';
 console.log(childs);
 this.setState({
 ...defaultState,
 ...options,
 childs
 })
 }
 //关闭弹窗
 close(){
 this.state.closeDialog();
 this.setState({
 ...defaultState
 })
 }
 renderChildren(props,childrens) {
 //遍历所有子组件
 var childs = [];
 childrens = childrens || [];
 var ps = {
 ...props, //给子组件绑定props
 _close:this.close //给子组件也绑定一个关闭弹窗的事件 
 };
 childrens.forEach((currentItem,index) => {
 childs.push(React.createElement(
 currentItem,
 {
 ...ps,
 key:index
 }
 ));
 })
 return childs;
 }
 shouldComponentUpdate(nextProps, nextState){
 return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
 }
 
 render(){
 return (
 <ReactCSSTransitionGroup
 component={this.FirstChild}
 transitionName='hide'
 transitionEnterTimeout={300}
 transitionLeaveTimeout={300}>
 <div className="dialog-con" style={this.state.alertStatus? {display:'block'}:{display:'none'}}>
 {this.state.childs} </div>
 </ReactCSSTransitionGroup>
 );
 }
}
let div = document.createElement('div');
let props = {
 
};
document.body.appendChild(div);
let Box = ReactD

子类:

//子类jsx
import React, { Component } from 'react';
class Child extends Component {
 constructor(props){
 super(props);
 this.state = {date: new Date()};
 }
 showValue=()=>{
 this.props.showValue && this.props.showValue()
 }
 render() {
 return (
 <div className="Child">
 <div className="content">
 Child <button onClick={this.showValue}>调用父的方法</button>
 </div>
 </div>
 );
 }
}
export default Child;

css:

.dialog-con{
 position: fixed;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 background: rgba(0, 0, 0, 0.3);
}

我现在有一个button,要用react在上面绑定点击事件,要怎么做?

class Demo {
 render() {
 return <button onClick={(e) => {
 alert('我点击了按钮')
 }}>
 按钮
 </button>
 }
}

你觉得你这样设置点击事件会有什么问题吗?

由于onClick使用的是匿名函数,所有每次重渲染的时候,会把该onClick当做一个新的prop来处理,会将内部缓存的onClick事件进行重新赋值,所以相对直接使用函数来说,可能有一点的性能下降

修改

class Demo {
 onClick = (e) => {
 alert('我点击了按钮')
 }
 render() {
 return <button onClick={this.onClick}>
 按钮
 </button>
 }

你对 Time Slice的理解?

时间分片

  • React 在渲染(render)的时候,不会阻塞现在的线程
  • 如果你的设备足够快,你会感觉渲染是同步的
  • 如果你设备非常慢,你会感觉还算是灵敏的
  • 虽然是异步渲染,但是你将会看到完整的渲染,而不是一个组件一行行的渲染出来
  • 同样书写组件的方式
也就是说,这是React背后在做的事情,对于我们开发者来说,是透明的,具体是什么样的效果呢?

组件是什么?类是什么?类变编译成什么

  • 组件指的是页面的一部分,本质就是一个类,最本质就是一个构造函数
  • 类编译成构造函数

state 和 props 共同点和区别

共同点

  • state 和props的改变都会触发render函数(界面会发生改变)

不同点

  • props 是readonly(只读),但是state是可读可写
  • props 来自父组件,state是组件内部的数据对象

为什么不直接更新 state 呢 ?

如果试图直接更新 state ,则不会重新渲染组件。

// 错误
This.state.message = 'Hello world';

需要使用setState()方法来更新 state。它调度对组件state对象的更新。当state改变时,组件通过重新渲染来响应:

// 正确做法
This.setState({message: ‘Hello World’});

这三个点(...)在 React 干嘛用的?

... 在React(使用JSX)代码中做什么?它叫什么?

<Modal {...this.props} title='Modal heading' animation={false}/>

这个叫扩展操作符号或者展开操作符,例如,如果this.props包含a:1b: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" } };
});
作者:紧张的大葱原文地址:https://segmentfault.com/a/1190000043274740

%s 个评论

要回复文章请先登录注册