JSX In Depth
本质上,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在线编译器
指定React的元素类型
JSX标签的第一部分明确了React元素的类型。
使用大写的JSX标签来表示React的一个组件。这些标签编译成对命名变量的直接的引用,所以,如果使用JSX标签<Foo />表达式,则Foo必须在作用域内。(译者注:必须引入组件)
作用域内必须引入React
由于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使用点(.)操作符
在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
在JSX中指定props有几种不同的方式。
使用JavaScript表达式作为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
如果没有给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中children props
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>HelloWorld</div><div>Hello World</div>
JSX子组件
同样,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表达式作为子元素
你可以将任意的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>;}
Children传递方法
通常来说,JSX中的JavaScript表达式会被转为字符串、React元素或者是其他。然而,props.children和其他的prop一样能够传递任何数据,而不仅仅是React能够渲染的内容。例如,如果你有一个自定义组件,你可以将其作为props.children的回调。
// Calls the children callback numTimes to produce a repeated componentfunction 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的功能,可以这样去使用。
Booleans,Null和Undefined会被忽略
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>
