JSX

本质上来讲,JSX 只是为 Nerv.createElement(component, props, …children) 方法提供的语法糖。比如下面的代码:

  1. <MyButton color="blue" shadowSize={2}>
  2. Click Me
  3. </MyButton>

编译为:

  1. Nerv.createElement(
  2. MyButton,
  3. {color: 'blue', shadowSize: 2},
  4. 'Click Me'
  5. )

如果没有子代,你还可以使用自闭合标签,比如:

  1. <div className="sidebar" />

编译为:

  1. Nerv.createElement(
  2. 'div',
  3. {className: 'sidebar'},
  4. null
  5. )

指定 Nerv 元素类型

JSX 的标签名决定了 Nerv 元素的类型。

大写开头的 JSX 标签表示一个 Nerv 组件。这些标签将会被编译为同名变量并被引用,所以如果你使用了 <Foo /> 表达式,则必须在作用域中先声明 Foo 变量。

点表示法

你还可以使用 JSX 中的点表示法来引用 Nerv 组件。你可以方便地从一个模块中导出许多 Nerv 组件。例如,有一个名为 MyComponents.DataPicker 的组件,你可以直接在 JSX 中使用它:

  1. import Nerv from 'Nerv';
  2. const MyComponents = {
  3. DatePicker: function DatePicker(props) {
  4. return <div>Imagine a {props.color} datepicker here.</div>;
  5. }
  6. }
  7. function BlueDatePicker() {
  8. return <MyComponents.DatePicker color="blue" />;
  9. }

首字母大写

当元素类型以小写字母开头时,它表示一个内置的组件,如 <div><span>,并将字符串 'div' 或 'span' 传 递给 Nerv.createElement。 以大写字母开头的类型,如 <Foo /> 编译为 Nerv.createElement(Foo),并它正对应于你在 JavaScript 文件中定义或导入的组件。

我们建议用大写开头命名组件。如果你的组件以小写字母开头,请在 JSX 中使用之前其赋值给大写开头的变量。

例如,下面的代码将无法按预期运行:

  1. import Nerv from 'Nerv';
  2. // 错误!组件名应该首字母大写:
  3. function hello(props) {
  4. // 正确!div 是有效的 HTML 标签:
  5. return <div>Hello {props.toWhat}</div>;
  6. }
  7. function HelloWorld() {
  8. // 错误!Nerv 会将小写开头的标签名认为是 HTML 原生标签:
  9. return <hello toWhat="World" />;
  10. }

为了解决这个问题,我们将 hello 重命名为 Hello,然后使用 <Hello /> 引用:

  1. import Nerv from 'Nerv';
  2. // 正确!组件名应该首字母大写:
  3. function Hello(props) {
  4. // 正确!div 是有效的 HTML 标签:
  5. return <div>Hello {props.toWhat}</div>;
  6. }
  7. function HelloWorld() {
  8. // 正确!Nerv 能够将大写开头的标签名认为是 Nerv 组件。
  9. return <Hello toWhat="World" />;
  10. }

在运行时选择类型

你不能使用表达式来作为 Nerv 元素的标签。如果你的确想通过表达式来确定 Nerv 元素的类型,请先将其赋值给大写开头的变量。这种情况一般会在你想通过属性值条件渲染组件时出现:

  1. import Nerv from 'Nerv';
  2. import { PhotoStory, VideoStory } from './stories';
  3. const components = {
  4. photo: PhotoStory,
  5. video: VideoStory
  6. };
  7. function Story(props) {
  8. // 错误!JSX 标签名不能为一个表达式。
  9. return <components[props.storyType] story={props.story} />;
  10. }

要解决这个问题,我们需要先将类型赋值给大写开头的变量。

  1. import Nerv from 'Nerv';
  2. import { PhotoStory, VideoStory } from './stories';
  3. const components = {
  4. photo: PhotoStory,
  5. video: VideoStory
  6. };
  7. function Story(props) {
  8. // 正确!JSX 标签名可以为大写开头的变量。
  9. const SpecificStory = components[props.storyType];
  10. return <SpecificStory story={props.story} />;
  11. }

属性

在 JSX 中有几种不同的方式来指定属性。

使用 JavaScript 表达式

你可以传递任何 {} 包裹的 JavaScript 表达式作为一个属性值。例如,在这个 JSX 中:

  1. <MyComponent foo={1 + 2 + 3 + 4} />

对于 MyComponent来说, props.foo 的值为 10,这是 1 + 2 + 3 + 4 表达式计算得出的。

if 语句和 for 循环在 JavaScript 中不是表达式,因此它们不能直接在 JSX 中使用,所以你可以将它们放在周围的代码中。

  1. function NumberDescriber(props) {
  2. let description;
  3. if (props.number % 2 == 0) {
  4. description = <strong>even</strong>;
  5. } else {
  6. description = <i>odd</i>;
  7. }
  8. return <div>{props.number} is an {description} number</div>;
  9. }

你可以在相关部分中了解有关 条件渲染循环 的更多信息。

字符串常量

你可以将字符串常量作为属性值传递。下面这两个 JSX 表达式是等价的:

  1. <MyComponent message="hello world" />
  2. <MyComponent message={'hello world'} />

当你传递一个字符串常量时,它不会对其进行 HTML 转义,所以下面两个 JSX 表达式是相同的:

  1. <MyComponent message="&lt;3" />
  2. <MyComponent message={'<3'} />

这种行为通常是无意义的,提到它只是为了完整性。

默认为 True

如果你没有给属性传值,它默认为 true。因此下面两个 JSX 是等价的:

  1. <MyTextBox autocomplete />
  2. <MyTextBox autocomplete={true} />

一般情况下,我们不建议这样使用,因为它会与 ES6 对象简洁表示法 混淆。比如 {foo}{foo: foo} 的简写,而不是 {foo: true}。这里能这样用,是因为它符合 HTML 的做法。

扩展属性

如果你已经有了个 props 对象,并且想在 JSX 中传递它,你可以使用 作为扩展操作符来传递整个属性对象。下面两个组件是等效的:

  1. function App1() {
  2. return <Greeting firstName="Ben" lastName="Hector" />;
  3. }
  4. function App2() {
  5. const props = {firstName: 'Ben', lastName: 'Hector'};
  6. return <Greeting {...props} />;
  7. }

当你构建通用容器时,扩展属性会非常有用。然而,这样做也可能让很多不相关的属性,传递到不需要它们的组件中使代码变得混乱。我们建议你谨慎使用此语法。

子代

在包含开始和结束标签的 JSX 表达式中,标记之间的内容作为特殊的参数传递:props.children。有几种不同的方法来传递子代:

字符串常量

你可以在开始和结束标签之间放入一个字符串,则 props.children 就是那个字符串。这对于许多内置 HTML 元素很有用。例如:

  1. <MyComponent>Hello world!</MyComponent>

这是有效的 JSX,并且 MyComponentprops.children 值将会直接是 "hello world!"。因为 HTML 未转义,所以你可以像写 HTML 一样写 JSX:

  1. <div>This is valid HTML &amp; JSX at the same time.</div>

JSX 会移除行空行和开始和结尾处的空格。标签邻近的新行也会被移除,字符串常量内部的换行会被压缩成一个空格,所以下面这些都等价:

  1. <div>Hello World</div>
  2. <div>
  3. Hello World
  4. </div>
  5. <div>
  6. Hello
  7. World
  8. </div>
  9. <div>
  10. Hello World
  11. </div>

JSX

你可以通过子代嵌入更多的 JSX 元素,这对于嵌套显示组件非常有用:

  1. <MyContainer>
  2. <MyFirstComponent />
  3. <MySecondComponent />
  4. </MyContainer>

你可以混合不同类型的子元素,同时用字符串常量和 JSX 子元素,这是 JSX 类似 HTML 的另一种形式,这在 JSX 和 HTML 中都是有效的:

  1. <div>
  2. Here is a list:
  3. <ul>
  4. <li>Item 1</li>
  5. <li>Item 2</li>
  6. </ul>
  7. </div>

一个 Nerv 组件不能返回多个 Nerv 元素,但是单个 JSX 表达式可以有多个子元素,因此,如果你希望一个组件渲染多个元素,你可以用 <div> 将其包起来。

Nerv 组件也可以通过数组的形式返回多个元素:

  1. render() {
  2. // 不需要使用额外的元素包裹数组中的元素
  3. return [
  4. // 不要忘记 key :)
  5. <li key="A">First item</li>,
  6. <li key="B">Second item</li>,
  7. <li key="C">Third item</li>,
  8. ];
  9. }

JavsScript 表达式

你可以将任何 {} 包裹的 JavaScript 表达式作为子代传递。例如,下面这些表达式是等价的:

  1. <MyComponent>foo</MyComponent>
  2. <MyComponent>{'foo'}</MyComponent>

这对于渲染任意长度的 JSX 表达式的列表很有用。例如,下面将会渲染一个 HTML 列表:

  1. function Item(props) {
  2. return <li>{props.message}</li>;
  3. }
  4. function TodoList() {
  5. const todos = ['finish doc', 'submit pr', 'nag dan to review'];
  6. return (
  7. <ul>
  8. {todos.map((message) => <Item key={message} message={message} />)}
  9. </ul>
  10. );
  11. }

JavsScript 表达式可以与其他类型的子代混合使用。这通常对于字符串模板非常有用:

  1. function Hello(props) {
  2. return <div>Hello {props.addressee}!</div>;
  3. }

函数

通常情况下,插入 JSX 中的 JavsScript 表达式将被认作字符串、Nerv 元素或这些内容的列表。然而,props.children 可以像其它属性一样传递任何数据,而不仅仅是 Nerv 元素。例如,如果你使用自定义组件,则可以将调用 props.children 来获得传递的子代:

  1. // Calls the children callback numTimes to produce a repeated component
  2. function Repeat(props) {
  3. let items = [];
  4. for (let i = 0; i < props.numTimes; i++) {
  5. items.push(props.children(i));
  6. }
  7. return <div>{items}</div>;
  8. }
  9. function ListOfTenThings() {
  10. return (
  11. <Repeat numTimes={10}>
  12. {(index) => <div key={index}>This is item {index} in the list</div>}
  13. </Repeat>
  14. );
  15. }

传递给自定义组件的子代可以是任何元素,只要该组件在 Nerv 渲染前将其转换成 Nerv 能够理解的东西。这个用法并不常见,但当你想扩展 JSX 时可以使用。

布尔值、Null 和 Undefined 被忽略

falsenullundefinedtrue 都是有效的子代,但它们不会直接被渲染。下面的表达式是等价的:

  1. <div />
  2. <div></div>
  3. <div>{false}</div>
  4. <div>{null}</div>
  5. <div>{undefined}</div>
  6. <div>{true}</div>

这在根据条件来确定是否渲染Nerv元素时非常有用。以下的JSX只会在showHeadertrue时渲染<Header />组件。

  1. <div>
  2. {showHeader && <Header />}
  3. <Content />
  4. </div>

值得注意的是,JavaScript 中的一些 "falsy" 值(比如数字0),它们依然会被渲染。例如,下面的代码不会像你预期的那样运行,因为当 props.message 为空数组时,它会打印0:

  1. <div>
  2. {props.messages.length &&
  3. <MessageList messages={props.messages} />
  4. }
  5. </div>

要解决这个问题,请确保 && 前面的表达式始终为布尔值:

  1. <div>
  2. {props.messages.length > 0 &&
  3. <MessageList messages={props.messages} />
  4. }
  5. </div>

相反,如果你想让类似 falsetruenullundefined 出现在输出中,你必须先把它转换成字符串 :

  1. <div>
  2. My JavaScript variable is {String(myVariable)}.
  3. </div>

原文: https://nervjs.github.io/docs/guides/jsx.html