再看JSX
本质上来讲,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.createElement
方法的调用,所以在你的 JSX 代码中,React
库必须也始终在作用域中。
比如,下面两个导入都是必须的,尽管 React
和 CustomButton
都没有在代码中被直接调用。
import React from 'react'; import CustomButton from './CustomButton'; function WarningButton() { // return React.createElement(CustomButton, {color: 'red'}, null); return <CustomButton color="red" />; }
如果你没有使用JavaScript 打捆机,而是从<script>
标签加载React,它已经在作用域中,以React
全局变量的形式。
点表示法用于JSX类型
你还可以使用 JSX 中的点表示法来引用 React 组件。你可以方便地从一个模块中导出许多 React 组件。例如,有一个名为 MyComponents.DatePicker
的组件,你可以直接在 JSX 中使用它:
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)
,并且它正对应于你在 JavaScript 文件中定义或导入的组件。
在运行时选择类型
你不能使用一个通用的表达式来作为 React 元素的标签。如果你的确想使用一个通用的表达式来确定 React 元素的类型,请先将其赋值给大写开头的变量。这种情况一般发生于当你想基于属性值渲染不同的组件时:
import React from 'react'; import { PhotoStory, VideoStory } from './stories'; const components = { photo: PhotoStory, video: VideoStory }; function Story(props) { // 错误!JSX 标签名不能为一个表达式。 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) { // 正确!JSX 标签名可以为大写开头的变量。 const SpecificStory = components[props.storyType]; return <SpecificStory story={props.story} />; }
props
属性默认为“True”
如果你没有给属性传值,它默认为 true
。因此下面两个 JSX 是等价的:
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />
一般情况下,我们不建议这样使用,因为它会与 ES6 对象简洁表示法 混淆。比如 {foo}
是 {foo: foo}
的简写,而不是 {foo: true}
。这里能这样用,是因为它符合 HTML 的做法。
展开属性(传参)
如果你已经有了个 props
对象,并且想在 JSX 中传递它,你可以使用 ...
作为“展开(spread)”操作符来传递整个属性对象。下面两个组件是等效的:
function App1() { return <Greeting firstName="Ben" lastName="Hector" />; } function App2() { const props = {firstName: 'Ben', lastName: 'Hector'}; return <Greeting {...props} />; }
You can also pick specific props that your component will consume while passing all other props using the spread operator.
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> ); };
In the example above, the kind
prop is safely consumed and is not passed on to the <button>
element in the DOM. All other props are passed via the ...other
object making this component really flexible. You can see that it passes an onClick
and children
props.
展开属性非常有用。但是他们也容易传递不必要的属性给组件,而组件并不需要这些多余属性。或者传递无效的HTML熟悉给DOM。我们建议你谨慎使用此语法。
函数作为子代
通常情况下,插入 JSX 中的 JavaScript 表达式将被认作字符串、React 元素或这些的一个列表。然而,props.children
可以像其它属性一样传递任何种类的数据,而不仅仅是 React 知道如何去渲染的数据种类。例如,如果你有一个自定义组件,你能使其取一个回调作为props.children
:(children就是一个参数而已可以将dom对象传入)
// 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> ); }
传递给自定义组件的子代可以是任何东西,只要该组件在 React 渲染前将其转换成 React 能够理解的东西。这个用法并不常见,但当你想扩展 JSX 时可以使用。
布尔值、Null 和 Undefined 被忽略
这在根据条件来确定是否渲染React元素时非常有用。以下的JSX只会在showHeader
为true
时渲染<Header />
组件。
<div>
{showHeader && <Header />}
<Content />
</div>
一个告诫是JavaScript中的一些 “falsy” 值(比如数字0
),它们依然会被React渲染。例如,下面的代码不会像你预期的那样运行,因为当 props.message
为空数组时,它会打印0
:
<div> {props.messages.length && <MessageList messages={props.messages} /> } </div>
要解决这个问题,请确保 &&
前面的表达式始终为布尔值:
<div> {props.messages.length > 0 && <MessageList messages={props.messages} /> } </div>
限制单个子代
使用 PropTypes.element
你可以指定只传递一个子代
import PropTypes from 'prop-types'; class MyComponent extends React.Component { render() { // This must be exactly one element or it will warn. const children = this.props.children; return ( <div> {children} </div> ); } } MyComponent.propTypes = { children: PropTypes.element.isRequired };
属性默认值
你可以通过配置 defaultProps
为 props
定义默认值:
class Greeting extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } } // 为属性指定默认值: Greeting.defaultProps = { name: 'Stranger' }; // 渲染 "Hello, Stranger": ReactDOM.render( <Greeting />, document.getElementById('example') );
如果你在使用像 transform-class-properties 的 Babel 转换器,你也可以在React 组件类中声明 defaultProps
作为静态属性。这个语法还没有最终通过,在浏览器中需要一步编译工作。更多信息,查看类字段提议。
class Greeting extends React.Component { static defaultProps = { name: 'stranger' } render() { return ( <div>Hello, {this.props.name}</div> ) } }
defaultProps
用来确保 this.props.name
在父组件没有特别指定的情况下,有一个初始值。类型检查发生在 defaultProps
赋值之后,所以类型检查也会应用在 defaultProps
上面。