二、JSX 渲染

本章将向您介绍 JSX。我们将从基本知识开始:什么是 JSX?然后,您将看到 JSX 内置了对 HTML 标记的支持,正如您所期望的那样;因此,我们将在这里运行几个示例。在看过一些 JSX 代码之后,我们将讨论它如何使我们更容易地描述 UI 的结构。然后,我们将开始构建我们自己的 JSX 元素,并使用 JavaScript 表达式生成动态内容。

准备好的

什么是 JSX?

在本节中,我们将实现强制性的hello worldJSX 应用。此时,我们只是将脚趾浸入水中;下面将有更深入的例子。我们还将讨论是什么使这种语法能够很好地用于声明性 UI 结构。

你好,JSX

不用多说,这里是您的第一个 JSX 应用:

// The "render()" function will render JSX markup and 
// place the resulting content into a DOM node. The "React" 
// object isn't explicitly used here, but it's used 
// by the transpiled JSX source. 
import React from 'react'; 
import { render } from 'react-dom'; 

// Renders the JSX markup. Notice the XML syntax 
// mixed with JavaScript? This is replaced by the 
// transpiler before it reaches the browser. 
render( 
  (<p>Hello, <strong>JSX</strong></p>), 
  document.getElementById('app') 
); 

很简单,对吧?让我们来看看这里发生了什么。首先,我们需要导入相关位。在本例中,render()函数才是真正重要的,因为它将 JSX 作为第一个参数,并将其呈现给作为第二个参数传递的 DOM 节点。

围绕 JSX 标记的括号不是绝对必要的。然而,这是一个 React 约定,它帮助我们避免将标记结构与 JavaScript 结构混淆。

本例中实际的 JSX 内容放在一行上,它只是呈现一个段落,其中包含一些粗体文本。这里没有什么奇怪的事情,所以我们可以直接将这个标记作为普通字符串插入 DOM。然而,JSX 比这里显示的要多得多。本例的目的是展示将 JSX 呈现到页面上所涉及的基本步骤。现在,让我们谈谈声明式 UI 结构。

JSX 被转换成 JavaScript 语句;浏览器不知道什么是 JSX。我强烈建议从下载本书的配套代码 https://github.com/PacktPublishing/React-and-React-Native ,并在阅读时运行它。一切都会自动为你传送;您只需要遵循简单的安装步骤。

声明式 UI 结构

在我们继续使用代码示例之前,让我们花一点时间来思考一下我们的 To.T0. Hello World Tyt1 示例。JSX 内容简短。它也是声明性的,因为它描述了要呈现的内容,而不是如何呈现。具体地说,通过查看 JSX,您可以看到该组件将呈现一个段落以及其中的一些粗体文本。如果这是强制性的,可能会涉及更多的步骤,并且可能需要按照特定的顺序执行。

因此,我们刚刚实现的示例应该让您了解声明性 React 是关于什么的。随着本章和本书的深入,JSX 标记将变得更加详细。但是,它总是要描述用户界面中的内容。让我们继续。

就像 HTML 一样

最后,React 组件的任务是将 HTML 呈现到浏览器 DOM 中。这就是 JSX 支持 HTML 标记的原因,即开箱即用。在本节中,我们将看一些呈现一些可用 HTML 标记的代码。然后,我们将介绍在使用 HTML 标记时 React 项目中通常遵循的一些约定。

内置 HTML 标签

在呈现 JSX 时,元素标记引用的是 React 组件。因为必须为 HTML 元素创建组件会很乏味,所以 React 附带了 HTML 组件。我们可以在 JSX 中呈现任何 HTML 标记,输出结果将与我们预期的一样。如果您不确定,可以始终运行以下代码以查看 React 包含哪些 HTML 元素标记:

// Prints a list of the global HTML tags 
// that React knows about. 
console.log( 
  'available tags', 
  Object.keys(React.DOM).sort() 
); 

您可以看到,React.DOM具有我们需要的所有内置 HTML 元素,实现为 React 组件。现在,让我们尝试渲染其中一些:

import React from 'react'; 
import { render } from 'react-dom'; 

// React internal defines all the standard HTML tags 
// that we use on a daily basis. Think of them being 
// the same as any other react component. 
render(( 
  <div> 
    <button /> 
    <code /> 
    <input /> 
    <label /> 
    <p /> 
    <pre /> 
    <select /> 
    <table /> 
    <ul /> 
  </div> 
  ), 
  document.getElementById('app') 
); 

对于本例,不要担心渲染输出;这没有道理。我们在这里所做的就是确保我们可以呈现任意的 HTML 标记,并且它们按照预期呈现。

您可能已经注意到周围的<div>标记,将所有其他标记组合在一起作为其子标记。这是因为 React 需要根组件来渲染;不能渲染相邻元素,例如(<p><p><p>)。

HTML 标记约定

在 JSX 标记中呈现 HTML 标记时,我们希望标记名使用小写字母。事实上,将 HTML 标记的名称大写将直接失败。标记名区分大小写,非 HTML 元素大写。通过这种方式,可以很容易地扫描标记并发现内置的 HTML 元素,而不是其他任何东西。

我们还可以传递 HTML 元素的任何标准属性。当我们向他们传递意外信息时,会记录有关未知属性的警告。这里有一个例子来说明这些想法。

import React from 'react'; 
import { render } from 'react-dom'; 

// This renders as expected, except for the "foo" 
// property, since this is not a recognized button 
// property. This will log a warning in the console. 
render(( 
  <button foo="bar"> 
    My Button 
  </button> 
  ), 
  document.getElementById('app') 
); 

// This fails with a "ReferenceError", because 
// tag names are case-sensitive. This goes against 
// the convention of using lower-case for HTML tag names. 
render( 
  <Button />, 
  document.getElementById('app') 
); 

在本书的后面,我们将介绍我们制作的组件的属性验证。这避免了沉默的不当行为,如本例中的foo属性所示。

描述 UI 结构

JSX 是描述复杂 UI 结构的最佳方式。让我们看看一些 JSX 标记,它们声明了比单个段落更复杂的结构:

import React from 'react'; 
import { render } from 'react-dom'; 

// This JSX markup describes some fairly-sophisticated 
// markup. Yet, it's easy to read, because it's XML and 
// XML is good for concisely-expressing hierarchical 
// structure. This is how we want to think of our UI, 
// when it needs to change, not as an individual element 
// or property. 
render(( 
  <section> 
    <header> 
      <h1>A Header</h1> 
    </header> 
    <nav> 
      <a href="item">Nav Item</a> 
    </nav> 
    <main> 
      <p>The main content...</p> 
    </main> 
    <footer> 
      <small>&copy; 2016</small> 
    </footer> 
  </section> 
  ), 
  document.getElementById('app') 
); 

如您所见,此标记中有许多语义元素,用于描述 UI 的结构。关键是这种复杂结构很容易推理,我们不需要考虑渲染它的特定部分。但是在我们开始实现动态 JSX 标记之前,让我们创建一些我们自己的 JSX 组件。以下是渲染内容的外观:

Describing UI structures

创建自己的 JSX 元素

组件是 React 的基本构建块。事实上,组件是 JSX 标记的词汇表。在本节中,我们将了解如何在组件中封装 HTML 标记。我们将构建示例,向您展示如何嵌套自定义 JSX 元素以及如何为组件命名名称空间。

封装 HTML

我们想要创建新的 JSX 元素的原因是为了能够封装更大的结构。这意味着不必键入复杂的标记,我们只需使用自定义标记。React 组件返回替换元素的 JSX。现在让我们看一个例子:

// We also need "Component" so that we can 
// extend it and make a new JSX tag. 
import React, { Component } from 'react'; 
import { render } from 'react-dom'; 

// "MyComponent" extends "Component", which means that 
// we can now use it in JSX markup. 
class MyComponent extends Component { 
  render() { 
    // All components have a "render()" method, which 
    // returns some JSX markup. In this case, "MyComponent" 
    // encapsulates a larger HTML structure. 
    return ( 
      <section> 
        <h1>My Component</h1> 
        <p>Content in my component...</p> 
      </section> 
    ); 
  } 
} 

// Now when we render "<MyComponent>" tags, the encapsulated 
// HTML structure is actually rendered. These are the 
// building blocks of our UI. 
render( 
  <MyComponent />, 
  document.getElementById('app') 
); 

以下是渲染输出的外观:

Encapsulating HTML

这是我们在本书中实现的第一个 React 组件,所以让我们花些时间剖析一下这里发生的事情。我们已经创建了一个名为MyComponent的类,它从 React 扩展了Component类。这就是我们创建新 JSX 元素的方式。正如您在对render()的调用中所看到的,我们正在呈现一个<MyComponent>元素。

该组件封装的 HTML 由render()方法返回。在本例中,当 JSX<MyComponent>react-dom呈现时,它将被<section>元素以及其中的所有元素替换。

在呈现 JSX 时,我们使用的任何自定义元素都必须在同一范围内具有相应的 React 组件。在前面的示例中,类MyComponent与对render()的调用在同一范围内声明,因此一切都按预期进行。通常,我们会导入组件,将它们添加到适当的范围。随着本书的阅读,我们将看到更多这方面的内容。

嵌套元素

使用 JSX 标记对于描述具有父子关系的 UI 结构非常有用。例如,<li>标记仅作为<ul><ol>标记的子级使用。因此,我们可能会用自己的 React 组件创建类似的嵌套结构。为此,您需要使用children属性。让我们看看它是如何工作的;下面是我们要呈现的 JSX 标记:

import React from 'react'; 
import { render } from 'react-dom'; 

// Imports our two components that render children... 
import MySection from './MySection'; 
import MyButton from './MyButton'; 

// Renders the "MySection" element, which has a child 
// component of "MyButton", which in turn has child text. 
render(( 
  <MySection> 
    <MyButton>My Button Text</MyButton> 
  </MySection> 
  ), 
  document.getElementById('app') 
); 

在这里,您可以看到我们正在导入两种我们自己的 React 组件:MySectionMyButton。现在,如果你看一下我们正在渲染的 JSX,你会注意到<MyButton><MySection>的孩子。您还会注意到,MyButton组件接受文本作为其子元素,而不是更多的 JSX 元素。让我们看看这些组件是如何工作的,从MySection开始:

import React, { Component } from 'react'; 

// Renders a "<section>" element. The section has 
// a heading element and this is followed by 
// "this.props.children". 
export default class MySection extends Component { 
  render() { 
    return ( 
      <section> 
        <h2>My Section</h2> 
        {this.props.children} 
      </section> 
    ); 
  } 
} 

该组件呈现一个标准的<section>HTML 元素、一个标题,然后是{this.props.children}。正是这最后一个构造允许组件访问嵌套元素或文本,并对其进行渲染。

上例中使用的两个大括号用于 JavaScript 表达式。我们将在下一节中讨论 JSX 标记中的 JavaScript 表达式语法的更多细节。

现在,让我们看看MyButton组件:

import React, { Component } from 'react'; 

// Renders a "<button>" element, using 
// "this.props.children" as the text. 
export default class MyButton extends Component { 
  render() { 
    return ( 
      <button>{this.props.children}</button> 
    ); 
  } 
} 

该组件使用与MySection完全相同的模式;取{this.props.children}值,并用有意义的标记将其包围。React 为我们处理了很多混乱的细节。在本例中,按钮文本是MyButton的子项,而MyButton又是MySection的子项。但是,按钮文本通过MySection是透明的。换句话说,我们不必在MySection中编写任何代码来确保MyButton得到了文本。很酷吧?以下是渲染输出的外观:

Nested elements

名称空间组件

到目前为止,我们创建的自定义元素使用了简单的名称。有时,我们可能希望为组件提供名称空间。我们将编写<MyNamespace.MyComponent>,而不是在 JSX 标记中编写<MyComponent>。这向任何阅读 JSX 的人表明,MyComponentMyNamespace的一部分。

通常,MyNamespace也将是一个组件。名称空间的想法是让名称空间组件使用名称空间语法呈现其子组件。让我们来看一个例子:

import React from 'react'; 
import { render } from 'react-dom'; 

// We only need to import "MyComponent" since 
// the "First" and "Second" components are part 
// of this "namespace". 
import MyComponent from './MyComponent'; 

// Now we can render "MyComponent" elements, 
// and it's "namespaced" elements as children. 
// We don't actually have to use the namespaced 
// syntax here, we could import the "First" and 
// "Second" components and render them without the 
// "namespace" syntax. It's a matter of readability 
// and personal taste. 
render(( 
  <MyComponent> 
    <MyComponent.First /> 
    <MyComponent.Second /> 
  </MyComponent> 
  ), 
  document.getElementById('app') 
); 

此标记呈现具有两个子元素的<MyComponent>元素。这里需要注意的关键是,我们不是写<First>,而是写<MyComponent.First>。与<MyComponent.Second>相同。我们的想法是要明确地表明FirstSecond属于MyComponent,在标记中。

我个人并不依赖于这些名称空间的组件,因为我更希望通过查看模块顶部的import语句来了解哪些组件正在使用。其他人宁愿导入一个组件并在标记中显式标记关系。没有正确的方法可以做到这一点;这是个人品味的问题。

现在让我们看一下 ToY0T0.模块:

import React, { Component } from 'react'; 

// The "First" component, renders some basic JSX... 
class First extends Component { 
  render() { 
    return ( 
      <p>First...</p> 
    ); 
  } 
} 

// The "Second" component, renders some basic JSX... 
class Second extends Component { 
  render() { 
    return ( 
      <p>Second...</p> 
    ); 
  } 
} 

// The "MyComponent" component renders it's children 
// in a "<section>" element. 
class MyComponent extends Component { 
  render() { 
    return ( 
      <section> 
        {this.props.children} 
      </section> 
    ); 
  } 
} 

// Here is where we "namespace" the "First" and 
// "Second" components, by assigning them to 
// "MyComponent" as class properties. This is how 
// other modules can render them as "<MyComponent.First>" 
// elements. 
MyComponent.First = First; 
MyComponent.Second = Second; 

export default MyComponent; 

// This isn't actually necessary. If we want to be able 
// to use the "First" and "Second" components independent 
// of "MyComponent", we would leave this in. Otherwise, 
// we would only export "MyComponent". 
   export { First, Second }; 

您可以看到这个模块声明了MyComponent以及属于这个名称空间的其他组件(FirstSecond。其思想是将组件作为类属性分配给名称空间组件(MyComponent。在本模块中,我们可以改变很多事情。例如,我们不必直接出口FirstSecond,因为它们可以通过MyComponent访问。我们也不需要在同一个模块中定义所有内容;我们可以导入FirstSecond并将它们指定为类属性。使用名称空间是完全可选的,如果您使用名称空间,则应该始终如一地使用它们。

使用 JavaScript 表达式

正如我们在上一节中所看到的,JSX 具有特殊的语法,允许我们嵌入 JavaScript 表达式。无论何时呈现 JSX 内容,都会计算标记中的表达式。这是 JSX 内容的动态方面,在本节中,您将学习如何使用表达式设置属性值和元素文本内容。您还将学习如何将数据集合映射到 JSX 元素。

动态属性值和文本

一些 HTML 属性或文本值是静态的,这意味着它们不会随着 JSX 的重新呈现而改变。其他值(属性值或文本值)基于应用中其他位置的数据。记住,React 只是视图层。让我们看一个示例,以便您能够了解 JSX 标记中的 JavaScript 表达式语法:

import React from 'react'; 
import { render } from 'react-dom'; 

// These constants are passed into the JSX 
// markup using the JavaScript expression syntax. 
const enabled = false; 
const text = 'A Button'; 
const placeholder = 'input value...'; 
const size = 50; 

// We're rendering a "<button>" and an "<input>" 
// element, both of which use the "{}" JavaScript 
// expression syntax to fill in property, and text 
// values. 
render(( 
  <section> 
    <button disabled={!enabled}>{text}</button> 
    <input placeholder={placeholder} size={size} /> 
  </section> 
  ), 
  document.getElementById('app') 
); 

任何有效的 JavaScript 表达式都可以放在大括号中:{}。对于属性和文本,这通常是变量名或对象属性。注意在这个例子中,!enabled表达式计算一个布尔值。以下是渲染输出的外观:

Dynamic property values and text

如果您遵循可下载的配套代码(我强烈建议您这样做),请尝试使用这些值,并查看呈现的 HTML 是如何变化的。

将集合映射到元素

有时我们需要编写 JavaScript 表达式来更改标记的结构。在上一节中,我们研究了如何使用 JavaScript 表达式语法动态更改 JSX 元素的属性值。当我们需要基于 JavaScript 集合添加或删除元素时,该怎么办?

在整本书中,当我提到 JavaScript集合时,我指的是普通对象和数组。或者更一般地说,任何可以接受的东西。

动态控制 JSX 元素的最佳方法是从集合映射它们。让我们看一个如何实现这一点的示例:

import React from 'react'; 
import { render } from 'react-dom'; 

// An array that we want to render as a list... 
const array = [ 
  'First', 
  'Second', 
  'Third', 
]; 

// An object that we want to render as a list... 
const object = { 
  first: 1, 
  second: 2, 
  third: 3, 
}; 

render(( 
  <section> 
    <h1>Array</h1> 

    { /* Maps "array" to an array of "<li>"s. 
         Note the "key" property on "<li>". 
         This is necessary for performance reasons, 
         and React will warn us if it's missing. */ } 
    <ul> 
      {array.map(i => ( 
        <li key={i}>{i}</li> 
      ))} 
    </ul> 
    <h1>Object</h1> 

    { /* Maps "object" to an array of "<li>"s. 
         Note that we have to use "Object.keys()" 
         before calling "map()" and that we have 
         to lookup the value using the key "i". */ } 
    <ul> 
      {Object.keys(object).map(i => ( 
        <li key={i}> 
          <strong>{i}: </strong>{object[i]} 
        </li> 
      ))} 
    </ul> 
  </section> 
  ), 
  document.getElementById('app') 
); 

第一个集合是一个名为array的数组,由字符串项填充。转到 JSX 标记,您可以看到我们正在调用array.map(),它将返回一个新数组。映射函数实际上返回了一个 JSX 元素(<li>),这意味着数组中的每个项现在都表示在标记中。

计算此表达式的结果是一个数组。别担心;JSX 知道如何呈现元素数组。

对象集合使用相同的技术,只是我们必须调用Object.keys()然后映射此数组。将集合映射到页面上的 JSX 元素的好处在于,我们可以基于集合数据驱动 React 组件的结构。这意味着我们不必依赖命令逻辑来控制 UI。

以下是渲染输出的外观:

Mapping collections to elements

总结

在本章中,您学习了关于 JSX 的基础知识,包括它的声明式结构以及为什么这是一件好事。然后,编写了一些代码来呈现一些基本的 HTML,并学习了如何使用 JSX 描述复杂的结构。

接下来,您将花一些时间学习如何通过实现自己的 React 组件(UI 的基本构建块)来扩展 JSX 标记的词汇表。最后,您学习了如何将动态内容引入 JSX 元素属性,以及如何将 JavaScript 集合映射到 JSX 元素,从而消除了控制 UI 显示的命令式逻辑的需要。

现在,您已经了解了通过在 JavaScript 模块中嵌入声明性 XML 来呈现 UI 的感觉,是时候进入下一章了,在下一章中,我们将更深入地了解组件属性和状态。