React入门教程(7)JSX高级与PropTypes检查

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库必须也始终在作用域中。

比如,下面两个导入都是必须的,尽管 ReactCustomButton 都没有在代码中被直接调用。

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="&lt;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 ...otherobject 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,并且 MyComponentprops.children 值将会直接是 "hello world!"。因为 HTML 未转义,所以你可以像写 HTML 一样写 JSX:

<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 元素作为子代,这对于嵌套显示组件非常有用:

<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 被忽略

falsenullundefinedtrue 都是有效的子代,只是它们不会被渲染。下面的JSX表达式将渲染为相同的东西:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

这在根据条件来确定是否渲染React元素时非常有用。以下的JSX只会在showHeadertrue时渲染<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>

相反,如果你想让类似 falsetruenullundefined 出现在输出中,你必须先把它转换成字符串 :

<div>
  My JavaScript variable is {String(myVariable)}.
</div>

使用 PropTypes 进行类型检查

使用 prop-types 库帮助我们对组件的属性进行类型的控制。

随着应用日渐庞大,你可以通过类型检查捕获大量错误。 对于某些应用来说,你还可以使用 FlowTypeScript 这样的 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
};

属性默认值

你可以通过配置 defaultPropsprops定义默认值:

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 上面。

参考

  1. 官网文档
  2. 老马React视频地址: https://ke.qq.com/course/379234?tuin=1eb4a0a4
  3. AICODER官网地址:https://www.aicoder.com/
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 以下内容是我在学习和研究React时,对React的特性、重点和注意事项的提取、精练和总结,可以做为React特性...
    科研者阅读 8,219评论 2 21
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,048评论 2 35
  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,422评论 1 33
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,806评论 0 24
  • GUIDS 第一章 为什么使用React? React 一个提供了用户接口的JavaScript库。 诞生于Fac...
    jplyue阅读 3,519评论 1 11