react相关面试题

React 的核心流程可以分为两个部分:

reconciliation (调度算法,也可称为 render): // 调和

    更新 state 与 props;

    调用生命周期钩子;

    生成 virtual dom;

    这里应该称为 Fiber Tree 更为符合;

    通过新旧 vdom 进行 diff 算法,获取 vdom change;

    确定是否需要重新渲染

  commit:

    如需要,则操作 dom 节点更新;

constructor ====》 初始化state
componentWillMount  ==== 》getDerivedStateFromProps // 当新 props 中的 data 发生变化时,同步更新到 state 上
componentDidMount ====》进行事件监听,数据的请求
componentWillUpdate === 》 getSnapshotBeforeUpdate componentDidUpdate ==== 》 当 id 发生变化时,重新获取数据 shouldComponentUpdate ====》 优化渲染的性能,return false 阻止后面的逻辑 componentWillReciveProps ==== 》少使用,使用getDerivedStateFromPropscomponentWillUnmount ====》 解绑事件

在新版本中,React 官方对生命周期有了新的 变动建议:

  • 使用getDerivedStateFromProps 替换componentWillMount
  • 使用getSnapshotBeforeUpdate替换componentWillUpdate
  • 避免使用componentWillReceiveProps

其实该变动的原因,正是由于上述提到的 Fiber。首先,从上面我们知道 React 可以分成 reconciliation 与 commit 两个阶段,对应的生命周期如下:

  • reconciliation:

    • componentWillMount
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
  • commit:

    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount

在 Fiber 中,reconciliation 阶段进行了任务分割,涉及到 暂停 和 重启,因此可能会导致 reconciliation 中的生命周期函数在一次更新渲染循环中被 多次调用 的情况,产生一些意外错误。

新版的建议生命周期如下:

class Component extends React.Component {
  // 替换 `componentWillReceiveProps` ,
  // 初始化和 update 时被调用
  // 静态函数,无法使用 this
  static getDerivedStateFromProps(nextProps, prevState) {}
  
  // 判断是否需要更新组件
  // 可以用于组件性能优化
  shouldComponentUpdate(nextProps, nextState) {}
  
  // 组件被挂载后触发
  componentDidMount() {}
  
  // 替换 componentWillUpdate
  // 可以在更新之前获取最新 dom 数据
  getSnapshotBeforeUpdate() {}
  
  // 组件更新后调用
  componentDidUpdate() {}
  
  // 组件即将销毁
  componentWillUnmount() {}
  
  // 组件已销毁
  componentDidUnMount() {}
}

  使用建议:

  • constructor初始化 state;
  • componentDidMount中进行事件监听,并在componentWillUnmount中解绑事件;
  • componentDidMount中进行数据的请求,而不是在componentWillMount
  • 需要根据 props 更新 state 时,使用getDerivedStateFromProps(nextProps, prevState)
    • 旧 props 需要自己存储,以便比较;
    • public static getDerivedStateFromProps(nextProps, prevState) {
              // 当新 props 中的 data 发生变化时,同步更新到 state 上
              if (nextProps.data !== prevState.data) {
                      return {
                              data: nextProps.data
                      }
              } else {
                      return null1
              }
      }
      

        

  • 可以在componentDidUpdate监听 props 或者 state 的变化,例如:
componentDidUpdate(prevProps) {
        // 当 id 发生变化时,重新获取数据
        if (this.props.id !== prevProps.id) {
                this.fetchData(this.props.id);
        }
}

 

  • componentDidUpdate使用setState时,必须加条件,否则将进入死循环;
  • getSnapshotBeforeUpdate(prevProps, prevState)可以在更新之前获取最新的渲染数据,它的调用是在 render 之后, update 之前;
  • shouldComponentUpdate: 默认每次调用setState,一定会最终走到 diff 阶段,但可以通过shouldComponentUpdate的生命钩子返回false来直接阻止后面的逻辑执行,通常是用于做条件渲染,优化渲染的性能。

二: 虚拟DOM的理解

  (1)Virtual DOM是对DOM的抽象,本质上是JavaScript对象,这个对象就是更加轻量级的对DOM的描述.

  (2)两个内容的差异,进行dom的对比,事件属性方法,耗性能,dom对象转换为js对象,比较会快。有效提升性能。

  (3)首先,我们都知道在前端性能优化的一个秘诀就是尽可能少地操作DOM,不仅仅是DOM相对较慢,更因为频繁变动DOM会造成浏览器的回流或者重回,这些都是性能的杀手,因此我们需要这一层抽象,在patch过程中尽可能地一次性将差异更新到DOM中,这样保证了DOM不会出现性能很差的情况.

  (4)更好的跨平台,比如Node.js就没有DOM,如果想实现SSR(服务端渲染),那么一个方式就是借助Virtual DOM,因为Virtual DOM本身是JavaScript对象.

三:diff算法

  (1)diff的目的就是比较新旧Virtual DOM Tree找出差异并更新.

  (2)一层节点发现有问题,不再往下比,直接放弃,n的平方降低为n

  • 把树形结构按照层级分解,只比较同级元素(层级比较)
  • 给列表结构的每个单元添加唯一的 key 属性,方便比较(列表添加key)
  • React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)
  • 选择性子树渲染。开发人员可以重写shouldComponentUpdate 提高 diff 的性能

四:key值的作用

  (1)如果key值相同,dom直接复用,不用再创建dom,直接这两个比,不用循环比

  (2)用新指针对应节点的key去旧数组寻找对应的节点,这里分三种情况,

      当没有对应的key,那么创建新的节点,

      如果有key并且是相同的节点,把新节点patch到旧节点,

      如果有key但是不是相同的节点,则创建新节点

五:怎么理解HOC高阶组件

   接收一些函数,返回函数,组件进行包装,返回一个新的组件,很多地方都要用,有一点区别,共用的东西写在高阶组件中,通过传递额外参数,动态改变组件不同场景下使用的差异。

六:redux中间件的原理

  action--store-reducer-props action和store之间,沟通的桥梁dispatch,改装dispatch,

  store.dispatch,直接把action(对象)传递给store,使用中间件,action可以是函数,可以将函数转换为对象,传递给store。

七:setState发生了什么?遇到过什么坑

  调和的过程。setState一般怎么用,传递什么进去,对象还是方法。

this.setState({
    name: '小李'
});
this.setState(() =>({
    name:'小李'
}));
永远用函数调用,会避免一些坑。
this.setState({
    age: this.state.age+1
});
setState异步,疯狂点击按钮,age不是每次加1,会一次加5,6个,state异步,会进行累加
<input  refs="input"  value={this.state.age} />  ====1 
this.setState((prevState) =>{
    age: ++ prevState
});
this.refs.input.value  ==== 1
this.setState((prevState) =>{
    age: ++ prevState
}, () => {
    this.refs.input.value  // 异步执行完state之后执行
});

八:ref是一个函数,有什么好处?

  方便react在销毁组件重新渲染,有效清空ref引用中的东西,防止内存泄漏

九:refs作用是什么,在什么场景下用过?

  操作dom(滚动条,点击按钮,滚动条滚动到上面)

  图片展示获取图片的宽和高,

十:react中this的指向问题

  

十一:react-router的实现原理

hash/location

十二:jsx代码的转化(babel)

createElement

十三:受控组件和非受控组件

受控:有生命周期

非受控:纯函数

十四:函数组件怎么做性能优化

十五:react-saga的设计思想

改装dispatch action-----store之间,action可以是一个函数,可以执行异步操作

十六:组件是什么?类是什么?类被编译成什么?

十七:reselect是做什么使用的?

十八:什么时候使用异步组件?

十九:xss攻击,react如何防范?

二十:react怎么提高性能优化?

shouldComponentUpdate

pureComponent

二十一:ssr

二十二:

  • 为什么返回多个标签或组件必须要用一个标签或组件包裹?
  • 为什么在根本没有使用React这个变量的情况下还要import React

整个UI实际上是通过层层嵌套的React.createElement方法返回的,所以我们要在文件开头import React,否则编译后就会发现createElement没有定义。

React.createElement执行的结果是一个对象,对象的属性描述了标签或组件的性状,对象再嵌套子对象。如果顶层返回多个标签,就无法表达为一个对象了