玩轉 React(四)- 創造一個新的 HTML 標籤

在第二篇文章 《新型前端開發方式》 中有說到 React 有很爽的一點就是給咱們一種創造 HTML 標籤的能力,那麼今天這篇文章就詳細講解下 React 是如何提供這種能力的,做爲前端開發者如何來運用這種能力。javascript

在第三篇文章 《JavaScript代碼裏寫HTML同樣能夠很優雅》 中介紹了 JavaScript 的擴展語法 JSX,相信你們已經知道了,所謂的創造新的 HTML 的能力,其實就是以極其相似 HTML 的 JSX 語法來使用基於 React 編寫的視圖層組件。因此說,要完成今天的任務,咱們只須要搞清楚一個問題便可:如何基於 React 編寫視圖層組件。html

內容摘要

  • 定義組件兩種方式:類繼承組件、函數式組件。
  • 類繼承組件有更豐富的特性,函數式組件書寫更簡潔,執行效率更高。
  • 組件名稱首字母要大寫。
  • 屬性是一個組件的外部輸入。
  • 屬性值能夠經過 {} 設置任意的 JS 表達式。
  • 屬性是隻讀的。
  • 屬性能夠設置默認值。
  • 屬性能夠設置類型,開發階段 React 會對屬性進行類型檢查。
  • 爲組件全部屬性設置類型檢查是個好習慣,有助於協做開發。

經過內容摘要可讓你快速瞭解本文內容是否對你有用,從而決定是否繼續閱讀,節省你的時間也是一件頗有意義的事情。前端

定義組件的幾種姿式

下面介紹一下在 React 中定義組件的幾種方式。java

1. 類繼承

有過 Java 等面向對象開發經驗的同窗必定很容易接受這種方式。ES6 爲 JavaScript 增長了類和類繼承的特性。子類會繼承父類的「基因」(成員方法、屬性),若是父類是一個組件,那子類天然而然也是一個組件。node

React 提供了 ComponentPureComponent 兩個父類,他們之間有一點點區別,咱們在以後的文章中會詳細介紹,如今你能夠將他們同等看待,暫且無須理會。react

經過繼承自 React 提供的組件基類,咱們能夠這樣來建立一個組件:segmentfault

import React, {Component} from 'react';

class HelloMessage extends Component {
    render() {
        return <div>Hello world.</div>;
    }
}

經過類繼承的方式建立一個組件,就是這麼簡單,只要繼承 Component 基類並實現 render 方法便可。而後就能夠把 HelloMessage 當成一個新的「HTML 標籤」來用了,以下你能夠把它渲染到頁面上:後端

ReactDOM.render(<HelloMessage />, document.querySelector('#root'));

你也能夠用它來裝配其它組件,如:數組

import React, {Component} from 'react';

class HelloMessageList extends React.Component {
    render() {
        return (
            <div>
                <HelloMessage />
                <HelloMessage />
                <HelloMessage />
            </div>
        )
    }
}

固然,例子沒有任何實際意義,只是爲了演示組件的定義及其用法。dom

演示代碼:https://codepen.io/Sarike/pen...

2. 函數式組件

顧名思義,函數式組件,就是以函數的形式來定義一個組件,以下所示:

import React from 'react';

function HelloMessage() {
    return <div>Hello world.</div>;
}

// 或者:

const HelloMessage = () => <div>Hello world.</div>;

實際上就是隻實現了類繼承方式中的 render 方法。

示例代碼:https://codepen.io/Sarike/pen...

類繼承 vs 函數式組件

這兩種定義組件的方式,在實際的開發中都常常會被用到,對大部分人來講類繼承的方式用得頻率會更高一些。

類繼承的方式,相較於函數式組件,雖然寫起來略繁瑣,可是它擁有更多的特性:

  • 內部狀態:state
  • 生命週期函數

函數式組件雖然沒有 state 和生命週期函數等特性,可是它有更簡潔的書寫方式,另外還有更好的性能,不用處理一些複雜的特性,執行效率固然高了。

如今你能夠無需關心 state 和生命週期函數的具體做用,下一篇文章我會詳細講解,等你看完下一篇文章以後,至於選擇哪一種方式的問題就很好解決了。在開發一個組件的時候,我是這樣來作的:當我一開始就知道這個組件會用到 state 或者生命週期函數時,毫無疑問直接使用類繼承的方式;若是一開始用不到這些特性也不肯定將來會不會用到,那我就先用函數式組件,若是隨着業務的演進,組件須要應用這些特性的時候,我會再把它重構成類繼承的方式。這個重構很是簡單,只須要將原來的函數變成組件類的 render 方法便可。

另外,還有一點須要注意,無論那種方式,組件的名稱首字母必須爲大寫。嚴格來講,是 JSX 要求用戶自定義的組件名首字母必須爲大寫,若是是小寫字母開頭,那麼 React 會將其當成內置的組件直接將其渲染成一個 html 標籤,從而不會正確渲染用戶自定義的組件。

若是你非要將組件名稱以小寫字母開頭,那你在以 JSX 語法使用以前也必須將其賦值爲一個大寫字母開頭的變量,以下所示:

function helloMessage() {
    return <div>Hello world.</div>
}

const HelloMessage = helloMessage;

ReactDOM.render(<HelloMessage />, mountNode);

但這有事何須呢,純粹是沒事兒找事兒,你們在實際項目開發時,直接將組件名以大寫字母開頭便可。

屬性

上面說完了在 React 中兩種定義組件的方式。在上面的例子中,咱們定義的組件都是靜態的,然而在實際的開發中,視圖層組件每每會進行頻繁更新,或者須要從後端 API 獲取動態數據在組件中展現。這就須要組件擁有接收外部輸入的能力。

屬性是組件的輸入

在第二篇文章 《新型前端開發方式》 中有說到 「視圖是數據的映射」,那麼其中說的數據指的就是屬性。

若是把組件理解爲一個函數,那麼屬性就是這個函數的參數,函數的返回值就是呈現到頁面上的視圖。並且經過上面部分的學習,在 React 中組件確實能夠以函數的形式來定義,並且函數的參數就是一個包含當前組件接收到的全部屬性的對象。

以下所示帶有屬性 name 的組件定義:

import React, {Component} from 'react';

class HelloMessage extends Component {
    render() {
        return <div>Hello {this.props.name}.</div>;
    }
}

函數式:

import React from 'react';

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

// 或者:

const HelloMessage = props => <div>Hello {props.name}.</div>;

屬性的傳遞也跟 HTML 同樣(在本文的最後一部分會有各類類型屬性的詳細介紹),以下所示:

import React, {Component} from 'react';
import ReactDOM from 'react-dom';

class HelloMessageList extends Component {
    render() {
        return (
            <div>
                <HelloMessage name="Lucy" />
                <HelloMessage name="Tom" />
                <HelloMessage name="Jack" />
            </div>
        )
    }
}

ReactDOM.render(<HelloMessageList />, document.querySelector('#root'));

這樣頁面上會展現出:

Hello Lucy.
Hello Tom.
Hello Jack.

示例代碼:https://codepen.io/Sarike/pen...

屬性必須爲只讀的

屬性必須爲只讀的,這一點很是重要,請嚴格遵照。對應到上面說到的,若是把組件理解爲一個函數,那麼這個函數必須爲一個純函數(Pure function),在純函數中不能修改其參數,肯定的輸入必須有肯定的輸出。

雖然有些時候,你修改了組件的屬性,貌似也能正常工做。沒錯,由於 JavaScript 語言特性的緣由,沒人能阻止你這麼作。可是請先相信我,嚴格遵照這條規則不只能讓你少踩不少坑,並且能讓你的應用穩定性更強、維護性更強。若是你直接修改組件的屬性,React 並不會感知到此修改,從而不會從新渲染組件,就致使了當前組件的視圖展現與數據不一致,但這個被修改的屬性會隨着下一次組件的渲染被生效到視圖上,並且此次渲染的時機是不肯定的,不難想象,若是一個規模較大的項目裏充滿了這種不肯定性是多麼痛苦的一件事情。總之,若是你隨意修改組件的屬性,會很容易讓你的應用充滿許多難以排查的 BUG。

默認屬性

一般狀況下,咱們須要爲組件的屬性設爲默認值。就像 HTML 標籤的屬性也有默認值同樣,例如 form 標籤的 method 屬性默認值是 GET,input 標籤的 type 屬性默認值是 text 同樣。

仍是上面 HelloMessage 組件,若是需求是當不傳入 name 屬性時,默認展現 Hello World.,也就是說 name 屬性的默認值是 World。

一種很容易想到的作法:

<div>Hello {this.props.name || 'World'}.</div>

這樣確實能夠解決當前這個需求,可是屬性可能還會是個 Object,也多是個函數,你固然能夠先判斷下這個屬性是否爲 undefined 而後決定是否使用默認值,可是這樣會讓代碼顯得很不優雅,並且也會增長不少繁瑣的判斷邏輯。

所以,React 提供了相應的機制能夠設置組件屬性的默認值,以下所示,你須要經過組件的靜態字段 defaultProps 來設置組件屬性的默認值。以下所示:

import React, {Component} from 'react';

class HelloMessage extends Component {
    render() {
        return <div>Hello {this.props.name}.</div>;
    }
}
HelloMessage.defaultProps = {
    name: 'World'
}

這樣就能夠了,<HelloMessage /> 這樣不爲組件設置任何屬性,那麼它就會在頁面上展現Hello World.

示例代碼:https://codepen.io/Sarike/pen...

屬性的類型及校驗

在開發較複雜的前端應用時,咱們常常會遇到許多由於類型檢查致使的問題,例如上面的 HelloMessage 組件,我指望其 name 屬性只能是字符串類型的,你要是給我一個 Object,我是無法正確展現的。爲了在開發過程當中儘快的發現這類問題,React 爲組件添加了類型檢查的機制,你須要給組件設置靜態字段 propTypes 來設置組件各個屬性的類型檢查器。

import React, {Component} from 'react';
import PropTypes from 'prop-types';

class HelloMessage extends Component {
    render() {
        return <div>Hello {this.props.name}.</div>;
    }
}
HelloMessage.defaultProps = {
    name: 'World'
}
HelloMessage.propTypes = {
    name: PropTypes.string
}

這樣在開發過程當中 React 就能校驗組件接收到的屬性值是否符合指定的類型,若是校驗不經過,將會拋出警告。React 只會在開發模式下進行屬性類型檢查,當代碼進行生產發佈後,爲了減小額外的性能開銷,類型檢查將會被略過。

其實,爲每個組件編寫完善的屬性類型是一個很是好的習慣,這不只能及時發現問題,更重要的是配合幾句簡單額註釋,這將成爲該組件一份很是好的文檔,一個完善的組件應該具備良好的封裝性和易複用性,在一個協做開發的項目中,其餘開發者須要引用你開發的組件時,只須要看一下組件的屬性列表,大體就能夠了解如何來使用這個組件,省去了不少沒必要要的溝通。

下面是 React 提供的可用的數據類型檢查器。

  • PropTypes.array
  • PropTypes.bool
  • PropTypes.func
  • PropTypes.number
  • PropTypes.object
  • PropTypes.string
  • PropTypes.symbol
  • PropTypes.element 元素,其實就是 JSX 表達式,上一篇文章有說過 JSX 是 React.createElement 的語法糖,一個 JSX 表達式實際上會生成一個 JS 對象,在 React 中稱之爲元素(Element)。
  • PropTypes.node 全部能夠被渲染的數據類型,包括:數值, 字符串, 元素或者這些類型的數組。
  • PropTypes.instanceOf(Message) 某個類的實例
  • PropTypes.oneOf(['News', 'Photos']) 枚舉,屬性值必須爲其中的某一個值。
  • PropTypes.oneOfType([PropTypes.string, PropTypes.number]) 類型枚舉,屬性必須爲其中某一個類型。
  • PropTypes.arrayOf(PropTypes.number) 屬性爲一個數組,且數組中的元素必須符合指定類型。
  • PropTypes.objectOf(PropTypes.number) 屬性爲一個對象,且對象中的各個字段的值必須符合指定類型。
  • PropTypes.any 任何類型

若是你想指定某些屬性爲必需屬性,你能夠鏈式調動其 isRequired 來標識某個屬性對於當前組件來講是必需的。若是在使用組件時未指定則會拋出警告提醒。

另外你還能夠經過一個函數自定義屬性驗證器,若是驗證不經過你須要返回一個 Error 實例,以下所示:

function(props, propName, componentName) {
  if (!/matchme/.test(props[propName])) {
    return new Error(
      'Invalid prop `' + propName + '` supplied to' +
      ' `' + componentName + '`. Validation failed.'
    );
  }
}

設置組件的屬性值

上面我們瞭解到組件的屬性有不少種類型,下面說一下各類類型的屬性是如何傳遞給組件的。其實很簡單,屬性的值能夠用一對大括號 { } 來包圍,其中能夠指定任意的 JavaScript 表達式。以下所示:

return (
    <User
        name="Tom"                            // 字符串
        age={18}                              // 數值
        isActivated={true}                    // 布爾值
        interests={['basketball', 'music']}   // 數組
        address={{ city: 'Beijing', road: 'BeiWuHuan' }} // 對象
    />
)

展開操做符

你也能夠用展開操做符 ... 將一個對象的全部字段展開,依次做爲屬性傳遞給組件,上面的代碼等價於:

const userInfo = {
    name: 'Tom',
    age: 18,
    isActivated: true,
    interests: ['basketball', 'music'],
    address: { city: 'Beijing', road: 'BeiWuHuan' }
}
return <User {...userInfo} />

值爲 true 的屬性的簡寫

若是是屬性類型爲布爾值,且當前屬性值爲 true 能夠只寫屬性名,以下所示:

<input
    disabled     // 禁用該輸入框
    type="text"
/>

children 屬性

用戶自定義的組件內能夠經過 this.props.children 來獲取一個特殊的屬性。該屬性與其它屬性的區別就是傳遞方式不一樣。

children 屬性的值是指一對閉合的 JSX 標籤中間的內容,以下所示:

<UserList>
    <User name="Tom" />
    <User name="Lucy" />
</UserList>

那麼在 UserList 內部能夠經過 this.props.children 來獲取下面這個 JSX 片斷:

<User name="Tom" />
<User name="Lucy" />

該示例中,獲取到的其實是一個包含兩個 User 元素對象的數組。

總結

本文主要介紹了在 React 中組件的定義方式,以及幾個關鍵的注意事項。另外介紹了組件屬性的做用、屬性默認值、屬性類型校驗以及如何爲組件傳遞屬性。

但願內容對你們有用,若有任何問題和建議能夠給我留言,謝謝。

相關文章
相關標籤/搜索