铜九银十 react 考察点梳理
React 中可以在 render 访问 refs 吗?为什么?
不可以,render 阶段 DOM 还没有生成,无法获取 DOM。DOM 的获取需要在 pre-commit 阶段和 commit 阶段:
setState 是同步异步?为什么?实现原理?
1. setState 是同步执行的
setState 是同步执行的,但是 state 并不一定会同步更新
2. setState 在 React 生命周期和合成事件中批量覆盖执行
在 React 的生命周期钩子和合成事件中,多次执行 setState,会批量执行
具体表现为,多次同步执行的 setState,会进行合并,类似于 Object.assign,相同的 key,后面的会覆盖前面的
当遇到多个 setState 调用时候,会提取单次传递 setState 的对象,把他们合并在一起形成一个新的
单一对象,并用这个单一的对象去做 setState 的事情,就像 Object.assign 的对象合并,后一个
key 值会覆盖前面的 key 值
经过 React 处理的事件是不会同步更新 this.state 的. 通过 addEventListener || setTimeout/setInterval 的方式处理的则会同步更新。
为了合并 setState,我们需要一个队列来保存每次 setState 的数据,然后在一段时间后执行合并操作和更新 state,并清空这个队列,然后渲染组件。
如何在 ReactJS 的 Props 上应用验证?
当应用程序在开发模式下运行时,React 将自动检查咱们在组件上设置的所有 props
,以确保它们具有正确的数据类型。对于不正确的类型,开发模式下会在控制台中生成警告消息,而在生产模式中由于性能影响而禁用它。强制的 props
用 isRequired
定义的。下面是一组预定义的 prop 类型:
React.PropTypes.string
React.PropTypes.number
React.PropTypes.func
React.PropTypes.node
React.PropTypes.bool 例如,咱们为用户组件定义了如下的
propTypes
组件通信的方式有哪些
⽗组件向⼦组件通讯: ⽗组件可以向⼦组件通过传 props 的⽅式,向⼦组件进⾏通讯
⼦组件向⽗组件通讯: props+回调的⽅式,⽗组件向⼦组件传递 props 进⾏通讯,此 props 为作⽤域为⽗组件⾃身的函 数,⼦组件调⽤该函数,将⼦组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中
兄弟组件通信: 找到这两个兄弟节点共同的⽗节点,结合上⾯两种⽅式由⽗节点转发信息进⾏通信
跨层级通信: Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如当前认证的⽤户、主题或⾸选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过
发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,我们可以通过引⼊event 模块进⾏通信
全局状态管理⼯具: 借助 Redux 或者 Mobx 等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态
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 版本差异
react16.8 hooks
React 16 之后有三个生命周期被废弃(但并未删除)
componentWillMount
componentWillReceiveProps
componentWillUpdate
官方计划在 17 版本完全删除这三个函数,只保留 UNSAVE_前缀的三个函数,目的是为了向下兼容,
react 16.4 新增
getSnapshotBeforeUpdate
getDerivedStateFromProps
对于废弃的生命周期函数,官方会采用逐步迁移的方式来实现版本的迁移:
16.3:为不安全的生命周期引入别名,UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。(旧的生命周期名称和新的别名都可以在此版本中使用。)
未来 16.x 版本:为 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 启用废弃告警。(旧的生命周期名称和新的别名都将在这个版本中工作,但是旧的名称在开发模式下会产生一个警告。)
17.0:删除 componentWillMount、componentWillReceiveProps 和 componentWillUpdate。(在此版本之后,只有新的 “UNSAFE_” 生命周期名称可以使用。)。
React 组件命名推荐的方式是哪个?
通过引用而不是使用来命名组件 displayName。
使用 displayName 命名组件:
React 推荐的方法:
useEffect 和 useLayoutEffect 的区别
useEffect
基本上 90%的情况下,都应该用这个,这个是在 render 结束后,你的 callback 函数执行,但是不会 block browser painting,算是某种异步的方式吧,但是 class 的 componentDidMount 和 componentDidUpdate 是同步的,在 render 结束后就运行,useEffect 在大部分场景下都比 class 的方式性能更好.
useLayoutEffect
这个是用在处理 DOM 的时候,当你的 useEffect 里面的操作需要处理 DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect 里面的 callback 函数会在**DOM 更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,**阻塞了浏览器的绘制.
constructor 为什么不先渲染?
由 ES6 的继承规则得知,不管子类写不写 constructor,在 new 实例的过程都会给补上 constructor。
所以:constructor 钩子函数并不是不可缺少的,子组件可以在一些情况略去。比如不自己的 state,从 props 中获取的情况
参考:前端react面试题详细解答**
React 与 Vue 的 diff 算法有何不同?
diff 算法是指生成更新补丁的方式,主要应用于虚拟 DOM 树变化后,更新真实 DOM。所以 diff 算法一定存在这样一个过程:触发更新 → 生成补丁 → 应用补丁。
React 的 diff 算法,触发更新的时机主要在 state 变化与 hooks 调用之后。此时触发虚拟 DOM 树变更遍历,采用了深度优先遍历算法。但传统的遍历方式,效率较低。为了优化效率,使用了分治的方式。将单一节点比对转化为了 3 种类型节点的比对,分别是树、组件及元素,以此提升效率。
树比对:由于网页视图中较少有跨层级节点移动,两株虚拟 DOM 树只对同一层次的节点进行比较。
组件比对:如果组件是同一类型,则进行树比对,如果不是,则直接放入到补丁中。
元素比对:主要发生在同层级中,通过标记节点操作生成补丁,节点操作对应真实的 DOM 剪裁操作。
以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停恢复,节点与树分别采用了 FiberNode 与 FiberTree 进行重构。fiberNode 使用了双链表的结构,可以直接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲完成。workInProgress 更新完成后,再通过修改 current 相关指针指向新节点。
Vue 的整体 diff 策略与 React 对齐,虽然缺乏时间切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,后期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其他的场景几乎都可以使用防抖和节流去提高响应性能。
为什么 React 要用 JSX?
JSX 是一个 JavaScript 的语法扩展,或者说是一个类似于 XML 的 ECMAScript 语法扩展。它本身没有太多的语法定义,也不期望引入更多的标准。
其实 React 本身并不强制使用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于使用 React.createElement 函数。代码如下:
而 JSX 更像是一种语法糖,通过类似 XML 的描述方式,描写函数对象。在采用 JSX 之后,这段代码会这样写:
通过对比,可以清晰地发现,代码变得更为简洁,而且代码结构层次更为清晰。
因为 React 需要将组件转化为虚拟 DOM 树,所以在编写代码时,实际上是在手写一棵结构树。而 XML 在树结构的描述上天生具有可读性强的优势。
但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会使用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。
总结: JSX 是一个 JavaScript 的语法扩展,结构类似 XML。JSX 主要用于声明 React 元素,但 React 中并不强制使用 JSX。即使使用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。
React 团队并不想引入 JavaScript 本身以外的开发体系。而是希望通过合理的关注点分离保持组件开发的纯粹性。
什么原因会促使你脱离 create-react-app 的依赖
当你想去配置 webpack 或 babel presets。
react 强制刷新
component.forceUpdate() 一个不常用的生命周期方法, 它的作用就是强制刷新
官网解释如下
默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。如果 render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。
调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate() 方法。如果标记发生变化,React 仍将只更新 DOM。
通常你应该避免使用 forceUpdate(),尽量在 render() 中使用 this.props 和 this.state。
shouldComponentUpdate 在初始化 和 forceUpdate 不会执行
类组件(Class component)和函数式组件(Functional component)之间有何不同
类组件不仅允许你使用更多额外的功能,如组件自身的状态和生命周期钩子,也能使组件直接访问
store
并维持状态当组件仅是接收
props
,并将组件自身渲染到页面时,该组件就是一个 '无状态组件(stateless component)',可以使用一个纯函数来创建这样的组件。这种组件也被称为哑组件(dumb components)或展示组件
为什么 React 并不推荐优先考虑使用 Context?
Context 目前还处于实验阶段,可能会在后面的发行版本中有很大的变化,事实上这种情况已经发生了,所以为了避免给今后升级带来大的影响和麻烦,不建议在 app 中使用 context。
尽管不建议在 app 中使用 context,但是独有组件而言,由于影响范围小于 app,如果可以做到高内聚,不破坏组件树之间的依赖关系,可以考虑使用 context
对于组件之间的数据通信或者状态管理,有效使用 props 或者 state 解决,然后再考虑使用第三方的成熟库进行解决,以上的方法都不是最佳的方案的时候,在考虑 context。
context 的更新需要通过 setState()触发,但是这并不是很可靠的,Context 支持跨组件的访问,但是如果中间的子组件通过一些方法不影响更新,比如 shouldComponentUpdate() 返回 false 那么不能保证 Context 的更新一定可以使用 Context 的子组件,因此,Context 的可靠性需要关注
对 React 中 Fragment 的理解,它的使用场景是什么?
在 React 中,组件返回的元素只能有一个根元素。为了不添加多余的 DOM 节点,我们可以使用 Fragment 标签来包裹所有的元素,Fragment 标签不会渲染出任何元素。React 官方对 Fragment 的解释:
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
对 React-Intl 的理解,它的工作原理?
React-intl 是雅虎的语言国际化开源项目 FormatJS 的一部分,通过其提供的组件和 API 可以与 ReactJS 绑定。
React-intl 提供了两种使用方法,一种是引用 React 组件,另一种是直接调取 API,官方更加推荐在 React 项目中使用前者,只有在无法使用 React 组件的地方,才应该调用框架提供的 API。它提供了一系列的 React 组件,包括数字格式化、字符串格式化、日期格式化等。
在 React-intl 中,可以配置不同的语言包,他的工作原理就是根据需要,在语言包之间进行切换。
React 中 setState 的第二个参数作用是什么?
setState
的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate
生命周期内执行。通常建议使用 componentDidUpdate
来代替此方式。在这个回调函数中你可以拿到更新后 state
的值:
在 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 直接可以支持。这个方法适合一些需要临时存储的场景。
对 componentWillReceiveProps 的理解
该方法当props
发生变化时执行,初始化render
时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用this.setState()
来更新你的组件状态,旧的属性还是可以通过this.props
来获取,这里调用更新状态是安全的,并不会触发额外的render
调用。
使用好处: 在这个生命周期中,可以在子组件的 render 函数执行前获取新的 props,从而更新子组件自己的 state。 可以将数据请求放在这里进行执行,需要传的参数则从 componentWillReceiveProps(nextProps)中获取。而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。
componentWillReceiveProps 在初始化 render 的时候不会执行,它会在 Component 接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。
评论