React 协调机制的工作原理

Avatar of Kingsley Silas
Kingsley Silas 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

React 速度很快!部分速度来自于仅更新 DOM 中需要更新的部分。您无需担心太多,并且可以获得速度提升。只要您了解 setState() 的工作原理,就可以顺利进行。但是,熟悉这个强大的库如何更新应用程序的 DOM 也很重要。了解这一点将对您作为 React 开发人员的工作起到至关重要的作用。

DOM 是什么?

浏览器通过解析您编写的代码来构建 DOM,它在渲染页面之前执行此操作。 DOM 将页面中的文档表示为节点和对象,提供了一个接口,以便编程语言可以插入并操作 DOM。DOM 的问题在于它没有针对动态 UI 应用程序进行优化。因此,当需要更改很多内容时,更新 DOM 会降低应用程序的速度;因为浏览器必须重新应用所有样式并渲染新的 HTML 元素。即使没有任何更改,也会发生这种情况。

什么是协调机制?

协调机制是 React 更新 DOM 的过程。当组件的 状态发生变化 时,React 必须计算是否有必要更新 DOM。它通过创建一个虚拟 DOM 并将其与当前 DOM 进行比较来实现。在这种情况下,虚拟 DOM 将包含组件的新状态。

让我们构建一个简单的组件来添加两个数字。数字将输入到输入字段中。

查看 Kingsley Silas Chijioke (@kinsomicrote) 在 CodePen 上创建的笔 reconciliation Pen

首先,我们需要为字段设置初始状态,然后在输入数字时更新状态。组件将如下所示

class App extends React.Component {
  
  state = {
    result: '',
    entry1: '',
    entry2: ''
  }

  handleEntry1 = (event) => {
    this.setState({entry1: event.target.value})
  }
  
  handleEntry2 = (event) => {
    this.setState({entry2: event.target.value})
  }

  handleAddition = (event) => {
    const firstInt = parseInt(this.state.entry1)
    const secondInt = parseInt(this.state.entry2)
    this.setState({result: firstInt + secondInt })
  }
  
  render() {
    const { entry1, entry2, result } = this.state
    return(
      <div>  
        <div>
          <p>Entry 1: { entry1 }</p>
          <p>Entry 2: { entry2 }</p>
          <p>Result: { result }</p>
        </div>
        <br />
        <div>
          <span>Entry 1: </span>
          <input type='text' onChange={this.handleEntry1} />
        </div>
        <br />
        <div>
          <span>Entry 2: </span>
          <input type='text' onChange={this.handleEntry2} />
        </div>
        <div>
          <button onClick={this.handleAddition} type='submit'>Add</button>
        </div>
      </div>
    )
  }
}

在初始渲染时,DOM 树将如下所示;

A screenshot from DevTools that shows the HTML rendering of the app.

在第一个输入字段中进行输入时,React 会创建一个新的树。新的树(即虚拟 DOM)将包含 entry1 的新状态。然后,React 将虚拟 DOM 与旧 DOM 进行比较,并根据比较结果找出两个 DOM 之间的差异,并仅对不同的部分进行更新。每次 App 组件的状态发生更改时,都会创建一个新的树——在任一输入字段中输入值时,或单击按钮时。

Animated gif showing how the markup in DevTools changes when numbers are added to the input field.

比较不同的元素

当组件的状态发生变化,导致需要将元素从一种类型更改为另一种类型时,React 会卸载整个树并从头开始构建一个新的树。这会导致该树中的每个节点都被销毁。

让我们看一个例子

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>
    )
  }
}

在初始渲染时,您将看到 div 及其内容,以及单击按钮如何导致 React 销毁 div 及其内容的树,并改为构建 <p> 元素的树。如果两种情况下我们都有相同的组件,也会发生同样的事情。该组件将与其所属的先前树一起被销毁,并构建一个新的实例。请参阅下面的演示;

查看 Kingsley Silas Chijioke (@kinsomicrote) 在 CodePen 上创建的笔 reconciliation-2 Pen

比较列表

React 使用键 来跟踪列表中的项目。键有助于它确定项目在列表中的位置。如果列表没有键会发生什么?React 将会变异列表的每个子元素,即使没有新的更改。

换句话说,React 会更改没有键的列表中的每个项目。

这是一个例子

const firstArr = ['codepen', 'codesandbox']
const secondArr = ['github', 'codepen', 'bitbucket', 'codesanbox']

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>

        <ul>
        {
          change ? 
            firstArr.map((e) => <li>{e}</li>)
          :
          secondArr.map((e) => <li>{e}</li>)
        }
        </ul>
      </div>
    )
  }
}

这里,我们有两个数组,它们根据组件的状态进行渲染。React 无法跟踪列表中的项目,因此每次需要重新渲染时,它都必须更改整个列表。这会导致性能问题。

在您的控制台中,您将看到如下警告

Warning: Each child in an array or iterator should have a unique "key" prop.

要解决此问题,您可以为列表中的每个项目添加一个唯一的键。在这种情况下,最好的解决方案是创建一个对象数组,每个项目都有一个唯一的 id。如果我们使用数组索引,那将是 一种反模式,最终会对我们造成伤害。

const firstArr = [
  { id: 1, name: 'codepen'},
  { id: 2, name: 'codesandbox'}
]
const secondArr = [
  { id: 1, name: 'github'},
  { id: 2, name: 'codepen'},
  { id: 3, name: 'bitbucket'},
  { id: 4, name: 'codesandbox'}
]

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>

        <ul>
          {
            change ? 
            firstArr.map((e) => <li key={e.id}>{e.name}</li>)
            :
            secondArr.map((e) => <li key={e.id}>{e.name}</li>)
          }
        </ul>
      </div>
    )
  }
}

查看 Kingsley Silas Chijioke (@kinsomicrote) 在 CodePen 上创建的笔 reconciliation-3 Pen

总结

总而言之,以下是了解 React 中协调机制概念的两个主要要点

  • React 可以使您的 UI 速度更快,但它需要您的帮助。了解其 协调过程 非常有益。
  • React 不会完全重新渲染您的 DOM 节点。它只更改需要更改的内容。差异化过程非常快,您可能不会注意到它。