代碼下載
React官網(wǎng)已經(jīng)都是函數(shù)式組件文檔,沒有類組件文檔,但是還是支持類組件這種寫法。
UI 描述
組件
組件 是 React 的核心概念之一,它們是構(gòu)建用戶界面(UI)的基礎(chǔ)。React 允許你將標(biāo)簽、CSS 和 JavaScript 組合成自定義“組件”,即 應(yīng)用程序中可復(fù)用的 UI 元素。
React 最為重視交互性且使用了相同的處理方式:React 組件是一段可以 使用標(biāo)簽進(jìn)行擴(kuò)展 的 JavaScript 函數(shù)。
組件可以渲染其他組件,但是 請(qǐng)不要嵌套他們的定義。當(dāng)子組件需要使用父組件的數(shù)據(jù)時(shí),你需要 通過 props 的形式進(jìn)行傳遞,而不是嵌套定義。
JSX
每個(gè) React 組件都是一個(gè) JavaScript 函數(shù),它會(huì)返回一些標(biāo)簽,React 會(huì)將這些標(biāo)簽渲染到瀏覽器上。React 組件使用一種被稱為 JSX 的語法擴(kuò)展來描述這些標(biāo)簽。JSX 看起來和 HTML 很像,但它的語法更加嚴(yán)格并且可以動(dòng)態(tài)展示信息。了解這些區(qū)別最好的方式就是將一些 HTML 標(biāo)簽轉(zhuǎn)化為 JSX 標(biāo)簽。
JSX and React 是相互獨(dú)立的 東西。但它們經(jīng)常一起使用,但你 可以 單獨(dú)使用它們中的任意一個(gè),JSX 是一種語法擴(kuò)展,而 React 則是一個(gè) JavaScript 的庫。
JSX 規(guī)則
- 只能返回一個(gè)根元素,如果想要在一個(gè)組件中包含多個(gè)元素,需要用一個(gè)父標(biāo)簽把它們包裹起來,如果你不想在標(biāo)簽中增加一個(gè)額外的
<div>,可以用<>和</>元素來代替。 - 標(biāo)簽必須閉合,JSX 要求標(biāo)簽必須正確閉合。像
<img>這樣的自閉合標(biāo)簽必須書寫成<img />,而像<li>oranges這樣只有開始標(biāo)簽的元素必須帶有閉合標(biāo)簽,需要改為<li>oranges</li>。 - 使用駝峰式命名法給 所有 大部分屬性命名! JSX 最終會(huì)被轉(zhuǎn)化為 JavaScript,而 JSX 中的屬性也會(huì)變成 JavaScript 對(duì)象中的鍵值對(duì)。但 JavaScript 對(duì)變量的命名有限制。例如,變量名稱不能包含
-符號(hào)或者像class這樣的保留字。
由于歷史原因,aria-* 和 data-* 屬性是以帶 - 符號(hào)的 HTML 格式書寫的。
轉(zhuǎn)化器 將 HTML 和 SVG 標(biāo)簽轉(zhuǎn)化為 JSX。
在 JSX 中通過大括號(hào)使用 JavaScript
在 JSX 中,只能在以下兩種場景中使用大括號(hào):
- 用作 JSX 標(biāo)簽內(nèi)的文本:
<h1>{name}'s To Do List</h1>是有效的,但是<{tag}>Gregorio Y. Zara's To Do List</{tag}>無效。 - 用作緊跟在 = 符號(hào)后的 屬性:
src={avatar}會(huì)讀取 avatar 變量,但是src="{avatar}"只會(huì)傳一個(gè)字符串{avatar}。
props
React 組件使用 props 來互相通信。每個(gè)父組件都可以提供 props 給它的子組件,從而將一些信息傳遞給它??梢酝ㄟ^它們傳遞任何 JavaScript 值,包括對(duì)象、數(shù)組和函數(shù)。
可以通過解構(gòu)語法來讀取 props 的值,也可以通過在參數(shù)后面寫 = 和默認(rèn)值來進(jìn)行解構(gòu):
function Avatar({ person, size = 100 }) {
// ...
}
一些組件將它們所有的 props 轉(zhuǎn)發(fā)給子組件,因?yàn)檫@些組件不直接使用他們本身的任何 props,所以使用更簡潔的“展開”語法是有意義的:
function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}
條件渲染
在一些情況下,不想有任何東西進(jìn)行渲染,可以直接返回 null。
當(dāng) JavaScript && 表達(dá)式 的左側(cè)(我們的條件)為 true 時(shí),它則返回其右側(cè)的值(在我們的例子里是勾選符號(hào))。但條件的結(jié)果是 false,則整個(gè)表達(dá)式會(huì)變成 false。在 JSX 里,React 會(huì)將 false 視為一個(gè)“空值”,就像 null 或者 undefined,這樣 React 就不會(huì)在這里進(jìn)行任何渲染。
列表渲染 key
必須給數(shù)組中的每一項(xiàng)都指定一個(gè) key——它可以是字符串或數(shù)字的形式,只要能唯一標(biāo)識(shí)出各個(gè)數(shù)組項(xiàng)就行。key 會(huì)告訴 React,每個(gè)組件對(duì)應(yīng)著數(shù)組里的哪一項(xiàng),所以 React 可以把它們匹配起來。這在數(shù)組項(xiàng)進(jìn)行移動(dòng)(例如排序)、插入或刪除等操作時(shí)非常重要。一個(gè)合適的 key 可以幫助 React 推斷發(fā)生了什么,從而得以正確地更新 DOM 樹。
不同來源的數(shù)據(jù)往往對(duì)應(yīng)不同的 key 值獲取方式:
- 來自數(shù)據(jù)庫的數(shù)據(jù): 如果你的數(shù)據(jù)是從數(shù)據(jù)庫中獲取的,那你可以直接使用數(shù)據(jù)表中的主鍵,因?yàn)樗鼈兲烊痪哂形ㄒ恍浴?/li>
- 本地產(chǎn)生數(shù)據(jù): 如果你數(shù)據(jù)的產(chǎn)生和保存都在本地(例如筆記軟件里的筆記),那么你可以使用一個(gè)自增計(jì)數(shù)器或者一個(gè)類似 uuid 的庫來生成 key。
key 需要滿足的條件:
- key 值在兄弟節(jié)點(diǎn)之間必須是唯一的。 不過不要求全局唯一,在不同的數(shù)組中可以使用相同的 key。
- key 值不能改變,否則就失去了使用 key 的意義!所以千萬不要在渲染時(shí)動(dòng)態(tài)地生成 key。
React 中為什么需要 key?key 讓我們可以從眾多的兄弟元素中唯一標(biāo)識(shí)出某一項(xiàng) JSX 節(jié)點(diǎn)。而一個(gè)精心選擇的 key 值所能提供的信息遠(yuǎn)遠(yuǎn)不止于這個(gè)元素在數(shù)組中的位置。即使元素的位置在渲染的過程中發(fā)生了改變,它提供的 key 值也能讓 React 在整個(gè)生命周期中一直認(rèn)得它。
陷阱:可能會(huì)想直接把數(shù)組項(xiàng)的索引當(dāng)作 key 值來用,實(shí)際上,如果你沒有顯式地指定 key 值,React 確實(shí)默認(rèn)會(huì)這么做。但是數(shù)組項(xiàng)的順序在插入、刪除或者重新排序等操作中會(huì)發(fā)生改變,此時(shí)把索引順序用作 key 值會(huì)產(chǎn)生一些微妙且令人困惑的 bug。與之類似,請(qǐng)不要在運(yùn)行過程中動(dòng)態(tài)地產(chǎn)生 key,像是 key={Math.random()} 這種方式。這會(huì)導(dǎo)致每次重新渲染后的 key 值都不一樣,從而使得所有的組件和 DOM 元素每次都要重新創(chuàng)建。這不僅會(huì)造成運(yùn)行變慢的問題,更有可能導(dǎo)致用戶輸入的丟失。所以,使用能從給定數(shù)據(jù)中穩(wěn)定取得的值才是明智的選擇。
注意:組件不會(huì)把 key 當(dāng)作 props 的一部分。Key 的存在只對(duì) React 本身起到提示作用。如果你的組件需要一個(gè) ID,那么請(qǐng)把它作為一個(gè)單獨(dú)的 prop 傳給組件:
<Profile key={id} userId={id} />。
保持組件純粹
在計(jì)算機(jī)科學(xué)中(尤其是函數(shù)式編程的世界中),純函數(shù) 通常具有如下特征:
- 只負(fù)責(zé)自己的任務(wù)。它不會(huì)更改在該函數(shù)調(diào)用前就已存在的對(duì)象或變量。
- 輸入相同,則輸出相同。給定相同的輸入,純函數(shù)應(yīng)總是返回相同的結(jié)果。
在渲染過程中,組件改變了 預(yù)先存在的 變量的值,將這種現(xiàn)象稱為 突變(mutation) 。純函數(shù)不會(huì)改變函數(shù)作用域外的變量、或在函數(shù)調(diào)用前創(chuàng)建的對(duì)象——這會(huì)使函數(shù)變得不純粹!
React 的渲染過程必須自始至終是純粹的。組件應(yīng)該只 返回 它們的 JSX,而不 改變 在渲染前,就已存在的任何對(duì)象或變量 — 這將會(huì)使它們變得不純粹!但是可以在渲染時(shí)更改剛剛 創(chuàng)建的變量和對(duì)象:
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaGathering() {
let cups = [];
for (let i = 1; i <= 12; i++) {
cups.push(<Cup key={i} guest={i} />);
}
return cups;
}
每次渲染時(shí)都是在 TeaGathering 函數(shù)內(nèi)部創(chuàng)建的它們。TeaGathering 之外的代碼并不會(huì)知道發(fā)生了什么。這就被稱為 “局部 mutation” — 如同藏在組件里的小秘密。
函數(shù)式編程在很大程度上依賴于純函數(shù),但 某些事物 在特定情況下不得不發(fā)生改變這些變動(dòng)包括更新屏幕、啟動(dòng)動(dòng)畫、更改數(shù)據(jù)等,它們被稱為 副作用。它們是 “額外” 發(fā)生的事情,與渲染過程無關(guān)。在 React 中,副作用通常屬于 事件處理程序。事件處理程序是 React 在執(zhí)行某些操作(如單擊按鈕)時(shí)運(yùn)行的函數(shù)。即使事件處理程序是在你的組件 內(nèi)部 定義的,它們也不會(huì)在渲染期間運(yùn)行! 因此事件處理程序無需是純函數(shù)。
將 UI 視為樹
React 以及許多其他 UI 庫,將 UI 建模為樹。將應(yīng)用程序視為樹對(duì)于理解組件之間的關(guān)系以及調(diào)試性能和狀態(tài)管理等未來將會(huì)遇到的一些概念非常有用。樹是項(xiàng)目和 UI 之間的關(guān)系模型,通常使用樹結(jié)構(gòu)來表示 UI。
渲染樹表示 React 應(yīng)用程序的單個(gè)渲染過程。在 條件渲染 中,父組件可以根據(jù)傳遞的數(shù)據(jù)渲染不同的子組件。盡管渲染樹可能在不同的渲染過程中有所不同,但通常這些樹有助于識(shí)別 React 應(yīng)用程序中的頂級(jí)和葉子組件。頂級(jí)組件是離根組件最近的組件,它們影響其下所有組件的渲染性能,通常包含最多復(fù)雜性。葉子組件位于樹的底部,沒有子組件,通常會(huì)頻繁重新渲染。識(shí)別這些組件類別有助于理解應(yīng)用程序的數(shù)據(jù)流和性能。
模塊依賴樹
在 React 應(yīng)用程序中,可以使用樹來建模的另一個(gè)關(guān)系是應(yīng)用程序的模塊依賴關(guān)系。當(dāng) 拆分組件 和邏輯到不同的文件中時(shí),就創(chuàng)建了 JavaScript 模塊,在這些模塊中可以導(dǎo)出組件、函數(shù)或常量。模塊依賴樹中的每個(gè)節(jié)點(diǎn)都是一個(gè)模塊,每個(gè)分支代表該模塊中的 import 語句。
與同一應(yīng)用程序的渲染樹相比,存在相似的結(jié)構(gòu),但也有一些顯著的差異:
- 構(gòu)成樹的節(jié)點(diǎn)代表模塊,而不是組件。
- 非組件模塊,在這個(gè)樹中也有所體現(xiàn)。渲染樹僅封裝組件。
- 組件可以 接受 JSX 作為 children props,因此它將 其他組件 作為子組件渲染,但不導(dǎo)入該模塊。
在為生產(chǎn)環(huán)境構(gòu)建 React 應(yīng)用程序時(shí),通常會(huì)有一個(gè)構(gòu)建步驟,該步驟將捆綁所有必要的 JavaScript 以供客戶端使用。負(fù)責(zé)此操作的工具稱為 bundler(捆綁器),并且 bundler 將使用依賴樹來確定應(yīng)包含哪些模塊。隨著應(yīng)用程序的增長,捆綁包大小通常也會(huì)增加。大型捆綁包大小對(duì)于客戶端來說下載和運(yùn)行成本高昂,并延遲 UI 繪制的時(shí)間。了解應(yīng)用程序的依賴樹可能有助于調(diào)試這些問題。
交互
useState
在 React 中,useState 以及任何其他以“use”開頭的函數(shù)都被稱為 Hook。Hook 是特殊的函數(shù),只在 React 渲染時(shí)有效。它們能讓你 “hook” 到不同的 React 特性中去。
Hooks ——以 use 開頭的函數(shù)——只能在組件或自定義 Hook 的最頂層調(diào)用。 不能在條件語句、循環(huán)語句或其他嵌套函數(shù)內(nèi)調(diào)用 Hook。Hook 是函數(shù),但將它們視為關(guān)于組件需求的無條件聲明會(huì)很有幫助。在組件頂部 “use” React 特性,類似于在文件頂部“導(dǎo)入”模塊。
State 是屏幕上組件實(shí)例內(nèi)部的狀態(tài)。換句話說,如果你渲染同一個(gè)組件兩次,每個(gè)副本都會(huì)有完全隔離的 state!改變其中一個(gè)不會(huì)影響另一個(gè)。
Hook 是能讓你的組件使用 React 功能的特殊函數(shù)(狀態(tài)是這些功能之一)。useState Hook 讓你聲明一個(gè)狀態(tài)變量。它接收初始狀態(tài)并返回一對(duì)值:當(dāng)前狀態(tài),以及一個(gè)讓你更新狀態(tài)的設(shè)置函數(shù)。
function StateHook() {
const [index, setIndex] = React.useState(0)
return (<>
<p>index: {index}</p>
<button onClick={() => {
setIndex(index + 1)
console.log('index: ', index);
}}>+1</button>
</>)
}
ReactDOM.createRoot(document.getElementById('stateHook')).render(<StateHook></StateHook>)
狀態(tài)的行為更像一個(gè)快照。設(shè)置它并不改變已有的狀態(tài)變量,而是觸發(fā)一次重新渲染??梢酝ㄟ^在設(shè)置狀態(tài)時(shí)傳遞一個(gè) 更新器函數(shù) 來解決這個(gè)問題,在需要排隊(duì)進(jìn)行多次狀態(tài)更新,那么這非常方便:
setIndex(index + 1)
setIndex(i => i + 1)
setIndex((i, p) => {
// 不支持第二個(gè)參數(shù) p
console.log('i: ', i, 'p: ', p);
return i + 1
// 不支持 更新后的回調(diào)函數(shù)
}, () => console.log('i: ', index))
注意:與類組件 setState 不同的是 useState 更新器函數(shù)只有一個(gè)參數(shù)就是最新的狀態(tài)值,并且不支持更新回調(diào)函數(shù)。類組件的 setState 是異步更新數(shù)據(jù)而 useState 狀態(tài)的行為更像一個(gè)快照,通過延時(shí)函數(shù)就可以看出他們的差別,一個(gè) useState 變量的值永遠(yuǎn)不會(huì)在一次渲染的內(nèi)部發(fā)生變化, 即使其事件處理函數(shù)的代碼是異步的:
class StateCom extends React.Component {
state = { index: 0 }
render() {
return (<>
<p>index: {this.state.index}</p>
<button onClick={() => {
this.setState({index: this.state.index + 1})
setTimeout(() => {
// 輸出 1
console.log('index: ', this.state.index);
}, 2000);
}}>+1</button>
</>)
}
}
function UseStateCom() {
const [i, setIndex] = useState(0)
return (<>
<p>index: {i}</p>
<button onClick={() => {
setIndex(i + 1)
setTimeout(() => {
// 輸出 0
console.log('index: ', i);
}, 2000);
}}>+1</button>
</>)
}
嚴(yán)格來說 React state 中存放的對(duì)象是可變的,但你應(yīng)該像處理數(shù)字、布爾值、字符串一樣將它們視為不可變的,因此應(yīng)該替換它們的值,而不是對(duì)它們進(jìn)行修改。數(shù)組只是另一種對(duì)象。同對(duì)象一樣,需要將 React state 中的數(shù)組視為只讀的。這意味著你不應(yīng)該使用類似于 arr[0] = 'bird' 這樣的方式來重新分配數(shù)組中的元素,也不應(yīng)該使用會(huì)直接修改原始數(shù)組的方法,例如 push() 和 pop()。