React 深入说明JSX语法与Props特性

JSX说明

我们可以将JSX理解为React.createElement(component, props, ...children)方法的语法糖。JSX的代码:

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

最终会被编译成一个React Element 对象:

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

我们可以使用“闭标签”来表示没有子元素的情况:

<div className="sidebar" />

它会编译成:

React.createElement(
  'div',
  {className: 'sidebar'},
  null
)

如果你想尝试各种JSX是如何转换成JavaScript代码的,你可以打开这个网站试试:the online Babel compiler

React组件的作用域

JSX标签声明的第一个部分是React元素的类型(Type)。首字母大写表明这个JSX标签是一个React的组件。这些标签会被编译成对命名变量的直接引用,因此如果你使用JSX的<Foo />表达式,那么Foo方法或对象必须包含在当前域中(可以理解在当前页面或闭包中可以找到这个对象)。

import React from 'react';
import Foo from './Foo'; //ES6的import语法,必须现在闭包中引入才能使用

React的作用域

因为JSX需要调用React.createElement来进行编译,因此在使用JSX表达式时,React应该始终被引用到当前域中(可以理解为页面或闭包可以访问到React.createElement)。

如下面代码的例子,即使没有显示的使用React.createElement方法,但是在使用任何React组建时,React和组件都必须在使用时被引入:

import React from 'react';
import CustomButton from './CustomButton';

function WarningButton() {
  // return React.createElement(CustomButton, {color: 'red'}, null);
  return <CustomButton color="red" />;
}

利用点号“.”来引用组件

在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方法,最终执行React.createElement('div')。而如果以大写字母开头,例如<Foo />,则会转译成一个对象作为参数传递,最终执行的方法是React.createElement(Foo)。

我们推荐在命名自定义组件时将首字母大写。如果不得不将自定义组件的首字母设置为小写字母,那么在使用JSX之前将其赋值给大写的变量。

下面的代码将不会按照预计执行:

import React from 'react';

// 错误!自定义组件首字母大写
function hello(props) {
  // 正确!<div>是一个HTML标签
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 错误!因首字母没有大写,React会认为<hello>是一个HTML标签:
  return <hello toWhat="World" />;
}

我们必须修改为:

import React from 'react';

function Hello(props) {
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  return <Hello toWhat="World" />;
}

在运行时确定类型

由于JavaScript的语言特性,我们可以在运行时再确定类型。但是我们不能将这个常规的经验应用在JSX表达式中。不过我们可以在JSX表达式之外去确定“运行时类型”,只要将JSX表达式赋值给一个大写变量即可。例子:

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} />;
}

这样写适用于我们基于某些条件来决定使用某个组件的场景。

使用Prop传递JSX参数

JavaScript表达式

可以传递任何JavaScript表达式作为props参数,JSX中嵌套的表达式要用{}包裹住。例如:

<MyComponent foo={1 + 2 + 3 + 4} />

MyComponent组件最终传入的参数是props.foo = 10,因为在传入参数之前“1 + 2 + 3 + 4”这个表达式已经先完成了计算。

在JSX的{}中不能使用for等循环表达式。可以在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>;
}

字符串文字

也可以直接使用字符串作为一个参数传递,下面的表达式是一样的效果:

//直接使用字符串
<MyComponent message="hello world" />

//在JavaScript表达式中字符串作为一个参数传入
<MyComponent message={'hello world'} />

如果直接传递一个字符串,它将会被解析成未转义的HTML语法,比如下面的2个表达式会得带一样的结果:

//传入字符串
<MyComponent message="&lt;3" />

//通过JavaScript语法传入变量
<MyComponent message={'<3'} />

Prop参数默认为"True"

如果传递了没有数据的prop参数,它的值默认为true。因此一下2个表达式完全一样:

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

通常情况下不推荐像上面这样使用,因为这会和ES6的简写语法混淆——{foo}是{foo:foo}的简写而不是{foo:true}。提供这个特性仅仅是因为很像HTML语法。

属性扩展传递(Spread 特性)

如果已经有一个类型为object的props,并且想将这个props传递给JSX。可以使用ES6的“...”语法来扩展传递整个参数。下面的表达式是一样的效果:

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}

属性扩展传递是一个非常有用的特性,尤其是当参数可变时。然而这个特性也会使得代码混乱并且传递一些无关紧要的参数到组件中,建议谨慎使用这个特性。

JSX中的子标签

JSX表达式既可以使用开放型标签页也可以使用封闭型标签(例如 开放型标签:<div></div>。封闭型标签:<img />)。开放型标签中的内容会通过props.children传递到组件中。

传递字符串

可以在开放标签之间传递一个字符串,然后在组件中通过props.children获取的数据就是一个字符串。这对于许多内置的HTML标签很有用。例如:

<MyComponent>Hello world!</MyComponent>

在组件“MyComponent”中通过props.children可以获取到"Hello world!"字符串。你只需要按照需求编写字符串而不必考虑HTML的为转移特性, 因此你们这样写JSX来影响HTML代码:

<div>This is valid HTML &amp; 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的开放标签中间,你可以设置多个子标签,这些标签的内容都可以通过props.children获取:

<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

也可以同时使用多种类型的子元素,这一点JSX和HTML几乎一模一样,我们可以把JSX的解析过程看成一个HTML,例如:

<div>
  Here is a list:
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</div>

一个React组件不能一次返回多个React元素,但是一条独立的JSX表达式可以包含多个子元素,因此,我们可以使用一个外层标签来包裹子元素实现一个React组件渲染多个节点。

JavaScript表达式作为子元素

在JSX的子元素中,你也可以使用JavaScript表达式,JSX使用{}来表示要执行一段JavaScript语句。例如下面的2个表达式执行完毕后是一样的效果:

<MyComponent>foo</MyComponent>

<MyComponent>{'foo'}</MyComponent>

在开发过程中,我们经常会遇到需要渲染一个JSX表达式列表的情况,我们可以直接将迭代语句嵌入到子元素中去处理,例如:

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>;
}

Function作为子元素

通常情况下,将JavaScript表达式嵌入到JSX中将会被成一段字符串、一个React元素或者一个包含字符串和React元素的列表。然而,props.chilidren和其他props参数一样,它可以传递任何类型的数据而不仅仅是React知晓的类型。例如,自定义自建Repeat,子元素将接收到一个方法列表,在Repeat逐一执行每个方法:

// prop.children会接收一个方法列表,每个方法将会被逐一调用。
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

// numTimes传递的是循环的次数,而子元素则是一系列方法。会在Repeat组件中被执行。
function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

props.children可以传递任意参数给自定义的组件,只要在React发生渲染之前处理成React可以理解的表达式即可,这样可以极大的延伸JSX的灵活性。

Booleans, Null, and Undefined被忽略

falsenullundefined, and true 都是有效的元素,它们在表达式中的含义为“不需要渲染”。下面的表达式都会得到同样的结果:   

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

这样的特性有利于编写各种条件表达式。例如下面的例子,只有当showHeadertrue时才会渲染<Header />元素:

<div>
  {showHeader && <Header />}
  <Content />
</div>

需要特别说明的是falsy值(参看mozilla官文说明),当变量值为数字型的0时,React还是会将其渲染的。下面的代码当 props.messages.length结果为0时,依然会发生渲染:

<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

需要始终保持&&之前的表达式结果都是boolean类型,所以为了得到正确的结果,我们需要将表达式调整为:

<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>

最后,如果想要将 falsetruenull, or undefined 这些输出到组件中,需要将他们转换成字符串(说明):

<div>
  My JavaScript variable is {String(myVariable)}.
</div>
上一篇:哟哟哟,JAVA组装的聊天室,最简单的实现


下一篇:ThinkPHP3.2.3 where注入