diff算法,key

React快速的致胜法宝是虚拟DOM及其高效的diff算法。

可以无需担心性能问题而“随时”刷新整个页面,虚拟DOM可以确保只对界面上真正变化的部分进行实际的DOM操作。虽然在实际开发中,基本无需关心虚拟DOM是如何运作的,但理解其运行机制不仅能帮助更好地理解React组件的生命周期,还会对进一步优化React程序也会有很大帮助。

1、Diff算法Web界面由DOM树构成,页面某部分发生变化,其实是某个DOM节点发生了变化。

变化前后对应两套界面,需要React比较两个界面的区别,这就需要通过diff算法对DOM树进行分析,即针对变化前后的两棵DOM树,找到最少的转换步骤。标准的diff算法的复杂度为O(n^3),Facebook工程师结合Web界面的特点做出了以下两个简单的假设,使得Diff算法的复杂度直接降低到O(n):

① 相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构;
② 对于同一层次的一组子节点,可以通过唯一的id进行区分。
逐层进行节点比较:在React中,两棵DOM树只会对同一层的节点进行比较。若发现节点已不存在,则该节点及其子节点会被完全删除,不会用于进一步的比较。这样,只需要对树进行一次遍历,就能完成整个DOM树的比较。
对于同层节点,若节点本身完全相同(类型相同,属性相同),只是位置不同,则React只需要考虑同层节点的位置变换,不需要进行节点的销毁和重新创建,这就需要用到下面介绍的key属性,
但对于不同层的节点,只能销毁和重新创建。比较两个虚拟DOM节点,可以分为以下三种情况:
1) 节点类型不同; 当在树中的同一位置前后的节点类型不同,React会直接删除原节点,然后创建并插入新的节点。
注意:删除节点即彻底销毁该节点,也就是说,后续不会查找是否有另外一个节点等同于删除的该节点。如果删除的该节点有子节点,那么子节点也会被删除。
这也是diff算法复杂度能降到O(n)的原因。同理,当树的同一个位置遇到前后不同的组件时,也是销毁原组件,把新的组件加上去。
这应用了第一个假设,不同的组件一般会产生不同的DOM结构,与其浪费时间去比较不同的DOM结构,还不如完全创建一个新的组件加上去。
2) 节点类型相同,但是属性不同。React会对属性进行重设从而实现节点的转换。
3) 节点类型相同且属性相同。
对于同层节点,若节点本身完全相同(类型相同且属性相同),只是位置不同,则React只需要考虑同层节点的位置变换,不需要进行节点的销毁和重新创建,这就需要用到下面介绍的key属性。
对于不同层的节点,即使节点本身完全相同(类型相同且属性相同),也只能销毁和重新创建。

2、key属性为列表节点提供唯一的key属性,

可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作的次数,提高性能。React在处理列表时如果未给每个元素设置key,会提示如下找不到key的警告:Warning: Each child in an array or iterator should have a unique "key" prop.虽然无视该警告大部分界面也能正确工作,但会带来潜在的性能问题——React可能无法高效地更新该列表。
eg1:某列表为a-b-c-d,若需要往b和c直接插入节点e,在jQuery中使用$(b).after(e)即可实现,而在React中只会告诉React新的列表应该是a-b-e-c-d,更新界面交由diff算法完成。
如果每个节点都没有唯一的标识key,那么React将无法识别每一个节点,导致更新过程很低效,即将c更新成e,d更新成c,最后再插入一个d节点;
而如果给每个节点唯一的标识key,React将能够找到正确的位置去插入新的节点。
eg2:某虚拟DOM树的某一层原有两个节点a和b,页面更新后得到的新DOM树的该层还是只有a和b,只是a和b的位置与原来不同了。
如果未提供唯一的标识key,React将认为a和b对应的位置前后的组件类型不同,而选择完全销毁后重新创建;而如果提供了唯一的标识key,React将能够判断出a和b只是位置不同,更新位置即可。

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

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

调用 setState 之后发生了什么?

在代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。
经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。在 React 得到元素树之后,
React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。

react 生命周期函数

初始化阶段:

getDefaultProps:获取实例的默认属性

getInitialState:获取每个实例的初始化状态

componentWillMount:组件即将被装载、渲染到页面上

render:组件在这里生成虚拟的 DOM 节点

componentDidMount:组件真正在被装载之后

运行中状态:

componentWillReceiveProps:组件将要接收到属性的时候调用

shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止 render 调用,后面的函数不会被继续执行了)

componentWillUpdate:组件即将更新不能修改属性和状态

render:组件重新描绘

componentDidUpdate:组件已经更新

销毁阶段:

componentWillUnmount:组件即将销毁

shouldComponentUpdate 是做什么的,(react 性能优化是哪个周期函数?)

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

为什么虚拟 dom 会提高性能?

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

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

react diff 原理

把树形结构按照层级分解,只比较同级元素。

给列表结构的每个单元添加唯一的 key 属性,方便比较。

React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)

合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.

选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。

React 中 refs 的作用是什么?

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

class CustomForm 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} />


)
}
如果你创建了类似于下面的 Twitter 元素,那么它相关的类定义是啥样子的?

{(user) => user === null
?
: }

import React, { Component, PropTypes } from 'react'
import fetchUser from 'twitter'
// fetchUser take in a username returns a promise
// which will resolve with that username's data.
class Twitter extends Component {
// finish this
}
如果你还不熟悉回调渲染模式(Render Callback Pattern),这个代码可能看起来有点怪。这种模式中,组件会接收某个函数作为其子组件,然后在渲染函数中以 props.children 进行调用:

import React, { Component, PropTypes } from 'react'
import fetchUser from 'twitter'
class Twitter extends Component {
  state = {
    user: null,
  }
  static propTypes = {
    username: PropTypes.string.isRequired,
  }
  componentDidMount () {
    fetchUser(this.props.username)
      .then((user) => this.setState({user}))
  }
  render () {
    return this.props.children(this.state.user)
  }
}

这种模式的优势在于将父组件与子组件解耦和,父组件可以直接访问子组件的内部状态而不需要再通过 Props 传递,这样父组件能够更为方便地控制子组件展示的 UI 界面。譬如产品经理让我们将原本展示的 Badge 替换为 Profile,我们可以轻易地修改下回调函数即可:

<Twitter username='tylermcginnis33'>
  {(user) => user === null
    ? <Loading />
    : <Profile info={user} />}
</Twitter>

展示组件(Presentational component)和容器组件(Container component)之间有何不同
展示组件关心组件看起来是什么。展示专门通过 props 接受数据和回调,并且几乎不会有自身的状态,但当展示组件拥有自身的状态时,通常也只关心 UI 状态而不是数据的状态。

容器组件则更关心组件是如何运作的。容器组件会为展示组件或者其它容器组件提供数据和行为(behavior),它们会调用 Flux actions,并将其作为回调提供给展示组件。容器组件经常是有状态的,因为它们是(其它组件的)数据源。

类组件(Class component)和函数式组件(Functional component)之间有何不同
类组件不仅允许你使用更多额外的功能,如组件自身的状态和生命周期钩子,也能使组件直接访问 store 并维持状态

当组件仅是接收 props,并将组件自身渲染到页面时,该组件就是一个 '无状态组件(stateless component)',可以使用一个纯函数来创建这样的组件。这种组件也被称为哑组件(dumb components)或展示组件

(组件的)状态(state)和属性(props)之间有何不同
State 是一种数据结构,用于组件挂载时所需数据的默认值。State 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。

Props(properties 的简写)则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的(immutable)。组件不能改变自身的 props,但是可以把其子组件的 props 放在一起(统一管理)。Props 也不仅仅是数据--回调函数也可以通过 props 传递。

何为受控组件(controlled component)

在 HTML 中,类似 ,

猜你喜欢

转载自www.cnblogs.com/cherishnow/p/10919439.html