React中的对帐详细说明

时间:2016-01-25 10:24:15

标签: reactjs

我是JS的新手。任何人都可以解释它的确切运作方式。我试过从官方网站的反应中了解它,但没有得到它。

3 个答案:

答案 0 :(得分:4)

简单地说,您告诉React您希望UI处于什么状态,并确保DOM匹配该状态。将新元素添加到UI时,将创建一个虚拟DOM(表示为树)。每个元素都是该树上的一个节点。如果这些元素中任何一个的状态改变,则将创建一个新的虚拟DOM树。然后将该树与先前的虚拟DOM树进行比较或“差异化”。完成此操作后,虚拟DOM将计算出对真实DOM进行这些更改的最佳方法。这样可以确保对实际DOM的操作最少。因此,降低了更新实际DOM的性能成本。这里最大的好处是,作为开发人员,您将不需要知道属性处理,事件处理或手动DOM更新在后台如何发生。我将列出React进行对帐的步骤顺序和原因:

  1. 频繁的DOM操作昂贵且性能沉重。
  2. 虚拟DOM是真实DOM的虚拟表示。
  3. 状态发生变化时,虚拟DOM会更新,而先前的和 比较了当前版本的虚拟DOM。这称为“差异”。
  4. 然后,虚拟DOM将批量更新发送到真实DOM 更新用户界面。
  5. React使用虚拟DOM来增强其性能。
  6. 它使用可观察对象来检测状态和道具更改。
  7. React使用高效的差异算法来比较虚拟DOM的版本。
  8. 然后确保将批处理更新发送到真实DOM,以用于 重新粉刷或重新渲染用户界面。

答案 1 :(得分:2)

React上下文中的协调意味着使React的虚拟DOM树与浏览器的真实DOM树保持一致。这发生在(重新)渲染期间

关键是,无法保证React的虚拟DOM的特定元素在其整个生命周期中引用浏览器的同一DOM节点。这样做的原因是React有效地更新DOM的方法。如果组件包含动态或有状态子项,则可以使用特殊key属性来解决此问题。

答案 2 :(得分:0)

这就是我的理解:

您将同意使用react使事情变得简单和快捷。 使用JSX,我们可以简化用户定义的组件。 一天结束时,所有这些都将转换为纯JavaScript(我想您了解React.createElement的工作原理),其中包含其他函数调用的函数调用作为其参数/属性包含其他函数调用的类,等等。 无论如何,我们无需担心,因为它会在内部自行做出反应。

但这如何给我们一个UI? 为什么从其他UI库更快?

<-ALL HAIL ReactDOM库和渲染方法->

一个普通的ReactDOM调用看起来像这样:

// I have avoided the usage of JSX as its get transpiled anyway 
ReactDOM.render(
  React.createElement(App, { //if any props to pass or child }),    // "creating" a component
  document.getElementById('#root')              // inserting it on a page
);
Heard about VirtualDOM ? { yes : 'Good'} : { no : 'still Good'} ;

基于我们编写的组件,React.createElement构造具有类型和道具的元素对象,并将子元素放置在道具内的子项下。 它递归地执行此操作,并填充最终对象,该对象准备好转换为等效HTML并绘制到浏览器。

这就是VirtualDOM,它位于react内存中,react对此进行所有操作,而不是对实际的Browser DOM进行操作。 看起来像这样:

{
  type: 'div',// could be other html'span' or user-diff 'MyComponent'
  props: {
    className: 'cn',
    //other props ...
    children: [
      'Content 1!', // could be a component itself
      'Content 2!', // could be a component itself
      'Content n!', // could be a component itself
    ]
  }
}

构建虚拟DOM对象后,ReactDOM.render会将其转换为我们的浏览器可以根据以下规则将UI分配给UI的DOM节点:

  

如果type属性包含带有标签名称的字符串,请使用props下列出的所有属性创建标签。   如果我们在类型下有一个函数或一个类,请调用它并对结果进行递归重复该过程。   如果道具下有任何孩子,请对每个孩子一个接一个地重复该过程,并将结果放置在父对象的DOM节点中。

浏览器将其绘制到UI,这是一项昂贵的任务。 React非常了解这一点。 更新组件意味着创建一个新对象并绘制到UI。即使涉及很小的更改,也将重新创建整个DOM树。 因此,我们如何使浏览器永远不必每次都创建DOM,而只绘制必要的内容。

这是我们需要对帐和React ..的差异算法的地方。 感谢做出反应,我们不必手动进行自我处理,它在内部here is a nice article to understand deeper

中得到了照顾

现在,您甚至可以参考official React docs for Reconsiliation

值得注意的几点:

  

React基于两个假设实现启发式O(n)算法:        1)不同类型的两个元素将产生不同的树。        2)开发人员可以使用关键道具提示哪些子元素在不同的渲染中可能稳定。

实际上,这些假设对几乎所有实际用例均有效。 如果不满足这些要求,则会导致性能问题。

我只是复制粘贴其他几点,只是为了了解其完成方式:

差异: 区分两棵树时,React首先比较两个根元素。行为因根元素的类型而异。

场景1:类型是字符串,两次调用时类型保持不变,道具也没有变化。

// before update
{ type: 'div', props: { className: 'cn' , title : 'stuff'} }

// after update
{ type: 'div', props: { className: 'cn' , title : 'stuff'} }

这是最简单的情况:DOM保持不变。

场景2:类型仍然是相同的字符串,道具不同。

// before update:
{ type: 'div', props: { className: 'cn' } }

// after update:
{ type: 'div', props: { className: 'cnn' } }

由于类型仍然代表HTML元素,React着眼于两者的属性,React知道如何通过标准DOM API调用更改其属性,而无需从DOM树中删除底层DOM节点。

React还知道仅更新已更改的属性。例如:

<div style={{color: 'red', fontWeight: 'bold'}} />

<div style={{color: 'green', fontWeight: 'bold'}} />
  

在这两个元素之间进行转换时,React知道仅修改颜色样式,而不修改fontWeight。

/////////当组件更新时,实例保持不变,因此在渲染器之间保持状态。 React更新基础组件实例的属性以匹配新元素,并在基础实例上调用componentWillReceiveProps()和componentWillUpdate()。 接下来,调用render()方法,并且diff算法根据先前的结果和新的结果进行递归。 处理完DOM节点后,React然后在子节点上递归。

方案3:类型已更改为其他String,或从String更改为组件。

// before update:
{ type: 'div', props: { className: 'cn' } }

// after update:
{ type: 'span', props: { className: 'cn' } }

正如React现在看到的类型不同,它甚至不会尝试更新我们的节点:旧元素将连同其所有子元素一起被移除(卸载)。

  

请记住,React使用===(等于三倍)比较类型值,因此它们必须是同一类或相同函数的相同实例。

方案4:类型是一个组件。

// before update:
{ type: Table, props: { rows: rows } }

// after update:
{ type: Table, props: { rows: rows } }

您可能会说:“但是什么都没有改变!”,这是错误的。

  

如果type是对函数或类(即常规的React组件)的引用,并且我们开始了树协调过程,那么React将始终尝试查看组件内部以确保在render上返回的值没有改变(某种预防副作用的措施)。冲洗并重复树中的每个组件,是的,复杂的渲染可能也会变得昂贵!

为了确保这些东西干净:

class App extends React.Component {

  state = {
    change: true
  }

  handleChange = (event) => {
    this.setState({change: !this.state.change})
  }

  render() {
    const { change } = this.state
    return(
      <div>
        <div>
          <button onClick={this.handleChange}>Change</button>
        </div>
        {
          change ? 
          <div>
            This is div cause it's true
            <h2>This is a h2 element in the div</h2>
          </div> :
          <p>
            This is a p element cause it's false
            <br />
            <span>This is another paragraph in the false paragraph</span>
          </p>
        }
      </div>
    )
  }
}

孩子============================>

当元素有多个子元素时,我们还需要考虑React的行为。假设我们有这样一个元素:

// ...
props: {
  children: [
      { type: 'div' },
      { type: 'span' },
      { type: 'br' }
  ]
},
// ...

我们想让周围的孩子们洗牌:

// ...
props: {
  children: [
    { type: 'span' },
    { type: 'div' },
    { type: 'br' }
  ]
},
// ...

那会发生什么?

如果“差异”时,React看到props.children中的任何数组,它开始按顺序查看它们中的元素与之前看到的元素进行比较:将索引0与索引0进行比较,索引1到索引1,依此类推。 对于每对,React将应用上述规则集。

React具有built-in way来解决此问题。如果元素具有键属性,则将通过键的值而不是索引来比较元素。只要键是唯一的,React就会移动元素而不将其从DOM树中删除,然后再放回它们(React中称为安装/卸载的过程)。

  

因此,密钥应稳定,可预测且唯一。不稳定的键(如Math.random()产生的键)将导致不必要地重新创建许多组件实例和DOM节点,这可能导致性能下降和子组件中的状态丢失。

由于React依赖启发式算法,如果不满足其背后的假设,性能将会受到损害。

状态更改时:========================================> < / p>

调用this.setState也会导致重新渲染,但不是整个页面,而是仅组件本身及其子代。父母和兄弟姐妹都可以幸免。当我们有一棵大树时,这很方便,而我们只想重绘其中的一部分。