新聞中心
通過 Medium 中的一篇文章來學習 React.js 的基本原理
你有沒有注意到在 React 的 logo 中隱藏著一個六角星?只是順便提下...
去年我寫了一本簡短的關(guān)于學習 React.js 的書,有 100 頁左右。今年,我要挑戰(zhàn)自己 —— 將其總結(jié)成一篇文章,并向 Medium 投稿。
這篇文章不是講什么是 React 或者 你該怎樣學習 React。這是在面向那些已經(jīng)熟悉了 JavaScript 和 DOM API 的人的 React.js 基本原理介紹
本文采用嵌入式 jsComplete 代碼段,所以為了方便閱讀,你需要一個合適的屏幕寬度。
下面所有的代碼都僅供參考。它們也純粹是為了表達概念而提供的例子。它們中的大多數(shù)有更好的實踐方式。
您可以編輯和執(zhí)行下面的任何代碼段。使用?Ctrl+Enter?執(zhí)行代碼。每一段的右下角有一個點擊后可以在 jsComplete/repl 進行全屏模式編輯或運行代碼的鏈接。
1 React 全部都是組件化的
React 是圍繞可重用組件的概念設(shè)計的。你定義小組件并將它們組合在一起形成更大的組件。
無論大小,所有組件都是可重用的,甚至在不同的項目中也是如此。
React 組件最簡單的形式,就是一個普通的 JavaScript 函數(shù):
function?Button?(props)?{?//?這里返回一個?DOM?元素,例如: ?return?; }//?將按鈕組件呈現(xiàn)給瀏覽器ReactDOM.render(,?mountNode)復制代碼
例 1:編輯上面的代碼并按 Ctrl+Enter 鍵執(zhí)行(譯者注:譯文暫時沒有這個功能,請訪問原文使用此功能,下同)
括號中的 button 標簽將稍后解釋?,F(xiàn)在不要擔心它們。ReactDOM 也將稍后解釋,但如果你想測試這個例子和所有接下來的例子,上述 render 函數(shù)是必須的。(React 將要接管和控制的是 ReactDOM.render 的第 2 個參數(shù)即目標 DOM 元素)。在 jsComplete REPL 中,你可以使用特殊的變量 mountNode。
例 1 的注意事項:
組件名稱首字母大寫,Button。必須要這樣做是因為我們將處理 HTML 元素和 React 元素的混合。小寫名稱是為 HTML 元素保留的。事實上,將 React 組件命名為 “button” 然后你就會發(fā)現(xiàn) ReactDOM 會忽略這個函數(shù),僅僅是將其作為一個普通的空 HTML 按鈕來渲染。
每個組件都接收一個屬性列表,就像 HTML 元素一樣。在 React 中,這個列表被稱為屬性。雖然你可以將一個函數(shù)隨意命名。
在上面 Button 函數(shù)組件的返回輸出中,我們奇怪地寫了段看上去像 HTML 的代碼。這實際上既不是 JavaScript 也不是 HTML,老實說,這甚至不是 React.js。然而它非常流行,以至于成為 React 應(yīng)用程序中的默認值。這就是所謂的 JSX,這是一個JavaScript 的擴展。JSX 也是一個折中方案!繼續(xù)嘗試并在上面的函數(shù)中返回其他 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 函數(shù)是主函數(shù)。這是你需要學習的 7 個 API 中的 1 個。React 的 API 就是這么小。
就像 DOM 自身有一個 document.createElement 函數(shù)來創(chuàng)建一個由標簽名指定的元素一樣,React 的 createElement 函數(shù)是一個高級函數(shù),有和 document.createElement 同樣的功能,但它也可以用于創(chuàng)建一個表示 React 組件的元素。當我們使用上面例 2 中的按鈕組件時,我們使用的是后者。
不像 document.createElement,React 的 createElement 在接收第二個參數(shù)后,接收一個動態(tài)參數(shù),它表示所創(chuàng)建元素的子元素。所以 createElement 實際上創(chuàng)建了一個樹。
這里就是這樣的一個例子:
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 創(chuàng)建元素的 API
上面例子中的一些事情值得注意:
InputForm 不是一個 React 組件;它僅僅是一個 React?元素。這就是為什么我們可以在 ReactDOM.render 中直接使用它并且可以在調(diào)用時不使用
的原因。 React.createElement 函數(shù)在前兩個參數(shù)后接收了多個參數(shù)。從第3個參數(shù)開始的參數(shù)列表構(gòu)成了創(chuàng)建元素的子項列表。
我們可以嵌套 React.createElement 調(diào)用,因為它是 JavaScript。
當這個元素不需要屬性時,React.createElement 的第二個參數(shù)可以為空或者是一個空對象。
我們可以在 React 組件中混合 HTML 元素。你可以將 HTML 元素作為內(nèi)置的 React 組件。
React 的 API 試圖和 DOM API 一樣,這就是為什么我們在 input 元素中使用 className 代替 class 的原因。我們都希望如果 React 的 API 成為 DOM API 本身的一部分,因為,你知道,它要好得多。
上述的代碼是當你引入 React 庫的時候瀏覽器是怎樣理解的。瀏覽器不會處理任何 JSX 業(yè)務(wù)。然而,我們更喜歡看到和使用 HTML,而不是那些 createElement 調(diào)用(想象一下只使用 document.createElement 構(gòu)建一個網(wǎng)站?。?。這就是 JSX 存在的原因。取代上述調(diào)用 React.createElement 的方式,我們可以使用一個非常簡單類似于 HTML 的語法:
const?InputForm?= ?;//?InputForm?“仍然”使用?Button?組件,所以我們也需要這樣。//?JXS?或者普通的表單都會這樣做function?Button?(props)?{?//?這里返回一個?DOM?元素。例如: ?return?; }//?然后我們可以直接通過?.render?使用?InputFormReactDOM.render(InputForm,?mountNode);復制代碼
例 4:為什么在 React 中 JSX 受歡迎(和例 3 相比)
注意上面的幾件事:
這不是 HTML 代碼。比如,我們?nèi)匀豢梢允褂?className 代替 class。
我們?nèi)栽诳紤]怎樣讓上述的 JavaScript 看起來像是 HTML??匆幌挛以谧詈笫窃鯓犹砑拥?。
我們在上面(例 4)中寫的就是 JSX。然而,我們要將編譯后的版本(例 3)給瀏覽器。要做到這一點,我們需要使用一個預處理器將 JSX 版本轉(zhuǎn)換為 React.createElement 版本。
這就是 JSX。這是一種折中的方案,允許我們用類似 HTML 的語法來編寫我們的 React 組件,這是一個很好的方法。
“Flux” 在頭部作為韻腳來使用,但它也是一個非常受歡迎的 應(yīng)用架構(gòu),由 Facebook 推廣。最出名的是 Redux,F(xiàn)lux 和 React 非常合適。
JSX,可以單獨使用,不僅僅適用于 React。
3 你可以在 JavaScript 的任何地方使用 JSX
在 JSX 中,你可以在一對花括號內(nèi)使用任何 JavaScript 表達式。
const?RandomValue?=?()?=>??{?Math.floor(Math.random()?*?100)?} ?; //?使用: ReactDOM.render(,?mountNode);復制代碼
例 5:在 JSX 中使用 JavaScript 表達式
任何 JavaScript 表達式可以直接放在花括號中。這相當于在 JavaScript 中插入 ${} 模板。
這是 JSX 內(nèi)唯一的約束:只有表達式。例如,你不能使用 if 語句,但三元表達式是可以的。
JavaScript 變量也是表達式,所以當組件接受屬性列表時(不包括 RandomValue 組件,props 是可選擇的),你可以在花括號里使用這些屬性。我們在上述(例 1)的 Button 組件是這樣使用的。
JavaScript 對象也是表達式。有些時候我們在花括號中使用 JavaScript 對象,這看起來像是使用了兩個花括號,但是在花括號中確實只有一個對象。其中一個用例就是將 CSS 樣式對象傳遞給響應(yīng)中的特殊樣式屬性:
const?ErrorDisplay?=?({message})?=>??{message} ?; //?使用 ReactDOM.render(?, ?mountNode );復制代碼
例 6:一個對象傳遞特殊的 React 樣式參數(shù)
注意我解構(gòu)的只是在屬性參數(shù)之外的信息。這只是 JavaScript。還要注意上面的樣式屬性是一個特殊的屬性(同樣,它不是 HTML,它更接近 DOM API)。我們使用一個對象作為樣式屬性的值并且這個對象定義樣式就像我們使用 JavaScript 那樣(我們可以這樣做)。
你可以在 JSX 中使用 React 元素。因為這也是一個表達式(記住,一個 React 元素只是一個函數(shù)調(diào)用):
const?MaybeError?=?({errorMessage})?=>??{errorMessage?&&?; //?MaybeError?組件使用?ErrorDisplay?組件 const?ErrorDisplay?=?({message})?=>?}? ?{message} ?; //?現(xiàn)在我們使用?MaybeError?組件: ReactDOM.render(??0.5???'Not?good'?:?''} ?/>, ?mountNode );復制代碼
例 7:一個 React 元素是一個可以通過 {} 使用的表達式
上述 MaybeError 組件只會在有 errorMessage 傳入或者另外有一個空的 div 才會顯示 ErrorDisplay 組件。React 認為 {true}、 {false}
{undefined} 和 {null} 是有效元素,不呈現(xiàn)任何內(nèi)容。
我們也可以在 JSX 中使用所有的 JavaScript 的集合方法(map、reduce 、filter、 concat 等)。因為他們返回的也是表達式:
const?Doubler?=?({value=[1,?2,?3]})?=>??{value.map(e?=>?e?*?2)} ?; //?使用下面內(nèi)容? ReactDOM.render(,?mountNode);復制代碼
例 8:在 {} 中使用數(shù)組
請注意我是如何給出上述 value 屬性的默認值的,因為這全部都只是 JavaScript。注意我只是在 div 中輸出一個數(shù)組表達式。React 是完全可以的。它只會在文本節(jié)點中放置每一個加倍的值。
4 你可以使用 JavaScript 類寫 React 組件
簡單的函數(shù)組件非常適合簡單的需求,但是有的時候我們需要的更多。React 也支持通過使用 JavaScript 類來創(chuàng)建組件。這里 Button 組件(在例 1 中)就是使用類的語法編寫的。
class?Button?extends?React.Component?{ ?render()?{?return?; ?} }//?使用(相同的語法)ReactDOM.render(,?mountNode);復制代碼
例 9:使用 JavaScript 類創(chuàng)建組件
類的語法是非常簡單的:定義一個擴展的 React.Component 類(另一個你需要學習的 React 的頂級 API)。該類定義了一個單一的實例函數(shù) —— render(),并使函數(shù)返回虛擬 DOM 對象。每一次我們使用基于類的 Button 組件(例如,通過 ),React 將從這個基于類的組件中實例化對象,并在 DOM 樹中使用該對象。
這就是為什么上面的例子中我們可以在 JSX 中使用 this.props.label 渲染輸出的原因,因為每一個組件都有一個特殊的稱為 props 的?實例?屬性,這讓所有的值傳遞給該組件時被實例化。
由于我們有一個與組件的單個使用相關(guān)聯(lián)的實例,所以我們可以按照自己的意愿定制該實例。例如,我們可以通過使用常規(guī) JavaScript 構(gòu)造函數(shù)來構(gòu)造它:
class?Button?extends?React.Component?{ ?constructor(props)?{?super(props);?this.id?=?Date.now(); ?} ?render()?{?return?; ?} }//?使用ReactDOM.render(,?mountNode);復制代碼
例 10:自定義組件實例
我們也可以定義類的原型并且在任何我們希望的地方使用,包括在返回的 JSX 輸出的內(nèi)部:
class?Button?extends?React.Component?{ ?clickCounter?=?0; ?handleClick?=?()?=>?{ ?console.log(`Clicked:?${++this.clickCounter}`); ?}; ?render()?{?return?( ? ?); ?} }//?使用ReactDOM.render(,?mountNode);復制代碼
例 11:使用類的屬性(通過單擊保存按鈕進行測試)
注意上述例 11 中的幾件事情
handleClick 函數(shù)使用 JavaScript 新提出的 class-field syntax 語法。這仍然是 stage-2,但是這是訪問組件安裝實例(感謝箭頭函數(shù))最好的選擇(因為很多原因)。然而,你需要使用類似 Babel 的編譯器解碼為 stage-2(或者僅僅是類字段語法)來讓上述代碼工作。 jsComplete REPL 有預編譯功能。
//?錯誤:onClick={this.handleClick()}//?正確:onClick={this.handleClick}復制代碼
5 React 中的事件:兩個重要的區(qū)別
當處理 React 元素中的事件時,我們與 DOM API 的處理方式有兩個非常重要的區(qū)別:
所有 React 元素屬性(包括事件)都使用?camelCase?命名,而不是?lowercase。例如是 onClick 而不是 onclick。
我們將實際的 JavaScript 函數(shù)引用傳遞給事件處理程序,而不是字符串。例如是 onClick={handleClick} 而不是 onClick="handleClick"。
React 用自己的對象包裝 DOM 對象事件以優(yōu)化事件處理的性能,但是在事件處理程序內(nèi)部,我們?nèi)匀豢梢栽L問 DOM 對象上所有可以訪問的方法。React 將經(jīng)過包裝的事件對象傳遞給每個調(diào)用函數(shù)。例如,為了防止表單提交默認提交操作,你可以這樣做:
class?Form?extends?React.Component?{ ?handleSubmit?=?(event)?=>?{ ?event.preventDefault(); ?console.log('Form?submitted'); ?}; ?render()?{?return?( ??); ?} }//?使用ReactDOM.render(,?mountNode);復制代碼
例 12:使用包裝過的對象
6 每一個 React 組件都有一個故事:第 1 部分
以下僅適用于類組件(擴展 React.Component)。函數(shù)組件有一個稍微不同的故事。
首先,我們定義了一個模板來創(chuàng)建組件中的元素。
然后,我們在某處使用 React。例如,在 render 內(nèi)部調(diào)用其他的組件,或者直接使用 ReactDOM.render。
然后,React 實例化一個對象然后給它設(shè)置?props?然后我們可以通過 this.props 訪問。這些屬性都是我們在第 2 步傳入的。
因為這些全部都是 JavaScript,constructor 方法將會被調(diào)用(如果定義的話)。這是我們稱之為的第一個:組件生命周期方法。
接下來 React 計算渲染之后的輸出方法(虛擬 DOM 節(jié)點)。
因為這是 React 第一次渲染元素,React 將會與瀏覽器連通(代表我們使用 DOM API)來顯示元素。這整個過程稱為?mounting。
接下來 React 調(diào)用另一個生命周期函數(shù),稱為 componentDidMount。我們可以這樣使用這個方法,例如:在 DOM 上做一些我們現(xiàn)在知道的在瀏覽器中存在的東西。在此生命周期方法之前,我們使用的 DOM 都是虛擬的。
一些組件的故事到此結(jié)束,其他組件得到卸載瀏覽器 DOM 中的各種原因。在后一種情況發(fā)生時,會調(diào)用另一個生命周期的方法,componentWillUnmount。
任何 mounted 的元素的狀態(tài)都可能會改變。該元素的父級可能會重新渲染。無論哪種情況,mounted 的元素都可能接收到不同的屬性集。React 的魔力就是這兒,我們實際上需要的正是 React 的這一點!在這一點之前,說實話,我們并不需要 React。
組價的故事還在繼續(xù),但是在此之前,我們需要理解我所說的這種狀態(tài)。
7 React 組件可以具有私有狀態(tài)
以下只適用于類組件。我有沒有提到有人叫表象而已的部件?dumb?
狀態(tài)類是任何 React 類組件中的一個特殊字段。React 檢測每一個組件狀態(tài)的變化,但是為了 React 更加有效,我們必須通過 React 的另一個 API 改變狀態(tài)字段,這就是我們要學習的另一個 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?(?? ??); ?} }//?使用ReactDOM.render(Clicked:?{this.state.clickCounter}
?Time:?{this.state.currentTimestamp.toLocaleString()}
?,?mountNode);復制代碼
例 13:setState 的 API
這可能是最重要的一個例子因為這將是你完全理解 React 基礎(chǔ)知識的方式。這個例子之后,還有一些小事情需要學習,但從那時起主要是你和你的 JavaScript 技能。
讓我們來看一下例 13,從類開始,總共有兩個,一個是一個初始化的有初始值為 0 的 clickCounter 對象和一個從 new Date() 開始的 currentTimestamp。
另一個類是 handleClick 函數(shù),在渲染方法中我們給按鈕元素傳入 onClick 事件。通過使用 setState 的 handleClick 方法修改了組件的實例狀態(tài)。要注意到這一點。
另一個我們修改狀態(tài)的地方是在一個內(nèi)部的定時器,開始在內(nèi)部的 componentDidMount 生命周期方法。它每秒鐘調(diào)用一次并且執(zhí)行另一個函數(shù)調(diào)用 this.setState。
在渲染方法中,我們使用具有正常讀語法的狀態(tài)上的兩個屬性(沒有專門的 API)。
現(xiàn)在,注意我們更新狀態(tài)使用兩種不同的方式:
通過傳入一個函數(shù)然后返回一個對象。我們在 handleClick 函數(shù)內(nèi)部這樣做。
通過傳入一個正則對象,我們在在區(qū)間回調(diào)中這樣做。
這兩種方式都是可以接受的,但是當你同時讀寫狀態(tài)時,第一種方法是選的(我們這樣做)。在區(qū)間回調(diào)中,我們只向狀態(tài)寫入而不讀它。當有問題時,總是使用第一個函數(shù)作為參數(shù)語法。伴隨著競爭條件這更安全,因為 setstate 實際上是一個異步方法。
我們應(yīng)該怎樣更新狀態(tài)呢?我們返回一個有我們想要更新的的值的對象。注意,在調(diào)用 setState 時,我們?nèi)慷紡臓顟B(tài)中傳入一個屬性或者全都不。這完全是可以的因為 setState 實際上?合并?了你通過它(返回值的函數(shù)參數(shù))與現(xiàn)有的狀態(tài),所以,沒有指定一個屬性在調(diào)用 setState 時意味著我們不希望改變屬性(但不刪除它)。
8 React 將要反應(yīng)
React 的名字是從狀態(tài)改變的反應(yīng)中得來的(雖然沒有反應(yīng),但也是在一個時間表中)。這里有一個笑話,React 應(yīng)該被命名為Schedule!
然而,當任何組件的狀態(tài)被更新時,我們用肉眼觀察到的是對該更新的反應(yīng),并自動反映了瀏覽器 DOM 中的更新(如果需要的話)。
將渲染函數(shù)的輸入視為兩種:
通過父元素傳入的屬性
以及可以隨時更新的內(nèi)部私有狀態(tài)
當渲染函數(shù)的輸入改變時,輸出可能也會改變。
React 保存了渲染的歷史記錄,當它看到一個渲染與前一個不同時,它將計算它們之間的差異,并將其有效地轉(zhuǎn)換為在 DOM 中執(zhí)行的實際 DOM 操作。
9 React 是你的代碼
您可以將 React 看作是我們用來與瀏覽器通信的代理。以上面的當前時間戳顯示為例。取代每一秒我們都需要手動去瀏覽器調(diào)用 DOM API 操作來查找和更新 p#timestamp 元素,我們僅僅改變組件的狀態(tài)屬性,React 做的工作代表我們與瀏覽器的通信。我相信這就是為什么 React 這么受歡迎的真正原因;我們只是不喜歡和瀏覽器先生談話(以及它所說的 DOM 語言的很多方言),并且 React 自愿傳遞給我們,免費的!
10 每一個 React 組件都有一個故事:第 2 部分
現(xiàn)在我們知道了一個組件的狀態(tài),當該狀態(tài)發(fā)生變化的時候,我們來了解一下關(guān)于這個過程的最后幾個概念。
當組件的狀態(tài)被更新時,或者它的父進程決定更改它傳遞給組件的屬性時,組件可能需要重新渲染。
如果后者發(fā)生,React 會調(diào)用另一個生命周期方法:componentWillReceiveProps。
如果狀態(tài)對象或傳遞的屬性改變了,React 有一個重要的決定要做:組件是否應(yīng)該在 DOM 中更新?這就是為什么它調(diào)用另一個重要的生命周期方法 shouldComponentUpdate 的原因 。此方法是一個實際問題,因此,如果需要自行定制或優(yōu)化渲染過程,則必須通過返回 true 或 false 來回答這個問題。
如果沒有自定義 shouldComponentUpdate,React 的默認事件在大多數(shù)情況下都能處理的很好。
首先,這個時候會調(diào)用另一生命周期的方法:componentWillUpdate。然后,React 將計算新渲染過的輸出,并將其與最后渲染的輸出進行對比。
如果渲染過的輸出和之前的相同,React 不進行處理(不需要和瀏覽器先生對話)。
如果有不同的地方,React 將不同傳達給瀏覽器,像我們之前看到的那樣。
在任何情況下,一旦一個更新程序發(fā)生了,無論以何種方式(即使有相同的輸出),React 會調(diào)用最后的生命周期方法:componentDidUpdate。
生命周期方法是逃生艙口。如果你沒有做什么特別的事情,你可以在沒有它們的情況下創(chuàng)建完整的應(yīng)用程序。它們非常方便地分析應(yīng)用程序中正在發(fā)生的事情,并進一步優(yōu)化 React 更新的性能。
感謝閱讀。如果您覺得這篇文章有幫助,請點擊下面的 。請關(guān)注我的更多關(guān)于 React.js 和 JavaScript 的文章。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
分享文章:學習React.js比你想象的要簡單-創(chuàng)新互聯(lián)
網(wǎng)頁網(wǎng)址:http://fisionsoft.com.cn/article/dphsee.html