React - (面试)

1,992 阅读12分钟

React概念(面试)

1.什么是虚拟DOM(VDOM)

  • 虚拟DOM(VDOM)是真实DOM在内存中的表示
  • UI的表示形式保存在内存中,并与实际的DOM同步。这是一个发生在渲染函数被调用和元素在屏幕上显示之间的步骤,整个过程被称为调和

2.类组件和函数组件之间的区别

  • 类组件: 可以使用其他特性,如状态(state)和生命周期函数
  • 函数组件: 也称为哑组件或者展示组件,当组件只接收props渲染到页面时,就是无状态组件,称为函数组件

函数组件的性能比类组件性能高,因为类组件在使用时要实例化,而函数组件直接调用函数取返回结果即可。

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

3.React中refs干嘛用,如何创建refs

  1. React中refs干嘛用
  • Refs创建了一种访问在render方法中创建的DOM节点或者Reaat元素的方法
  • Refs也可以在函数组件中使用(利用JS中的闭包与函数组件一起使用)
function CustomForm ({handleSubmit}) {
  let inputElement
  return (
    <form onSubmit={() => handleSubmit(inputElement.value)}>
      <input
        type='text'
        ref={(input) => inputElement = input} />
      <button type='submit'>Submit</button>
    </form>
  )
}

  1. 创建refs
  • refs使用React.createRef()创建,并通过ref属性附加到React元素上。在构建组件时,通常将refs分配给实例属性,以便可以在整个组件中引用他们
class MyComponent extends React.Component {
    constructor(props) {
      super(props);
      this.myRef = React.createRef();
    }
    render() {
      return <div ref={this.myRef} />;
    }
  }

4.在React中如何处理事件

  • 为了解决跨浏览器的兼容问题,SyntheticEvent 实例将被传递给你的的事件处理函数,SyntheticEvent 是React跨浏览器的浏览器原生事件包装器,他还拥有和浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault()。
  • React并不是将事件附加到子节点本身,React使用单个事件侦听器侦听顶层的所有事件。这对性能有好处,也就是说,React在更新DOM的时候不需要跟踪事件监听器。

5.state和props的区别是啥

state和props都是普通的JS对象,虽然他们都包含影响渲染输出的信息,但是他们在组件方面的功能是不同的

  • state是组件自己管理数据,控制自己的状态,可变
  • props是外部传入的数据参数,不可变
  • 没有state的叫做无状态组件,有state的叫做有状态组件
  • 多用props,少用state,也就是多写无状态组件

6.什么是高阶组件

  • 高阶组件(HOC)是接收一个组件并返回一个新组件的函数
  • 是从React的组合特性中衍生出来的 ,称其为纯组件。因为他们可以接收任何动态提供的组件,但不会修改或者复制输入组件的任何行为
const EnhancedComponent = higherOrderComponent(WrappedComponent);

HOC可以用于以下许多用例
  • 代码重写、引导和逻辑抽象
  • 渲染劫持
  • state抽象和操作
  • props处理

7.在构造函数调用super并将props作为参数传入的作用是啥

  • 在调用super之前,子类构造函数无法使用this引用,ES6子类也是这样
  • 将props参数传递给super()调用的主要原因是在子构造函数中能够通过this.props来获取传入的props
  1. 传递props
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    console.log(this.props);  // { name: 'sudheer',age: 30 }
  }
}

  1. 没传props
class MyComponent extends React.Component {
  constructor(props) {
    super();
    console.log(this.props); // undefined
    // 但是 Props 参数仍然可用
    console.log(props); // Prints { name: 'sudheer',age: 30 }
  }

  render() {
    // 构造函数外部不受影响
    console.log(this.props) // { name: 'sudheer',age: 30 }
  }
}

props 的行为只在构造函数中是不同的,在构造函数之外也是一样的

8.受控组件 && 非受控组件

  • 受控组件和非受控组件主要取决于组件是否受父级传入的props控制
受控组件: 受React控制的组件,是表单数据真实的唯一来源

受控组件更新state流程:

  1. 可以通过在初始state中设置表单的默认值。
  2. 每当表单的值发生变化时,调用onChange事件处理器。
  3. 事件处理器通过合成事件对象e拿到改变后的状态,并更新state。
  4. setState触发视图的重新渲染,完成表单组件值得更新。
非受控组件: 由DOM处理表单数据
  • 非受控组件的值不受自身的state 或者 props 控制,通常需要为其添加ref来访问它的底层DOM

一般建议优先选择受控组件: 1. 支持即时字段校验 2. 允许有条件的禁用/启动按钮 3. 强制输入格式

9.React.createElenet的理解

  React.createElement(
      type,
      [props],
      [...children]
  )
let element = (
    <button id="sayHello" style={{ color: 'red', backgroundColor: 'green' }} onClick={() => sayHello()}>
        say
        <b>
            Hello
        </b>
    </button>
)

// 转换为JS后为
React.createElement(
    'button',
    {
        id: 'sayHello',
        style: {
            color: 'red', 
            backgroundColor: 'green'
        },
        onClick: () => sayHello()
    },
    'say',
    React.createElement(
        'b',
        {},
        'Hello'
    )
)

参数说明

  • type: 传入的类似HTML的标签名称 必填
  • [props]: 表示传入的属性(eg: className) 可选
  • [...children]: 子节点(要显示的节点内容) 可选

10.什么是JSX

  • JSX 是JavaScript XML,是React提供的一种语法糖,可以让我们在JS中写html标记语言。
  • JSX本身不能被浏览器读取,需要使用babel + webpack将其转换为传统的JS。

11.React中的StrictMode(严格模式)是什么?

  • React的StrictModde是一种辅助组件,可以帮助我们编写更好的React组件,可以使用<StrictMode />包装一组组件,并且可以帮我们做一下检查:
    1. 验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告
    2. 验证是否使用了已经废弃的方法, 如果有,会在控制台给出警告
    3. 通过识别一些潜在的风险预防一些副作用

12.React中,为什么类方法要绑定到类实例上

  • 在JS中,this值会根据当前上下文变化。在React类组件中,开发人员希望this指向当前的实例,因此需要将这些方法绑定到类实例中,一般在构造函数中完成。
class SubmitButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isFormSubmitted: false
    };
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit() {
    this.setState({
      isFormSubmitted: true
    });
  }

  render() {
    return (
      <button onClick={this.handleSubmit}>Submit</button>
    )
  }
}

  • 也可也使用箭头函数解决this指向问题
handleSubmit = () =>  {
    this.setState({
      isFormSubmitted: true
    });
  }

13. 什么是prop drilling?如何避免?

  • 在React组件中,在多层嵌套组件中需要使用另一个嵌套组件提供的数据,一般使用props从父级组件一层层将数据传递下去。将props从源组件传递到深层嵌套组件,叫做prop drilling

    1. 缺点: 原本不需要数据的组件变得复杂,难以维护
    2. 如何避免: a. React Context b. 使用 Redux 或者 Hooks 进行数据管理

14. 描述 Flux 与 MVC

传统的 MVC 模式在分离数据(Model)、UI(View和逻辑(Controller)方面工作得很好,但是 MVC 架构经常遇到两个主要问题:

  1. 数据流不够清晰:跨视图发生的级联更新常常会导致混乱的事件网络,难于调试
  2. 缺乏数据完整性:模型数据可以在任何地方发生突变,从而在整个UI中产生不可预测的结果

使用 Flux 模式的复杂用户界面不再遭受级联更新,任何给定的React 组件都能够根据 store 提供的数据重建其状态。Flux 模式还通过限制对共享数据的直接访问来加强数据完整性

15.什么是 React Fiber?

  • Fiber 是 React 16 中新的协调引擎或重新实现核心算法。它的主要目标是支持虚拟DOM的增量渲染。React Fiber 的目标是提高其在动画、布局、手势、暂停、中止或重用等方面的适用性,并为不同类型的更新分配优先级,以及新的并发原语。
  • React Fiber 的目标是增强其在动画、布局和手势等领域的适用性。它的主要特性是增量渲染:能够将渲染工作分割成块,并将其分散到多个帧中。

16.getDefaultProps() && getInitialState()

  • 分别定义this.props的默认值和this.state的初始值的阶段
  • getDefaultProps()方法被调用一次并缓存起来,该方法返回一个对象,并且属性通过父组件传入并且挂在到this.props
  • getInitialState()方法也只调用一次,返回值被当作this.state的初始值
  • componentWillReceviProps()当组件接收到新的属性时被调用,我们可以利用此方法为React组件提供一个在render之前修改state的机会,在此方法内调用this.setState()将不会导致重复渲染render,然后可以通过this.props访问旧的属性

17.在React中使用构造函数和getInitialState的区别

  • 构造函数和getInitialState的区别就是ES6 和 ES5本身的区别,在使用ES6类时,应该在构造函数里边初始化state,并在使用React.createClass时定义getInitialState方法
<!-- 构造函数 -->
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { /* initial state */ };
  }
}

<!-- getInitialState() -->
var MyComponent = React.createClass({
  getInitialState() {
    return { /* initial state */ };
  },
});

18.如何避免组件的重新渲染

  • React.memo():可以防止重复渲染不必要的函数组件
  • PureComponent:可以防止重复渲染不必要的类组件

这两种方法都依赖于对传递给组件的props的浅比较,如果props没有变,那么组件将不会重新渲染,虽然这两种工具都非常有用,但是浅比较会带来额外的性能损失,因此如果使用不当,这两种方法都会对性能产生负面影响。 通过使用 React Profiler,可以在使用这些方法前后对性能进行测量,从而确保通过进行给定的更改来实际改进性能。

19.什么是纯函数 && 什么是纯组件

  • 纯函数是不依赖并且不会在其作用域外修改变量状态的函数。本质上,纯函数始终在给定相同参数的情况下返回相同的结果
  • 纯组件是通过控制shouldComponentUpdate生命周期函数,减少render渲染次数来减少性能损耗的。
    1. 优点:这相对于Component来说,减少了手动判断state变化的繁琐操作
    2. 缺点:因为PureComponent只进行了一层浅比较,简单来说,他只比较了propsstate的内存地址,如果内存地址相同,则shouldComponentUpdate就会返回false,
    3. PureComponent应用场景:局部数据发生改变的时候,比如带有输入框、switch开关等的UI组件就可以使用PureComponent组件封装,PureComponent中如果有数据操作最好配合一个第三方组件——Immutable一起使用,Immutable需要使用npm安装该插件才可以使用,因为Immutable可以保证数据的不变性。

PureComponent可能出现的问题及解决方案: 当props 或 state 为 复杂的数据结构 (例如:嵌套对象和数组)时,因为 React.PureComponent 仅仅是 浅比较 ,可能会渲染出 错误的结果 。这时有 两种解决方案 :

  • 当 知道 有深度数据结构更新时,可以直接调用 forceUpdate 强制更新
  • 考虑使用 immutable objects (不可突变的对象),实现快速的比较对象

注意

  • React.PureComponent 中的 shouldComponentUpdate() 将跳过所有子组件树的 prop 更新,所以使用 React.PureComponent 的组件,它的所有 子组件也必须都为 React.PureComponent 。

  • 何时使用 PureComponent? PureComponent 提高了性能,因为它减少了应用程序中的渲染操作次数,这对于复杂的 UI 来说是一个巨大的胜利,因此建议尽可能使用。此外,还有一些情况需要使用 Component 的生命周期方法,在这种情况下,我们不能使用无状态组件。

  • 何时使用无状态组件? 无状态组件易于实施且快速实施。它们适用于非常小的 UI 视图,其中重新渲染成本无关紧要。它们提供更清晰的代码和更少的文件来处理。

20. setState更新是同步的还是异步的

  • 如果是正常情况下,也就是没有使用Concurrent组件(React16新加的)的情况下,setState是同步更新的,但是不会立即获取到最新的state的值,因为调用setState只是单纯的将你传进来的新的state放入updateQueue这条链表上,等这个点击事件(React叫这类事件为合成事件,只有合成事件会触发回调事件进行更新state)结束之后,会触发内部的一个回调函数,在这个回调函数中,才会真正的去更新State以及重新渲染
  • 当使用了Concurrent组件的时候,这种情况下才是真正的异步更新模式,同样的没法立即获取最新的状态,并且在执行React的更新和渲染(这个渲染是创建Fiber的过程)的过程中,使用了真正的异步方式(API: postMessage),会将Fiber的更新过程放在eventLoop里边进行
  • 当使用了flushSync(React新版本提供的)这个API的时候,React的更新渲染完全是同步的,React会立即触发更新state以及渲染的过程,这种情况是可以获取到最新状态的

21. setState更新完状态之后如何立即获取到最近状态值

  1. 方法一:使用flushSyncAPI实现
  handleClick = () => {
      flushSync(() => {
          this.setState({
              ding: newDing
          })
      })
      console.log(this.state.ding); // 此时可以获取到最新的state
  }
  1. 方法二:使用原生事件进行触发获取
  • 因为React使用的是JSX语法,元素上绑定的事件属于合成事件,这种事件是通过内部的一个回调函数来触发React的更新和渲染的(这个回调函数只有当该事件执行完成之后才会执行),所以不会立即获取到更新后state
  • 而使用原生事件是立即触发React的更新和渲染的,所以可以立即获取到更新后的state
  div.addEventListener('click', () => {
      this.setState({
          ding: newDing
      });
      console.log(this.state.ding); // 此时可以获取到最新的state
  })
  1. 方法三:使用setTimeout
  setTimeout(() => {
      this.setState({
          ding: newDing
      });
      console.log(this.state.ding); // 此时可以获取到最新的state
  })

22. React 和 Vue项目中,在列表组件中写key的作用?

key是给每一个vnode的唯一id,可以依靠key,更准确, 更快的拿到oldVnode中对应的vnode节点。

  1. 更准确
  • 因为key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。
  1. 更快
  • 利用key的唯一性生成map对象来获取对应节点,比遍历方式更快