社招前端常见 react 面试题(必备)
解释 React 中 render() 的目的。
每个 React 组件强制要求必须有一个 **render()**。它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 <form>
、<group>
、<div>
等。此函数必须保持纯净,即必须每次调用时都返回相同的结果。
React 中 constructor 和 getInitialState 的区别?
两者都是用来初始化 state 的。前者是 ES6 中的语法,后者是 ES5 中的语法,新版本的 React 中已经废弃了该方法。
getInitialState 是 ES5 中的方法,如果使用 createClass 方法创建一个 Component 组件,可以自动调用它的 getInitialState 方法来获取初始化的 State 对象,
React 在 ES6 的实现中去掉了 getInitialState 这个 hook 函数,规定 state 在 constructor 中实现,如下:
对 React-Fiber 的理解,它解决了什么问题?
React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。
为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。
所以 React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:
分批延时对 DOM 进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。
核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
在 React 中,何为 state
State 和 props 类似,但它是私有的,并且完全由组件自身控制。State 本质上是一个持有数据,并决定组件如何渲染的对象。
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 自动做了一层浅比较。
除了在构造函数中绑定 this
,还有其它方式吗
你可以使用属性初始值设定项(property initializers)来正确绑定回调,create-react-app 也是默认支持的。在回调中你可以使用箭头函数,但问题是每次组件渲染时都会创建一个新的回调。
参考 前端进阶面试题详细解答
调用 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-redux 的实现原理?
通过 redux 和 react context 配合使用,并借助高阶函数,实现了 react-redux
react 中的 Portal 是什么?
Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。
第二个参数(container)则是一个 DOM 元素。
说说 React 组件开发中关于作用域的常见问题。
在 EMAScript5 语法规范中,关于作用域的常见问题如下。(1)在 map 等方法的回调函数中,要绑定作用域 this(通过 bind 方法)。(2)父组件传递给子组件方法的作用域是父组件实例化对象,无法改变。(3)组件事件回调函数方法的作用域是组件实例化对象(绑定父组件提供的方法就是父组件实例化对象),无法改变。在 EMAScript6 语法规范中,关于作用域的常见问题如下。(1)当使用箭头函数作为 map 等方法的回调函数时,箭头函数的作用域是当前组件的实例化对象(即箭头函数的作用域是定义时的作用域),无须绑定作用域。(2)事件回调函数要绑定组件作用域。(3)父组件传递方法要绑定父组件作用域。总之,在 EMAScript6 语法规范中,组件方法的作用域是可以改变的。
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 帮我们做了这些。
为什么要使用 React. Children. map( props. children,( )=>)而不是 props. children. map ( ( ) => )?
因为不能保证 props. children 将是一个数组。以下面的代码为例。
在父组件内部,如果尝试使用 props.children. map 映射子对象,则会抛出错误,因为 props. children 是一个对象,而不是一个数组。如果有多个子元素, React 会使 props.children 成为一个数组,如下所示。
建议使用如下方式,避免在上一个案例中抛出错误。
何为 Children
在 JSX 表达式中,一个开始标签(比如<a>
)和一个关闭标签(比如</a>
)之间的内容会作为一个特殊的属性props.children
被自动传递给包含着它的组件。
这个属性有许多可用的方法,包括 React.Children.map
,React.Children.forEach
, React.Children.count
, React.Children.only
,React.Children.toArray
。
简述 react 事件机制
当用户在为 onClick 添加函数时,React 并没有将 Click 时间绑定在 DOM 上面
而是在 document 处监听所有支持的事件,当事件发生并冒泡至 document 处时,React 将事件内容封装交给中间层 SyntheticEvent(负责所有事件合成)
所以当事件触发的时候,对使用统一的分发函数 dispatchEvent 将指定函数执行。React 在自己的合成事件中重写了 stopPropagation 方法,将 isPropagationStopped 设置为 true,然后在遍历每一级事件的过程中根据此遍历判断是否继续执行。这就是 React 自己实现的冒泡机制
React 实现的移动应用中,如果出现卡顿,有哪些可以考虑的优化方案
增加
shouldComponentUpdate
钩子对新旧props
进行比较,如果值相同则阻止更新,避免不必要的渲染,或者使用PureReactComponent
替代Component
,其内部已经封装了shouldComponentUpdate
的浅比较逻辑对于列表或其他结构相同的节点,为其中的每一项增加唯一
key
属性,以方便React
的diff
算法中对该节点的复用,减少节点的创建和删除操作render
函数中减少类似onClick={() => {doSomething()}}
的写法,每次调用 render 函数时均会创建一个新的函数,即使内容没有发生任何变化,也会导致节点没必要的重渲染,建议将函数保存在组件的成员对象中,这样只会创建一次组件的
props
如果需要经过一系列运算后才能拿到最终结果,则可以考虑使用reselect
库对结果进行缓存,如果 props 值未发生变化,则结果直接从缓存中拿,避免高昂的运算代价webpack-bundle-analyzer
分析当前页面的依赖包,是否存在不合理性,如果存在,找到优化点并进行优化
React 中 refs 的作用是什么
Refs
是React
提供给我们的安全访问DOM
元素或者某个组件实例的句柄可以为元素添加
ref
属性然后在回调函数中接受该元素在DOM
树中的句柄,该值会作为回调函数的第一个参数返回
用户不同权限 可以查看不同的页面 如何实现?
Js 方式
根据用户权限类型,把菜单配置成 json, 没有权限的直接不显示
react-router 方式 在 route 标签上 添加 onEnter 事件,进入路由之前替换到首页
自己封装一个 privateRouter 组件 里面判断是否有权限,有的话返回
没有权限的话 component 返回一个提示信息的组件。
扩展一下,如果是根据用权限来判断是否隐藏组件该怎么做呢?
react 可以使用高阶组件,在高阶组件里面判断是否有权限,然后判断是否返回组件,无权限返回 null
vue 可以使用自定义指令,如果没有权限移除组件
React 数据持久化有什么实践吗?
封装数据持久化组件:
在 React 项目中,通过 redux 存储全局数据时,会有一个问题,如果用户刷新了网页,那么通过 redux 存储的全局数据就会被全部清空,比如登录信息等。这时就会有全局数据持久化存储的需求。首先想到的就是 localStorage,localStorage 是没有时间限制的数据存储,可以通过它来实现数据的持久化存储。
但是在已经使用 redux 来管理和存储全局数据的基础上,再去使用 localStorage 来读写数据,这样不仅是工作量巨大,还容易出错。那么有没有结合 redux 来达到持久数据存储功能的框架呢?当然,它就是 redux-persist。redux-persist 会将 redux 的 store 中的数据缓存到浏览器的 localStorage 中。其使用步骤如下:
(1)首先要安装 redux-persist:
(2)对于 reducer 和 action 的处理不变,只需修改 store 的生成代码,修改如下:
(3)在 index.js 中,将 PersistGate 标签作为网页内容的父标签:
这就完成了通过 redux-persist 实现 React 持久化本地数据存储的简单应用。
在生命周期中的哪一步你应该发起 AJAX 请求
我们应当将 AJAX 请求放到
componentDidMount
函数中执行,主要原因有下
React
下一代调和算法Fiber
会通过开始或停止渲染的方式优化应用性能,其会影响到componentWillMount
的触发次数。对于componentWillMount
这个生命周期函数的调用次数会变得不确定,React
可能会多次频繁调用componentWillMount
。如果我们将AJAX
请求放到componentWillMount
函数中,那么显而易见其会被触发多次,自然也就不是好的选择。如果我们将
AJAX
请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState
函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在componentDidMount
函数中进行AJAX
请求则能有效避免这个问题
react 有什么优点
提高应用性能
可以方便的在客户端和服务端使用
使用 jsx 模板进行数据渲染,可读性好
评论