React面试题整理
最近在看React相关的面试题,对相关的面试题整理了一下,作为平时学习的记录
一、React组件的构造函数
(1)constructor必须用super()初始化this,可以绑定this到事件上
(2)如果在constructor中使用this.props,就必须在super中传入props参数,super(props)
(3)无论有没有constructor,在render或其他生命周期函数中都可以使用this.props,因为是默认自带的
(4)如果组件中没有声明constructor,React会默认添加一个空的constructor
(5)ES6中采用的是先创建父类的实例this,因此要先调用super方法,然后再用子类的构造函数去修改this
二、super()和super(props)的区别
(1) constructor()构造方法
这是ES6对类的默认方法,通过new命令生成对象实例时自动调用该方法,而且该方法是类中必须有的。如果没有显式定义,则会默认添加空的 constructor
(2)super()继承
在class方法中,继承是使用extends关键字实现的。子类必须在constructor()调用super()方法,否则新建实例会报错,这是因为子类没有自己的this对象,它只能继承父类的this对象,而super()就是将父类中的this对象继承给子类。所以没有super子类就没有this对象
(3)ES5和ES6关于继承的实现的不同之处
在ES5中new constructor(),这个过程发生了一下四件事情:
①.生成一个空对象,并将其作为this
②.将空对象的__proto__指向构造函数的prototype
③.运行构造函数
④.如果构造函数没有return或者return返回的是基本数据类型,则返回this,如果返回的是一个引用类型,则返回这个引用类型的对象
因此,在ES5的继承中,是先创建子类的实例对象,然后再见父类的方法添加到this上,即parentObject.apply(this)。而ES6采用的是先创建父类的实例this对象,因此要先调用super()方法,然后再用子类的构造函数去修改this
(4)super(props)和super()以及不写super的区别
如果用到constructor就必须写super(),是用来初始化this的,可以绑定this到事件上
如果要在constructor中使用this.props,就必须给super加props
如果没有用到constructor,可以不写,React会默认添加一个空的constructor
(无论有没有constructor,在render中this.props都是可以使用的,这是React自动附带的)
三、React的渲染机制
(1)React整个的渲染机制就是React会调用render()函数构建一棵DOM树
(2)在state/props变更的时候。render()函数会被再次调用渲染出另外一棵树,重新渲染出所有的节点,构建一棵虚拟的DOM树,用diff算法跟原来的DOM进行对比,找到需要更新和修改的地方进行批量改动,这样就减少了对DOM的频繁操作,节省了开销,提升了性能。
延伸:
URL从输入到页面展示这个过程发生了什么?
浏览器渲染HTML的步骤
(1)HTML被HTML解析器解析成DOM树,CSS被CSS解析器解析成CSSOM树
(2)DOM树和CSSOM树解析完成后,被附加到一起,形成渲染树(Render Tree)
(3)节点信息计算(重排),这个过程叫做Layout(WebKit)或Reflow(Mozilla)。根据渲染树计算每个节点的几何信息
(4)渲染绘制(重绘),这个过程叫做Painting或Repaint,也就是根据计算好的信息绘制页面
所以,每一次的DOM更改或者CSS几何属性的更改,都会引起一次浏览器的重排/重绘,而如果只是CSS的非几何属性的更改,则只会引起重绘。因此,重排一定会引起重绘,而重绘不一定会引起重排
重绘和重排
重排:
浏览器渲染页面默认采用的是流式布局。重排就是根据渲染树中的每个渲染对象的信息,计算出每个渲染的几何信息(DOM对象的位置和大小),将其布局到页面中的对应位置。
当某个DOM节点的信息发生改变的时候,浏览器就需要对DOM进行重新计算,对页面进行重新布局,引起回流。
引起重排的常见操作:
① 页面的首次渲染
② 浏览器窗口大小发生改变
③元素尺寸或者大小发生改变
④元素内容发生变化(比如:图片的长宽,文字的数量等)
⑤元素的字体大小发生变化
⑥添加或者删除可见的元素
⑦设置style属性
⑧激活CSS伪类
⑨查询某些属性或调用某些方法
重绘:
当页面中的元素样式改变但不影响元素的位置或者大小的时候,比如更改字体的颜色,改变某个元素的背景颜色。
前端性能优化
(1)减少DOM操作
(2)减少重排
(3)css
(4)图片懒加载
(5)使用事件委托
(6)防抖节流
(7)CDN加速
四、为什么React中的props是只读的
因为在React16中props的属性描述符中,属性configurable默认值为false,因此,props既不能修改也不能删除,仅是只读
这也符合React中父组件数据自上而下单向流向子组件,在子组件中不能直接去修改父组件传过来的props,如果子组件需要更新数据,子组件会将更新的数据传给父组件,由父组件进行更改,父组件更新完数据之后,重新传给子组件
五、Hooks使用规则
不应该在render方法中调用effect,effect在DOM渲染或更新完毕之后执行
useEffect在React的生命周期中相当于componentDidMount,componentDidUpdate,componentWillUnmount的结合
Hooks只能在顶层中使用,不允许在嵌套,条件和循环的函数中使用
只能通过React提供的方法调用Hooks
六、React16新特性
(1)render支持数组和字符串
(2)Fragment标签<></>
可以将一些子元素添加到DOM树上且不需要为这些元素提供额外的父节点,相当于render返回数组元素
(3)使用 Error Boundary处理错误组件
我们可以在容易出错的组件外层使用<ErrorBoundary></ErrorBoundary>标签包裹,当该节点有错误抛出的时候,整个组件结构就会根节点卸载,而不影响其他组件的渲染。
(4)Fiber
对React核心算法的一次重新实现,将原来的同步更新碎片化,避免主线程长时间阻塞,使渲染更加流畅。
(5)Strict Mode
Strict Mode可以在开发阶段开启严格模式,发现应用存在的潜在的问题,提升应用的健壮性
(6)Hooks
多个状态不会嵌套,依然平铺写法
Hooks可以引用其他Hooks
更容易的将组件的UI与状态进行分离
Hooks并不是通过Proxy或getters实现,而是通过数组实现,每次useState都会改变下标。
七、React三种声明组件的方式及区别
1.函数式定义的无状态组件
2.ES5原生方式React.createClass定义的组件
3.ES6形式的extends React.Component定义的组件
函数式定义的无状态组件
只有一个render方法的组件类,通过普通函数或者箭头函数的方式来创建。因为只有render方法,且是无状态组件,所以这种方式就不会有组件实例化的过程,因此就不需要分配多余的内存空间,一定程度上提高了性能。当然没有实例化过程自然就不能访问this对象。另外,由于是无状态组件,就不需要状态管理和生命周期函数了,所以,无状态组件是不能参与管理组件的生命周期。无状态组件只能访问输入的props,不会有副作用
React.createClass方式创建组件
React.createClass方式是最早出现的创建组件的方式,是用ES5原生JavaScript的方式来创建组件。与无状态组件相比,该种方式和React.Component都是创建有状态的组件,这两种方式创建组件是要被实例化的,而且都可以访问组件的生命周期方法。但是,React.createClass方式创建组件有很大的缺陷。它会自动绑定函数方法(不像React.Component那样只关心需要绑定的函数),这样就导致了不必要的性能开销
React.Component方式创建组件
React.Component是以ES6的形式创建组件,是React目前最常用的创建有状态组件的方式,相对React.createClss方式创建组件,能更好的实现复用
八、React.createClass和React.Component的区别
(1)this绑定问题
React.createClass方式创建的组件,每一个成员函数的this都有React自动绑定,使用的时候直接this.method就可以,函数中的this会被正确的设置
React.Component方式创建的组件,不会自动绑定this,需要我们自己手动的去绑定,否则this就不能获得当前组件的实例对象
React.Component方式创建的组件,有3种方式绑定this
①.
constructor(props){
super(props)
this.method = this.method.bind(this) // 在构造函数中绑定
}
②.
<div onClick={this.method.bind(this)}></div> // 在标签上使用bind绑定
③.
<div onClick={() => this.method()}></div> // 使用箭头函数绑定
(2)组件属性类型(propTypes)和默认props(defaultProps)属性配置不同
React.createClass在创建组件时,有关组件的propTypes和组件的defaultProps会作为组件实例的属性来配置,其中defaultProps是使用getDefaultProps的方法获取默认组件属性
varTodoList = React.createClass({
propTypes:{
age: React.propTypes.string
},
getDefaultProps(){
return{
age: ''
}
}
render(){
return(
<div></div>
)
}
})
而React.Component在创建组件的时候,propTypes和defaultProps是作为组件类的属性,不是组件实例属性,也就是类的静态属性来配置的
class TodoList extends React.Component{
static propTypes = {
name: React.propTypes.string
},
static defaultProps = {
name: ''
}
}
(3)组件初始状态state的配置不同
React.createClass创建的组件,其状态state是通过getInitialState方法来配置组件相关的状态
const TodoList = React.createClass({
getInitialState(){
return{
disabled: false
}
}
render(){
return(
<div></div>
)
}
})
React.Component创建的组件,其状态state是在construct构造函数中初始化组件属性一样声明的
class TodoList extends React.Component{
constructor(props){
super(props)
this.state = {
disabled: false
}
}
render(){
return(
<div></div>
)
}
}
九、React生命周期函数
(1)挂载卸载过程
1.1constructor()
constructor()中完成了React数据的初始化,它接受两个参数:props和context,当想在函数内使用这两个参数,需要使用super()方法传入这两个参数。只要使用了constructor()就一定要super(),否则会导致this指向错误
1.2componentWillMount()
该生命周期函数用的较少,这个生命周期函数代表的过程是组件已经经历constructor()初始化数据,但是还没有渲染DOM。它更多的是服务端渲染的时候使用
1.3componentDidMount()
组件第一次渲染完成,此时DOM节点已经生成,可以在这里调用Ajax请求,返回数据setState后组件会重新渲染
1.4componentWillUnmount()
在此处完成数据的销毁和组件的卸载
①.清除组件中所有的定时器setTimeout,setInterval
②.移除所有组件中的监听removeEventListener
(2)更新过程
2.1componentWillReceiveProps(nextProps)
在接受父组件改变后的props需要重新渲染组件的时候,这种情况下该生命周期函数用的比较多
①.接受一个参数nextProps
②.通过对比nextProps和this.props,将nextProps的state为当前组件的state,从而重新渲染组件
componentWillReceiveProps(nextProps){
nextProps.age !== this.props.age && this.setState({
age: nextProps.age
}, () => {
console.log(this.state.age: nextProps)
//将state更新为nextProps,在setState的第二个参数(回调函数中可以拿到新的state)
})
}
2.2shouldComponentUpdate(nextProps, nextState)
①.主要用于性能优化(部分更新)
②.唯一用于控制组件重新渲染的生命周期,由于在React中,当state发生变化,组件会进入重新渲染的过程,在该生命周期函数中return false可以阻止组件的更新
③.因为React父组件的重新渲染会导致所有子组件跟着一起重新渲染,当我们不需要子组件重新渲染的时候,我们可以在对应的子组件的该生命周期函数中进行阻止
2.3componentWillUpdate(nextProps, nextState)
shouldComponentUpdate返回true之后,组件进入重新渲染的过程,进入componentWillUpdate,这里也能拿到nextProps, nextState
2.4componentDidUpdate(prevProps, prevState)
组件更新完毕之后,React只会在第一次初始化成功的时候进入ComponentDidmount,而之后的每次重新渲染都会进入这个生命周期函数,这里可以拿到prevProps和 prevState,也就是更新之前的props和state
2.5render()
render方法会插入jsx生成DOM结构,会生成一棵虚拟DOM树,当每次组件更新的时候,React会通过diff算法来比较更新前后的新旧DOM树,比较之后,找到最小的有差异的DOM节点,重新渲染
(3)新增的生命周期
3.1getDerivedStateFromProps(nextProps, prevState)(谨慎使用)
代替componentWillReceiveProps()
该生命周期函数的作用是为了props能够更新到组件内部的props中。它的应用场景主要是:
①.无条件的根据props来更新组件内部的state,也就是只要传入props就更新state
②.只有props的值和state的值不相同时才更新state值
3.2getSnapshotBeforeUpdate(prevProps, prevState)
代替componentWillUpdate
componentWillUpdate是在组件更新前读取当前某个DOM元素的状态,然后在componentDidUpdate中进行相应的处理
而getSnapshotBeforeUpdate与componentDidUpdate的区别在于:
①.在React开启异步渲染模式之后,render阶段读取到的DOM元素状态并不总是和commit阶段相同,这样就导致了componentDidUpdate中使用componentWillUpdate中读取到的DOM元素状态是不安全的,可能这时候的值已经失效了。而getSnapshotBeforeUpdate会在render之前就调用。因此,getSnapshotBeforeUpdate中读取到的DOM元素状态是可以保证和componentDidUpdate中读取到的DOM元素状态一致的。该生命周期中返回的任何值都将作为参数传递给componentDidUpdate()
十、render方法的原理?返回的数据类型是什么
render函数是不做实际的渲染的,它只是返回一个jsx描述的结构,最终的渲染过程是交给React的,因此render函数也并不会DOM树上做装载或渲染内容。render函数应该是一个纯函数,完全根据props和state决定返回的结果,而不产生任何副作用。在render方法中调用this.setState是错误的,因为纯函数不应该引起状态的改变
render函数返回值:
1.元素
通常通过jsx创建
render(){
return(<div></div>)
}
2.数组或DOM片段
render(){
return [
<div>1</div>
<div>2</div>
]
}
3.字符串或数值类型:它们在DOM被渲染为文本节点
render(){
return 123
}
render(){
return 'abc'
}
4.布尔类型或null(什么都不渲染)
render(){
return null
}
十一、在React中怎么阻止事件的默认行为
在HTML中,我们组织以默认行为可以使用return false。但是在React中阻止事件默认行为要用e.preventDefault()
function handleClick (e) {
e.preventDefault()
}
以上就是我最近整理的关于React方面的面试题,后续应该还会有React-Router,Redux方面的面试题和对第一部分的补充。个人水平有限,有错误或者不当的地方请指正