[译]深刻解读 React 核心之元素篇

引言

本系列涵盖了使用 React 的全部知识,分为上、中、下三篇。此为上篇,本篇主讲 React 元素渲染。前端

本系列涵盖 React v16.9,但更多的是 React 全面解析,具体 React v16.9 新特性可查看 [译]React v16.9 新特性react

完整系列包含:git

1、React

React 官网上定义:数组

React 是一个用于构建用户界面的 JavaScript 库。浏览器

首先,让咱们看一下这个定义的两个不一样部分:框架

1. React 是一个 JavaScript 库

这意味着它不彻底是一个 框架 。它不是一个完整的解决方案,你常常须要使用更多的库来辅助 React 造成一套完整的解决方案。React 不对解决方案中的其余部分做任何假设。模块化

框架是一个伟大的目标,特别是对于年轻的团队和初创公司。在使用框架时,已经为你作出了许多明智的设计决策,这为你提供了一条清晰的道路,能够专一于编写良好的应用程序逻辑。可是,框架存在一些缺点。对于从事大型代码库开发工做的,而且经验丰富的开发人员来讲,这些缺点有时会极具破坏性的。

尽管有些人声称,框架并不灵活。框架一般但愿你以某种方式编码全部内容。若是你试图偏离这种方式,框架常常会为此与你发生冲突。框架一般很大而且功能齐全,若是你只须要使用它们中的一小部分,你必需要引入整个框架。不能否认今天这一点正在改变,但仍然不理想,一些框架正在模块化,我认为这很棒,但我是纯 Unix 哲学的忠实粉丝:

编写作一件事并作得好的程序。编写程序以协同工做。 - 道格麦克罗伊

React 遵循 Unix 哲学,由于它是一个小型库,专一于一件事而且很是好地完成这件事。“一件事” 是React定义的第二部分:构建用户界面

2. React 用于构建用户界面

用户界面(UI)是展示在用户面前,用于与机器交互的媒介。用户界面无处不在,从微波炉上的简单按钮到航天飞机的仪表板。若是咱们尝试链接的设备能够识别 JavaScript ,咱们就可使用 React 来描述它的 UI 。因为 Web 浏览器识别 JavaScript ,咱们可使用 React 来描述 Web UI 。

咱们只须要告诉浏览器咱们想要什么!React 将表明咱们在 Web 浏览器中构建实际的 UI。若是没有React或相似的库,咱们须要使用原生 Web API 和 JavaScript 手动构建 UI,这并不容易。

当你听到 React 是声明 的陈述时,这正是它的含义。咱们用 React 描述 UI 并告诉它咱们想要什么(而不是如何作)。React将负责“how”并将咱们的声明性描述(咱们用React语言编写)转换为浏览器中的实际UI。React 与 HTML 自己共享这种简单的声明能力,可是使用React,咱们能够声明表明动态数据的HTML UI,而不只仅是静态数据。

当 React 发布时,有不少关于它性能的质疑,由于它引入了一个虚拟 DOM 的聪明想法,能够用来协调实际的DOM(咱们将在下一节讨论)。

DOM是文档对象模型(Document Object Model)。它是HTML(和XML)文档的浏览器编程接口,将它们视为树结构。DOM API可用于更改文档结构,样式和内容。

虽然今天 React 很是流行的最重要缘由之一就是 React 的高性能,但我并无把它归类为 React 的最好的一点。我认为 React 是一个游戏规则改变者,由于它在开发人员和浏览器之间建立了一种通用语言,容许开发人员以声明方式描述UI并管理其状态(state)上的操做,而不是对 DOM 元素的操做。它只是用户界面“结果”的语言。开发人员只是根据“最终”状态(如函数)来描述接口,而不是采用步骤来描述接口上的操做。当更新该状态时,React会根据它来更新 DOM 中的 UI(高效更新)。

若是有人要求你给出一个 React 为何值得学习的缘由,就是它是一个基于结果的 UI 语言。咱们能够将这种语言称为 React语言

2、React 语言

假设咱们有一个像这样的 todos 列表:

const todos: [
  { body: 'Learn React Fundamentals', done: true },
  { body: 'Build a TODOs App', done: false },
  { body: 'Build a Game', done: false },
];
复制代码

todos 数组是 UI 的起始状态。你须要构建一个 UI 来显示和管理。在这个页面上有三个操做,风别是一个添加新 todo 的表单 ,一个将 todo 标记为已完成,以及删除全部已完成的 todo

todos

这些操做中的每个都将要求应用程序执行DOM操做以建立,插入,更新或删除 DOM 节点。使用 React ,你没必要担忧全部这些 DOM 操做。你没必要担忧什么时候须要发生或如何有效地执行它们。你只需将 todos 数组置于应用程序的 state 中,而后使用 React 语言命令 React 在 UI 中以某种方式显示该状态:

<header>TODO List</header>

<ul>
  {todos.map(todo =>
    <li>{todo.body}</li>
  )}
</ul>

// Other form elements...
复制代码

以后,你能够专一于对该todos 数组进行数据操做!你能够添加,删除和更新该数组,React 会将你对该对象所作的更改渲染到浏览器上。

这种基于最终状态建模 UI 的心理模型更易于理解和使用,尤为是当视图具备大量数据转换时。例如,考虑一下能够告诉你有多少朋友在线的视图。该视图的 state 只是目前有多少朋友在线的一个数字。它并不关心刚才三个朋友上网,而后其中一个断线,而后两个加入。它只知道在这个时刻,有四个朋友在线。

3、树协调算法

在 React 以前,当咱们须要使用浏览器的API(DOM API)时,咱们尽量避免遍历 DOM 树,那是由于 DOM 上的任何操做都在同一个线程中完成,该线程负责浏览器中发生的全部事情,包括对用户事件的反应:如打字,滚动,调整大小等。

对 DOM 的任何昂贵的操做均可能给用户带来缓慢的操做体验。很是重要的是,你的应用程序执行最小的操做时,应尽量地批量处理。React 就提出了一个独特的概念来帮助咱们作到这一点!

当咱们告诉 React 在浏览器中渲染元素树时,它首先生成该树的虚拟表示并将其保存在内存中以供往后使用。而后它将继续执行DOM操做,使树显示在浏览器中。

当咱们告诉 React 更新以前渲染的元素树时,它会生成树的新的虚拟表示。如今React在内存中有2个版本的树!

要在浏览器中呈现更新的树,React 不会丢弃已呈现的内容。相反,它将比较它在内存中的2个虚拟版本,计算它们之间的差别,找出主树中须要更新的子树,而且只在浏览器中更新这些子树。

这个过程就是所谓的树协调算法,它是 React 渲染浏览器 DOM 树的一种很是有效的方法。

除了基于声明结果的语言和有效的树协调以外,如下是我认为React得到其普遍流行的其余一些缘由:

  • 使用 DOM API 很难。React 使开发人员可以使用比真实浏览器更友好的**“虚拟”浏览**器。React将代理你与DOM进行通讯。
  • React 常常被赋予 Just JavaScript 标签。这意味着它有一个很是小的API可供学习,以后你的 JavaScript 技能使你成为更好的 React 开发人员。这比具备更大 API 的库更具优点。此外,React API 主要是函数(若是须要,还能够选择类)。当你听到 UI 视图是你的数据的函数时,在 React 中确实如此。
  • 学习 React也为 iOS 和 Android 移动应用程序带来了巨大的回报。**React Native **容许你使用 React 技能来构建本机移动应用程序。你甚至能够在 Web ,iOS 和 Android 应用程序之间共享一些逻辑。
  • Facebook 的 React 团队测试了在 facebook.com 上引入 React 的全部改进和新功能,这增长了社区对库的信任。React版本中不多见到大而严重的错误,由于它们只有在 Facebook 进行完全的生产测试后才能发布。React 还支持其余频繁使用的 Web 应用程序,如 Netflix,Twitter,Airbnb 等等。

4、React 示例

为了看到树协调算法的实际好处及其所带来的巨大差别,让咱们看一个只关注该概念的简单示例。让咱们生成并更新两次HTML元素树,一次使用本机Web API,而后使用React API(及其协调工做)。为了简化这个例子,我不会使用组件或 JSX(与React一块儿使用的 JavaScript 扩展)。我还将在 JavaScript 间隔计时器内执行更新操做。这不是咱们编写React应用程序的方式,而是让咱们一次关注一个概念。

在此会话中,使用2种方法将简单的HTML元素呈现给显示:

  • 方法1:直接使用 Web DOM API

    document.getElementById('mountNode').innerHTML = ` <div> Hello HTML </div> `;
    复制代码
  • 方法2:使用 React API

    ReactDOM.render(
      React.createElement(
        'div',
        null,
        'Hello React',
      ),
      document.getElementById('mountNode2'),
    );
    复制代码

ReactDOM.render 方法和 React.createElement 方法是 React 应用程序中的核心 API 方法。事实上,若是不使用这两种方法,React Web 应用程序就不可能存在。简要介绍一下:

ReactDOM.render

是 React 应用程序渲染到浏览器 DOM 的入口点。它有两个参数:

  • 第一个参数是向浏览器呈现的内容。这是一个 React 元素。
  • 第二个参数是 React 渲染在浏览器上的位置。这必须是存在于静态的 HTML 中的有效 DOM 节点。上面的示例使用了一个特殊 mountNode2元素,该元素存在于playground 的显示区域中(第一个 mountNode 用于本机版本)。

React元素到底是什么?它是用来描述 Actual DOM 元素的 Virtual 元素。也就是 React.createElement API方法返回的内容。

React.createElement

在 React 中,咱们不使用字符串来表示 DOM 元素(如上面的 DOM 示例中),而是使用对方法的调用来表示带有对象的 DOM 元素 React.createElement 。这些对象称为 React 元素。

React.createElement 函数有不少参数:

  • 第一个参数是要表示的DOM元素的 HTML 标记,div 在此示例中。
  • 第二个参数为任何属性(如idhreftitle,等),若是没有属性,可使用 null
  • 第三个参数是 DOM 元素的内容。咱们在那里放了一个 Hello React 字符串。可选的第三个参数以及它后面的全部可选参数,造成渲染元素的列表。元素能够包含0个或更多子元素。

React.createElement 也可用于从 React 组件建立元素。

React 元素在内存中建立。为了实际在真实 DOM 中显示一个 React 元素,咱们使用 ReactDOM.render 来实现将 React 元素的状态映射到浏览器中的真实 DOM 树中。

3. 嵌套 React 元素

咱们有两个节点:一个用 DOM API 直接控制,另外一个用 React API 控制。

咱们在浏览器中构建这两个节点的方式之间惟一的区别是,在 HTML 版本中,咱们使用字符串来表示 DOM 树,而在 React 版本中,咱们使用纯 JavaScript 调用并使用对象表示 DOM 树。

不管 HTML UI 有多复杂,使用 React 时,每一个 HTML 元素都将用 React 元素表示。

示例一:添加多个 HTML 元素,添加一个文本框来读取用户的输入

对于 HTML 版本,你能够直接在模板中注入新元素的标记:

document.getElementById('mountNode').innerHTML = ` <div> Hello HTML <input /> </div> `;
复制代码

而对 React 执行相同操做,就须要在 React.createElement 上面的第三个参数以后添加更多参数。为了匹配到在原生 DOM 示例中的内容,咱们能够添加第四个参数,这是另外一个 React.createElement 呈现 input 元素的调用:

ReactDOM.render(
  React.createElement(
    "div",
    null,
    "Hello React ",
    React.createElement("input")
  ),
  document.getElementById('mountNode2'),
);
复制代码
示例二:渲染当前时间

可使用它 new Date().toLocaleTimeString() 来显示简单的时间字符串,并把它放在一个 pre 标签中。

原生 DOM 版本执行的操做:

document.getElementById('mountNode1').innerHTML = ` <div> Hello HTML <input /> <pre>${new Date().toLocaleTimeString()}</pre> </div> `;
复制代码

在 React 中,咱们须要在顶层 div 元素中添加第五个参数。而且,这个新的第五个参数是另外一个 React.createElement 调用,建立一个 pre 标签,而且内容为 Date().toLocaleTimeString()

ReactDOM.render(
  React.createElement(
    'div',
    null,
    'Hello React ',
    React.createElement('input'),
    React.createElement(
      'pre',
      null,
      new Date().toLocaleTimeString()
    )
  ),
  document.getElementById('mountNode2')
);
复制代码

所以,你可能认为使用 React 比使用简单熟悉的原生方式要困可贵多。那么为何咱们要放弃熟悉的 HTML 而且必须学习 React API 来编写能够用 HTML 编写实现的内容喃?

答案不在于渲染第一个 HTML 视图,这是在于咱们如何更新已渲染的 DOM 视图。

4. 更新React元素

咱们对 DOM 树进行更新操做。例如:简单地让时间字符串每秒更新。

咱们可使用 setInterval Web 计时器 API 轻松地在浏览器中重复 JavaScript 函数调用。将两个版本的全部 DOM 操做放入一个函数中,命名它 render ,并在 setInterval 调用中使用它以使其每秒重复一次。

如下是此示例的完整代码:

const render = () => {
  // HTML DOM
  document.getElementById('mountNode').innerHTML = ` <div> Hello HTML <input /> <pre>${new Date().toLocaleTimeString()}</pre> </div> `;
    
  // React DOM
  ReactDOM.render(
    React.createElement(
      'div',
      null,
      'Hello React',
      React.createElement('input', null),
      React.createElement('pre', null, new Date().toLocaleTimeString())
    ),
    document.getElementById('mountNode2')
  );
};

// 每秒更新一次
setInterval(render, 1000);
复制代码

点击查看实例

请注意两个版本中的时间字符串如何每秒更新。咱们如今正在更新 DOM 中的 UI 。

**这是React可能会让你大吃一惊的时刻。**若是你尝试在原生 DOM 版本的文本框中键入内容,则没法执行此操做。这是由于咱们每秒都会抛弃原有的整个DOM节点并从新生成它。

可是,若是你尝试在 React 版本中的文本框中键入内容,却能够执行。

虽然整个 React 渲染代码都在计时器内,但 React只更改 pre 元素的内容而不是整个 DOM 树。这就是文本输入框没有从新生成的缘由,咱们能够输入它。

若是你检查 Chrome DevTools 元素面板中的两个DOM节点,你能够看到这两种方式更新 DOM 的不一样。

  • 原生 HTML 版本:div#mountNode 每秒从新生成其整个 DOM 树
  • React 版本:div#mountNode2 容器中仅 pre 每秒从新生成

更新DOM

这是 React 的智能差别算法。它只在主 DOM 树中更新实际须要更新的内容,同时保持其余全部内容相同。这种差别化过程是可行的,由于它在内存中保留了 React 的虚拟 DOM 表示。不管 UI 视图须要从新生成多少次, React 将只向浏览器提供所需的更新部分。

这种方法不只效率更高,并且消除了咱们考虑更新 UI 的方式的复杂性。让 React 处理关因而否须要更新 DOM 的全部计算模块,使咱们可以专一于思考咱们的数据(state 状态)以及 UI 展现。

而后,咱们根据须要管理数据状态的更新,而没必要担忧在浏览器的实际 UI 中渲染这些更新所需的步骤(由于咱们知道 React 将彻底执行此操做而且以更有效的方式执行!)

本文翻译自:jscomplete.com/learn/compl…

系列文章

想看更过系列文章,点击前往 github 博客主页

走在最后,欢迎关注:前端瓶子君,每日更新

前端瓶子君
相关文章
相关标签/搜索