JSX即Javascript Extension,是在js语言上做的扩展,是为了在js里面更方便地书写html而开发。
以下即是最简单的一段JSX。
const hi = <h1>Hello, JSX!</h1>JSX里面可以很容易地嵌入JS表达式,比如:
const hi = <h1>Hello, JSX! 1 + 1 == {1 + 1}. today is {new Date()}</h1>而JSX本身也是一个表达式,会被Babel等编译成普通的JS对象,所以任何可以使用对象的地方都可以使用JSX,比如if判断,for循环里面,赋值给变量,作为函数参数传递或者返回值返回。比如:
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}JSX跟html很像(其实就是为了方便使用html才发明了JSX),所以支持指定属性。比如:
const img = <img src={user.photo} alt="this is a photo." />注意,不要使用引号把{}包起来。
JSX可以包含子元素(就跟html标签一样),如果没有子元素可以直接用/>来结束标签。
const element = (
<div>
<h1 className="hi">Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);注意JSX其实是JS,所以各种命名等都习惯用JS常用的camelCase风格,并且由于class是JS的关键字,所以标签里面要用className。
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;所以如果title里面有标签<>,则会被转义为<>,如果你确保要插入的html没有问题(比如就是你自己生成的),你可以使用dangerouslySetInnerHTML属性插入未被转义的html内容:
const element = <div dangerouslySetInnerHTML={{__html: '<h1>Dangerous!</h>'}} />注意这里是两个{}包起来哦, 因为外面一层是JSX里面写JS表达式, 里面一层是JS对象本身。
Babel会把JSX编译成React.createElement()调用,如下两者是完全等同的:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);React.createElement()会做一些检查(比如props类型等)来减少bug,然后生产如下的对象:
// Note: this structure is simplified
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};这些对象叫“React elements”,可以把它看成是对想要展现在屏幕上的内容描述。React读取这些对象,然后用它们构造DOM节点,并保持状态一致。
如果没有子元素的话,可以直接使用闭标签, 比如:
<img src="x.jpg" alt="a pic" />会被翻译成:
React.createElement(
'img',
{src: 'x.jpg', alt: 'a pic'},
null
)你可以使用在线Babel编译器来查看JSX具体编译成什么样的JS。
因为JSX是编译成React.createElement,所以React必须在JSX文件中被import,否则会编译报错。比如下面的例子,虽然没有直接引用React,但是也需要import进来。这是初学者常犯的一个错误,经常出现在stateless component文件中, 因为container component里面需要显示地extends React.Component。
import React from 'React';
import CustomButton from './CustomButton';
function WarningButton() {
// return React.createElement(CustomButton, {color: 'red'}, null);
return <CustomButton color="red" />;
}小写字母开头的组件是built-in的组件,比如<div>或者<span>等。如果你定义了一个小写字母开头的组件,那也可以, 不过在用在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" />;
// 可以这样修复
//const Hello = hello;
//return <Hello toWhat="World" />;
}最好是定义的时候就定义成大写字母开头的:
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" />;
}如果你只有一个moduleexport了很多React components,那么可以用.表达式直接访问,比如:
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" />;
}React组件的类型不能是一个表达式。如果需要根据运行时的状态(比如props的某个值)来渲染不同的组件类型, 可以先把表达式赋值给某个大写字母开头的变量。
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} />;
}这样才可以:
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}JSX中props的值可以是很多种类型。
<MyComponent foo={1 + 2 + 3 + 4} />下面两种写法是一样的, 当然我们一般用第一种,因为简单啊!
<MyComponent message="hello world" />
<MyComponent message={'hello world'} />下面两者等价,不过官方说不推荐第一种,因为容易与ES6 object shorthand的写法{autocomplete}表达的是{autocomplete: autocomplete}混淆,官方提供这种写法只是为了跟HTML保持一致。
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />如果父组件传递的props你想全部传递给子组件,用...展开object操作符会很方便,这个也是ES6的新特性Spread syntax。
function App1(props) {
return <Greeting firstName={props.firstName} lastName={props.lastName} />;
}
function App2(props) {
return <Greeting {...props} />;
}
<App1 props={firstName: 'Ben', lastName: 'Hector'} />
<App2 props={firstName: 'Ben', lastName: 'Hector'} />不过这个功能要慎用,我见过很多人不管父组件传递过来的props包含多少个属性,统统都直接{...props}传递给子组件,而其实父组件传递过来的props可能有10个属性,而子组件只需要1个。而且这样写也看不出来子组件具体需要那几个参数,所以如果不是特别多的话,最好还是显示地写出来传递的props值。记住:
Explicit is better than implicit.。
JSX中在开标签和闭标签之间的内容会通过props.children传递给组件,值的类型有很多种。
<ComponentA>Hello, React!</ComponentA>上述写法ComponentA中可以通过props.children拿到字符串Hello, React!。字符串是unescaped的,所以<>需要转义之后传递<>。
JSX会把下面这些空白去掉:
- 每行开始和结尾的空白
- 空行会去掉
- 挨着标签的换行会去掉
- 字符串中间的很多换行会合并成一个空格
- 字符串中间的空格不会去除,不过HTML显示的时候本身多个空格只会显示一个
所以下面这几种都是一样的, 大家可以合理利用空白来增强代码的阅读性。
<div>Hello World</div>
<div>
Hello World
</div>
<div>
Hello
World
</div>
<div>
Hello World
</div>跟HTML一样,JSX组件可以嵌套,不同种类的子组件也可以混用,比如:
<MyContainer>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<ComponentA>sth...</ComponentA>
<ComponentB />
</MyContainer>比如:
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
<MyComponent>{1 + 2 * 3}</MyComponent>这种方式用来循环渲染列表的时候很适合,如:
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>
);
}JS里面函数是第一等公民,也是可以像数字、字符串一样传递的。当然传递一个函数拿来显示会很奇怪(试一下,发现不会渲染,除非调用toString()转化为String),所以一般传递过去的函数会被调用进行一些转换,转化为React可以渲染的东西。
// 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>
);
}false, null, undefined, 以及***true***都是合法的子元素,但是不会被渲染出来。下面这些JSX渲染出来对的都是一样的: 空元素!
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>这样做条件渲染就比较方便了,下面这样写的话只有showHeader为true的时候<Header />才会被渲染:
<div>
{showHeader && <Header />}
<Content />
</div>注意有些所谓的falsy value,比如0是会被React渲染的。所以下面的例子中,如果messages是空的话,是会显示0的。
<div>
{props.messages.length &&
<MessageList messages={props.messages} />
}
</div>可以把&&之前的表达式变成boolean来修复:
<div>
{props.messages.length > 0 &&
<MessageList messages={props.messages} />
}
</div>如果确实需要显示false, null, undefined以及true的话,需要转化为String。
value is {String(true)}
value is {'' + null}
value is {`${undefined}`}