腾讯前端经典 react 面试题汇总
概述一下 React 中的事件处理逻辑。
为了解决跨浏览器兼容性问题, React 会将浏览器原生事件( Browser Native Event)封装为合成事件( Synthetic Event)并传入设置的事件处理程序中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外, React 并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理(基于事件委托原理)。这样 React 在更新 DOM 时就不需要考虑如何处理附着在 DOM 上的事件监听器,最终达到优化性能的目的。
如果用索引值作为 key 会出现什么样的问题
若对数据进行逆序添加,逆序删除等破坏顺序的操作
则会产生没有必要的真实 DOM 更新,界面想过看不出区别,但是效力低,性能不好
如果结构中还包含输入类的 DOM
会产生错误的 DOM 更新===》界面会有问题
如果不存在对数据的逆序添加 逆序删除等破坏顺序操作,仅用于渲染展示,用 index 作为 key 也没有问题
react hooks,它带来了那些便利
代码逻辑聚合,逻辑复用
HOC 嵌套地狱
代替 class
React 中通常使用 类定义 或者 函数定义 创建组件:
在类定义中,我们可以使用到许多 React 特性,例如 state、 各种组件生命周期钩子等,但是在函数定义中,我们却无能为力,因此 React 16.8 版本推出了一个新功能 (React Hooks),通过它,可以更好的在函数定义组件中使用 React 特性。
好处:
跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
类定义更为复杂
不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
时刻需要关注 this 的指向问题;
代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
状态与 UI 隔离: 正是由于 Hooks 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。
注意:
避免在 循环/条件判断/嵌套函数 中调用 hooks,保证调用顺序的稳定;
只有 函数定义组件 和 hooks 可以调用 hooks,避免在 类组件 或者 普通函数 中调用;
不能在 useEffect 中使用 useState,React 会报错提示;
类组件不会被替换或废弃,不需要强制改造类组件,两种方式能并存;
重要钩子
状态钩子 (useState): 用于定义组件的 State,其到类定义中 this.state 的功能;
生命周期钩子 (useEffect):
类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (useEffect),这里可以看做 componentDidMount、componentDidUpdate 和 componentWillUnmount 的结合。
useEffect(callback, [source])接受两个参数
callback: 钩子回调函数;
source: 设置触发条件,仅当 source 发生改变时才会触发;
useEffect 钩子在没有传入[source]参数时,默认在每次 render 时都会优先调用上次保存的回调中返回的函数,后再重新调用回调;
通过第二个参数,我们便可模拟出几个常用的生命周期:
componentDidMount: 传入[]时,就只会在初始化时调用一次
componentWillUnmount: 传入[],回调中的返回的函数也只会被最终执行一次
mounted: 可以使用 useState 封装成一个高度可复用的 mounted 状态;
componentDidUpdate: useEffect 每次均会执行,其实就是排除了 DidMount 后即可;
其它内置钩子:
useContext
: 获取 context 对象useReducer
: 类似于 Redux 思想的实现,但其并不足以替代 Redux,可以理解成一个组件内部的 redux:并不是持久化存储,会随着组件被销毁而销毁;
属于组件内部,各个组件是相互隔离的,单纯用它并无法共享数据;
配合 useContext`的全局性,可以完成一个轻量级的 Redux;(easy-peasy)
useCallback
: 缓存回调函数,避免传入的回调每次都是新的函数实例而导致依赖组件重新渲染,具有性能优化的效果;useMemo
: 用于缓存传入的 props,避免依赖的组件每次都重新渲染;useRef
: 获取组件的真实节点;useLayoutEffect
DOM 更新同步钩子。用法与 useEffect 类似,只是区别于执行时间点的不同
useEffect 属于异步执行,并不会等待 DOM 真正渲染后执行,而 useLayoutEffect 则会真正渲染后才触发;
可以获取更新后的 state;
自定义钩子(useXxxxx): 基于 Hooks 可以引用其它 Hooks 这个特性,我们可以编写自定义钩子,如上面的 useMounted。又例如,我们需要每个页面自定义标题:
React Portal 有哪些使用场景
在以前, react 中所有的组件都会位于 #app 下,而使用 Portals 提供了一种脱离 #app 的组件
因此 Portals 适合脱离文档流(out of flow) 的组件,特别是 position: absolute 与 position: fixed 的组件。比如模态框,通知,警告,goTop 等。
以下是官方一个模态框的示例,可以在以下地址中测试效果
React Hooks 当中的 useEffect 是如何区分生命周期钩子的
useEffect 可以看成是
componentDidMount
,componentDidUpdate
和componentWillUnmount
三者的结合。useEffect(callback, [source])接收两个参数,调用方式如下
生命周期函数的调用主要是通过第二个参数[source]来进行控制,有如下几种情况:
[source]
参数不传时,则每次都会优先调用上次保存的函数中返回的那个函数,然后再调用外部那个函数;[source]
参数传[]时,则外部的函数只会在初始化时调用一次,返回的那个函数也只会最终在组件卸载时调用一次;[source]
参数有值时,则只会监听到数组中的值发生变化后才优先调用返回的那个函数,再调用外部的函数。
createElement 和 cloneElement 有什么区别?
createElement 是 JSX 被转载得到的,在 React 中用来创建 React 元素(即虚拟 DOM)的内容。cloneElement 用于复制元素并传递新的 props。
redux 有什么缺点
一个组件所需要的数据,必须由父组件传过来,而不能像
flux
中直接从store
取。当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新
render
,可能会有效率影响,或者需要写复杂的shouldComponentUpdate
进行判断。
描述事件在 React 中的处理方式。
为了解决跨浏览器兼容性问题, React 中的事件处理程序将传递 SyntheticEvent 的实例,它是跨浏览器事件的包装器。这些 SyntheticEvent 与你习惯的原生事件具有相同的接口,它们在所有浏览器中都兼容。React 实际上并没有将事件附加到子节点本身。而是通过事件委托模式,使用单个事件监听器监听顶层的所有事件。这对于性能是有好处的。这也意味着在更新 DOM 时, React 不需要担心跟踪事件监听器。
redux 中间件
中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer 。这种机制可以让我们改变数据流,实现如异步 action ,action 过 滤,日志输出,异常报告等功能
常见的中间件:
redux-logger:提供日志输出;
redux-thunk:处理异步操作;
redux-promise: 处理异步操作;
actionCreator 的返回值是 promise
在 React 中元素( element)和组件( component)有什么区别?
简单地说,在 React 中元素(虛拟 DOM)描述了你在屏幕上看到的 DOM 元素。换个说法就是,在 React 中元素是页面中 DOM 元素的对象表示方式。在 React 中组件是一个函数或一个类,它可以接受输入并返回一个元素。注意:工作中,为了提高开发效率,通常使用 JSX 语法表示 React 元素(虚拟 DOM)。在编译的时候,把它转化成一个 React. createElement 调用方法。
如何使用 4.0 版本的 React Router?
React Router 4.0 版本中对 hashHistory 做了迁移,执行包安装命令 npm install react-router-dom 后,按照如下代码进行使用即可。
key 的作用
是给每一个 vnode 的唯一 id,可以依靠 key,更准确,更快的拿到 oldVnode 中对应的 vnode 节点
如果没有 key,React 会认为 div 的第一个子节点由 p 变成 h3,第二个子节点由 h3 变成 p,则会销毁这两个节点并重新构造。
但是当我们用 key 指明了节点前后对应关系后,React 知道 key === "ka"
的 p 更新后还在,所以可以复用该节点,只需要交换顺序。
key 是 React 用来追踪哪些列表元素被修改、被添加或者被移除的辅助标志。
在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React diff 算法中,React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重新渲染。同时,React 还需要借助 key 来判断元素与本地状态的关联关系。
componentWillReceiveProps 调用时机
已经被废弃掉
当 props 改变的时候才调用,子组件第二次接收到 props 的时候
如何用 React 构建( build)生产模式?
通常,使用 Webpack 的 DefinePlugin 方法将 NODE ENV 设置为 production。这将剥离 propType 验证和额外的警告。除此之外,还可以减少代码,因为 React 使用 Uglify 的 dead-code 来消除开发代码和注释,这将大大减少包占用的空间。
react 旧版生命周期函数
初始化阶段
getDefaultProps
:获取实例的默认属性getInitialState
:获取每个实例的初始化状态componentWillMount
:组件即将被装载、渲染到页面上render
:组件在这里生成虚拟的DOM
节点componentDidMount
:组件真正在被装载之后
运行中状态
componentWillReceiveProps
:组件将要接收到属性的时候调用shouldComponentUpdate
:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止render
调用,后面的函数不会被继续执行了)componentWillUpdate
:组件即将更新不能修改属性和状态render
:组件重新描绘componentDidUpdate
:组件已经更新
销毁阶段
componentWillUnmount
:组件即将销毁
在 React 中遍历的方法有哪些?
(1)遍历数组:map && forEach
(2)遍历对象:map && for in
React-Router 的实现原理是什么?
客户端路由实现的思想:
基于 hash 的路由:通过监听
hashchange
事件,感知 hash 的变化改变 hash 可以直接通过 location.hash=xxx
基于 H5 history 路由:
改变 url 可以通过 history.pushState 和 resplaceState 等,会将 URL 压入堆栈,同时能够应用
history.go()
等 API监听 url 的变化可以通过自定义事件触发实现
react-router 实现的思想:
基于
history
库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render
React 16.X 中 props 改变后在哪个生命周期中处理
在 getDerivedStateFromProps 中进行处理。
这个生命周期函数是为了替代componentWillReceiveProps
存在的,所以在需要使用componentWillReceiveProps
时,就可以考虑使用getDerivedStateFromProps
来进行替代。
两者的参数是不相同的,而getDerivedStateFromProps
是一个静态函数,也就是这个函数不能通过 this 访问到 class 的属性,也并不推荐直接访问属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,根据新传入的 props 来映射到 state。
需要注意的是,如果 props 传入的内容不需要影响到你的 state,那么就需要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的末尾:
为什么虚拟 dom 会提高性能
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要 的 dom 操作,从而提高性能
具体实现步骤如下:
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树, 插到文档当中;
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记 录两棵树差异;
把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
setState 方法的第二个参数有什么用?使用它的目的是什么?
它是一个回调函数,当 setState 方法执行结束并重新渲染该组件时调用它。在工作中,更好的方式是使用 React 组件生命周期之——“存在期”的生命周期方法,而不是依赖这个回调函数。
React 中 setState 的第二个参数作用是什么?
setState
的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate
生命周期内执行。通常建议使用 componentDidUpdate
来代替此方式。在这个回调函数中你可以拿到更新后 state
的值:
评论