React入門教程(7)JSX高級與PropTypes檢查

JSX高級

本質(zhì)上來講,JSX 只是為 React.createElement(component, props, ...children) 方法提供的語法糖。比如下面的代碼:

<MyButton color="blue" shadowSize=>
  Click Me
</MyButton>

編譯為:

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

如果沒有子代,你還可以使用自閉合標(biāo)簽,比如:

<div className="sidebar" />

編譯為:

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

如果你想徹底驗(yàn)證 JSX 是如何轉(zhuǎn)換為 JavaScript 的,你可以嘗試 在線 Babel 編譯器.

指定 React 元素類型

JSX 的標(biāo)簽的第一部分決定了 React 元素的類型。

首字母大寫的類型表示 JSX 標(biāo)簽引用到一個(gè) React 組件。這些標(biāo)簽將會(huì)被編譯為直接引用同名變量,所以如果你使用了 <Foo /> JSX 表達(dá)式,則 Foo 必須在作用域中。

React 必須在作用域中

由于 JSX 編譯成React.createElement方法的調(diào)用,所以在你的 JSX 代碼中,React庫必須也始終在作用域中。

比如,下面兩個(gè)導(dǎo)入都是必須的,盡管 ReactCustomButton 都沒有在代碼中被直接調(diào)用。

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

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

如果你沒有使用JavaScript 打捆機(jī),而是從<script>標(biāo)簽加載React,它已經(jīng)在作用域中,以React全局變量的形式。

點(diǎn)表示法用于JSX類型

你還可以使用 JSX 中的點(diǎn)表示法來引用 React 組件。你可以方便地從一個(gè)模塊中導(dǎo)出許多 React 組件。例如,有一個(gè)名為 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" />;
}

用戶定義組件必須首字母大寫

當(dāng)元素類型以小寫字母開頭時(shí),它表示一個(gè)內(nèi)置的組件,如 <div><span>,將導(dǎo)致字符串 'div''span' 傳遞給 React.createElement。 以大寫字母開頭的類型,如 <Foo /> 編譯為 React.createElement(Foo),并且它正對應(yīng)于你在 JavaScript 文件中定義或?qū)氲慕M件。

我們建議用大寫開頭命名組件。如果你的組件以小寫字母開頭,請?jiān)?JSX 中使用之前其賦值給大寫開頭的變量。

例如,下面的代碼將無法按預(yù)期運(yùn)行:

import React from 'react';

// 錯(cuò)誤!組件名應(yīng)該首字母大寫:
function hello(props) {
  // 正確!div 是有效的 HTML 標(biāo)簽:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 錯(cuò)誤!React 會(huì)將小寫開頭的標(biāo)簽名認(rèn)為是 HTML 原生標(biāo)簽:
  return <hello toWhat="World" />;
}

為了解決這個(gè)問題,我們將 hello 重命名為 Hello,然后使用 <Hello /> 引用:

import React from 'react';

// 正確!組件名應(yīng)該首字母大寫:
function Hello(props) {
  // 正確!div 是有效的 HTML 標(biāo)簽:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 正確!React 能夠?qū)⒋髮戦_頭的標(biāo)簽名認(rèn)為是 React 組件。
  return <Hello toWhat="World" />;
}

在運(yùn)行時(shí)選擇類型

你不能使用一個(gè)通用的表達(dá)式來作為 React 元素的標(biāo)簽。如果你的確想使用一個(gè)通用的表達(dá)式來確定 React 元素的類型,請先將其賦值給大寫開頭的變量。這種情況一般發(fā)生于當(dāng)你想基于屬性值渲染不同的組件時(shí):

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 錯(cuò)誤!JSX 標(biāo)簽名不能為一個(gè)表達(dá)式。
  return <components[props.storyType] story={props.story} />;
}

要解決這個(gè)問題,我們需要先將類型賦值給大寫開頭的變量。

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 正確!JSX 標(biāo)簽名可以為大寫開頭的變量。
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

JSX的屬性(Props)

在 JSX 中有幾種不同的方式來指定屬性。

使用 JavaScript 表達(dá)式作為屬性

你可以傳遞JavaScript 表達(dá)式作為一個(gè)屬性,再用大括號(hào){}括起來。例如,在這個(gè) JSX 中:

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

對于 MyComponent來說, props.foo 的值為 10,這是 1 + 2 + 3 + 4 表達(dá)式計(jì)算得出的。

if 語句和 for 循環(huán)在 JavaScript 中不是表達(dá)式,因此它們不能直接在 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>;
}

你可以在相關(guān)部分中了解有關(guān) 條件渲染循環(huán) 的更多信息。

字符串常量

你可以將字符串常量作為屬性值傳遞。下面這兩個(gè) JSX 表達(dá)式是等價(jià)的:

<MyComponent message="hello world" />

<MyComponent message={'hello world'} />

當(dāng)傳遞一個(gè)字符串常量時(shí),該值為HTML非轉(zhuǎn)義的,所以下面兩個(gè) JSX 表達(dá)式是相同的:

<MyComponent message="&lt;3" />

<MyComponent message={'<3'} />

這種行為通常是無意義的,提到它只是為了完整性。

屬性默認(rèn)為“True”

如果你沒有給屬性傳值,它默認(rèn)為 true。因此下面兩個(gè) JSX 是等價(jià)的:

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

一般情況下,我們不建議這樣使用,因?yàn)樗鼤?huì)與 ES6 對象簡潔表示法 混淆。比如 {foo}{foo: foo} 的簡寫,而不是 {foo: true}。這里能這樣用,是因?yàn)樗?HTML 的做法。

展開屬性

如果你已經(jīng)有了個(gè) props 對象,并且想在 JSX 中傳遞它,你可以使用 ... 作為“展開(spread)”操作符來傳遞整個(gè)屬性對象。下面兩個(gè)組件是等效的:

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.

展開屬性非常有用。但是他們也容易傳遞不必要的屬性給組件,而組件并不需要這些多余屬性?;蛘邆鬟f無效的HTML熟悉給DOM。我們建議你謹(jǐn)慎使用此語法。

JSX中的子代

在既包含開始標(biāo)簽又包含結(jié)束標(biāo)簽的 JSX 表達(dá)式中,這兩個(gè)標(biāo)簽之間的內(nèi)容被傳遞為專門的屬性:props.children。有幾種不同的方法來傳遞子代:

字符串字面量

你可以在開始和結(jié)束標(biāo)簽之間放入一個(gè)字符串,則 props.children 就是那個(gè)字符串。這對于許多內(nèi)置 HTML 元素很有用。例如:

<MyComponent>Hello world!</MyComponent>

這是有效的 JSX,并且 MyComponentprops.children 值將會(huì)直接是 "hello world!"。因?yàn)?HTML 未轉(zhuǎn)義,所以你可以像寫 HTML 一樣寫 JSX:

<div>This is valid HTML &amp; JSX at the same time.</div>

JSX 會(huì)移除空行和開始與結(jié)尾處的空格。標(biāo)簽鄰近的新行也會(huì)被移除,字符串常量內(nèi)部的換行會(huì)被壓縮成一個(gè)空格,所以下面這些都等價(jià):

<div>Hello World</div>

<div>
  Hello World
</div>

<div>
  Hello
  World
</div>

<div>

  Hello World
</div>

JSX子代

你可以提供更多個(gè) JSX 元素作為子代,這對于嵌套顯示組件非常有用:

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

你可以混合不同類型的子代,同時(shí)使用字符串字面量和 JSX子代,這是 JSX 類似 HTML 的另一種形式,這在 JSX 和 HTML 中都是有效的:

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

React 組件也可以返回包含多個(gè)元素的一個(gè)數(shù)組:

render() {
  // 不需要使用額外的元素包裹數(shù)組中的元素!
  return [
    // 不要忘記 key :)
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

JavaScript 表達(dá)式作為子代

你可以將任何 {} 包裹的 JavaScript 表達(dá)式作為子代傳遞。例如,下面這些表達(dá)式是等價(jià)的:

<MyComponent>foo</MyComponent>

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

這對于渲染任意長度的 JSX 表達(dá)式的列表很有用。例如,下面將會(huì)渲染一個(gè) 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 表達(dá)式可以與其他類型的子代混合使用。這通常對于字符串模板非常有用:

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

布爾值、Null 和 Undefined 被忽略

false、nullundefinedtrue 都是有效的子代,只是它們不會(huì)被渲染。下面的JSX表達(dá)式將渲染為相同的東西:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

這在根據(jù)條件來確定是否渲染React元素時(shí)非常有用。以下的JSX只會(huì)在showHeadertrue時(shí)渲染<Header />組件。

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

一個(gè)告誡是JavaScript中的一些 "falsy" 值(比如數(shù)字0),它們依然會(huì)被React渲染。例如,下面的代碼不會(huì)像你預(yù)期的那樣運(yùn)行,因?yàn)楫?dāng) props.message 為空數(shù)組時(shí),它會(huì)打印0:

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

要解決這個(gè)問題,請確保 && 前面的表達(dá)式始終為布爾值:

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

相反,如果你想讓類似 false、true、nullundefined 出現(xiàn)在輸出中,你必須先把它轉(zhuǎn)換成字符串 :

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

使用 PropTypes 進(jìn)行類型檢查

使用 prop-types 庫幫助我們對組件的屬性進(jìn)行類型的控制。

隨著應(yīng)用日漸龐大,你可以通過類型檢查捕獲大量錯(cuò)誤。 對于某些應(yīng)用來說,你還可以使用 FlowTypeScript 這樣的 JavsScript 擴(kuò)展來對整個(gè)應(yīng)用程序進(jìn)行類型檢查。然而即使你不用它們,React 也有一些內(nèi)置的類型檢查功能。要檢查組件的屬性,你需要配置特殊的 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 包含一整套驗(yàn)證器,可用于確保你接收的數(shù)據(jù)是有效的。在這個(gè)示例中,我們使用了 PropTypes.string。當(dāng)你給屬性傳遞了無效值時(shí),JavsScript 控制臺(tái)將會(huì)打印警告。出于性能原因,propTypes只在開發(fā)模式下進(jìn)行檢查。

PropTypes

下面是使用不同驗(yàn)證器的例子:

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,

  // 任何可被渲染的元素(包括數(shù)字、字符串、子元素或數(shù)組)。
  optionalNode: PropTypes.node,

  // 一個(gè) React 元素
  optionalElement: PropTypes.element,

  // 你也可以聲明屬性為某個(gè)類的實(shí)例,這里使用 JS 的
  // instanceof 操作符實(shí)現(xiàn)。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你也可以限制你的屬性值是某個(gè)特定值之一
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 限制它為列舉類型之一的對象
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 一個(gè)指定元素類型的數(shù)組
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 一個(gè)指定類型的對象
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 一個(gè)指定屬性及其類型的對象
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 你也可以在任何 PropTypes 屬性后面加上 `isRequired` 
  // 后綴,這樣如果這個(gè)屬性父組件沒有提供時(shí),會(huì)打印警告信息
  requiredFunc: PropTypes.func.isRequired,

  // 任意類型的數(shù)據(jù)
  requiredAny: PropTypes.any.isRequired,

  // 你也可以指定一個(gè)自定義驗(yàn)證器。它應(yīng)該在驗(yàn)證失敗時(shí)返回
  // 一個(gè) 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.'
      );
    }
  },

  // 不過你可以提供一個(gè)自定義的 `arrayOf` 或 `objectOf` 
  // 驗(yàn)證器,它應(yīng)該在驗(yàn)證失敗時(shí)返回一個(gè) Error 對象。 它被用
  // 于驗(yàn)證數(shù)組或?qū)ο蟮拿總€(gè)值。驗(yàn)證器前兩個(gè)參數(shù)的第一個(gè)是數(shù)組
  // 或?qū)ο蟊旧恚诙€(gè)是它們對應(yīng)的鍵。
  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.'
      );
    }
  })
};

限制單個(gè)子代

使用 PropTypes.element 你可以指定只傳遞一個(gè)子代

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

屬性默認(rèn)值

你可以通過配置 defaultPropsprops定義默認(rèn)值:

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

// 為屬性指定默認(rèn)值:
Greeting.defaultProps = {
  name: 'Stranger'
};

// 渲染 "Hello, Stranger":
ReactDOM.render(
  <Greeting />,
  document.getElementById('example')
);

如果你在使用像 transform-class-properties 的 Babel 轉(zhuǎn)換器,你也可以在React 組件類中聲明 defaultProps 作為靜態(tài)屬性。這個(gè)語法還沒有最終通過,在瀏覽器中需要一步編譯工作。更多信息,查看類字段提議。

class Greeting extends React.Component {
  static defaultProps = {
    name: 'stranger'
  }

  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

defaultProps 用來確保 this.props.name 在父組件沒有特別指定的情況下,有一個(gè)初始值。類型檢查發(fā)生在 defaultProps 賦值之后,所以類型檢查也會(huì)應(yīng)用在 defaultProps 上面。

參考

  1. 官網(wǎng)文檔
  2. 老馬React視頻地址: https://ke.qq.com/course/379234?tuin=1eb4a0a4
  3. AICODER官網(wǎng)地址:https://www.aicoder.com/
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 以下內(nèi)容是我在學(xué)習(xí)和研究React時(shí),對React的特性、重點(diǎn)和注意事項(xiàng)的提取、精練和總結(jié),可以做為React特性...
    科研者閱讀 8,396評論 2 21
  • 深入JSX date:20170412筆記原文其實(shí)JSX是React.createElement(componen...
    gaoer1938閱讀 8,175評論 2 35
  • 作為一個(gè)合格的開發(fā)者,不要只滿足于編寫了可以運(yùn)行的代碼。而要了解代碼背后的工作原理;不要只滿足于自己的程序...
    六個(gè)周閱讀 8,664評論 1 33
  • 3. JSX JSX是對JavaScript語言的一個(gè)擴(kuò)展語法, 用于生產(chǎn)React“元素”,建議在描述UI的時(shí)候...
    pixels閱讀 2,971評論 0 24
  • GUIDS 第一章 為什么使用React? React 一個(gè)提供了用戶接口的JavaScript庫。 誕生于Fac...
    jplyue閱讀 3,700評論 1 11

友情鏈接更多精彩內(nèi)容