本质上,JSX只是React.createElement(component, props, ...children)
的语法糖,比如下面代码:
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
将被编译成:
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
如果没有子标签的话,也可以使用自闭和标签:
<div className="sidebar" />
会被编译成:
React.createElement(
'div',
{className: 'sidebar'},
null
)
如果想测试JSX如何被编译成JavaScript,可以尝试Babel在线编译器
JSX标签的第一部分明确了React元素的类型。
使用大写的JSX标签来表示React的一个组件。这些标签编译成对命名变量的直接的引用,所以,如果使用JSX标签<Foo />
表达式,则Foo
必须在作用域内。(译者注:必须引入组件)
由于JSX会被编译成React.createElement
,因此在JSX的代码中,必须引入React
库。
例如,下面代码中即使React
和CustomButton
没有直接在JavaScript中使用,但是这两者都必须在一起引入:
import React from 'react';
import CustomButton from './CustomButton';
function WarningButton() {
// return React.createElement(CustomButton, {color: 'red'}, null);
return <CustomButton color="red" />;
}
如果使用<script>
标签加载React,而不是使用模块化加载,React也是在作用域内的,此时React是全局作用域中。
在JSX中可以使用点(.
)操作符使用React组件,如果通过一个模块导出多个React组件的话,使用点操作符非常的使用和方便。
例如,如果MyComponents.DatePicker
是一个组件,可以在JSC中直接向下面这样使用:
import React from 'react';
const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
}
}
function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />;
}
如果定义的组件使用小写开头,他会把<div>
,<span>
这样的内置组件作为div
和span
字符串传递给React.createElement
。
如果以大写开头, 比如<Foo/>
会被编译成React.createElement(Foo)
,并且Foo
对应在jsx文件中引入的组件.
React推荐使用大写开头定义组件,如果有小写开头的组件,则在jsx中使用前,应当将其赋值给大写的变量。
例如,下面的代码将不会起作用:
import React from 'react';
// Wrong! This is a component and should have been capitalized:
function hello(props) {
// Correct! This use of <div> is legitimate because div is a valid HTML tag:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// Wrong! React thinks <hello /> is an HTML tag because it's not capitalized:
return <hello toWhat="World" />;
}
为了解决上面的问题,需要将hello
改成Hello
,并且使用<Hello/>
:
import React from 'react';
// Correct! This is a component and should be capitalized:
function Hello(props) {
// Correct! This use of <div> is legitimate because div is a valid HTML tag:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// Correct! React knows <Hello /> is a component because it's capitalized.
return <Hello toWhat="World" />;
}
不能使用通用表达式来作为React的元素类型,如果你想使用一个通用表达式来指明元素类型,首先需要将其赋值给大写的变量。
这种情况经常发生在需要根据prop来渲染不同的组件的情况:
(下面代码会出错)
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Wrong! JSX type can't be an expression.
return <components[props.storyType] story={props.story} />;
}
为了解决这个问题,需要将标签赋值给大写的变量:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
在JSX中指定props有几种不同的方式。
对于MyComponent
,props.foo
的值是10
,因为1 + 2 + 3 + 4
结果是10.
由于if
语句和for
循环不是JavaScript的表达式,所以不能直接在JSX使用。不过,可以在JSX外部的代码中使用,比如:
function NumberDescriber(props) {
let description;
if (props.number % 2 == 0) {
description = <strong>even</strong>;
} else {
description = <i>odd</i>;
}
return <div>{props.number} is an {description} number</div>;
}
你可以将字符串文本作为prop,下面两个JSX表达式是等价的:
<MyComponent message="hello world" />
<MyComponent message={'hello world'} />
如果将字符串作为prop,则prop的值是对HTML的转义,因此下面两个JSX表达式是等价的:
<MyComponent message="<3" />
<MyComponent message={'<3'} />
不过两者没直接的什么关系,只是提一下。
如果没有给prop指定值,则默认值是true
,因此下面两个JSX表达式是等价的:
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />
一般我们不建议这样去使用,因为这将会和ES6对象简写冲突。
比如 {foo}
会被看做{foo:foo}
的简写,而不是{foo:true}
的简写。
This behavior is just there so that it matches the behavior of HTML.
如果你的props
作为一个对象,如果在JSX中使用这个对象传递props
,可以通过扩展运算符...
展开props对象,下面两个组件是等价的:
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}
你也可以在利用展开运算符实现一些特殊的props:
const Button = props => {
const { kind, ...other } = props;
const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
return <button className={className} {...other} />;
};
const App = () => {
return (
<div>
<Button kind="primary" onClick={() => console.log("clicked!")}>
Hello World!
</Button>
</div>
);
};
在上面的例子中,kind
不会传递给DOM中的<button>
元素,只会被处理。
而其他的props则会通过...other
对象传递给DOM元素,可以看到传递了一个onClick
和children
.
虽然扩展运算符能够非常方便的将props传递,但是也会将一些不需要的props或者是无效的HTML属性传递给DOM,所以建议谨慎使用这种语法。
JSX表达式中,除了自闭和标签外还有非自闭和标签,中间的内容通过一个特殊的prop,props.children
。下面有几种不同的方式来传递这些props:
你可以在标签中间放一个字符串,props.children
将只是该字符串。这对于许多内置的HTML元素很有用,比如:
<MyComponent>Hello world!</MyComponent>
上述代码在JSX中是有用的,并且MyComponent
中的props.children
是字符串Hello world!
.HTML会进行转移,因此可以像下面这样子写HTML元素:
<div>This is valid HTML & JSX at the same time.</div>
JSX会移除行开始和结尾的空格,同时也会删除空行。
与标签相邻的新行也会被移除,字符串文字中间的新行会被压缩成一个空格。
例如:
<div>Hello World</div>
<div>
Hello World
</div>
<div>
Hello
World
</div>
<div>
Hello World
</div>
同样,JSX可以将JSX组件作为子元素,这对于显示嵌套的组件很有用:
<MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>
可以混合不同类型的子元素,所以可以同时一起使用字符串文本作为JSX的子元素。就像使用HTML一样使用JSX,因此既是有用的JSX也是有用的HTML:
<div>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
React的组件同样能够返回一个html元素数组:
render() {
// No need to wrap list items in an extra element!
return [
// Don't forget the keys :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
你可以将任意的JavaScript表达式作为Children,通过一个闭合的{}
即可。例如,下面两个是等价的:
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
经常用于显示任意长度的JSX表达式,比如呈现一个todos
列表:
function Item(props) {
return <li>{props.message}</li>;
}
function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
<ul>
{todos.map((message) => <Item key={message} message={message} />)}
</ul>
);
}
JavaScript 表达式能够和其他类型的表达式混合使用,比如替代字符串模板:
function Hello(props) {
return <div>Hello {props.addressee}!</div>;
}
通常来说,JSX中的JavaScript表达式会被转为字符串、React元素或者是其他。然而,props.children
和其他的prop一样能够传递任何数据,而不仅仅是React能够渲染的内容。例如,如果你有一个自定义组件,你可以将其作为props.children
的回调。
// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}
function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}
传递给自定义组件的Children可以是任意的内容,只要组件能够将其转换成能够渲染的内容即可。这种用法并不常见,如果想要扩展JSX的功能,可以这样去使用。
false
、null
、undefined
和true
都是正确的Children,他们只是不会被渲染。
这些JSX表达式最后渲染的结果是一样的:
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>
这对于条件渲染React元素很有用,比如下面的例子(如果showHeader
是true
,则会渲染<Header/>
):
(译者注:这里觉得文档中的表述并不是非常的好,下面的表述某种程度上容易被理解成只会渲染<Header/>
而不渲染 <Content/>
,虽然这是不可能的)
This JSX only renders a
<Header />
if showHeader is true:
<div>
{showHeader && <Header/>}
<Content/>
</div>
不过需要注意的是,一些falsy
值(实际上就是JavaScirpt中一些类型转换),比如0
,依旧会在React中进行渲染。
例如下面的例子,代码中不会按照预期的渲染,因为props.message
是空数组的时候,会被渲染出0
,而不会忽略。(译者注:因为0
不是标准的Boolean)
<div>
{props.messages.length &&
<MessageList messages={props.messages} />
}
</div>
为了解决上述问题,需要确保在使用&&
运算符的时候,前面必须是Boolean
类型的:
<div>
{props.messages.length > 0 &&
<MessageList messages={props.messages} />
}
</div>
反之,如果你想将false
、true
、null
或者undefined
渲染出来,你需要将其转成string:
<div>
My JavaScript variable is {String(myVariable)}.
</div>