前端面试,备考第 20 天—— React 基础,再进一步

Posted 前端修罗场

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端面试,备考第 20 天—— React 基础,再进一步相关的知识,希望对你有一定的参考价值。

  • ⭐️ 本文首发自 前端修罗场(点击加入社区,参与学习打卡,获取奖励)是一个由资深开发者独立运行的专业技术社区,我专注 Web 技术、答疑解惑、面试辅导以及职业发展。
  • 🏆 目前就职于全球前100强知名外企,曾就职于头部互联网企业 | 清华大学出版社签约作者 | CSDN 银牌讲师 | 蓝桥云课2021年度人气作者Top2 | CSDN 博客专家 | 阿里云专家博主 | 华为云享专家
    出品著作
    《ElementUI 详解与实战》| 《ThreeJS 在网页中创建动画》|《PWA 渐进式Web应用开发》
  • 🔥 本文已收录至前端面试题库专栏 《前端面试宝典》(点击订阅)
  • 💯 此专栏文章针对准备找工作的应届生、初中高级前端工程师设计,以及想要巩固前端基础知识的开发者文章中包含 90% 的面试考点和实际开发中需要掌握的知识,内容按点分类,环环相扣重要的是,形成了前端知识技能树,多数同学已经通过面试拿到 offer,并提升了自己的前端知识面。
    作者对重点考题做了详细解析和重点标注(建议在 PC 上阅读),并通过图解、思维导图的方式帮你降低了学习成本,节省备考时间,尽可能快地提升。可以说目前市面上没有像这样完善的面试备考指南!
  • ❤️ 现在订阅专栏,私聊博主,即可享受一次免费的模拟面试、简历修改、答疑服务拉你进前端答疑互助交流群,享受博主答疑服务和备考服务,优质文章分享。【私聊备注:前端修罗场】
  • 🚀 加入前端修罗场,从此快人一步,和一群人一起更进一步
  • 👉🏻 目前优惠中,即将恢复至原价 ~

紧接上一篇 React 基础一点通,本文将进一步讲解 React 基础与核心知识。

1. 对有状态组件和无状态组件的理解及使用场景

(1)有状态组件

特点:

  • 类组件
  • 有继承
  • 可以使用 this
  • 可以使用 react 的生命周期
  • 使用较多,容易频繁触发生命周期钩子函数,影响性能
  • 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state 进行渲染。

使用场景:

  • 需要使用到状态的。
  • 需要使用状态操作组件的(无状态组件的也可以实现新版本 react hooks 也可实现)

总结:

类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。 当一个类组件不需要管理自身状态时,也可称为无状态组件。

(2)无状态组件

特点:

  • 不依赖自身的状态 state
  • 可以是类组件或者函数组件。
  • 可以完全避免使用 this 关键字。(由于使用的是箭头函数事件无需绑定
  • 更高的性能当不需要使用生命周期钩子时,应该首先使用无状态函数组件
  • 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。

使用场景:

  • 组件不需要管理 state,纯展示

优点:

  • 简化代码、专注于 render
  • 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用
  • 视图和数据的解耦分离

缺点:

  • 无法使用 ref
  • 无生命周期方法
  • 无法控制组件的重渲染,因为无法使用 shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染

总结:

组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。 当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。比如自定义的 <Button/><Input /> 等组件。

2. 对 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 />
    </>
  );

3. React 如何获取组件对应的 DOM 元素?

可以用 ref 来获取某个子节点的实例,然后通过当前 class 组件实例的一些特定属性来直接获取子节点实例。ref 有三种实现方法:

  • 字符串格式:字符串格式,这是 React16 版本之前用得最多的,例如:<p ref="info">span</p>
  • 函数格式:ref 对应一个方法,该方法有一个参数,也就是对应的节点实例,例如:<p ref=ele => this.info = ele></p>
  • createRef方法:React 16 提供的一个API,使用 React.createRef() 来实现

4. 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 阶段:

5. 对 React 的插槽(Portals)的理解,如何使用,有哪些使用场景(模态框)

React 官方对 Portals 的定义:

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

场景:对话框、模态框

Portals 是React 16提供的官方解决方案,使得组件可以脱离父组件层级挂载在DOM树的任何位置。通俗来讲,就是我们** render 一个组件,但这个组件的 DOM 结构并不在本组件内。**

Portals语法如下:

ReactDOM.createPortal(child, container);
  • 第一个参数 child 是可渲染的 React 子项,比如元素,字符串或者片段等;
  • 第二个参数 container 是一个 DOM 元素。

一般情况下,组件的render函数返回的元素会被挂载在它的父级组件上:

import DemoComponent from './DemoComponent';
render() 
  // DemoComponent元素会被挂载在id为parent的div的元素上
  return (
    <div id="parent">
        <DemoComponent />
    </div>
  );

然而,有些元素需要被挂载在更高层级的位置。最典型的应用场景:当父组件具有 overflow: hidden或者 z-index 的样式设置时,组件有可能被其他元素遮挡,这时就可以考虑要不要使用 Portal 使组件的挂载脱离父组件。例如:对话框,模态窗。

import DemoComponent from './DemoComponent';
render() 
  // react会将DemoComponent组件直接挂载在真实的 dom 节点 domNode 上,生命周期还和16版本之前相同。
  return ReactDOM.createPortal(
    <DemoComponent />,
    domNode,
  );

6. 在React中如何避免不必要的 render ?

React 基于虚拟 DOM 和高效 Diff 算法的完美配合,实现了对 DOM 最小粒度的更新。大多数情况下,React 对 DOM 的渲染效率足以业务日常。但在个别复杂业务场景下,性能问题依然会困扰我们。此时需要采取一些措施来提升运行性能,其很重要的一个方向,就是避免不必要的渲染(Render)。这里提下优化的点:

  • shouldComponentUpdate 和 PureComponent

在 React 类组件中,可以利用 shouldComponentUpdate 或者 PureComponent 来减少因父组件更新而触发子组件的 render,从而达到目的。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false 即可。

  • 利用高阶组件

在函数组件中,并没有 shouldComponentUpdate 这个生命周期,可以利用高阶组件,封装一个类似 PureComponet 的功能

  • 使用 React.memo

React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,避免不必要的更新其实也是一个高阶组件,与 PureComponent 十分类似,但不同的是,React.memo只能用于函数组件。

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

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

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

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

8. 对 React context 的理解

作用域链原理。

关于 Context 的定义, React 官方也没有确定给出;我认为 Context 可以看做子组件到父组件上的作用域链。该对象上存储着所有组件的 Context 对象,可以通过 getChildContext() 获取。
在React中,数据传递一般使用 props 传递数据维持单向数据流,这样可以让组件之间的关系变得简单且可预测,但是单项数据流在某些场景中并不适用。单纯一对的父子组件传递并无问题,但要是组件之间层层依赖深入,props 就需要层层传递。显然这样做太繁琐了。

官方解释:Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

可以把context 当做是特定一个组件树内共享的 store,用来做数据传递。简单说就是,当你不想在组件树中通过逐层传递props 或者 state 的方式来传递数据时,可以使用 Context 来实现跨层级的组件数据传递。

JS 的代码块在执行期间,会创建一个相应的作用域链,这个作用域链记录着运行时 JS 代码块执行期间所能访问的活动对象,包括变量和函数,JS 程序通过作用域链访问到代码块内部或者外部的变量和函数。

假如以 JS 的作用域链作为类比,React 组件提供的 Context 对象其实就好比一个提供给子组件访问的作用域,而 Context 对象的属性可以看成作用域上的活动对象。由于组件的 Context 由其父节点链上_所有组件通过 __getChildContext() 返回的Context 对象组合而成,所以,组件通过 Context 是可以访问到其父组件链上__所有节点组件提供的 __Context 的属性_。

9. 为什么 React 并不推荐优先考虑使用 Context ?

  • 尽管不建议在 app 中使用 context,但是独有组件而言,由于影响范围小于 app,如果可以做到高内聚,不破坏组件树之间的依赖关系,可以考虑使用 context
  • 对于组件之间的数据通信或者状态管理,优先考虑使用 props 或者 state 解决,然后再考虑使用第三方的成熟库进行解决以上的方法都不是最佳的方案的时候,在考虑 context。
  • 如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
  • context 的更新需要通过 setState() 触发,但是这并不是很可靠的。Context 支持跨组件的访问,但是如果中间的子组件通过一些方法不影响更新,比如 shouldComponentUpdate() 返回 false 那么不能保证 Context 的更新一定可以使用 Context 的子组件,因此,Context 的可靠性需要关注

简而言之,只要你能确保 Context 是可控的,使用 Context 并无大碍,甚至如果能够合理的应用,Context 其实可以给 React 组件开发带来很强大的体验。

10. React 中什么是受控组件和非控组件?

(1)受控组件:组件的状态可通过 state 控制,且只能通过 setState() 来更新

在使用表单来收集用户输入时,例如等元素都要绑定一个 change 事件,当表单的状态发生变化,就会触发 onChange 事件更新组件的 state。这种组件在React中被称为受控组件,在受控组件中,组件渲染出的状态与它的 value 或 checked 属性相对应,react 通过这种方式消除了组件的局部状态,使整个状态可控。 react 官方推荐使用受控表单组件。

受控组件更新 state 的流程:

  • 可以通过初始 state 中设置表单的默认值
  • 每当表单的值发生变化时,调用 onChange 事件处理器
  • 事件处理器通过事件对象 e 拿到改变后的状态,并更新组件的 state
  • 一旦通过 setState 方法更新 state,就会触发视图的重新渲染,完成表单组件的更新

受控组件缺陷:

表单元素的值都是由React组件进行管理,当有多个输入框,或者多个这种组件时,如果想同时获取到全部的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种情况,出现了非受控组件。

(2)非受控组件:没有 state 值,获取组件的值要通过 ref

如果一个表单组件没有 value props(单选和复选按钮对应的是 checked props)时,就可以称为非受控组件。在非受控组件中,可以使用一个 ref 来从 DOM 获得表单值。而不是为每个状态更新编写一个事件处理程序。

React官方的解释:

要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以使用 ref 来从 DOM 节点中获取表单数据。

因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。

例如,下面的代码在非受控组件中接收单个属性:

class NameForm extends React.Component 
  constructor(props) 
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  
  handleSubmit(event) 
    alert('A name was submitted: ' + this.input.value);
    event.preventDefault();
  
  render() 
    return (
      <form onSubmit=this.handleSubmit>
        <label>
          Name:
          <input type="text" ref=(input) => this.input = input />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  

总结:页面中所有输入类的 DOM 如果是现用现取的称为非受控组件,而通过 setState 将输入的值维护到了 state 中,需要时再从 state 中取出,这里的数据就受到了 state 的控制,称为受控组件。

11. React 中 refs 的作用是什么?有哪些应用场景?

Refs 提供了一种方式,用于访问在 render 方法中创建的 React 元素或 DOM 节点。Refs 应该谨慎使用,如下场景使用 Refs 比较适合:

  • 处理焦点、文本选择或者媒体的控制
  • 触发必要的动画
  • 集成第三方 DOM 库

Refs 是使用 React.createRef() 方法创建的,他通过 ref 属性附加到 React 元素上。要在整个组件中使用 Refs,需要将 ref 在构造函数中分配给其实例属性:

class MyComponent extends React.Component 
  constructor(props) 
    super(props)
    this.myRef = React.createRef()
  
  render() 
    return <div ref=this.myRef />
  

由于函数组件没有实例,因此不能在函数组件上直接使用 ref

function MyFunctionalComponent() 
  return <input />;

class Parent extends React.Component 
  constructor(props) 
    super(props);
    this.textInput = React.createRef();
  
  render() 
    // 这将不会工作!
    return (
      <MyFunctionalComponent ref=this.textInput />
    );
  

可以通过闭合的帮助在函数组件内部进行使用 Refs:

function CustomTextInput(props) 
  // 这里必须声明 textInput,这样 ref 回调才可以引用它
  let textInput = null;
  function handleClick() 
    textInput.focus();
  
  return (
    <div>
      <input
        type="text"
        ref=(input) =>  textInput = input;  />
      <input
        type="button"
        value="Focus the text input"
        onClick=handleClick
      />
    </div>
  );  

注意:

  • 不应该过度的使用 Refs
  • ref返回值取决于节点的类型:
    • ref 属性被用于一个普通的 html 元素时,React.createRef()接收底层 DOM 元素作为他的 current 属性以创建 ref
    • ref 属性被用于一个自定义的类组件时,ref 对象将接收该组件已挂载的实例作为他的 current
  • 当在父组件中需要访问子组件中的 ref 时可使用传递 Refs 或回调 Refs。

12. React 中除了在构造函数中绑定 this,还有别的方式吗?

  • 构造函数中绑定 this (super(props)方式)
constructor(props)
      super(props); 
       this.state=
           msg:'hello world',
       
       this.getMsg = this.getMsg.bind(this)
   
  • 函数定义的时候使用箭头函数
constructor(props)
    super(props);
    this.state=
           msg:'hello world',
    
    render()
      <button onClcik=()=>alert(this.state.msg)>点我</button>
    

  • 函数调用是使用 bind 绑定 this
 <button onClick=this.getMsg.bind(this)>点我</button>

13. React 组件的构造函数有什么作用?它是必须的吗?

构造函数主要用于两个目的:

  • 通过将对象分配给 this.state 来初始化本地状态
  • 事件处理程序方法绑定到实例上

所以,当在 React class 中需要设置 state 的初始值或者绑定事件时,需要加上构造函数,官方 Demo:

class LikeButton extends React.Component 
  constructor() 
    super();
    this.state = 
      liked: false
    ;
    this.handleClick = this.handleClick.bind(this);
  
  handleClick() 
    this.setState(liked: !this.state.liked);
  
  render() 
    const text = this.state.liked ? 'liked' : 'haven\\'t liked';
    return (
      <div onClick=this.handleClick>
        You text this. Click to toggle.
      </div>
    );
  

ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);

构造函数用来新建父类的 this 对象;子类必须在 constructor 方法中调用 super 方法;否则新建实例时会报错;因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法;子类就得不到 this 对象。

注意:

  • constructor() 必须配上 super(), 如果要在constructor 内部使用 this.props 就要 传入 props , 否则不用
  • javascript 中的 bind 每次都会返回一个新的函数, 为了性能等考虑, 尽量在 constructor 中绑定事件

14. React.forwardRef 是什么?它有什么作用?

React.forwardRef 会创建一个 React 组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:

  • 转发 refs 到 DOM 组件
  • 在高阶组件中转发 refs

15. 类组件与函数组件有什么异同?

相同点:

组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终呈现效果上都是完全一致的。

我们甚至可以将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(虽然并不推荐这种重构行为)。从使用者的角度而言,很难从使用体验上区分两者,而且在现代浏览器中,闭包和类的性能只在极端场景下才会有明显的差别。所以,基本可认为两者作为组件是完全一致的。

不同点:

  • 它们在开发时的心智模型上却存在巨大的差异类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、引用透明等特点。
  • 之前,在使用场景上,如果存在需要使用生命周期的组件,那么主推类组件;设计模式上,如果需要使用继承,那么主推类组件。但现在由于 React Hooks 的推出,生命周期概念的淡出,函数组件可以完全取代类组件。其次继承并不是组件最佳的设计模式,官方更推崇“组合优于继承”的设计概念,所以类组件在这方面的优势也在淡出。
  • 性能优化上,类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能。
  • 从上手程度而言,类组件更容易上手,从未来趋势上看,由于React Hooks 的推出,函数组件成了社区未来主推的方案。
  • 类组件在未来时间切片与并发模式中,由于生命周期带来的复杂度,并不易于优化。而函数组件本身轻量简单,且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的未来发展。

以上是关于前端面试,备考第 20 天—— React 基础,再进一步的主要内容,如果未能解决你的问题,请参考以下文章

前端面试,备考第 21 天—— React 怎么做数据管理,原理是什么

前端面试,备考第 12 天 - 原型与原型链

前端面试,备考第 13 天 - 执行上下文 | 作用域链 | 闭包

前端面试,备考第 15 天 - 异步编程:Promise | Async/Await | 定时器 | 扩展

前端面试,备考第 14 天 - 指针指向问题:this/call/apply/bind

前端面试,备考第 17 天—— 怎么对项目做性能优化:图片优化 | CDN | 懒加载 | 回流重绘 | 动画