一天梳理完 react 面试高频知识点
描述事件在 React 中的处理方式。
为了解决跨浏览器兼容性问题, React 中的事件处理程序将传递 SyntheticEvent 的实例,它是跨浏览器事件的包装器。这些 SyntheticEvent 与你习惯的原生事件具有相同的接口,它们在所有浏览器中都兼容。React 实际上并没有将事件附加到子节点本身。而是通过事件委托模式,使用单个事件监听器监听顶层的所有事件。这对于性能是有好处的。这也意味着在更新 DOM 时, React 不需要担心跟踪事件监听器。
React 中的 key 是什么?为什么它们很重要?
key 可以帮助 React 跟踪循环创建列表中的虚拟 DOM 元素,了解哪些元素已更改、添加或删除。每个绑定 key 的虚拟 DOM 元素,在兄弟元素之间都是独一无二的。在 React 的和解过程中,比较新的虛拟 DOM 树与上一个虛拟 DOM 树之间的差异,并映射到页面中。key 使 React 处理列表中虛拟 DOM 时更加高效,因为 React 可以使用虛拟 DOM 上的 key 属性,快速了解元素是新的、需要删除的,还是修改过的。如果没有 key,Rεat 就不知道列表中虚拟 DOM 元素与页面中的哪个元素相对应。所以在创建列表的时候,不要忽略 key。
react 旧版生命周期函数
初始化阶段
getDefaultProps:获取实例的默认属性getInitialState:获取每个实例的初始化状态componentWillMount:组件即将被装载、渲染到页面上render:组件在这里生成虚拟的DOM节点componentDidMount:组件真正在被装载之后
运行中状态
componentWillReceiveProps:组件将要接收到属性的时候调用shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)componentWillUpdate:组件即将更新不能修改属性和状态render:组件重新描绘componentDidUpdate:组件已经更新
销毁阶段
componentWillUnmount:组件即将销毁
React 中 Diff 算法的原理是什么?
原理如下。(1)节点之间的比较。节点包括两种类型:一种是 React 组件,另一种是 HTML 的 DOM。如果节点类型不同,按以下方式比较。如果 HTML DOM 不同,直接使用新的替换旧的。如果组件类型不同,也直接使用新的替换旧的。如果 HTML DOM 类型相同,按以下方式比较。在 React 里样式并不是一个纯粹的字符串,而是一个对象,这样在样式发生改变时,只需要改变替换变化以后的样式。修改完当前节点之后,递归处理该节点的子节点。如果组件类型相同,按以下方式比较。如果组件类型相同,使用 React 机制处理。一般使用新的 props 替换旧的 props,并在之后调用组件的 componentWillReceiveProps 方法,之前组件的 render 方法会被调用。节点的比较机制开始递归作用于它的子节点。(2)两个列表之间的比较。一个节点列表中的一个节点发生改变, React 无法很妤地处理这个问题。循环新旧两个列表,并找出不同,这是 React 唯一的处理方法。但是,有一个办法可以把这个算法的复杂度降低。那就是在生成一个节点列表时给每个节点上添加一个 key。这个 key 只需要在这一个节点列表中唯一,不需要全局唯一。(3)取舍需要注意的是,上面的启发式算法基于两点假设。类型相近的节点总是生成同样的树,而类型不同的节点也总是生成不同的树可以为多次 render 都表现稳定的节点设置 key。上面的节点之间的比较算法基本上就是基于这两个假设而实现的。要提高 React 应用的效率,需要按照这两点假设来开发。
请说岀 React 从 EMAScript5 编程规范到 EMAScript6 编程规范过程中的几点改变。
主要改变如下。(1)创建组件的方法不同。EMAScript5 版本中,定义组件用 React.createClass。EMAScript6 版本中,定义组件要定义组件类,并继承 Component 类。(2)定义默认属性的方法不同。EMAScript5 版本中,用 getDefaultProps 定义默认属性。EMAScript6 版本中,为组件定义 defaultProps 静态属性,来定义默认属性。(3)定义初始化状态的方法不同。EMAScript5 版本中,用 getInitialState 定义初始化状态。EMAScript6 版本中,在构造函数中,通过 this. state 定义初始化状态。注意:构造函数的第一个参数是属性数据,一定要用 super 继承。(4)定义属性约束的方法不同。EMAScript5 版本中,用 propTypes 定义属性的约束。EMAScript6 版本中,为组件定义 propsTypes 静态属性,来对属性进行约束。(5)使用混合对象、混合类的方法不同。EMAScript5 版本中,通过 mixins 继承混合对象的方法。EMAScript6 版本中,定义混合类,让混合类继承 Component 类,然后让组件类继承混合类,实现对混合类方法的继承。(6)绑定事件的方法不同。EMAScript5 版本中,绑定的事件回调函数作用域是组件实例化对象。EMAScript6 版本中,绑定的事件回调函数作用域是 null。(7)父组件传递方法的作用域不同。EMAScript5 版本中,作用域是父组件。 EMAScript6 版本中,变成了 null。(8)组件方法作用域的修改方法不同。EMAScript5 版本中,无法改变作用域。EMAScript6 版本中,作用域是可以改变的。
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]参数有值时,则只会监听到数组中的值发生变化后才优先调用返回的那个函数,再调用外部的函数。
react-router 里的<Link>标签和<a>标签有什么区别
对比
<a>,Link组件避免了不必要的重渲染
react 的渲染过程中,兄弟节点之间是怎么处理的?也就是 key 值不一样的时候
通常我们输出节点的时候都是 map 一个数组然后返回一个
ReactNode,为了方便react内部进行优化,我们必须给每一个reactNode添加key,这个key prop在设计值处不是给开发者用的,而是给 react 用的,大概的作用就是给每一个reactNode添加一个身份标识,方便 react 进行识别,在重渲染过程中,如果 key 一样,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新,如果 key 不一样,则 react 先销毁该组件,然后重新创建该组件
什么是 React 的 refs?为什么它们很重要
refs 允许你直接访问 DOM 元素或组件实例。为了使用它们,可以向组件添加个 ref 属性。如果该属性的值是一个回调函数,它将接受底层的 DOM 元素或组件的已挂载实例作为其第一个参数。可以在组件中存储它。
如果该属性值是一个字符串, React 将会在组件实例化对象的 refs 属性中,存储一个同名属性,该属性是对这个 DOM 元素的引用。可以通过原生的 DOM API 操作它。
useEffect(fn, []) 和 componentDidMount 有什么差异
useEffect会捕获props和 state。所以即便在回调函数里,你拿到的还是初始的 props 和 state。如果想得到“最新”的值,可以使用 ref。
在 ReactNative 中,如何解决 adb devices 找不到连接设备的问题?
在使用 Genymotion 时,首先需要在 SDK 的 platform-tools 中加入环境变量,然后在 Genymotion 中单击 Setting,选择 ADB 选项卡,单击 Use custom Android SDK tools,浏览本地 SDK 的位置,单击 OK 按钮就可以了。启动虛拟机后,在 cmd 中输入 adb devices 可以查看设备。
我现在有一个 button,要用 react 在上面绑定点击事件,要怎么做?
你觉得你这样设置点击事件会有什么问题吗?
由于
onClick使用的是匿名函数,所有每次重渲染的时候,会把该onClick当做一个新的prop来处理,会将内部缓存的onClick事件进行重新赋值,所以相对直接使用函数来说,可能有一点的性能下降
修改
这段代码有什么问题?
render ( < App intro=" 前端技术专业学习平台">,ickt )在页面中正常输出“有课前端网-前端技术专业学习平台”。但是这种写法很少使用,并不是常用的写法。React 允许对 setState 方法传递一个函数,它接收到先前的状态和属性数据并返回一个需要修改的状态对象,正如我们在上面所做的那样。它不但没有问题,而且如果根据以前的状态( state)以及属性来修改当前状态,推荐使用这种写法。
什么是纯函数?
纯函数是不依赖并且不会在其作用域之外修改变量状态的函数。本质上,纯函数始终在给定相同参数的情况下返回相同结果。
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 来判断元素与本地状态的关联关系。
setState 方法的第二个参数有什么用?使用它的目的是什么?
它是一个回调函数,当 setState 方法执行结束并重新渲染该组件时调用它。在工作中,更好的方式是使用 React 组件生命周期之——“存在期”的生命周期方法,而不是依赖这个回调函数。
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 中页面重新加载时怎样保留数据?
这个问题就设计到了数据持久化, 主要的实现方式有以下几种:
Redux: 将页面的数据存储在 redux 中,在重新加载页面时,获取 Redux 中的数据;
data.js: 使用 webpack 构建的项目,可以建一个文件,data.js,将数据保存 data.js 中,跳转页面后获取;
sessionStorge: 在进入选择地址页面之前,componentWillUnMount 的时候,将数据存储到 sessionStorage 中,每次进入页面判断 sessionStorage 中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的 sessionStorage,保证下次进入是初始化的数据
history API: History API 的
pushState函数可以给历史记录关联一个任意的可序列化state,所以可以在路由push的时候将当前页面的一些信息存到state中,下次返回到这个页面的时候就能从state里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。
diff 算法?
把树形结构按照层级分解,只比较同级元素
给列表结构的每个单元添加唯一的 key 属性,方便比较
React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)
合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个 事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。
新版生命周期
在新版本中,React 官方对生命周期有了新的 变动建议:
使用
getDerivedStateFromProps替换componentWillMount;使用
getSnapshotBeforeUpdate替换componentWillUpdate;避免使用
componentWillReceiveProps;
其实该变动的原因,正是由于上述提到的
Fiber。首先,从上面我们知道 React 可以分成reconciliation与commit两个阶段,对应的生命周期如下:
reconciliation
componentWillMountcomponentWillReceivePropsshouldComponentUpdatecomponentWillUpdate
commit
componentDidMountcomponentDidUpdatecomponentWillUnmount
在
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 来直接阻止后面的逻辑执行,通常是用于做条件渲染,优化渲染的性能。









评论