React面试题总结


虽然前端并不是我的强项,但是工作几年了一直做着以后端为主的全栈开发。所以对前端的一些技术框架也比较关注,履历中也有前端开发的经历,因此有时也会被问到一些前端的知识。现如今前端技术发展迅速,JavaScript 工具缓慢而稳定地在市场中扎根,对 React 的需求呈指数级增长。选择合适的技术来开发应用或网站变得越来越有挑战性。其中 React 被认为是增长最快的 Javascript 框架。在此总结了常见的知识点

Q1:什么是虚拟DOM?

难度:⭐️

虚拟DOM(VDOM)它是真实DOM的内存表示,一种编程概念,一种模式。它会和真实的DOM同步,比如通过ReactDOM这种库,这个同步的过程叫做调和(reconcilation)。虚拟DOM更多是一种模式,不是一种特定的技术。为什么要使用虚拟DOM,原因是真实的DOM是非常耗资源的操作,虚拟DOM可以把原本多个DOM操作在内存中简化,移除不必要的DOM操作,以最小的消耗来操作DOM节点

Q2:类组件和函数组件之间有什么区别?

难度:⭐️⭐️

  1. 类组件Class components

    类组件允许使用更多额外的功能,如组件自身的状态和生命周期钩子

    class Welcome extends React.Component {
      render() {
        return (
          <h1>Welcome { this.props.name }</h1>
        );
      }
    }
    ReactDOM.render(<Welcome name='react' />, document.getElementById('root'));
  2. 函数组件(functional component)

    也叫无状态组件(stateless component),仅接收 props,没有state

    function Welcome (props) {
      return <h1>Welcome {props.name}</h1>
    }
    ReactDOM.render(<Welcome name='react' />, document.getElementById('root'));

主要区别

函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。

区别 函数组件 类组件
是否有 this 没有
是否有生命周期 没有
是否有状态 state 没有

Q3:React中的refs作用是什么?

难度: ⭐️⭐️

Refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素。在典型的React数据流中,props是父组件与子组件交互的唯一方式,要修改一个子组件,你需要使用新的props来重新渲染它,但是在某些情况下,你需要在典型数据流之外强制修改子组件,被修改的子组件可能是一个React组件的实例,也可能是一个DOM元素,对于这两种情况React都提供了解决办法。避免使用refs来做任何可以通过声明式实现来完成的事情,通常在可以使用propsstate的情况下勿依赖refs,下面是几个适合使用refs的情况:

  • 管理焦点、文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方DOM库。

React提供的这个ref属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()返回的组件实例,需要区分一下渲染组件与渲染原生DOM元素,渲染组件时返回的是组件实例,而渲染DOM元素时,返回是具体的DOM节点

class UnControlledForm extends Component {
  handleSubmit = () => {
    console.log("Input Value: ", this.input.value)
  }
  render () {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type='text'
          ref={(input) => this.input = input} />
        <button type='submit'>Submit</button>
      </form>
    )
  }
}

上述代码中的 input 域包含了一个 ref 属性,该属性声明的回调函数会接收 input 对应的 DOM 元素,我们将其绑定到 this 指针以便在其他的类函数中使用。另外值得一提的是,refs 并不是类组件的专属,函数式组件同样能够利用闭包暂存其值

function CustomForm ({handleSubmit}) {
  let inputElement
  return (
    <form onSubmit={() => handleSubmit(inputElement.value)}>
      <input
        type='text'
        ref={(input) => inputElement = input} />
      <button type='submit'>Submit</button>
    </form>
  )
}

Q4:如何创建refs?

难度: ⭐️⭐️

Reactref3种创建方式。

  1. 字符串

    ref可以直接设置为字符串值,这种方式基本不推荐使用,或者在未来的React版本中不会再支持该方式。这主要是因为使用字符串导致的一些问题,例如当ref定义为string时,需要React追踪当前正在渲染的组件,在reconciliation阶段,React Element创建和更新的过程中,ref会被封装为一个闭包函数,等待commit阶段被执行,这会对React的性能产生一些影响等。

    class InputOne extends React.Component {
        componentDidMount() {
            this.refs.inputRef.value = 1;
          }
        render() {
            return <input ref="inputRef" />;
        }
    }
  2. 回调

    React支持给任意组件添加特殊属性,ref属性接受一个回调函数,其在组件被加载或卸载时会立即执行。

    • 当给HTML元素添加ref属性时,ref回调接收了底层的DOM元素作为参数。
    • 当给组件添加ref属性时,ref回调接收当前组件实例作为参数。
    • 当组件卸载的时候,会传入null
    • ref回调会在componentDidMountcomponentDidUpdate等生命周期回调之前执行。

    Callback Ref我们通常会使用内联函数的形式,那么每次渲染都会重新创建,由于React会清理旧的ref然后设置新的,因此更新期间会调用两次,第一次为null,如果在Callback中带有业务逻辑的话,可能会出错,可以通过将Callback定义成类成员函数并进行绑定的方式避免。

    class InputTwo extends React.Component {
        componentDidMount() {
            this.inputRef.value = 2;
          }
        render() {
            return <input ref={(element) =>this.inputRef = element} />;
        }
    }
  3. API创建

    React v16.3中经0017-new-create-ref提案引入了新的React.createRefAPI,当ref被传递给render中的元素时,对该节点的引用可以在refcurrent属性中被访问,ref的值根据节点的类型而有所不同:

    • ref属性用于HTML元素时,构造函数中使用React.createRef()创建的ref接收底层DOM元素作为其current属性。
    • ref属性用于自定义class组件时,ref对象接收组件的挂载实例作为其current属性。
    • 不能在函数组件上使用ref属性,因为他们没有实例。

    对比新的CreateRefCallback Ref,并没有压倒性的优势,只是希望成为一个便捷的特性,在性能上会会有微小的优势,Callback Ref采用了组件Render过程中在闭包函数中分配ref的模式,而CreateRef则采用了Object Ref

    class InputThree extends React.Component {
        constructor(props) {
            super(props);
            this.inputRef = React.createRef();
        }
        componentDidMount() {
            this.inputRef.current.value = 3;
        }
        render() {
            return <input ref={this.inputRef} />;
        }
    }

Q5:state 和 props有什么区别?

难度: ⭐️⭐️

state 和 props都是普通的JavaScript对象。尽管它们两者都具有影响渲染输出的信息,但它们在组件方面的功能不同。即

  • props是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的props来重新渲染子组件,否则子组件的props以及展现形式不会改变。
  • state的主要作用是用于组件保存、控制以及修改自己的状态,它只能在constructor中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。

Q7:什么是高阶组件?

难度: ⭐️⭐️

高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。基本上,这是从React的组成性质派生的一种模式,我们称它们为“纯”组件, 因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件的任何行为。下面我们来实现一个最简单的高阶组件(函数),它接受一个React组件,包裹后然后返回。

export default function withHeader(WrappedComponent) {
  return class HOC extends Component {
    render() {
      return <div>
        <div className="demo-header">
          我是标题
        </div>
        <WrappedComponent {...this.props}/>
      </div>
    }
  }
}

然后在其他组件里,我们引用这个高阶组件,用来强化它。

@withHeader
export default class Demo extends Component {
  render() {
    return (
      <div>
        我是一个普通组件
      </div>
    );
  }
}

在这里使用了ES7里的decorator,来提升写法上的优雅,但是实际上它只是一个语法糖,下面这种写法也是可以的。

const EnhanceDemo = withHeader(Demo);

高阶组件的进阶用法

  1. 组件参数

    还是以上述例子为例,此高阶组件仅仅只是展示了我是标题这个名称,但是为了更好的抽象,此标题应当可以被参数化,如下方式调用。

    // 如果传入参数,则传入的参数将作为组件的标题呈现
    @withHeader('Demo') 
    export default class Demo extends Component {
      render() {
        return (
          //...
        );
      }
    }

    withHeader需要被改写成如下形式,它接受一个参数,然后返回一个高阶组件(函数)。

    export default function (title) {
      return function (WrappedComponent) {
        return class HOC extends Component {
          render() {
            return <div>
              <div className="demo-header">
                {title
                  ? title
                  : '我是标题'}
              </div>
              <WrappedComponent {...this.props}/>
            </div>
          }
        }
      }
    }

    使用ES6写法可以更加简洁。

    export default(title) => (WrappedComponent) => class HOC extends Component {
      render() {
        return <div>
          <div className="demo-header">
            {title
              ? title
              : '我是标题'}
          </div>
          <WrappedComponent {...this.props}/>
        </div>
      }
    }

    这个进阶用法使用了函数编程里的柯里化 Curry

    概念:只传递函数的一部分参数来调用它,让它返回一个函数去处理剩下的参数。

    函数签名:fun(params)(otherParams)

    应用:在React里,通过柯里化,我们可以通过传入不同的参数来得到不同的高阶组件。

  2. 组合多个高阶组件

    上述高阶组件为React组件增强了一个功能,如果需要同时增加多个功能需要怎么做?这种场景非常常见,例如我既需要增加一个组件标题,又需要在此组件未加载完成时显示Loading。

    @withHeader
    @withLoading
    class Demo extends Component{
    
    }

    使用compose可以简化上述过程,也能体现函数式编程的思想。

    const enhance = compose(withHeader,withLoading);
    @enhance
    class Demo extends Component{
    
    }

    组合 Compose

    compose可以帮助我们组合任意个(包括0个)高阶函数,例如compose(a,b,c)返回一个新的函数d,函数d依然接受一个函数作为入参,只不过在内部会依次调用c,b,a从表现层对使用者保持透明

    基于这个特性,我们便可以非常便捷地为某个组件增强或减弱其特征,只需要去变更compose函数里的参数个数便可。

    compose函数实现方式有很多种,这里推荐其中一个recompact.compose

Q8:什么是受控组件?

难度:⭐️⭐️⭐️

在HTML当中,像<input>,<textarea>, 和 <select>这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。

非受控组件

非受控组件,即组件的状态不受React控制的组件,例如下边这个

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Demo1 extends Component {
    render() {
        return (
            <input />
        )
    }
}

ReactDOM.render(<Demo1/>, document.getElementById('content'))

在这个最简单的输入框组件里,我们并没有干涉input中的value展示,即用户输入的内容都会展示在上面。如果我们通过props给组件设置一个初始默认值,defaultValue属性是React内部实现的一个属性,目的类似于input的placeholder属性。
ps: 此处如果使用value代替defaultValue,会发现输入框的值无法改变

受控组件

同样的,受控组件就是组件的状态受React控制。上面提到过,既然通过设置input的value属性, 无法改变输入框值,那么我们把它和state结合在一起,再绑定onChange事件,实时更新value值就行了。

class Demo1 extends Component {
    constructor(props) {
        super(props);
        this.state = {
            value: props.value
        }
    }

    handleChange(e) {
        this.setState({
            value: e.target.value
        })
    }

    render() {
        return (
            <input value={this.state.value} onChange={e => this.handleChange(e)}/>
        )
    }
}

Q9:constructor中super与props参数一起使用的目的是什么?

难度:⭐️⭐️

在调用方法之前,子类构造函数无法使用this引用super()。在ES6中,在子类的constructor中必须先调用super才能引用this。在constructor中可以使用this.props

使用props

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        console.log(this.props);  // Prints { name: 'sudheer',age: 30 }
    }
}

不使用props

class MyComponent extends React.Component {
    constructor(props) {
        super();
        console.log(this.props); // Prints undefined
        // But Props parameter is still available
        console.log(props); // Prints { name: 'sudheer',age: 30 }
    }

    render() {
        // No difference outside constructor
        console.log(this.props) // Prints { name: 'sudheer',age: 30 }
    }
}

上面的代码片段揭示了this.props行为仅在构造函数中有所不同。外部构造函数相同。

Q10:以下使用React.createElement的等价项是什么?

难度:⭐️⭐️⭐️

以下等同于什么使用React.createElement

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

:

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

Q11:什么是JSX?

难度:⭐️⭐️⭐️

JSX即JavaScript XML。一种在React组件内部构建标签的类XML语法。JSX为react.js开发的一套语法糖,也是react.js的使用基础。React在不使用JSX的情况下一样可以工作,然而使用JSX可以提高组件的可读性,因此推荐使用JSX。

class MyComponent extends React.Component {
  render() {
    let props = this.props;  
    return (
      <div className="my-component">
      <a href={props.url}>{props.name}</a>
      </div>
    );
  }
}

JSX的优点:

  1. 允许使用熟悉的语法来定义 HTML 元素树
  2. 提供更加语义化且移动的标签
  3. 程序结构更容易被直观化
  4. 抽象了 React Element 的创建过程
  5. 可以随时掌控 HTML 标签以及生成这些标签的代码
  6. 是原生的 JavaScript

Q12:React生命周期有哪些不同阶段?

难度:⭐️⭐️⭐️

React组件的生命周期分为四个不同阶段:

  1. 初始化:在此阶段,react组件准备设置初始状态和默认道具。
  2. 挂载: react组件已准备好挂载在浏览器DOM中。此阶段涵盖componentWillMountcomponentDidMount生命周期方法。
  3. 更新:在此阶段,组件以两种方式进行更新,即发送新道具和更新状态。此阶段涵盖了shouldComponentUpdate,componentWillUpdate和componentDidUpdate生命周期方法。
  4. 卸载:在最后一个阶段,不需要该组件,并且可以从浏览器DOM上卸载该组件。此阶段包括componentWillUnmount生命周期方法。

Q13:React的生命周期方法是什么?

难度:⭐️⭐️⭐️

  • componentWillMount:在渲染之前执行,用于根组件中的应用程序级别配置。
  • componentDidMount:仅在客户端的第一次渲染之后执行。 这是AJAX请求和DOM或状态更新应该发生的地方。此方法也用于与其他JavaScript框架以及任何延迟执行的函数(如setTimeoutsetInterval)进行集成,在这里使用它来更新状态,以便我们可以触发其他生命周期方法。
  • componentWillReceiveProps:只要在另一个渲染被调用之前更新props就被调用。 当我们更新状态时,从setNewNumber触发它。
  • shouldComponentUpdate:确定是否将更新组件。默认情况下,它返回true。如果您确定组件在状态或道具更新后不需要渲染,则可以返回false值。这是提高性能的好地方,因为如果组件收到新的道具,它可以防止重新渲染。
  • componentWillUpdate:在由shouldComponentUpdate确认返回正值的优点和状态更改时,在重新渲染组件之前执行。
  • componentDidUpdate:通常用于更新DOM以响应属性或状态更改。
  • componentWillUnmount:它将用于取消任何传出的网络请求,或删除与该组件关联的所有事件侦听器。

Q14:React Fiber是什么?

难度:⭐️⭐️⭐️⭐️

React Fiber 并不是所谓的纤程(微线程、协程),而是一种基于浏览器的单线程调度算法,背后的支持 API 是大名鼎鼎的:requestIdleCallback。

Fiberl是一种将 recocilation (递归 diff),拆分成无数个小任务的算法;它随时能够停止,恢复。停止恢复的时机取决于当前的一帧(16ms)内,还有没有足够的时间允许计算。


Author: 顺坚
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source 顺坚 !
评论
 Previous
LeetCode-八皇后问题 LeetCode-八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯
2021-04-17
Next 
InnoDB存储引擎 InnoDB存储引擎
InnoDB是事务安全的mysql存储引擎,设计上采用了类似于Oracle数据库的架构。InnoDB存储引擎是多线程模型,其后台有多个不同的后台线程,负责处理不同的任务 Master Thread:主要负责将缓存池中的数据异步刷新到磁盘,
2021-04-04
  TOC