写点什么

铜九银十 react 考察点梳理

作者:beifeng1996
  • 2022-10-12
    浙江
  • 本文字数:6772 字

    阅读完需:约 22 分钟

React 中可以在 render 访问 refs 吗?为什么?

<>  <span id="name" ref={this.spanRef}>{this.state.title}</span>  <span>{     this.spanRef.current ? '有值' : '无值'  }</span></>
复制代码


不可以,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,以确保它们具有正确的数据类型。对于不正确的类型,开发模式下会在控制台中生成警告消息,而在生产模式中由于性能影响而禁用它。强制的 propsisRequired定义的。下面是一组预定义的 prop 类型:


  • React.PropTypes.string

  • React.PropTypes.number

  • React.PropTypes.func

  • React.PropTypes.node

  • React.PropTypes.bool 例如,咱们为用户组件定义了如下的propTypes


  import PropTypes from "prop-types";  class User extends React.Component {    render() {      return (        <>          <h1>Welcome, {this.props.name}</h1>          <h2>Age, {this.props.age}</h2>        </>      );    }  }    User.propTypes = {    name: PropTypes.string.isRequired,    age: PropTypes.number.isRequired,  };
复制代码

组件通信的方式有哪些

  • ⽗组件向⼦组件通讯: ⽗组件可以向⼦组件通过传 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 命名组件:


export default React.createClass({  displayName: 'TodoApp',  // ...})
复制代码


React 推荐的方法:


export default class TodoApp extends React.Component {  // ...}
复制代码

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 函数。代码如下:


class Hello extends React.Component {  render() {    return React.createElement(        'div',        null,         `Hello ${this.props.toWhat}`      );  }}ReactDOM.render(  React.createElement(Hello, {toWhat: 'World'}, null),  document.getElementById('root'));
复制代码


而 JSX 更像是一种语法糖,通过类似 XML 的描述方式,描写函数对象。在采用 JSX 之后,这段代码会这样写:


class Hello extends React.Component {  render() {    return <div>Hello {this.props.toWhat}</div>;  }}ReactDOM.render(  <Hello toWhat="World" />,  document.getElementById('root'));
复制代码


通过对比,可以清晰地发现,代码变得更为简洁,而且代码结构层次更为清晰。


因为 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 添加额外节点。


import React, { Component, Fragment } from 'react'
// 一般形式render() { return ( <React.Fragment> <ChildA /> <ChildB /> <ChildC /> </React.Fragment> );}// 也可以写成以下形式render() { return ( <> <ChildA /> <ChildB /> <ChildC /> </> );}
复制代码

对 React-Intl 的理解,它的工作原理?

React-intl 是雅虎的语言国际化开源项目 FormatJS 的一部分,通过其提供的组件和 API 可以与 ReactJS 绑定。


React-intl 提供了两种使用方法,一种是引用 React 组件,另一种是直接调取 API,官方更加推荐在 React 项目中使用前者,只有在无法使用 React 组件的地方,才应该调用框架提供的 API。它提供了一系列的 React 组件,包括数字格式化、字符串格式化、日期格式化等。


在 React-intl 中,可以配置不同的语言包,他的工作原理就是根据需要,在语言包之间进行切换。

React 中 setState 的第二个参数作用是什么?

setState 的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate 生命周期内执行。通常建议使用 componentDidUpdate 来代替此方式。在这个回调函数中你可以拿到更新后 state 的值:


this.setState({    key1: newState1,    key2: newState2,    ...}, callback) // 第二个参数是 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)时被触发,一般用于父组件状态更新时子组件的重新渲染。

用户头像

beifeng1996

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
铜九银十react考察点梳理_React_beifeng1996_InfoQ写作社区