JSX In Depth
最根本上,JSX仅仅提供React.createElement(component,props, ...children)
函数这样的语法糖,JSX代码:
//JSX代码
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
//编译之后的JavaScript代码
React.createElement(
MyButton,
{ color:"blue", shadowSize:2 },
"Click Me"
)
//自封闭式标签风格来描述没有子元素的元素
<div className="sidebar" />
//可以编译成JavaScript代码
React.createElement(
'div',
{ className: 'sidebar' },
null
)
指定React元素类型
JSX标签的第一个部分指定了React元素的类型,首字母大写的标签说明了它是一个引用的React组件,这些标签会被编译称为一个命名属性的直接引用,所以如果需要使用JSX<Foo />
标签,那么Foo
必须要在作用域内被定义。
React必须在定义域内
如果JSX编译器调用了React.createElement
,那么React
库必须一直在你的JSX代码中,比如:
//下面的两个引用对于这段代码都是必须的,React和CustomButton都不是直接引用
import React from 'react';
import CustomButton from './CustomButton';
function WarningButton() {
return <CustomButton color="red" />
}
如果不使用打包工具,并且将React添加到script标签内,那么React就已经是一个全局变量了。
在JSX类型中使用点运算符
也可以在JSX中使用点运算符来引用一个React组件,当从一个模块中导出了许多React组件的时候,这样的使用方法非常方便,比如:
import React from 'react';
//MyComponents.DatePicker是一个组件,通过点运算符进行调用
const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>
}
}
function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />
}
用户自定义组件必须首字母大写
当一个元素是小写开头的时候,一般被引用为内建组件,比如<div>
或者<span>
,会给React.createElement
传入一个字符串div
或者span
,而大写字母开头的组件比如:<Foo />
会编译成React.createElement(Foo)
并且和组件定义联系起来。
我们建议使用大写字母开头来定义组件,如果你有一个组件,并且它以小写字母开头,应该为其分配一个大写字母开头的变量。
比如:
import React from 'react';
function hello(props) {
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
//这里的调用会出现问题,React会将hello标签误认为是HTML标签,因为它没有首字母大写
//为了解决这个问题,将hello重命名为Hello就可以解决问题
return <hello toWhat="World" />;
}
运行时选择类型
你不能够使用一般的表达式作为React元素的类型,如果你需要使用一个一般表达式来指定元素类型,就需要将这个表达式分配到大写字母开头的变量中,这种情况经常发生在你需要基于props来进行不同的渲染的时候。
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
//错误,不能够在React组件的类型上使用一般表达式
function Story(props) {
return <components[props.storyType] story={props.story}>
}
//正确,JSX类型可以是一个大写字母开头的变量
function Story(props) {
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story}>
}
JSX中的props
下面有几种不同的方式在JSX中指定props
JavaScript表达式
你可以传递任何JavaScript表达式作为props,但是要通过花括号来进行包裹
//对于下面这个组件,props.foo的值将为10,因为表达式1+2+3+4的结果为10
<MyComponent foo={1 + 2 + 3 + 4} />
if
表达式和for
循环在JavaScript中并不是表达式,所以不能直接用在JSX中,相反,你可以将这些代码放在周围的代码中:
function NumberDescriber(props) {
let description;
//将逻辑表达式提出到JSX语句外部
if (props.number % 2 === 0) {
description = <strong>even</strong>;
} else {
description = <i>odd</i>;
}
return <div>{props.number} is an {description} number.</div>;
}
字符串语法
你可以传递一个字符串作为props:
//下面两个表达式是等价的
<MyComponents message="hello world" />
<MyComponents message={'hello world'} />
当你传递一个字符串的时候,这个值是非HTML保有的:
//下面两个表达式也是等价的
<MyComponent message="<3" />
<MyComponent message={"<3"} />
props默认为True
如果你没有为某个属性传递一个值,那这个属性的默认值为true
//下面两个JSX表达式是等价的
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />
一般来说,我们不建议使用默认值,因为ES6语法中的对象简写{foo}
等价于{foo: foo}
,所以这样容易混淆,只有这个行为和在HTML中的写法相同的时候,才这么使用。
扩展属性
如果你已经有了一个props
的对象变量,并且希望将这个变量传递给JSX,可以使用...
作为扩展运算符来将整个对象传递给props:
//下面连个函数是等价的
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />
}
function App2() {
const props = { firstName: "Ben", lastName: "Hector" };
return <Greeting {...props} />
}
这种语法很容易去创建一个易于继承的容器,但是这可能会使你的代码变得很混乱,因为你可能会传递许多不相关的属性给子元素。
JSX中的子元素
在包含了一个开放标签和一个闭合标签的JSX表达式中,在这些标签当中的内容会传递一个特殊的属性:props.children
有几种方法可以进行传递。
字符串语法
你可以在开始和结束标签之中放置一个字符串,并且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移除了一行开头和结束的空格(相当于调用了一次trim()
函数),并且也会移除空行,标签附近的新行会被删除,字符串中间的新行会被解析为一个空格:
//下面的几种JSX语法渲染得到的结果都是相同的
<div>Hello World</div>
<div>
Hello World
</div>
<div>
Hello
World
</div>
<div>
Hello World
</div>
JSX子元素
你可以提供更多的JSX元素作为子元素,这对于渲染嵌套组件非常好用:
<MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>
你可以混合多种不同类型的子元素,所以你可以使用字符串在JSX子元素中:
<div>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
一个React组件不能返回多个React元素,但是一个单独的JSX可以有多个子元素,所以如果你希望一个组件返回多个元素的时候,你可以将这些元素包裹在一个div
中。
JavaScript表达式
你可以传递任何JavaScript表达式作为子元素,通过闭合的{}
,比如:
//下面两个表达式有相同的渲染结果
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
这对于渲染一个随机长度的JSX表达式是十分有用的:
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>
}
函数作为子元素
正常情况下,JavaScript表达式插入JSX的时候都是字符串,React元素或者一个上述元素的列表,然而props.children
像其他属性一样,可以传递任何类型的数据,并不仅仅只是React知道如何渲染的那些数据,比如,如果你有一个自定义组件,你也可以用一个回调函数作为props.children
。
function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}
function Repeat(props) {
let items = [];
for (let i=0; i<props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>
}
传递给自定义组件的子元素可以是任何类型的,然后在渲染之前,组件会被转化为可以被React识别的类型,这种使用方法并不常见,但是如果需要在JSX中使用的时候也是没有问题的。
被忽略的Boolean, Null和Undefined
false
,null
,undefined
,true
是可用的子元素,这些元素不会被渲染:
//下面的几个组件都有相同的渲染效果。
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{true}</div>
这在条件渲染某些React元素的时候有些作用的。
//下面的代码会渲染出一个<Header />,如果showHeader为true的话
<div>
{showHeader && <Header />}
<Content />
</div>
需要注意的是一些”falsy”值,比如数字0,仍然会被React进行渲染:
//下面的代码并不会得到预期的结果,如果props.messages如果没有值的话,就会打印0
<div>
{props.messages.length &&
<MessageList messages={props.messages} />
}
</div>
如果需要得到正确的输出,那么需要保证&&
符号之前必须是boolean值
<div>
{
props.messages.length > 0 &&
<MessageList messages={props.messages} />
}
</div>
相反,如果你希望有false
,true
,null
,undefined
被打印到页面上的时候,你需要将其先转换为字符串:
<div>
My JavaScript variable is {String(myVariable)}.
</div>