React JSX 详解

很多人说 React 的学习曲线高,我觉得有一部分就是这个 JSX 引起的。这篇文章讲 JSX 是个什么东西以及它设计得有多丑陋。

JSX 是什么?

JSX is a JavaScript syntax extension that looks similar to XML. You can use a simple JSX syntactic transform with React. - JSX In Depth

即是说,它是一个 JS 的扩展语法,它本质是还是 JS 只是长得像 XML:

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});

你可以用 Babel 把它编译成纯粹的 JS:

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

  render: function render() {
    return React.createElement(
      "div",
      { className: "commentBox" },
      "Hello, world! I am a CommentBox."
    );
  }
});

可以观察到,JSX 部分被替换成 React.createElement。Facebook 在 JSX 的 规范 中表示,JSX 只是一种语言扩展,并无意变成 JS 的合法语法加入 ECMAScript 规范中,只适用于各种 transpiler 做转换;而且它也不是 XML/HTML,不会遵循 XML/HTML 的规范,只是让大家感觉熟悉而已。

核心细节

不同类型的组件

JSX 中分两种类型:

  • 一种类似 HTML 结点的结构,tag 用小写:<div>
  • 自定义组件类型,tag 首字母大写:<MyComponent>

注意,第一种结构跟浏览器 DOM 并不一样,是 React 自己维护的一种 DOM。

在类 HTML 组件中:

  1. 如果是 HTML 规范中定义的 tag 名(比如 div),那它的属性只能是 HTML 规范中存在的属性,否则会被忽略
    • 如果你想要带自定义数据,需要使用 data-* 形式
    • 因为 class, for 是 JS 定义的关键字,因此不能被当作属性名,需要相应替换成 className, htmlFor(不太理解为什么,看上文转换后的 JS 代码,className 只是作为 Object 的属性名,除了一些老浏览器不支持外应该没别的问题;而且 custom element 居然可以直接用 class / for?!)
  2. 如果是 custom element,则任意的属性名都可以使用
    • 可以直接使用 class, for

在自定义组件中,属性名需要用 camelCase,这是为了跟 JS 的代码风格一致。

JavaScript Expression

在 JSX 的 tag 属性值中,用 {} 包裹起来的代码就是 JavaScript Expression:

var person = <Person name={window.isLoggedIn ? window.name : ''} />;

除了可以写正常的 JS 代码,还可以嵌入其他的 component class:

var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;

用 Spread 语义 (...) 传递参数

React 借鉴了 ES6 新加的 Spread Syntax 表示属性值的传递(准确地说 ES6 只支持了 Array 部分,Object 部分还在提议中):

function FancyCheckbox(props) {
  var { checked, ...other } = props;
  var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
  // `other` contains { onClick: console.log } but not the checked property
  return (
    <div {...other} className={fancyClass} />
  );
}
ReactDOM.render(
  <FancyCheckbox checked={true} onClick={console.log.bind(console)}>
    Hello world!
  </FancyCheckbox>,
  document.getElementById('example')
);

它的功能类似 Python 的 unpacking,但是语法和功能上不一样。官方文档 JSX Spread Attributes 有一些简单的描述。

官网的文档真垃圾

React 的官网整体感觉太一般,各种概率解释得含糊不清,特别是 JSX。

文档拿这个长得像 XML 的东西,硬是强调它就是 JavaScript,可是看起来这玩意就是个嵌入了 JS 逻辑的模板。

Since JSX is JavaScript, identifiers such as class and for are discouraged as XML attribute names. Instead, React DOM components expect DOM property names like className and htmlFor, respectively.

这段话又说得不明不白的,在这里说是不鼓励作为 XML 属性名,在其他的文档又说是因为 class 是 JS 保留字,可是又不说清楚是保留字会有什么影响。然后在 规范 里又说 JSX 不是 XML/HTML,只是长得像而已;那你在 class 的问题上说 XML 干嘛?另外明明就只有这两个保留字需要替换,可是文档又写得好像有很多类似的情况一样,可是你又没法在文档找到所有情况。再然后替换的规则也是奇葩,className => class 还能理解,htmlFor 这个名字就太丑了,敢不敢全部统一一下加个前缀试试?

JSX 在很多地方使用时都需要用括号包起来。我估计 Babel 也是通过 ( 后接 < 来判断是不是 JSX。但是文档一点都没提到括号这个点。不知道这种语法会不会给后面 JS 的演进带来问题,这种转义语言多了后,把各种 JS 未使用的语法形式都给占了那就坑了。

JSX 的文档散落在多处(看下面参考文档),东一点西一点拼起来才能看到全貌,写得不系统又有一堆潜规则要记,感觉 Facebook 真心不是个技术很强的公司。要不是 React 先发制人火起来,Angular 2 应该也是个不错的选择。后面看看 Angular 2 是否值得投入。

参考文档:

  1. JSX in Depth
  2. JSX Gotchas
  3. DOM Differences
  4. Transferring Props
  5. JSX Spread Attributes
  6. Draft: JSX Specification
评论系统被墙,可以发邮件到 onlyice0328@gmail.com 与我交流。

Live with your brain

> NOTE: 这篇文章同步发在 [知乎](https://zhuanlan.zhihu.com/p/24147306) 上感觉到人类的思维不是一个容易持久化的东西。比如某段时间内你可能持续地思考了一个问题,有了一些看法和结论,但是如果你没有用一些方式记下来这些想法,很可能...… Continue reading

从五月天 MV 说起

Published on August 26, 2016

「奇特的一生」读后感

Published on June 25, 2016