什么是JSX?

JSX is an embeddable XML-like syntax extension to JavaScript without any defined semantics.

JSX是一种类XML语言,是JavaScript的语法扩展,全称是JavaScript XMLJSX并不是由引擎或浏览器来实现,也并不打算并入到ECMAScript标准规范中,它旨在被各种预处理器转换成标准的ECMAScript语法。正如前面所说,JSX就像XML一样,它定义有自己的标签名、属性、子元素,需要注意的是,如果属性的值在大括号内,会当作JavaScript表达式处理,如果在引号内,则会当作字符串类型(String)处理。
首先,看一个简单的示例,使用JSX来表示一个UI下拉选项框组件:

1
2
3
4
5
6
7
8
9
10
11
const dropdown =
<Dropdown>
A dropdown list
<Menu>
<MenuItem>Do Something</MenuItem>
<MenuItem>Do Something Fun!</MenuItem>
<MenuItem>Do Something Else</MenuItem>
</Menu>
</Dropdown>;
render(dropdown);

为什么要学习JSX?

由于最近在学习 React,而官方是推荐使用JSX来编写其组件,从技术角度来说,React可以不使用JSX来编写组件,但是使用JSX可以让代码可读性更高、语义更清晰、对React元素进行抽象。基于此,结合对React的学习需求,还是很有必要先学习并掌握JSX的使用,并且学习成本也相对较低,只要了解其语法规则就可以使用了。

JSX的语法规范

JSX标签

  • 自闭合标签
    如果标签没有子元素,可以使用自闭合标签:< JSXElementName JSXAttributesopt / >

    1
    2
    3
    <div className="sidebar" />
    <MyCounter color="blue" count='{3 + 5}' />
    <Scoreboard className="results" scores={gameScores} />
  • 开/闭标签
    也可以写成开/闭标签的形式:< JSXElementName JSXAttributesopt > </ JSXElementName >

    1
    2
    3
    const element1 = <h1 className="red">Hello, world!</h1>;
    const element2 = <img src={user.avatarUrl}></img>;
    const element3 = <div tabIndex="0">{user.title}</div>;

    如果属性的值在引号内,会当作字符串类型(String)处理,如果属性值是在大括号内,则会当作JavaScript表达式处理。

JSX元素名

JSX的元素名可是一个标识符、命名空间和成员表达式:

1
2
3
4
// JSXIdentifier
<FormInput></FormInput>
//JSXIdentifier.JSXIdentifier
<MyComponents.DatePicker color="blue" />

JSX属性

JSX属性有一般属性、展开属性:JSXAttribute JSXAttributesopt{ … AssignmentExpression }
属性的值可以用双引号("string")、单引号('string')和大括号({expression}),需要注意的是,如果属性值使用了单/双引号,该字符串值内部就不能再出现相对应的引号了。

JSX子元素

  • String字面值(String Literals)
    可以直接写字符串类型或HTML格式:

    1
    2
    <MyComponent>Hello world!</MyComponent>
    <div>This is valid HTML &amp; JSX at the same time.</div>
  • JSX子组件

    1
    2
    3
    4
    <MyContainer>
    <MyFirstComponent />
    <MySecondComponent />
    </MyContainer>
  • JavaScript表达式
    比如稍微复杂一点的表达式,包括一些取值、方法调用和类型转换的场景:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <ul>
    {todos.map((message) => <Item key={message} message={message} />)}
    </ul>
    <Repeat numTimes={10}>
    {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
    <div>
    Hello {props.addressee}!
    My JavaScript variable is {String(myVariable)}.
    </div>

在React中使用JSX

JSX命名规则

在使用JSX编写React时,针对HTML标签,保留原来的关键字和命名规则,即全部字母小写,而对于自定义的组件(Component),需要遵循首字母大写的约定。由于JSX相对于HTML更接近于JavaScript,对于所有的DOM属性及事件处理器,都应该使用camelCased(骆峰命名)与标准的JavaScript风格保持一致,如:

1
2
3
4
// HTML tag
<button onClick={() => props.onClick()}>Click {props.toWhat}</button>
// React Component
<Hello toWhat="Me" />

因为classforJavaScript的保留字,内建 DOM nodesJSX元素应该分别使用属性名classNamehtmlFor,另外,如tabindex也应写成tabIndex

1
<div className="foo" />

而自定义的JSX元素可以直接使用classfor

1
<my-tag class="foo" />

JSX命名空间组件

如果你正在构建一个有很多子组件的组件,比如表单,你也许会最终得到许多的变量声明,比如以下不太友好的代码块。

1
2
3
4
5
6
7
8
9
10
11
12
13
var Form = MyFormComponent;
var FormRow = Form.Row;
var FormLabel = Form.Label;
var FormInput = Form.Input;
var App = (
<Form>
<FormRow>
<FormLabel />
<FormInput />
</FormRow>
</Form>
);

为了使其更简单和容易,命名空间组件令你使用包含其他组件作为属性的单一的组件,通过使用点符号.很方便地在一个组件上表示多个成员组件,使得在语法结构上更加清晰。

1
2
3
4
5
6
7
8
9
10
var Form = MyFormComponent;
var App = (
<Form>
<Form.Row>
<Form.Label />
<Form.Input />
</Form.Row>
</Form>
);

JavaScript表达式

  • 属性表达式
    对于属性表达式,之前已经在语法规范中提到过了,要使用JavaScript表达式作为属性值,只需把这个表达式用一对大括号({})包起来即可。

    1
    var person = <Person name={window.isLoggedIn ? window.name : ''} />;
  • Boolean属性
    对于Boolean属性,省略一个属性的值会导致JSX把它当做true,要传值false必须使用属性表达式,通常会出现于使用HTML表单元素,含有属性如disabled, required, checkedreadOnly

    1
    2
    3
    4
    5
    6
    7
    // 在JSX中,对于禁用按钮这二者是相同的
    <input type="button" disabled />;
    <input type="button" disabled={true} />;
    // 在JSX中,对于不禁用按钮这二者是相同的
    <input type="button" />;
    <input type="button" disabled={false} />;
  • 子节点表达式
    对于子节点表达式,同样可以在大括号内表示,比如<Nav /><Login />

    1
    var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
  • 如何添加注释
    JSX里添加注释很容易,它们只是JavaScript表达式而已,但需要小心的是,当在一个标签的子节点块时,要用{}包围要注释的部分。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var content = (
    <Nav>
    {/* child comment, 用 {} 包围 */}
    <Person
    /* 多
    注释 */
    name={window.isLoggedIn ? window.name : ''} // 行尾注释
    />
    </Nav>
    );

JSX DOM陷阱

  • 自定义HTML属性
    React不显示HTML规范里不存在的元素属性,若需要使用自定义属性,要加data-前缀。

    1
    <div data-custom-attribute="foo" />

    而在自定义元素中任意的属性都是被支持的(在标签名里带有连接符或is="..."属性的)。

    1
    <x-my-component custom-attribute="foo" />

    aria-开头的网络无障碍属性可以正常使用。

    1
    <div aria-hidden={true} />
  • HTML实体
    HTML实体可以插入到JSX的文本中,如果想在JSX表达式中显示HTML实体,会遇到二次转义的问题,因为React默认会转义所有字符串,为了防止各种 XSS 攻击。

    1
    2
    3
    4
    // 正确,可以正常解析HTML
    <div>First &middot; Second</div>
    // 错误,不能解析,会直接显示“First &middot; Second”
    <div>{'First &middot; Second'}</div>

    但可以通过使用Unicode字符解决,但需要确保文件是UTF-8编码,且网页也指定为UTF-8编码。

    1
    2
    <div>{'First \u00b7 Second'}</div>
    <div>{'First ' + String.fromCharCode(183) + ' Second'}</div>

    可以在数组里混合使用字符串和JSX元素。

    1
    <div>{['First ', <span>&middot;</span>, ' Second']}</div>

JSX运行与安全

JSX在运行时被加载元素类型,因此,元素类型不能是表达式,以下是错误的示例:

1
2
// 错误,不能是表达式
return <components[props.storyType] story={props.story} />;

但可以定义变量,但首字母须大写,如下格式是正确的示例:

1
2
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;

JSX可以防止注入攻击,在React DOM中,默认在渲染前会转义在JSX中的所有值,因此,可以保证不会被注入攻击,所有的内容都已经被转换为字符串进行渲染,同进也会防止跨站脚本攻击(XSS)。

1
2
3
// 在JSX内嵌了用户输入,更加安全
const title = response.potentiallyMaliciousInput;
const element = <h1>{title}</h1>;

小结

主要对JSX的基本概念、语法规范、在React中使用等方面进行了介绍并举例,在React中用法主要包括JSX命名规则、命名空间组件、JavaScript表达式、DOM陷阱以及运行与安全方面的介绍和示例。
通常,建议在你的编辑器中配置Babel语法体系,从而使得ES6JSX代码都可以高亮显示。另外,可以到在线的 JS BINBabel REPL 进行简单练习。


References