[譯]深刻解讀 React 核心之組件篇

引言

本系列涵蓋了使用 React 的全部知識,分爲上、中、下三篇。此爲中篇,本篇主講 React 組件。前端

本系列涵蓋 React v16.9,但更多的是 React 全面解析,具體 React v16.9 新特性可查看 [譯]React v16.9 新特性react

完整系列包含:git

5、React 核心是組件

在 React 中,咱們使用組件(有狀態、可組合、可重用)來描述 UI 。 在任何編程語言中,你均可以將組件視爲簡單的函數。瀏覽器

React 組件也同樣, 它的輸入是 props,輸出是關於 UI 的描述。咱們能夠在多個 UI 中重用單個組件,組件也能夠包含其餘組件。React 組件的本質上就是一個普通的 JavaScript 函數。 儘管一些 React 組件是純組件,但也能夠在組件中引入反作用。例如,組件在瀏覽器中渲染時可能會更改網頁的標題,或者可能會將瀏覽器視圖滾動到某個位置。 最重要的是,React 組件能夠擁有一個私有狀態來保存在組件生命週期內可能發生變化的數據。這個私有狀態驅動組件輸出到原生 DOM 中!app

爲何將 React 稱爲響應式設計?框架

當 React 組件的狀態(它是其輸入的一部分)發生更改時,它所表明的 UI (其輸出)也會發生更改。UI 描述中的這種變化必須反映在咱們正在使用的設備中。在瀏覽器中,咱們須要更新 DOM 樹。在 React 應用程序中,咱們不會手動執行此操做。 state 更新時,React 自動響應,並在須要時自動(並有效)更新到 DOM 上。dom

6、函數組件

React 組件,最簡單的形式就是 JavaScript 函數:

function Button (props) {
  // 在這裏返回一個DOM / React元素。例如:
  return <button type="submit">{props.label}</button>;
}

// 在瀏覽器中渲染一個 Button 元素 
ReactDOM.render(<Button label="Save" />, mountNode); 複製代碼

咱們在 ReactDOM.render 中渲染 Button 組件,使用了相似 HTML 的樣式,但它既不是 HTML,也不是 JS,甚至不是 React。這就是 JSX ,它是 JavaScript 的擴展,容許咱們以相似於 HTML 的函數語法編寫函數調用。

你能夠嘗試在 Button 函數內返回其餘 HTML 元素,看看它們是如何被支持的(例如,返回 input 元素或 textarea 元素)。

1. JSX 不是 HTML

每一個 JSX 元素只是調用 React.createElement(component, props, ...children) 的語法糖。所以,使用 JSX 能夠完成的任何事情均可以經過純 JS 完成。

上例就能夠編寫爲不使用 JSX 的代碼:

ReactDOM.render(
  React.createElement(
    Hello,
    {name: 'Button'},
    React.createElement(
      'div',
       null,
       `Hello ${this.props.name}`,
    )
  ),
  document.getElementById('root'),
); // 在瀏覽器中渲染一個簡單的 div 元素,顯示 Hello Bottle
複製代碼

React.renderReact.createElement 是 React 最核心的 API 方法,每個 React 項目都必需要引入這兩個API。

瀏覽器不識別 JSX。咱們在瀏覽器中運行 JSX,會報錯:

JSX

因此,在項目中運用 JSX,咱們須要使用像 Babel 或 TypeScript 這樣的轉換器。例如,當 咱們使用 create-react-app 建立項目時,就會在內部使用 Babel 來轉換項目中的 JSX。

JSX 基本上是一種折中,使咱們可以使用與 HTML 很是類似的語法,使用編譯器將其轉換爲 React.createElement 調用,而不是直接使用 React.createElement 語法建立 React 組件。

React 組件是一個返回 React 元素的 JS 函數。當使用 JSX 時,<tag></tag>語法會被轉化爲 React.createElement("tag") 。在建立 React 組件時應該牢記這一點。咱們不是在寫 HTML,而實在使用 JS 擴展來建立 React 元素(其實是 JS 對象)的函數調用。

所以,JSX 容許咱們類 HTML 的語法來表示 React 樹,瀏覽器和 React 均不須要識別它,只有編譯器纔有。咱們發送給瀏覽器的是無 JSX 代碼。

2. 命名必須以大寫字母開頭

請注意咱們在上面例子中將組件命名爲 Button。第一個字母是大寫字母,這是一個規定,由於咱們在處理混合的 HTML 元素和 React 元素時,JSX 編譯器(如 Babel )會將全部以小寫字母開頭的名稱視爲 HTML 元素。

  • HTML 元素做爲字符串傳遞給 React.createElement 調用
  • React 元素須要做爲變量傳遞
<button></button> // React.createElement("button", null)
<Button></Button> // React.createElement(Button, null)
複製代碼

再看一個例子:

function button () {
  return <div>My Fancy Button</div>;
};

// 將會渲染一個 HTML button 組件
// 忽略 React 組件 button
ReactDOM.render(<button />, mountNode); 複製代碼

3. 第一個參數是 props 的對象

就像能夠爲 HTML 元素傳遞 idtitle 等屬性同樣,React 元素在渲染時也能夠接收屬性列表。例如,上面的 Button 元素就接受了 一個 label 屬性。在 React 中,React 元素接收的屬性列表稱爲 props

使用函數組件時,你沒必要將包含屬性列表的對象命名爲 props,但這是標準作法。但當咱們使用類組件時,屬性列表始終命名爲 props

請注意,props 是可選的。有些組件能夠沒有 props。可是,組件必須有返回值。React 組件不能返回 undefined(顯式或隱式)。它必須返回一個值。它能夠返回 null 以使渲染器忽略其輸出。

每當我使用 props(或 state)時,我喜歡使用對象解構。例如,Button組件函數可使用 props 解構寫法:

const Button = ({ label }) => (
  <button type="submit">{label}</button>
);
複製代碼

這種方法有許多好處,但最重要的是看上去方便,並確保組件不會收到任何其餘不須要的額外 props

注意我這裏使用的是 箭頭函數 而不是常規函數。這只是我我的的一種風格偏好。有些人喜歡常規函數,這沒有任何問題。我認爲重要的是要與你選擇的風格保持一致

4. JSX 中的表達式

你能夠在 JSX 中的任何位置使用一對大括號來包含 JavaScript 表達式:

const RandomValue = () => (
  <div> { Math.floor(Math.random() * 100) } </div>
);

ReactDOM.render(<RandomValue />, mountNode); 複製代碼

請注意,只有表達式 能夠包含在 {} 內。

例如,你不能包含常規 if 語句,但三元表達式是能夠的。任何有 返回值的 都是能夠。

你能夠在函數中放入任何代碼,使它返回一些值,並在大括號內調用該函數。可是,儘可能不要在 {} 內進行復雜的邏輯操做。

JavaScript 變量也是表達式,所以當組件收到 props 時,你能夠在 {} 使用 props。這就是咱們爲何能在 Button 函數組件中使用 {label} 的緣由。

JavaScript 對象也是表達式。咱們使用大括號內的 JavaScript 對象,這使得它看起來像雙大括號:{{a:42}}。但這並非一個不一樣的語法,它僅僅表示在常規 JSX 括號內,使用對象而已。

例如,在這些 {} 中使用對象的一個用例是將 CSS 樣式對象傳遞給 style

const ErrorDisplay = ({ message }) => (
  <div style={ { color:'red', backgroundColor:'yellow' } }> {message} </div>
);

ReactDOM.render(
  <ErrorDisplay message="These aren't the droids you're looking for" />, mountNode ); 複製代碼

style上面的屬性是一個特殊的屬性。React 將這些樣式對象轉換爲內聯 CSS 樣式屬性。固然,這不是設置 React 組件樣式的最佳方法,但在條件樣式中,使用它很是方便。例如,隨機將輸出的文本設爲綠色或紅色:

class ConditionalStyle extends React.Component {
  render() {
    return (
      <div style={{ color: Math.random() < 0.5 ? 'green': 'red' }}> How do you like this? </div>
    );
  }
}

ReactDOM.render(
  <ConditionalStyle />, mountNode, ); 複製代碼

這比有條件地使用類名更容易使用。

5. JSX不是模板語言

一些處理 HTML 的庫爲它提供了模板語言。使用具備循環和條件的"加強"HTML 語法編寫動態視圖。而後,這些庫使用 JavaScript 將模板轉換爲 DOM 操做。能夠在瀏覽器中使用 DOM 操做來顯示加強的 HTML 描述的 DOM 樹。

React取消了那一步。咱們不會使用 React 應用程序向瀏覽器發送模板。咱們向它發送了一個用 React API 描述的對象樹。React 使用這些對象生成顯示所需 DOM 樹的操做。

使用 HTML 模板時,庫會將你的應用程序解析爲字符串,React 應用程序被解析爲對象樹。

雖然 JSX 可能看起來像模板語言,但實際上並不是如此。它只是一個JavaScript擴展,它容許咱們用一個看起來像HTML 模板的語法來表示React的對象樹。瀏覽器根本不須要處理 JSX ,React 也沒必要處理它!只有編譯器纔有。咱們發送給瀏覽器的是無模板和無 JSX 代碼。

例如,對於todos咱們上面看到的數組,若是咱們要使用模板語言在UI中顯示該數組,咱們須要執行如下操做:

<ul>
  <% FOR each todo in the list of todos %> <li><%= todo.body %></li> <% END FOR %> </ul>
複製代碼

<% %>是表示動態加強部分的一種語法。你可能還會看到{{ }}語法。某些模板語言使用特殊屬性來加強邏輯。一些模板語言使用空格縮進(off-side rule)。

todos 數組發生更改時(咱們須要使用模板語言更新 DOM 中呈現的內容),咱們必須從新呈現該模板或計算DOM樹中咱們須要反映 todos 數組中更改的位置。

在 React 應用程序中,根本沒有模板語言。相反,咱們使用 JSX :

<ul>
  {todos.map(todo =>
    <li>{todo.body}</li>
  )}
</ul>
複製代碼

在瀏覽器中使用以前,它被轉換爲:

React.createElement(
  "ul",
  null,
  todos.map(todo =>
    React.createElement("li", null, todo.body)
  ),
);
複製代碼

React 獲取這個對象樹並將其轉換爲 DOM 元素樹。從咱們的角度來看,咱們已經完成了這棵樹。咱們無論理任何行動。咱們只管理 todos 數組自己的操做。

7、class 組件

React 也支持經過 JavaScript class 語法建立組件。這是使用 class 編寫的相同 Button 組件示例:

class Button extends React.Component {
  render() {
    return (
      <button>{this.props.label}</button>
    );
  }
}

// 相同的語法使用它 
ReactDOM.render(<Button label="Save" />, mountNode); 複製代碼

在此語法中,你定義了 Button 繼承自 React.Component ,它是 React 頂級 API 中的主要類之一。基於類的 React 組件必須至少定義一個名爲的實例方法 render 。此 render 方法返回表示從組件實例化的對象的輸出的元素。每次咱們使用 Button 組件(經過渲染 <Button … />)時,React 將從這個基於類的組件中實例化一個對象,並使用該對象來建立一個 DOM 元素。它還會將DOM 呈現的元素與它從類建立的實例相關聯。

注意咱們在渲染的 JSX 中使用 this.props.label 的方式 ,每一個組件有 props 屬性,在組件實例化時,它包含傳遞給該組件元素的參數。與函數組件不一樣的是,class 組件中的 render 函數不接收任何參數。

8、函數與類

在 React 中使用函數組件是受限的。由於函數組件沒有 state 狀態。但在 React v16.8 引入 Hooks 以後就變得不一樣了,它能讓組件在不使用 class 的狀況下使用 state 以及其餘的 React 特性,

我相信新的 API 會慢慢取代舊的 API ,但這並非我想鼓勵你使用它的惟一緣由。

我在大型應用程序中使用了這兩個 API ,我能夠告訴你,新 API 比舊 API 更優越的方面有不少,其中我認爲這些是最重要的:

  • 你沒必要使用 class 及其 state。你僅須要使用在每一個渲染上刷新的簡單函數。state 被明確聲明,沒有任何隱藏。全部這些基本上意味着你將在代碼中遇到更少的驚喜。
  • 你能夠將相關的 state 邏輯分組,並將其分爲獨立的可組合和可共享單元。這使得咱們更容易將複雜組件分解爲更小的部件。它還使測試組件更容易。
  • 你能夠以聲明方式使用任何有狀態邏輯,而無需在組件樹中使用任何分層 「嵌套」 。

雖然在可預見的將來,基於 class 的組件將繼續成爲 React 的一部分,但做爲 React 開發人員,我認爲開始使用函數(和 Hook ),並專一於學習新 API 是有意義的。

1. 組件與元素

你可能會在 React 指南和教程中找到 componentelement 這兩個詞。我認爲 React 學習者須要理解重要的區別。

  • React Component 是一個模板,藍圖,全球定義。能夠是函數或類(使用render方法)。
  • React Element 是從組件返回的元素。它是與真實 DOM 相對應的虛擬節點。對於函數組件,此元素是函數返回的對象,對於類組件,元素是組件的 render 方法返回的對象。React 元素不是你在瀏覽器中看到的,它們只是內存中的對象,你沒法改變它們。

React 在內部建立、更新和銷燬對象,以找出須要渲染在瀏覽器的 DOM 元素樹。使用類組件時,一般將其瀏覽器渲染的 DOM 元素稱爲組件實例。你能夠渲染同一組件的許多實例。你不須要手動在類中建立實例,你只須要記住它就在 React 的內存中。對於函數組件,React 只使用函數的調用來肯定要渲染的 DOM 實例。

9、組件的優勢

術語 "組件" 被許多框架和庫使用。咱們可使用 HTML5 功能(如自定義元素和 HTML 導入)編寫原生 Web 組件。

組件,不管咱們是在原生調用仍是經過像 React 這樣的庫調用,都有許多優勢。

首先,組件使你的代碼更易讀,更易於使用。考慮這個UI:

<a href=」http://facebook.com」>
 <img src=」facebook.png」 />
</a>
複製代碼

這個 UI 表明什麼?若是你說 HTML ,你能夠在這裏快速解析並說 「這是一個可點擊的圖像」。若是咱們要將這個 UI 轉換成一個組件,咱們能夠命名它 ClickableImage

<ClickableImage />
複製代碼

當事件變得更復雜時,HTML 變得更加困難,此時,組件容許咱們使用咱們熟悉的語言快速理解 UI 所表明的內容。這是一個更大的例子:

<TweetBox>
  <TextAreaWithLimit limit="280" /> <RemainingCharacters /> <TweetButton /> </TweetBox>
複製代碼

在不查看實際 HTML 代碼的狀況下,咱們確切地知道此 UI 表示的內容。此外,若是咱們須要修改剩餘字符部分的輸出,咱們必須知道確切要去哪裏修改。

React 組件也能夠在同一個應用程序中和多個應用程序中重用。例如,如下是 ClickableImage 組件:

const ClickableImage = ({ href, src }) => {
 return (
   <a href={href}> <img src={src} /> </a> ); }; 複製代碼

擁有 hrefsrc 屬性的變量是使該組件可重用的緣由。例如,要使用此組件,咱們可使用一組 props 渲染它:

<ClickableImage href="http://google.com" src="google.png" />
複製代碼

咱們能夠經過使用不一樣的 props 重用它:

<ClickableImage href="http://bing.com" src="bing.png" />
複製代碼

在函數式編程中,咱們有純函數的概念。 若是咱們給純函數相同的輸入,咱們將始終得到相同的輸出。

若是 React 組件不依賴於其定義以外的任何內容,咱們也能夠將該組件標記爲純組件。純組件在沒有任何問題的狀況下更有可能被重用。

咱們能夠將 HTML 元素視爲瀏覽器中的內置組件。咱們也可使用本身的自定義組件來組成更大的組件。例如,讓咱們編寫一個顯示搜索引擎列表的組件。

const SearchEngines = () => {
  return (
    <div className="search-engines">
      <ClickableImage href="http://google.com" src="google.png" />
      <ClickableImage href="http://bing.com" src="bing.png" />
    </div>
  );
};
複製代碼

注意我是如何使用 ClickableImage 組件來組成 SearchEngines 組件的!

咱們還能夠 SearchEngines 經過將數據提取到變量中並將其設計爲使用該變量來使組件可重用。

例如,咱們能夠採用如下格式引入數據數組:

const data = [
  { href: "http://google.com", src: "google.png" },
  { href: "http://bing.com", src: "bing.png" },
  { href: "http://yahoo.com", src: "yahoo.png" }
];
複製代碼

而後,爲了 <SearchEngines data={data} /> 能成功渲染,咱們須要將 data 數組從對象列表映射到 ClickableImage 組件列表:

const SearchEngines = ({ engines }) => {
  return (
    <List>
      {engines.map(engine => <ClickableImage {...engine} />)}
    </List>
  );
};

ReactDOM.render(
 <SearchEngines engines={data} />,
 document.getElementById("mountNode")
);
複製代碼

SearchEngines 適用於咱們提供給它的任何搜索引擎列表。

本文翻譯自:jscomplete.com/learn/compl…

系列文章

想看更過系列文章,點擊前往 github 博客主頁

走在最後,歡迎關注:前端瓶子君,每日更新

前端瓶子君
相關文章
相關標籤/搜索