JSX高级
本质上来讲,JSX 只是为 React.createElement(component, props, ...children)
方法提供的语法糖。比如下面的代码:
<MyButton color="blue" shadowSize=>
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 组件。这些标签将会被编译为直接引用同名变量,所以如果你使用了 <Foo />
JSX 表达式,则 Foo
必须在作用域中。
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 文件中定义或导入的组件。
我们建议用大写开头命名组件。如果你的组件以小写字母开头,请在 JSX 中使用之前其赋值给大写开头的变量。
例如,下面的代码将无法按预期运行:
import React from 'react';
// 错误!组件名应该首字母大写:
function hello(props) {
// 正确!div 是有效的 HTML 标签:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// 错误!React 会将小写开头的标签名认为是 HTML 原生标签:
return <hello toWhat="World" />;
}
为了解决这个问题,我们将 hello
重命名为 Hello
,然后使用 <Hello />
引用:
import React from 'react';
// 正确!组件名应该首字母大写:
function Hello(props) {
// 正确!div 是有效的 HTML 标签:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// 正确!React 能够将大写开头的标签名认为是 React 组件。
return <Hello toWhat="World" />;
}
在运行时选择类型
你不能使用一个通用的表达式来作为 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} />;
}
JSX的属性(Props)
在 JSX 中有几种不同的方式来指定属性。
使用 JavaScript 表达式作为属性
你可以传递JavaScript 表达式作为一个属性,再用大括号{}
括起来。例如,在这个 JSX 中:
<MyComponent foo={1 + 2 + 3 + 4} />
对于 MyComponent
来说, props.foo
的值为 10,这是 1 + 2 + 3 + 4
表达式计算得出的。
if
语句和 for
循环在 JavaScript 中不是表达式,因此它们不能直接在 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>;
}
你可以在相关部分中了解有关 条件渲染 和 循环 的更多信息。
字符串常量
你可以将字符串常量作为属性值传递。下面这两个 JSX 表达式是等价的:
<MyComponent message="hello world" />
<MyComponent message={'hello world'} />
当传递一个字符串常量时,该值为HTML非转义的,所以下面两个 JSX 表达式是相同的:
<MyComponent message="<3" />
<MyComponent message={'<3'} />
这种行为通常是无意义的,提到它只是为了完整性。
属性默认为“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中的子代
在既包含开始标签又包含结束标签的 JSX 表达式中,这两个标签之间的内容被传递为专门的属性:props.children
。有几种不同的方法来传递子代:
字符串字面量
你可以在开始和结束标签之间放入一个字符串,则 props.children
就是那个字符串。这对于许多内置 HTML 元素很有用。例如:
<MyComponent>Hello world!</MyComponent>
这是有效的 JSX,并且 MyComponent
的 props.children
值将会直接是 "hello world!"
。因为 HTML 未转义,所以你可以像写 HTML 一样写 JSX:
<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子代,这是 JSX 类似 HTML 的另一种形式,这在 JSX 和 HTML 中都是有效的:
<div>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
React 组件也可以返回包含多个元素的一个数组:
render() {
// 不需要使用额外的元素包裹数组中的元素!
return [
// 不要忘记 key :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
JavaScript 表达式作为子代
你可以将任何 {}
包裹的 JavaScript 表达式作为子代传递。例如,下面这些表达式是等价的:
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
这对于渲染任意长度的 JSX 表达式的列表很有用。例如,下面将会渲染一个 HTML 列表:
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>;
}
布尔值、Null 和 Undefined 被忽略
false
、null
、undefined
和 true
都是有效的子代,只是它们不会被渲染。下面的JSX表达式将渲染为相同的东西:
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>
这在根据条件来确定是否渲染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>
相反,如果你想让类似 false
、true
、null
或 undefined
出现在输出中,你必须先把它转换成字符串 :
<div>
My JavaScript variable is {String(myVariable)}.
</div>
使用 PropTypes 进行类型检查
使用 prop-types
库帮助我们对组件的属性进行类型的控制。
随着应用日渐庞大,你可以通过类型检查捕获大量错误。 对于某些应用来说,你还可以使用 Flow 或 TypeScript 这样的 JavsScript 扩展来对整个应用程序进行类型检查。然而即使你不用它们,React 也有一些内置的类型检查功能。要检查组件的属性,你需要配置特殊的 propTypes
属性:
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
PropTypes
包含一整套验证器,可用于确保你接收的数据是有效的。在这个示例中,我们使用了 PropTypes.string
。当你给属性传递了无效值时,JavsScript 控制台将会打印警告。出于性能原因,propTypes
只在开发模式下进行检查。
PropTypes
下面是使用不同验证器的例子:
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为以下 JS 原生类型
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、子元素或数组)。
optionalNode: PropTypes.node,
// 一个 React 元素
optionalElement: PropTypes.element,
// 你也可以声明属性为某个类的实例,这里使用 JS 的
// instanceof 操作符实现。
optionalMessage: PropTypes.instanceOf(Message),
// 你也可以限制你的属性值是某个特定值之一
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 限制它为列举类型之一的对象
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 一个指定元素类型的数组
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 一个指定类型的对象
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 一个指定属性及其类型的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// 你也可以在任何 PropTypes 属性后面加上 `isRequired`
// 后缀,这样如果这个属性父组件没有提供时,会打印警告信息
requiredFunc: PropTypes.func.isRequired,
// 任意类型的数据
requiredAny: PropTypes.any.isRequired,
// 你也可以指定一个自定义验证器。它应该在验证失败时返回
// 一个 Error 对象而不是 `console.warn` 或抛出异常。
// 不过在 `oneOfType` 中它不起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 不过你可以提供一个自定义的 `arrayOf` 或 `objectOf`
// 验证器,它应该在验证失败时返回一个 Error 对象。 它被用
// 于验证数组或对象的每个值。验证器前两个参数的第一个是数组
// 或对象本身,第二个是它们对应的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
限制单个子代
使用 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
上面。