w3ctech

React 初学者教程 7:深入 JSX

你可能已经注意到,在前面的教程中我们用到很多 JSX。但是我们确实还没有认真看看 JSX 到底是什么。它实际上是如何工作的呢?为什么我们不就把它叫 HTML 呢?它到底有哪些怪癖?在本教程中,我们将回答所有这些问题。我们将做一些严肃的回溯(以及一些前溯),来深入看看为了探险,我们需要知道有关的 JSX 的什么。

一、JSX 背后发生了什么?

我们已经掩盖的最大的事情之一,是试图推断在编写完 JSX 后,JSX 发生了什么。它是如何最后在浏览器中变成 HTML 的?看看如下示例,在这里我们定义了一个 Card 组件:

var Card = React.createClass({
  render: function() {
      var cardStyle = {
        height: 200,
        width: 150,
        padding: 0,
        backgroundColor: "#FFF",
        WebkitFilter: "drop-shadow(0px 0px 5px #666)",
        filter: "drop-shadow(0px 0px 5px #666)"
      };

      return (
        <div style={cardStyle}>
          <Square color={this.props.color}/>
          <Label color={this.props.color}/>
        </div>
      );
    }
});

这里面 JSX 是如下的四行:

<div style={cardStyle}>
  <Square color={this.props.color}/>
  <Label color={this.props.color}/>
</div>

要记住的是浏览器是不知道如何处理 JSX的,所以我们得用 Babel 把 JSX 转换成浏览器可以理解的 JavaScript。

也就是说,我们写 JSX 只是为人类眼球来写的。当这个 JSX 到了浏览器中时,最终会被转换为纯 JavaScript:

return React.createElement(
  "div",
  { style: cardStyle },
  React.createElement(Square, { color: this.props.color }),
  React.createElement(Label, { color: this.props.color })
);

所有这些整齐嵌套的像 HTML 的元素、它们的属性、它们的子元素都会变成一系列带有默认初始化值的 createElement 调用。这里是 Card 组件变成 JavaScript 时的完整代码:

var Card = React.createClass({
  displayName: "Card",

  render: function render() {
    var cardStyle = {
      height: 200,
      width: 150,
      padding: 0,
      backgroundColor: "#FFF",
      WebkitFilter: "drop-shadow(0px 0px 5px #666)",
      filter: "drop-shadow(0px 0px 5px #666)"
    };

    return React.createElement(
      "div",
      { style: cardStyle },
      React.createElement(Square, { color: this.props.color }),
      React.createElement(Label, { color: this.props.color })
    );
  }
});

可以看到这里完全没有 JSX 的痕迹!在我们写的代码和浏览器看到的代码之间的所有变化,是我们在《创建第一个 React 应用》教程中讨论的转译步骤的一部分。这种转译完全发生在幕后,由 Babel 完全在浏览器中执行 JSX -> JS 的转换。我们最终会考虑用 Babel 作为构建环境的一部分,这样我们将只生成一个转换了的 JS 文件。

是的,到了这里你已经有了一个所有 JSX 发生了什么的答案:被转变成 JavaScript。

二、要记住的 JSX 怪癖

我们已经用了 JSX,可能你已经注意到我们遇到一些能做什么和不能做什么的随意的规则和异常。在本节,我们把所有这些怪癖放在一起,甚至会遇到一些全新的。

1. 只能返回一个根节点

这可能是我们遇到的第一个怪癖。在 JSX 中,return 或者 render 的东西不能由多个根元素组成:

ReactDOM.render(
  <Letter>B</Letter>
  <Letter>E</Letter>
  <Letter>I</Letter>
  <Letter>O</Letter>
  <Letter>U</Letter>,
  document.querySelector("#container")
);

如果你想像这样做点什么,那么你需要先将所有元素用一个父元素包起来:

ReactDOM.render(
  <div>
    <Letter>A</Letter>
    <Letter>E</Letter>
    <Letter>I</Letter>
    <Letter>O</Letter>
    <Letter>U</Letter>
  </div>,
  document.querySelector("#container")
);

之前我们看到这个时,这看起来像一个古怪的需求,但是你可以把这事怪到 createElement 头上去。当使用 render 和 return 函数时,最终返回的是单个 createElement 调用(这个 createElement 可能会有很多嵌套的 createElement 调用)。如下是上面的 JSX 转换为 JavaScript 后的代码:

ReactDOM.render(React.createElement(
  "div",
  null,
  React.createElement(
    Letter,
    null,
    "A"
  ),
  React.createElement(
    Letter,
    null,
    "E"
  ),
  React.createElement(
    Letter,
    null,
    "I"
  ),
  React.createElement(
    Letter,
    null,
    "O"
  ),
  React.createElement(
    Letter,
    null,
    "U"
  )
), document.querySelector("#container"));

如果是有多个根元素,就会破坏函数返回值以及 createElement 的机制,所以这就是为什么只能指定一个返回一个根元素的原因。

2. 不能指定 inline CSS

在《React 中的样式》教程中,我们已经看到,JSX 中的 style 属性与 HTML 中的 style 属性起的作用是不同的。在 HTML 中,你可以直接在 HTML 元素的 style 属性上设定 CSS 属性:

<div style="font-family:Arial;font-size:24px">
    <p>Blah!</p>
</div>

在 JSX 中,style 属性不能包含 CSS,而是引用一个包含样式信息的对象:

var Letter = React.createClass({
  render: function() {
      var letterStyle = {
        padding: 10,
        margin: 10,
        backgroundColor: this.props.bgcolor,
        color: "#333",
        display: "inline-block",
        fontFamily: "monospace",
        fontSize: "32",
        textAlign: "center"
      };

      return (
        <div style={letterStyle}>
          {this.props.children}
        </div>
      );
    }
});

可以看到我们有一个 letterStyle 对象,该对象包含所有的 CSS 属性(按驼峰命名法则命名的 JavaScript 形式)及其值。该对象就是我们指定给 style 属性的对象。

3. 保留关键字和 className

JavaScript 有很多不能用作标识符(即变量和属性名)的关键字和值。这些关键字包括:break, case, class, catch, const, continue, debugger, default, delete, do, else, export, extends, finally, for, function, if, import, in, instanceof, new, return, super, switch, this, throw, try, typeof, var, void, while, with, yield。

当我们在编写 JSX 时,也必须注意不要用这些关键字作为标识符。这有点困难,某些很流行的关键字,比如 class,在 HTML 中很常用,但是也在 JavaScript 的保留关键字列表中。

我们来看如下代码:

ReactDOM.render(
  <div class="slideIn">
    <p class="emphasis">Gabagool!</p>
    <Label/>
  </div>,
  document.querySelector("#container")
);

无视 JavaScript 的保留关键字 class(就像上面代码中一样)是不行的。你需要做的是用 DOM API 版本的 class 属性 className 来代替:

ReactDOM.render(
  <div className="slideIn">
    <p className="emphasis">Gabagool!</p>
    <Label/>
  </div>,
  document.querySelector("#container")
);

你可以在这里看到支持的属性的完整列表,并且可以看到所有属性都是遵循驼峰命名法则。这个细节是重要的,因为用小写版本的属性行不通。如果你粘贴了一大块想让 JSX 处理的 HTML 代码,必须确保回到你粘贴的 HTML,做出这些小的调整,以将其变成有效的 JSX。

这带来另一个点。因为这些与 HTML 行为的细小偏差,我们趋向于说 JSX 支持 HTML 类似的语法,而不是就说 JSX 支持 HTML。这是深思熟虑的 React 主义。对JSX 和 HTML 之间的关系的最清晰的回答来自于 React 团队成员,Ben Alpert,他在 Quora 中这样回答:

…我们的想法是 JSX 的主要优点是 匹配关闭标记的对称性让代码更容易读懂,而不是直接与 HTML 或者 XML 相似。直接复制/粘贴 HTML 很方便,但是其它 It's convenient to copy/paste HTML directly, but other minor differences (in self-closing tags, for example) make this a losing battle and we have a HTML to JSX converter to help you anyway. 最后,要将 HTML 翻译为通顺的 React 代码,相当大的工作量通常是将标记打碎为有意义的组件,所以将 class 变为 className 只是工作量中的一个小部分。

4. 注释

就像在 HTML、CSS 和 JavaScript 中写注释是一个好主意一样,在 JSX 内提供注释也是个好主意。在 JSX 中指定注释与在 JavaScript 写注释很相似,只有一个例外。如果你指定一个注释作为一个标记的子节点,那么你必须用 { 和 } 把注释包起来,以确保它被解析为一个表达式:

ReactDOM.render(
  <div class="slideIn">
    <p class="emphasis">Gabagool!</p>
    {/* I am a child comment */}
    <Label/>
  </div>,
  document.querySelector("#container")
);

本例中的注释是 div 元素的一个子元素。如果完全在一个标记内部指定注释,那么就不需要用大括号把单行或者多行注释括起来:

ReactDOM.render(
  <div class="slideIn">
    <p class="emphasis">Gabagool!</p>
    <Label
      /* This comment
         goes across
         multiple lines */
         className="colorCard" // end of line
    />
  </div>,
  document.querySelector("#container")
);

5. 大小写、HTML 元素和组件

大小写很重要。要表示 HTML 元素,必须确保 HTML 标记是小写字母:

ReactDOM.render(
  <div>
    <section>
      <p>Something goes here!</p>
    </section>
  </div>,
  document.querySelector("#container")
);

当表示组件时,组件的名称必须是大写:

ReactDOM.render(
  <div>
    <MyCustomComponent/>
  </div>,
  document.querySelector("#container")
);

如果大小写出错,React 将不会正确渲染内容。当代码运行不正确时,大小写问题可能是你想到的最后的事情,所以记住这个小技巧。

6. JSX 可以出现在任何地方

在很多情况下,JSX 并不是像我们前面看到的示例一样,整齐地排列在一个 render 或者 return 函数内。看看如下示例:

var swatchComponent = <Swatch color="#2F004F"></Swatch>;

ReactDOM.render(
  <div>
    {swatchComponent}
  </div>,
  document.querySelector("#container")
);

我们有一个 swatchComponent 变量,该变量被初始化为一行 JSX。当 swatchComponent 变量放在 render 函数内时,Swatch 组件就被初始化。这一切都是有效的,并且在将来当我们学习如何用 JavaScript 生成和操作 JSX 时,我们将会做更多这种事情。

总结

通过本教程,我们最终将前面教程引入的各种 JSX 信息综合到一个地方。要记住的最重要的事情是 JSX 不是 HTML。

在很多常见的场景下, JSX 看起来像 HTML,行为也像 HTML。但是,它最终是被设计为翻译成 JavaScript。这意味着我们可以用普通 HTML 不能想像的方式干活。能够计算表达式或者编程操作整个 JSX 块只是开端。在后面的教程中,我们将更深入探讨这种 JavaScript 和 JSX 的交叉。

w3ctech微信

扫码关注w3ctech微信公众号

共收到0条回复