一篇包含了react所有基本點的文章
去年,我寫了一本關於學習React.js的小書,原來是大約100頁。 今年我要挑戰自己,把它歸納為一篇文章。
本文不會涵蓋什麼是React,或者為什麼要學習它。 相反,這是面向已經熟悉JavaScript並熟悉DOM API基礎知識的人,對React.js的基礎知識的介紹。
以下所有程式碼示例均標示為參考。 它們純粹是為了提供概念而寫的例子。 他們大多數可以寫得更好一些。
1:元件是React的一切
React是圍繞可重用元件的概念設計的。 您定義小元件,並將它們放在一起形成更大的元件。
所有小或小的元件都可重複使用,甚至跨不同的專案。
一個React元件(以其最簡單的形式)是一個簡單的JavaScript函式:
// Example 1 // https://jscomplete.com/repl?j=Sy3QAdKHW function Button (props) { // Returns a DOM element here. For example: return <button type="submit">{props.label}</button>; } // To render the Button component to the browser 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
。建立功能元件,你可以通過使用任意名稱命名props
。 - 在上面的Button元件的返回中,我們寫出了奇怪的HTML。 這既不是JavaScript也不是HTML,甚至不是React.js。 但是,它非常受歡迎,成為React應用程式中的預設設定。 它被稱為JSX,它是一個JavaScript擴充套件。 JSX也是妥協! 繼續嘗試在上面的函式中的任何其他HTML元素,並檢視它們是如何支援的(例如,返回一個文字輸入元素)。
2: What the flux is JSX?
上面的示例1可以用純粹的React.js來編寫,而不需要JSX,如下所示:
// Example 2 - React component without JSX
// https://jscomplete.com/repl?j=HyiEwoYB-
function Button (props) {
return React.createElement(
"button",
{ type: "submit" },
props.label
);
}
// To use Button, you would do something like
ReactDOM.render(
React.createElement(Button, { label: "Save" }),
mountNode
);
createElement
函式是React頂級API中函式。 您需要學習的這個級別中共有7件事情中的1項。 可見ReactApi多麼簡短。
很像DOM本身有一個document.createElement
函式來建立一個由標籤名稱指定的元素,React的createElement
函式是一個更高級別的函式,可以做類似於document.createElement
的功能。 但它也可以用於建立一個表示React元件的元素。 當我們使用上面的例2中的Button元件時,我們這裡就是建立了一個React元件。
與document.createElement
不同,React的createElement
可以接受第二個引數之後的動態引數,以表示建立的元素的後代。 所以createElement
實際上建立一個樹。
這是一個例子:
/ Example 3 - React’s createElement API
// https://jscomplete.com/repl?j=r1GNoiFBb
const InputForm = React.createElement(
"form",
{ target: "_blank", action: "https://google.com/search" },
React.createElement("div", null, "Enter input and click Search"),
React.createElement("input", { name: "q", className: "input" }),
React.createElement(Button, { label: "Search" })
);
// InputForm uses the Button component, so we need that too:
function Button (props) {
return React.createElement(
"button",
{ type: "submit" },
props.label
);
}
// Then we can use InputForm directly with .render
ReactDOM.render(InputForm, mountNode);
關於以上例子要注意的幾點:
-
InputForm
不是React元件; 它只是一個React元素。 這就是為什麼我們直接在ReactDOM.render
呼叫中使用它,而不是使用<InputForm />
。 - 我們可以巢狀
React.createElement
呼叫,因為它都是JavaScript。 -
React.createElement
的第二個引數可以是null,也可以是一個空物件,當元素不需要attributes和props時。 - 我們可以將HTML元素與React元件混合使用。 您可以將HTML元素視為內建的React元件。
- React的API嘗試儘可能接近DOM API,因此我們為輸入元素使用className而不是類。 私以為,我們都希望React的API將成為DOM API本身的一部分。 因為,你知道的,這有太多的好處了。
上面的程式碼是您在引入React庫時瞭解的內容。 瀏覽器不處理任何JSX業務。 然而,我們人類喜歡看HTML並且使用HTML而不是這些createElement
呼叫(想象一下使用document.createElement
構建一個網站,我相信你可以的!)。 這就是為什麼存在JSX的原因。 我們可以用非常類似於HTML的語法編寫它,而不是用React.createElement
呼叫上面的表單:
// Example 4 - JSX (compare with Example 3)
// https://jscomplete.com/repl?j=SJWy3otHW
const InputForm =
<form target="_blank" action="https://google.com/search">
<div>Enter input and click Search</div>
<input name="q" className="input" />
<Button label="Search" />
</form>;
// InputForm "still" uses the Button component, so we need that too.
// Either JSX or normal form would do
function Button (props) {
// Returns a DOM element here. For example:
return <button type="submit">{props.label}</button>;
}
// Then we can use InputForm directly with .render
ReactDOM.render(InputForm, mountNode);
關於上面的例子注意以下幾點
- 它不是HTML。 例如,我們仍然在使用className而不是類。
- 我們仍然在考慮將以上HTML作為JavaScript。 看看我在末尾添加了分號。
我們上面寫的(例4)是JSX。 然而,我們在瀏覽器的執行版本是它的編譯版本(示例3)。 為了實現這一點,我們需要使用前處理器將JSX版本轉換為React.createElement
版本。
那就是JSX。 這是一個折中,允許我們以類似於HTML的語法編寫我們的React元件,這是一個很好的共識。
上面標題中的“Flux”一詞被選為韻腳(...),但它也是Facebook流行的非常受歡迎的應用程式架構的名稱。 最著名的實現是Redux。
JSX,順便說一下,可以自己在其他地方使用。 這不是隻有在React中才可以使用的。
3: 您可以在JSX中的任何位置使用JavaScript表示式
在JSX部分中,您可以在一對花括號內使用任何JavaScript表示式。
// Example 5 - Using JavaScript expressions in JSX
// https://jscomplete.com/repl?j=SkNN3oYSW
const RandomValue = () =>
<div>
{ Math.floor(Math.random() * 100) }
</div>;
// To use it:
ReactDOM.render(<RandomValue />, mountNode);
任何JavaScript表示式都可以放在那些花括號內。 這相當於JavaScript模板文字中的$ {}
插值語法。
這是JSX中唯一的約束:只有表示式。 所以,你不能使用常規的if語句,但是三元表示式是可以的。
JavaScript變數也是表示式,所以當元件接收到props
列表(RandomValue元件沒有,props
是可選的)時,可以在花括號內使用這些props
。 我們在上面的Button元件中這樣做了(示例1)。
JavaScript物件也是表示式。 有時候,我們在一個花括號裡面使用一個JavaScript物件,這使得它看起來像雙花括號,但它實際上只是一個大括號內的一個物件。 一個用例是將CSS樣式物件傳遞給React中的style屬性:
// Example 6 - An object passed to the special React style prop
// https://jscomplete.com/repl?j=S1Kw2sFHb
const ErrorDisplay = ({message}) =>
<div style={ { color: 'red', backgroundColor: 'yellow' } }>
{message}
</div>;
// Use it:
ReactDOM.render(
<ErrorDisplay
message="These aren't the droids you're looking for"
/>,
mountNode
);
請注意,我如何僅解析props
引數中的message的。 這是JavaScript。 還要注意上面的style屬性是一個特殊的屬性(再次,它不是HTML,它更接近於DOM API)。 我們使用一個物件作為style屬性的值。 該物件定義了樣式,就像我們使用JavaScript一樣(因為確實就是)。
甚至可以在JSX中使用React元素,因為這也是一個表示式。 記住,一個React元素就是一個函式呼叫:
const MaybeError = ({errorMessage}) =>
<div>
{errorMessage && <ErrorDisplay message={errorMessage} />}
</div>;
// The MaybeError component uses the ErrorDisplay component:
const ErrorDisplay = ({message}) =>
<div style={ { color: 'red', backgroundColor: 'yellow' } }>
{message}
</div>;
// Now we can use the MaybeError component:
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等)。 再次宣告原因是因為它們返回的是表示式:
// Example 8 - Using an array map inside {}
// https://jscomplete.com/repl?j=SJ29aiYH-
const Doubler = ({value=[1, 2, 3]}) =>
<div>
{value.map(e => e * 2)}
</div>;
// Use it
ReactDOM.render(<Doubler />, mountNode);
請注意,我是如何給value
props預設值的,因為它全是Javascript。 還要注意,我在div中輸出了一個數組表示式,這在React中是可行的。 它將把每一個雙倍的值放在一個文字節點中。
4: 您可以使用JavaScript類編寫React元件
簡單的功能元件非常適合簡單的需求,但有時我們需要更多的功能。 React支援通過JavaScript類語法建立元件。 這是使用類語法編寫的Button元件(在示例1中):
// Example 9 - Creating components using JavaScript classes
// https://jscomplete.com/repl?j=ryjk0iKHb
class Button extends React.Component {
render() {
return <button>{this.props.label}</button>;
}
}
// Use it (same syntax)
ReactDOM.render(<Button label="Save" />, mountNode);
類語法很簡單。 定義一個擴充套件了React.Component基類的類(需要學習的另一個頂級的React API)。 該類定義一個唯一例項函式render(),該render函式返回虛擬DOM物件。 每次我們使用上面的基於Button類的元件(例如,通過執行<Button ... />),React將從這個基於類的元件中例項化一個物件,並在DOM樹中使用該物件。
這就是為什麼我們在上面的渲染輸出中在JSX中使用this.props.label
的原因。 因為每個元件都獲得一個稱為props
的特殊例項屬性,該例項屬性在例項化時儲存傳遞給該元件的所有值。
// Example 10 - Customizing a component instance
// https://jscomplete.com/repl?j=rko7RsKS-
class Button extends React.Component {
constructor(props) {
super(props);
this.id = Date.now();
}
render() {
return <button id={this.id}>{this.props.label}</button>;
}
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);
我們還可以定義類屬性函式,並在我們想使用的地方使用,包括返回的JSX輸出內:
// Example 11 — Using class properties
// https://jscomplete.com/repl?j=H1YDCoFSb
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>
);
}
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);
關於例子11有幾點需要注意
-
handleClick
函式是使用JavaScript中新建的類欄位語法編寫的。這種語法仍然屬於stage-2,
,但由於很多原因,它是訪問元件安裝例項(由於箭頭功能)的最佳選擇。 但是,您需要使用像Babel這樣的編譯器來配置它來理解stage-2
,(或類欄位語法)來獲取上面的程式碼。 jsComplete REPL具有預配置。 - 我們還使用相同的類欄位語法定義了
ClickCounter
例項變數。 這允許我們完全跳過使用類建構函式呼叫。 - 當我們將
handleClick
函式指定為特殊的onClick
,React屬性的值時,我們沒有呼叫它。 我們把handleClick函式引用傳遞給出去了。 在這個屬性裡面呼叫函式是使用React最常見的錯誤之一。
// Wrong:
onClick={this.handleClick()}
// Right:
onClick={this.handleClick}
5: React的事件中,兩個最重要的區別
在React元素中處理事件時,與DOM API的方式有兩個非常重要的區別:
- 所有React元素屬性(包括事件)使用camelCase命名,而不是小寫。 它是
onClick
,而不是onclick
。 - 我們傳遞一個實際的JavaScript函式引用作為事件處理程式,而不是一個字串。 它是
onClick = {handleClick}
,而不是onClick =“handleClick”
。
使用自己的物件將DOM事件物件包裝起來,以優化事件處理的效能。 但是在事件處理程式中,我們仍然可以訪問DOM事件物件上可用的所有方法。 React將包裝的事件物件傳遞給每個控制代碼呼叫。 例如,為了防止表單從預設提交操作中,您可以執行以下操作:
// Example 12 - Working with wrapped events
// https://jscomplete.com/repl?j=HkIhRoKBb
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>
);
}
}
// Use it
ReactDOM.render(<Form />, mountNode);
6: 每一個React元件都有故事
以下僅適用於類元件(擴充套件為React.Component的元件)。 函式元件有一個略有不同的故事。
- 首先,我們為React定義一個模板,以從元件建立元素。
- 然後,我們指示React在某處使用它。 例如,在另一個元件的render呼叫中,或者使用ReactDOM.render。
- 然後,React例項化一個元素,並給出一組我們可以使用
this.props
訪問的props
。 那些props
正是我們在上面的步驟2中傳遞的。 - 由於它都是JavaScript,所以構造方法將被呼叫(如果已經定義的話)。 這是我們要說的第一個:元件生命週期方法。
- 然後React計算
render
方法(虛擬DOM節點)的輸出。 - 由於這是React渲染元素的第一次,React將與瀏覽器進行通訊(代表我們使用DOM API)來顯示元素。 這個過程通常被稱為掛載。
- 然後,React呼叫另一個生命週期方法,稱為
componentDidMount
。 我們可以使用這種方法做一些事情,例如,在DOM上做一些我們現在知道在瀏覽器中支援處理的東西。 在此生命週期方法之前,我們處理的DOM全部是虛擬的。 - 一些元件故事在這裡結束。 出於各種原因,其他元件可以從瀏覽器DOM中解除掛載。 在後一種情況發生之前,React呼叫另一個生命週期方法
componentWillUnmount
。 - 任何已掛載元件的狀態可能會改變。 該元素的父代可能會重新呈現。 在任一種情況下,安裝的元件可能會接收不同的
props
。 這裡的魔法發生了,我們現在開始需要React了! 在此之前,我們完全不需要做任何事情 - 這個元件的故事繼續下去,但在之前,我們需要了解我所說的這個狀態。
7: React元件有一個私有狀態
以下也僅適用於類元件。 有沒有人提到有些人把只做展現的元件叫做啞巴?
狀態類欄位是任何React類元件中的特殊欄位。 React監視每個元件狀態以進行更改。 但是對於React要有效地執行這些操作,我們必須通過另一個需要學習的React API函式來更改state欄位,this.setState
:
// Example 13 - the setState API
// https://jscomplete.com/repl?j=H1fek2KH-
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>
);
}
}
// Use it
ReactDOM.render(<CounterButton />, mountNode);
這是瞭解state
最重要的例子。 它將完善您對React互動方式的基礎知識。 在這個例子之後,還有一些你需要學習的小事情,但是從這一點來看,它主要是你和你的JavaScript技能。
我們來看一下例項13,從類欄位開始。 它有兩個。 特殊狀態欄位被初始化為一個物件,該物件包含起始值為0的clickCounter
,以及起始值為new Date()
的currentTimestamp
。
第二個類欄位是一個handleClick
函式,我們傳遞給render方法中的button元素的onClick
事件。 handleClick
方法使用setState
修改此元件例項狀態。 注意到這一點。
我們在componentDidMount
生命週期方法內部啟動的間隔定時器中修改狀態。 它每秒鐘打勾並執行呼叫this.setState
。
在render方法中,我們使用了正常讀取語法對state兩個屬性的讀取。 沒有特殊的API。
現在,請注意,我們使用兩種不同的方式更新了狀態:
- 傳遞返回一個物件的函式。 我們
handleClick
函式中實現了這部分內容。 - 通過傳遞一個常規物件。 我們在間隔回撥中實現了。
這兩種方式都是可以接受的,但是當您同時讀取和寫入狀態時,第一個是首選的(我們這樣做)。 在間隔回撥之內,我們只寫給狀態,而不是讀取它。 當兩難時,始終使用第一個函式引數語法。 它更加安全,因為setState
實際上是一個非同步方法。
我們如何更新狀態? 我們返回一個包含我們要更新的值的物件。 注意在兩次呼叫setState
中,我們只是從state
欄位傳遞一個屬性,而不是兩者。 這是完全可以的,因為setState
實際上將您傳遞的內容(函式引數的返回值)與現有狀態合併。 因此,在呼叫setState
時不指定屬性意味著我們不希望更改該屬性(而不是刪除它)。
8:React是可以響應的
React從它對狀態變化做出響應的事實(雖然不是反應性的,而是按計劃進行)而得名。 有一個笑話,反應應該被命名為Schedule!
然而,當任何元件的狀態被更新時,我們用肉眼看到的是React對該更新做出反應,並自動反映瀏覽器DOM中的更新(如果需要)。
將render函式輸入視為兩者
- 從父元素得到
props
- 可以隨時更新的內部私有狀態
當渲染功能的輸入變化時,其輸出可能會改變。
React保留了渲染歷史的記錄,當它看到一個渲染與前一個渲染不同時,它將計算它們之間的差異,並將其有效地轉換為在DOM中執行的實際DOM操作。
9: React是你的代理
您可以將React視為我們聘請的與瀏覽器通訊的代理。 以上面的當前時間戳顯示為例。 我們不是手動去瀏覽器並呼叫DOM API操作來每秒查詢和更新p#timestamp元素,而是在元件狀態上更改了一個屬性,而React代表我們與瀏覽器進行通訊。 我相信這是真正受歡迎的真正原因。 我們討厭瀏覽器(domApi很繁瑣),React自願為我們做所有對接工作,免費!
10: 每個React元件都有一個故事(第2部分)
現在我們知道一個元件的狀態,以及當這個狀態改變了一些魔法的時候,讓我們來學習關於該過程的最後幾個概念。
- 元件可能需要在其狀態更新時重新呈現,或者當其父級決定更改傳遞給元件的
props
時,該元件可能需要重新呈現 - 如果後者發生,React會呼叫另一個生命週期方法
componentWillReceiveProps
。 - 如果狀態物件或傳入
props
被更改,則React有一個重要的決定。 元件應該在DOM中更新嗎? 這就是為什麼它在這裡呼叫另一個重要的生命週期方法,shouldComponentUpdate
。 這個方法是一個實際的問題,所以如果你需要自己定製或優化渲染過程,你必須通過返回true或false來回答這個問題。 - 如果沒有指定
customComponentUpdate
,React預設是一個非常聰明的事情,在大多數情況下實際上足夠好。 - 首先,React在此時呼叫另一個生命週期方法
componentWillUpdate
。 然後React將計算新的渲染輸出並將其與最後渲染的輸出進行比較。 - 如果渲染的輸出完全一樣,React什麼都不做。
- 如果存在差異,則React會將這些差異對映到瀏覽器內。
- 無論如何,由於更新過程無論如何(即使輸出完全相同),React會呼叫最終的生命週期方法
componentDidUpdate
。
生命週期方法實際上是艙口。 如果你沒有做任何事情,你可以建立沒有他們的完整的應用程式。 他們可以用來非常方便地分析應用程式中發生的情況,並進一步優化了React更新的效能。
根據以上學到的東西(或其中的一部分,真的),您就可以開始建立一些有趣的React應用程式。 如果您渴望瞭解更多資訊,請訪問我們的Plactsight的React.js課程入門:
翻譯自All the fundamental React.js concepts, jammed into this single Medium article
關注我的公眾號,更多優質文章定時推送