京东前端高频 react 面试题集锦
你理解“在 React 中,一切都是组件”这句话。
组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。
useEffect 与 useLayoutEffect 的区别
(1)共同点
运用效果: useEffect 与 useLayoutEffect 两者都是用于处理副作用,这些副作用包括改变 DOM、设置订阅、操作定时器等。在函数组件内部操作副作用是不被允许的,所以需要使用这两个函数去处理。
使用方式: useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl 方法,在使用上也没什么差异,基本可以直接替换。
(2)不同点
使用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理,所以需要避免在 useLayoutEffect 做计算量较大的耗时任务从而造成阻塞。
使用效果: useEffect 是按照顺序执行代码的,改变屏幕像素之后执行(先渲染,后改变 DOM),当改变屏幕内容时可能会产生闪烁;useLayoutEffect 是改变屏幕像素之前就执行了(会推迟页面显示的事件,先改变 DOM 后渲染),不会产生闪烁。useLayoutEffect 总是比 useEffect 先执行。
在未来的趋势上,两个 API 是会长期共存的,暂时没有删减合并的计划,需要开发者根据场景去自行选择。React 团队的建议非常实用,如果实在分不清,先用 useEffect,一般问题不大;如果页面有异常,再直接替换为 useLayoutEffect 即可。
React 性能优化在哪个生命周期?它优化的原理是什么?
react 的父级组件的 render 函数重新渲染会引起子组件的 render 方法的重新渲染。但是,有的时候子组件的接受父组件的数据没有变动。子组件 render 的执行会影响性能,这时就可以使用 shouldComponentUpdate 来解决这个问题。
使用方法如下:
shouldComponentUpdate 提供了两个参数 nextProps 和 nextState,表示下一次 props 和一次 state 的值,当函数返回 false 时候,render()方法不执行,组件也就不会渲染,返回 true 时,组件照常重渲染。此方法就是拿当前 props 中值和下一次 props 中的值进行对比,数据相等时,返回 false,反之返回 true。
需要注意,在进行新旧对比的时候,是浅对比,也就是说如果比较的数据时引用数据类型,只要数据的引用的地址没变,即使内容变了,也会被判定为 true。
面对这个问题,可以使用如下方法进行解决:(1)使用 setState 改变数据之前,先采用 ES6 中 assgin 进行拷贝,但是 assgin 只深拷贝的数据的第一层,所以说不是最完美的解决办法:
(2)使用 JSON.parse(JSON.stringfy())进行深拷贝,但是遇到数据为 undefined 和函数时就会错。
React 中有使用过 getDefaultProps 吗?它有什么作用?
通过实现组件的 getDefaultProps,对属性设置默认值(ES5 的写法):
React key 是干嘛用的 为什么要加?key 主要是解决哪一类问题的
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。
在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系。
注意事项:
key 值一定要和具体的元素—一对应;
尽量不要用数组的 index 去作为 key;
不要在 render 的时候用随机数或者其他操作给元素加上不稳定的 key,这样造成的性能开销比不加 key 的情况下更糟糕。
同时引用这三个库 react.js、react-dom.js 和 babel.js 它们都有什么作用?
react:包含 react 所必须的核心代码
react-dom:react 渲染在不同平台所需要的核心代码
babel:将 jsx 转换成 React 代码的工具
参考 前端进阶面试题详细解答
React 声明组件有哪几种方法,有什么不同?
React 声明组件的三种方式:
函数式定义的
无状态组件
ES5 原生方式
React.createClass
定义的组件ES6 形式的
extends React.Component
定义的组件
(1)无状态函数式组件 它是为了创建纯展示组件,这种组件只负责根据传入的 props 来展示,不涉及到 state 状态的操作组件不会被实例化,整体渲染性能得到提升,不能访问 this 对象,不能访问生命周期的方法
(2)ES5 原生方式 React.createClass // RFC React.createClass 会自绑定函数方法,导致不必要的性能开销,增加代码过时的可能性。
(3)E6 继承形式 React.Component // RCC 目前极为推荐的创建有状态组件的方式,最终会取代 React.createClass 形式;相对于 React.createClass 可以更好实现代码复用。
无状态组件相对于于后者的区别: 与无状态组件相比,React.createClass 和 React.Component 都是创建有状态的组件,这些组件是要被实例化的,并且可以访问组件的生命周期方法。
React.createClass 与 React.Component 区别:
① 函数 this 自绑定
React.createClass 创建的组件,其每一个成员函数的 this 都有 React 自动绑定,函数中的 this 会被正确设置。
React.Component 创建的组件,其成员函数不会自动绑定 this,需要开发者手动绑定,否则 this 不能获取当前组件实例对象。
② 组件属性类型 propTypes 及其默认 props 属性 defaultProps 配置不同
React.createClass 在创建组件时,有关组件 props 的属性类型及组件默认的属性会作为组件实例的属性来配置,其中 defaultProps 是使用 getDefaultProps 的方法来获取默认组件属性的
React.Component 在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。
③ 组件初始状态 state 的配置不同
React.createClass 创建的组件,其状态 state 是通过 getInitialState 方法来配置组件相关的状态;
React.Component 创建的组件,其状态 state 是在 constructor 中像初始化组件属性一样声明的。
对 React 中 Fragment 的理解,它的使用场景是什么?
在 React 中,组件返回的元素只能有一个根元素。为了不添加多余的 DOM 节点,我们可以使用 Fragment 标签来包裹所有的元素,Fragment 标签不会渲染出任何元素。React 官方对 Fragment 的解释:
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
React 中 refs 的作用是什么?有哪些应用场景?
Refs 提供了一种方式,用于访问在 render 方法中创建的 React 元素或 DOM 节点。Refs 应该谨慎使用,如下场景使用 Refs 比较适合:
处理焦点、文本选择或者媒体的控制
触发必要的动画
集成第三方 DOM 库
Refs 是使用 React.createRef() 方法创建的,他通过 ref 属性附加到 React 元素上。
要在整个组件中使用 Refs,需要将 ref 在构造函数中分配给其实例属性:
什么原因会促使你脱离 create-react-app 的依赖
当你想去配置 webpack 或 babel presets。
React 中发起网络请求应该在哪个生命周期中进行?为什么?
对于异步请求,最好放在 componentDidMount 中去操作,对于同步的状态改变,可以放在 componentWillMount 中,一般用的比较少。
如果认为在 componentWillMount 里发起请求能提早获得结果,这种想法其实是错误的,通常 componentWillMount 比 componentDidMount 早不了多少微秒,网络上任何一点延迟,这一点差异都可忽略不计。
react 的生命周期: constructor() -> componentWillMount() -> render() -> componentDidMount()
上面这些方法的调用是有次序的,由上而下依次调用。
constructor 被调用是在组件准备要挂载的最开始,此时组件尚未挂载到网页上。
componentWillMount 方法的调用在 constructor 之后,在 render 之前,在这方法里的代码调用 setState 方法不会触发重新 render,所以它一般不会用来作加载数据之用。
componentDidMount 方法中的代码,是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载。此外,在这方法中调用 setState 方法,会触发重新渲染。所以,官方设计这个方法就是用来加载外部数据用的,或处理其他的副作用代码。与组件上的数据无关的加载,也可以在 constructor 里做,但 constructor 是做组件 state 初绐化工作,并不是做加载数据这工作的,constructor 里也不能 setState,还有加载的时间太长或者出错,页面就无法加载出来。所以有副作用的代码都会集中在 componentDidMount 方法里。
总结:
跟服务器端渲染(同构)有关系,如果在 componentWillMount 里面获取数据,fetch data 会执行两次,一次在服务器端一次在客户端。在 componentDidMount 中可以解决这个问题,componentWillMount 同样也会 render 两次。
在 componentWillMount 中 fetch data,数据一定在 render 后才能到达,如果忘记了设置初始状态,用户体验不好。
react16.0 以后,componentWillMount 可能会被执行多次。
React setState 调用的原理
具体的执行过程如下(源码级解析):
首先调用了
setState
入口函数,入口函数在这里就是充当一个分发器的角色,根据入参的不同,将其分发到不同的功能函数中去;
enqueueSetState
方法将新的state
放进组件的状态队列里,并调用enqueueUpdate
来处理将要更新的实例对象;
在
enqueueUpdate
方法中引出了一个关键的对象——batchingStrategy
,该对象所具备的isBatchingUpdates
属性直接决定了当下是要走更新流程,还是应该排队等待;如果轮到执行,就调用batchedUpdates
方法来直接发起更新流程。由此可以推测,batchingStrategy
或许正是 React 内部专门用于管控批量更新的对象。
注意:batchingStrategy
对象可以理解为“锁管理器”。这里的“锁”,是指 React 全局唯一的 isBatchingUpdates
变量,isBatchingUpdates
的初始值是 false
,意味着“当前并未进行任何批量更新操作”。每当 React 调用 batchedUpdate
去执行更新动作时,会先把这个锁给“锁上”(置为 true
),表明“现在正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents
里排队等候下一次的批量更新,而不能随意“插队”。此处体现的“任务锁”的思想,是 React 面对大量状态仍然能够实现有序分批处理的基石。
用户不同权限 可以查看不同的页面 如何实现?
Js 方式
根据用户权限类型,把菜单配置成 json, 没有权限的直接不显示
react-router 方式 在 route 标签上 添加 onEnter 事件,进入路由之前替换到首页
自己封装一个 privateRouter 组件 里面判断是否有权限,有的话返回
没有权限的话 component 返回一个提示信息的组件。
扩展一下,如果是根据用权限来判断是否隐藏组件该怎么做呢?
react 可以使用高阶组件,在高阶组件里面判断是否有权限,然后判断是否返回组件,无权限返回 null
vue 可以使用自定义指令,如果没有权限移除组件
React Hooks 在平时开发中需要注意的问题和原因
(1)不要在循环,条件或嵌套函数中调用 Hook,必须始终在 React 函数的顶层使用 Hook
这是因为 React 需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用 Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
(2)使用 useState 时候,使用 push,pop,splice 等直接更改数组对象的坑
使用 push 直接更改数组无法获取到新值,应该采用析构方式,但是在 class 里面不会有这个问题。代码示例:
(3)useState 设置状态的时候,只有第一次生效,后期需要更新状态,必须通过 useEffect
TableDeail 是一个公共组件,在调用它的父组件里面,我们通过 set 改变 columns 的值,以为传递给 TableDeail 的 columns 是最新的值,所以 tabColumn 每次也是最新的值,但是实际 tabColumn 是最开始的值,不会随着 columns 的更新而更新:
(4)善用 useCallback
父组件传递给子组件事件句柄时,如果我们没有任何参数变动可能会选用 useMemo。但是每一次父组件渲染子组件即使没变化也会跟着渲染一次。
(5)不要滥用 useContext
可以使用基于 useContext 封装的状态管理工具。
react-router4 的核心
路由变成了组件
分散到各个页面,不需要配置 比如
<link> <route></route>
React 组件的构造函数有什么作用?它是必须的吗?
构造函数主要用于两个目的:
通过将对象分配给 this.state 来初始化本地状态
将事件处理程序方法绑定到实例上
所以,当在 React class 中需要设置 state 的初始值或者绑定事件时,需要加上构造函数,官方 Demo:
构造函数用来新建父类的 this 对象;子类必须在 constructor 方法中调用 super 方法;否则新建实例时会报错;因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法;子类就得不到 this 对象。
注意:
constructor () 必须配上 super(), 如果要在 constructor 内部使用 this.props 就要 传入 props , 否则不用
JavaScript 中的 bind 每次都会返回一个新的函数, 为了性能等考虑, 尽量在 constructor 中绑定事件
react 的全家桶有哪些
react:核心
redux:相当于数据,主要存储数据状态
react-redux 可以完成数据订阅
redux-thunk 可以实现异步的 action
redux-logger 是 redux 的日志中间件
react-router 专门为 react 提供路由解决方案,它利用 HTML5 的 history API,来操作浏览器的 session history (会话历史)
react-router:提供核心的路由组件与函数
react-router-config:用来配置静态路由(还在开发中)
react-router-native:
react-router-dom:
axios:是基于 promise 的用于浏览器和服务端进行数据交互的技术
antd:Ant Degisn 是个很好的 React UI 库
如何避免组件的重新渲染?
React 中最常见的问题之一是组件不必要地重新渲染。React 提供了两个方法,在这些情况下非常有用:
React.memo()
:这可以防止不必要地重新渲染函数组件PureComponent
:这可以防止不必要地重新渲染类组件这两种方法都依赖于对传递给组件的props
的浅比较,如果props
没有改变,那么组件将不会重新渲染。虽然这两种工具都非常有用,但是浅比较会带来额外的性能损失,因此如果使用不当,这两种方法都会对性能产生负面影响。通过使用 React Profiler,可以在使用这些方法前后对性能进行测量,从而确保通过进行给定的更改来实际改进性能。
React 最新的⽣命周期是怎样的?
React 16 之后有三个⽣命周期被废弃(但并未删除)
componentWillMount
componentWillReceiveProps
componentWillUpdate
官⽅计划在 17 版本完全删除这三个函数,只保留 UNSAVE_前缀的三个函数,⽬的是为了向下兼容,但是对于开发者⽽⾔应该尽量避免使⽤他们,⽽是使⽤新增的⽣命周期函数替代它们。
⽬前 React16.8+的⽣命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段。
挂载阶段:
constructor:构造函数,最先被执⾏,我们通常在构造函数⾥初始化 state 对象或者给⾃定义⽅法绑定 this;
getDerivedStateFromProps:static getDerivedStateFromProps(nextProps, prevState),这是个静态⽅法,当我们接收到新的属性想去修改我们 state, 可以使⽤getDerivedStateFromProps
render:render 函数是纯函数,只返回需要渲染的东⻄,不应该包含其它的业务逻辑,可以返回原⽣的 DOM、React 组件、Fragment、Portals、字符串和数字、 Boolean 和 null 等内容;
componentDidMount:组件装载之后调⽤,此时我们可以获取到 DOM 节点并操作,⽐如对 canvas,svg 的操作,服务器请求,订阅都可以写在这个⾥⾯,但是记得在 componentWillUnmount 中取消订阅;
更新阶段:
getDerivedStateFromProps: 此⽅法在更新个挂载阶段都可能会调⽤;
shouldComponentUpdate:shouldComponentUpdate(nextProps, nextState),有两个参数 nextProps 和 nextState,表示新的属性和变化之后的 state,返回⼀个布尔值,true 表示会触发重新渲染,false 表示不会触发重新渲染,默认返回 true,我们通常利⽤此⽣命周期来优化 React 程序性能;
render:更新阶段也会触发此⽣命周期;
getSnapshotBeforeUpdate:getSnapshotBeforeUpdate(prevProps, prevState),这个⽅法在 render 之后,componentDidUpdate 之前调⽤,有两个参数 prevProps 和 prevState,表示之前的属性和之前的 state,这个函数有⼀个返回值,会作为第三个参数传给 componentDidUpdate,如果你不想要返回值,可以返回 null,此⽣命周期必须与 componentDidUpdate 搭配使⽤;
componentDidUpdate:componentDidUpdate(prevProps, prevState, snapshot),该⽅法在 getSnapshotBeforeUpdate⽅法之后被调⽤,有三个参数 prevProps,prevState,snapshot,表示之前的 props,之前的 state,和 snapshot。第三个参数是 getSnapshotBeforeUpdate 返回的,如果触发某些回调函数时需要⽤到 DOM 元素的状态,则将对⽐或计算的过程迁移⾄getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统⼀触发回调或更新状态。
卸载阶段:
-componentWillUnmount:当我们的组件被卸载或者销毁了就会调⽤,我们可以在这个函数⾥去清除⼀些定时器,取消⽹络请求,清理⽆效的 DOM 元素等垃圾清理⼯作。
总结:
componentWillMount:在渲染之前执行,用于根组件中的 App 级配置;
componentDidMount:在第一次渲染之后执行,可以在这里做 AJAX 请求,DOM 的操作或状态更新以及设置事件监听器;
componentWillReceiveProps:在初始化 render 的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染
shouldComponentUpdate:确定是否更新组件。默认情况下,它返回 true。如果确定在 state 或 props 更新后组件不需要在重新渲染,则可以返回 false,这是一个提高性能的方法;
componentWillUpdate:在 shouldComponentUpdate 返回 true 确定要更新组件之前件之前执行;
componentDidUpdate:它主要用于更新 DOM 以响应 props 或 state 更改;
componentWillUnmount:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器。
新版生命周期
在新版本中,React 官方对生命周期有了新的 变动建议:
使用
getDerivedStateFromProps
替换componentWillMount;
使用
getSnapshotBeforeUpdate
替换componentWillUpdate;
避免使用
componentWillReceiveProps
;
其实该变动的原因,正是由于上述提到的
Fiber
。首先,从上面我们知道 React 可以分成reconciliation
与commit
两个阶段,对应的生命周期如下:
reconciliation
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
commit
componentDidMount
componentDidUpdate
componentWillUnmount
在
Fiber
中,reconciliation
阶段进行了任务分割,涉及到 暂停 和 重启,因此可能会导致reconciliation
中的生命周期函数在一次更新渲染循环中被 多次调用 的情况,产生一些意外错误
新版的建议生命周期如下:
使用建议:
在
constructor
初始化state
;在
componentDidMount
中进行事件监听,并在componentWillUnmount
中解绑事件;在
componentDidMount
中进行数据的请求,而不是在componentWillMount
;需要根据
props
更新state
时,使用getDerivedStateFromProps(nextProps, prevState)
;旧 props 需要自己存储,以便比较;
可以在 componentDidUpdate 监听 props 或者 state 的变化,例如:
在 componentDidUpdate 使用 setState 时,必须加条件,否则将进入死循环;
getSnapshotBeforeUpdate(prevProps, prevState)可以在更新之前获取最新的渲染数据,它的调用是在 render 之后, update 之前;
shouldComponentUpdate: 默认每次调用 setState,一定会最终走到 diff 阶段,但可以通过 shouldComponentUpdate 的生命钩子返回 false 来直接阻止后面的逻辑执行,通常是用于做条件渲染,优化渲染的性能。
评论