JSX 是类 XML 语法的 ECMAScript 扩展,完美地利用了 JavaScript 自带的语法和特性,并使用大家熟悉的 HTML 语法来创建虚拟元素。
由于 JSX 这种声明式语法实际是在构建一个抽象的视图层,本身没有定义任何语义,这种抽象不会被引擎或浏览器执行,需要通过不同适配器(编译器、处理器)适配(编译为标准的 ECMAScript)到各种显示终端。
React 引入 JSX 主要是为了 方便 View 层组件化,承载了构建 HTML 结构化页面的职责。这一点与其他很多的 JavaScript 模板语言异曲同工,不过 React 将 JSX 映射为虚拟元素,并且通过创建与更新虚拟元素来管理整个 Virtual DOM 系统。
📌 本质上来说,JSX 只是为 React.createElement(component, props, ...children)
提供的一种语法糖。
每个 DOM 元素的结构都可以用 JavaScript 的对象来表示。你会发现一个 DOM 元素包含的信息其实只有三个:
tagName
props
children
<div class="box" id="content"><div class="title">Hello</div><button>Click</button></div>
上述的 HTML 所有信息可以用合法的 JavaScript 对象来表示。
{tag: 'div',attrs: { className: 'box', id: 'content' },children: [{tag: 'div',attrs: { className: 'title' },children: ['Hello']},{tag: 'button',attrs: null,children: ['Click']}]}
我们可以使用 JavaScript 对象来描述所有能用 HTML 表示的 UI 信息。但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。
于是 React 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类 HTML 的 JSX 结构转换成 JavaScript 的对象结构。
React.createElement
会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等。
📌 使用守则:必须要在 JSX 声明的文件中引入 React
import React from 'react';const myButton = (<MyButton color="blue" shadow={2}>Click Me</MyButton>);
上述代码会被编译成:
React.createElement(MyButton,{color: 'blue',shadow: 2,},'Click Me');
⚠️ 注意事项:
对应规则是 HTML 标签首字母是否为小写字母,其中小写首字母对应 DOM 元素,而 React 组件元素自然对应大写首字母。
JSX 还可以通过命名空间的方式使用 React 组件元素,以解决组件相同名称冲突的问题,或是对一组组件进行归类。
// DOM 元素<div>Hello World!</div>;// React 组件元素<Foo>Hello World!</Foo>;// 注释{/* Hello World! */}
特殊标签:注释和 DOCTYPE。
组件元素的属性是完全自定义的属性,也可以理解为实现组件所需要的参数。
在 React 中,所有的 DOM 特性和属性(包括事件处理函数)都是 小驼峰命名法 命名。
省略 Boolean 属性值会导致 JSX 认为布尔值设为了 true
。要传 false
时,必须使用属性表达式。这常用于表单元素中,比如 disabled
、required
、checked
和 readOnly
等。
// BAD<Checkbox checked={true}/>// BETTER<Checkbox checked/>
checked
checked
defaultChecked
// 已选中<input type='checkbox' checked />// 未选中<input type='checkbox' />
className
为常规 DOM 节点和 SVG 元素指定 CSS 类。
<div className="foo" />
dangerouslySetInnerHTML
用于替换浏览器 DOM 中 innerHTML
接口的一个函数。由于容易把用户信息暴露给跨站脚本攻击,因此使用该函数是非常危险的。
<div dangerouslySetInnerHTML={{ __html: '✓' }} />
htmlFor
由于 for
在 JavaScript 中是保留字,React 元素使用 htmlFor
代替。
<form><label htmlFor="male" /><input type="radio" name="sex" id="male" /></form>
更详细的元素属性介绍:
虽然 React 组件元素的属性采取小驼峰命名法命名,但是 aria-*
和 data-*
属性是例外的,一律使用小写字母命名。
如果在 JSX 中往 DOM 元素中传入自定义属性,React 是不会渲染的(因为 React 无法识别)。
<div d="xxx">content</div>
📌 如果要使用 HTML 自定义属性,要使用 data-
前缀,这与 HTML 标准也是一致 。
<div data-attr="xxx">content</div>
然而,在自定义标签中任意的属性都是被支持的。
<x-my-component custom-attr="foo" />
📌 以 aria-
开头的网络无障碍属性同样可以正常使用。
<div aria-hidden={true} />
属性值要使用表达式,只要用 {}
(花括号)替换 ""
(双引号)即可。
render(){const containerCls = 'container';return (<div className={containerCls}><div onClick={this.onStateChange}></div></div>)}
React 会将所有要显示到 DOM 的字符串转义,防止 XSS。所以,如果 JSX 中含有转义后的实体字符,比如 ©
(©),则最后 DOM 中不会正确显示,因为 React 自动把 ©
中的特殊字符转义了。
有几种解决方案:
<div>{['cc ', <span>©</span>, ' 2015']}</div>
此外,React 提供了 dangerouslySetInnerHTML
属性。正如其名,它的作用就是避免 React 转义字符,在确定必要的情况下可以使用它。
<div dangerouslySetInnerHTML={{ __html: 'cc © 2015' }} />
避免 XSS 注入攻击
最后需要提及的是,React 中 JSX 能够帮我们自动防护部分 XSS 攻击,譬如我们常见的需要将用户输入的内容再呈现出来:
const title = response.potentiallyMaliciousInput;// This is safe:const element = <h1>{title}</h1>;
在标准的 HTML 中,如果我们不对用户输入作任何的过滤,那么当用户输入 <script>alert(1)<script/>
这样的可执行代码之后,就存在被 XSS 攻击的危险。而 React 在实际渲染之前会帮我们自动过滤掉嵌入在 JSX 中的危险代码,将所有的输入进行编码,保证其为纯字符串之后再进行渲染。不过这种安全过滤有时候也会对我们造成不便,譬如如果我们需要使用 ©
这样的实体字符时,React 会自动将其转移最后导致无法正确渲染,上面提及的字符串转义就起到作用了。
function createMarkup() {return { __html: 'First · Second' };}function MyComponent() {return <div dangerouslySetInnerHTML={createMarkup()} />;}
参考资料: