React 部分

react 生命周期函数

初始化阶段

  • getDefaultProps:获取实例的默认属性
  • getInitialState:获取每个实例的初始化状态 s
  • componentWillMount:组件即将被装载、渲染到页面上
  • render:组件在这里生成虚拟的DOM节点
  • componentDidMount:组件真正在被装载之后

运行中状态

  • componentWillReceiveProps:组件将要接收到属性的时候调用
  • shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)
  • componentWillUpdate:组件即将更新不能修改属性和状态
  • render:组件重新描绘
  • componentDidUpdate:组件已经更新

销毁阶段

  • componentWillUnmount:组件即将销毁

react 性能优化是哪个周期函数

shouldComponentUpdate这个方法用来判断是否需要调用 render 方法重新描绘 dom。因为 dom 的描绘非常消耗性能,如果我们能在shouldComponentUpdate方法中能够写出更优化的dom diff算法,可以极大的提高性能

react 性能优化方案

  • 重写shouldComponentUpdate来避免不必要的 dom 操作
  • 使用production版本的react.js
  • 使用key来帮助React识别列表中所有子组件的最小变化

React 中 keys 的作用是什么?

KeysReact用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识

  • 在开发过程中,我们需要保证某个元素的key在其同级元素中具有唯一性。在React Diff算法中React会借助元素的Key值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助Key值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中Key的重要性

传入 setState 函数的第二个参数的作用是什么?

该函数会在setState函数调用完成并且组件开始重渲染的时候被调用,我们可以用该函数来监听渲染是否完成

React 中 refs 的作用是什么

  • RefsReact提供给我们的安全访问DOM元素或者某个组件实例的句柄
  • 可以为元素添加ref属性然后在回调函数中接受该元素在DOM树中的句柄,该值会作为回调函数的第一个参数返回

在生命周期中的哪一步你应该发起 AJAX 请求

我们应当将 AJAX 请求放到componentDidMount函数中执行,主要原因有下

  • React下一代调和算法Fiber会通过开始或停止渲染的方式优化应用性能,其会影响到componentWillMount的触发次数。对于componentWillMount这个生命周期函数的调用次数会变得不确定,React可能会多次频繁调用componentWillMount。如果我们将AJAX请求放到componentWillMount函数中,那么显而易见其会被触发多次,自然也就不是好的选择。
  • 如果我们将AJAX请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在componentDidMount函数中进行AJAX请求则能有效避免这个问题

shouldComponentUpdate 的作用

shouldComponentUpdate允许我们手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新

如何告诉 React 它应该编译生产环境版

通常情况下我们会使用WebpackDefinePlugin方法来将NODE_ENV变量值设置为production。编译版本中React会忽略propType验证以及其他的告警信息,同时还会降低代码库的大小,React使用了Uglify插件来移除生产环境下不必要的注释等信息

概述下 React 中的事件处理逻辑

为了解决跨浏览器兼容性问题,React会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样React在更新DOM的时候就不需要考虑如何去处理附着在DOM上的事件监听器,最终达到优化性能的目的

createElement 与 cloneElement 的区别是什么

createElement函数是 JSX 编译之后使用的创建React Element的函数,而cloneElement则是用于复制某个元素并传入新的Props

redux 中间件

中间件提供第三方插件的模式,自定义拦截action->reducer的过程。变为action->middlewares->reducer。这种机制可以让我们改变数据流,实现如异步actionaction过滤,日志输出,异常报告等功能

  • redux-logger:提供日志输出
  • redux-thunk:处理异步操作
  • redux-promise:处理异步操作,actionCreator的返回值是promise

redux 有什么缺点

  • 一个组件所需要的数据,必须由父组件传过来,而不能像flux中直接从store取。
  • 当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会有效率影响,或者需要写复杂的shouldComponentUpdate进行判断。

react 组件的划分业务组件技术组件?

  • 根据组件的职责通常把组件分为 UI 组件和容器组件。
  • UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
  • 两者通过React-Redux提供connect方法联系起来

为什么虚拟 dom 会提高性能

虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能

具体实现步骤如下

  • JavaScript对象结构表示 DOM 树的结构;然后用这个树构建一个真正的DOM树,插到文档当中
  • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  • 把 2 所记录的差异应用到步骤 1 所构建的真正的DOM树上,视图就更新

diff 算法?

  • 把树形结构按照层级分解,只比较同级元素。
  • 给列表结构的每个单元添加唯一的key属性,方便比较。
  • React只会匹配相同classcomponent(这里面的class指的是组件的名字)
  • 合并操作,调用componentsetState方法的时候,React将其标记为 -dirty.到每一个事件循环结束,React检查所有标记dirtycomponent重新绘制.
  • 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能

react 的虚拟 dom 是怎么实现的

首先说说为什么要使用Virtual DOM,因为操作真实DOM的耗费的性能代价太高,所以react内部使用js实现了一套 dom 结构,在每次操作在和真实 dom 之前,使用实现好的 diff 算法,对虚拟 dom 进行比较,递归找出有变化的 dom 节点,然后对其进行更新操作。为了实现虚拟DOM,我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是 prop,每次进行diff的时候,react会先比较该节点类型,假如节点类型不一样,那么react会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较prop是否有更新,假如有prop不一样,那么react会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点

react 的渲染过程中,兄弟节点之间是怎么处理的?也就是 key 值不一样的时候

通常我们输出节点的时候都是 map 一个数组然后返回一个ReactNode,为了方便react内部进行优化,我们必须给每一个reactNode添加key,这个key prop在设计值处不是给开发者用的,而是给 react 用的,大概的作用就是给每一个reactNode添加一个身份标识,方便 react 进行识别,在重渲染过程中,如果 key 一样,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新,如果 key 不一样,则 react 先销毁该组件,然后重新创建该组件

简述 flux 思想

Flux的最大特点,就是数据的"单向流动"。

  • 用户访问View
  • View发出用户的Action
  • Dispatcher收到Action,要求Store进行相应的更新
  • Store更新后,发出一个"change"事件
  • View收到"change"事件后,更新页面

说说你用 react 有什么坑点?

1. JSX 做表达式判断时候,需要强转为 boolean 类型

如果不使用!!b进行强转数据类型,会在页面里面输出0

render() {
  const b = 0;
  return <div>
    {
      !!b && <div>这是一段文本</div>
    }
  </div>
}

2. 尽量不要在 componentWillReviceProps 里使用 setState,如果一定要使用,那么需要判断结束条件,不然会出现无限重渲染,导致页面崩溃

3. 给组件添加 ref 时候,尽量不要使用匿名函数,因为当组件更新的时候,匿名函数会被当做新的 prop 处理,让 ref 属性接受到新函数的时候,react 内部会先清空 ref,也就是会以 null 为回调参数先执行一次 ref 这个 props,然后在以该组件的实例执行一次 ref,所以用匿名函数做 ref 的时候,有的时候去 ref 赋值后的属性会取到 null

4. 遍历子节点的时候,不要用 index 作为组件的 key 进行传入

我现在有一个 button,要用 react 在上面绑定点击事件,要怎么做?

class Demo {
  render() {
    return (
      <button
        onClick={e => {
          alert('我点击了按钮');
        }}
      >
        按钮
      </button>
    );
  }
}

你觉得你这样设置点击事件会有什么问题吗?

由于onClick使用的是匿名函数,所有每次重渲染的时候,会把该onClick当做一个新的prop来处理,会将内部缓存的onClick事件进行重新赋值,所以相对直接使用函数来说,可能有一点的性能下降

修改

class Demo {

  onClick = (e) => {
    alert('我点击了按钮')
  }

  render() {
    return <button onClick={this.onClick}>
      按钮
    </button>
  }

那给我介绍一下 react

  1. 以前我们没有 jquery 的时候,我们大概的流程是从后端通过 ajax 获取到数据然后使用 jquery 生成 dom 结果然后更新到页面当中,但是随着业务发展,我们的项目可能会越来越复杂,我们每次请求到数据,或则数据有更改的时候,我们又需要重新组装一次 dom 结构,然后更新页面,这样我们手动同步 dom 和数据的成本就越来越高,而且频繁的操作 dom,也使我我们页面的性能慢慢的降低。
  2. 这个时候 mvvm 出现了,mvvm 的双向数据绑定可以让我们在数据修改的同时同步 dom 的更新,dom 的更新也可以直接同步我们数据的更改,这个特定可以大大降低我们手动去维护 dom 更新的成本,mvvm 为 react 的特性之一,虽然 react 属于单项数据流,需要我们手动实现双向数据绑定。
  3. 有了 mvvm 还不够,因为如果每次有数据做了更改,然后我们都全量更新 dom 结构的话,也没办法解决我们频繁操作 dom 结构(降低了页面性能)的问题,为了解决这个问题,react 内部实现了一套虚拟 dom 结构,也就是用 js 实现的一套 dom 结构,他的作用是讲真实 dom 在 js 中做一套缓存,每次有数据更改的时候,react 内部先使用算法,也就是鼎鼎有名的 diff 算法对 dom 结构进行对比,找到那些我们需要新增、更新、删除的 dom 节点,然后一次性对真实 DOM 进行更新,这样就大大降低了操作 dom 的次数。 那么 diff 算法是怎么运作的呢,首先,diff 针对类型不同的节点,会直接判定原来节点需要卸载并且用新的节点来装载卸载的节点的位置;针对于节点类型相同的节点,会对比这个节点的所有属性,如果节点的所有属性相同,那么判定这个节点不需要更新,如果节点属性不相同,那么会判定这个节点需要更新,react 会更新并重渲染这个节点。
  4. react 设计之初是主要负责 UI 层的渲染,虽然每个组件有自己的 state,state 表示组件的状态,当状态需要变化的时候,需要使用 setState 更新我们的组件,但是,我们想通过一个组件重渲染它的兄弟组件,我们就需要将组件的状态提升到父组件当中,让父组件的状态来控制这两个组件的重渲染,当我们组件的层次越来越深的时候,状态需要一直往下传,无疑加大了我们代码的复杂度,我们需要一个状态管理中心,来帮我们管理我们状态 state。
  5. 这个时候,redux 出现了,我们可以将所有的 state 交给 redux 去管理,当我们的某一个 state 有变化的时候,依赖到这个 state 的组件就会进行一次重渲染,这样就解决了我们的我们需要一直把 state 往下传的问题。redux 有 action、reducer 的概念,action 为唯一修改 state 的来源,reducer 为唯一确定 state 如何变化的入口,这使得 redux 的数据流非常规范,同时也暴露出了 redux 代码的复杂,本来那么简单的功能,却需要完成那么多的代码。
  6. 后来,社区就出现了另外一套解决方案,也就是 mobx,它推崇代码简约易懂,只需要定义一个可观测的对象,然后哪个组价使用到这个可观测的对象,并且这个对象的数据有更改,那么这个组件就会重渲染,而且 mobx 内部也做好了是否重渲染组件的生命周期 shouldUpdateComponent,不建议开发者进行更改,这使得我们使用 mobx 开发项目的时候可以简单快速的完成很多功能,连 redux 的作者也推荐使用 mobx 进行项目开发。但是,随着项目的不断变大,mobx 也不断暴露出了它的缺点,就是数据流太随意,出了 bug 之后不好追溯数据的流向,这个缺点正好体现出了 redux 的优点所在,所以针对于小项目来说,社区推荐使用 mobx,对大项目推荐使用 redux