- 原文地址:Learning React.js is easier than you think
- 原文做者:Samer Buna
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Cherry
- 校對者:LeviDing、undead25
你有沒有注意到在 React 的 logo 中隱藏着一個六角星?只是順便提下...
去年我寫了一本簡短的關於學習 React.js 的書,有 100 頁左右。今年,我要挑戰本身 —— 將其總結成一篇文章,並向 Medium 投稿。javascript
這篇文章不是講什麼是 React 或者 你該怎樣學習 React。這是在面向那些已經熟悉了 JavaScript 和 DOM API 的人的 React.js 基本原理介紹html
本文采用嵌入式 jsComplete 代碼段,因此爲了方便閱讀,你須要一個合適的屏幕寬度。前端
下面全部的代碼都僅供參考。它們也純粹是爲了表達概念而提供的例子。它們中的大多數有更好的實踐方式。java
您能夠編輯和執行下面的任何代碼段。使用 Ctrl+Enter 執行代碼。每一段的右下角有一個點擊後能夠在 jsComplete/repl 進行全屏模式編輯或運行代碼的連接。node
React 是圍繞可重用組件的概念設計的。你定義小組件並將它們組合在一塊兒造成更大的組件。react
不管大小,全部組件都是可重用的,甚至在不一樣的項目中也是如此。android
React 組件最簡單的形式,就是一個普通的 JavaScript 函數:ios
function Button (props) {
// 這裏返回一個 DOM 元素,例如:
return <button type="submit">{props.label}</button>;
}
// 將按鈕組件呈現給瀏覽器
ReactDOM.render(<Button label="Save" />, mountNode)複製代碼
例 1:編輯上面的代碼並按 Ctrl+Enter 鍵執行(譯者注:譯文暫時沒有這個功能,請訪問原文使用此功能,下同)git
括號中的 button 標籤將稍後解釋。如今不要擔憂它們。
ReactDOM
也將稍後解釋,但若是你想測試這個例子和全部接下來的例子,上述render
函數是必須的。(React 將要接管和控制的是ReactDOM.render
的第 2 個參數即目標 DOM 元素)。在 jsComplete REPL 中,你可使用特殊的變量mountNode
。github
例 1 的注意事項:
Button
。必需要這樣作是由於咱們將處理 HTML 元素和 React 元素的混合。小寫名稱是爲 HTML 元素保留的。事實上,將 React 組件命名爲 「button」 而後你就會發現 ReactDOM 會忽略這個函數,僅僅是將其做爲一個普通的空 HTML 按鈕來渲染。上面的例 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 方法直接使用 InputForm
ReactDOM.render(InputForm, mountNode);複製代碼
例 3:React 建立元素的 API
上面例子中的一些事情值得注意:
InputForm
不是一個 React 組件;它僅僅是一個 React 元素。這就是爲何咱們能夠在 ReactDOM.render
中直接使用它而且能夠在調用時不使用 <InputForm />
的緣由。React.createElement
調用,由於它是 JavaScript。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 使用 InputForm
ReactDOM.render(InputForm, mountNode);複製代碼
例 4:爲何在 React 中 JSX 受歡迎(和例 3 相比)
注意上面的幾件事:
className
代替 class
。咱們在上面(例 4)中寫的就是 JSX。然而,咱們要將編譯後的版本(例 3)給瀏覽器。要作到這一點,咱們須要使用一個預處理器將 JSX 版本轉換爲 React.createElement
版本。
這就是 JSX。這是一種折中的方案,容許咱們用相似 HTML 的語法來編寫咱們的 React 組件,這是一個很好的方法。
「Flux」 在頭部做爲韻腳來使用,但它也是一個很是受歡迎的 應用架構,由 Facebook 推廣。最出名的是 Redux,Flux 和 React 很是合適。
JSX,能夠單獨使用,不只僅適用於 React。
在 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 是徹底能夠的。它只會在文本節點中放置每個加倍的值。
簡單的函數組件很是適合簡單的需求,可是有的時候咱們須要的更多。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}複製代碼
當處理 React 元素中的事件時,咱們與 DOM API 的處理方式有兩個很是重要的區別:
onClick
而不是 onclick
。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:使用包裝過的對象
如下僅適用於類組件(擴展 React.Component
)。函數組件有一個稍微不一樣的故事。
render
內部調用其餘的組件,或者直接使用 ReactDOM.render
。this.props
訪問。這些屬性都是咱們在第 2 步傳入的。constructor
方法將會被調用(若是定義的話)。這是咱們稱之爲的第一個:組件生命週期方法。componentDidMount
。咱們能夠這樣使用這個方法,例如:在 DOM 上作一些咱們如今知道的在瀏覽器中存在的東西。在今生命週期方法以前,咱們使用的 DOM 都是虛擬的。componentWillUnmount
。如下只適用於類組件。我有沒有提到有人叫表象而已的部件 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
時意味着咱們不但願改變屬性(但不刪除它)。
React 的名字是從狀態改變的反應中得來的(雖然沒有反應,但也是在一個時間表中)。這裏有一個笑話,React 應該被命名爲Schedule!
然而,當任何組件的狀態被更新時,咱們用肉眼觀察到的是對該更新的反應,並自動反映了瀏覽器 DOM 中的更新(若是須要的話)。
將渲染函數的輸入視爲兩種:
當渲染函數的輸入改變時,輸出可能也會改變。
React 保存了渲染的歷史記錄,當它看到一個渲染與前一個不一樣時,它將計算它們之間的差別,並將其有效地轉換爲在 DOM 中執行的實際 DOM 操做。
您能夠將 React 看做是咱們用來與瀏覽器通訊的代理。以上面的當前時間戳顯示爲例。取代每一秒咱們都須要手動去瀏覽器調用 DOM API 操做來查找和更新 p#timestamp
元素,咱們僅僅改變組件的狀態屬性,React 作的工做表明咱們與瀏覽器的通訊。我相信這就是爲何 React 這麼受歡迎的真正緣由;咱們只是不喜歡和瀏覽器先生談話(以及它所說的 DOM 語言的不少方言),而且 React 自願傳遞給咱們,免費的!
如今咱們知道了一個組件的狀態,當該狀態發生變化的時候,咱們來了解一下關於這個過程的最後幾個概念。
componentWillReceiveProps
。shouldComponentUpdate
的緣由 。此方法是一個實際問題,所以,若是須要自行定製或優化渲染過程,則必須經過返回 true 或 false 來回答這個問題。shouldComponentUpdate
,React 的默認事件在大多數狀況下都能處理的很好。componentWillUpdate
。而後,React 將計算新渲染過的輸出,並將其與最後渲染的輸出進行對比。componentDidUpdate
。生命週期方法是逃生艙口。若是你沒有作什麼特別的事情,你能夠在沒有它們的狀況下建立完整的應用程序。它們很是方便地分析應用程序中正在發生的事情,並進一步優化 React 更新的性能。
信不信由你,經過上面所學的知識(或部分知識),你能夠開始建立一些有趣的 React 應用程序。若是你渴望更多,看看個人 Pluralsight 的 React.js 入門課程。
感謝閱讀。若是您以爲這篇文章有幫助,請點擊下面的 💚。請關注個人更多關於 React.js 和 JavaScript 的文章。
我 Pluralsight 和 Lynda 建立了在線課程。我最新的文章在Advanced React.js、 Advanced Node.js 和 Learning Full-stack JavaScript中。我也作小組的在線和現場培訓,覆蓋初級到高級的 JavaScript、 Node.js、 React.js、GraphQL。若是你須要一個導師,請來找我 。若是你對此篇文章或者我寫的其餘任何文章有疑問,經過這個聯繫我,而且在 #questions 中提問。
感謝不少檢驗和改進這篇文章的讀者,Łukasz Szewczak、Tim Broyles、 Kyle Holden、 Robert Axelse、 Bruce Lane、Irvin Waldman 和 Amie Wilt.
特別要感謝「驚人的」 Amie,經驗是一個實際的 Unicorn。謝謝你全部的幫助,Anime,真的很是感謝你。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。