本系列涵蓋了使用 React 的全部知識,分爲上、中、下三篇。此爲中篇,本篇主講 React 組件。前端
本系列涵蓋 React v16.9,但更多的是 React 全面解析,具體 React v16.9 新特性可查看 [譯]React v16.9 新特性。react
完整系列包含:git
上篇主講 React 元素渲染,可查看:深刻解讀 React 核心之元素篇。github
中篇主講 React 元素組件,可查看:深刻解讀 React 核心之組件篇。編程
下篇主講 React Hooks,可查看:深刻解讀 React 核心之 Hooks 篇。數組
在 React 中,咱們使用組件(有狀態、可組合、可重用)來描述 UI 。 在任何編程語言中,你均可以將組件視爲簡單的函數。瀏覽器
React 組件也同樣, 它的輸入是 props,輸出是關於 UI 的描述。咱們能夠在多個 UI 中重用單個組件,組件也能夠包含其餘組件。React 組件的本質上就是一個普通的 JavaScript 函數。 儘管一些 React 組件是純組件,但也能夠在組件中引入反作用。例如,組件在瀏覽器中渲染時可能會更改網頁的標題,或者可能會將瀏覽器視圖滾動到某個位置。 最重要的是,React 組件能夠擁有一個私有狀態來保存在組件生命週期內可能發生變化的數據。這個私有狀態驅動組件輸出到原生 DOM 中!app
爲何將 React 稱爲響應式設計?框架
當 React 組件的狀態(它是其輸入的一部分)發生更改時,它所表明的 UI (其輸出)也會發生更改。UI 描述中的這種變化必須反映在咱們正在使用的設備中。在瀏覽器中,咱們須要更新 DOM 樹。在 React 應用程序中,咱們不會手動執行此操做。
state
更新時,React 自動響應,並在須要時自動(並有效)更新到 DOM 上。dom
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
元素)。
每一個 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.render
與 React.createElement
是 React 最核心的 API 方法,每個 React 項目都必需要引入這兩個API。
瀏覽器不識別 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 代碼。
請注意咱們在上面例子中將組件命名爲 Button
。第一個字母是大寫字母,這是一個規定,由於咱們在處理混合的 HTML 元素和 React 元素時,JSX 編譯器(如 Babel )會將全部以小寫字母開頭的名稱視爲 HTML 元素。
React.createElement
調用<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); 複製代碼
props
的對象就像能夠爲 HTML 元素傳遞 id
或 title
等屬性同樣,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
。
注意我這裏使用的是 箭頭函數 而不是常規函數。這只是我我的的一種風格偏好。有些人喜歡常規函數,這沒有任何問題。我認爲重要的是要與你選擇的風格保持一致。
你能夠在 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, ); 複製代碼
這比有條件地使用類名更容易使用。
一些處理 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
數組自己的操做。
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
函數不接收任何參數。
在 React 中使用函數組件是受限的。由於函數組件沒有 state
狀態。但在 React v16.8 引入 Hooks 以後就變得不一樣了,它能讓組件在不使用 class
的狀況下使用 state
以及其餘的 React 特性,
我相信新的 API 會慢慢取代舊的 API ,但這並非我想鼓勵你使用它的惟一緣由。
我在大型應用程序中使用了這兩個 API ,我能夠告訴你,新 API 比舊 API 更優越的方面有不少,其中我認爲這些是最重要的:
class
及其 state
。你僅須要使用在每一個渲染上刷新的簡單函數。state
被明確聲明,沒有任何隱藏。全部這些基本上意味着你將在代碼中遇到更少的驚喜。state
邏輯分組,並將其分爲獨立的可組合和可共享單元。這使得咱們更容易將複雜組件分解爲更小的部件。它還使測試組件更容易。雖然在可預見的將來,基於 class
的組件將繼續成爲 React 的一部分,但做爲 React 開發人員,我認爲開始使用函數(和 Hook ),並專一於學習新 API 是有意義的。
你可能會在 React 指南和教程中找到 component
和 element
這兩個詞。我認爲 React 學習者須要理解重要的區別。
render
方法返回的對象。React 元素不是你在瀏覽器中看到的,它們只是內存中的對象,你沒法改變它們。React 在內部建立、更新和銷燬對象,以找出須要渲染在瀏覽器的 DOM 元素樹。使用類組件時,一般將其瀏覽器渲染的 DOM 元素稱爲組件實例。你能夠渲染同一組件的許多實例。你不須要手動在類中建立實例,你只須要記住它就在 React 的內存中。對於函數組件,React 只使用函數的調用來肯定要渲染的 DOM 實例。
術語 "組件" 被許多框架和庫使用。咱們可使用 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> ); }; 複製代碼
擁有 href
和 src
屬性的變量是使該組件可重用的緣由。例如,要使用此組件,咱們可使用一組 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 博客主頁
走在最後,歡迎關注:前端瓶子君,每日更新