去年,我寫了一本關於學習React.js的簡短書,原來是大約100頁。今年,我將挑戰自己,把它概括為一篇關於中度的文章。
這篇文章不會涵蓋什麼是React或者為什麼你應該學習它。相反,這是對已經熟悉JavaScript並熟悉DOM API基礎知識的人們對React.js的基礎知識的實踐介紹。
以下所有程式碼示例均標示為參考。它們純粹是為了提供概念的例子。他們大多數可以寫得更好一些。
基礎 #1:React都是元件
React是圍繞可重用元件的概念設計的。您定義小元件,並將它們放在一起以形成更大的元件。
所有小或大的元件都可重複使用,甚至跨不同的專案。
一個React元件(以其最簡單的形式)是一個簡單的JavaScript函式:。
示例1https://jscomplete.com/repl?j=Sy3QAdKHW
1 2 3 4 |
function Button (props) { // 返回一個DOM元素。例如: return <button type="submit">{props.label}</button>; } |
1 2 |
// 將Button元件渲染到瀏覽器 ReactDOM.render(<Button label="Save" />, mountNode) |
下面說明用於按鈕標籤的花括號。現在不要擔心他們。ReactDOM
稍後也會進行說明,但是如果要測試這個例子和所有即將到來的程式碼示例,上面的render
函式就是你所需要的。
第二個引數ReactDOM.render
是React將要接管和控制的目標DOM元素。在jsComplete REPL中,您可以使用特殊變數mountNode
。
請注意以下關於示例1:
- 元件名稱以大寫字母開頭。這是必需的,因為我們將處理HTML元素和React元素的混合。小寫名稱保留給HTML元素。事實上,請繼續嘗試將React元件命名為“button”。ReactDOM將忽略該函式並渲染一個常規的空HTML按鈕。
- 每個元件都接收一個屬性列表,就像HTML元素一樣。在React中,這個列表被稱為props。使用函式元件,您可以命名任何東西。
- 我們奇怪地在上面的
Button
函式元件的返回輸出中寫出了什麼樣的HTML 。這既不是JavaScript也不是HTML,甚至不是React.js。但是,它非常受歡迎,成為React應用的預設設定。它被稱為JSX ,它是一個JavaScript擴充套件。JSX也是折衷!繼續嘗試並返回上面的函式中的任何其他HTML元素,並檢視它們是如何支援的(例如,返回一個文字輸入元素)。
基本原理 #2:JSX有什麼好處?
上面的示例1可以用純粹的React.js來編寫,沒有JSX,如下所示:
示例2 – 不用JSX的React 元件 https://jscomplete.com/repl?j=HyiEwoYB-
1 2 3 4 5 6 7 |
function Button (props) { return React.createElement( "button", { type: "submit" }, props.label ); } |
1 2 3 4 5 |
// 要使用Button,您可以執行類似這樣 ReactDOM.render( React.createElement(Button, { label: "Save" }), mountNode ); |
該 createElement
函式是React頂級API中的主要函式。您需要學習的這個級別中共有7件事情中的1項。這是多麼小的React的API。
很像DOM本身具有document.createElement
建立由標籤名稱指定的元素的函式,React的createElement函式是一個更高階別的函式,可以做document.createElement
能做的任何事。但它也可以用於建立一個元素來表示一個React元件。我們使用上面的例2中的元件Button
時,我們用了後者。
不同於document.createElement
,React 的createElement
接受第二個引數後的動態數量來表示建立元素的子代。所以createElement
實際上建立一個樹。
這是一個例子:
示例3 – React的createElement API https://jscomplete.com/repl?j=r1GNoiFBb
1 2 3 4 5 6 7 |
const InputForm = React.createElement( "form", { target: "_blank", action: "[https://google.com/search](https://google.com/search)" }, React.createElement("div", null, "Enter input and click Search"), React.createElement("input", { className: "big-input" }), React.createElement(Button, { label: "Search" }) ); |
1 2 3 4 5 6 7 8 |
// InputForm使用Button元件,所以我們也需要: function Button (props) { return React.createElement( "button", { type: "submit" }, props.label ); } |
1 2 |
// 然後我們可以直接使用InputForm ReactDOM.render(InputForm, mountNode); |
請注意以上幾個例子:
InputForm
不是React元件; 它只是一個React 元素。這就是為什麼我們直接在ReactDOM.render
中呼叫它,而不是與。- 該
React.createElement
函式在前兩個之後接受多個引數。從第3個起始的引數列表包括建立元素的子項列表。 - 我們能夠巢狀
React.createElement
,因為它都是JavaScript。 React.createElement
當元素不需要屬性或特性時,第二個引數可以為null或空物件。- 我們可以將HTML元素與React元件混合使用。您可以將HTML元素視為內建的React元件。
- React的API嘗試儘可能接近DOM API,這就是為什麼我們使用
className
而不是class
輸入元素。祕密地,我們都希望React的API將成為DOM API本身的一部分。因為,你知道,這好多了。
上面的程式碼是您在包含React庫時瞭解的內容。瀏覽器不處理任何JSX業務。然而,我們人類喜歡看HTML並且使用HTML而不是這些createElement
呼叫(想象一下建立一個網站僅使用document.createElement
,你可以!)。這就是為什麼存在JSX妥協的原因。React.createElement我們可以用非常類似於HTML的語法來編寫上面的表單:
示例4 – JSX(與示例3相比) https://jscomplete.com/repl?j=SJWy3otHW
1 2 3 4 5 6 |
const InputForm = <form target="_blank" action="[https://google.com/search](https://google.com/search)"> <div>Enter input and click Search</div> <input className="big-input" name="q" /> <Button label="Search" /> </form>; |
1 2 3 4 5 6 |
// InputForm "仍然" 使用按鈕元件, 所以我們也需要它。 // 無論是 JSX 或正常的形式 function Button (props) { // Returns a DOM element here. For example: return <button type="submit">{props.label}</button>; } |
1 2 |
// 然後, 我們可以在.render裡直接使用 InputForm ReactDOM.render(InputForm, mountNode); |
請注意以上幾點:
- 它不是HTML。例如,我們仍在使用
className
而不是class
。 - 我們仍然在考慮將以上HTML作為JavaScript。看看我在末尾新增了分號。
我們上面寫的(例4)是JSX。然而,我們對瀏覽器的編譯是它的編譯版本(示例3)。為了實現這一點,我們需要使用前處理器將JSX版本轉換為React.createElement
版本。
那就是JSX。這是一個妥協,允許我們以類似於HTML的語法編寫我們的React元件,這是一個很好的交易。
上面標題中的“Flux”一詞被選為韻詞,但它也是Facebook 流行的非常受歡迎的 應用架構 的名稱。最著名的實現是Redux。Flux完美適應React反應模式。
JSX,順便說一下,可以自己單獨使用。這不是一個React唯一的事情。
基礎 #3:您可以在JSX中的任何地方使用JavaScript表示式
在JSX部分中,您可以使用一對花括號內的任何JavaScript表示式。
*示例5 – 在JSX中使用JavaScript表示式 https://jscomplete.com/repl?j=SkNN3oYSW
1 2 3 4 |
const RandomValue = () => <div> { Math.floor(Math.random() * 100) } </div>; |
1 2 |
// 使用它: ReactDOM.render(<RandomValue />, mountNode); |
任何JavaScript表示式都可以放在那些花括號內。這相當於JavaScript 模板文字中的${}
插值語法。
這是JSX中唯一的約束:只有表示式。所以,例如,你不能使用一個常規if語句,但三元表示式是可以的。
JavaScript變數也是表示式,所以當元件接收到一個屬性列表(RandomValue
元件沒有,props
是可選的)時,可以在花括號內使用這些屬性。我們在Button上面的元件中做了這個(例1)。
JavaScript物件也是表示式。有時候,我們在花括號內使用一個JavaScript物件,這使得它看起來像是雙花括號,但它只是一個大括號內的物件。一個用例是將CSS樣式物件傳遞給React中的特殊樣式屬性:
示例6 – 傳遞給特殊的React樣式的物件prop https://jscomplete.com/repl?j=S1Kw2sFHb
1 2 3 4 |
const ErrorDisplay = ({message}) => <div style={ { color: 'red', backgroundColor: 'yellow' } }> {message} </div>; |
1 2 3 4 5 6 7 |
// 使用它: ReactDOM.render( <ErrorDisplay message="These aren't the droids you're looking for" />, mountNode ); |
請注意我是如何解體的只有訊息出來的屬性引數。這是JavaScript。還要注意上面的style屬性是一個特殊的屬性(再次,它不是HTML,它更接近於DOM API)。我們使用一個物件作為style屬性的值。該物件定義了樣式,就像我們使用JavaScript一樣(因為我們就是)。 甚至可以在JSX中使用React元素,因為這也是一個表示式。記住,一個React元素是一個函式呼叫:
示例7 – 在React元素裡面使用 {} https://jscomplete.com/repl?j=SkTLpjYr-
1 2 3 4 5 6 7 8 9 10 |
const MaybeError = ({errorMessage}) => <div> {errorMessage && <ErrorDisplay message={errorMessage} />} </div>; // MaybeError元件使用ErrorDisplay元件: const ErrorDisplay = ({message}) => <div style={ { color: 'red', backgroundColor: 'yellow' } }> {message} </div>; |
1 2 3 4 5 6 7 |
// 現在我們可以使用 MaybeError 元件: ReactDOM.render( <MaybeError errorMessage={Math.random() > 0.5 ? 'Not good' : ''} />, mountNode ); |
上面的MaybeError
元件將僅顯示ErrorDisplay
元件,如果有一個errorMessage
字串傳遞給它,並且是空的div
。React認為{true}
, {false}
, {undefined}
和 {null}
是有效的元素孩子,不渲染任何內容。
您也可以在JSX內使用所有的JavaScript函式方法的集合(map
, reduce
, filter
, concat
, 等)。再次,因為它們返回表示式:
示例8 – 在{}中使用陣列 map https://jscomplete.com/repl?j=SJ29aiYH-
1 2 3 4 |
const Doubler = ({value=[1, 2, 3]}) => <div> {value.map(e => e * 2)} </div>; |
1 2 |
// 使用它 ReactDOM.render(<Doubler />, mountNode); |
請注意,我如何將value
prop設定為上面的預設值,因為它只是Javascript。還要注意,我在div
輸出了一個陣列表示式。在React中這是可以的。它將在文字節點中放置2倍的值。
基礎 #4:您可以使用JavaScript類編寫React元件
簡單的函式元件非常適合簡單的需求,但有時我們需要更多的函式。React支援通過JavaScript類語法 建立元件。這是Button使用類語法編寫的元件(在示例1中):
示例9 – 使用JavaScript類建立元件 https://jscomplete.com/repl?j=ryjk0iKHb
1 2 3 4 5 |
class Button extends React.Component { render() { return <button>{this.props.label}</button>; } } |
1 2 |
// 使用它(相同的語法) ReactDOM.render(<Button label="Save" />, mountNode); |
類語法很簡單。定義一個React.Component
的擴充套件類(需要學習的另一個頂級的React API)。該類定義單個例項函式render(),並且該render函式返回虛擬DOM物件。每次我們使用Button上面的基於類的元件(例如,通過這樣做),React將從這個基於類的元件中例項化一個物件,並在DOM樹中使用該物件。
這就是為什麼我們在JSX中使用this.props.label
渲染輸出的原因。因為每個元件都獲得一個特殊的例項屬性props,所以它被例項化時儲存傳遞給該元件的所有值。
由於我們有一個與元件單次使用相關聯的例項,我們可以根據需要自定義該例項。例如,我們可以通過使用常規JavaScriptconstructor
函式構建它來定製它:
示例10 – 自定義元件例項 https://jscomplete.com/repl?j=rko7RsKS-
1 2 3 4 5 6 7 8 9 |
class Button extends React.Component { constructor(props) { super(props); this.id = Date.now(); } render() { return <button id={this.id}>{this.props.label}</button>; } } |
1 2 |
// 使用它 ReactDOM.render(, mountNode); |
我們還可以定義類原型函式,並將它們隨意使用,包括返回的JSX輸出內:
示例11 — 使用類屬性 https://jscomplete.com/repl?j=H1YDCoFSb
1 2 |
class Button extends React.Component { clickCounter = 0; |
1 2 3 4 5 6 7 8 9 10 11 12 |
handleClick = () => { console.log(Clicked: ${++this.clickCounter}); }; render() { return ( <button id={this.id} onClick={this.handleClick}> {this.props.label} </button> ); } } |
1 2 |
// Use it ReactDOM.render(<Button label="Save" />, mountNode); |
注意上面關於示例11的幾件事情:
- 該
handleClick
函式使用JavaScript中新提出的類欄位語法 編寫。這仍然在第二階段,但由於很多原因,它是訪問元件裝載例項(感謝箭頭函式)的最佳選擇。但是,您需要使用像Babel這樣的編譯器來配置它來了解第2階段(或類欄位語法)來獲取上面的程式碼。jsComplete REPL已預先配置。 - 我們還使用相同的類欄位語法定義了
clickCounter
例項變數。這允許我們完全跳過使用類建構函式呼叫。 - 當我們將該
handleClick
函式指定為特殊onClick
React屬性的值時,我們沒有呼叫它。我們通過在引用的handleClick
函式。呼叫該級別的函式是使用React最常見的錯誤之一。
1 2 |
// 錯誤: onClick={**this.handleClick()**} |
1 2 |
// 正確: onClick={**this.handleClick**} |
基礎 #5:React事件:兩個重大差異
在React元素中處理事件時,與DOM API的方式有兩個非常重要的區別:
- 所有React元素屬性(包括事件)使用camelCase命名,而不是小寫。是
onClick
而不是onclick
.。 - 我們傳遞一個實際的JavaScript函式引用作為事件處理程式,而不是一個字串。是
onClick={**handleClick**}
不是onClick="**handleClick"**
。
使用自己的物件對DOM事件物件進行反射來優化事件處理的效能。但是在事件處理程式中,我們仍然可以訪問DOM事件物件上可用的所有方法。React將包裝的事件物件傳遞給每個控制程式碼呼叫。例如,為了防止表單從預設提交操作中,您可以執行以下操作:
示例12 – 使用包裝事件 https://jscomplete.com/repl?j=HkIhRoKBb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Form extends React.Component { handleSubmit = (event) => { event.preventDefault(); console.log('Form submitted'); }; render() { return ( <form onSubmit={this.handleSubmit}> <button type="submit">Submit</button> </form> ); } } |
1 2 |
// 使用它 ReactDOM.render(<Form />, mountNode); |
基礎 #6:每個React元件都有一個故事
以下僅適用於類元件(那些擴充套件自React.Component
)。函式元件有一個略有不同的故事。
- 首先,我們為React定義一個模板,以從元件建立元素。
- 然後,我們指示React在某處使用它。例如,在
render
另一個元件的呼叫中,或ReactDOM.render
。 - 然後,React例項化一個元素,並給出一組我們可以訪問的
this.props
屬性。那些屬性正是我們在上面的步驟2中傳遞的。 - 由於它都是JavaScript,所以
constructor
將呼叫該方法(如果已定義)。這是我們所說的第一個:元件生命週期方法。 - React然後計算render方法(虛擬DOM節點)的輸出。
- 由於這是React第一次渲染元素,所以React將與瀏覽器進行通訊(代表我們使用DOM API)來顯示元素。這個過程通常稱為裝載。
- 然後,React呼叫另一個
componentDidMount
生命週期方法。我們可以使用這種方法,例如,在DOM上做一些我們現在知道在瀏覽器中存在的東西。在此生命週期方法之前,我們處理的DOM都是虛擬的。 - 一些元件故事在這裡結束。出於各種原因,其他元件可以從瀏覽器DOM中解除裝載。在後一種情況發生之前,React呼叫另一種生命週期方法
componentWillUnmount
。 - 任何已裝載元件的狀態可能會更改。該元素的父代可能會重新渲染。在任一種情況下,裝載的元件可能會接收不同的屬性。這裡的魔法發生在這裡,我們實際上開始需要React!在此之前,我們根本就不需要React。
- 這個元件的故事繼續下去,但在它之前,我們需要了解我所說的這個狀態。
基礎 #7:React元件可以具有私有狀態
以下也僅適用於類元件。有沒有人提到有些人把表演式的元件叫做啞巴?
狀態類欄位是任何React類元件中的特殊欄位。React監視每個元件狀態以進行更改。但是對於React這樣做有效,我們必須通過我們需要學習的另一個React API事件來更改狀態欄位this.setState
:
Example 13 – setState API https://jscomplete.com/repl?j=H1fek2KH-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class CounterButton extends React.Component { state = { clickCounter: 0, currentTimestamp: new Date(), }; handleClick = () => { this.setState((prevState) => { return { clickCounter: prevState.clickCounter + 1 }; }); }; componentDidMount() { setInterval(() => { this.setState({ currentTimestamp: new Date() }) }, 1000); } render() { return ( <div> <button onClick={this.handleClick}>Click</button> <p>Clicked: {this.state.clickCounter}</p> <p>Time: {this.state.currentTimestamp.toLocaleString()}</p> </div> ); } } |
1 2 |
// 使用它 ReactDOM.render(<CounterButton />, mountNode); |
這是瞭解最重要的例子。它將基本完成您對“基本法”的基本知識。在這個例子之後,還有一些你需要學習的小事情,但是從這一點來看,它主要是你和你的JavaScript技能。
我們來看一下例項13,從類欄位開始。它有兩個。特殊state
欄位被初始化與持有的物件clickCounter
以0
開始,並且currentTimestamp
以new Date()
開始。
第二類欄位是一個handleClick
函式,我們傳遞給render方法中的button元素的onClick
事件。該handleClick
方法使用setState
修改此元件例項狀態。注意到這一點。
我們正在修改狀態的另一個地方在我們在componentDidMount
l生命週期方法內部啟動的間隔定時器中。它每秒鐘執行另一個呼叫this.setState
.。
在render方法中,我們使用了正常讀取語法對狀態的兩個屬性。沒有專門的API。
現在,請注意,我們使用兩種不同的方式更新了狀態:
- 傳遞返回一個物件的函式。我們在
handleClick
函式中做了這個。 - 通過傳遞一個常規物件。我們在間隔回撥中做到了。
這兩種方式都是可以接受的,但是當您同時讀取和寫入狀態時,首先是首選的(我們這樣做)。在間隔回撥期間,我們只寫給狀態,而不是讀取它。當有疑問時,始終使用第一個函式引數語法。它競爭條件更安全,因為setState
實際上是一種非同步方法。
我們如何更新狀態?我們返回一個具有我們要更新的新值的物件。注意在兩次呼叫中setState
,,我們只是從狀態欄位傳遞一個屬性,而不是兩者。這是完全可以的,因為setState實際上將您傳遞的內容(函式引數的返回值)與現有狀態合併。因此,在呼叫時不指定屬性setState意味著我們不希望更改該屬性(而不是刪除它)。
基礎 #8:React會反應
React從它對狀態變化做出的事實(雖然不是反應性的,而是按計劃)。有一個笑話,React應該被命名為 Schedule!
然而,當任何元件的狀態更新時,我們用肉眼看到的是,React對該更新做出反應,並自動反映瀏覽器DOM中的更新(如果需要)。
將渲染函式的輸入視為兩者
- 由父母傳遞的屬性
- 可以隨時更新的內部私有狀態
當render函式的輸入變化時,其輸出可能會改變。
React保留了渲染歷史的記錄,當它看到一個渲染與前一個渲染不同時,它將計算它們之間的差異,並有效地將其轉換為在DOM中執行的實際DOM操作。
基礎 #9:React是您的代理
您可以將React視為我們聘請的與瀏覽器通訊的代理。以上面的當前時間戳顯示為例。我們不是手動去瀏覽器並呼叫DOM API操作來每秒查詢和更新p#timestamp
元素,而是在元件的狀態上更改了一個屬性,而React則代表我們與瀏覽器通訊。我相信這是React流行的真正原因。我們討厭瀏覽器(和所說的DOM語言的很多方言),React自願為我們做所有的談話,免費!
基礎 #10:每個React元件都有一個故事(第2部分)
現在我們知道一個元件的狀態,以及當這個狀態改變了一些魔法的時候,讓我們來學習關於該過程的最後幾個概念。
- 元件可能需要在其狀態更新時或者當其父級決定更改傳遞給元件的屬性時重新渲染
- 如果後者發生,React會呼叫另一個生命週期方法
componentWillReceiveProps
。 - 如果狀態物件或傳入屬性被更改,則React有一個重要的決定。元件應該在DOM中更新嗎?這就是為什麼它在這裡呼叫另一個重要的生命週期方法
shouldComponentUpdate
。這個方法是一個實際的問題,所以如果你需要定製或自己進行優化渲染過程中,你必須回答返回這個問題無論是true還是false。 - 如果沒有自定義
shouldComponentUpdate
指定,React預設是一個非常聰明的事情,在大多數情況下實際上足夠好。 - 首先,React此時呼叫另一個生命週期方法
componentWillUpdate
。然後React將計算新的渲染輸出並將其與最後渲染的輸出進行比較。 - 如果渲染的輸出完全相同,React什麼都不做(不需要和瀏覽器交談)。
- 如果有差異,則React會將瀏覽器的差異,就像我們之前看到的那樣。
- 無論如何,由於更新過程無論如何發生(即使輸出完全一樣),React呼叫最終的生命週期方法
componentDidUpdate
。
生命週期方法實際上是逃避艙口。如果你沒有做任何特別的事情,你可以建立沒有他們的完整的應用。它們非常方便地分析應用中發生的情況,並進一步優化了React更新的效能。
僅此而已。相信與否,上面你學到了什麼(或者部分內容,真的),你可以開始建立一些有趣的React應用。如果您渴望瞭解更多資訊,請訪問我們的Pluralsight的React.js課程入門Getting Started with React.js course at Pluralsight
我也強烈推薦Alex和Eve的 學習反饋書 !
這篇文章最初發表在EdgeCoders 這裡。本文的EdgeCoders版本使用互動式程式碼示例,您可以在文章中編輯和重新執行。