React 入門手冊

React 是目前為止最受歡迎的 JavaScript 框架之一,而且我相信它也是目前最好用的開發(fā)工具之一。
這篇文章的目的在于為 React 初學者提供一些學習指導。
在學習完這篇文章后,你就可以對 React 有初步的了解:
什么是 React,它為什么這么受歡迎 如何安裝 React React 組件 React State React Props 在 React 中處理用戶事件 React 組件的生命周期事件
以上這些內(nèi)容是你構(gòu)建高級 React 應用的基礎(chǔ)。
這篇文章是專門為剛接觸 React 的 JavaScript 程序員寫的。現(xiàn)在就讓我們開始學習吧。
什么是 React?
React 是一個 JavaScript 庫,旨在簡化 UI 的開發(fā)。
2013 年,F(xiàn)acebook 首次向全世界發(fā)布了 React。此后,人們用它開發(fā)了一些應用最廣泛的 APP,并且它也使 Facebook 和 Instagram 在無數(shù)應用中占得領(lǐng)先地位。
React 最初是為了使開發(fā)者可以在任意時間點都能輕松地追蹤 UI 及它的狀態(tài)。它通過將 UI 劃分為多個組件的集合來達到這個目的。
在學習 React 的時候,你可能遇到一些小困難,但是只要解決了它們,我保證這將會是你最美好的經(jīng)歷。React 可以使前端開發(fā)工作變得更加簡單,而且它的生態(tài)里還有很多好用的庫和工具。
React 自身有一套易于使用的 API,當你開始學習的時候,需要先明白以下 4 個基本概念:
組件 JSX State Props
我們將在這篇指導中學習以上幾個基本概念,那些高級的概念我們會留給其它的教程,我也會在文章的末尾給出深入學習 React 的資料。
你可以免費下載 PDF / ePub / Mobi 格式的本篇指導。
學習目錄
學習 React 需要知道多少 JavaScript 為什么要學習 React 如何安裝 React React 組件 JSX 簡介 使用 JSX 實現(xiàn) UI JSX 與 HTML 的區(qū)別 在 JSX 中嵌入 JavaScript React 中的狀態(tài)管理 React 組件中的 Props React 應用中的數(shù)據(jù)流 在 React 中處理用戶事件 React 組件中的生命周期事件 參考資料
學習 React 需要了解多少 JavaScript
在真正開始學習 React 之前,你需要對 JavaScript 的核心概念有很好的理解。
你不需要成為 JavaScript 專家,但是我希望你對以下內(nèi)容有很好的了解:
變量 箭頭函數(shù) 使用擴展運算符處理對象和數(shù)組 對象和數(shù)組的解構(gòu) 模板字符串 回調(diào)函數(shù) ES 模塊化
如果你對這些概念不熟悉,我為你提供了一些資料來學習這些概念(點擊文末“閱讀原文”可見)。
為什么要學習 React?
我強烈建議每一位 Web 開發(fā)者都可以對 React 有基本的了解。
這是因為以下幾個原因:
React 十分受歡迎。作為一名開發(fā)者,你很可能在將來參與 React 項目。它們可能是目前正在進行的項目,也可能是你的團隊希望你使用 React 開發(fā)的一個全新的 APP。 現(xiàn)在很多工具都是基于 React 開發(fā)的,比如 Next.js,Gatsby 等流行框架與工具,它們在后臺都使用了 React。 作為一名前端工程師,你很可能會在面試時遇到關(guān)于 React 的問題。
這些都是很好的理由,但是我希望你學習 React 的一個主要原因是它真的非常優(yōu)秀。
React 促成了包括代碼復用、組件化開發(fā)在內(nèi)的幾種很好的開發(fā)實踐。它高效、輕量,并且使開發(fā)者關(guān)注于應用中的數(shù)據(jù)流,這種開發(fā)思想適用于很多常見的場景。
如何安裝 React
有幾種不同的方式安裝 React。
在開始時,我強烈建議一種方法,那就是使用官方推薦的工具:create-react-app。
create-react-app 是一個命令行工具,旨在讓你快速了解 React。
你可以從使用 npx 開始,這是一種不需要安裝就能下載和執(zhí)行 Node.js 命令的便捷方法。
在這里查看我的 npx 指南:https://flaviocopes.com/npx/
從 5.2 版的 npm 開始,增加了 npx 命令。如果你現(xiàn)在還沒安裝 npm,那么點擊這里 https://nodejs.org 安裝吧(npm 是隨 Node 安裝的)。
如果你不能確定你的 npm 版本號,那么執(zhí)行 npm -v 命令來檢查你是否需要更新 npm。
注意:如果你不熟悉終端的使用方法,請訪問 https://flaviocopes.com/macos-terminal/, 查看我的 OSX 終端教程。這份教程適用于 Mac 和 Linux。
當你執(zhí)行 npx create-react-app <app-name> 命令時,npx 首先會 下載 最新版的 create-react-app,然后再運行它,運行結(jié)束后會把它從你的系統(tǒng)中刪除。
這點很不錯,因為你的系統(tǒng)上永遠不會有舊的版本,并且每次運行的時候,你都會獲得最新、最全的可用版本。
讓我們開始吧:
npx create-react-app todolist

運行成功后你會看到:

create-react-app 會在你指定的文件夾下創(chuàng)建項目的目錄結(jié)構(gòu)(本示例中為 todolist),同時將它初始化為一個 Git 倉庫。
它也會在 package.json 文件中添加幾個命令:

所以你可以即刻進入到新創(chuàng)建的應用目錄下,運行 npm start 命令來啟動 app。

默認情況下,這個命令會在你本地的 3000 端口啟動 app,并打開瀏覽器,為你展示歡迎界面:

現(xiàn)在你就可以開始開發(fā)這個應用程序了!
React 組件
在上一節(jié)課程里,我們創(chuàng)建了我們的第一個 React 應用。
在這個應用中,包含了一系列執(zhí)行各種操作的文件,大部分文件都與配置有關(guān),但是有一個文件十分的不同:App.js。
App.js 是你遇到的 第一個 React 組件。
文件中的代碼如下:
import React from 'react'
import logo from './logo.svg'
import './App.css'
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)
}
export default App
一個使用 React 或者其他的主流前端框架(如:Vue、Svelte)創(chuàng)建的應用,都是由很多的組件構(gòu)成的。
不過,我們還是先分析這個組件吧。我把這個組件代碼簡化如下:
import React from 'react'
import logo from './logo.svg'
import './App.css'
function App() {
return /* something */
}
export default App
現(xiàn)在你可以看到幾件事情:我們使用 import 導入了一些東西,并且 導出 了一個名為 App 的函數(shù)。
在這段示例代碼中,我們導入了一個 JavaScript 庫(react npm 包)、一個 SVG 圖片和一個 CSS 文件。
create-react-app設(shè)置了一種方法,它允許我們導入圖片和 CSS,然后在 JavaScript 中使用它們。但這不是我們現(xiàn)在需要關(guān)心的內(nèi)容,我們現(xiàn)在關(guān)心的是 組件 的概念。
App 是一個官方示例中的函數(shù), 返回了一些初看之下非常怪異的內(nèi)容。
它看起來很像 HTML,但是內(nèi)嵌了一些 JavaScript。
其實這就是 JSX,一種我們構(gòu)建組件時使用的特殊語言。我們將會在下一節(jié)討論 JSX。
除了可以返回 JSX,組件還具有一些其他特征。
一個組件可以有它自己的 state(狀態(tài)),這就是說它可以封裝一些其他組件無法訪問的屬性,除非它把這些 state 暴露給應用中的其他組件。
一個組件也可以接收來自其他組件的數(shù)據(jù),我們稱這些數(shù)據(jù)為 props。
先不要著急,我們很快就會詳細學習所有的這些概念(JSX,State 和 Props)了。
JSX 簡介
要想學習 React 就必須首先了解 JSX。
在上一節(jié)中,我們創(chuàng)建了第一個 React 組件,即 App,它定義在由 create-react-app 構(gòu)建的默認應用程序中。
它的代碼如下:
import React from 'react'
import logo from './logo.svg'
import './App.css'
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)
}
export default App
當時我們忽略了 return 語句中的所有內(nèi)容,但是在本節(jié)中我們將會討論它們。
我們將包含在組件返回語句后的括號內(nèi)的所有內(nèi)容稱為 JSX:
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
這些內(nèi)容 看起來 很像 HTML,但是卻又不是真正的 HTML。它們之間有一些不同點。
而且將這樣的代碼包含在 JavaScript 文件中有點奇怪:它們看起來一點都不像 JavaScript!
在后臺,React 會處理 JSX,它們會被轉(zhuǎn)換為瀏覽器可以識別的 JavaScript。
因此,雖然我們編寫了 JSX,但是最終會有一個轉(zhuǎn)換的步驟,使它可以被 JavaScript 解析器所識別。
React 這樣做的一個主要原因就是:使用 JSX 能更加輕松的開發(fā) UI 界面。
當然了,前提是你必須非常熟悉它。
在下一節(jié)中,我們將會學習 JSX 是怎么使 UI 開發(fā)變?nèi)菀椎摹T偃缓笪覀儗懻撍c“標準 HTML”的區(qū)別,而這些差異是你必須知道的。
使用 JSX 構(gòu)建 UI
就像上一節(jié)中介紹的那樣,JSX 的一個主要作用就是借助它可以非常容易的編寫 UI。
特別的,在 React 組件中,你可以導入其他 React 組件,然后將它們嵌入當前組件以展示它們。
通常情況下,一個文件就是一個 React 組件,這是我們可以非常容易的在其它組件中復用(通過導入的方式)它們的原因。
但是同一個文件中也可以定義其它的 React 組件,這些組件只會在當前文件中用到。這里并沒有明確的規(guī)則來規(guī)定一個文件中是否需要定義多個組件,選擇最適合你的那種方式即可。
當一個文件中的代碼行數(shù)過多時,我通常會將代碼進行拆分,放到單獨的文件中。
為了方便學習,我們在 App.js 文件中再定義一個組件。
我們計劃創(chuàng)建一個名為 WelcomeMessage 的組件:
function WelcomeMessage() {
return <p>Welcome!</p>
}
看到了嗎?這個組件就是一個簡單的函數(shù),它返回了一行 JSX,表示一個 p 標簽。
我們將這個函數(shù)添加到 App.js 文件中。
現(xiàn)在,我們將 <WelcomeMessage /> 添加到 App 組件的 JSX 代碼中,就可以在 UI 中展示這個組件:
import React from 'react'
import logo from './logo.svg'
import './App.css'
function WelcomeMessage() {
return <p>Welcome!</p>
}
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<WelcomeMessage />
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)
}
export default App
下面是運行結(jié)果,你應該可以在屏幕中看到“Welcome!”信息。

我們稱 WelcomeMessage 為子組件,App 為父組件。
我們像使用 HTML 標簽一樣,添加 <WelcomeMessage /> 組件。
這就是 React 組件和 JSX 優(yōu)雅的地方:我們構(gòu)建應用程序組件,并且像使用 HTML 標簽一樣使用它們。
關(guān)于 JSX 與 THML 的區(qū)別,我們將會在下一節(jié)中學習。
JSX 與 HTML 的區(qū)別
JSX 看起來像 HTML,但事實并不是這樣。
在這節(jié)課程里,我會介紹一些在使用 JSX 時你必須要知道的東西。
如果你仔細閱讀過 App 組件的 JSX 代碼,會發(fā)現(xiàn)一個很明顯的不同點:組件中有一個名為 className 的屬性。
在 HTML 中,我們使用的是 class 屬性。出于各種原因,它可能是使用最廣泛的屬性,而 CSS 就是其中一個原因。class 屬性使我們可以輕松的設(shè)置 HTML 樣式,并且在設(shè)計 UI 時,Tailwind 之類的 CSS 框架就是以這個屬性為核心的。
但是這里有個問題。我們在 JavaScript 文件中編寫 UI 代碼,而 class 是 JavaScript 語言的保留字,這就意味著我們不能使用它,它有特殊的作用(定義 JavaScript 類)。由于這個原因,React 的作者們不得不選擇一個其它的名稱。
這就是我們?yōu)槭裁从?className 替代了 class。
當你將一些現(xiàn)有的 HTML 代碼改寫為 JSX 時,需要牢記這點。
React 為了保證頁面能正常顯示,對這種情況進行了特殊處理,但是它會在開發(fā)者工具中給出警告:

這種情況非常普遍,并不是只有 HTML 會遇到這種困擾,
JSX 與 HTML 的另一個非常大的不同點是 HTML 是很 寬松。當出現(xiàn)語法錯誤、標簽沒有正確閉合或者匹配時,瀏覽器會盡可能的解析 HTML,而不是中斷解析過程。
這是 Web 的一個核心特點,它非常寬松。
但是 JSX 并不寬松。如果你忘記將一個標簽閉合,你將會得到一條錯誤信息:

React 會給出非常友好的錯誤信息,使你可以準確地定位問題并解決問題。
第三個 JSX 與 HTML 的不同點在于:在 JSX 中,我們可以內(nèi)嵌 JavaScript。
我們會在下一節(jié)討論這點。
在 JSX 嵌入 JavaScript
React 的一大特點就是我們可以非常容易的在 JSX 中嵌入 JavaScript。
其他的前端框架(如 Angular 和 Vue)有自己的特殊方法來在模板中顯示 JavaScript 值,或者執(zhí)行類似循環(huán)的操作。
React 并沒有添加類似的新特性。React 通過使用大括號的方式,容許我們在 JSX 中嵌入 JavaScript。
我們展示的第一個示例,來自于我們之前學習過的 App 組件。
我們可以使用下面的方法導入 logo 的 SVG 文件:
import logo from './logo.svg'
然后在 JSX 中,我們將這個 SVG 文件賦值給 img 標簽的 src 屬性。
<img src={logo} className="App-logo" alt="logo" />
我們再來展示一個示例。假設(shè) App 組件有一個變量,名為 message:
function App() {
const message = 'Hello!'
//...
}
我們可以通過在 JSX 的任意位置添加 {message},來在 JSX 中顯示這個變量的值。
我們可以在 { } 中添加任何 Javscript 表達式,但是每對大括號中只能有 一個 表達式,并且這個表達式必須是可正確求值的。
如下所示,這是一個在 JSX 中非常常見的表達式。我們編寫了一個三元運算符,在其中定義了一個條件語句(message === 'Hello!'),當條件為真時,我們輸出一個值(The message was "Hello!");條件為假時,輸出另一個值(當前示例中為變量 message 的值):
{
message === 'Hello!' ? 'The message was "Hello!"' : message
}
在 React 中管理 state
每一個 React 組件都可以有它自己的 state。
那么什么是 state ?state 就是 由組件管理的狀態(tài)的集合。
例如,對于表單來說,它的每一個獨立的 input 元素都管理著它自己的 state:它的輸入值。
一個按鈕負責處理自己是否被點擊;是否獲得焦點。
一個鏈接負責管理鼠標是否懸停在它上面。
在 React 或者其他組件化的框架、庫中,我們所有的應用都是以大量使用含有 state 的組件為基礎(chǔ)構(gòu)建的。
我們使用由 React 提供的高效管理工具 useState 來管理 state。從技術(shù)上來說,它是個 鉤子 (盡管事實就是這樣,但是現(xiàn)在我們還不需要知道鉤子的詳細信息)。
你可以使用下面的方法來從 React 中導入 useState:
import React, { useState } from 'react'
通過調(diào)用 useState(),我們將會得到一個 state,以及一個供我們調(diào)用,用來修改 state 值的函數(shù)。
useState() 可以傳入一個參數(shù),用來初始化 state。它會返回一個數(shù)組,這個數(shù)組包含一個 state 和一個修改 state 值的函數(shù)。
如下所示:
const [count, setCount] = useState(0)
這一點非常重要。我們不能直接修改 state,只能通過調(diào)用修改函數(shù)來修改它,否則,React 組件無法及時將數(shù)據(jù)的變化反映在 UI 中。
調(diào)用修改函數(shù)是一種將組件 state 的變化告知 React 的方法。
這個語法是不是看起來有點奇怪?這是因為 useState() 返回的是數(shù)組,所以我們使用了數(shù)組解構(gòu)的方法來獲取每個數(shù)組成員,就像這樣:const [count, setCount] = useState(0)
下面是一個示例:
import { useState } from 'react'
const Counter = () => {
const [count, setCount] = useState(0)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('app'))
我們也可以調(diào)用多次調(diào)用 useState(),來創(chuàng)建多個 state:
const [count, setCount] = useState(0)
const [anotherCounter, setAnotherCounter] = useState(0)
React 組件中的 Props
我們稱傳入組件的初始值為 props。
我們之前創(chuàng)建了一個 WelcomeMessage 組件:
function WelcomeMessage() {
return <p>Welcome!</p>
}
我們這樣使用它:
<WelcomeMessage />
這個組件沒有任何初始值,所以它沒有 props。
在 JSX 中,props 可以作為屬性傳給組件。
<WelcomeMessage myprop={'somevalue'} />
在組件中,我們以函數(shù)參數(shù)的形式接收 props:
function WelcomeMessage(props) {
return <p>Welcome!</p>
}
通常情況下,我們用對象解構(gòu)的形式來獲取 props 的名稱:
function WelcomeMessage({ myprop }) {
return <p>Welcome!</p>
}
現(xiàn)在我們獲得了 props,并可以在組件中使用它了。如下所示,我們可以在 JSX 中輸出它的值:
function WelcomeMessage({ myprop }) {
return <p>{myprop}</p>
}
這里的大括號有多種含義。對于函數(shù)參數(shù)來說,大括號是對象解構(gòu)語法的一部分。我們也可以用它來定義函數(shù)代碼塊;而在 JSX 中,我們用它來輸出 JavaScript 值。
將 props 傳遞給組件是一種在應用中傳遞值的好方法。
一個組件既可以有自己的狀態(tài)(state),也可以通過 props 來接收數(shù)據(jù)。
當將函數(shù)作為 props 時,子組件就可以調(diào)用父組件中定義的函數(shù)。
有一種被稱為 children 的特殊 props,它代表了包含在組件的開始標簽和結(jié)束標簽之間的所有內(nèi)容,例如:
<WelcomeMessage> Here is some message </WelcomeMessage>
這種情況下,在 WelcomeMessage 中,我們可以通過使用名為 children 的 props 來獲取 Here is some message。
function WelcomeMessage({ children }) {
return <p>{children}</p>
}
React 應用中的數(shù)據(jù)流
在一個 React 應用中,數(shù)據(jù)通常以 props 的方式從父組件流向子組件,就像我們在上一節(jié)看到的那樣:
<WelcomeMessage myprop={'somevalue'} />
如果給子組件傳遞一個函數(shù),你就可以在子組件中修改父組件的 state:
const [count, setCount] = useState(0)
<Counter setCount={setCount} />
如下所示,在 Counter 組件內(nèi)部,我們?nèi)〉昧?setCount,然后在適當情況下,可以調(diào)用它來修改父組件中的 count:
function Counter({ setCount }) {
//...
setCount(1)
//...
}
其實還有很多更高級的方法來管理數(shù)據(jù),比如 Context API 和 Redux 之類的庫。但是這些方法會增加復雜性,而在大約 90% 的時間里,我們剛剛介紹的兩種方法都是完美的解決方案。
在 React 中處理用戶事件
React 提供了一種簡單的方法來管理從 DOM 觸發(fā)的事件,如點擊事件、表單事件等。
這里我們以最容易理解單擊事件為例來進行說明。
你可以在任意的 JSX 元素上使用 onClick 屬性:
<button
onClick={(event) => {
/* handle the event */
}}
>
Click here
</button>
每當元素被點擊的時候,傳遞給 onClick 屬性的函數(shù)就會被觸發(fā)。
你也可以在 JSX 的外部定義這些函數(shù):
const handleClickEvent = (event) => {
/* handle the event */
}
function App() {
return <button onClick={handleClickEvent}>Click here</button>
}
當點擊 button 時,就會觸發(fā) click 事件,此時,React 就會調(diào)用 click 事件的處理函數(shù)。
React 支持非常多的事件類型,如:onKeyUp,onFocus,onChange,onMouseDown,onSubmit 等。
React 組件的生命周期事件
到目前為止,我們已經(jīng)學習了怎么使用 useState 鉤子來管理 state。
在本節(jié)中,我想介紹另外一個鉤子:userEffect。
useEffect 鉤子允許組件訪問它的生命周期事件。
當你調(diào)用這個鉤子時,你需要傳入一個函數(shù)。在組件第一次被渲染的時候,以及在隨后的每次重新渲染 / 更新時,React 都會調(diào)用這個函數(shù)。
React 首先更新 DOM,然后調(diào)用任何傳遞給 useEffect() 的函數(shù)。
所有這些都不會阻塞 UI 的渲染,即使是同步函數(shù)。
這里是一個示例:
const { useEffect, useState } = React
const CounterWithNameAndSideEffect = () => {
const [count, setCount] = useState(0)
useEffect(() => {
console.log(`You clicked ${count} times`)
})
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
因為在隨后的每次重新渲染 / 更新時,傳遞給 useEffect() 的函數(shù)都會被執(zhí)行,所以出于性能上的考慮,我們可以告訴 React 在某些時候不要執(zhí)行這個函數(shù)。為了實現(xiàn)這個目的,我們可以為 useEffect() 傳入第二個參數(shù),這個參數(shù)是一個數(shù)組,它的成員是需要監(jiān)視的 state 變量。只有在這些 state 發(fā)生變化的時候,React 才會執(zhí)行這個函數(shù)。
useEffect(() => {
console.log(`Hi ${name} you clicked ${count} times`)
}, [name, count])
類似的,你可以傳入一個空數(shù)組,這會使 React 只在組件掛載的時候才執(zhí)行這個函數(shù)。
useEffect(() => {
console.log(`Component mounted`)
}, [])
這是一個非常有用的技巧。
useEffect() 非常適合添加日志,訪問第三方 API 等。
接下來做什么?
熟練掌握在這篇文章中提到主題是朝著學習 React 目標邁出的重要一步。
在這里我想給出一些指導,防止你在有關(guān) React 教程和課程的海洋中迷失方向。
接下來該學習什么呢?
了解有關(guān)虛擬 DOM,編寫聲明式代碼,單向數(shù)據(jù)流,不變性,組合的更多理論。
構(gòu)建一些簡單的 React 應用。例如:一個簡單的計數(shù)器或者與公共 API 交互。
學習如何使用條件渲染,如何在 JSX 中使用循環(huán),如何使用 React 開發(fā)者工具
通過 plain CSS 或者 Styled Components 學習如何在 React 應用中使用 CSS。
學習如何使用 Context API,useContext 與 Redux 來管理 state。
學習如何與 forms 交互。
學習如何使用 React 路由。
學習如何測試 React 應用。
了解基于 React 構(gòu)建的應用程序框架,如 Gatsby 或者 Next.js。
當然,最重要的是,請確保在構(gòu)建應用的過程中實踐你所學習的每一個知識點。
結(jié)語
非常感謝閱讀這篇入門指導。
我希望這篇指導可以激發(fā)你去學習更多關(guān)于 React 知識的興趣以及了解 React 能做的每一件事。
