您好,登錄后才能下訂單哦!
通過 Medium 中的一篇文章來學習 React.js 的基本原理
你有沒有注意到在 React 的 logo 中隱藏著一個六角星?只是順便提下...
去年我寫了一本簡短的關于學習 React.js 的書,有 100 頁左右。今年,我要挑戰自己 —— 將其總結成一篇文章,并向 Medium 投稿。
這篇文章不是講什么是 React 或者 你該怎樣學習 React。這是在面向那些已經熟悉了 JavaScript 和 DOM API 的人的 React.js 基本原理介紹
本文采用嵌入式 jsComplete 代碼段,所以為了方便閱讀,你需要一個合適的屏幕寬度。
下面所有的代碼都僅供參考。它們也純粹是為了表達概念而提供的例子。它們中的大多數有更好的實踐方式。
您可以編輯和執行下面的任何代碼段。使用?Ctrl+Enter?執行代碼。每一段的右下角有一個點擊后可以在 jsComplete/repl 進行全屏模式編輯或運行代碼的鏈接。
1 React 全部都是組件化的
React 是圍繞可重用組件的概念設計的。你定義小組件并將它們組合在一起形成更大的組件。
無論大小,所有組件都是可重用的,甚至在不同的項目中也是如此。
React 組件最簡單的形式,就是一個普通的 JavaScript 函數:
function?Button?(props)?{?//?這里返回一個?DOM?元素,例如: ?return?<button?type="submit">{props.label}</button>; }//?將按鈕組件呈現給瀏覽器ReactDOM.render(<Button?label="Save"?/>,?mountNode)復制代碼
例 1:編輯上面的代碼并按 Ctrl+Enter 鍵執行(譯者注:譯文暫時沒有這個功能,請訪問原文使用此功能,下同)
括號中的 button 標簽將稍后解釋。現在不要擔心它們。ReactDOM 也將稍后解釋,但如果你想測試這個例子和所有接下來的例子,上述 render 函數是必須的。(React 將要接管和控制的是 ReactDOM.render 的第 2 個參數即目標 DOM 元素)。在 jsComplete REPL 中,你可以使用特殊的變量 mountNode。
例 1 的注意事項:
組件名稱首字母大寫,Button。必須要這樣做是因為我們將處理 HTML 元素和 React 元素的混合。小寫名稱是為 HTML 元素保留的。事實上,將 React 組件命名為 “button” 然后你就會發現 ReactDOM 會忽略這個函數,僅僅是將其作為一個普通的空 HTML 按鈕來渲染。
每個組件都接收一個屬性列表,就像 HTML 元素一樣。在 React 中,這個列表被稱為屬性。雖然你可以將一個函數隨意命名。
在上面 Button 函數組件的返回輸出中,我們奇怪地寫了段看上去像 HTML 的代碼。這實際上既不是 JavaScript 也不是 HTML,老實說,這甚至不是 React.js。然而它非常流行,以至于成為 React 應用程序中的默認值。這就是所謂的 JSX,這是一個JavaScript 的擴展。JSX 也是一個折中方案!繼續嘗試并在上面的函數中返回其他 HTML 元素,看看它們是如何被支持的(例如,返回一個文本輸入元素)。
2 JSX 輸出的是什么?
上面的例 1 可以用沒有 JSX 的純 React.js 編寫,如下:
function?Button?(props)?{ ?return?React.createElement(?"button", ?{?type:?"submit"?}, ?props.label ?); }//?要使用?Button,你可以這么做ReactDOM.render( ?React.createElement(Button,?{?label:?"Save"?}), ?mountNode );復制代碼
例 2:不使用 JSX 編寫 React 組件
在 React 頂級 API 中,createElement 函數是主函數。這是你需要學習的 7 個 API 中的 1 個。React 的 API 就是這么小。
就像 DOM 自身有一個 document.createElement 函數來創建一個由標簽名指定的元素一樣,React 的 createElement 函數是一個高級函數,有和 document.createElement 同樣的功能,但它也可以用于創建一個表示 React 組件的元素。當我們使用上面例 2 中的按鈕組件時,我們使用的是后者。
不像 document.createElement,React 的 createElement 在接收第二個參數后,接收一個動態參數,它表示所創建元素的子元素。所以 createElement 實際上創建了一個樹。
這里就是這樣的一個例子:
const?InputForm?=?React.createElement(?"form", ?{?target:?"_blank",?action:?"https://google.com/search"?}, ?React.createElement("div",?null,?"Enter?input?and?click?Search"), ?React.createElement("input",?{?className:?"big-input"?}), ?React.createElement(Button,?{?label:?"Search"?}) );//?InputForm?使用?Button?組件,所以我們需要這樣做:function?Button?(props)?{?return?React.createElement(?"button", ?{?type:?"submit"?}, ?props.label ?); }//?然后我們可以通過?.render?方法直接使用?InputFormReactDOM.render(InputForm,?mountNode);復制代碼
例 3:React 創建元素的 API
上面例子中的一些事情值得注意:
InputForm 不是一個 React 組件;它僅僅是一個 React?元素。這就是為什么我們可以在 ReactDOM.render 中直接使用它并且可以在調用時不使用 <InputForm /> 的原因。
React.createElement 函數在前兩個參數后接收了多個參數。從第3個參數開始的參數列表構成了創建元素的子項列表。
我們可以嵌套 React.createElement 調用,因為它是 JavaScript。
當這個元素不需要屬性時,React.createElement 的第二個參數可以為空或者是一個空對象。
我們可以在 React 組件中混合 HTML 元素。你可以將 HTML 元素作為內置的 React 組件。
React 的 API 試圖和 DOM API 一樣,這就是為什么我們在 input 元素中使用 className 代替 class 的原因。我們都希望如果 React 的 API 成為 DOM API 本身的一部分,因為,你知道,它要好得多。
上述的代碼是當你引入 React 庫的時候瀏覽器是怎樣理解的。瀏覽器不會處理任何 JSX 業務。然而,我們更喜歡看到和使用 HTML,而不是那些 createElement 調用(想象一下只使用 document.createElement 構建一個網站!)。這就是 JSX 存在的原因。取代上述調用 React.createElement 的方式,我們可以使用一個非常簡單類似于 HTML 的語法:
const?InputForm?= ?<form?target="_blank"?action="https://google.com/search"> ?<div>Enter?input?and?click?Search</div> ?<input?className="big-input"?name="q"?/> ?<Button?label="Search"?/> ?</form>;//?InputForm?“仍然”使用?Button?組件,所以我們也需要這樣。//?JXS?或者普通的表單都會這樣做function?Button?(props)?{?//?這里返回一個?DOM?元素。例如: ?return?<button?type="submit">{props.label}</button>; }//?然后我們可以直接通過?.render?使用?InputFormReactDOM.render(InputForm,?mountNode);復制代碼
例 4:為什么在 React 中 JSX 受歡迎(和例 3 相比)
注意上面的幾件事:
這不是 HTML 代碼。比如,我們仍然可以使用 className 代替 class。
我們仍在考慮怎樣讓上述的 JavaScript 看起來像是 HTML。看一下我在最后是怎樣添加的。
我們在上面(例 4)中寫的就是 JSX。然而,我們要將編譯后的版本(例 3)給瀏覽器。要做到這一點,我們需要使用一個預處理器將 JSX 版本轉換為 React.createElement 版本。
這就是 JSX。這是一種折中的方案,允許我們用類似 HTML 的語法來編寫我們的 React 組件,這是一個很好的方法。
“Flux” 在頭部作為韻腳來使用,但它也是一個非常受歡迎的 應用架構,由 Facebook 推廣。最出名的是 Redux,Flux 和 React 非常合適。
JSX,可以單獨使用,不僅僅適用于 React。
3 你可以在 JavaScript 的任何地方使用 JSX
在 JSX 中,你可以在一對花括號內使用任何 JavaScript 表達式。
const?RandomValue?=?()?=>?<div> ?{?Math.floor(Math.random()?*?100)?} ?</div>; //?使用: ReactDOM.render(<RandomValue?/>,?mountNode);復制代碼
例 5:在 JSX 中使用 JavaScript 表達式
任何 JavaScript 表達式可以直接放在花括號中。這相當于在 JavaScript 中插入 ${} 模板。
這是 JSX 內唯一的約束:只有表達式。例如,你不能使用 if 語句,但三元表達式是可以的。
JavaScript 變量也是表達式,所以當組件接受屬性列表時(不包括 RandomValue 組件,props 是可選擇的),你可以在花括號里使用這些屬性。我們在上述(例 1)的 Button 組件是這樣使用的。
JavaScript 對象也是表達式。有些時候我們在花括號中使用 JavaScript 對象,這看起來像是使用了兩個花括號,但是在花括號中確實只有一個對象。其中一個用例就是將 CSS 樣式對象傳遞給響應中的特殊樣式屬性:
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 );復制代碼
例 6:一個對象傳遞特殊的 React 樣式參數
注意我解構的只是在屬性參數之外的信息。這只是 JavaScript。還要注意上面的樣式屬性是一個特殊的屬性(同樣,它不是 HTML,它更接近 DOM API)。我們使用一個對象作為樣式屬性的值并且這個對象定義樣式就像我們使用 JavaScript 那樣(我們可以這樣做)。
你可以在 JSX 中使用 React 元素。因為這也是一個表達式(記住,一個 React 元素只是一個函數調用):
const?MaybeError?=?({errorMessage})?=>?<div> ?{errorMessage?&&?<ErrorDisplay?message={errorMessage}?/>}?</div>; //?MaybeError?組件使用?ErrorDisplay?組件 const?ErrorDisplay?=?({message})?=>?<div?style={?{?color:?'red',?backgroundColor:?'yellow'?}?}>?{message} ?</div>; //?現在我們使用?MaybeError?組件: ReactDOM.render(?<MaybeError ?errorMessage={Math.random()?>?0.5???'Not?good'?:?''} ?/>, ?mountNode );復制代碼
例 7:一個 React 元素是一個可以通過 {} 使用的表達式
上述 MaybeError 組件只會在有 errorMessage 傳入或者另外有一個空的 div 才會顯示 ErrorDisplay 組件。React 認為 {true}、 {false}
{undefined} 和 {null} 是有效元素,不呈現任何內容。
我們也可以在 JSX 中使用所有的 JavaScript 的集合方法(map、reduce 、filter、 concat 等)。因為他們返回的也是表達式:
const?Doubler?=?({value=[1,?2,?3]})?=>?<div> ?{value.map(e?=>?e?*?2)} ?</div>; //?使用下面內容? ReactDOM.render(<Doubler?/>,?mountNode);復制代碼
例 8:在 {} 中使用數組
請注意我是如何給出上述 value 屬性的默認值的,因為這全部都只是 JavaScript。注意我只是在 div 中輸出一個數組表達式。React 是完全可以的。它只會在文本節點中放置每一個加倍的值。
4 你可以使用 JavaScript 類寫 React 組件
簡單的函數組件非常適合簡單的需求,但是有的時候我們需要的更多。React 也支持通過使用 JavaScript 類來創建組件。這里 Button 組件(在例 1 中)就是使用類的語法編寫的。
class?Button?extends?React.Component?{ ?render()?{?return?<button>{this.props.label}</button>; ?} }//?使用(相同的語法)ReactDOM.render(<Button?label="Save"?/>,?mountNode);復制代碼
例 9:使用 JavaScript 類創建組件
類的語法是非常簡單的:定義一個擴展的 React.Component 類(另一個你需要學習的 React 的頂級 API)。該類定義了一個單一的實例函數 —— render(),并使函數返回虛擬 DOM 對象。每一次我們使用基于類的 Button 組件(例如,通過 <Button ... />),React 將從這個基于類的組件中實例化對象,并在 DOM 樹中使用該對象。
這就是為什么上面的例子中我們可以在 JSX 中使用 this.props.label 渲染輸出的原因,因為每一個組件都有一個特殊的稱為 props 的?實例?屬性,這讓所有的值傳遞給該組件時被實例化。
由于我們有一個與組件的單個使用相關聯的實例,所以我們可以按照自己的意愿定制該實例。例如,我們可以通過使用常規 JavaScript 構造函數來構造它:
class?Button?extends?React.Component?{ ?constructor(props)?{?super(props);?this.id?=?Date.now(); ?} ?render()?{?return?<button?id={this.id}>{this.props.label}</button>; ?} }//?使用ReactDOM.render(<Button?label="Save"?/>,?mountNode);復制代碼
例 10:自定義組件實例
我們也可以定義類的原型并且在任何我們希望的地方使用,包括在返回的 JSX 輸出的內部:
class?Button?extends?React.Component?{ ?clickCounter?=?0; ?handleClick?=?()?=>?{ ?console.log(`Clicked:?${++this.clickCounter}`); ?}; ?render()?{?return?( ?<button?id={this.id}?onClick={this.handleClick}> ?{this.props.label} ?</button> ?); ?} }//?使用ReactDOM.render(<Button?label="Save"?/>,?mountNode);復制代碼
例 11:使用類的屬性(通過單擊保存按鈕進行測試)
注意上述例 11 中的幾件事情
handleClick 函數使用 JavaScript 新提出的 class-field syntax 語法。這仍然是 stage-2,但是這是訪問組件安裝實例(感謝箭頭函數)最好的選擇(因為很多原因)。然而,你需要使用類似 Babel 的編譯器解碼為 stage-2(或者僅僅是類字段語法)來讓上述代碼工作。 jsComplete REPL 有預編譯功能。
//?錯誤:onClick={this.handleClick()}//?正確:onClick={this.handleClick}復制代碼
5 React 中的事件:兩個重要的區別
當處理 React 元素中的事件時,我們與 DOM API 的處理方式有兩個非常重要的區別:
所有 React 元素屬性(包括事件)都使用?camelCase?命名,而不是?lowercase。例如是 onClick 而不是 onclick。
我們將實際的 JavaScript 函數引用傳遞給事件處理程序,而不是字符串。例如是 onClick={handleClick} 而不是 onClick="handleClick"。
React 用自己的對象包裝 DOM 對象事件以優化事件處理的性能,但是在事件處理程序內部,我們仍然可以訪問 DOM 對象上所有可以訪問的方法。React 將經過包裝的事件對象傳遞給每個調用函數。例如,為了防止表單提交默認提交操作,你可以這樣做:
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> ?); ?} }//?使用ReactDOM.render(<Form?/>,?mountNode);復制代碼
例 12:使用包裝過的對象
6 每一個 React 組件都有一個故事:第 1 部分
以下僅適用于類組件(擴展 React.Component)。函數組件有一個稍微不同的故事。
首先,我們定義了一個模板來創建組件中的元素。
然后,我們在某處使用 React。例如,在 render 內部調用其他的組件,或者直接使用 ReactDOM.render。
然后,React 實例化一個對象然后給它設置?props?然后我們可以通過 this.props 訪問。這些屬性都是我們在第 2 步傳入的。
因為這些全部都是 JavaScript,constructor 方法將會被調用(如果定義的話)。這是我們稱之為的第一個:組件生命周期方法。
接下來 React 計算渲染之后的輸出方法(虛擬 DOM 節點)。
因為這是 React 第一次渲染元素,React 將會與瀏覽器連通(代表我們使用 DOM API)來顯示元素。這整個過程稱為?mounting。
接下來 React 調用另一個生命周期函數,稱為 componentDidMount。我們可以這樣使用這個方法,例如:在 DOM 上做一些我們現在知道的在瀏覽器中存在的東西。在此生命周期方法之前,我們使用的 DOM 都是虛擬的。
一些組件的故事到此結束,其他組件得到卸載瀏覽器 DOM 中的各種原因。在后一種情況發生時,會調用另一個生命周期的方法,componentWillUnmount。
任何 mounted 的元素的狀態都可能會改變。該元素的父級可能會重新渲染。無論哪種情況,mounted 的元素都可能接收到不同的屬性集。React 的魔力就是這兒,我們實際上需要的正是 React 的這一點!在這一點之前,說實話,我們并不需要 React。
組價的故事還在繼續,但是在此之前,我們需要理解我所說的這種狀態。
7 React 組件可以具有私有狀態
以下只適用于類組件。我有沒有提到有人叫表象而已的部件?dumb?
狀態類是任何 React 類組件中的一個特殊字段。React 檢測每一個組件狀態的變化,但是為了 React 更加有效,我們必須通過 React 的另一個 API 改變狀態字段,這就是我們要學習的另一個 API —— this.setState:
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> ?); ?} }//?使用ReactDOM.render(<CounterButton?/>,?mountNode);復制代碼
例 13:setState 的 API
這可能是最重要的一個例子因為這將是你完全理解 React 基礎知識的方式。這個例子之后,還有一些小事情需要學習,但從那時起主要是你和你的 JavaScript 技能。
讓我們來看一下例 13,從類開始,總共有兩個,一個是一個初始化的有初始值為 0 的 clickCounter 對象和一個從 new Date() 開始的 currentTimestamp。
另一個類是 handleClick 函數,在渲染方法中我們給按鈕元素傳入 onClick 事件。通過使用 setState 的 handleClick 方法修改了組件的實例狀態。要注意到這一點。
另一個我們修改狀態的地方是在一個內部的定時器,開始在內部的 componentDidMount 生命周期方法。它每秒鐘調用一次并且執行另一個函數調用 this.setState。
在渲染方法中,我們使用具有正常讀語法的狀態上的兩個屬性(沒有專門的 API)。
現在,注意我們更新狀態使用兩種不同的方式:
通過傳入一個函數然后返回一個對象。我們在 handleClick 函數內部這樣做。
通過傳入一個正則對象,我們在在區間回調中這樣做。
這兩種方式都是可以接受的,但是當你同時讀寫狀態時,第一種方法是首選的(我們這樣做)。在區間回調中,我們只向狀態寫入而不讀它。當有問題時,總是使用第一個函數作為參數語法。伴隨著競爭條件這更安全,因為 setstate 實際上是一個異步方法。
我們應該怎樣更新狀態呢?我們返回一個有我們想要更新的的值的對象。注意,在調用 setState 時,我們全部都從狀態中傳入一個屬性或者全都不。這完全是可以的因為 setState 實際上?合并?了你通過它(返回值的函數參數)與現有的狀態,所以,沒有指定一個屬性在調用 setState 時意味著我們不希望改變屬性(但不刪除它)。
8 React 將要反應
React 的名字是從狀態改變的反應中得來的(雖然沒有反應,但也是在一個時間表中)。這里有一個笑話,React 應該被命名為Schedule!
然而,當任何組件的狀態被更新時,我們用肉眼觀察到的是對該更新的反應,并自動反映了瀏覽器 DOM 中的更新(如果需要的話)。
將渲染函數的輸入視為兩種:
通過父元素傳入的屬性
以及可以隨時更新的內部私有狀態
當渲染函數的輸入改變時,輸出可能也會改變。
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 的默認事件在大多數情況下都能處理的很好。
首先,這個時候會調用另一生命周期的方法:componentWillUpdate。然后,React 將計算新渲染過的輸出,并將其與最后渲染的輸出進行對比。
如果渲染過的輸出和之前的相同,React 不進行處理(不需要和瀏覽器先生對話)。
如果有不同的地方,React 將不同傳達給瀏覽器,像我們之前看到的那樣。
在任何情況下,一旦一個更新程序發生了,無論以何種方式(即使有相同的輸出),React 會調用最后的生命周期方法:componentDidUpdate。
生命周期方法是逃生艙口。如果你沒有做什么特別的事情,你可以在沒有它們的情況下創建完整的應用程序。它們非常方便地分析應用程序中正在發生的事情,并進一步優化 React 更新的性能。
感謝閱讀。如果您覺得這篇文章有幫助,請點擊下面的 。請關注我的更多關于 React.js 和 JavaScript 的文章。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。