十二、深入理解JSX
从根本上讲,JSX就是提供了一个React.createElement(component, props, ...children)
函数的语法糖。就像下面的JSX代码:
<MyButton color="blue" shadow={2}>
Click Me
</MyButton>
经过编译后为:
React.createElement(
MyButton,
{color: 'blue', shadow: 2},
'Click Me'
)
如果一个标签没有子元素的话,你可以使用/>
来自动闭合。例如:
<div className="sidebar" />
经过编译后为:
React.createElement(
'div',
{className: 'sidebar'},
null
)
如果你想测试一些特定的JSX是如何转换成JavaScript的话,你可以试试在线Babel编译器
。
指定React元素类型
JSX标记的第一部分决定了React元素的类型。
首字母大写的类型表示JSX标记指的为React组件。 这些标签被编译为对指定变量的直接引用,因此如果使用JSX <Foo />
表达式,Foo必须在当前的作用域内。
React必须在作用域内
由于JSX编译的本质是对React.createElement
的调用,因此React库也必须始终在JSX代码的作用域中。
例如,虽然CustomButton
没有直接引用React
,但是这两个导入的模块在这段代码中也还是很有必要的:
import React from 'react';
import ReactDOM from 'react-dom';
function WarningButton(props) {
// return React.createElement(CustomButton, {color: 'red'}, null);
return <CustomButton color="red" />
}
如果不使用JavaScript打包工具并将React通过script标签引入,那么它就会作为一个全局变量React
。
对JSX类型使用『点』表示符
您还可以使用JSX中的点表示符来引用React组件。 如果您有一个模块会导出很多React组件的话,使用这种方法就会十分方便。 例如,如果MyComponents.DatePicker
是一个组件,您可以直接从JSX使用它:
import React from 'react';
import ReactDOM from 'react-dom';
const MyComponents = {
DatePicker(props) {
return <div>这里有一个颜色为{props.color}的日期选择器</div>
}
};
function BlueDataPicker(props) {
return <MyComponents.DatePicker color="blue" />
}
ReactDOM.render(
<BlueDataPicker />,
document.getElementById('root')
);
用户自定义组件必须是首字母大写
当元素类型以是小写字母开头时,它指向一个内置组件,如<div>
或<span>
,并生成一个字符串'div'
或'span'
传递给React.createElement
。 以大写字母开头的类型,如<Foo />
编译为React.createElement(Foo)
,并且在当前作用域内寻找这个名称为Foo
的已定义或已导入组件。
我们建议使用首字母大写
命名组件。 如果你有一个以小写字母开头的组件,请在JSX中使用它之前请将它赋值给一个首字母大写的变量。
下面代码不会按预期运行:
import React from 'react';
//这是错误的,这个组件应该为首字母大写
function hello(props) {
// 这是正确的,因为div是一个有效的html标签
return <div>Hello {props.name}</div>;
}
function HelloWorld(props) {
// 这是错误的,因为它是首字母小写,所以React认为<hello />是一个html标签
return <hello name="zhangyatao" />
}
想要修复上面的问题,我们必须将hello
重命名为Hello
,通过<Hello />
来使用该组件:
import React from 'react';
// 这是正确的
function Hello(props) {
return <div>Hello {props.name}</div>;
}
function HelloWorld(props) {
// 这是正确的
return <Hello name="zhangyatao" />;
}
在运行的时候选择组件类型
不能将常规的javascript表达式用作React元素类型。 如果你想使用一个通用表达式来表示元素的类型,只需将它赋值给一个首字母大写的变量即可。
这通常出现在当你想基于同一个props渲染一个不同的组件的情况下:
import React from 'react';
import {Com1, Com2} from './Components';
const components = {
myCom1: Com1,
myCom2: Com2
}
function RunCom(props) {
// 这是错误的,JSX的类型不能这么写
return <components[props.comType] type={props.type} />;
}
想要解决上面的问题,只需要将它们赋值给一个首字母大写的变量即可:
import React from 'react';
import {Com1, Com2} from './Components';
const components = {
myCom1: Com1,
myCom2: Com2
}
function RunCom(props) {
// 这是正确的,将它们赋值给一个首字母大写的变量
const MyCom = components[props.comType];
return <MyCom type={props.type} />;
}
JSX中的Props
在JSX中指定Props有以下几种不同的方法。
JavaScript表达式
你可以传递任何JavaScript表达式作为Props,用{}
括住它们就可以使用。 例如,在这个JSX中:
<MyComponents foo={1 + 2 + 3 + 4} />
对于MyComponent
来说,props.foo
的值将为10
,因为是通过表达式1 + 2 + 3 + 4
计算得到的。
if
语句和for
循环在JavaScript中不是表达式,因此它们不能在JSX中直接使用。 相反,写完它们之后你可以把JSX放在里面。 例如:
function NumberDescriber(props) {
let description;
if (props.number % 2 === 0) {
description = <strong>偶数</strong>
} else {
description = <strong>奇数</strong>
}
return <div>{props.number}是一个{description}.</div>;
}
字符串直接量
你可以传递一个字符串内容作为props。 这两个JSX表达式是等价的:
<MyComponent message="hi zhangyatao" />
<MyComponent message={'hi zhangyatao'} />
当你传递一个字符串直接量时,它的值是经过html转义的。 所以这两个JSX表达式是等价的:
<MyComponent message='<3' />
<MyComponent message={'<3'} />
Props默认值为true
如果你没有给Props传入一个值,那么它的默认值为true
,这两个JSX表达式是等价的:
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />
一般来说,我们不建议使用它,因为它可以使用ES6对象的简写{foo}
,也就是{foo:foo}
的简称会和{foo:true}
混淆。 这种行为在这里只是方便它匹配到HTML行为。
Props传递
如果你有一个对象类似的数据作为props,并且想在JSX中传递它,你可以使用...
作为一个“spread”
运算符传递整个props对象。 这两个组件是等效的:
function App() {
return <Greeting firstName="yatao" lastName="zhang" />;
}
function App() {
const props = {firstName: 'yatao', lastName: 'zhang'};
return <Greeting {...props} />;
}
当创建一个通用容器时,spread
props很有用。
然而,他们也可以让你的代码变得有点凌乱,这样很容易使大量不相关的prps传递给那些不关心它们的组件。 建议您谨慎使用此语法。
JSX中的子元素和子组件
在包含开始标记和结束标记的JSX表达式中,这些标记之间的内容通过一种特殊的prop:props.children
传递。 有几种不同的方式传递子组件:
字符串直接量
你可以在开始和结束标签之间放一个字符串,那么props.children
就是那个字符串。 这对许多内置的HTML元素很有用。 例如:
function MyComponent(props) {
return <div>{props.children}<div>; //=> <div>hello zhangyatao</div>
}
<MyComponent>Hello zhangyatao</MyComponent>
这是有效的JSX,并且MyComponent
中的props.children
将是字符串“Hello zhangyatao”
。 HTML标签是不会经过转义的,所以你一般可以写JSX就像你写HTML一样:
<div>这是一个html标签 & 同时也是个JSX</div>
JSX会删除行的开始和结尾处的空格。 它也会删除中间的空行。 与标签相邻的空行被会被删除;
在字符串文本中间出现的空行会缩合成一个空格。 所以这些都渲染相同的事情:
<div>hello zhangyatao</div>
<div>
hello zhangyatao
</div>
<div>
hello
zhangyatao
</div>
<div>
hello zhangyatao
</div>
JSX子元素
你可以使用很多个JSX元素作为子元素。 这对需要嵌套的显示类型组件很有用:
<Dialog>
<DialogHeader />
<DialogBody />
<DialogFooter />
</Dialog>
你可以将不同类型的子元素混合在一起,因此JSX子元素可以与字符串直接量一起使用。 这是JSX的另一种方式,就像一个HTML一样:
<div>
这是一个列表
<ul>
<li>item 1</li>
<li>item 2</li>
</ul>
</div>
一个React组件不可能返回多个React元素,但是一个JSX表达式可以包含多个子元素,因此如果你想让一个组件渲染多个东西,你可以将它们统一放置在就像上面那样的div中。
Javascript表达式
您可以将任何JavaScript表达式放在{}
中作为子组件传递。 例如,下面这些表达式是等价的:
function MyComponent(props) {
return <div>{props.children}<div>; //=> <div>hi zhangyatao</div>
}
<MyComponent>hi zhangyatao</MyComponent>
<MyComponent>{'hi zhangyatao'}</MyComponent>
这通常用于渲染任意长度的JSX表达式列表。 例如,这将渲染一个HTML列表:
function Item(props) {
return <li>{props.message}</li>;
}
function TodoList(props) {
const todos = ['完成文档', '出去逛街', '打一局dota'];
return (
<ul>
{todos.map(message => <Item key={message} message={message} />)}
</ul>
);
}
JavaScript表达式可以与其他类型的子元素混合使用。 这通常用于替换字符串模板:
function Hello(props) {
return <div>Hello {props.name}</div>;
}
使用函数作为子元素
通常,插入JSX中的JavaScript表达式都最终返回为一个字符串、React元素、一个列表。
当然,props.children
可以像任何其他props那样工作,它可以传递任何类型的数据,并不局限于那些告诉React应该如何渲染的东东。 例如,如果您有一个自定义组件,您可以将props.children
作为一个回调函数:
import React from 'react';
import ReactDOM from 'react-dom';
function Repeat(props) {
let items = [];
let callback = props.children;
var numTimes = props.numTimes;
for(var i = 0 ; i < numTimes ; i++ ){
items.push(callback(i));
}
return <div>{items}</div>;
}
function ListOfTenThings(props) {
return (
<Repeat numTimes={10}>
{index => <div key={index}>这是列表中的第{index}项</div>}
</Repeat>
);
}
ReactDOM.render(
<ListOfTenThings/>,
document.getElementById('root')
);
传递给自定义组件的子元素可以是任何东西,只要在React在渲染之前,该组件将它们转换为可以理解的东西即可。 这种用法并不常见,如果你想扩展JSX的其他能力,可以通过这个例子了解下它的工作原理。
布尔值、null、undefined在渲染时会被自动忽略
false
,null
,undefined
和true
是有效的子元素,不过他们从根本上讲是不参与渲染的。 这些JSX表达式将渲染处相同的东西:
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{true}</div>
这对于有条件地呈现React元素很有用。 如果showHeader
为true
,那么这个JSX只渲染一个<Header />
:
<div>
{showHeader && <Header />}
<Content />
</div>
如果返回一些“假的”
值就会收到一个警告,如数字0
,不过React仍然会渲染。 例如,此代码将不会像您预期的那样工作,因为当props.messages
是空数组时将打印0
:
<div>
{props.messages.length && <Message messages={props.messages} />}
</div>
想要修复上面的问题,你要确定这个表达式在&&之前总返回布尔值
:
<div>
{props.messages.length > 0 && <Message messages={props.messages} />}
</div>
相反,如果你想要一个值如false
,true
,null
或undefined
出现在输出中,你必须先将它转换为字符串
:
import React from 'react';
import ReactDOM from 'react-dom';
function MyVariable(props) {
const myVariable = false;
// 如果这里不把false转换为字符串,这只会输出『我的javascript变量是』
const convertedVar = String(myVariable);
return (
<div>
我的javascript变量是{convertedVar}
</div>
);
}
ReactDOM.render(
<MyVariable/>,
document.getElementById('root')
);