- ReactJS 教程
- ReactJS - 首頁
- ReactJS - 簡介
- ReactJS - 路線圖
- ReactJS - 安裝
- ReactJS - 特性
- ReactJS - 優勢與劣勢
- ReactJS - 架構
- ReactJS - 建立 React 應用
- ReactJS - JSX
- ReactJS - 元件
- ReactJS - 巢狀元件
- ReactJS - 使用新建立的元件
- ReactJS - 元件集合
- ReactJS - 樣式
- ReactJS - 屬性 (props)
- ReactJS - 使用屬性建立元件
- ReactJS - props 驗證
- ReactJS - 建構函式
- ReactJS - 元件生命週期
- ReactJS - 事件管理
- ReactJS - 建立一個事件感知元件
- ReactJS - 在 Expense Manager APP 中引入事件
- ReactJS - 狀態管理
- ReactJS - 狀態管理 API
- ReactJS - 無狀態元件
- ReactJS - 使用 React Hooks 進行狀態管理
- ReactJS - 使用 React Hooks 的元件生命週期
- ReactJS - 佈局元件
- ReactJS - 分頁
- ReactJS - Material UI
- ReactJS - Http 客戶端程式設計
- ReactJS - 表單程式設計
- ReactJS - 受控元件
- ReactJS - 非受控元件
- ReactJS - Formik
- ReactJS - 條件渲染
- ReactJS - 列表
- ReactJS - Keys
- ReactJS - 路由
- ReactJS - Redux
- ReactJS - 動畫
- ReactJS - Bootstrap
- ReactJS - Map
- ReactJS - 表格
- ReactJS - 使用 Flux 管理狀態
- ReactJS - 測試
- ReactJS - CLI 命令
- ReactJS - 構建和部署
- ReactJS - 示例
- Hooks
- ReactJS - Hooks 簡介
- ReactJS - 使用 useState
- ReactJS - 使用 useEffect
- ReactJS - 使用 useContext
- ReactJS - 使用 useRef
- ReactJS - 使用 useReducer
- ReactJS - 使用 useCallback
- ReactJS - 使用 useMemo
- ReactJS - 自定義 Hooks
- ReactJS 高階
- ReactJS - 可訪問性
- ReactJS - 程式碼分割
- ReactJS - Context
- ReactJS - 錯誤邊界
- ReactJS - 轉發 Refs
- ReactJS - Fragments
- ReactJS - 高階元件
- ReactJS - 與其他庫整合
- ReactJS - 效能最佳化
- ReactJS - Profiler API
- ReactJS - Portals
- ReactJS - 無 ES6 ECMAScript 的 React
- ReactJS - 無 JSX 的 React
- ReactJS - Reconciliation
- ReactJS - Refs 和 DOM
- ReactJS - Render Props
- ReactJS - 靜態型別檢查
- ReactJS - Strict Mode
- ReactJS - Web Components
- 其他概念
- ReactJS - 日期選擇器
- ReactJS - Helmet
- ReactJS - 內聯樣式
- ReactJS - PropTypes
- ReactJS - BrowserRouter
- ReactJS - DOM
- ReactJS - 走馬燈
- ReactJS - 圖示
- ReactJS - 表單元件
- ReactJS - 參考 API
- ReactJS 有用資源
- ReactJS 快速指南
- ReactJS - 有用資源
- ReactJS - 討論
Reactjs 快速指南
ReactJS - 簡介
ReactJS 是一個免費且開源的前端 JavaScript 庫,用於開發各種互動式使用者介面。它是一個簡單、功能豐富且基於元件的 UI 庫。當我們說基於元件時,我們的意思是 React 透過建立各種可重用和獨立的程式碼來開發應用程式。因此,這個 UI 庫廣泛用於 Web 開發。
ReactJS 可用於開發小型應用程式以及大型複雜應用程式。ReactJS 提供了最少且可靠的功能集來啟動 Web 應用程式。React 社群透過提供大量現成的元件來補充 React 庫,以便在創紀錄的時間內開發 Web 應用程式。React 社群還在 React 庫之上提供了狀態管理、路由等高階概念。
React 版本
Reactjs 庫由 Facebook 的軟體工程師 Jordan Walke 於 2011 年建立。然後,React 的初始版本 0.3.0 於 2013 年 5 月釋出,最新版本 *17.0.1* 於 2020 年 10 月釋出。主要版本引入了重大更改,次要版本引入了新功能,而不會破壞現有功能。根據需要釋出錯誤修復。React 遵循 *語義版本控制 (semver)* 原則。
為什麼需要 ReactJS?
儘管有各種庫提供開發使用者介面的媒介,但 ReactJS 在受歡迎程度方面仍然名列前茅。原因如下:
**基於元件** - ReactJS 使用多個元件來構建應用程式。這些元件是獨立的,並具有自己的邏輯,這使得它們在整個開發過程中都可重用。這將大大減少應用程式的開發時間。
**更好更快的效能** - ReactJS 使用虛擬 DOM。虛擬 DOM 將應用程式元件的先前狀態與當前狀態進行比較,並且僅更新真實 DOM 中的更改。而傳統的 Web 應用程式會再次更新所有元件。這有助於 ReactJS 更快地建立 Web 應用程式。
**極其靈活** - React 允許開發人員和團隊設定他們認為最合適的約定,並根據需要實現它,因為 React 中沒有關於程式碼約定的嚴格規則。
**輕鬆建立動態應用程式** - 動態 Web 應用程式需要更少的程式碼,同時提供更多功能。因此,ReactJS 可以輕鬆建立它們。
**還可以開發移動應用程式** - React 不僅可以開發 Web 應用程式,還可以使用 React Native 開發移動應用程式。React Native 是一個開源的 UI 軟體框架,它源自 React 本身。它使用 React 框架為 Android、macOS、Web、Windows 等開發應用程式。
**除錯更容易** - React 中的資料流是單向的,即在使用 React 設計應用程式時,子元件巢狀在父元件中。由於資料流是單向的,因此更容易除錯錯誤並發現錯誤。
應用程式
以下是使用 *React 庫* 的一些流行網站:
- *Facebook*,流行的社交媒體應用程式 - React 最初是在 Facebook(或 Meta)開發的,因此它們使用它來執行其應用程式是很自然的。至於它們的移動應用程式,它使用 React Native 來顯示 Android 和 iOS 元件,而不是 DOM。Facebook 的程式碼庫現在包含超過 20,000 個元件,並使用公開的 React 版本。
- *Instagram*,流行的圖片分享應用程式 - Instagram 也完全基於 React,因為它也由 Meta 提供支援。顯示其用法的主要功能包括地理位置、標籤、Google Maps API 等。
- *Netflix*,流行的媒體流應用程式 - Netflix 於 2015 年切換到 React。影響此決定的主要因素是:1) 啟動速度,以減少渲染主頁的處理時間並啟用 UI 中的動態元素;2) 模組化,以允許必須與控制體驗共存的各種功能;3) 執行時效能,以實現高效的 UI 渲染。
- *Code Academy*,流行的線上培訓應用程式 - Code Academy 使用 React,因為“指令碼經過實戰檢驗、易於思考、易於進行 SEO,並且與舊程式碼相容,並且足夠靈活以適應未來”。
- *Reddit*,流行的內容分享應用程式 - Reddit 也是從頭開始使用 React 開發的。
正如您所看到的,每個領域的大多數流行應用程式都是由 *React 庫* 開發的。
ReactJS - 安裝
本章解釋如何在您的機器上安裝 React 庫及其相關工具。在開始安裝之前,讓我們先驗證先決條件。
React 為開發人員提供了 CLI 工具,可以加快基於 React 的 Web 應用程式的建立、開發和部署速度。React CLI 工具依賴於 Node.js,必須安裝在您的系統中。希望您已經在您的機器上安裝了 Node.js。我們可以使用以下命令進行檢查:
node --version
您可以看到您可能安裝的 Nodejs 版本。對我來說,顯示如下:
v14.2.0
如果未安裝 *Nodejs*,您可以訪問 https://nodejs.com.tw/en/download/. 下載並安裝。
工具鏈
要開發輕量級功能(如表單驗證、模式對話方塊等),可以直接透過內容交付網路 (CDN) 將 React 庫包含到 Web 應用程式中。這類似於在 Web 應用程式中使用 jQuery 庫。對於中等規模到大型應用程式,建議將應用程式編寫為多個檔案,然後使用 webpack、parcel、rollup 等捆綁器在部署程式碼之前編譯和捆綁應用程式。
React 工具鏈有助於建立、構建、執行和部署 React 應用程式。React 工具鏈基本上提供了一個啟動專案模板,其中包含啟動應用程式所需的所有必要程式碼。
一些流行的開發 React 應用程式的工具鏈包括:
- Create React App - 面向 SPA 的工具鏈
- Next.js - 面向伺服器端渲染的工具鏈
- Gatsby - 面向靜態內容的工具鏈
開發 React 應用程式所需的工具包括:
- *serve*,一個靜態伺服器,用於在開發過程中為我們的應用程式提供服務
- Babel 編譯器
- Create React App CLI
讓我們在本節中學習上述工具的基礎知識以及如何在本章中安裝它們。
*serve* 靜態伺服器
*serve* 是一個輕量級的 Web 伺服器。它提供靜態站點和單頁應用程式。它載入速度快,佔用記憶體最少。它可以用來為 React 應用程式提供服務。讓我們使用系統中的 *npm* 包管理器安裝該工具。
npm install serve -g
讓我們建立一個簡單的靜態站點,並使用 *serve* 應用程式為該應用程式提供服務。
開啟命令提示符並轉到您的工作區。
cd /go/to/your/workspace
建立一個新資料夾 *static_site* 並更改為新建立的資料夾。
mkdir static_site cd static_site
接下來,使用您喜歡的 html 編輯器在資料夾中建立一個簡單的網頁。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Static website</title>
</head>
<body>
<div><h1>Hello!</h1></div>
</body>
</html>
接下來,執行 *serve* 命令。
serve .
我們還可以為單個檔案 *index.html* 提供服務,而不是整個資料夾。
serve ./index.html
接下來,開啟瀏覽器並在位址列中輸入 *https://:5000* 並按 Enter 鍵。serve 應用程式將提供我們的網頁,如下所示。
*serve* 將使用預設埠 5000 為應用程式提供服務。如果該埠不可用,它將選擇一個隨機埠並指定它。
│ Serving! │ │ │ │ - Local: https://:57311 │ │ - On Your Network: http://192.168.56.1:57311 │ │ │ │ This port was picked because 5000 is in use. │ │ │ │ Copied local address to clipboard!
Babel 編譯器
Babel 是一個 JavaScript 編譯器,它將許多 JavaScript 變體(es2015、es6 等)編譯成所有瀏覽器都支援的標準 JavaScript 程式碼。React 使用 JSX,這是 JavaScript 的擴充套件,用於設計使用者介面程式碼。Babel 用於將 JSX 程式碼編譯成 JavaScript 程式碼。
要安裝 Babel 及其 React 伴侶,請執行以下命令:
npm install babel-cli@6 babel-preset-react-app@3 -g ... ... + babel-cli@6.26.0 + babel-preset-react-app@3.1.2 updated 2 packages in 8.685s
Babel 幫助我們使用下一代高階 JavaScript 語法編寫應用程式。
Create React App 工具鏈
*Create React App* 是一個現代化的 CLI 工具,用於建立單頁 React 應用程式。它是 React 社群支援的標準工具。它也處理 babel 編譯器。讓我們在本地系統中安裝 *Create React App*。
> npm install -g create-react-app + create-react-app@4.0.1 added 6 packages from 4 contributors, removed 37 packages and updated 12 packages in 4.693s
更新工具鏈
Create React App 工具鏈使用 react-scripts 包來構建和執行應用程式。一旦我們開始開發應用程式,就可以隨時使用 npm 包管理器將 react-script 更新到最新版本。
npm install react-scripts@latest
使用 React 工具鏈的優勢
React 工具鏈開箱即用地提供了許多功能。使用 React 工具鏈的一些優勢包括:
- 預定義的標準應用程式結構。
- 為不同型別的應用程式提供現成的專案模板。
- 包含開發 Web 伺服器。
- 輕鬆包含第三方 React 元件。
- 預設設定用於測試應用程式。
ReactJS - 特性
ReactJS 正在逐漸成為 Web 開發人員中最好的 JavaScript 框架之一。它在前端生態系統中扮演著至關重要的角色。以下是 ReactJS 的重要特性:
虛擬 DOM (Virtual DOM)
元件 (Components)
JSX
單向資料繫結 (One-way data binding)
可擴充套件性 (Scalable)
靈活性 (Flexible)
模組化 (Modular)
虛擬 DOM (Virtual DOM)
虛擬 DOM 是 React 建立的一個特殊的 DOM。虛擬 DOM 代表當前 HTML 文件的真實 DOM。每當 HTML 文件發生更改時,React 都會將更新後的虛擬 DOM 與虛擬 DOM 的先前狀態進行比較,並僅更新實際/真實 DOM 中的不同部分。這提高了 HTML 文件渲染的效能。
例如,如果我們建立一個 React 元件來顯示當前時間,並透過 `setInterval()` 方法定期更新時間,那麼 React 將只更新當前時間,而不是更新元件的全部內容。
元件 (Components)
React 建立在元件的概念之上。所有現代前端框架都依賴於元件架構。元件架構使開發人員能夠將大型應用程式分解成較小的元件,這些元件可以進一步分解成更小的元件。將應用程式分解成更小的元件可以簡化應用程式,並使其更易於理解和管理。
JSX
JSX 是 JavaScript 的擴充套件,可以使用類似於 HTML 的語法建立任意 HTML 元素。這將簡化 HTML 文件的建立,並易於理解文件。React 將 JSX 轉換為包含 React 的 `createElement()` 函式呼叫的 JavaScript 物件,然後再執行它。它提高了應用程式的效能。此外,React 也允許使用純 `createElement()` 函式而無需 JSX 來建立 HTML 文件。這使開發人員能夠在 JSX 不太適合的情況下直接建立 HTML 文件。
單向資料繫結 (One-way data binding)
單向資料繫結防止元件中的資料向後流動。元件只能將其資料傳遞給其子元件。在任何情況下,資料都不能由元件傳遞給其父元件。這將簡化資料處理並降低複雜性。雙向資料繫結起初似乎是必要的,但仔細觀察表明,應用程式只需要單向資料繫結即可完成,這簡化了應用程式的概念。
可擴充套件性 (Scalable)
React 可用於建立任何規模的應用程式。React 元件架構、虛擬 DOM 和單向資料繫結可以在前端應用程式所需合理的時間範圍內正確處理大型應用程式。這些特性使 React 成為一個可擴充套件的解決方案。
靈活性 (Flexible)
React 只提供一些基本的概念來建立真正可擴充套件的應用程式。React 不會以任何方式限制開發人員遵循嚴格的流程。這使開發人員能夠在基本概念之上應用自己的架構,使其更具靈活性。
模組化 (Modular)
React 元件可以在單獨的 JavaScript 檔案中建立,並且可以匯出。這使開發人員能夠將某些元件分類和分組到一個模組中,以便可以在需要的地方匯入和使用。
ReactJS - 優勢與劣勢
React 是一個用於構建可組合使用者介面的庫。它鼓勵建立可重用的 UI 元件,這些元件呈現隨時間變化的資料。許多人使用 React 作為 MVC 中的 V。
React 將 DOM 從您那裡抽象出來,提供更簡單的程式設計模型和更好的效能。React 還可以使用 Node 在伺服器端渲染,並且可以使用 React Native 驅動原生應用程式。React 實現單向反應式資料流,這減少了樣板程式碼,並且比傳統資料繫結更容易理解。
ReactJS 的優勢
以下是 ReactJS 的主要優勢:
效能 (Performance)
易於學習 (Easy to learn)
大量的第三方元件 (Huge collection of third party components)
龐大的社群 (Large community)
SEO 友好性 (SEO Friendliness)
輕鬆啟動 React 專案 (Easy kick-starting of the React project)
豐富的開發者工具 (Rich set of developer tools)
處理大型應用程式 (Handle large application)
效能 (Performance)
React 使用虛擬 DOM 概念來檢查和更新 HTML 文件。虛擬 DOM 是 React 建立的一個特殊的 DOM。虛擬 DOM 代表當前文件的真實 DOM。每當文件發生更改時,React 都會將更新後的虛擬 DOM 與虛擬 DOM 的先前狀態進行比較,並僅更新實際/真實 DOM 中的不同部分。這提高了 HTML 文件渲染的效能。
例如,如果我們建立一個 React 元件來顯示當前時間,並透過 `setInterval()` 方法定期更新時間,那麼 React 將只更新當前時間,而不是更新元件的全部內容。
易於學習 (Easy to learn)
React 的核心概念可以在不到一天的時間內學會。React 可以使用純 JavaScript (ES6) 或 TypeScript 進行編碼。要開始使用 React,只需具備 JavaScript 的基本知識即可。對於高階開發人員,TypeScript 提供型別安全性和豐富的語言特性。開發人員可以透過學習 JSX(類似於 HTML)和屬性 (props) 在幾小時內建立一個 React 元件。學習 React 狀態管理將使開發人員能夠建立動態元件,該元件在狀態更改時更新內容。React 為其元件提供了簡單的生命週期,可用於正確設定和銷燬元件。
大量的第三方元件 (Huge collection of third party components)
除了核心 React 庫(大小隻有幾 KB)之外,React 社群還為各種應用程式提供了大量的元件,從簡單的 UI 元件到功能齊全的 PDF 檢視器元件。React 在每個類別中都提供了多種選擇。例如,可以使用 Redux 或 MobX 庫進行高階狀態管理。Redux 和 MobX 只是兩種流行的狀態管理庫。React 有超過 10 個庫可以實現相同的功能。類似地,React 社群在每個類別中都提供了許多第三方庫,例如路由、資料網格、日曆、表單程式設計等。
龐大的社群 (Large community)
React 開發者社群是一個擁有大量活動的龐大社群。React 社群非常活躍,您可以透過 Google、Stack Overflow 等在幾分鐘內獲得任何與 React 相關的疑問的答案。
SEO 友好性
React 是少數支援 SEO 功能的 JavaScript 庫之一。由於 React 元件和 JSX 類似於 HTML 元素,因此無需太多程式碼/設定即可輕鬆實現 SEO。
輕鬆啟動 React 專案
React 提供了一個 CLI 應用程式 create-react-app 來建立一個新的 React 應用程式。create-react-app 應用程式不僅可以建立一個新的應用程式,還可以構建並在本地環境中執行應用程式,而無需任何其他依賴項。create-react-app 允許開發人員選擇一個模板,該模板允許應用程式在初始設定期間包含更多樣板程式碼。這允許開發人員只需點選幾下即可啟動小型應用程式到大型應用程式。
除了 create-react-app 之外,React 社群還有其他工具,例如 Next.js、Gatsby 等,這些工具允許開發人員在短時間內建立高階應用程式。
豐富的開發者工具 (Rich set of developer tools)
React 社群提供了必要的開發者工具來提高開發人員的生產力。Chrome、Edge 和 Firefox 瀏覽器的 React 開發者工具 使開發人員能夠選擇一個 React 元件並檢視元件的當前狀態。此外,它還使開發人員能夠透過在瀏覽器的開發者選項卡中將其顯示為元件樹來清晰地瞭解元件層次結構的檢視。
處理大型應用程式 (Handle large application)
React 使用組合將多個元件合併成一個更大的元件,這反過來又允許建立更大的元件。React 元件可以在單個 JavaScript 檔案中建立,並可以設定為可匯出。此功能允許將多個元件分組到一個公共類別(模組)下,並可在其他地方重用。
React 庫的可組合和模組化特性允許開發人員建立大型應用程式,與其他前端框架相比,這些應用程式相對易於維護。
React 的缺點
即使 React 庫有很多優點,它也有一些缺點。一些缺點如下:
缺乏高質量的文件
沒有標準/推薦的應用程式開發方法
快速的發展節奏
高階 JavaScript 的使用
JavaScript 擴充套件
只是一個 UI 庫
缺乏高質量的文件
React 庫在其主要網站上提供了不錯的文件。它涵蓋了基本概念和一些示例。即使這是一個理解 React 基本概念的良好開端,它也沒有提供具有多個示例的深入和詳細的解釋。React 社群參與並提供了許多不同複雜性和質量的文章。但是,它們沒有組織在一個地方,開發人員無法輕鬆學習。
沒有或很少有標準的應用程式開發方法
React 只是一個 UI 庫,只有少量概念和標準建議。即使 React 可用於建立大型/複雜應用程式,也沒有標準或推薦的建立應用程式的方法。由於沒有標準方法,React 社群使用多種架構來構建其應用程式。開發人員可以自由地為其應用程式選擇一種方法。在應用程式開發之初做出錯誤的選擇會使應用程式複雜化,並延遲應用程式的開發。
快速的發展節奏
React 每年都會發布幾次新版本的庫。每個版本都有一些附加功能和一些重大更改。開發人員需要快速學習並應用新概念來穩定應用程式。
高階 JavaScript 的使用
即使 React 庫的核心概念非常簡單易學,但高階概念卻相當複雜,因為它利用了 JavaScript 的高階特性。此外,React 還有許多高階概念來解決 HTML/表單程式設計的許多複雜場景。大量的高階概念對於開發人員來說確實是一個很大的挑戰,需要學習和掌握。
JavaScript 擴充套件
JSX 是 JavaScript 語言的擴充套件。JSX 類似於 HTML,簡化了元件開發。JSX 與 HTML 程式設計也有一些區別,需要小心正確應用。此外,JSX 需要在瀏覽器中執行之前編譯成 JavaScript,這會增加應用程式的步驟/負擔。
只是一個 UI 庫
正如我們前面所瞭解的,React 只是一個 UI 庫,而不是一個框架。建立一個具有良好架構的 React 應用程式需要仔細選擇和應用額外的第三方 React 庫。不良的設計可能會在應用程式開發的後期/最終階段影響應用程式。
ReactJS - 架構
React 庫建立在堅實的基礎之上。它簡單、靈活且可擴充套件。正如我們前面瞭解到的,React 是一個用於在 Web 應用程式中建立使用者介面的庫。React 的主要目的是使開發人員能夠使用純 JavaScript 建立使用者介面。通常,每個使用者介面庫都會引入一種新的模板語言(我們需要學習)來設計使用者介面,並提供在模板內或單獨編寫邏輯的選項。
React 沒有引入新的模板語言,而是引入了以下三個簡單的概念:
React 元素
HTML DOM 的 JavaScript 表示。React 提供了一個 API,`React.createElement` 用於建立 React 元素。
JSX
一種用於設計使用者介面的 JavaScript 擴充套件。JSX 是一種基於 XML 的可擴充套件語言,支援略微修改的 HTML 語法。JSX 可以編譯成 React 元素,並用於建立使用者介面。
React 元件
React 元件是 React 應用程式的主要構建塊。它使用 React 元素和 JSX 來設計其使用者介面。React 元件基本上是一個 JavaScript 類(擴充套件了`React.Component` 類)或純 JavaScript 函式。React 元件具有屬性、狀態管理、生命週期和事件處理程式。React 元件能夠執行簡單和高階邏輯。
讓我們在 React 元件章節中瞭解更多關於元件的資訊。
React 應用程式的架構
React 庫只是一個 UI 庫,它不強制執行任何特定模式來編寫複雜的應用程式。開發人員可以自由選擇他們選擇的模式。React 社群提倡某些設計模式。其中一種模式是 Flux 模式。React 庫還提供了許多概念,如高階元件、上下文、渲染屬性、Refs 等,以編寫更好的程式碼。React Hooks 是一個不斷發展的概念,用於在大專案中進行狀態管理。讓我們嘗試瞭解 React 應用程式的高階架構。
React 應用從單個根元件開始。
根元件使用一個或多個元件構建。
每個元件都可以巢狀到任何級別的其他元件中。
組合是 React 庫的核心概念之一。因此,每個元件都是透過組合更小的元件而不是從另一個元件繼承來構建的。
大多陣列件都是使用者介面元件。
React 應用可以包含用於特定目的的第三方元件,例如路由、動畫、狀態管理等。
React 應用程式的工作流程
本章將透過建立和分析一個簡單的 React 應用程式來了解 React 應用程式的工作流程。
開啟命令提示符並轉到您的工作區。
cd /go/to/your/workspace
接下來,建立一個資料夾 `static_site` 並切換到新建立的資料夾。
mkdir static_site cd static_site
示例
接下來,建立一個檔案 `hello.html` 並編寫一個簡單的 React 應用程式。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React Application</title>
</head>
<body>
<div id="react-app"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script language="JavaScript">
element = React.createElement('h1', {}, 'Hello React!')
ReactDOM.render(element, document.getElementById('react-app'));
</script>
</body>
</html>
接下來,使用 serve web 伺服器啟動應用程式。
serve ./hello.html
輸出
接下來,開啟您喜歡的瀏覽器。在位址列中輸入 `https://:5000`,然後按 Enter。
讓我們分析程式碼並進行少量修改,以便更好地理解 React 應用程式。
這裡,我們使用了 React 庫提供的兩個 API。
React.createElement
用於建立 React 元素。它需要三個引數:
- 元素標籤
- 元素屬性(作為物件)
- 元素內容 - 它也可以包含巢狀的 React 元素
ReactDOM.render
用於將元素渲染到容器中。它需要兩個引數:
- React 元素或 JSX
- 網頁的根元素
巢狀的 React 元素
由於`React.createElement` 允許巢狀 React 元素,讓我們新增如下所示的巢狀元素:
示例
<script language="JavaScript">
element = React.createElement('div', {}, React.createElement('h1', {}, 'Hello React!'));
ReactDOM.render(element, document.getElementById('react-app'));
</script>
輸出
它將生成以下內容:
<div><h1> Hello React!</h1></div>
使用 JSX
接下來,讓我們完全刪除 React 元素並引入如下所示的 JSX 語法:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React Application</title>
</head>
<body>
<div id="react-app"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
ReactDOM.render(
<div><h1>Hello React!</h1></div>,
document.getElementById('react-app')
);
</script>
</body>
</html>
在這裡,我們包含了 babel 來將 JSX 轉換成 JavaScript,並在 script 標籤中添加了 `type="text/babel"`。
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> ... ... </script>
接下來,執行應用程式並開啟瀏覽器。應用程式的輸出如下所示:
接下來,讓我們建立一個新的 React 元件 Greeting,然後嘗試在網頁中使用它。
<script type="text/babel">
function Greeting() {
return <div><h1>Hello JSX!</h1></div>
}
ReactDOM.render(<Greeting />, document.getElementById('react-app') );
</script>
結果相同,如下所示:
透過分析應用程式,我們可以將 React 應用程式的工作流程視覺化,如下圖所示。
React 應用透過傳遞使用 React 元件(使用 JSX 或 React 元素格式編寫)建立的使用者介面和渲染使用者介面的容器來呼叫`ReactDOM.render` 方法。
`ReactDOM.render` 處理 JSX 或 React 元素併發出虛擬 DOM。
虛擬 DOM 將被合併並渲染到容器中。
React 應用程式的架構
React 庫只是一個 UI 庫,它不強制執行任何特定模式來編寫複雜的應用程式。開發人員可以自由選擇他們選擇的模式。React 社群提倡某些設計模式。其中一種模式是 Flux 模式。React 庫還提供了許多概念,如高階元件、上下文、渲染屬性、Refs 等,以編寫更好的程式碼。React Hooks 是一個不斷發展的概念,用於在大專案中進行狀態管理。讓我們嘗試瞭解 React 應用程式的高階架構。
ReactJS - 建立 React 應用
正如我們前面瞭解到的,React 庫既可以用於簡單的應用程式,也可以用於複雜的應用程式。簡單的應用程式通常在其指令碼部分包含 React 庫。在複雜的應用程式中,開發人員必須將程式碼分成多個檔案,並將程式碼組織成標準結構。在這裡,React 工具鏈提供預定義的結構來引導應用程式。此外,開發人員可以自由使用他們自己的專案結構來組織程式碼。
讓我們看看如何建立簡單和複雜的 React 應用程式:
使用 Rollup 打包器
Rollup 是一個小型且快速的 JavaScript 打包器。讓我們在本節中學習如何使用 Rollup 打包器。
以下是使用 Rollup 打包器建立應用程式的步驟:
步驟 1 - 開啟終端並轉到您的工作區。
cd /go/to/your/workspace
步驟 2 - 接下來,建立一個資料夾 `expense-manager-rollup` 並移動到新建立的資料夾。同時,在您喜歡的編輯器或 IDE 中開啟該資料夾。
mkdir expense-manager-rollup cd expense-manager-rollup
然後,建立並初始化專案。
npm init -y
步驟 3 - 要安裝 React 庫(`react` 和 `react-dom`),請執行以下命令。
npm install react@^17.0.0 react-dom@^17.0.0 --save
然後,使用以下命令將 babel 及其預設庫作為開發依賴項安裝。
npm install @babel/preset-env @babel/preset-react @babel/core @babel/plugin-proposal-class-properties -D
接下來,將 rollup 及其外掛庫作為開發依賴項安裝。
npm i -D rollup postcss@8.1 @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-replace rollup-plugin-livereload rollup-plugin-postcss rollup-plugin-serve postcss@8.1 postcss-modules@4 rollup-plugin-postcss
接下來,安裝 corejs 和 regenerator runtime 用於非同步程式設計。
npm i regenerator-runtime core-js
步驟 4 - 稍後,在根資料夾下建立一個 babel 配置檔案 `.babelrc` 來配置 babel 編譯器。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3,
"targets": "> 0.25%, not dead"
}
],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}
rollup.config.js
接下來,在根資料夾中建立一個 `rollup.config.js` 檔案來配置 rollup 打包器。
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import replace from '@rollup/plugin-replace';
import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';
import postcss from 'rollup-plugin-postcss'
export default {
input: 'src/index.js',
output: {
file: 'public/index.js',
format: 'iife',
},
plugins: [
commonjs({
include: [
'node_modules/**',
],
exclude: [
'node_modules/process-es6/**',
],
}),
resolve(),
babel({
exclude: 'node_modules/**'
}),
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
postcss({
autoModules: true
}),
livereload('public'),
serve({
contentBase: 'public',
port: 3000,
open: true,
}), // index.html should be in root of project
]
}
package.json
接下來,更新 `package.json` 幷包含我們的入口點(`public/index.js` 和 `public/styles.css`)以及構建和執行應用程式的命令。
...
"main": "public/index.js",
"style": "public/styles.css",
"files": [
"public"
],
"scripts": {
"start": "rollup -c -w",
"build": "rollup"
},
...
步驟 5 - 接下來,在應用程式的根目錄下建立一個 `src` 資料夾,該資料夾將儲存應用程式的所有原始碼。
接下來,在 `src` 下建立一個資料夾 `components` 來包含我們的 React 元件。我們的想法是建立兩個檔案,`
應用程式的最終結構如下所示:
|-- package-lock.json |-- package.json |-- rollup.config.js |-- .babelrc `-- public |-- index.html `-- src |-- index.js `-- components | |-- mycom.js | |-- mycom.css
現在,讓我們建立一個新的元件 `HelloWorld` 來確認我們的設定是否正常工作。
HelloWorld.js
建立一個檔案 `HelloWorld.js`(在 `components` 資料夾下),並編寫一個簡單的元件來發出 `Hello World` 訊息。
import React from "react";
class HelloWorld extends React.Component {
render() {
return (
<div>
<h1>Hello World!</h1>
</div>
);
}
}
export default HelloWorld;
index.js
接下來,建立我們的主檔案 `index.js`(在 `src` 資料夾下),並呼叫我們新建立的元件。
import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorld from './components/HelloWorld';
ReactDOM.render(
<React.StrictMode>
<HelloWorld />
</React.StrictMode>,
document.getElementById('root')
);
在根目錄下建立一個 `public` 資料夾。
index.html
接下來,建立一個 html 檔案 `index.html`(在 `public` 資料夾下),它將成為我們的應用程式入口點。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Expense Manager :: Rollup version</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
最後,構建並執行應用程式。
npm start
`npm` build 命令將執行 `rollup` 並將我們的應用程式打包到單個檔案 `dist/index.js` 中,並開始啟動應用程式。`dev` 命令將在原始碼更改時重新編譯程式碼,並在瀏覽器中重新載入更改。
> expense-manager-rollup@1.0.0 build /path/to/your/workspace/expense-manager-rollup > rollup -c rollup v2.36.1 bundles src/index.js → dist\index.js... LiveReload enabled https://:10001 -> /path/to/your/workspace/expense-manager-rollup/dist created dist\index.js in 4.7s waiting for changes...
開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter。serve 應用程式將提供我們的網頁,如下所示。
使用 Parcel 打包器
Parcel 是一個快速且零配置的打包器。它只需要應用程式的入口點,它將自行解析依賴項並打包應用程式。讓我們在本節中學習如何使用 Parcel 打包器。
步驟 1 - 首先,安裝 Parcel 打包器。
npm install -g parcel-bundler
然後,開啟終端並轉到您的工作區。
cd /go/to/your/workspace
步驟 2 - 接下來,建立一個資料夾 `expense-manager-parcel` 並移動到新建立的資料夾。同時,在您喜歡的編輯器或 IDE 中開啟該資料夾。
mkdir expense-manager-parcel cd expense-manager-parcel
使用以下命令建立並初始化專案。
npm init -y
步驟 3 - 安裝 React 庫(`react` 和 `react-dom`)。
npm install react@^17.0.0 react-dom@^17.0.0 --save
將 babel 及其預設庫作為開發依賴項安裝。
npm install @babel/preset-env @babel/preset-react @babel/core @babel/plugin-proposal-class-properties -D
然後,在根資料夾下建立一個 babel 配置檔案 `.babelrc` 來配置 babel 編譯器。
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}
步驟 4 - 更新 `package.json` 幷包含我們的入口點(`src/index.js`)以及構建和執行應用程式的命令。
...
"main": "src/index.js",
"scripts": {
"start": "parcel public/index.html",
"build": "parcel build public/index.html --out-dir dist"
},
...
步驟 5 - 在應用程式的根目錄下建立一個 `src` 資料夾,該資料夾將儲存應用程式的所有原始碼。
接下來,在 `src` 下建立一個資料夾 `components` 來包含我們的 React 元件。我們的想法是建立兩個檔案,`
應用程式的最終結構如下所示:
|-- package-lock.json |-- package.json |-- .babelrc `-- public |-- index.html `-- src |-- index.js `-- components | |-- mycom.js | |-- mycom.css
讓我們建立一個新的元件 `HelloWorld` 來確認我們的設定是否正常工作。建立一個檔案 `HelloWorld.js`(在 `components` 資料夾下),並編寫一個簡單的元件來發出 `Hello World` 訊息。
HelloWorld.js
import React from "react";
class HelloWorld extends React.Component {
render() {
return (
<div>
<h1>Hello World!</h1>
</div>
);
}
}
export default HelloWorld;
index.js
現在,建立我們的主檔案 `index.js`(在 `src` 資料夾下),並呼叫我們新建立的元件。
import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorld from './components/HelloWorld';
ReactDOM.render(
<React.StrictMode>
<HelloWorld />
</React.StrictMode>,
document.getElementById('root')
);
接下來,在根目錄下建立一個 `public` 資料夾。
index.html
建立一個 html 檔案 `index.html`(在 `public` 資料夾下),它將成為我們的應用程式入口點。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Expense Manager :: Parcel version</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="../src/index.js"></script>
</body>
</html>
最後,構建並執行應用程式。
npm start
`npm` build 命令將執行 parcel 命令。它將打包並動態提供應用程式。它會在原始碼更改時重新編譯,並在瀏覽器中重新載入更改。
> expense-manager-parcel@1.0.0 dev /go/to/your/workspace/expense-manager-parcel > parcel index.html Server running at https://:1234 √ Built in 10.41s.
開啟瀏覽器並在位址列中輸入 `https://:1234` 並按 Enter。
要建立應用程式的生產包以將其部署到生產伺服器,請使用 `build` 命令。它將在 `dist` 資料夾下生成包含所有打包原始碼的 `index.js` 檔案。
npm run build > expense-manager-parcel@1.0.0 build /go/to/your/workspace/expense-manager-parcel > parcel build index.html --out-dir dist &sqrt; Built in 6.42s. dist\src.80621d09.js.map 270.23 KB 79ms dist\src.80621d09.js 131.49 KB 4.67s dist\index.html 221 B 1.63s
ReactJS - JSX
正如我們前面瞭解到的,React JSX 是 JavaScript 的擴充套件。它允許編寫看起來像 HTML 程式碼的 JavaScript 程式碼。例如,考慮以下程式碼
const element = <h1>Hello React!</h1>
上面程式碼中提供的標籤稱為 JSX。JSX 主要用於提供有關介面外觀的資訊。然而,它並非完全是模板語言,而是 JavaScript 的語法擴充套件。JSX 生成渲染到 DOM 的元素,以指定輸出的外觀。
在 ReactJS 中使用 JSX
JSX 使開發人員能夠使用 XML 語法建立虛擬 DOM。它編譯成純 JavaScript(`React.createElement` 函式呼叫),因此,它可以在任何有效的 JavaScript 程式碼中使用。
- 賦值給變數。
var greeting = <h1>Hello React!</h1>
- 根據條件賦值給變數。
var canGreet = true;
if(canGreet) {
greeting = <h1>Hello React!</h1>
}
- 可以用作函式的返回值。
function Greeting() {
return <h1>Hello React!</h1>
}
greeting = Greeting()
- 可以用作函式的引數。
function Greet(message) {
ReactDOM.render(message, document.getElementById('react-app')
}
Greet(<h1>Hello React!</h1>)
為什麼使用 JSX?
在 React 中使用 JSX 不是必須的,因為有很多方法可以實現與 JSX 相同的功能;但它作為一種視覺輔助工具,在處理 JavaScript 程式碼中的 UI 時非常有用。
JSX 在將程式碼轉換為 JavaScript 時會執行最佳化,使其比普通的 JavaScript 更快。
React 使用包含單個檔案中標記和邏輯的元件,而不是單獨的檔案。
由於資料流是單向的,因此大多數錯誤都可以在編譯時發現。
使用 JSX 可以更輕鬆地建立模板。
我們可以在條件語句(if-else)和迴圈語句(for 迴圈)中使用 JSX,可以將其賦值給變數,作為引數接受它,或從函式返回它。
使用 JSX 可以防止跨站點指令碼攻擊或注入攻擊。
JSX 中的表示式
JSX 支援純 JavaScript 語法的表示式。表示式必須用大括號 `{ }` 括起來。表示式可以包含 JSX 定義的上下文中可用的所有變數。讓我們建立一個帶有表示式的簡單 JSX。
示例
<script type="text/babel">
var cTime = new Date().toTimeString();
ReactDOM.render(
<div><p>The current time is {cTime}</p></div>,
document.getElementById('react-app') );
</script>
輸出
此處,在 JSX 表示式中使用了 `cTime`。上述程式碼的輸出如下所示:
The Current time is 21:19:56 GMT+0530(India Standard Time)
在 JSX 中使用表示式的積極副作用之一是它可以防止 _注入攻擊_,因為它會將任何字串轉換為 HTML 安全字串。
JSX 中的函式
JSX 支援使用者自定義的 JavaScript 函式。函式的使用類似於表示式。讓我們建立一個簡單的函式並在 JSX 中使用它。
示例
<script type="text/babel">
var cTime = new Date().toTimeString();
ReactDOM.render(
<div><p>The current time is {cTime}</p></div>,
document.getElementById('react-app')
);
</script>
輸出
此處,使用 `getCurrentTime()` 獲取當前時間,輸出與下面指定的類似:
The Current time is 21:19:56 GMT+0530(India Standard Time)
JSX 中的屬性
JSX 支援類似 HTML 的屬性。所有 HTML 標籤及其屬性都受支援。屬性必須使用駝峰命名法(遵循 JavaScript DOM API)而不是普通的 HTML 屬性名稱來指定。例如,HTML 中的 class 屬性必須定義為 `className`。以下是一些其他示例:
- `htmlFor` 代替 `for`
- `tabIndex` 代替 `tabindex`
- `onClick` 代替 `onclick`
示例
<style>
.red { color: red }
</style>
<script type="text/babel">
function getCurrentTime() {
return new Date().toTimeString();
}
ReactDOM.render(
<div>
<p>The current time is <span className="red">{getCurrentTime()}</span></p>
</div>,
document.getElementById('react-app')
);
</script>
輸出
輸出如下:
The Current time is 22:36:55 GMT+0530(India Standard Time)
在屬性中使用表示式
JSX 支援在屬性內指定表示式。在屬性中,不應與表示式一起使用雙引號。必須使用表示式或使用雙引號的字串。上面的例子可以修改為在屬性中使用表示式。
<style>
.red { color: red }
</style>
<script type="text/babel">
function getCurrentTime() {
return new Date().toTimeString();
}
var class_name = "red";
ReactDOM.render(
<div>
<p>The current time is <span className={class_name}>{getCurrentTime()}</span></p>
</div>,
document.getElementById('react-app')
);
</script>
JSX 中的巢狀元素
JSX 中的巢狀元素可以用作 JSX 子元素。在顯示巢狀元件時,它們非常有用。您可以一起使用任何型別的元素,包括標籤、字面量、函式、表示式等。但是 false、null、undefined 和 true 都是 JSX 的有效元素;它們只是不呈現,因為這些 JSX 表示式都將呈現為相同的內容。在這種情況下,JSX 類似於 HTML。
以下是一個簡單的程式碼,用於演示在 JSX 中使用巢狀元素:
<div>
This is a list:
<ul>
<li>Element 1</li>
<li>Element 2</li>
</ul>
</div>
ReactJS - 元件
React 元件是 React 應用程式的構建塊。讓我們在本節中學習如何建立一個新的 React 元件以及 React 元件的功能。
React 元件表示網頁中一小部分使用者介面。React 元件的主要工作是呈現其使用者介面並在其內部狀態發生變化時更新它。除了呈現 UI 之外,它還管理屬於其使用者介面的事件。總而言之,React 元件提供以下功能:
- 使用者介面的初始渲染。
- 事件的管理和處理。
- 每當內部狀態發生變化時更新使用者介面。
React 元件使用三個概念來實現這些功能:
**屬性** - 使元件能夠接收輸入。
**事件** - 使元件能夠管理 DOM 事件和終端使用者互動。
**狀態** - 使元件保持狀態。有狀態元件會根據其狀態更新其 UI。
React 中有兩種型別的元件:分別是:
函式元件
類元件
函式元件
函式元件實際上是 JavaScript 函式。此 React 元件接受單個物件引數並返回一個 React 元素。請注意,React 中的元素不是元件,但元件由多個元素組成。以下是 React 中函式元件的語法:
function function_name(argument_name) {
function_body;
}
類元件
類似地,類元件是由多個函式組成的基本類。React 的所有類元件都是 `**React.Component**` 類的子類,因此類元件必須始終擴充套件它。以下是基本語法:
class class_name extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
讓我們在接下來的章節中逐一學習所有概念。
建立 React 元件
React 庫有兩種元件型別。這些型別根據其建立方式進行分類。
- 函式元件 - 使用純 JavaScript 函式。
- ES6 類元件 - 使用 ES6 類。
函式元件和類元件之間的核心區別在於:
函式元件非常簡潔。它的唯一要求是返回一個 _React 元素_。
function Hello() {
return '<div>Hello</div>'
}
可以使用 ES6 類元件完成相同的功能,但需要編寫少量額外的程式碼。
class ExpenseEntryItem extends React.Component {
render() {
return (
<div>Hello</div>
);
}
}
類元件開箱即用地支援狀態管理,而函式元件不支援狀態管理。但是,React 為函式元件提供了一個鉤子 `useState()` 來維護其狀態。
類元件具有生命週期,並且可以透過專用的回撥 API 訪問每個生命週期事件。函式元件沒有生命週期。同樣,React 為函式元件提供了一個鉤子 `useEffect()` 來訪問元件的不同階段。
建立類元件
讓我們建立一個新的 React 元件(在我們的 expense-manager 應用程式中),`ExpenseEntryItem` 來展示一個支出條目項。支出條目項包括名稱、金額、日期和類別。支出條目項的物件表示如下:
{
'name': 'Mango juice',
'amount': 30.00,
'spend_date': '2020-10-10'
'category': 'Food',
}
在您喜歡的編輯器中開啟 `expense-manager` 應用程式。
接下來,在 `src/components` 資料夾下建立一個檔案 `ExpenseEntryItem.css` 來設定我們元件的樣式。
接下來,透過擴充套件 `React.Component`,在 `src/components` 資料夾下建立一個檔案 `ExpenseEntryItem.js`。
import React from 'react';
import './ExpenseEntryItem.css';
class ExpenseEntryItem extends React.Component {
}
接下來,在 `ExpenseEntryItem` 類中建立一個方法 `render`。
class ExpenseEntryItem extends React.Component {
render() {
}
}
接下來,使用 JSX 建立使用者介面並從 `render` 方法返回它。
class ExpenseEntryItem extends React.Component {
render() {
return (
<div>
<div><b>Item:</b> <em>Mango Juice</em></div>
<div><b>Amount:</b> <em>30.00</em></div>
<div><b>Spend Date:</b> <em>2020-10-10</em></div>
<div><b>Category:</b> <em>Food</em></div>
</div>
);
}
}
接下來,將元件指定為預設匯出類。
import React from 'react';
import './ExpenseEntryItem.css';
class ExpenseEntryItem extends React.Component {
render() {
return (
<div>
<div><b>Item:</b> <em>Mango Juice</em></div>
<div><b>Amount:</b> <em>30.00</em></div>
<div><b>Spend Date:</b> <em>2020-10-10</em></div>
<div><b>Category:</b> <em>Food</em></div>
</div>
);
}
}
export default ExpenseEntryItem;
現在,我們成功建立了我們的第一個 React 元件。讓我們在 `index.js` 中使用我們新建立的元件。
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItem from './components/ExpenseEntryItem'
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItem />
</React.StrictMode>,
document.getElementById('root')
);
示例
可以使用 CDN 在網頁中完成相同的功能,如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React application :: ExpenseEntryItem component</title>
</head>
<body>
<div id="react-app"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class ExpenseEntryItem extends React.Component {
render() {
return (
<div>
<div><b>Item:</b> <em>Mango Juice</em></div>
<div><b>Amount:</b> <em>30.00</em></div>
<div><b>Spend Date:</b> <em>2020-10-10</em></div>
<div><b>Category:</b> <em>Food</em></div>
</div>
);
}
}
ReactDOM.render(
<ExpenseEntryItem />,
document.getElementById('react-app') );
</script>
</body>
</html>
接下來,使用 npm 命令啟動應用程式。
npm start
輸出
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
Item: Mango Juice Amount: 30.00 Spend Date: 2020-10-10 Category: Food
建立函式元件
React 元件也可以使用純 JavaScript 函式建立,但功能有限。基於函式的 React 元件不支援狀態管理和其他高階功能。它可以用來快速建立一個簡單的元件。
上面的 `ExpenseEntryItem` 可以用函式重寫,如下所示:
function ExpenseEntryItem() {
return (
<div>
<div><b>Item:</b> <em>Mango Juice</em></div>
<div><b>Amount:</b> <em>30.00</em></div>
<div><b>Spend Date:</b> <em>2020-10-10</em></div>
<div><b>Category:</b> <em>Food</em></div>
</div>
);
}
在這裡,我們只包含了渲染功能,這足以建立一個簡單的 React 元件。
拆分元件
即使 JavaScript 被認為更容易執行,但對於一個相對簡單的專案,由於大量的類或依賴項,程式碼在很多時候會變得複雜。並且程式碼越大,瀏覽器中的載入時間就越長。結果,降低了其效能效率。這就是可以使用程式碼分割的地方。程式碼分割用於將元件或包分成較小的塊以提高效能。
程式碼分割只會載入瀏覽器當前需要的元件。此過程稱為延遲載入。這將大大提高應用程式的效能。必須注意的是,我們並不是試圖減少程式碼量,而只是試圖透過載入使用者可能永遠不需要的元件來減輕瀏覽器的負擔。讓我們來看一個示例程式碼。
示例
讓我們首先檢視一個示例應用程式的捆綁程式碼以執行任何操作。
// file name = app.js
import { sub } from './math.js';
console.log(sub(23, 14));
// file name = math.js
export function sub(a, b) {
return a - b;
}
上述應用程式的捆綁包將如下所示:
function sub(a, b) {
return a - b;
}
console.log(sub(23, 14));
現在,在您的應用程式中引入程式碼分割的最佳方法是使用動態 `import()`。
// Before code-splitting
import { sub } from './math';
console.log(add(23, 14));
// After code-splitting
import("./math").then(math => {
console.log(math.sub(23, 14));
});
當使用此語法(在 Webpack 等捆綁包中)時,程式碼分割將自動開始。但是,如果您使用的是 Create React App,則程式碼分割已為您配置,您可以立即開始使用它。
ReactJS - 巢狀元件
React 中的巢狀元件是一個與另一個元件相關的元件。您也可以將其視為父元件內的子元件;但它們不是使用繼承概念而是使用組合概念連結在一起的。因此,所有元件都巢狀在一起以建立更大的元件,而不是較小的元件繼承自父元件。
React 元件是 React 應用程式的構建塊。React 元件由多個單個元件組成。React 允許組合多個元件以建立更大的元件。此外,React 元件可以巢狀到任意級別。
巢狀元件將使您的程式碼更高效和結構化。但是,如果元件沒有正確巢狀或組裝,則您的程式碼可能會變得更復雜,從而導致效率降低。讓我們在本節中瞭解如何正確地組合 React 元件。
FormattedMoney 元件
讓我們建立一個元件 `FormattedMoney`,在渲染之前將金額格式化為兩位小數。
**步驟 1** - 在您喜歡的編輯器中開啟我們的 `expense-manager` 應用程式。
在 `src/components` 資料夾中建立一個名為 `FormattedMoney.js` 的檔案,並匯入 React 庫。
import React from 'react';
**步驟 2** - 然後透過擴充套件 `React.Component` 建立一個類 `FormattedMoney`。
class FormattedMoney extends React.Component {
}
接下來,引入帶有引數 props 的建構函式,如下所示:
constructor(props) {
super(props);
}
建立一個方法 `format()` 來格式化金額。
format(amount) {
return parseFloat(amount).toFixed(2)
}
建立另一個方法 `render()` 來發出格式化的金額。
render() {
return (
<span>{this.format(this.props.value)}</span>
);
}
在這裡,我們透過 `this.props` 傳遞 `value` 屬性來使用 `format` 方法。
**步驟 3** - 接下來,將元件指定為預設匯出類。
export default FormattedMoney;
現在,我們已經成功建立了我們的 `FormattedMoney` React 元件。
import React from 'react';
class FormattedMoney extends React.Component {
constructor(props) {
super(props)
}
format(amount) {
return parseFloat(amount).toFixed(2)
}
render() {
return (
<span>{this.format(this.props.value)}</span>
);
}
}
export default FormattedMoney;
FormattedDate 元件
讓我們建立另一個元件 `FormattedDate` 來格式化並顯示支出的日期和時間。
**步驟 1** - 在您喜歡的編輯器中開啟我們的 `expense-manager` 應用程式。
在 `src/components` 資料夾中建立一個檔案 `FormattedDate.js` 並匯入 `React` 庫。
import React from 'react';
**步驟 2** - 接下來,透過擴充套件 `React.Component` 建立一個類。
class FormattedDate extends React.Component {
}
然後引入帶有引數 props 的建構函式,如下所示:
constructor(props) {
super(props);
}
**步驟 3** - 接下來,建立一個方法 `format()` 來格式化日期。
format(val) {
const months = ["JAN", "FEB", "MAR","APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
let parsed_date = new Date(Date.parse(val));
let formatted_date = parsed_date.getDate() +
"-" + months[parsed_date.getMonth()] +
"-" + parsed_date.getFullYear()
return formatted_date;
}
建立另一個方法 `render()` 來發出格式化的日期。
render() { return ( <span>{this.format(this.props.value)}</span> ); }
在這裡,我們透過 `this.props` 傳遞 `value` 屬性來使用 `format` 方法。
**步驟 4** - 接下來,將元件指定為預設匯出類。
export default FormattedDate;
現在,我們已經成功建立了我們的 FormattedDate React 元件。完整程式碼如下:
import React from 'react';
class FormattedDate extends React.Component {
constructor(props) {
super(props)
}
format(val) {
const months = ["JAN", "FEB", "MAR","APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
let parsed_date = new Date(Date.parse(val));
let formatted_date = parsed_date.getDate() +
"-" + months[parsed_date.getMonth()] +
"-" + parsed_date.getFullYear()
return formatted_date;
}
render() {
return (
<span>{this.format(this.props.value)}</span>
);
}
}
export default FormattedDate;
ReactJS - 使用元件
React 元件表示網頁中一小部分使用者介面。React 元件的主要工作是呈現其使用者介面並在其內部狀態發生變化時更新它。除了呈現 UI 之外,它還管理屬於其使用者介面的事件。總而言之,React 元件提供以下功能:
在 ReactJS 中使用元件
在本節中,讓我們使用新建立的元件並增強我們的 `ExpenseEntryItem` 元件。
**步驟 1** - 在您喜歡的編輯器中開啟我們的 `expense-manager` 應用程式並開啟 `ExpenseEntryItem.js` 檔案。
然後,使用以下語句匯入 `FormattedMoney` 和 `FormattedDate`。
import FormattedMoney from './FormattedMoney' import FormattedDate from './FormattedDate'
**步驟 2** - 接下來,透過包含 `FormattedMoney` 和 `FormattedDater` 元件來更新 `render` 方法。
render() {
return (
<div>
<div><b>Item:</b> <em>{this.props.item.name}</em></div>
<div><b>Amount:</b>
<em>
<FormattedMoney value={this.props.item.amount} />
</em>
</div>
<div><b>Spend Date:</b>
<em>
<FormattedDate value={this.props.item.spendDate} />
</em>
</div>
<div><b>Category:</b>
<em>{this.props.item.category}</em></div>
</div>
);
}
這裡,我們透過元件的 value 屬性傳遞了金額和支出日期。
下面給出 ExpenseEntryItem 元件最終更新後的原始碼:
import React from 'react'
import FormattedMoney from './FormattedMoney'
import FormattedDate from './FormattedDate'
class ExpenseEntryItem extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div><b>Item:</b> <em>{this.props.item.name}</em></div>
<div><b>Amount:</b>
<em>
<FormattedMoney value={this.props.item.amount} />
</em>
</div>
<div><b>Spend Date:</b>
<em>
<FormattedDate value={this.props.item.spendDate} />
</em>
</div>
<div><b>Category:</b>
<em>{this.props.item.category}</em></div>
</div>
);
}
}
export default ExpenseEntryItem;
index.js
開啟 index.js 並呼叫 ExpenseEntryItem 元件,傳入 item 物件。
const item = {
id: 1,
name : "Grape Juice",
amount : 30.5,
spendDate: new Date("2020-10-10"),
category: "Food"
}
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItem item={item} />
</React.StrictMode>,
document.getElementById('root')
);
接下來,使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
ReactJS - 元件集合
在現代應用程式中,開發人員會遇到很多情況,需要以表格格式或相簿格式呈現專案列表(例如待辦事項、訂單、發票等)。React 提供了一種清晰、直觀且易於使用的技術來建立基於列表的使用者介面。React 使用兩個現有功能來實現此功能。
- JavaScript 的內建 map 方法。
- JSX 中的 React 表示式。
Map 方法
map 函式接受一個集合和一個對映函式。map 函式將應用於集合中的每一個專案,並將結果用於生成一個新的列表。
例如,宣告一個包含 5 個隨機數的 JavaScript 陣列,如下所示:
let list = [10, 30, 45, 12, 24]
現在,應用一個匿名函式,該函式將其輸入加倍,如下所示:
result = list.map((input) => input * 2);
然後,生成的列表為:
[20, 60, 90, 24, 48]
為了重新整理 React 表示式,讓我們建立一個新變數並賦值一個 React 元素。
var hello = <h1>Hello!</h1>
var final = <div>{helloElement}</div>
現在,React 表示式 hello 將與 final 合併,生成:
<div><h1>Hello!</h1></div>
示例
讓我們應用這個概念來建立一個元件,以表格格式顯示一系列支出條目。
**步驟 1** - 在您喜歡的編輯器中開啟我們的 `expense-manager` 應用程式。
在 src/components 資料夾中建立一個名為 ExpenseEntryItemList.css 的檔案,用於包含元件的樣式。
在 src/components 資料夾中建立另一個檔案 ExpenseEntryItemList.js 來建立 ExpenseEntryItemList 元件
步驟 2 - 匯入 React 庫和樣式表。
import React from 'react'; import './ExpenseEntryItemList.css';
步驟 3 - 建立 ExpenseEntryItemList 類並呼叫建構函式。
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
}
}
建立一個 render() 函式。
render() {
}
步驟 4 - 使用 map 方法生成一系列 HTML 表格行,每一行代表列表中的單個支出條目。
render() {
const lists = this.props.items.map( (item) =>
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
</tr>
);
}
這裡,key 標識每一行,它在列表中必須是唯一的。
步驟 5 - 接下來,在 render() 方法中,建立一個 HTML 表格,並將 lists 表示式包含在行部分。
return (
<table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{lists}
</tbody>
</table>
);
最後,匯出元件。
export default ExpenseEntryItemList;
現在,我們已經成功建立了將支出項渲染到 HTML 表格中的元件。完整的程式碼如下:
import React from 'react';
import './ExpenseEntryItemList.css'
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
}
render() {
const lists = this.props.items.map( (item) =>
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
</tr>
);
return (
<table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{lists}
</tbody>
</table>
);
}
}
export default ExpenseEntryItemList;
index.js
開啟 index.js 並匯入我們新建立的 ExpenseEntryItemList 元件。
import ExpenseEntryItemList from './components/ExpenseEntryItemList'
接下來,宣告一個列表(支出條目列表),並在 index.js 檔案中用一些隨機值填充它。
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 1, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 1, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 1, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 1, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 1, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 1, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 1, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 1, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 1, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
透過 items 屬性傳入專案來使用 ExpenseEntryItemList 元件。
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItemList items={items} />
</React.StrictMode>,
document.getElementById('root')
);
index.js 的完整程式碼如下:
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList'
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 1, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 1, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 1, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 1, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 1, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 1, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 1, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 1, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 1, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItem item={item} />
</React.StrictMode>,
document.getElementById('root')
);
ExpenseEntryItemList.css
開啟 ExpenseEntryItemList.css 並新增表格樣式。
html {
font-family: sans-serif;
}
table {
border-collapse: collapse;
border: 2px solid rgb(200,200,200);
letter-spacing: 1px;
font-size: 0.8rem;
}
td, th {
border: 1px solid rgb(190,190,190);
padding: 10px 20px;
}
th {
background-color: rgb(235,235,235);
}
td, th {
text-align: left;
}
tr:nth-child(even) td {
background-color: rgb(250,250,250);
}
tr:nth-child(odd) td {
background-color: rgb(245,245,245);
}
caption {
padding: 10px;
}
使用 npm 命令啟動應用程式。
npm start
輸出
最後,開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
| 專案 | 金額 | 日期 | 類別 |
|---|---|---|---|
| 披薩 | 80 | 2020年10月10日(週六) | 食品 |
| 葡萄汁 | 30 | 2020年10月12日(週一) | 食品 |
| 電影院 | 210 | 2020年10月16日(週五) | 娛樂 |
| Java程式設計書籍 | 242 | 2020年10月15日(週四) | 學術 |
| 芒果汁 | 35 | 2020年10月16日(週五) | 食品 |
| 衣服 | 2000 | 2020年10月25日(週日) | 服裝 |
| 旅遊 | 2555 | 2020年10月29日(週四) | 娛樂 |
| 餐飲 | 300 | 2020年10月30日(週五) | 食品 |
| 手機 | 3500 | 2020年11月2日(週一) | 電子產品 |
| 考試費 | 1245 | 2020年11月4日(週三) | 學術 |
ReactJS - 樣式
一般來說,React 允許元件透過 className 屬性使用 CSS 類進行樣式設定。由於 React JSX 支援 JavaScript 表示式,因此可以使用許多常見的 CSS 方法。一些頂級選項如下:
CSS 樣式表 - 常規 CSS 樣式以及 className
內聯樣式 - 作為 JavaScript 物件的 CSS 樣式以及 camelCase 屬性。
CSS 模組 - 區域性作用域的 CSS 樣式。
樣式化元件 - 元件級樣式。
Sass 樣式表 - 透過在構建時將樣式轉換為普通 css 來支援基於 Sass 的 CSS 樣式。
後處理樣式表 - 透過在構建時將樣式轉換為普通 css 來支援後處理樣式。
讓我們學習如何在本章中應用三種重要的樣式設定方法。
CSS 樣式表
內聯樣式
CSS 模組
CSS 樣式表
CSS 樣式表 是一種常用且久經考驗的方法。只需為元件建立一個 CSS 樣式表,並輸入該元件的所有樣式。然後,在元件中使用 className 來引用樣式。
讓我們為 ExpenseEntryItem 元件設定樣式。
在您喜歡的編輯器中開啟 `expense-manager` 應用程式。
接下來,開啟 ExpenseEntryItem.css 檔案並新增一些樣式。
div.itemStyle {
color: brown;
font-size: 14px;
}
接下來,開啟 ExpenseEntryItem.js 並將 className 新增到主容器。
import React from 'react';
import './ExpenseEntryItem.css';
class ExpenseEntryItem extends React.Component {
render() {
return (
<div className="itemStyle">
<div><b>Item:</b> <em>Mango Juice</em></div>
<div><b>Amount:</b> <em>30.00</em></div>
<div><b>Spend Date:</b> <em>2020-10-10</em></div>
<div><b>Category:</b> <em>Food</em></div>
</div>
);
}
}
export default ExpenseEntryItem;
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
CSS 樣式表易於理解和使用。但是,當專案規模增大時,CSS 樣式也會增多,最終會導致類名衝突。此外,直接載入 CSS 檔案僅在 Webpack 捆綁器中受支援,在其他工具中可能不受支援。
內聯樣式
內聯樣式 是為 React 元件設定樣式的最安全方法之一。它使用基於 DOM 的 css 屬性將所有樣式宣告為 JavaScript 物件,並透過 style 屬性將其設定為元件。
讓我們在元件中新增內聯樣式。
在您喜歡的編輯器中開啟 expense-manager 應用程式,並修改 src 資料夾中的 ExpenseEntryItem.js 檔案。宣告一個物件型別的變數並設定樣式。
itemStyle = {
color: 'brown',
fontSize: '14px'
}
這裡,fontSize 代表 css 屬性 font-size。所有 css 屬性都可以使用 camelCase 格式表示。
接下來,使用花括號 {} 在元件中設定 itemStyle 樣式:
render() {
return (
<div style={ this.itemStyle }>
<div><b>Item:</b> <em>Mango Juice</em></div>
<div><b>Amount:</b> <em>30.00</em></div>
<div><b>Spend Date:</b> <em>2020-10-10</em></div>
<div><b>Category:</b> <em>Food</em></div>
</div>
);
}
此外,樣式可以直接在元件內設定:
render() {
return (
<div style={
{
color: 'brown',
fontSize: '14px'
}
}>
<div><b>Item:</b> <em>Mango Juice</em></div>
<div><b>Amount:</b> <em>30.00</em></div>
<div><b>Spend Date:</b> <em>2020-10-10</em></div>
<div><b>Category:</b> <em>Food</em></div>
</div>
);
}
現在,我們已經成功地在應用程式中使用了內聯樣式。
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
CSS 模組
Css 模組 提供了定義樣式的最安全和最簡單的方法。它使用具有普通語法的普通 css 樣式表。在匯入樣式時,CSS 模組會將所有樣式轉換為區域性作用域樣式,因此不會發生名稱衝突。讓我們將元件更改為使用 CSS 模組
在您喜歡的編輯器中開啟 expense-manager 應用程式。
接下來,在 src/components 資料夾下建立一個新的樣式表 ExpenseEntryItem.module.css 檔案,並編寫常規 css 樣式。
div.itemStyle {
color: 'brown';
font-size: 14px;
}
這裡,檔案命名約定非常重要。React 工具鏈將透過 CSS 模組 預處理以 .module.css 結尾的 css 檔案。否則,它將被視為普通樣式表。
接下來,開啟 src/component 資料夾中的 ExpenseEntryItem.js 檔案並匯入樣式。
import styles from './ExpenseEntryItem.module.css'
接下來,在元件中將樣式用作 JavaScript 表示式。
<div className={styles.itemStyle}>
現在,我們已經成功地在應用程式中使用了 CSS 模組。
最終完整的程式碼是:
import React from 'react';
import './ExpenseEntryItem.css';
import styles from './ExpenseEntryItem.module.css'
class ExpenseEntryItem extends React.Component {
render() {
return (
<div className={styles.itemStyle} >
<div><b>Item:</b> <em>Mango Juice</em></div>
<div><b>Amount:</b> <em>30.00</em></div>
<div><b>Spend Date:</b> <em>2020-10-10</em></div>
<div><b>Category:</b> <em>Food</em></div>
</div>
);
}
}
export default ExpenseEntryItem;
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
ReactJS - 屬性 (props)
React 使開發人員能夠使用屬性建立動態和高階元件。每個元件都可以具有類似於 HTML 屬性的屬性,並且每個屬性的值都可以透過屬性 (props) 在元件內部訪問。
例如,具有 name 屬性的 Hello 元件可以透過 this.props.name 變數在元件內部訪問。
<Hello name="React" /> // value of name will be "Hello* const name = this.props.name
React 屬性支援不同型別的屬性值。它們如下:
- 字串
- 數字
- 日期時間
- 陣列
- 列表
- 物件
使用 Props
當我們需要在元件中使用不可變資料時,我們只需將 props 新增到 main.js 中的 reactDOM.render() 函式,並在元件內部使用它。
App.jsx
import React from 'react';
class App extends React.Component {
render() {
return (
<div>
<h1>{this.props.headerProp}</h1>
<h2>{this.props.contentProp}</h2>
</div>
);
}
}
export default App;
main.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(<App headerProp = "Header from props..." contentProp = "Content
from props..."/>, document.getElementById('app'));
export default App;
這將產生以下結果。
預設 Props
您還可以直接在元件建構函式上設定預設屬性值,而不是將其新增到 reactDom.render() 元素。
App.jsx
import React from 'react';
class App extends React.Component {
render() {
return (
<div>
<h1>{this.props.headerProp}</h1>
<h2>{this.props.contentProp}</h2>
</div>
);
}
}
App.defaultProps = {
headerProp: "Header from props...",
contentProp:"Content from props..."
}
export default App;
main.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(<App/>, document.getElementById('app'));
輸出與之前相同。
狀態與 Props
下面的示例演示瞭如何在應用程式中組合 狀態和 props。我們在父元件中設定狀態,並使用 props 將其傳遞到元件樹中。在 render 函式內部,我們設定了子元件中使用的 headerProp 和 contentProp。
App.jsx
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
header: "Header from props...",
content: "Content from props..."
}
}
render() {
return (
<div>
<Header headerProp = {this.state.header}/>
<Content contentProp = {this.state.content}/>
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>{this.props.headerProp}</h1>
</div>
);
}
}
class Content extends React.Component {
render() {
return (
<div>
<h2>{this.props.contentProp}</h2>
</div>
);
}
}
export default App;
main.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(<App/>, document.getElementById('app'));
結果將再次與之前的兩個示例相同,唯一不同的是我們的資料來源,它現在最初來自 狀態。當我們想要更新它時,我們只需要更新狀態,所有子元件都將被更新。更多內容請參閱事件章節。
讓我們在本節中逐一學習以下概念。
ReactJS - 使用屬性建立元件
正如我們之前在本教程中學到的那樣,React 是一個非常靈活的庫,規則可以彎曲,但它嚴格遵循一條規則:如果元件定義為函式或類,它必須相對於其屬性像純函式一樣工作。React 中的純函式定義為其輸入不能更改的函式,因此它不會改變其結果。
簡而言之,傳遞給元件的 Props 是隻讀的。但是由於應用程式 UI 是動態的並隨時間改變其輸入,我們使用“狀態”概念來處理它。
狀態的概念允許 React 元件響應使用者操作、網路響應等更改其結果,而不會違反此規則。
如何使用屬性建立元件?
在本節中,讓我們看看使用屬性建立元件的步驟:
我們首先修改 ExpenseEntryItem 元件並嘗試使用屬性。
**步驟 1** - 在您喜歡的編輯器中開啟我們的 `expense-manager` 應用程式。
開啟 src/components 資料夾中的 ExpenseEntryItem 檔案。
步驟 2 - 使用引數 props 引入建構函式。
constructor(props) {
super(props);
}
接下來,更改 render 方法並從 props 中填充值。
render() {
return (
<div>
<div><b>Item:</b> <em>{this.props.name}</em></div>
<div><b>Amount:</b> <em>{this.props.amount}</em></div>
<div><b>Spend date:</b>
<em>{this.props.spenddate.tostring()}</em></div>
<div><b>Category:</b> <em>{this.props.category}</em></div>
</div>
);
}
這裡:
name 代表專案的名稱(字串型別)
amount 代表專案的金額(數字型別)
spendDate 代表專案的支出日期(日期型別)
category 代表專案的類別(字串型別)
現在,我們已經成功地使用屬性更新了元件。
import React from 'react'
import './ExpenseEntryItem.css';
import styles from './ExpenseEntryItem.module.css'
class ExpenseEntryItem extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div><b>Item:</b> <em>{this.props.name}</em></div>
<div><b>Amount:</b> <em>{this.props.amount}</em></div>
<div><b>Spend Date:</b>
<em>{this.props.spendDate.toString()}</em></div>
<div><b>Category:</b> <em>{this.props.category}</em></div>
</div>
);
}
}
export default ExpenseEntryItem;
index.js
現在,我們可以透過在 index.js 中的屬性中傳遞所有屬性來使用該元件。
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItem from './components/ExpenseEntryItem'
const name = "Grape Juice"
const amount = 30.00
const spendDate = new Date("2020-10-10")
const category = "Food"
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItem
name={name}
amount={amount}
spendDate={spendDate}
category={category} />
</React.StrictMode>,
document.getElementById('root')
);
使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
在網頁中使用 CDN 執行此操作的完整程式碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React based application</title>
</head>
<body>
<div id="react-app"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class ExpenseEntryItem extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div><b>Item:</b> <em>{this.props.name}</em></div>
<div><b>Amount:</b> <em>{this.props.amount}</em></div>
<div><b>Spend Date:</b> <em>{this.props.spendDate.toString()}</em></div>
<div><b>Category:</b> <em>{this.props.category}</em></div>
</div>
);
}
}
const name = "Grape Juice"
const amount = 30.00
const spendDate = new Date("2020-10-10")
const category = "Food"
ReactDOM.render(
<ExpenseEntryItem
name={name}
amount={amount}
spendDate={spendDate}
category={category} />,
document.getElementById('react-app') );
</script>
</body>
</html>
物件作為屬性
讓我們在本節中學習如何在本章中使用 JavaScript 物件作為屬性。
**步驟 1** - 在您喜歡的編輯器中開啟我們的 `expense-manager` 應用程式。
開啟 ExpenseEntryItem.js 檔案。
步驟 2 - 接下來,更改 render() 方法並透過 this.props.item 屬性訪問輸入物件 item。
render() {
return (
<div>
<div><b>Item:</b> <em>{this.props.item.name}</em></div>
<div><b>Amount:</b> <em>{this.props.item.amount}</em></div>
<div><b>Spend Date:</b>
<em>{this.props.item.spendDate.toString()}</em></div>
<div><b>Category:</b> <em>{this.props.item.category}</em></div>
</div>
);
}
開啟 index.js 並用 JavaScript 物件表示支出條目。
const item = {
id: 1,
name : "Grape Juice",
amount : 30.5,
spendDate: new Date("2020-10-10"),
category: "Food"
}
使用花括號 ({}) 語法在元件屬性中將物件傳遞給元件。
<ExpenseEntryItem item={item} />
index.js
index.js 的完整程式碼如下:
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItem from './components/ExpenseEntryItem'
const item = {
id: 1,
name : "Grape Juice",
amount : 30.5,
spendDate: new Date("2020-10-10"),
category: "Food"
}
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItem item={item} />
</React.StrictMode>,
document.getElementById('root')
);
使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
在網頁中使用 CDN 執行此操作的完整程式碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React based application</title>
</head>
<body>
<div id="react-app"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class ExpenseEntryItem extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div><b>Item:</b>
<em>{this.props.item.name}</em></div>
<div><b>Amount:</b>
<em>{this.props.item.amount}</em></div>
<div><b>Spend Date:</b>
<em>{this.props.item.spendDate.toString()}</em>
</div>
<div><b>Category:</b>
<em>{this.props.item.category}</em>
</div>
</div>
);
}
}
const item = {
id: 1,
name : "Grape Juice",
amount : 30.5,
spendDate: new Date("2020-10-10"),
category: "Food"
}
ReactDOM.render(
<ExpenseEntryItem item={item} />,
document.getElementById('react-app')
);
</script>
</body>
</html>
ReactJS - props 驗證
程式中一個費時的過程是查詢錯誤的根本原因。在 React 中,props 被廣泛使用。元件的 Props 將具有不同的來源。一些元件將具有靜態 props,而一些元件將具有來自直接父元件的動態 props。錯誤的來源之一是 props 的值與開發人員設計的 props 型別不匹配。這種不匹配會產生很多錯誤。React 提供了許多選項來解決這個問題,其中一項功能是 PropTypes 及其驗證。
我們將在本章學習什麼是 PropTypes 以及如何使用它來建立無錯誤的 React 應用程式。
PropTypes
React 社群提供了一個特殊的包 prop-types 來解決屬性型別不匹配問題。prop-types 允許透過元件內的自定義設定 (propTypes) 指定元件屬性的型別。例如,可以使用 PropTypes.number 選項指定數字型別的屬性,如下所示。
Sum.propTypes = {
num1: PropTypes.number,
num2: PropTypes.number
}
一旦指定了屬性的型別,React 就會在應用程式的開發階段發出警告。
讓我們在我們的應用程式中包含 propTypes,看看它如何幫助捕獲屬性型別不匹配問題。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用節點包管理器 (npm) 安裝 prop-types 包,如下所示:
npm i prop-types --save
接下來,開啟 App.css (src/App.css) 並刪除所有 CSS 類。然後,建立一個簡單的元件 Sum (src/Components/Sum.js),如下所示:
import React from 'react'
import PropTypes from 'prop-types'
class Sum extends React.Component {
render() {
return <p>The sum of {this.props.num1} and {this.props.num2}
is {parseInt(this.props.num1) + parseInt(this.props.num2)}</p>
}
}
Sum.propTypes = {
num1: PropTypes.number,
num2: PropTypes.number
}
export default Sum
這裡:
該元件的目的是找到給定 props(num1 和 num2)的總和值,並在前端顯示。
使用propTypes 將num1 和num2 的資料型別設定為數字 (PropTypes.number)。
接下來,開啟App 元件 (src/App.js),匯入 bootstrap css 並呈現日期選擇器,如下所示:
import './App.css'
import Sum from './Components/Sum'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Sum num1="10" num2="John" />
</div>
</div>
</div>
);
}
export default App;
在這裡,我們使用 10 和 John 作為 props 呈現了 Sum 元件
最後,在您喜歡的瀏覽器中開啟應用程式,並透過開發者工具開啟 JavaScript 控制檯。JavaScript 會發出警告,提示提供了意外的型別,如下所示。
propTypes 僅在開發階段有效,以消除由於額外檢查 props 型別而導致的應用程式效能降低。這不會影響生產/上線環境中的應用程式效能。
可用驗證器
prop-types 提供了大量現成的驗證器。它們如下所示:
PropTypes.array
PropTypes.bigint
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.symbol
PropTypes.node - 可渲染的任何內容
PropTypes.element - React 元件
PropTypes.elementType - React 元件的型別
PropTypes.instanceOf() - 指定類的例項
propTypes.oneOf(['Value1', 'valueN']) - Value 和 ValueN 之一
PropTypes.oneOfType([]) - 例如,PropTypes.oneOfType([PropTypes.number, PropTypes.bigint])
PropTypes.arrayOf() - 例如,PropTypes.arrayOf(PropTypes.number)
PropTypes.objectOf() - 例如,PropTypes.objectOf(PropTypes.number)
PropTypes.func.isRequired
propTypes.element.isRequired
PropTypes.any.isRequired
自定義驗證器
還可以建立自定義驗證器並用於驗證屬性的值。讓我們假設元件具有一個 email 屬性,其值應為有效的電子郵件地址。然後,可以編寫一個驗證函式並將其附加到 email 屬性,如下所示:
Sum.propTypes = {
num1: PropTypes.number,
num2: PropTypes.number,
email: function(myProps, myPropName, myComponentName) {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(myProps[myPropName])) {
return new Error(
'Invalid prop value `' + myProps[myPropName] + '` supplied to' +
' `' + myComponentName + '/' + myPropName + '`. Validation failed.'
);
}
}
}
這裡:
/^[^\s@]+@[^\s@]+\.[^\s@]+$/ 是一個簡單的正則表示式電子郵件模式。
myProps 代表所有屬性。
myPropName 代表正在驗證的當前屬性。
myComponentName 代表正在驗證的元件的名稱。
同樣,可以使用以下函式簽名建立和使用自定義驗證器來驗證陣列和物件屬性:
PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) { ... })
這裡:
propValue 代表陣列/物件值。
key 代表當前專案的鍵。
componentName 代表元件的名稱。
propFullName 代表正在驗證的屬性的名稱。
總結
Props 型別是開發人員編寫無錯誤軟體的好工具之一。它肯定會幫助開發人員更快、更安全地編寫程式碼。
ReactJS - 建構函式
通常,類中的構造器方法用於設定新建立物件的初始值。React 也使用 constructor() 來實現相同的初始化目的。然而在 React 中,構造器也用於狀態初始化和事件繫結。
讓我們在本節學習如何在 React 元件中使用構造器。
Props 的初始化
眾所周知,每個 React 元件都有 props 和 state。應該使用 super 關鍵字在構造器中初始化 props。如果在基於類的 React 元件中沒有正確初始化 props,則 this.props 將無法正常工作並導致錯誤。讓我們建立一個具有正確構造器方法的簡單元件。
class Welcome extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div><h3>Welcome {this.props.name}</h3></div>
)
}
}
這裡:
super(props) 將在 Welcome 元件中初始化 props。
this.props.* 將提供對 props 細節的訪問。
元件可以使用如下所示:
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Welcome name={'John'} />
</div>
</div>
</div>
);
}
元件將呈現如下所示的歡迎訊息:
狀態的初始化
與 props 初始化類似,狀態初始化非常重要,可以在構造器中完成。通常,React 提供不同的方法來設定和獲取元件中的狀態資訊。它們如下所示:
使用 this.state = obj
這用於使用物件初始化狀態
this.state = {
pageSize: 10
}
使用 this.state.*
這用於訪問狀態資訊。(this.state.pageSize)
使用 this.setState()
這是一個接受物件或 lambda 函式的函式。用於設定狀態資訊
this.setState({
pageSize: 20
})
this.setState((state, props) => ({
pageSize: state.pageSize + 1
}))
讓我們建立一個具有正確狀態初始化的簡單元件
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {
welcomeMessage: "Hello"
}
}
render() {
return (
<div><h3>{this.state.welcomeMessage}, {this.props.name}</h3></div>
)
}
}
這裡,this.state 用於設定歡迎訊息的預設(初始)值。元件可以使用如下所示:
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Welcome name={'John'} />
</div>
</div>
</div>
);
}
元件將呈現如下所示的歡迎訊息:
事件繫結
與 props 和 state 初始化類似,事件處理程式必須正確繫結,以便 this 將在事件處理程式中被正確訪問。讓我們在 Welcome 元件中建立一個新的按鈕來更改歡迎訊息,並新增一個事件處理程式來處理按鈕的 onClick 事件,如下所示:
import React from "react";
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {
welcomeMessage: "Hello"
}
this.changeMessageHandler = this.changeMessageHandler.bind(this)
}
changeMessageHandler() {
this.setState(prevState => ({
welcomeMessage: prevState.welcomeMessage == "Hello" ? "Welcome" : "Hello"
}));
}
render() {
return (
<div>
<div><h3>{this.state.welcomeMessage}, {this.props.name}</h3></div>
<div><button onClick={this.changeMessageHandler}>Change welcome message</button></div>
</div>
)
}
}
export default Welcome;
這裡:
步驟1 - 新增一個帶有 onClick 事件的按鈕
<div><button onClick={this.changeMessageHandler}>Change welcome message</button></div>
步驟2 - 將 this.changeMessageHandler 方法設定為 onClick 事件處理程式
步驟3 - 在構造器中繫結事件處理程式 this.changeMessageHandler
this.changeMessageHandler = this.changeMessageHandler.bind(this)
步驟4 - 添加了事件處理程式並使用 this.setState 更新了狀態。
changeMessageHandler() {
this.setState(prevState => ({
welcomeMessage: prevState.welcomeMessage == "Hello" ? "Welcome" : "Hello"
}));
}
元件可以使用如下所示:
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Welcome name={'John'} />
</div>
</div>
</div>
);
}
元件將呈現如下所示的歡迎訊息:
總結
構造器在基於類的 React 元件中非常重要。它的主要工作是設定元件,以便 props、state 和事件被正確配置並準備好被元件事件和渲染方法訪問。
ReactJS - 元件生命週期
在 React 中,元件的生命週期代表元件在其存在期間的不同階段。React 提供回撥函式來在 React 生命週期的每個階段附加功能。讓我們在本節學習 React 元件的生命週期(以及相關的 API)。
生命週期 API
每個 React 元件都有三個不同的階段。
掛載 - 掛載代表在給定的 DOM 節點中渲染 React 元件。
更新 - 更新代表在狀態更改/更新期間在給定的 DOM 節點中重新渲染 React 元件。
解除安裝 - 解除安裝代表刪除 React 元件。
React 提供了一組生命週期事件(或回撥 API)來附加功能,這些功能將在元件的各個階段執行。生命週期的視覺化以及生命週期事件 (API) 的呼叫順序如下所示。
constructor() - 在 React 元件的初始構造階段呼叫。用於設定元件的初始狀態和屬性。
render() - 在元件構造完成後呼叫。它在虛擬 DOM 例項中渲染元件。這被指定為元件在 DOM 樹中的掛載。
componentDidMount() - 在元件在 DOM 樹中初始掛載後呼叫。這是呼叫 API 端點和執行網路請求的好地方。在我們的時鐘元件中,可以在這裡設定 setInterval 函式以每秒更新狀態(當前日期和時間)。
componentDidMount() {
this.timeFn = setInterval( () => this.setTime(), 1000);
}
componentDidUpdate() - 類似於 ComponentDidMount(),但在更新階段呼叫。在此階段可以執行網路請求,但前提是元件的當前屬性和先前屬性之間存在差異。
API 的簽名如下:
componentDidUpdate(prevProps, prevState, snapshot)
prevProps - 元件的先前屬性。
prevState - 元件的先前狀態。
snapshot - 當前渲染的內容。
componentWillUnmount() - 在元件從 DOM 中解除安裝後呼叫。這是清理物件的好地方。在我們的時鐘示例中,我們可以在此階段停止更新日期和時間。
componentDidMount() {
this.timeFn = setInterval( () => this.setTime(), 1000);
}
shouldComponentUpdate() - 在更新階段呼叫。用於指定元件是否應該更新。如果它返回 false,則不會發生更新。
簽名如下:
shouldComponentUpdate(nextProps, nextState)
nextProps - 元件即將到來的屬性
nextState - 元件即將到來的狀態
getDerivedStateFromProps - 在初始和更新階段以及render() 方法之前呼叫。它返回新的狀態物件。它很少用於屬性更改導致狀態更改的情況。它主要用於動畫環境,其中需要元件的各種狀態來進行流暢的動畫。
API 的簽名如下:
static getDerivedStateFromProps(props, state)
props - 元件的當前屬性
state - 元件的當前狀態
這是一個靜態方法,無法訪問this 物件。
getSnapshotBeforeUpdate - 在渲染的內容提交到 DOM 樹之前呼叫。它主要用於獲取有關新內容的一些資訊。此方法返回的資料將傳遞給ComponentDidUpdate() 方法。例如,它用於在新生成的內容中維護使用者的滾動位置。它返回使用者的滾動位置。componentDidUpdate() 使用此滾動位置來設定實際 DOM 中輸出的滾動位置。
API 的簽名如下:
getSnapshotBeforeUpdate(prevProps, prevState)
prevProps - 元件的先前屬性。
prevState - 元件的先前狀態。
生命週期 API 的工作示例
讓我們在react-clock-app 應用程式中使用生命週期 api。
步驟1 - 在您喜歡的編輯器中開啟react-clock-hook-app。
開啟src/components/Clock.js 檔案並開始編輯。
步驟2 - 從構造器中刪除setInterval() 方法。
constructor(props) {
super(props);
this.state = {
date: new Date()
}
}
步驟3 - 新增componentDidMount() 方法並呼叫setInterval() 以每秒更新日期和時間。此外,儲存引用以稍後停止更新日期和時間。
componentDidMount() {
this.setTimeRef = setInterval(() => this.setTime(), 1000);
}
新增componentWillUnmount() 方法並呼叫 clearInterval() 以停止日期和時間的更新呼叫。
componentWillUnmount() {
clearInterval(this.setTimeRef)
}
現在,我們已經更新了 Clock 元件,下面給出了元件的完整原始碼:
import React from 'react';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
date: new Date()
}
}
componentDidMount() {
this.setTimeRef = setInterval(() => this.setTime(), 1000);
}
componentWillUnmount() {
clearInterval(this.setTimeRef)
}
setTime() {
this.setState((state, props) => {
console.log(state.date);
return {
date: new Date()
}
})
}
render() {
return (
<div>
<p>The current time is {this.state.date.toString()}</p>
</div>
);
}
}
export default Clock;
接下來,開啟 index.js 並使用setTimeout 在 5 秒後從 DOM 中刪除時鐘。
import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';
ReactDOM.render(
<React.StrictMode>
<Clock />
</React.StrictMode>,
document.getElementById('root')
);
setTimeout(() => {
ReactDOM.render(
<React.StrictMode>
<div><p>Clock is removed from the DOM.</p></div>
</React.StrictMode>,
document.getElementById('root')
);
}, 5000);
使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
時鐘會顯示 5 秒,然後從 DOM 中移除。透過檢查控制檯日誌,我們可以發現清理程式碼已正確執行。
支出管理器應用程式中的生命週期 API
讓我們在支出管理器中新增生命週期 API,並在每次呼叫 API 時記錄它。這將提供元件生命週期的洞察。
步驟 1 − 在您喜歡的編輯器中開啟expense-manager應用程式。
接下來,使用以下方法更新 ExpenseEntryItemList 元件。
componentDidMount() {
console.log("ExpenseEntryItemList :: Initialize :: componentDidMount :: Component mounted");
}
shouldComponentUpdate(nextProps, nextState) {
console.log("ExpenseEntryItemList :: Update :: shouldComponentUpdate invoked :: Before update");
return true;
}
static getDerivedStateFromProps(props, state) {
console.log("ExpenseEntryItemList :: Initialize / Update :: getDerivedStateFromProps :: Before update");
return null;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("ExpenseEntryItemList :: Update :: getSnapshotBeforeUpdate :: Before update");
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("ExpenseEntryItemList :: Update :: componentDidUpdate :: Component updated");
}
componentWillUnmount() {
console.log("ExpenseEntryItemList :: Remove :: componentWillUnmount :: Component unmounted");
}
步驟 2 − 使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
接下來,檢查控制檯日誌。它將顯示初始化階段的生命週期 API,如下所示。
ExpenseEntryItemList :: Initialize / Update :: getDerivedStateFromProps :: Before update ExpenseEntryItemList :: Initialize :: componentDidMount :: Component mounted
移除一個專案,然後檢查控制檯日誌。它將顯示更新階段的生命週期 API,如下所示。
ExpenseEntryItemList :: Initialize / Update :: getDerivedStateFromProps :: Before update ExpenseEntryItemList.js:109 ExpenseEntryItemList :: Update :: shouldComponentUpdate invoked :: Before update ExpenseEntryItemList.js:121 ExpenseEntryItemList :: Update :: getSnapshotBeforeUpdate :: Before update ExpenseEntryItemList.js:127 ExpenseEntryItemList :: Update :: componentDidUpdate :: Component updated
最後,移除所有生命週期 API,因為它可能會影響應用程式效能。只有在需要的情況下才應使用生命週期 API。
ReactJS - 事件管理
事件只是使用者與任何應用程式互動時執行的一些操作。它們可能是最小的操作,例如將滑鼠指標懸停在觸發下拉選單的元素上、調整應用程式視窗大小或拖放元素以上傳它們等。React 中的事件分為三類
滑鼠事件 − onClick、onDrag、onDoubleClick
鍵盤事件 − onKeyDown、onKeyPress、onKeyUp
焦點事件 − onFocus、onBlur
對於這些事件中的每一個,JavaScript 都提供響應。因此,每次使用者執行事件時,通常都需要應用程式做出某種反應;這些反應被定義為某些函式或程式碼塊,稱為事件處理程式。使用事件處理程式處理事件的整個過程稱為事件管理。
ReactJS 中的事件管理
事件管理是 Web 應用程式的重要功能之一。它使使用者能夠與應用程式互動。React 支援 Web 應用程式中可用的所有事件。React 事件處理與 DOM 事件非常相似,只有細微的差別。以下是人們在基於 React 的網站上可以觀察到的一些常見事件:
單擊元件。
滾動當前頁面。
將滑鼠懸停在當前頁面的元素上。
提交表單。
重定向到另一個網頁。
載入影像。
合成 React 事件
在 JavaScript 中,當指定事件時,您將處理稱為合成事件的 React 事件型別,而不是常規 DOM 事件。SyntheticEvent 是原生事件例項的簡單跨瀏覽器包裝器,使事件在所有瀏覽器上的工作方式相同。所有事件處理程式都必須作為此包裝器的例項傳遞。但是,就 CPU 資源而言,它代價很高,因為每個建立的合成事件都需要進行垃圾回收。每個合成事件物件都具有以下屬性
布林值 bubbles
布林值 cancelable
DOMEventTarget currentTarget
布林值 defaultPrevented
數字 eventPhase
布林值 isTrusted
DOMEvent nativeEvent
void preventDefault()
布林值 isDefaultPrevented()
void stopPropagation()
布林值 isPropagationStopped()
void persist()
DOMEventTarget target
數字 timeStamp
字串 type
由於合成事件使用了大量資源,因此它們通常會被重複使用,並且在呼叫事件回撥後其所有屬性都將被清除,以最佳化其在瀏覽器中的效能。SyntheticEvent 具有與原生事件相同的介面。並且由於合成事件是由文件節點授權的,因此原生事件會先觸發,然後是合成事件。
新增事件
正如我們已經看到的,React 與 HTML 具有相同的事件:click、change、mouseover 等。但是,React 事件是用 camelCase 定義的,反應寫在花括號內。新增事件的語法在函式元件和類元件中有所不同。
以下是向 React 函式元件新增 onClick 事件的語法
onClick = {action to be performed}
以下是向 React 類元件新增 onClick 事件的語法
onClick = {this.action_to_be_performed}
處理事件
現在讓我們學習如何透過以下分步過程在 React 應用程式中處理這些事件。
定義一個事件處理程式方法來處理給定的事件。
log() {
console.log("Event is fired");
}
React 提供了一種使用 lambda 函式定義事件處理程式的替代語法。lambda 語法為:
log = () => {
console.log("Event is fired");
}
向事件處理程式傳遞引數
有兩種方法可以向事件處理程式傳遞引數
箭頭方法
繫結方法
箭頭方法
如果要了解事件的目標,則在處理程式方法中新增引數e。React 會將事件目標詳細資訊傳送到處理程式方法。
log(e) {
console.log("Event is fired");
console.log(e.target);
}
替代 lambda 語法為:
log = (e) => {
console.log("Event is fired");
console.log(e.target);
}
如果要在事件期間傳送額外的詳細資訊,則將額外的詳細資訊作為初始引數新增,然後為事件目標新增引數(e)。
log(extra, e) {
console.log("Event is fired");
console.log(e.target);
console.log(extra);
console.log(this);
}
替代 lambda 語法如下:
log = (extra, e) => {
console.log("Event is fired");
console.log(e.target);
console.log(extra);
console.log(this);
}
繫結方法
我們還可以在元件的建構函式中繫結事件處理程式方法。這將確保在事件處理程式方法中this的可用性。
constructor(props) {
super(props);
this.logContent = this.logContent.bind(this);
}
如果事件處理程式是在替代 lambda 語法中定義的,則不需要繫結。this關鍵字將自動繫結到事件處理程式方法。
為特定事件設定事件處理程式方法,如下所示:
<div onClick={this.log}> ... </div>
要設定額外的引數,請繫結事件處理程式方法,然後將額外資訊作為第二個引數傳遞。
<div onClick={this.log.bind(this, extra)}> ... </div>
替代 lambda 語法如下:
<div onClick={this.log(extra, e)}> ... </div>
這裡:
ReactJS - 建立事件感知元件
事件管理是 Web 應用程式的重要功能之一。它使使用者能夠與應用程式互動。React 支援 Web 應用程式中可用的所有事件。React 事件處理與 DOM 事件非常相似,只有細微的差別。例如,單擊元件是人們在基於 React 的網站上可以觀察到的一些常見事件。
React 中的事件感知元件只不過是一個在其內部包含事件處理程式方法的元件。該元件可以是類元件或函式元件。在本章中,我們將學習如何使用 React 建立此類事件感知元件。
如何建立事件感知元件?
以下是建立新的事件感知元件的步驟:
讓我們建立一個新的元件,MessageWithEvent,並在元件中處理事件,以便更好地理解 React 應用程式中的事件管理。
步驟 1 − 在您喜歡的編輯器中開啟expense-manager應用程式。
接下來,在src/components資料夾中建立一個檔案MessageWithEvent.js來建立MessageWithEvent元件。
匯入React庫。
import React from 'react';
步驟 2 − 建立一個類MessageWithEvent並使用 props 呼叫建構函式。
class MessageWithEvent extends React.Component {
constructor(props) {
super(props);
}
}
步驟 3 − 建立一個事件處理程式方法logEventToConsole,它將事件詳細資訊記錄到控制檯。
logEventToConsole(e) {
console.log(e.target.innerHTML);
}
步驟 4 − 建立一個render函式。
render() {
}
在 render() 函式中,建立一個問候訊息並返回它。
render() {
return (
<div>
<p>Hello {this.props.name}!</p>
</div>
);
}
步驟 5 − 然後,將logEventToConsole方法設定為根容器(div)單擊事件的事件處理程式。
render() {
return (
<div onClick={this.logEventToConsole}>
<p>Hello {this.props.name}!</p>
</div>
);
}
步驟 6 − 透過繫結事件處理程式來更新建構函式。
class MessageWithEvent extends React.Component {
constructor(props) {
super(props);
this.logEventToConsole = this.logEventToConsole.bind();
}
}
最後,匯出元件。
export default MessageWithEvent;
MessageWithEvent 元件的完整程式碼如下:
import React from 'react';
class MessageWithEvent extends React.Component {
constructor(props) {
super(props);
this.logEventToConsole = this.logEventToConsole.bind();
}
logEventToConsole(e) {
console.log(e.target.innerHTML);
}
render() {
return (
<div onClick={this.logEventToConsole}>
<p>Hello {this.props.name}!</p>
</div>
);
}
}
export default MessageWithEvent;
index.js
接下來,開啟 index.js 並匯入MessageWithEvent。
import MessageWithEvent from './components/MessageWithEvent'
使用MessageWithEvent元件構建應用程式的使用者介面。
import React from 'react';
import ReactDOM from 'react-dom';
import MessageWithEvent from './components/MessageWithEvent'
ReactDOM.render(
<React.StrictMode>
<div>
<MessageWithEvent name="React" />
<MessageWithEvent name="React developer" />
</div>
</React.StrictMode>,
document.getElementById('root')
);
使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
現在,單擊MessageWithEvent元件,應用程式將在控制檯中發出訊息,如下所示。
向事件處理程式傳遞額外資訊
讓我們嘗試向事件處理程式傳遞額外資訊(例如,msgid)。
步驟 1 − 首先,更新logEventToConsole以接受額外引數msgid。
logEventToConsole(msgid, e) {
console.log(e.target.innerHTML);
console.log(msgid);
}
步驟 2 − 接下來,透過在render方法中繫結訊息 ID 來將訊息 ID 傳遞給事件處理程式。
render() {
return (
<div onClick={this.logEventToConsole.bind(this, Math.floor(Math.random() * 10))}>
<p>Hello {this.props.name}!</p>
</div>
);
}
步驟 3 − 完整更新後的程式碼如下:
import React from 'react';
class MessageWithEvent extends React.Component {
constructor(props) {
super(props);
this.logEventToConsole = this.logEventToConsole.bind();
}
logEventToConsole(msgid, e) {
console.log(e.target.innerHTML);
console.log(msgid);
}
render() {
return (
>div onClick={this.logEventToConsole.bind(this, Math.floor(Math.random() * 10))}>
>p>Hello {this.props.name}!>/p>
>/div>
);
}
}
export default MessageWithEvent;
執行應用程式,您會發現事件在控制檯中發出訊息 ID。
在支出管理器應用程式中引入事件
在前面的章節中,我們瞭解到事件只是使用者與任何應用程式互動時執行的一些操作。它們可能是最小的操作,例如將滑鼠指標懸停在觸發下拉選單的元素上、調整應用程式視窗大小或拖放元素以上傳它們等。
對於這些事件中的每一個,JavaScript 都提供響應。因此,每次使用者執行事件時,通常都需要應用程式做出某種反應;這些反應被定義為某些函式或程式碼塊,稱為事件處理程式。
為了更好地理解事件處理,讓我們嘗試將事件引入示例應用程式(支出管理器應用程式)。在本章中,我們嘗試在支出管理器應用程式中處理滑鼠事件。滑鼠事件只是使用者使用滑鼠執行的一些操作。這些包括懸停、單擊、拖動或任何可以使用滑鼠在應用程式上執行的操作。
在支出管理器應用程式中處理事件
讓我們在支出應用程式中進行一些事件管理。當用戶將游標懸停在表格上時,我們可以嘗試突出顯示錶格中的支出條目。
步驟 1 − 在您喜歡的編輯器中開啟expense-manager應用程式。
開啟ExpenseEntryItemList.js檔案,並新增一個方法handleMouseEnter來處理當用戶將滑鼠指標移入支出專案(td - 表格單元格)時觸發的事件(onMouseEnter)。
handleMouseEnter(e) {
e.target.parentNode.classList.add("highlight");
}
這裡:
事件處理程式嘗試使用parentNode方法查詢事件目標(td)節點的父節點(tr)。parentNode方法是查詢當前節點的直接父節點的標準 DOM 方法。
找到父節點後,事件處理程式訪問附加到父節點的 css 類列表,並使用 add 方法新增“highlight”類。classList是獲取附加到節點的類列表的標準 DOM 屬性,它可以用來向 DOM 節點新增/刪除類。
步驟 2 − 接下來,新增一個方法handleMouseLeave()來處理使用者從支出專案移出時觸發的事件。
handleMouseLeave(e) {
e.target.parentNode.classList.remove("highlight");
}
在這裡,事件處理程式從 DOM 中刪除highlight類。
新增另一個方法,handleMouseOver(),用於檢查滑鼠當前位置。在 DOM 中查詢滑鼠指標位置是可選的。
handleMouseOver(e) {
console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
}
步驟 3 − 在元件的建構函式中繫結所有事件處理程式。
this.handleMouseEnter = this.handleMouseEnter.bind(); this.handleMouseLeave = this.handleMouseLeave.bind(); this.handleMouseOver = this.handleMouseOver.bind();
步驟 4 − 在 render 方法中將事件處理程式附加到相應的標籤。
render() {
const lists = this.props.items.map((item) =>
<tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
</tr>
);
return (
<table onMouseOver={this.handleMouseOver}>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{lists}
</tbody>
</table>
);
}
ExpenseEntryItemList 的最終完整程式碼如下所示 −
import React from 'react';
import './ExpenseEntryItemList.css';
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
this.handleMouseEnter = this.handleMouseEnter.bind();
this.handleMouseLeave = this.handleMouseLeave.bind();
this.handleMouseOver = this.handleMouseOver.bind();
}
handleMouseEnter(e) {
e.target.parentNode.classList.add("highlight");
}
handleMouseLeave(e) {
e.target.parentNode.classList.remove("highlight");
}
handleMouseOver(e) {
console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
}
render() {
const lists = this.props.items.map((item) =>
<tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
</tr>
);
return (
<table onMouseOver={this.handleMouseOver}>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{lists}
</tbody>
</table>
);
}
}
export default ExpenseEntryItemList;
ExpenseEntryItemList.css
接下來,開啟 css 檔案 ExpenseEntryItemList.css 並新增一個 css 類 highlight。
tr.highlight td {
background-color: #a6a8bd;
}
index.js
開啟 index.js 並使用 ExpenseEntryItemList 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList'
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItemList items={items} />
</React.StrictMode>,
document.getElementById('root')
);
使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
應用程式將響應滑鼠事件並突出顯示當前選定的行。
| 專案 | 金額 | 日期 | 類別 |
| 披薩 | 80 | 2020年10月10日(週六) | 食品 |
| 葡萄汁 | 30 | 2020年10月12日(週一) | 食品 |
| 電影院 | 210 | 2020年10月16日(週五) | 娛樂 |
| Java程式設計書籍 | 242 | 2020年10月15日(週四) | 學術 |
| 芒果汁 | 35 | 2020年10月16日(週五) | 食品 |
| 衣服 | 2000 | 2020年10月25日(週日) | 服裝 |
| 旅遊 | 2555 | 2020年10月29日(週四) | 娛樂 |
| 餐飲 | 300 | 2020年10月30日(週五) | 食品 |
| 手機 | 3500 | 2020年11月2日(週一) | 電子產品 |
| 考試費 | 1245 | 2020年11月4日(週三) | 學術 |
ReactJS - 狀態管理
狀態管理是任何動態應用程式的重要且不可避免的功能之一。React 提供了一個簡單靈活的 API 來支援 React 元件中的狀態管理。本章讓我們瞭解如何在 React 應用程式中維護狀態。
什麼是狀態?
狀態表示給定例項下 React 元件的動態屬性的值。React 為每個元件提供了一個動態資料儲存。內部資料表示 React 元件的狀態,可以使用元件的 this.state 成員變數訪問。每當元件的狀態發生更改時,元件都會透過呼叫 render() 方法以及新狀態來重新渲染自身。
一個簡單的例子可以更好地理解狀態管理,就是分析一個即時時鐘元件。時鐘元件的主要工作是在給定例項下顯示某個位置的日期和時間。由於當前時間每秒都會變化,因此時鐘元件應該在其狀態中維護當前日期和時間。由於時鐘元件的狀態每秒都會變化,因此時鐘的 render() 方法將每秒被呼叫一次,並且 render() 方法將使用其當前狀態顯示當前時間。
狀態的簡單表示如下所示 −
{
date: '2020-10-10 10:10:10'
}
讓我們在 無狀態元件章節中建立一個新的 Clock 元件。
定義狀態
React 中的狀態可以與函式式元件和類元件一起使用。要在元件中使用狀態,必須存在一個起點,即初始狀態。元件的初始狀態必須在元件類的建構函式中定義。以下是定義任何類的狀態的語法 −
state = {attribute: "value"};
讓我們來看一個具有初始狀態的類元件的示例程式碼 −
Class SampleClass extends React.Component
{
constructor(props)
{
super(props);
this.state = { name : "John Doe" };
}
}
建立狀態物件
React 元件具有內建的狀態物件。狀態物件用於儲存屬於定義此狀態的元件的所有屬性值。當狀態物件更改時,元件將重新渲染。
讓我們來看一個示例程式碼,演示如何在 React 中建立狀態物件。
Class BookClass extends React.Component
{
constructor(props)
{
super(props);
this.state = { name : "John Doe" };
}
render() {
return (
<div>
<h1>Name of the Author</h1>
</div>
);
}
}
要更好地理解狀態管理,請檢視以下章節。
ReactJS - 狀態管理 API
正如我們前面瞭解到的,React 元件透過元件的 this.state 來維護和公開其狀態。React 提供了一個單一的 API 來維護元件中的狀態。該 API 是 this.setState()。它接受一個 JavaScript 物件或返回 JavaScript 物件的函式。
setState() 用於更新元件的狀態物件。這是透過安排對該元件的狀態物件的更新來完成的。因此,當狀態發生變化時,此元件會透過重新渲染來響應。
setState API 的簽名如下所示 −
this.setState( { ... object ...} );
一個設定/更新名稱的簡單示例如下所示 −
this.setState( { name: 'John' } )
使用函式的 setState()
使用函式的 setState 的簽名如下所示 −
this.setState( (state, props) => ... function returning JavaScript object ... );
這裡:
state 指的是 React 元件的當前狀態
props 指的是 React 元件的當前屬性。
React 建議使用函式來使用 setState API,因為它在非同步環境中可以正常工作。除了 Lambda 函式外,也可以使用普通的 JavaScript 函式。
this.setState( function(state, props) {
return ... JavaScript object ...
}
示例
一個使用函式更新金額的簡單示例如下所示 −
this.setState( (state, props) => ({
amount: this.state.amount + this.props.additionaAmount
})
不應透過 this.state 成員變數直接修改 React 狀態,並且透過成員變數更新狀態不會重新渲染元件。
React 狀態 API 的特殊功能
React 狀態 API 的一個特殊功能是它將與現有狀態合併,而不是替換狀態。例如,我們可以一次更新一個狀態欄位,而不是更新整個物件。此功能使開發人員能夠輕鬆處理狀態資料。
例如,讓我們考慮內部狀態包含學生記錄。
{
name: 'John', age: 16
}
我們可以使用 setState API 只更新年齡,它會自動將新物件與現有學生記錄物件合併。
this.setState( (state, props) => ({
age: 18
});
ReactJS - 無狀態元件
具有內部狀態的 React 元件稱為有狀態元件,沒有任何內部狀態管理的 React 元件稱為無狀態元件。React 建議儘可能建立和使用無狀態元件,只有在絕對必要時才建立有狀態元件。此外,React 不會與子元件共享狀態。資料需要透過子元件的屬性傳遞給子元件。
將日期傳遞給 FormattedDate 元件的示例如下所示 −
<FormattedDate value={this.state.item.spend_date} />
總的想法是不使應用程式邏輯過於複雜,只在必要時才使用高階功能。
建立一個有狀態元件
讓我們建立一個 React 應用程式來顯示當前日期和時間。
步驟 1 − 首先,按照 建立 React 應用程式章節中的說明,使用 Create React App 或 Rollup 捆綁器建立一個新的 react 應用程式 react-clock-app。
在您喜歡的編輯器中開啟應用程式。
步驟 2 − 在應用程式的根目錄下建立 src 資料夾。
在 src 資料夾下建立 components 資料夾。
在 src/components 資料夾下建立一個檔案 Clock.js 並開始編輯。
匯入React庫。
import React from 'react';
接下來,建立 Clock 元件。
class Clock extends React.Component {
constructor(props) {
super(props);
}
}
步驟 3 − 使用當前日期和時間初始化狀態。
constructor(props) {
super(props);
this.state = {
date: new Date()
}
}
步驟 4 − 新增一個方法 setTime() 來更新當前時間 −
setTime() {
console.log(this.state.date);
this.setState((state, props) => (
{
date: new Date()
}
))
}
步驟 5 − 使用 JavaScript 方法 setInterval 並每秒呼叫 setTime() 方法,以確保元件的狀態每秒更新一次。
constructor(props) {
super(props);
this.state = {
date: new Date()
}
setInterval( () => this.setTime(), 1000);
}
步驟 6 − 建立一個 render 函式。
render() {
}
Next, update the render() method to show the current time.
render() {
return (
<div><p>The current time is {this.state.date.toString()}</p></div>
);
}
最後,匯出元件。
export default Clock;
Clock 元件的完整原始碼如下所示 −
import React from 'react';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
date: new Date()
}
setInterval( () => this.setTime(), 1000);
}
setTime() {
console.log(this.state.date);
this.setState((state, props) => (
{
date: new Date()
}
))
}
render() {
return (
<div>
<p>The current time is {this.state.date.toString()}</p>
</div>
);
}
}
export default Clock;
index.js
接下來,在 src 資料夾下建立一個檔案 index.js 並使用 Clock 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';
ReactDOM.render(
<React.StrictMode>
<Clock />
</React.StrictMode>,
document.getElementById('root')
);
index.html
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Clock</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器並在位址列中輸入 https://:3000 並按 Enter 鍵。應用程式將顯示時間並每秒更新一次。
The current time is Wed Nov 11 2020 10:10:18 GMT+0530(Indian Standard Time)
上述應用程式執行良好,但在控制檯中丟擲錯誤。
Can't call setState on a component that is not yet mounted.
錯誤訊息表明,只有在元件掛載後才能呼叫 setState。
什麼是掛載?
React 元件具有生命週期,掛載是生命週期中的一個階段。讓我們在接下來的章節中進一步瞭解生命週期。
在費用管理器應用程式中引入狀態
讓我們透過新增一個簡單的刪除費用項的功能,在費用管理器應用程式中引入狀態管理。
步驟 1 − 在您喜歡的編輯器中開啟expense-manager應用程式。
開啟 ExpenseEntryItemList.js 檔案。
使用透過屬性傳遞到元件的費用項初始化元件的狀態。
this.state = {
items: this.props.items
}
步驟 2 − 在 render() 方法中新增 刪除 標籤。
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
<th>Remove</th>
</tr>
</thead>
步驟 3 − 更新 render() 方法中的列表以包含刪除連結。此外,使用狀態中的專案 (this.state.items) 而不是屬性中的專案 (this.props.items)。
const lists = this.state.items.map((item) =>
<tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
<td><a href="#" onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
</tr>
);
步驟 4 − 實現 handleDelete 方法,該方法將從狀態中刪除相關的費用項。
handleDelete = (id, e) => {
e.preventDefault();
console.log(id);
this.setState((state, props) => {
let items = [];
state.items.forEach((item, idx) => {
if(item.id != id)
items.push(item)
})
let newState = {
items: items
}
return newState;
})
}
這裡:
費用項是從元件的當前狀態中獲取的。
遍歷當前費用項以查詢使用者使用專案 ID 引用到的專案。
建立一個新的專案列表,其中包含除使用者引用的專案之外的所有費用專案
步驟 5 − 新增新行以顯示總費用金額。
<tr>
<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
<td colSpan="4" style={{ textAlign: "left" }}>
{this.getTotal()}
</td>
</tr>
步驟 6 − 實現 getTotal() 方法來計算總費用金額。
getTotal() {
let total = 0;
for(var i = 0; i < this.state.items.length; i++) {
total += this.state.items[i].amount
}
return total;
}
render() 方法的完整程式碼如下所示 −
render() {
const lists = this.state.items.map((item) =>
<tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
<td><a href="#"
onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
</tr>
);
return (
<table onMouseOver={this.handleMouseOver}>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{lists}
<tr>
<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
<td colSpan="4" style={{ textAlign: "left" }}>
{this.getTotal()}
</td>
</tr>
</tbody>
</table>
);
}
最後,ExpenseEntryItemList 的更新程式碼如下所示 −
import React from 'react';
import './ExpenseEntryItemList.css';
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
this.state = {
items: this.props.items
}
this.handleMouseEnter = this.handleMouseEnter.bind();
this.handleMouseLeave = this.handleMouseLeave.bind();
this.handleMouseOver = this.handleMouseOver.bind();
}
handleMouseEnter(e) {
e.target.parentNode.classList.add("highlight");
}
handleMouseLeave(e) {
e.target.parentNode.classList.remove("highlight");
}
handleMouseOver(e) {
console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
}
handleDelete = (id, e) => {
e.preventDefault();
console.log(id);
this.setState((state, props) => {
let items = [];
state.items.forEach((item, idx) => {
if(item.id != id)
items.push(item)
})
let newState = {
items: items
}
return newState;
})
}
getTotal() {
let total = 0;
for(var i = 0; i < this.state.items.length; i++) {
total += this.state.items[i].amount
}
return total;
}
render() {
const lists = this.state.items.map((item) =>
<tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
<td><a href="#"
onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
</tr>
);
return (
<table onMouseOver={this.handleMouseOver}>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{lists}
<tr>
<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
<td colSpan="4" style={{ textAlign: "left" }}>
{this.getTotal()}
</td>
</tr>
</tbody>
</table>
);
}
}
export default ExpenseEntryItemList;
index.js
更新 index.js 幷包含 ExpenseEntyItemList 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList'
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItemList items={items} />
</React.StrictMode>,
document.getElementById('root')
);
使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 https://:3000 並按 Enter 鍵。
最後,要刪除費用項,請單擊相應的刪除連結。它將刪除相應的專案並重新整理使用者介面,如動畫 GIF 中所示。
ReactJS - 使用 React Hooks 進行狀態管理
React 從 React 16.8 開始引入了全新的概念,稱為 React Hooks。即使它是一個相對較新的概念,它也使 React 函式式元件能夠擁有自己的狀態和生命週期。此外,React Hooks 使函式式元件能夠使用許多以前不可用的功能。本章讓我們看看如何在函式式元件中使用 React Hooks 進行狀態管理。
什麼是 React Hooks?
React Hooks 是 React 提供的特殊函式,用於在 React 函式式元件內處理特定功能。React 為每個受支援的功能提供了一個 Hook 函式。例如,React 提供了 useState() 函式來管理函式式元件中的狀態。當 React 函式式元件使用 React Hooks 時,React Hooks 會附加到元件中並提供附加功能。
useState() Hook 的通用簽名如下所示 −
const [<state variable>, <state update function>] = useState(<initial value>);
例如,使用 Hooks 在時鐘元件中進行狀態管理可以按如下所示進行 −
const [currentDateTime, setCurrentDateTime] = useState(new Date()); setInterval(() => setCurrentDateTime(new Date()), 1000);
這裡:
- currentDateTime − 用於儲存當前日期和時間(由 setState() 返回)的變數
- setCurrentDate() − 用於設定當前日期和時間(由 setState() 返回)的函式
建立一個有狀態元件
讓我們在本節中使用 Hooks 重新建立我們的時鐘元件。
步驟 1 − 首先,按照 建立 React 應用程式章節中的說明,使用 Create React App 或 Rollup 捆綁器建立一個新的 react 應用程式 react-clock-hook-app。
步驟 2 − 在您喜歡的編輯器中開啟應用程式。
在應用程式的根目錄下建立 src 資料夾。
在 src 資料夾下建立 components 資料夾。
在 src/components 資料夾下建立一個檔案 Clock.js 並開始編輯。
匯入 React 庫和 React 狀態 Hook,setState。
import React, { useState } from 'react';
步驟 2 − 建立 Clock 元件。
function Clock() {
}
建立狀態 Hooks 來維護日期和時間。
const [currentDateTime, setCurrentDateTime] = useState(new Date());
每秒設定日期和時間。
setInterval(() => setCurrentDateTime(new Date()), 1000);
建立使用者介面以使用 currentDateTime 顯示當前日期和時間並將其返回。
return ( <div><p>The current time is {currentDateTime.toString()}</p></div> );
步驟 3 − 最後,使用程式碼片段匯出元件 −
export default Clock;
Clock 元件的完整原始碼如下所示 −
import React, { useState } from 'react';
function Clock(props) {
const [currentDateTime, setCurrentDateTime] = useState(new Date());
setInterval(() => setCurrentDateTime(new Date()), 1000);
return (
<div><p>The current time is {currentDateTime.toString()}</p></div>
);
}
export default Clock;
index.js
接下來,在 src 資料夾下建立一個檔案 index.js 並使用 Clock 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';
ReactDOM.render(
<React.StrictMode>
<Clock />
</React.StrictMode>,
document.getElementById('root')
);
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Clock</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
然後,使用 npm 命令提供服務。
npm start
開啟瀏覽器並在位址列中輸入 https://:3000 並按 Enter 鍵。應用程式將顯示時間並每秒更新一次。
The current time is Wed Nov 11 2020 10:10:18 GMT+0530 (India Standard Time)
上述應用程式執行良好。但是,當應用程式結束時,必須刪除設定為每秒執行的 setCurrentDateTime()。我們可以使用 React 提供的另一個 Hook,useEffect 來做到這一點。我們將在接下來的章節(元件生命週期)中學習它。
在費用管理器應用程式中引入狀態
讓我們在本節中透過新增一個簡單的使用 Hooks 刪除費用項的功能,在費用管理器應用程式中引入狀態管理。
步驟 1 − 在您喜歡的編輯器中開啟expense-manager應用程式。
在 src/components 資料夾下建立一個新檔案 ExpenseEntryItemListFn.js 並開始編輯。
匯入 React 庫和 React 狀態 Hook,setState。
import React, { useState } from 'react';
匯入 css,ExpenseEntryItem.css。
import './ExpenseEntryItemList.css'
步驟 2 − 建立 ExpenseEntryItemListFn 元件。
function ExpenseEntryItemListFn(props) { }
使用透過屬性傳遞到元件的費用項初始化元件的狀態 Hooks。
const [items, setItems] = useState(props.items);
步驟 3 − 建立事件處理程式以突出顯示行。
function handleMouseEnter(e) {
e.target.parentNode.classList.add("highlight");
}
function handleMouseLeave(e) {
e.target.parentNode.classList.remove("highlight");
}
function handleMouseOver(e) {
console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
}
步驟 4 − 建立事件處理程式,使用 items 和 setItems() 刪除選定的專案。
function handleDelete(id, e) {
e.preventDefault();
console.log(id);
let newItems = [];
items.forEach((item, idx) => {
if (item.id != id)
newItems.push(item)
})
setItems(newItems);
}
步驟 5 − 建立 getTotal() 方法來計算總金額。
function getTotal() {
let total = 0;
for (var i = 0; i < items.length; i++) {
total += items[i].amount
}
return total;
}
步驟 6 − 建立使用者介面,透過迴圈遍歷 items 來顯示支出。
const lists = items.map((item) =>
<tr key={item.id} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
<td><a href="#" onClick={(e) => handleDelete(item.id, e)}>Remove</a></td>
</tr>
);
步驟 7 − 建立完整的 UI 來顯示支出並將其返回。
return (
<table onMouseOver={handleMouseOver}>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{lists}
<tr>
<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
<td colSpan="4" style={{ textAlign: "left" }}>
{getTotal()}
</td>
</tr>
</tbody>
</table>
);
最後,匯出函式,如下所示:
export default ExpenseEntryItemListFn;
ExpenseEntryItemListFn 的完整程式碼如下:
import React, { useState } from 'react';
import './ExpenseEntryItemList.css'
function ExpenseEntryItemListFn(props) {
const [items, setItems] = useState(props.items);
function handleMouseEnter(e) {
e.target.parentNode.classList.add("highlight");
}
function handleMouseLeave(e) {
e.target.parentNode.classList.remove("highlight");
}
function handleMouseOver(e) {
console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
}
function handleDelete(id, e) {
e.preventDefault();
console.log(id);
let newItems = [];
items.forEach((item, idx) => {
if (item.id != id)
newItems.push(item)
})
setItems(newItems);
}
function getTotal() {
let total = 0;
for (var i = 0; i < items.length; i++) {
total += items[i].amount
}
return total;
}
const lists = items.map((item) =>
<tr key={item.id} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
<td><a href="#"
onClick={(e) => handleDelete(item.id, e)}>Remove</a></td>
</tr>
);
return (
<table onMouseOver={handleMouseOver}>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{lists}
<tr>
<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
<td colSpan="4" style={{ textAlign: "left" }}>
{getTotal()}
</td>
</tr>
</tbody>
</table>
);
}
export default ExpenseEntryItemListFn;
index.js
更新 index.js 幷包含 ExpenseEntyItemListFn 元件:
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemListFn from './components/ExpenseEntryItemListFn'
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItemListFn items={items} />
</React.StrictMode>,
document.getElementById('root')
);
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
最後,要刪除費用項,請單擊相應的刪除連結。它將刪除相應的專案並重新整理使用者介面,如動畫 GIF 中所示。
ReactJS - 使用 React Hooks 的元件生命週期
React Hooks 提供了一個特殊的 Hook,useEffect(),用於在元件的生命週期中執行某些功能。useEffect() 將 componentDidMount, componentDidUpdate, 和 componentWillUnmount 生命週期組合到單個 API 中。
useEffect() API 的簽名如下:
useEffect( <executeFn>, <values> );
這裡:
executeFn − 當發生效果時要執行的函式,以及可選的返回函式。當需要清理時(類似於 componentWillUnmount),將執行返回函式。
values − 效果依賴的值陣列。只有當值更改時,React Hooks 才會執行 executeFn。這將減少不必要的 executeFn 呼叫。
讓我們在我們的 react-clock-hook-app 應用程式中新增 useEffect() Hooks。
在您喜歡的編輯器中開啟 react-clock-hook-app。
接下來,開啟 src/components/Clock.js 檔案並開始編輯。
接下來,匯入 useEffect api。
import React, { useState, useEffect } from 'react';
接下來,使用 setInterval 呼叫 useEffect 並傳入一個函式,以便每秒設定日期和時間,並返回一個函式,使用 clearInterval 停止更新日期和時間。
useEffect(
() => {
let setTime = () => {
console.log("setTime is called");
setCurrentDateTime(new Date());
}
let interval = setInterval(setTime, 1000);
return () => {
clearInterval(interval);
}
},
[]
);
這裡:
建立了一個函式 setTime,用於將當前時間設定為元件的狀態。
呼叫 setInterval JavaScript API 以每秒執行 setTime,並將 setInterval 的引用儲存在 interval 變數中。
建立了一個返回函式,該函式呼叫 clearInterval API 透過傳遞 interval 引用 來停止每秒執行 setTime。
現在,我們已經更新了 Clock 元件,該元件的完整原始碼如下:
import React, { useState, useEffect } from 'react';
function Clock() {
const [currentDateTime, setCurrentDateTime] = useState(new Date());
useEffect(
() => {
let setTime = () => {
console.log("setTime is called");
setCurrentDateTime(new Date());
}
let interval = setInterval(setTime, 1000);
return () => {
clearInterval(interval);
}
},
[]
);
return (
<div>
<p>The current time is {currentDateTime.toString()}</p>
</div>
);
}
export default Clock;
接下來,開啟 index.js 並使用 setTimeout 在 5 秒後從 DOM 中移除時鐘。
import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';
ReactDOM.render(
<React.StrictMode>
<Clock />
</React.StrictMode>,
document.getElementById('root')
);
setTimeout(() => {
ReactDOM.render(
<React.StrictMode>
<div><p>Clock is removed from the DOM.</p></div>
</React.StrictMode>,
document.getElementById('root')
);
}, 5000);
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
時鐘將顯示 5 秒,然後將從 DOM 中移除。透過檢查控制檯日誌,我們可以發現清理程式碼已正確執行。
React children 屬性,又名包含
React 允許在元件內部包含任意子使用者介面內容。可以透過 this.props.children 訪問元件的子元素。在元件內部新增子元素稱為 包含。包含 用於元件的某些部分本質上是動態的情況。
例如,富文字訊息框在被呼叫之前可能不知道其內容。讓我們在本節中建立一個 RichTextMessage 元件來展示 React 子元素屬性的功能。
首先,使用 Create React App 或 Rollup bundler 建立一個新的 React 應用程式 react-message-app,方法是按照 建立 React 應用程式 一章中的說明操作。
接下來,在您喜歡的編輯器中開啟應用程式。
接下來,在應用程式的根目錄下建立 src 資料夾。
接下來,在 src 資料夾下建立 components 資料夾。
接下來,在 src/components 資料夾下建立一個檔案 RichTextMessage.js 並開始編輯。
接下來,匯入 React 庫。
import React from 'react';
接下來,建立一個類 RichTextMessage 並使用 props 呼叫建構函式。
class RichTextMessage extends React.Component {
constructor(props) {
super(props);
}
}
接下來,新增 render() 方法並顯示元件的使用者介面及其子元素。
render() {
return (
<div>{this.props.children}</div>
)
}
這裡:
props.children 返回元件的子元素。
將子元素包裝在 div 標籤內。
最後,匯出元件。
export default RichTextMessage;
RichTextMessage 元件的完整原始碼如下:
import React from 'react';
class RichTextMessage extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>{this.props.children}</div>
)
}
}
export default RichTextMessage;
接下來,在 src 資料夾下建立一個檔案 index.js 並使用 RichTextMessage 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import RichTextMessage from './components/RichTextMessage';
ReactDOM.render(
<React.StrictMode>
<RichTextMessage>
<h1>Containment is really a cool feature.</h1>
</RichTextMessage>
</React.StrictMode>,
document.getElementById('root')
);
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
瀏覽器發出包裝在 div 標籤中的元件子元素,如下所示:
<div id="root">
<div>
<div>
<h1>Containment is really a cool feature.</h1>
</div>
</div>
</div>
接下來,更改 index.js 中 RichTextMessage 元件的子元素屬性。
import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';
ReactDOM.render(
<React.StrictMode>
<RichTextMessage>
<h1>Containment is really an excellent feature.</h1>
</RichTextMessage>
</React.StrictMode>,
document.getElementById('root')
);
現在,瀏覽器更新元件的子元素內容併發出如下所示的內容:
<div id="root">
<div>
<div>
<h1>Containment is really an excellent feature.</h1>
</div>
</div>
</div>
簡而言之,包含是一個將任意使用者介面內容傳遞給元件的優秀功能。
ReactJS - 元件中的佈局
React 的一個高階特性是它允許使用屬性將任意使用者介面 (UI) 內容傳遞到元件中。與 React 的特殊 children 屬性(僅允許將單個使用者介面內容傳遞到元件中)相比,此選項允許將多個 UI 內容傳遞到元件中。此選項可以被視為 children 屬性的擴充套件。此選項的用例之一是佈局元件的使用者介面。
例如,具有可自定義頁首和頁尾的元件可以使用此選項透過屬性獲取自定義頁首和頁尾並佈局內容。
示例
下面是一個帶有兩個屬性 header 和 footer 的快速簡單的示例
<Layout header={<h1>Header</h1>} footer={<p>footer</p>} />
佈局渲染邏輯如下:
return (<div>
<div>
{props.header}
</div>
<div>
Component user interface
</div>
<div>
{props.footer}
</div>
</div>)
讓我們向我們的支出條目列表 (ExpenseEntryItemList) 元件新增一個簡單的頁首和頁尾。
在您喜歡的編輯器中開啟 `expense-manager` 應用程式。
接下來,開啟 src/components 資料夾中的 ExpenseEntryItemList.js 檔案。
接下來,在 render() 方法中使用 header 和 footer 屬性。
return (
<div>
<div>{this.props.header}</div>
... existing code ...
<div>{this.props.footer}</div>
</div>
);
接下來,開啟 index.js 並使用 ExpenseEntryItemList 元件時包含 header 和 footer 屬性。
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItemList items={items}
header={
<div><h1>Expense manager</h1></div>
}
footer={
<div style={{ textAlign: "left" }}>
<p style={{ fontSize: 12 }}>Sample application</p>
</div>
}
/>
</React.StrictMode>,
document.getElementById('root')
);
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
在元件中共享邏輯,又名渲染屬性
渲染屬性 是用於在 React 元件之間共享邏輯的高階概念。正如我們之前瞭解到的那樣,元件可以透過屬性接收任意 UI 內容或 React 元素(物件)。通常,元件會按原樣渲染它接收到的 React 元素及其自己的使用者介面,正如我們在 children 和佈局概念中看到的那樣。它們之間不共享任何邏輯。
更進一步,React 允許元件透過屬性獲取一個返回使用者介面的函式,而不是普通的使用者介面物件。該函式的唯一目的是渲染 UI。然後,元件將進行高階計算,並將計算值與傳入的函式一起呼叫以渲染 UI。
簡而言之,接受渲染使用者介面的 JavaScript 函式的元件屬性稱為 渲染屬性。接收 渲染屬性 的元件將執行高階邏輯並將其與 渲染屬性 共享,後者將使用共享邏輯渲染使用者介面。
許多高階的第三方庫都是基於 渲染屬性 的。一些使用 渲染屬性 的庫包括:
- React Router
- Formik
- Downshift
例如,Formik 庫元件將執行表單驗證和提交,並將表單設計傳遞給呼叫函式,又名 渲染屬性。類似地,React Router 執行路由邏輯,同時使用 渲染屬性 將 UI 設計委託給其他元件。
ReactJS - 分頁
React 透過第三方 UI 元件庫提供分頁元件。React 社群提供大量 UI/UX 元件,為我們的需求選擇合適的庫很困難。Bootstrap UI 庫是開發人員的熱門選擇之一,並且被廣泛使用。React Bootstrap (https://react-bootstrap.github.io/) 幾乎移植了所有 Bootstrap UI 元件,並且對 Pagination 元件也有最好的支援。
讓我們在本節中學習如何在 React 應用程式中使用來自 react-bootstrap 庫的 Pagination 元件。
分頁元件
Pagination 元件允許開發人員在 Web 應用程式中使用 Bootstrap 設計建立簡單的分頁。分頁元件接受以下元件。
Pagination.Item
Pagination.First
Pagination.Last
Pagination.Previous
Pagination.Next
Pagination 元件接受少量道具來自定義分頁元件,它們如下:
size (sm | lg)
設定分頁按鈕的大小
bsPrefix (string)
能夠更改底層元件 CSS
Pagination.Item 元件接受少量道具來自定義分頁元件,它們如下:
active (boolean)
將專案設定為活動專案,並且不渲染標籤。
activeLabel (string)
指示分頁專案狀態的標籤
disabled (boolean)
停用分頁專案
href (string)
分頁專案的連結
onClick (function)
單擊事件觸發時要呼叫的回撥函式
應用 Pagination 元件
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 bootstrap 庫:
npm install --save bootstrap react-bootstrap
接下來,開啟 App.css (src/App.css) 並刪除所有 CSS 類。
// remove the css
接下來,建立一個簡單的分頁元件 SimplePagination (src/Components/SimplePagination.js),如下所示:
import React from 'react';
import { Table, Pagination } from 'react-bootstrap';
class SimplePagination extends React.Component {
render() {
<div>Pagination component</div>
}
}
export default SimplePagination;
接下來,在 public 資料夾下建立一個檔案 users.json,並使用以下使用者資訊填充:
[
{
"id":1,
"name":"Fowler",
"age":18
},
{
"id":2,
"name":"Donnell",
"age":24
},
{
"id":3,
"name":"Pall",
"age":26
},
{
"id":4,
"name":"Christos",
"age":19
},
{
"id":5,
"name":"Dud",
"age":29
},
{
"id":6,
"name":"Rayner",
"age":22
},
{
"id":7,
"name":"Somerset",
"age":31
},
{
"id":8,
"name":"Stavros",
"age":32
},
{
"id":9,
"name":"Cody",
"age":19
},
{
"id":10,
"name":"Sharai",
"age":19
},
{
"id":11,
"name":"Kristo",
"age":28
},
{
"id":12,
"name":"Harvey",
"age":27
},
{
"id":13,
"name":"Christen",
"age":27
},
{
"id":14,
"name":"Hillard",
"age":19
},
{
"id":15,
"name":"Jaine",
"age":32
},
{
"id":16,
"name":"Annabel",
"age":29
},
{
"id":17,
"name":"Hildagarde",
"age":29
},
{
"id":18,
"name":"Cherlyn",
"age":18
},
{
"id":19,
"name":"Herold",
"age":32
},
{
"id":20,
"name":"Gabriella",
"age":32
},
{
"id":21,
"name":"Jessalyn",
"age":32
},
{
"id":22,
"name":"Opal",
"age":31
},
{
"id":23,
"name":"Westbrooke",
"age":27
},
{
"id":24,
"name":"Morey",
"age":22
},
{
"id":25,
"name":"Carleton",
"age":26
},
{
"id":26,
"name":"Cosimo",
"age":22
},
{
"id":27,
"name":"Petronia",
"age":23
},
{
"id":28,
"name":"Justino",
"age":32
},
{
"id":29,
"name":"Verla",
"age":20
},
{
"id":30,
"name":"Lanita",
"age":18
},
{
"id":31,
"name":"Karlik",
"age":23
},
{
"id":32,
"name":"Emmett",
"age":22
},
{
"id":33,
"name":"Abran",
"age":26
},
{
"id":34,
"name":"Holly",
"age":23
},
{
"id":35,
"name":"Beverie",
"age":23
},
{
"id":36,
"name":"Ingelbert",
"age":27
},
{
"id":37,
"name":"Kailey",
"age":30
},
{
"id":38,
"name":"Ralina",
"age":26
},
{
"id":39,
"name":"Stella",
"age":29
},
{
"id":40,
"name":"Ronnica",
"age":20
},
{
"id":41,
"name":"Brucie",
"age":20
},
{
"id":42,
"name":"Ryan",
"age":22
},
{
"id":43,
"name":"Fredek",
"age":20
},
{
"id":44,
"name":"Corliss",
"age":28
},
{
"id":45,
"name":"Kary",
"age":32
},
{
"id":46,
"name":"Kaylee",
"age":21
},
{
"id":47,
"name":"Haskell",
"age":25
},
{
"id":48,
"name":"Jere",
"age":29
},
{
"id":49,
"name":"Kathryne",
"age":31
},
{
"id":50,
"name":"Linnea",
"age":21
},
{
"id":51,
"name":"Theresina",
"age":24
},
{
"id":52,
"name":"Arabela",
"age":32
},
{
"id":53,
"name":"Howie",
"age":22
},
{
"id":54,
"name":"Merci",
"age":21
},
{
"id":55,
"name":"Mitchel",
"age":30
},
{
"id":56,
"name":"Clari",
"age":18
},
{
"id":57,
"name":"Laurena",
"age":19
},
{
"id":58,
"name":"Odessa",
"age":30
},
{
"id":59,
"name":"Pippy",
"age":25
},
{
"id":60,
"name":"Wilmar",
"age":23
},
{
"id":61,
"name":"Cherianne",
"age":24
},
{
"id":62,
"name":"Huberto",
"age":25
},
{
"id":63,
"name":"Ariella",
"age":26
},
{
"id":64,
"name":"Lorant",
"age":30
},
{
"id":65,
"name":"Francesca",
"age":25
},
{
"id":66,
"name":"Ingamar",
"age":28
},
{
"id":67,
"name":"Myrta",
"age":27
},
{
"id":68,
"name":"Nicolette",
"age":26
},
{
"id":69,
"name":"Petra",
"age":22
},
{
"id":70,
"name":"Cyrill",
"age":27
},
{
"id":71,
"name":"Ad",
"age":23
},
{
"id":72,
"name":"Denys",
"age":22
},
{
"id":73,
"name":"Karilynn",
"age":23
},
{
"id":74,
"name":"Gunner",
"age":30
},
{
"id":75,
"name":"Falkner",
"age":20
},
{
"id":76,
"name":"Thurston",
"age":19
},
{
"id":77,
"name":"Codi",
"age":30
},
{
"id":78,
"name":"Jacob",
"age":31
},
{
"id":79,
"name":"Gasparo",
"age":26
},
{
"id":80,
"name":"Mitzi",
"age":29
},
{
"id":81,
"name":"Rubetta",
"age":21
},
{
"id":82,
"name":"Clary",
"age":20
},
{
"id":83,
"name":"Oliviero",
"age":24
},
{
"id":84,
"name":"Ranique",
"age":21
},
{
"id":85,
"name":"Shae",
"age":24
},
{
"id":86,
"name":"Woodrow",
"age":20
},
{
"id":87,
"name":"Junia",
"age":31
},
{
"id":88,
"name":"Athene",
"age":26
},
{
"id":89,
"name":"Veriee",
"age":18
},
{
"id":90,
"name":"Rickie",
"age":30
},
{
"id":91,
"name":"Carly",
"age":23
},
{
"id":92,
"name":"Vern",
"age":19
},
{
"id":93,
"name":"Trix",
"age":26
},
{
"id":94,
"name":"Lenore",
"age":20
},
{
"id":95,
"name":"Hanna",
"age":30
},
{
"id":96,
"name":"Dominique",
"age":21
},
{
"id":97,
"name":"Karlotta",
"age":22
},
{
"id":98,
"name":"Levey",
"age":20
},
{
"id":99,
"name":"Dalila",
"age":18
},
{
"id":100,
"name":"Launce",
"age":21
}
]
接下來,在 SimplePagination 元件中新增一個建構函式,並設定初始狀態,如下所示:
constructor(props) {
super(props);
this.state = {
users: [],
usersToBeShown: [],
currentPage: 1
}
};
接下來,新增 componentDidMount 生命週期事件,並新增以下程式碼來獲取和處理使用者資訊。
componentDidMount() {
fetch('users.json')
.then((response) => response.json())
.then((data) => {
// console.log(data);
this.setState({
users: data,
pageSize: 3,
usersToBeShown: [],
pageArray: []
});
this.calculatePaginationDetails(1)
});
}
接下來,在 calculatePaginationDetails 方法中實現分頁邏輯,如下所示:
calculatePaginationDetails = (page) => {
console.log(page)
let users = this.state.users;
let total = users.length;
let pages = Math.floor((users.length / this.state.pageSize) + 1);
let firstPage = 1;
let lastPage = pages;
let pageArray = []
let usersToBeShown = []
let currentPage = 1;
if(page.toString().toLowerCase().indexOf('previous') > 0) {
currentPage = this.state.currentPage - 1;
if(currentPage < 1) {
currentPage = 1
}
} else if(page.toString().toLowerCase().indexOf('next') > 0) {
currentPage = this.state.currentPage + 1;
if(currentPage > pages) {
currentPage = pages;
}
} else if(page.toString().toLowerCase().indexOf('first') > 0) {
currentPage = 1
} else if(page.toString().toLowerCase().indexOf('last') > 0) {
currentPage = pages;
} else {
currentPage = parseInt(page);
}
console.log(parseInt(page))
console.log(currentPage)
for(let i = currentPage; i <= currentPage + 4; i++) {
if(i <= pages)
pageArray.push(i)
}
let currentItemIndex = (currentPage - 1) * this.state.pageSize;
for(let i = currentItemIndex; i < currentItemIndex + 3 && i <= (total - 1); i++) {
usersToBeShown.push(users[i])
}
let updatedState = {
usersToBeShown: usersToBeShown,
pageArray: pageArray,
firstPage: firstPage,
lastPage: lastPage,
currentPage: currentPage
}
console.log(updatedState)
this.setState({
usersToBeShown: usersToBeShown,
pageArray: pageArray,
firstPage: firstPage,
lastPage: lastPage,
currentPage: currentPage
});
}
接下來,新增一個事件處理程式來處理分頁,並根據使用者選擇的頁面設定資料,如下所示:
handlePagination = (e) => {
e.preventDefault();
console.log(e.target);
if(e.target.text != undefined) {
this.calculatePaginationDetails(e.target.text);
}
}
接下來,使用 Table 元件渲染資料,並使用 Pagination 元件進行分頁。
render() {
return (
<>
<Table bordered hover striped>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Age</th>
<th>Email</th>
</tr>
</thead>
<tbody>{
this.state.usersToBeShown && this.state.usersToBeShown.length &&
this.state.usersToBeShown.map(
(item) => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.age}</td>
<td>{item.name.toLowerCase()}.example@tutorialspoint.com</td>
</tr>
)
)
}
</tbody>
</Table>
<Pagination>
<Pagination.First onClick={(e) => this.handlePagination(e)} />
<Pagination.Prev onClick={(e) => this.handlePagination(e)} />{
this.state.pageArray && this.state.pageArray.length &&
this.state.pageArray.map(
(item) => (
<Pagination.Item key={item} onClick={(e) => this.handlePagination(e)}
active={this.state.currentPage == item}>{item}</Pagination.Item>
)
)
}
<Pagination.Next onClick={(e) => this.handlePagination(e)} />
<Pagination.Last onClick={(e) => this.handlePagination(e)} />
</Pagination>
</>
);
}
接下來,開啟 App 元件 (src/App.js),匯入 bootstrap css 並使用 SimplePagination 元件更新內容,如下所示:
import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimplePagination from './Components/SimplePagination'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimplePagination />
</div>
</div>
</div>
);
}
export default App;
最後,在瀏覽器中開啟應用程式,並檢查分頁是否正常工作,如下所示:
總結
React Bootstrap 分頁元件提供了渲染簡單和複雜分頁設計的必要元件。
ReactJS - Material UI
React 社群提供了大量的先進 UI 元件框架。Material UI 是流行的 React UI 框架之一。讓我們在本節中學習如何在 React 應用中使用 Material UI 庫。
安裝
可以使用 npm 包安裝 Material UI。
npm install @material-ui/core
Material UI 建議使用 roboto 字型。要使用 Roboto 字型,請使用 Gooogleapi 連結包含它。
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
要使用字型圖示,請使用來自 googleapis 的圖示連結:
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
要使用 SVG 圖示,請安裝 @material-ui/icons 包:
npm install @material-ui/icons
工作示例
讓我們重新建立支出列表應用程式,並使用 Material UI 元件代替 html 表格。
步驟 1 − 首先,使用Create React App或Rollup打包器建立一個新的React應用程式,名為react-materialui-app,具體步驟請參考建立React應用程式章節。
步驟 2 − 安裝React Transition Group庫 −
cd /go/to/project npm install @material-ui/core @material-ui/icons --save
在您喜歡的編輯器中開啟應用程式。
在應用程式的根目錄下建立 src 資料夾。
在 src 資料夾下建立 components 資料夾。
在src/components資料夾中建立一個名為ExpenseEntryItemList.js的檔案,用於建立ExpenseEntryItemList元件。
匯入React庫和樣式表。
import React from 'react';
步驟 2 − 接下來,匯入Material-UI庫。
import { withStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
建立ExpenseEntryItemList類並呼叫建構函式。
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
}
}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
建立一個 render() 函式。
render() {
}
在render方法中為表格行和表格單元格應用樣式。
const StyledTableCell = withStyles((theme) => ({
head: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
body: {
fontSize: 14,
},
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
root: {
'&:nth-of-type(odd)': {
backgroundColor: theme.palette.action.hover,
},
},
}))(TableRow);
使用map方法生成一系列Material UI StyledTableRow,每個TableRow代表列表中單個的支出條目。
const lists = this.props.items.map((item) =>
<StyledTableRow key={item.id}>
<StyledTableCell component="th" scope="row">
{item.name}
</StyledTableCell>
<StyledTableCell align="right">{item.amount}</StyledTableCell>
<StyledTableCell align="right">
{new Date(item.spendDate).toDateString()}
</StyledTableCell>
<StyledTableCell align="right">{item.category}</StyledTableCell>
</StyledTableRow>
);
這裡,key 標識每一行,它在列表中必須是唯一的。
步驟 3 − 在render()方法中,建立一個Material UI表格,並將列表表示式包含在rows部分中並返回。
return (
<TableContainer component={Paper}>
<Table aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell>Title</StyledTableCell>
<StyledTableCell align="right">Amount</StyledTableCell>
<StyledTableCell align="right">Spend date</StyledTableCell>
<StyledTableCell align="right">Category</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{lists}
</TableBody>
</Table>
</TableContainer> );
最後,匯出元件。
export default ExpenseEntryItemList;
現在,我們已經成功建立了使用Material UI元件渲染支出條目的元件。
元件的完整原始碼如下所示 −
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
}
render() {
const StyledTableCell = withStyles((theme) => ({
head: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
body: {
fontSize: 14,
},
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
root: {
'&:nth-of-type(odd)': {
backgroundColor: theme.palette.action.hover,
},
},
}))(TableRow);
const lists = this.props.items.map((item) =>
<StyledTableRow key={item.id}>
<StyledTableCell component="th" scope="row">
{item.name}
</StyledTableCell>
<StyledTableCell align="right">{item.amount}</StyledTableCell>
<StyledTableCell align="right">{new Date(item.spendDate).toDateString()}</StyledTableCell>
<StyledTableCell align="right">{item.category}</StyledTableCell>
</StyledTableRow>
);
return (
<TableContainer component={Paper}>
<Table aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell>Title</StyledTableCell>
<StyledTableCell align="right">Amount</StyledTableCell>
<StyledTableCell align="right">Spend date</StyledTableCell>
<StyledTableCell align="right">Category</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{lists}
</TableBody>
</Table>
</TableContainer> );
}
}
export default ExpenseEntryItemList;
index.js
開啟index.js並匯入React庫和我們新建立的ExpenseEntryItemList元件。
import React from 'react'; import ReactDOM from 'react-dom'; import ExpenseEntryItemList from './components/ExpenseEntryItemList';
在index.js檔案中宣告一個列表(支出條目列表)並用一些隨機值填充它。
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 1, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 1, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 1, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 1, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 1, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 1, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 1, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 1, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 1, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
透過items屬性傳遞專案來使用ExpenseEntryItemList元件。
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItemList items={items} />
</React.StrictMode>,
document.getElementById('root')
);
index.js 的完整程式碼如下:
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList';
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 1, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 1, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 1, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 1, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 1, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 1, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 1, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 1, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 1, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItemList items={items} />
</React.StrictMode>,
document.getElementById('root')
);
使用 npm 命令啟動應用程式。
npm start
index.html
開啟public資料夾中的index.html檔案,幷包含Material UI字型和圖示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Material UI App</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
開啟瀏覽器並在位址列中輸入https://:3000,然後按回車鍵。
ReactJS - HTTP客戶端程式設計
HTTP客戶端程式設計使應用程式能夠透過JavaScript連線到HTTP伺服器並從中獲取資料。它減少了客戶端和伺服器之間的資料傳輸量,因為它只獲取所需的資料,而不是整個設計,從而提高了網路速度。它改善了使用者體驗,併成為每個現代Web應用程式不可或缺的功能。
如今,許多伺服器端應用程式透過REST API(基於HTTP協議的功能)公開其功能,並允許任何客戶端應用程式使用這些功能。
React本身並不提供HTTP程式設計API,但它支援瀏覽器的內建fetch() API以及axios等第三方客戶端庫來進行客戶端程式設計。本章將學習如何在React應用程式中進行HTTP程式設計。開發者應該具備HTTP程式設計的基礎知識才能理解本章內容。
支出REST API伺服器
進行HTTP程式設計的先決條件是對HTTP協議和REST API技術的瞭解。HTTP程式設計包括兩部分:伺服器端和客戶端。React提供支援來建立客戶端應用程式。Express是一個流行的Web框架,提供支援來建立伺服器端應用程式。
讓我們首先使用Express框架建立一個支出REST API伺服器,然後使用瀏覽器的內建fetch API從我們的ExpenseManager應用程式訪問它。
開啟命令提示符並建立一個新資料夾,名為express-rest-api。
cd /go/to/workspace mkdir apiserver cd apiserver
使用以下命令初始化一個新的Node應用程式 −
npm init
npm init將提示我們輸入基本的專案詳細資訊。讓我們為專案名稱輸入apiserver,為入口點輸入server.js。其他配置使用預設選項。
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (apiserver)
version: (1.0.0)
description: Rest api for Expense Application
entry point: (index.js) server.js
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to \path\to\workspace\expense-rest-api\package.json:
{
"name": "expense-rest-api",
"version": "1.0.0",
"description": "Rest api for Expense Application",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this OK? (yes) yes
接下來,使用以下命令安裝express, nedb & cors模組 −
npm install express nedb cors
express用於建立伺服器端應用程式。
nedb是一個用於儲存支出資料的資料庫。
cors是express框架的一箇中間件,用於配置客戶端訪問詳細資訊。
接下來,讓我們建立一個名為data.csv的檔案,並用初始支出資料填充它以進行測試。該檔案結構為每行一個支出條目。
Pizza,80,2020-10-10,Food Grape Juice,30,2020-10-12,Food Cinema,210,2020-10-16,Entertainment Java Programming book,242,2020-10-15,Academic Mango Juice,35,2020-10-16,Food Dress,2000,2020-10-25,Cloth Tour,2555,2020-10-29,Entertainment Meals,300,2020-10-30,Food Mobile,3500,2020-11-02,Gadgets Exam Fees,1245,2020-11-04,Academic
接下來,建立一個名為expensedb.js的檔案,幷包含將初始支出資料載入到資料庫中的程式碼。該程式碼檢查資料庫中是否存在初始資料,僅當資料庫中不存在資料時才載入資料。
var store = require("nedb")
var fs = require('fs');
var expenses = new store({ filename: "expense.db", autoload: true })
expenses.find({}, function (err, docs) {
if (docs.length == 0) {
loadExpenses();
}
})
function loadExpenses() {
readCsv("data.csv", function (data) {
console.log(data);
data.forEach(function (rec, idx) {
item = {}
item.name = rec[0];
item.amount = parseFloat(rec[1]);
item.spend_date = new Date(rec[2]);
item.category = rec[3];
expenses.insert(item, function (err, doc) {
console.log('Inserted', doc.item_name, 'with ID', doc._id);
})
})
})
}
function readCsv(file, callback) {
fs.readFile(file, 'utf-8', function (err, data) {
if (err) throw err;
var lines = data.split('\r\n');
var result = lines.map(function (line) {
return line.split(',');
});
callback(result);
});
}
module.exports = expenses
接下來,建立一個名為server.js的檔案,幷包含列出、新增、更新和刪除支出條目的實際程式碼。
var express = require("express")
var cors = require('cors')
var expenseStore = require("./expensedb.js")
var app = express()
app.use(cors());
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
var HTTP_PORT = 8000
app.listen(HTTP_PORT, () => {
console.log("Server running on port %PORT%".replace("%PORT%", HTTP_PORT))
});
app.get("/", (req, res, next) => {
res.json({ "message": "Ok" })
});
app.get("/api/expenses", (req, res, next) => {
expenseStore.find({}, function (err, docs) {
res.json(docs);
});
});
app.get("/api/expense/:id", (req, res, next) => {
var id = req.params.id;
expenseStore.find({ _id: id }, function (err, docs) {
res.json(docs);
})
});
app.post("/api/expense/", (req, res, next) => {
var errors = []
if (!req.body.item) {
errors.push("No item specified");
}
var data = {
name: req.body.name,
amount: req.body.amount,
category: req.body.category,
spend_date: req.body.spend_date,
}
expenseStore.insert(data, function (err, docs) {
return res.json(docs);
});
})
app.put("/api/expense/:id", (req, res, next) => {
var id = req.params.id;
var errors = []
if (!req.body.item) {
errors.push("No item specified");
}
var data = {
_id: id,
name: req.body.name,
amount: req.body.amount,
category: req.body.category,
spend_date: req.body.spend_date,
}
expenseStore.update( { _id: id }, data, function (err, docs) {
return res.json(data);
});
})
app.delete("/api/expense/:id", (req, res, next) => {
var id = req.params.id;
expenseStore.remove({ _id: id }, function (err, numDeleted) {
res.json({ "message": "deleted" })
});
})
app.use(function (req, res) {
res.status(404);
});
現在,是執行應用程式的時候了。
npm run start
接下來,開啟瀏覽器並在位址列中輸入https://:8000/。
{
"message": "Ok"
}
這確認我們的應用程式執行良好。
最後,將URL更改為https://:8000/api/expense並按回車鍵。瀏覽器將以JSON格式顯示初始支出條目。
[
...
{
"name": "Pizza",
"amount": 80,
"spend_date": "2020-10-10T00:00:00.000Z",
"category": "Food",
"_id": "5H8rK8lLGJPVZ3gD"
},
...
]
讓我們在接下來的部分中,透過fetch() API在我們的支出管理器應用程式中使用我們新建立的支出伺服器。
fetch() API
讓我們建立一個新的應用程式來展示React中的客戶端程式設計。
首先,使用Create React App或Rollup打包器建立一個新的React應用程式,名為react-http-app,具體步驟請參考建立React應用程式章節。
接下來,在您喜歡的編輯器中開啟應用程式。
接下來,在應用程式的根目錄下建立 src 資料夾。
接下來,在src資料夾下建立components資料夾。
接下來,在src/components資料夾下建立一個名為ExpenseEntryItemList.css的檔案,幷包含通用的表格樣式。
html {
font-family: sans-serif;
}
table {
border-collapse: collapse;
border: 2px solid rgb(200,200,200);
letter-spacing: 1px;
font-size: 0.8rem;
}
td, th {
border: 1px solid rgb(190,190,190);
padding: 10px 20px;
}
th {
background-color: rgb(235,235,235);
}
td, th {
text-align: left;
}
tr:nth-child(even) td {
background-color: rgb(250,250,250);
}
tr:nth-child(odd) td {
background-color: rgb(245,245,245);
}
caption {
padding: 10px;
}
tr.highlight td {
background-color: #a6a8bd;
}
接下來,在src/components資料夾下建立一個名為ExpenseEntryItemList.js的檔案並開始編輯。
接下來,匯入 React 庫。
import React from 'react';
接下來,建立一個類,ExpenseEntryItemList,並使用props呼叫建構函式。
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
}
}
接下來,在建構函式中用空列表初始化狀態。
this.state = {
isLoaded: false,
items: []
}
接下來,建立一個方法setItems,用於格式化從遠端伺服器接收到的專案,然後將其設定為元件的狀態。
setItems(remoteItems) {
var items = [];
remoteItems.forEach((item) => {
let newItem = {
id: item._id,
name: item.name,
amount: item.amount,
spendDate: item.spend_date,
category: item.category
}
items.push(newItem)
});
this.setState({
isLoaded: true,
items: items
});
}
接下來,新增一個方法fetchRemoteItems,用於從伺服器獲取專案。
fetchRemoteItems() {
fetch("https://:8000/api/expenses")
.then(res => res.json())
.then(
(result) => {
this.setItems(result);
},
(error) => {
this.setState({
isLoaded: false,
error
});
}
)
}
這裡:
fetch API用於從遠端伺服器獲取專案。
setItems用於格式化並將專案儲存在狀態中。
接下來,新增一個方法deleteRemoteItem,用於從遠端伺服器刪除專案。
deleteRemoteItem(id) {
fetch('https://:8000/api/expense/' + id, { method: 'DELETE' })
.then(res => res.json())
.then(
() => {
this.fetchRemoteItems()
}
)
}
這裡:
fetch API用於從遠端伺服器刪除和獲取專案。
setItems再次用於格式化並將專案儲存在狀態中。
接下來,呼叫componentDidMount生命週期API,在元件掛載階段將專案載入到元件中。
componentDidMount() {
this.fetchRemoteItems();
}
接下來,編寫一個事件處理程式以從列表中刪除專案。
handleDelete = (id, e) => {
e.preventDefault();
console.log(id);
this.deleteRemoteItem(id);
}
接下來,編寫render方法。
render() {
let lists = [];
if (this.state.isLoaded) {
lists = this.state.items.map((item) =>
<tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
<td><a href="#" onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
</tr>
);
}
return (
<div>
<table onMouseOver={this.handleMouseOver}>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{lists}
</tbody>
</table>
</div>
);
}
最後,匯出元件。
export default ExpenseEntryItemList;
接下來,在src資料夾下建立一個名為index.js的檔案,並使用ExpenseEntryItemList元件。
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList';
ReactDOM.render(
<React.StrictMode>
<ExpenseEntryItemList />
</React.StrictMode>,
document.getElementById('root')
);
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
接下來,開啟一個新的終端視窗並啟動我們的伺服器應用程式。
cd /go/to/server/application npm start
接下來,使用npm命令服務客戶端應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
嘗試透過點選刪除連結來刪除專案。
ReactJS - 表單程式設計
表單是Web應用程式的常見組成部分,主要用於允許使用者與應用程式互動。這些表單可以包含在網頁上,用於收集使用者資訊、讓使用者搜尋網站、進行支付等等。表單可以包含的基本元素包括輸入欄位、按鈕、複選框、下拉選單等等。從這些表單中獲得的資料通常由React中的元件處理。
要了解如何在React中使用表單,讓我們檢視一些示例。
表單程式設計
與HTML不同的是,HTML表單根據使用者輸入資料進行更新,而React則藉助其狀態更新表單。可變狀態通常在元件的state屬性中指定,並且只能使用setState()進行更新。
表單程式設計的性質需要維護狀態。因為,輸入欄位資訊會隨著使用者與表單互動而改變。但正如我們前面所學,React庫本身不儲存或維護任何狀態資訊,元件必須使用狀態管理API來管理狀態。考慮到這一點,React提供了兩種型別的元件來支援表單程式設計。
受控元件 − 在受控元件中,React為所有輸入元素提供了一個特殊的屬性value,並控制輸入元素。value屬性可用於獲取和設定輸入元素的值。它必須與元件的狀態同步。
非受控元件 − 在非受控元件中,React對錶單程式設計的支援最小。它必須使用Ref概念(另一個React概念,用於在執行時獲取React元件中的DOM元素)來進行表單程式設計。
本章將學習使用受控元件和非受控元件進行表單程式設計。
ReactJS - 受控元件
在受控元件中,React為所有輸入元素提供了一個特殊的屬性value,並控制輸入元素。value屬性可用於獲取和設定輸入元素的值。它必須與元件的狀態同步。
換句話說,渲染表單的React元件也控制在後續使用者輸入中該表單中發生的情況。以這種方式由React控制其值的輸入表單元素稱為“受控元件”。
受控元件必須遵循特定的流程來進行表單程式設計。
帶有單個輸入的受控元件
讓我們檢查一下單個輸入元素需要遵循的分步過程。
步驟 1 − 建立表單元素。
<input type="text" name="username" />
步驟 2 − 為輸入元素建立狀態。
this.state = {
username: ''
}
步驟 3 − 新增value屬性並從狀態中賦值。
<input type="text" name="username" value={this.state.username} />
步驟 4 − 新增onChange屬性並分配一個處理程式方法。
<input type="text" name="username" value={this.state.username} onChange={this.handleUsernameChange} />
步驟 5 − 編寫處理程式方法,並在事件觸發時更新狀態。
handleUsernameChange(e) {
this.setState({
username = e.target.value
});
}
步驟 6 − 在元件的建構函式中繫結事件處理程式。
this.handleUsernameChange = this.handleUsernameChange.bind(this)
最後,在驗證和提交期間,從this.state中使用username獲取輸入值。
handleSubmit(e) {
e.preventDefault();
alert(this.state.username);
}
建立簡單的表單
本章將建立一個簡單的表單,使用受控元件新增支出條目。
步驟 1 − 首先,使用Create React App或Rollup打包器建立一個新的React應用程式,名為react-form-app,具體步驟請參考建立React應用程式章節。
步驟 2 − 在您喜歡的編輯器中開啟應用程式。
下一步,在應用程式的根目錄下建立src資料夾。
接下來,在src資料夾下建立components資料夾。
步驟 3 − 在 src 資料夾下建立一個名為 ExpenseForm.css 的檔案來為元件設定樣式。
input[type=text], input[type=number], input[type=date], select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type=submit] {
width: 100%;
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #45a049;
}
input:focus {
border: 1px solid #d9d5e0;
}
#expenseForm div {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
步驟 4 − 在 src/components 資料夾下建立一個名為 ExpenseForm.js 的檔案並開始編輯。
步驟 5 − 匯入 React 庫。
import React from 'react';
匯入 ExpenseForm.css 檔案。
import './ExpenseForm.css'
步驟 6 − 建立一個名為 ExpenseForm 的類,並使用 props 呼叫建構函式。
class ExpenseForm extends React.Component {
constructor(props) {
super(props);
}
}
初始化元件的狀態。
this.state = {
item: {}
}
建立 render() 方法,並新增一個帶有輸入欄位的表單來新增支出專案。
render() {
return (
<div id="expenseForm">
<form>
<label for="name">Title</label>
<input type="text" id="name" name="name" placeholder="Enter expense title" />
<label for="amount">Amount</label>
<input type="number" id="amount" name="amount" placeholder="Enter expense amount" />
<label for="date">Spend Date</label>
<input type="date" id="date" name="date" placeholder="Enter date" />
<label for="category">Category</label>
<select id="category" name="category"
<option value="">Select</option>
<option value="Food">Food</option>
<option value="Entertainment">Entertainment</option>
<option value="Academic">Academic</option>
</select>
<input type="submit" value="Submit" />
</form>
</div>
)
}
為所有輸入欄位建立事件處理程式,以更新狀態中的支出詳細資訊。
handleNameChange(e) {
this.setState( (state, props) => {
let item = state.item
item.name = e.target.value;
return { item: item }
});
}
handleAmountChange(e) {
this.setState( (state, props) => {
let item = state.item
item.amount = e.target.value;
return { item: item }
});
}
handleDateChange(e) {
this.setState( (state, props) => {
let item = state.item
item.date = e.target.value;
return { item: item }
});
}
handleCategoryChange(e) {
this.setState( (state, props) => {
let item = state.item
item.category = e.target.value;
return { item: item }
});
}
在建構函式中繫結事件處理程式。
this.handleNameChange = this.handleNameChange.bind(this); this.handleAmountChange = this.handleAmountChange.bind(this); this.handleDateChange = this.handleDateChange.bind(this); this.handleCategoryChange = this.handleCategoryChange.bind(this);
接下來,為提交操作新增一個事件處理程式。
onSubmit = (e) => {
e.preventDefault();
alert(JSON.stringify(this.state.item));
}
將事件處理程式附加到表單。
render() {
return (
<div id="expenseForm">
<form onSubmit={(e) => this.onSubmit(e)}>
<label for="name">Title</label>
<input type="text" id="name" name="name" placeholder="Enter expense title"
value={this.state.item.name}
onChange={this.handleNameChange} />
<label for="amount">Amount</label>
<input type="number" id="amount" name="amount" placeholder="Enter expense amount"
value={this.state.item.amount}
onChange={this.handleAmountChange} />
<label for="date">Spend Date</label>
<input type="date" id="date" name="date" placeholder="Enter date"
value={this.state.item.date}
onChange={this.handleDateChange} />
<label for="category">Category</label>
<select id="category" name="category"
value={this.state.item.category}
onChange={this.handleCategoryChange} >
<option value="">Select</option>
<option value="Food">Food</option>
<option value="Entertainment">Entertainment</option>
<option value="Academic">Academic</option>
</select>
<input type="submit" value="Submit" />
</form>
</div>
)
}
最後,匯出元件。
export default ExpenseForm
ExpenseForm 元件的完整程式碼如下所示:
import React from 'react';
import './ExpenseForm.css'
class ExpenseForm extends React.Component {
constructor(props) {
super(props);
this.state = {
item: {}
}
this.handleNameChange = this.handleNameChange.bind(this);
this.handleAmountChange = this.handleAmountChange.bind(this);
this.handleDateChange = this.handleDateChange.bind(this);
this.handleCategoryChange = this.handleCategoryChange.bind(this);
}
handleNameChange(e) {
this.setState( (state, props) => {
let item = state.item
item.name = e.target.value;
return { item: item }
});
}
handleAmountChange(e) {
this.setState( (state, props) => {
let item = state.item
item.amount = e.target.value;
return { item: item }
});
}
handleDateChange(e) {
this.setState( (state, props) => {
let item = state.item
item.date = e.target.value;
return { item: item }
});
}
handleCategoryChange(e) {
this.setState( (state, props) => {
let item = state.item
item.category = e.target.value;
return { item: item }
});
}
onSubmit = (e) => {
e.preventDefault();
alert(JSON.stringify(this.state.item));
}
render() {
return (
<div id="expenseForm">
<form onSubmit={(e) => this.onSubmit(e)}>
<label for="name">Title</label>
<input type="text" id="name" name="name" placeholder="Enter expense title"
value={this.state.item.name}
onChange={this.handleNameChange} />
<label for="amount">Amount</label>
<input type="number" id="amount" name="amount" placeholder="Enter expense amount"
value={this.state.item.amount}
onChange={this.handleAmountChange} />
<label for="date">Spend Date</label>
<input type="date" id="date" name="date" placeholder="Enter date"
value={this.state.item.date}
onChange={this.handleDateChange} />
<label for="category">Category</label>
<select id="category" name="category"
value={this.state.item.category}
onChange={this.handleCategoryChange} >
<option value="">Select</option>
<option value="Food">Food</option>
<option value="Entertainment">Entertainment</option>
<option value="Academic">Academic</option>
</select>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
export default ExpenseForm;
index.js −
接下來,在 src 資料夾下建立一個名為 index.js 的檔案,並使用 ExpenseForm 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseForm from './components/ExpenseForm'
ReactDOM.render(
<React.StrictMode>
<ExpenseForm />
</React.StrictMode>,
document.getElementById('root')
);
index.html −
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
最後,輸入示例支出詳細資訊並單擊提交。提交的資料將被收集並在彈出訊息框中顯示。
ReactJS - 非受控元件
正如我們之前瞭解到的,不受控元件不支援基於 React 的表單程式設計。如果不使用 React api,則無法獲取 React DOM 元素(表單元素)的值。獲取 React 元件內容的一種方法是使用 React 的 ref 功能。
React 為其所有 DOM 元素提供了一個 ref 屬性,並提供了一個相應的 api,React.createRef() 用於建立一個新的引用 (this.ref)。新建立的引用可以附加到表單元素,並且可以在需要時(在驗證和提交期間)使用 this.ref.current.value 訪問附加的表單元素的值。
不受控元件中的表單程式設計
讓我們看看在不受控元件中進行表單程式設計的分步過程。
步驟 1 − 建立一個引用。
this.inputRef = React.createRef();
步驟 2 − 建立一個表單元素。
<input type="text" name="username" />
步驟 3 − 將已建立的引用附加到表單元素。
<input type="text" name="username" ref={this.inputRef} />
要設定輸入元素的預設值,請使用 defaultValue 屬性而不是 value 屬性。如果使用 value,它將在元件的渲染階段更新。
<input type="text" name="username" ref={this.inputRef} defaultValue="default value" />
最後,在驗證和提交期間使用 this.inputRef.current.value 獲取輸入值。
handleSubmit(e) {
e.preventDefault();
alert(this.inputRef.current.value);
}
建立簡單的表單
在本節中,讓我們建立一個簡單的表單,使用不受控元件新增支出條目。
步驟 1 − 首先,按照《建立 React 應用程式》一章中的說明,使用 Create React App 或 Rollup 打包器建立一個新的 React 應用程式 react-form-uncontrolled-app。
步驟 2 − 在您喜歡的編輯器中開啟應用程式。
在應用程式的根目錄下建立 src 資料夾。
在 src 資料夾下建立 components 資料夾。
步驟 3 − 在 src 資料夾下建立一個名為 ExpenseForm.css 的檔案來為元件設定樣式。
input[type=text], input[type=number], input[type=date], select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type=submit] {
width: 100%;
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #45a049;
}
input:focus {
border: 1px solid #d9d5e0;
}
#expenseForm div {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
步驟 4 − 在 src/components 資料夾下建立一個名為 ExpenseForm.js 的檔案並開始編輯。
步驟 5 − 匯入 React 庫。
import React from 'react';
匯入 ExpenseForm.css 檔案。
import './ExpenseForm.css'
建立一個名為 ExpenseForm 的類,並使用 props 呼叫建構函式。
class ExpenseForm extends React.Component {
constructor(props) {
super(props);
}
}
為所有輸入欄位建立 React 引用。
this.nameInputRef = React.createRef(); this.amountInputRef = React.createRef(); this.dateInputRef = React.createRef(); this.categoryInputRef = React.createRef();
建立 render() 方法,並新增一個帶有輸入欄位的表單來新增支出專案。
render() {
return (
<div id="expenseForm">
<form>
<label for="name">Title</label>
<input type="text" id="name" name="name" placeholder="Enter expense title" />
<label for="amount">Amount</label>
<input type="number" id="amount" name="amount" placeholder="Enter expense amount" />
<label for="date">Spend Date</label>
<input type="date" id="date" name="date" placeholder="Enter date" />
<label for="category">Category</label>
<select id="category" name="category" >
<option value="">Select</option>
<option value="Food">Food</option>
<option value="Entertainment">Entertainment</option>
<option value="Academic">Academic</option>
</select>
<input type="submit" value="Submit" />
</form>
</div>
)
}
為提交操作新增一個事件處理程式。
onSubmit = (e) => {
e.preventDefault();
let item = {};
item.name = this.nameInputRef.current.value;
item.amount = this.amountInputRef.current.value;
item.date = this.dateInputRef.current.value;
item.category = this.categoryInputRef.current.value;
alert(JSON.stringify(item));
}
將事件處理程式附加到表單。
render() {
return (
<div id="expenseForm">
<form onSubmit={(e) => this.onSubmit(e)}>
<label for="name">Title</label>
<input type="text" id="name" name="name" placeholder="Enter expense title"
ref={this.nameInputRef} />
<label for="amount">Amount</label>
<input type="number" id="amount" name="amount" placeholder="Enter expense amount"
ref={this.amountInputRef} />
<label for="date">Spend Date</label>
<input type="date" id="date" name="date" placeholder="Enter date"
ref={this.dateInputRef} />
<label for="category">Category</label>
<select id="category" name="category"
ref={this.categoryInputRef} >
<option value="">Select</option>
<option value="Food">Food</option>
<option value="Entertainment">Entertainment</option>
<option value="Academic">Academic</option>
</select>
<input type="submit" value="Submit" />
</form>
</div>
)
}
最後,匯出元件。
export default ExpenseForm
ExpenseForm 元件的完整程式碼如下所示
import React from 'react';
import './ExpenseForm.css'
class ExpenseForm extends React.Component {
constructor(props) {
super(props);
this.nameInputRef = React.createRef();
this.amountInputRef = React.createRef();
this.dateInputRef = React.createRef();
this.categoryInputRef = React.createRef();
}
onSubmit = (e) => {
e.preventDefault();
let item = {};
item.name = this.nameInputRef.current.value;
item.amount = this.amountInputRef.current.value;
item.date = this.dateInputRef.current.value;
item.category = this.categoryInputRef.current.value;
alert(JSON.stringify(item));
}
render() {
return (
<div id="expenseForm">
<form onSubmit={(e) => this.onSubmit(e)}>
<label for="name">Title</label>
<input type="text" id="name" name="name" placeholder="Enter expense title"
ref={this.nameInputRef} />
<label for="amount">Amount</label>
<input type="number" id="amount" name="amount" placeholder="Enter expense amount"
ref={this.amountInputRef} />
<label for="date">Spend Date</label>
<input type="date" id="date" name="date" placeholder="Enter date"
ref={this.dateInputRef} />
<label for="category">Category</label>
<select id="category" name="category"
ref={this.categoryInputRef} >
<option value="">Select</option>
<option value="Food">Food</option>
<option value="Entertainment">Entertainment</option>
<option value="Academic">Academic</option>
</select>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
export default ExpenseForm;
index.js
接下來,在 src 資料夾下建立一個名為 index.js 的檔案,並使用 ExpenseForm 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseForm from './components/ExpenseForm'
ReactDOM.render(
<React.StrictMode>
<ExpenseForm />
</React.StrictMode>,
document.getElementById('root')
);
index.html
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
最後,輸入示例支出詳細資訊並單擊提交。提交的資料將被收集並在彈出訊息框中顯示。
ReactJS - Formik
Formik 是一個第三方的 React 表單庫。它提供基本的表單程式設計和驗證功能。它基於受控元件,大大減少了表單程式設計的時間。
表單程式設計的性質需要維護狀態。因為,輸入欄位資訊會隨著使用者與表單互動而改變。但正如我們前面所學,React庫本身不儲存或維護任何狀態資訊,元件必須使用狀態管理API來管理狀態。考慮到這一點,React提供了兩種型別的元件來支援表單程式設計。
使用 Formik 的支出表單
在本節中,讓我們使用 Formik 庫重新建立支出表單。
步驟 1 − 首先,按照《建立 React 應用程式》一章中的說明,使用 Create React App 或 Rollup 打包器建立一個新的 React 應用程式 react-formik-app。
步驟 2 − 安裝 Formik 庫。
cd /go/to/workspace npm install formik --save
步驟 3 − 在您喜歡的編輯器中開啟應用程式。
在應用程式的根目錄下建立 src 資料夾。
在 src 資料夾下建立 components 資料夾。
步驟 4 − 在 src 資料夾下建立一個名為 ExpenseForm.css 的檔案來為元件設定樣式。
input[type=text], input[type=number], input[type=date], select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type=submit] {
width: 100%;
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #45a049;
}
input:focus {
border: 1px solid #d9d5e0;
}
#expenseForm div {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
#expenseForm span {
color: red;
}
步驟 5 − 在 src/components 資料夾下建立另一個名為 ExpenseForm.js 的檔案並開始編輯。
匯入 React 和 Formik 庫。
import React from 'react';
import { Formik } from 'formik';
接下來,匯入 ExpenseForm.css 檔案。
import './ExpenseForm.css'
接下來,建立 ExpenseForm 類。
class ExpenseForm extends React.Component {
constructor(props) {
super(props);
}
}
在建構函式中設定支出專案的初始值。
this.initialValues = { name: '', amount: '', date: '', category: '' }
接下來,建立一個驗證方法。Formik 將傳送使用者輸入的當前值。
validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = 'Required';
}
if (!values.amount) {
errors.amount = 'Required';
}
if (!values.date) {
errors.date = 'Required';
}
if (!values.category) {
errors.category = 'Required';
}
return errors;
}
建立一個提交表單的方法。Formik 將傳送使用者輸入的當前值。
handleSubmit = (values, setSubmitting) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}
建立 render() 方法。使用 Formik 提供的 handleChange、handleBlur 和 handleSubmit 方法作為輸入元素的事件處理程式。
render() {
return (
<div id="expenseForm">
<Formik
initialValues={this.initialValues}
validate={values => this.validate(values)}
onSubmit={(values, { setSubmitting }) => this.handleSubmit(values, setSubmitting)} >{
({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
/* and other goodies */
})
=> (
<form onSubmit={handleSubmit}>
<label for="name">Title <span>{errors.name && touched.name && errors.name}</span></label>
<input type="text" id="name" name="name" placeholder="Enter expense title"
onChange={handleChange}
onBlur={handleBlur}
value={values.name} />
<label for="amount">Amount <span>{errors.amount && touched.amount && errors.amount}</span></label>
<input type="number" id="amount" name="amount" placeholder="Enter expense amount"
onChange={handleChange}
onBlur={handleBlur}
value={values.amount} />
<label for="date">Spend Date <span>{errors.date && touched.date && errors.date}</span></label>
<input type="date" id="date" name="date" placeholder="Enter date"
onChange={handleChange}
onBlur={handleBlur}
value={values.date} />
<label for="category">Category <span>{errors.category && touched.category && errors.category}</span></label>
<select id="category" name="category"
onChange={handleChange}
onBlur={handleBlur}
value={values.category}>
<option value="">Select</option>
<option value="Food">Food</option>
<option value="Entertainment">Entertainment</option>
<option value="Academic">Academic</option>
</select>
<input type="submit" value="Submit" disabled={isSubmitting} />
</form>
)
}
</Formik>
</div>
)
}
最後,匯出元件。
export default ExpenseForm
ExpenseForm 元件的完整程式碼如下所示。
import React from 'react';
import './ExpenseForm.css'
import { Formik } from 'formik';
class ExpenseFormik extends React.Component {
constructor(props) {
super(props);
this.initialValues = { name: '', amount: '', date: '', category: '' }
}
validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = 'Required';
}
if (!values.amount) {
errors.amount = 'Required';
}
if (!values.date) {
errors.date = 'Required';
}
if (!values.category) {
errors.category = 'Required';
}
return errors;
}
handleSubmit = (values, setSubmitting) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}
render() {
return (
<div id="expenseForm">
<Formik
initialValues={this.initialValues}
validate={values => this.validate(values)}
onSubmit={(values, { setSubmitting }) => this.handleSubmit(values, setSubmitting)} >
{
({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
/* and other goodies */
}) =>
(
<form onSubmit={handleSubmit}>
<label for="name">Title <span>{errors.name && touched.name && errors.name}</span></label>
<input type="text" id="name" name="name" placeholder="Enter expense title"
onChange={handleChange}
onBlur={handleBlur}
value={values.name} />
<label for="amount">Amount <span>{errors.amount && touched.amount && errors.amount}</span></label>
<input type="number" id="amount" name="amount" placeholder="Enter expense amount"
onChange={handleChange}
onBlur={handleBlur}
value={values.amount} />
<label for="date">Spend Date <span>{errors.date && touched.date && errors.date}</span></label>
<input type="date" id="date" name="date" placeholder="Enter date"
onChange={handleChange}
onBlur={handleBlur}
value={values.date} />
<label for="category">Category <span>{errors.category && touched.category && errors.category}</span></label>
<select id="category" name="category"
onChange={handleChange}
onBlur={handleBlur}
value={values.category}>
<option value="">Select</option>
<option value="Food">Food</option>
<option value="Entertainment">Entertainment</option>
<option value="Academic">Academic</option>
</select>
<input type="submit" value="Submit" disabled={isSubmitting} />
</form>
)
}
</Formik>
</div>
)
}
}
export default ExpenseForm;
index.js
在 src 資料夾下建立一個名為 index.js 的檔案,並使用 ExpenseForm 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseForm from './components/ExpenseForm'
ReactDOM.render(
<React.StrictMode>
<ExpenseForm />
</React.StrictMode>,
document.getElementById('root')
);
index.html
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
使用 npm 命令啟動應用程式。
npm start
開啟瀏覽器,在位址列輸入 https://:3000 並按回車鍵。
最後,輸入示例支出詳細資訊並單擊提交。提交的資料將被收集並在彈出訊息框中顯示。
表單的互動式版本如下所示:
ReactJS - 條件渲染
React 中的條件渲染
條件渲染用於根據情況向用戶顯示/隱藏 UI 的特定部分。例如,如果使用者未登入 Web 應用程式,則 Web 應用程式將顯示登入按鈕。當用戶登入到 Web 應用程式時,相同的連結將被歡迎訊息替換。
讓我們學習 React 提供的支援條件渲染的選項。
條件渲染的方法
React 提供多種方法在 Web 應用程式中進行條件渲染。它們如下所示:
條件語句
JSX/UI 變數
JSX 中的邏輯 && 運算子
JSX 中的條件運算子
null 返回值
條件語句
條件語句是根據條件渲染 UI 的簡單直接的方法。讓我們考慮一下,需求是編寫一個元件,該元件將根據使用者的登入狀態顯示登入連結或歡迎訊息。以下是使用條件渲染實現元件的方法:
function Welcome(props) {
if(props.isLoggedIn) {
return <div>Welcome, {props.userName}</div>
} else {
return <div><a href="/login">Login</a></div>
}
}
這裡:
使用 isLoggedIn props 檢查使用者是否已登入。
如果使用者已登入,則返回歡迎訊息。
如果使用者未登入,則返回登入連結。
元件可以使用如下所示的 C− 方法。
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Welcome isLoggedIn={true} userName={'John'} />
</div>
</div>
</div>
);
}
元件將呈現如下所示的歡迎訊息:
JSX/UI 變數
React 允許將 JSX 元素儲存到變數中,並在需要時使用它。開發人員可以透過條件語句建立必要的 JSX 並將其儲存在變數中。一旦 UI 儲存在變數中,就可以按如下所示渲染它:
function Welcome(props) {
let output = null;
if(props.isLoggedIn) {
output = <div>Welcome, {props.userName}</div>
} else {
output = <div><a href="/login">Login</a></div>
}
return output
}
在這裡,我們使用變數 output 來儲存 UI。元件可以使用如下所示的方法。
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Welcome isLoggedIn={true} userName={'John'} />
</div>
</div>
</div>
);
}
元件將呈現如下所示的歡迎訊息:
邏輯 && 運算子
React 允許在 JSX 程式碼中使用任何表示式。在 Javascript 中,條件從左到右應用。如果最左邊的條件為假,則不會處理下一個條件。開發人員可以利用此功能並按如下所示在 JSX 本身中輸出訊息:
function ShowUsers(props) {
return (
<div>
<ul>
{props.users && props.users.length > 0 &&
props.users.map((item) =>
(
<li>{item}</li>
)
)}
</ul>
</div>
);
}
export default ShowUsers;
這裡:
首先,將檢查 props.users 是否可用。如果 props.users 為 null,則不會進一步處理該條件。
一旦 props.users 可用,則將檢查陣列的長度,並且只有當長度大於零時,才會進一步處理該條件。
最後,將透過 map 函式遍歷 props.users,並將使用者資訊渲染為無序列表。
元件可以使用如下所示:
function App() {
const users = ['John', 'Peter']
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<ShowUsers users={users} />
</div>
</div>
</div>
);
}
元件將按如下所示渲染使用者:
JSX 中的條件運算子
由於 React 允許在 JSX 中使用任何 javascript 表示式,因此開發人員可以在 JSX 中使用條件運算子 (a =b ? x : y) 並僅按如下所示渲染必要的 UI 元素:
function Welcome(props) {
if(props.isLoggedIn) {
return props.isLoggedIn ?
<div>Welcome, {props.userName}</div> : <div><a href="/login">Login</a></div>
}
}
在這裡,我們使用條件運算子來顯示歡迎訊息或登入連結。元件可以使用如下所示的方法。
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Welcome isLoggedIn={true} userName={'John'} />
</div>
</div>
</div>
);
}
元件將呈現如下所示的歡迎訊息:
null 返回值
只有當元件返回 UI 元素時,React 才會渲染元件。否則,它將靜默地跳過渲染,沒有任何錯誤訊息。開發人員可以利用此功能,僅在滿足條件時才渲染某些 UI。
function Welcome(props) {
return props.isLoggedIn ? <div>Welcome, {props.userName}</div> : null
}
這裡:
我們使用條件運算子來顯示/隱藏歡迎訊息。
null 不渲染任何 UI
元件可以使用如下所示:
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<div>Welcome component will not output any content</div>
<Welcome isLoggedIn={false} />
</div>
</div>
</div>
</div>
);
}
元件將呈現如下所示的歡迎訊息:
總結
React 提供多種方法來有條件地渲染 UI 元素。開發人員必須透過分析情況來選擇方法。
ReactJS - 列表
列表和 For 迴圈
React 中最常見的模式是將專案的集合轉換為 React 元素。JavaScript 有很多操作集合的選項。讓我們在本節中看看如何使用 for 迴圈使用集合。
for 迴圈
簡單易用的解決方案是經過時間考驗的 for 迴圈,它可以遍歷集合並使用 JSX 表示式建立最終的 React 元素。讓我們建立一個 React 應用程式並嘗試應用 for 迴圈。
使用 create-react-app 建立一個新應用程式並啟動應用程式。
create-react-app myapp cd myapp npm start
接下來,在 components 資料夾下建立一個元件 ExpenseListUsingForLoop (src/components/ExpenseListUsingForLoop.js)
import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
render() {
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th></th>
</tr>
</tfoot>
</table>
}
}
export default ExpenseListUsingForLoop
在這裡,我們建立了一個帶有標題和頁尾的基本表結構。
接下來,建立一個函式來查詢總支出金額。我們稍後將在 render() 方法中使用它。
getTotalExpenses() {
var items = this.props['expenses'];
var total = 0;
for(let i = 0; i < items.length; i++) {
total += parseInt(items[i]);
}
return total;
}
在這裡,getTotalExpenses 遍歷支出 props 並彙總總支出。然後,在 render 方法中新增支出專案和總金額。
render() {
var items = this.props['expenses'];
var expenses = []
for(let i = 0; i < items.length; i++) {
expenses.push(<tr><td>item {i + 1}</td><td>{items[i]}</td></tr>)
}
var total = this.getTotalExpenses();
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{expenses}
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th>{total}</th>
</tr>
</tfoot>
</table>
}
這裡:
使用 for 迴圈遍歷 expense 陣列中的每個專案,使用 JSX 生成錶行 (tr),最後將其推入 expenses 陣列。
我們在 JSX 表示式中使用了 expenses 陣列來包含生成的 rows。
getTotalExpenses 方法用於查詢總支出金額並將其新增到 render 方法中。
ExpenseListUsingForLoop 元件的完整原始碼如下所示:
import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
getTotalExpenses() {
var items = this.props['expenses'];
var total = 0;
for(let i = 0; i < items.length; i++) {
total += parseInt(items[i]);
}
return total;
}
render() {
var items = this.props['expenses'];
var expenses = []
for(let i = 0; i < items.length; i++) {
expenses.push(<tr><td>item {i + 1}</td><td>{items[i]}</td></tr>)
}
var total = this.getTotalExpenses();
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{expenses}
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th>{total}</th>
</tr>
</tfoot>
</table>
}
}
export default ExpenseListUsingForLoop
接下來,使用 ExpenseListUsingForLoop 元件更新 App 元件 (App.js)。
import ExpenseListUsingForLoop from './components/ExpenseListUsingForLoop';
import './App.css';
function App() {
var expenses = [100, 200, 300]
return (
<div>
<ExpenseListUsingForLoop expenses={expenses} />
</div>
);
}
export default App;
接下來,在 App.css 中新增基本樣式。
/* Center tables for demo */
table {
margin: 0 auto;
}
div {
padding: 5px;
}
/* Default Table Style */
table {
color: #333;
background: white;
border: 1px solid grey;
font-size: 12pt;
border-collapse: collapse;
}
table thead th,
table tfoot th {
color: #777;
background: rgba(0,0,0,.1);
text-align: left;
}
table caption {
padding:.5em;
}
table th,
table td {
padding: .5em;
border: 1px solid lightgrey;
}
最後,在瀏覽器中檢查應用程式。它將按如下所示顯示支出:
ECMASCript 6 for 迴圈
讓我們更改應用程式並使用 ECMAScript 6 中引入的 for .. of 迴圈。
for(const [idx, item] of items.entries()) {
expenses.push(<tr><td>item {idx + 1}</td><td>{item}</td></tr>)
}
這裡:
idx 指的是陣列的索引。
item 指的是陣列中每個位置的支出專案。
entries 方法解析陣列並將其返回為鍵值對陣列。鍵指的是陣列的索引,值指的是陣列中對應鍵的值。
如果我們不需要索引,那麼我們可以跳過entries() 方法,如下所示:
for(const item of items) {
expenses.push(<tr><td></td><td>{item}</td></tr>)
}
ReactJS - Keys
列表和鍵
在前面的章節中,我們學習瞭如何在 React 中使用 for 迴圈和 map 函式使用集合。如果我們執行應用程式,它將按預期輸出。如果我們在瀏覽器中開啟開發者控制檯,則會顯示如下警告:
Warning: Each child in a list should have a unique "key" prop. Check the render method of `ExpenseListUsingForLoop`. See https://react.com.tw/link/warning-keys for more information. tr ExpenseListUsingForLoop@ div App
那麼,這是什麼意思,它如何影響我們的 React 應用程式?眾所周知,React 嘗試透過各種機制僅渲染 DOM 中已更新的值。當 React 渲染集合時,它嘗試透過僅更新列表中已更新的項來最佳化渲染。
但是,React 沒有提示來查詢哪些項是新的、已更新的或已刪除的。為了獲取資訊,React 允許所有元件使用 key 屬性。唯一的要求是 key 的值在當前集合中必須唯一。
讓我們重新建立我們之前的應用程式之一併應用 key 屬性。
create-react-app myapp cd myapp npm start
接下來,在 components 資料夾下(src/components/ExpenseListUsingForLoop.js)建立一個元件,ExpenseListUsingForLoop。
import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
render() {
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th></th>
</tr>
</tfoot>
</table>
}
}
export default ExpenseListUsingForLoop
在這裡,我們建立了一個帶有表頭和表尾的基本表格結構。然後,建立一個函式來查詢總支出金額。我們稍後將在 render 方法中使用它。
getTotalExpenses() {
var items = this.props['expenses'];
var total = 0;
for(let i = 0; i < items.length; i++) {
total += parseInt(items[i]);
}
return total;
}
在這裡,getTotalExpenses 遍歷 expense props 並彙總總支出。然後,在 render 方法中新增支出項和總金額。
render() {
var items = this.props['expenses'];
var expenses = []
expenses = items.map((item, idx) => <tr key={idx}><td>item {idx + 1}</td><td>{item}</td></tr>)
var total = this.getTotalExpenses();
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{expenses}
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th>{total}</th>
</tr>
</tfoot>
</table>
}
這裡:
使用map 函式遍歷 expense 陣列中的每個項,使用轉換函式為每個條目建立表格行 (tr),最後將返回的陣列設定在expenses 變數中。
為每一行設定 key 屬性,其值為專案的索引值。
在 JSX 表示式中使用expenses 陣列來包含生成的 rows。
使用getTotalExpenses 方法查詢總支出金額並將其新增到 render 方法中。
ExpenseListUsingForLoop 元件的完整原始碼如下:
import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
getTotalExpenses() {
var items = this.props['expenses'];
var total = 0;
for(let i = 0; i < items.length; i++) {
total += parseInt(items[i]);
}
return total;
}
render() {
var items = this.props['expenses'];
var expenses = []
expenses = items.map(
(item, idx) => <tr key={idx}><td>item {idx + 1}</td><td>{item}</td></tr>)
var total = this.getTotalExpenses();
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{expenses}
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th>{total}</th>
</tr>
</tfoot>
</table>
}
}
export default ExpenseListUsingForLoop
接下來,使用 ExpenseListUsingForLoop 元件更新 App 元件 (App.js)。
import ExpenseListUsingForLoop from './components/ExpenseListUsingForLoop';
import './App.css';
function App() {
var expenses = [100, 200, 300]
return (
<div>
<ExpenseListUsingForLoop expenses={expenses} />
</div>
);
}
export default App;
接下來,在App.css中新增基本樣式。
/* Center tables for demo */
table {
margin: 0 auto;
}
div {
padding: 5px;
}
/* Default Table Style */
table {
color: #333;
background: white;
border: 1px solid grey;
font-size: 12pt;
border-collapse: collapse;
}
table thead th,
table tfoot th {
color: #777;
background: rgba(0,0,0,.1);
text-align: left;
}
table caption {
padding:.5em;
}
table th,
table td {
padding: .5em;
border: 1px solid lightgrey;
}
接下來,在瀏覽器中檢查應用程式。它將顯示如下所示的支出:
最後,開啟開發者控制檯,發現沒有顯示關於 key 的警告。
鍵和索引
我們瞭解到 key 應該唯一,以最佳化元件的渲染。我們使用了索引值,錯誤消失了。這仍然是為列表提供值的正確方法嗎?答案是肯定的和否定的。設定索引鍵在大多數情況下都有效,但在應用程式中使用非受控元件時,其行為會出乎意料。
讓我們更新我們的應用程式並新增如下所示的兩個新功能:
在支出金額旁邊新增到每一行的輸入元素。
新增一個按鈕以刪除列表中的第一個元素。
首先,新增一個建構函式並設定應用程式的初始狀態。由於我們將在應用程式的執行時刪除某些項,因此我們應該使用狀態而不是 props。
constructor(props) {
super(props)
this.state = {
expenses: this.props['expenses']
}
}
接下來,新增一個函式以刪除列表的第一個元素。
remove() {
var itemToRemove = this.state['expenses'][0]
this.setState((previousState) => ({
expenses: previousState['expenses'].filter((item) => item != itemToRemove)
}))
}
接下來,在建構函式中繫結 remove 函式,如下所示:
constructor(props) {
super(props)
this.state = {
expenses: this.props['expenses']
}
this.remove = this.remove.bind(this)
}
接下來,在表格下方新增一個按鈕,並在其 onClick 操作中設定 remove 函式。
render() {
var items = this.state['expenses'];
var expenses = []
expenses = items.map(
(item, idx) => <tr key={idx}><td>item {idx + 1}</td><td>{item}</td></tr>)
var total = this.getTotalExpenses();
return (
<div>
<table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{expenses}
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th>{total}</th>
</tr>
</tfoot>
</table>
<div>
<button onClick={this.remove}>Remove first item</button>
</div>
</div>
)
}
接下來,在所有行中支出金額旁邊新增一個輸入元素,如下所示:
expenses = items.map((item, idx) => <tr key={idx}><td>item {idx + 1}</td><td>{item} <input /></td></tr>)
接下來,在瀏覽器中開啟應用程式。應用程式將按如下所示渲染:
接下來,在第一個輸入框中輸入一個金額(例如,100),然後單擊“刪除第一個專案”按鈕。這將刪除第一個元素,但輸入的金額將填充到第二個元素旁邊的輸入框中(金額:200),如下所示:
為了解決這個問題,我們應該刪除使用索引作為 key 的方法。相反,我們可以使用表示專案的唯一 ID。讓我們將專案從數字陣列更改為物件陣列,如下所示 (App.js),
import ExpenseListUsingForLoop from './components/ExpenseListUsingForLoop';
import './App.css';
function App() {
var expenses = [
{
id: 1,
amount: 100
},
{
id: 2,
amount: 200
},
{
id: 3,
amount: 300
}
]
return (
<div>
<ExpenseListUsingForLoop expenses={expenses} />
</div>
);
}
export default App;
接下來,透過將item.id 作為 key 來更新渲染邏輯,如下所示:
expenses = items.map((item, idx) => <tr key={item.id}><td>{item.id}</td><td>{item.amount} <input /></td></tr>)
接下來,更新getTotalExpenses 邏輯,如下所示:
getTotalExpenses() {
var items = this.props['expenses'];
var total = 0;
for(let i = 0; i < items.length; i++) {
total += parseInt(items[i].amount);
}
return total;
}
在這裡,我們更改了從物件 (item[i].amount) 獲取金額的邏輯。最後,在瀏覽器中開啟應用程式並嘗試複製之前的錯誤。在第一個輸入框中新增一個數字(例如 100),然後單擊刪除第一個專案。現在,第一個元素被刪除,並且在下一個輸入框中也不會保留輸入的值,如下所示:
ReactJS - 路由
在 Web 應用程式中,路由是將 Web URL 繫結到 Web 應用程式中特定資源的過程。在 React 中,它是將 URL 繫結到元件。React 本身並不支援路由,因為它基本上是一個使用者介面庫。React 社群提供了許多第三方元件來處理 React 應用程式中的路由。讓我們學習 React Router,這是 React 應用程式的首選路由庫。
安裝 React Router
讓我們學習如何在我們的支出管理器應用程式中安裝React Router 元件。
開啟命令提示符並轉到應用程式的根資料夾。
cd /go/to/expense/manager
使用以下命令安裝 react router。
npm install react-router-dom --save
React Router
React router 提供四個元件來管理 React 應用程式中的導航。
Router − Router 是頂級元件。它包含整個應用程式。
Link − 類似於 html 中的錨標記。它設定目標 url 和參考文字。
<Link to="/">Home</Link>
這裡,to 屬性用於設定目標 url。
Route − 將目標 url 對映到元件。
巢狀路由
React router 也支援巢狀路由。讓我們使用以下示例來理解巢狀路由以建立一個應用程式:
Home.jsx
import React from "react";
function Home() {
return (
<div className="Home">
<h1>This is Home</h1>
</div>
);
}
export default Home;
About.jsx
import React from "react";
function About() {
return (
<div className="About">
<h1>AboutUs</h1>
<p>tutorialspoint India</p>
</div>
);
}
export default About;
Contact.jsx
import React from "react";
function Contact() {
return (
<div className="Contact">
<h1>Contact-Us</h1>
<p>
Tutorials Point India Private Limited, 4th Floor, Incor9 Building, Plot
No: 283/A, Kavuri Hills, Madhapur, Hyderabad, Telangana, INDIA-500081
</p>
</div>
);
}
export default Contact;
建立導航
讓我們介紹一下我們在上面建立的元件之間的導航。應用程式的最小螢幕如下所示:
主頁螢幕− 應用程式的登入或初始螢幕
關於− 顯示應用程式的描述
聯絡− 包含聯絡資訊
Navigate.jsx 檔案的以下完整程式碼將包含從一個元件到另一個元件的連結。它將建立從登入頁面到其他元件的連結。
Navigate.jsx
import React from "react";
import { Outlet, Link } from "react-router-dom";
function Navigate() {
return (
<div>
<ul style={{ listStyle: "none" }}>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About-Us</Link>
</li>
<li>
<Link to="/contact">Contact-Us</Link>
</li>
</ul>
<Outlet />
</div>
);
}
export default Navigate;
接下來,在src/components 資料夾下建立一個檔案App.js 並開始編輯。App 元件的目的是在一個元件中處理所有螢幕。它將配置路由並啟用導航到所有其他元件。
我們將 React 庫和應用程式的其他元件匯入到 App.jsx。在最新版本的 React 中,我們不再使用 Switch,而是隻使用<Route> 標記。這就是巢狀路由發生的地方。
App.jsx
import { Route, Routes, BrowserRouter } from "react-router-dom";
import "./App.css"
import Home from "./Router/Home";
import About from "./Router/About";
import Contact from "./Router/Contact";
import Navigate from "./Router/Navigate";
function App() {
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate />}>
<Route index element={<Home />} />
<Route path="About" element={<About />} />
<Route path="Contact" element={<Contact />} />
</Route>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
嘗試導航連結並確認路由是否有效。
React Router 的優點
以下是 React Routing 的優點列表:
如果渲染的資料量較少,則元件之間的路由速度會更快。
在不同元件之間切換時,實現動畫和過渡變得更容易。這提供了更好的使用者體驗。
允許在不重新整理頁面的情況下進行導航,因為它允許單頁 Web 或移動應用程式。
ReactJS - Redux
React redux 是一個用於 React 的高階狀態管理庫。正如我們前面瞭解到的,React 只支援元件級狀態管理。在一個大型且複雜的應用程式中,使用了大量的元件。React 建議將狀態移動到頂級元件,並使用屬性將狀態傳遞給巢狀元件。這在某種程度上有所幫助,但是當元件增加時,它會變得複雜。
React redux 參與進來,並幫助在應用程式級別維護狀態。React redux 允許任何元件隨時訪問狀態。此外,它允許任何元件隨時更改應用程式的狀態。
讓我們在本節中學習如何使用 React redux 編寫 React 應用程式。
概念
React redux 在一個稱為 Redux store 的地方維護應用程式的狀態。React 元件可以從 store 獲取最新狀態,也可以隨時更改狀態。Redux 提供了一個簡單的過程來獲取和設定應用程式的當前狀態,並涉及以下概念。
Store − 儲存應用程式狀態的中心位置。
Actions − Action 是一個簡單的物件,其中包含要執行的操作型別和執行操作所需的輸入(稱為 payload)。例如,在 store 中新增專案的 action 包含ADD_ITEM 作為型別,以及一個包含專案詳細資訊的物件作為 payload。action 可以表示為:
{
type: 'ADD_ITEM',
payload: { name: '..', ... }
}
Reducers − Reducers 是純函式,用於基於現有狀態和當前 action 建立新狀態。它返回新建立的狀態。例如,在新增專案的情況下,它建立一個新的專案列表,並將專案從狀態和新專案合併,並返回新建立的列表。
Action creators − Action creator 建立一個具有適當操作型別和操作所需資料的 action,並返回該 action。例如,addItem action creator 返回以下物件:
{
type: 'ADD_ITEM',
payload: { name: '..', ... }
}
Component − 元件可以連線到 store 以獲取當前狀態並向 store 分派 action,以便 store 執行 action 並更新其當前狀態。
典型的 redux store 的工作流程可以表示如下。
- React 元件訂閱 store,並在應用程式初始化期間獲取最新狀態。
- 要更改狀態,React 元件會建立必要的 action 並分派該 action。
- Reducer 基於 action 建立新狀態並返回它。Store 使用新狀態更新自身。
- 狀態更改後,store 將更新後的狀態傳送給所有已訂閱的元件。
Redux API
Redux 提供單個 api,connect,它將元件連線到 store,並允許元件獲取和設定 store 的狀態。
connect API 的簽名為:
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
所有引數都是可選的,它返回一個 HOC(高階元件)。高階元件是一個包裝元件並返回新元件的函式。
let hoc = connect(mapStateToProps, mapDispatchToProps) let connectedComponent = hoc(component)
讓我們看看前兩個引數,對於大多數情況來說這已經足夠了。
mapStateToProps − 接受具有以下簽名的函式。
(state, ownProps?) => Object
此處,state 指的是儲存的當前狀態,Object 指的是元件的新 props。每當儲存的狀態更新時,它都會被呼叫。
(state) => { prop1: this.state.anyvalue }
mapDispatchToProps − 接受一個具有以下簽名的函式。
Object | (dispatch, ownProps?) => Object
此處,dispatch 指的是用於在 redux 儲存中分派操作的分派物件,Object 指的是元件的一個或多個分派函式作為 props。
(dispatch) => {
addDispatcher: (dispatch) => dispatch({ type: 'ADD_ITEM', payload: { } }),
removeispatcher: (dispatch) => dispatch({ type: 'REMOVE_ITEM', payload: { } }),
}
Provider 元件
React Redux 提供了一個 Provider 元件,其唯一目的是使 Redux 儲存可用於其所有使用 connect API 連線到儲存的巢狀元件。示例程式碼如下:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { App } from './App'
import createStore from './createReduxStore'
const store = createStore()
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
現在,App 元件內的所有元件都可以使用 connect API 訪問 Redux 儲存。
工作示例
讓我們重新建立我們的支出管理器應用程式,並使用 React Redux 概念來維護應用程式的狀態。
首先,使用 Create React App 或 Rollup 捆綁器建立一個新的 React 應用程式 react-message-app,方法是按照“建立 React 應用程式”章節中的說明進行操作。
接下來,安裝 Redux 和 React Redux 庫。
npm install redux react-redux --save
接下來,安裝 uuid 庫來為新的支出生成唯一識別符號。
npm install uuid --save
接下來,在您喜歡的編輯器中開啟應用程式。
接下來,在應用程式的根目錄下建立 src 資料夾。
接下來,在 src 資料夾下建立一個 actions 資料夾。
接下來,在 src/actions 資料夾下建立一個檔案 types.js 並開始編輯。
接下來,新增兩種操作型別,一種用於新增支出,一種用於刪除支出。
export const ADD_EXPENSE = 'ADD_EXPENSE'; export const DELETE_EXPENSE = 'DELETE_EXPENSE';
接下來,在 src/actions 資料夾下建立一個檔案 index.js 來新增操作並開始編輯。
接下來,匯入 uuid 來建立唯一識別符號。
import { v4 as uuidv4 } from 'uuid';
接下來,匯入操作型別。
import { ADD_EXPENSE, DELETE_EXPENSE } from './types';
接下來,新增一個新函式來返回新增支出的操作型別並匯出它。
export const addExpense = ({ name, amount, spendDate, category }) => ({
type: ADD_EXPENSE,
payload: {
id: uuidv4(),
name,
amount,
spendDate,
category
}
});
在這裡,該函式需要支出物件並返回 ADD_EXPENSE 的操作型別以及支出資訊的有效負載。
接下來,新增一個新函式來返回刪除支出的操作型別並匯出它。
export const deleteExpense = id => ({
type: DELETE_EXPENSE,
payload: {
id
}
});
在這裡,該函式需要要刪除的支出專案的 id,並返回操作型別 'DELETE_EXPENSE' 以及支出 id 的有效負載。
操作的完整原始碼如下:
import { v4 as uuidv4 } from 'uuid';
import { ADD_EXPENSE, DELETE_EXPENSE } from './types';
export const addExpense = ({ name, amount, spendDate, category }) => ({
type: ADD_EXPENSE,
payload: {
id: uuidv4(),
name,
amount,
spendDate,
category
}
});
export const deleteExpense = id => ({
type: DELETE_EXPENSE,
payload: {
id
}
});
接下來,在 src 資料夾下建立一個新的資料夾 reducers。
接下來,在 src/reducers 下建立一個檔案 index.js 來編寫 reducer 函式並開始編輯。
接下來,匯入操作型別。
import { ADD_EXPENSE, DELETE_EXPENSE } from '../actions/types';
接下來,新增一個函式 expensesReducer 來執行在 redux 儲存中新增和更新支出的實際功能。
export default function expensesReducer(state = [], action) {
switch (action.type) {
case ADD_EXPENSE:
return [...state, action.payload];
case DELETE_EXPENSE:
return state.filter(expense => expense.id !== action.payload.id);
default:
return state;
}
}
reducer 的完整原始碼如下:
import { ADD_EXPENSE, DELETE_EXPENSE } from '../actions/types';
export default function expensesReducer(state = [], action) {
switch (action.type) {
case ADD_EXPENSE:
return [...state, action.payload];
case DELETE_EXPENSE:
return state.filter(expense => expense.id !== action.payload.id);
default:
return state;
}
}
在這裡,reducer 檢查操作型別並執行相關程式碼。
接下來,在 src 資料夾下建立 components 資料夾。
接下來,在 src/components 資料夾下建立一個檔案 ExpenseEntryItemList.css 併為 html 表格新增通用樣式。
html {
font-family: sans-serif;
}
table {
border-collapse: collapse;
border: 2px solid rgb(200,200,200);
letter-spacing: 1px;
font-size: 0.8rem;
}
td, th {
border: 1px solid rgb(190,190,190);
padding: 10px 20px;
}
th {
background-color: rgb(235,235,235);
}
td, th {
text-align: left;
}
tr:nth-child(even) td {
background-color: rgb(250,250,250);
}
tr:nth-child(odd) td {
background-color: rgb(245,245,245);
}
caption {
padding: 10px;
}
tr.highlight td {
background-color: #a6a8bd;
}
接下來,在src/components資料夾下建立一個名為ExpenseEntryItemList.js的檔案並開始編輯。
接下來,匯入 React 和 React Redux 庫。
import React from 'react';
import { connect } from 'react-redux';
接下來,匯入 ExpenseEntryItemList.css 檔案。
import './ExpenseEntryItemList.css';
接下來,匯入操作建立者。
import { deleteExpense } from '../actions';
import { addExpense } from '../actions';
接下來,建立一個類 ExpenseEntryItemList 並使用 props 呼叫建構函式。
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
}
}
接下來,建立 mapStateToProps 函式。
const mapStateToProps = state => {
return {
expenses: state
};
};
在這裡,我們將輸入狀態複製到元件的 expenses props。
接下來,建立 mapDispatchToProps 函式。
const mapDispatchToProps = dispatch => {
return {
onAddExpense: expense => {
dispatch(addExpense(expense));
},
onDelete: id => {
dispatch(deleteExpense(id));
}
};
};
在這裡,我們建立了兩個函式,一個用於分派新增支出 (addExpense) 函式,另一個用於分派刪除支出 (deleteExpense) 函式,並將這些函式對映到元件的 props。
接下來,使用 connect api 匯出元件。
export default connect( mapStateToProps, mapDispatchToProps )(ExpenseEntryItemList);
現在,元件獲得了三個新的屬性,如下所示:
expenses − 支出列表
onAddExpense − 用於分派 addExpense 函式的函式
onDelete − 用於分派 deleteExpense 函式的函式
接下來,使用 onAddExpense 屬性在建構函式中向 redux 儲存中新增一些支出。
if (this.props.expenses.length == 0)
{
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
items.forEach((item) => {
this.props.onAddExpense(
{
name: item.name,
amount: item.amount,
spendDate: item.spendDate,
category: item.category
}
);
})
}
接下來,新增一個事件處理程式,使用支出 id 刪除支出項。
handleDelete = (id,e) => {
e.preventDefault();
this.props.onDelete(id);
}
在這裡,事件處理程式呼叫 onDelete 分派器,該分派器呼叫 deleteExpense 以及支出 id。
接下來,新增一個方法來計算所有支出的總金額。
getTotal() {
let total = 0;
for (var i = 0; i < this.props.expenses.length; i++) {
total += this.props.expenses[i].amount
}
return total;
}
接下來,新增 render() 方法並以表格格式列出支出項。
render() {
const lists = this.props.expenses.map(
(item) =>
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
<td><a href="#"
onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
</tr>
);
return (
<div>
<table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{lists}
<tr>
<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
<td colSpan="4" style={{ textAlign: "left" }}>
{this.getTotal()}
</td>
</tr>
</tbody>
</table>
</div>
);
}
在這裡,我們將事件處理程式 handleDelete 設定為從儲存中刪除支出。
ExpenseEntryItemList 元件的完整原始碼如下:
import React from 'react';
import { connect } from 'react-redux';
import './ExpenseEntryItemList.css';
import { deleteExpense } from '../actions';
import { addExpense } from '../actions';
class ExpenseEntryItemList extends React.Component {
constructor(props) {
super(props);
if (this.props.expenses.length == 0){
const items = [
{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
{ id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
{ id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
{ id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
{ id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
{ id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
{ id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
{ id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
{ id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
{ id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
items.forEach((item) => {
this.props.onAddExpense(
{
name: item.name,
amount: item.amount,
spendDate: item.spendDate,
category: item.category
}
);
})
}
}
handleDelete = (id,e) => {
e.preventDefault();
this.props.onDelete(id);
}
getTotal() {
let total = 0;
for (var i = 0; i < this.props.expenses.length; i++) {
total += this.props.expenses[i].amount
}
return total;
}
render() {
const lists = this.props.expenses.map((item) =>
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.amount}</td>
<td>{new Date(item.spendDate).toDateString()}</td>
<td>{item.category}</td>
<td><a href="#"
onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
</tr>
);
return (
<div>
<table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Date</th>
<th>Category</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{lists}
<tr>
<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
<td colSpan="4" style={{ textAlign: "left" }}>
{this.getTotal()}
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
const mapStateToProps = state => {
return {
expenses: state
};
};
const mapDispatchToProps = dispatch => {
return {
onAddExpense: expense => {
dispatch(addExpense(expense));
},
onDelete: id => {
dispatch(deleteExpense(id));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExpenseEntryItemList);
接下來,在 src/components 資料夾下建立一個檔案 App.js 並使用 ExpenseEntryItemList 元件。
import React, { Component } from 'react';
import ExpenseEntryItemList from './ExpenseEntryItemList';
class App extends Component {
render() {
return (
<div>
<ExpenseEntryItemList />
</div>
);
}
}
export default App;
接下來,在 src 資料夾下建立一個檔案 index.js。
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers';
import App from './components/App';
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
這裡:
透過附加我們的 reducer 來使用 createStore 建立一個儲存。
使用 React Redux 庫中的 Provider 元件並將儲存設定為 props,這使得所有巢狀元件都可以使用 connect api 連線到儲存。
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React Containment App</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
單擊刪除連結將從 redux 儲存中刪除該項。
ReactJS - 動畫
動畫是現代 Web 應用程式的一項令人興奮的功能。它為應用程式帶來了煥然一新的感覺。React 社群提供了許多優秀的基於 React 的動畫庫,例如 React Motion、React Reveal、react-animations 等,React 本身也提供了一個動畫庫,早些時候作為附加選項提供了 React Transition Group。它是一個獨立的庫,增強了早期版本的庫。讓我們在本節中學習 React Transition Group 動畫庫。
React Transition Group
React Transition Group 庫是一個簡單的動畫實現。它本身不執行任何動畫。相反,它公開核心動畫相關資訊。每個動畫基本上都是元素從一種狀態到另一種狀態的轉換。該庫公開每個元素的最小可能狀態,如下所示:
- Entering(進入)
- Entered(已進入)
- Exiting(退出)
- Exited(已退出)
該庫提供選項為每個狀態設定 CSS 樣式,並在元素從一種狀態移動到另一種狀態時根據樣式設定動畫。該庫在 props 中提供設定元素當前狀態的選項。如果 in props 值為 true,則表示元素正在從 entering 狀態移動到 exiting 狀態。如果 in props 值為 false,則表示元素正在從 exiting 移動到 exited。
安裝
要安裝此 React Transition Group 庫,請使用以下命令之一:
# npm npm install react-transition-group --save # yarn yarn add react-transition-group
Transition(過渡)
Transition 是 React Transition Group 提供的基本元件,用於設定元素動畫。讓我們建立一個簡單的應用程式,並嘗試使用 Transition 元素淡入/淡出元素。
首先,使用 Create React App 或 Rollup 捆綁器建立一個新的 React 應用程式 react-animation-app,方法是按照“建立 React 應用程式”章節中的說明進行操作。
接下來,安裝 React Transition Group 庫。
cd /go/to/project npm install react-transition-group --save
接下來,在您喜歡的編輯器中開啟應用程式。
接下來,在應用程式的根目錄下建立 src 資料夾。
接下來,在 src 資料夾下建立 components 資料夾。
接下來,在 src/components 資料夾下建立一個檔案 HelloWorld.js 並開始編輯。
接下來,匯入 React 和動畫庫。
import React from 'react';
import { Transition } from 'react-transition-group'
接下來,建立 HelloWorld 元件。
class HelloWorld extends React.Component {
constructor(props) {
super(props);
}
}
接下來,在建構函式中將與轉換相關的樣式定義為 JavaScript 物件。
this.duration = 2000;
this.defaultStyle = {
transition: `opacity ${this.duration}ms ease-in-out`,
opacity: 0,
}
this.transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
exited: { opacity: 0 },
};
這裡:
defaultStyles 設定轉換動畫
transitionStyles 設定各種狀態的樣式
接下來,在建構函式中設定元素的初始狀態。
this.state = {
inProp: true
}
接下來,透過每 3 秒更改一次 inProp 值來模擬動畫。
setInterval(() => {
this.setState((state, props) => {
let newState = {
inProp: !state.inProp
};
return newState;
})
}, 3000);
接下來,建立一個 render 函式。
render() {
return (
);
}
接下來,新增 Transition 元件。將 this.state.inProp 用於 in prop,將 this.duration 用於 timeout prop。Transition 元件需要一個函式,該函式返回使用者介面。它基本上是一個 Render props。
render() {
return (
<Transition in={this.state.inProp} timeout={this.duration}>
{state => ({
... component's user interface.
})
</Transition>
);
}
接下來,將元件使用者介面寫入容器中,併為容器設定 defaultStyle 和 transitionStyles。
render() {
return (
<Transition in={this.state.inProp} timeout={this.duration}>
{state => (
<div style={{
...this.defaultStyle,
...this.transitionStyles[state]
}}>
<h1>Hello World!</h1>
</div>
)}
</Transition>
);
}
最後,公開元件。
export default HelloWorld
元件的完整原始碼如下:
import React from "react";
import { Transition } from 'react-transition-group';
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.duration = 2000;
this.defaultStyle = {
transition: `opacity ${this.duration}ms ease-in-out`,
opacity: 0,
}
this.transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
exited: { opacity: 0 },
};
this.state = {
inProp: true
}
setInterval(() => {
this.setState((state, props) => {
let newState = {
inProp: !state.inProp
};
return newState;
})
}, 3000);
}
render() {
return (
<Transition in={this.state.inProp} timeout={this.duration}>
{state => (
<div style={{
...this.defaultStyle,
...this.transitionStyles[state]
}}>
<h1>Hello World!</h1>
</div>
)}
</Transition>
);
}
}
export default HelloWorld;
接下來,在 src 資料夾下建立一個檔案 index.js 並使用 HelloWorld 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorld from './components/HelloWorld';
ReactDOM.render(
<React.StrictMode
<HelloWorld /
</React.StrictMode ,
document.getElementById('root')
);
最後,在根資料夾下建立一個 public 資料夾並建立一個 index.html 檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React Containment App</title>
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
單擊刪除連結將從 redux 儲存中刪除該項。
要了解有關使用 React Transition Group 設定元素動畫的更多資訊,請單擊此處。
CSSTransition
CSSTransition 構建在 Transition 元件之上,它透過引入 classNames prop 來改進 Transition 元件。classNames prop 指的是用於元素各種狀態的 css 類名。
例如,classNames=hello prop 指的是以下 css 類。
.hello-enter {
opacity: 0;
}
.hello-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.hello-exit {
opacity: 1;
}
.hello-exit-active {
opacity: 0;
transition: opacity 200ms;
}
讓我們使用 CSSTransition 元件建立一個新的元件 HelloWorldCSSTransition。
首先,在您喜歡的編輯器中開啟我們的 react-animation-app 應用程式。
接下來,在 src/components 資料夾下建立一個新檔案 HelloWorldCSSTransition.css 並輸入轉換類。
.hello-enter {
opacity: 1;
transition: opacity 2000ms ease-in-out;
}
.hello-enter-active {
opacity: 1;
transition: opacity 2000ms ease-in-out;
}
.hello-exit {
opacity: 0;
transition: opacity 2000ms ease-in-out;
}
.hello-exit-active {
opacity: 0;
transition: opacity 2000ms ease-in-out;
}
接下來,在 src/components 資料夾下建立一個新檔案 HelloWorldCSSTransition.js 並開始編輯。
接下來,匯入 React 和動畫庫。
import React from 'react';
import { CSSTransition } from 'react-transition-group'
接下來,匯入 HelloWorldCSSTransition.css。
import './HelloWorldCSSTransition.css'
接下來,建立 HelloWorld 元件。
class HelloWorldCSSTransition extends React.Component {
constructor(props) {
super(props);
}
}
接下來,在建構函式中定義轉換的持續時間。
this.duration = 2000;
接下來,在建構函式中設定元素的初始狀態。
this.state = {
inProp: true
}
接下來,透過每 3 秒更改一次 inProp 值來模擬動畫。
setInterval(() => {
this.setState((state, props) => {
let newState = {
inProp: !state.inProp
};
return newState;
})
}, 3000);
接下來,建立一個 render 函式。
render() {
return (
);
}
接下來,新增 CSSTransition 元件。將 this.state.inProp 用於 in prop,將 this.duration 用於 timeout prop,將 hello 用於 classNames prop。CSSTransition 元件需要使用者介面作為子 prop。
render() {
return (
<CSSTransition in={this.state.inProp} timeout={this.duration}
classNames="hello">
// ... user interface code ...
</CSSTransition>
);
}
接下來,編寫元件的使用者介面。
render() {
return (
<CSSTransition in={this.state.inProp} timeout={this.duration}
classNames="hello">
<div>
<h1>Hello World!</h1>
</div>
</CSSTransition>
);
}
最後,公開元件。
export default HelloWorldCSSTransition;
元件的完整原始碼如下:
import React from 'react';
import { CSSTransition } from 'react-transition-group'
import './HelloWorldCSSTransition.css'
class HelloWorldCSSTransition extends React.Component {
constructor(props) {
super(props);
this.duration = 2000;
this.state = {
inProp: true
}
setInterval(() => {
this.setState((state, props) => {
let newState = {
inProp: !state.inProp
};
return newState;
})
}, 3000);
}
render() {
return (
<CSSTransition in={this.state.inProp} timeout={this.duration}
classNames="hello">
<div>
<h1>Hello World!</h1>
</div>
</CSSTransition>
);
}
}
export default HelloWorldCSSTransition;
接下來,在 src 資料夾下建立一個檔案 index.js 並使用 HelloWorld 元件。
import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorldCSSTransition from './components/HelloWorldCSSTransition';
ReactDOM.render(
<React.StrictMode>
<HelloWorldCSSTransition />
</React.StrictMode>,
document.getElementById('root')
);
接下來,使用 npm 命令啟動應用程式。
npm start
接下來,開啟瀏覽器並在位址列中輸入 `https://:3000` 並按 Enter 鍵。
訊息將每 3 秒淡入淡出一次。
TransitionGroup
TransitionGroup 是一個容器元件,它管理列表中的多個轉換元件。例如,當列表中的每個專案都使用 CSSTransition 時,可以使用 TransitionGroup 將所有專案分組以進行適當的動畫。
<TransitionGroup>
{items.map(({ id, text }) => (
<CSSTransition key={id} timeout={500} classNames="item" >
<Button
onClick={() =>
setItems(items =>
items.filter(item => item.id !== id)
)
}
>
×
</Button>
{text}
</CSSTransition>
))}
</TransitionGroup>
ReactJS - Bootstrap
Bootstrap 是全球前端開發人員使用的流行 CSS 框架。Bootstrap 透過其靈活、響應式和高效能的實用程式 CSS 元件,為設計網頁提供了極好的支援。Bootstrap 還提供大量基於 jQuery 的 UI 元件。
使用 Bootstrap CSS 和 JavaScript 元件,前端開發人員可以設計精美的網頁以及對任何裝置的響應式支援。React 可以與 Bootstrap 一起使用,並在其 Web 應用程式中獲得 Bootstrap 的所有好處。讓我們在本節中瞭解如何將 Bootstrap 整合到 React 應用程式中。
整合 Bootstrap
可以透過多種方式將 Bootstrap 整合到 React 應用程式中。如果開發人員只想使用 Bootstrap 庫中的 CSS 功能,則開發人員可以透過 CDN 匯入 Bootstrap 庫,並在需要的地方使用 Bootstrap CSS 類。
如果開發人員想要使用 Bootstrap JavaScript 庫,則開發人員可以使用圍繞原始 Bootstrap jQuery 元件的 React 元件,或者使用專門設計的 React UI 庫來利用 Bootstrap 庫的功能。
以下是將 Bootstrap 庫整合到 React 應用程式中的選項列表。
Link 標籤(僅限 CSS)。
import 功能(僅限 CSS)。
Link 標籤(Bootstrap + jQuery UI)。
包裝器 React 元件。
原生 React Bootstrap 元件。
Link 標籤(僅限 CSS)
讓我們在本節中學習如何透過建立一個 React 應用程式來應用連結標籤。首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟主 html 頁面 (public/index.html) 並將以下標籤包含在 head 中
<!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
接下來,開啟 App.css (src/App.css) 並更新 CSS 以設定按鈕元素的邊距。
button {
margin: 5px;
}
接下來,開啟 App 元件 (src/App.js) 並使用 Bootstrap 按鈕更新內容,如下所示:
import './App.css'
function App() {
return (
<div className="container">
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-secondary">Secondary</button>
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-danger">Danger</button>
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-light">Light</button>
<button type="button" class="btn btn-dark">Dark</button>
<button type="button" class="btn btn-link">Link</button>
</div>
);
}
export default App;
這裡:
為不同型別的按鈕應用了 Bootstrap CSS 類。
包含了 App.css 樣式。
最後,在瀏覽器中開啟應用程式,並檢查 Bootstrap 類是否已正確應用於按鈕元素,如下所示:
import 功能(僅限 CSS)
讓我們在本節中瞭解如何使用 import 功能整合 Bootstrap CSS。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 Bootstrap 庫。
npm install --save bootstrap
接下來,開啟 App.css (src/App.css) 並更新 CSS 以設定按鈕元素的邊距。
button {
margin: 5px;
}
接下來,開啟 App 元件 (src/App.js),匯入 Bootstrap css 並使用 Bootstrap 按鈕更新內容,如下所示:
// Bootstrap CSS
import "bootstrap/dist/css/bootstrap.min.css";
// Bootstrap Bundle JS
import "bootstrap/dist/js/bootstrap.bundle.min";
import './App.css'
function App() {
return (
<div className="container">
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-secondary">Secondary</button>
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-danger">Danger</button>
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-light">Light</button>
<button type="button" class="btn btn-dark">Dark</button>
<button type="button" class="btn btn-link">Link</button>
</div>
);
}
export default App;
這裡我們有:
使用 import 語句匯入了 Bootstrap 類。
為不同型別的按鈕應用了 Bootstrap CSS 類。
包含了 App.css 樣式。
最後,在瀏覽器中開啟應用程式,並檢查 Bootstrap 類是否已正確應用於按鈕元素,如下所示:
Link 標籤(Bootstrap + jQuery UI)
React 允許開發者使用 `createRoot` 方法將 React 元件整合到網頁的特定部分。此功能使開發者能夠在一個網頁中同時使用 React 和 Bootstrap 元件。開發者可以混合使用這兩個庫而不會互相影響。對於小型網頁來說,這是簡單且最佳的選擇。因為它不需要額外的學習成本,所以易於安全地應用於 Web 應用程式。
包裝 React 元件
開發者可以為必要的 Bootstrap 元件建立一個包裝 React 元件,並在他們的應用程式中使用它。此方法可用於 Bootstrap 元件使用不廣泛的中小型複雜 Web 應用程式。
原生 React Bootstrap 元件
React 社群建立了許多整合 Bootstrap 和 React 的元件庫。一些流行的庫如下:
React-Bootstrap (https://react-bootstrap.github.io/)
Bootstrap 4 React (https://bootstrap-4-react.com//)
Reactstrap (https://reactstrap.github.io/)
來自 coreUI 的 Bootstrap React (https://coreui.io/bootstrap-react/)
本章將透過建立一個簡單的 React 應用程式來演示如何使用 React-bootstrap。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 bootstrap 庫:
npm install --save react-bootstrap bootstrap
接下來,開啟 App.css (src/App.css) 並更新 CSS 以設定按鈕元素的邊距。
button {
margin: 5px;
}
接下來,開啟 App 元件 (src/App.js),匯入 Bootstrap css 並使用 Bootstrap 按鈕更新內容,如下所示:
// Bootstrap CSS
import "bootstrap/dist/css/bootstrap.min.css";
// Bootstrap Bundle JS
import "bootstrap/dist/js/bootstrap.bundle.min";
import './App.css'
import { Button } from 'react-bootstrap';
function App() {
return (
<div className="container">
<Button variant="primary">Primary</Button>{' '}
<Button variant="secondary">Secondary</Button>{' '}
<Button variant="success">Success</Button>{' '}
<Button variant="warning">Warning</Button>{' '}
<Button variant="danger">Danger</Button>{' '}
<Button variant="info">Info</Button>{' '}
<Button variant="light">Light</Button>{' '}
<Button variant="dark">Dark</Button> <Button variant="link">Link</Button>
</div>
);
}
export default App;
這裡我們有:
使用 import 語句匯入了 Bootstrap 類。
匯入了 Bootstrap 按鈕元件。
使用不同變體的按鈕元件。
包含了 App.css 樣式。
最後,在瀏覽器中開啟應用程式,並檢查 Bootstrap 類是否已正確應用於按鈕元素,如下所示:
總結
React 提供了許多與 Bootstrap 庫整合的選項。React 能夠在 Web 應用程式中將 Bootstrap 元件平滑遷移到 React Bootstrap 元件。豐富的第三方 Bootstrap 元件集使開發者能夠提供出色的 UI/UX 體驗,而無需離開 Bootstrap 庫。
ReactJS - Map
JavaScript 的 `Array` 資料型別提供了一系列易於使用的函式來運算元組及其值。`map()` 就是這樣一個函式,它接受一個轉換函式,並透過應用轉換函式轉換給定陣列中的每個專案來建立一個新陣列,並返回新建立的陣列。
`map` 函式的簽名如下:
array.map(function(item, index, items), thisValue)
這裡:
`currentValue` 指的是當前元素的值
`index` 指的是當前元素的索引值
`items` 指的是當前元素的陣列
`thisValue` 是可選的 `this` 值,可在呼叫 `map` 函式時傳遞
假設我們有一列數字,並希望將陣列中的每個值加倍。我們可以使用 `map` 函式在一行程式碼中完成此操作,如下所示:
var numbers = [2, 4, 6]
var transformed = numbers.map((val) => val + val)
for(var item of transformed) { console.log(item) }
這裡的輸出將如下所示:
4 8 12
示例
讓我們使用 create-react-app 建立一個新應用程式並啟動它。
create-react-app myapp cd myapp npm start
接下來,在 components 資料夾下 (src/components/ExpenseListUsingForLoop.js) 建立一個元件 `ExpenseListUsingForLoop`。
import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
render() {
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th></th>
</tr>
</tfoot>
</table>
}
}
export default ExpenseListUsingForLoop
在這裡,我們建立了一個帶有標題和頁尾的基本表結構。
接下來,建立一個函式來查詢總支出金額。我們稍後將在 `render` 方法中使用它。
getTotalExpenses() {
var items = this.props['expenses'];
var total = 0;
for(let i = 0; i < items.length; i++) {
total += parseInt(items[i]);
}
return total;
}
這裡,`getTotalExpenses` 迴圈遍歷支出 props 並總結總支出。
接下來,在 `render` 方法中新增支出專案和總金額。
render() {
var items = this.props['expenses'];
var expenses = []
expenses = items.map((item, idx) => <tr><td>item {idx + 1}</td><td>{item}</td></tr>)
var total = this.getTotalExpenses();
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{expenses}
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th>{total}</th>
</tr>
</tfoot>
</table>
}
這裡我們有:
使用 `map` 函式遍歷支出陣列中的每個專案,為每個條目使用轉換函式建立表格行 (tr),最後將返回的陣列設定在 `expenses` 變數中。
在 JSX 表示式中使用expenses 陣列來包含生成的 rows。
使用getTotalExpenses 方法查詢總支出金額並將其新增到 render 方法中。
ExpenseListUsingForLoop 元件的完整原始碼如下所示:
import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
getTotalExpenses() {
var items = this.props['expenses'];
var total = 0;
for(let i = 0; i < items.length; i++) {
total += parseInt(items[i]);
}
return total;
}
render() {
var items = this.props['expenses'];
var expenses = []
expenses = items.map((item, idx) => <tr><td>item {idx + 1}</td><td>{item}</td></tr>)
var total = this.getTotalExpenses();
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{expenses}
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th>{total}</th>
</tr>
</tfoot>
</table>
}
}
export default ExpenseListUsingForLoop
接下來,使用 ExpenseListUsingForLoop 元件更新 App 元件 (App.js)。
import ExpenseListUsingForLoop from './components/ExpenseListUsingForLoop';
import './App.css';
function App() {
var expenses = [100, 200, 300]
return (
<div>
<ExpenseListUsingForLoop expenses={expenses} />
</div>
);
}
export default App;
接下來,在 App.css 中新增基本樣式。
/* Center tables for demo */
table {
margin: 0 auto;
}
div {
padding: 5px;
}
/* Default Table Style */
table {
color: #333;
background: white;
border: 1px solid grey;
font-size: 12pt;
border-collapse: collapse;
}
table thead th,
table tfoot th {
color: #777;
background: rgba(0,0,0,.1);
text-align: left;
}
table caption {
padding:.5em;
}
table th,
table td {
padding: .5em;
border: 1px solid lightgrey;
}
最後,在瀏覽器中檢查應用程式。它將按如下所示顯示支出:
JSX 中的 Map
JSX 允許包含任何 JavaScript 表示式。由於 `map` 只是 JavaScript 中的一個表示式,因此我們可以像下面這樣直接在 JSX 中使用它:
render() {
var items = this.props['expenses'];
var expenses = []
// expenses = items.map((item, idx) => <tr><td>item {idx + 1}</td><td>{item}</td></tr>)
var total = this.getTotalExpenses();
return <table>
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{items.map((item, idx) => <tr><td>item {idx + 1}</td><td>{item}</td></tr>)}
</tbody>
<tfoot>
<tr>
<th>Sum</th>
<th>{total}</th>
</tr>
</tfoot>
</table>
}
export default ExpenseListUsingForLoop
ReactJS - 表格
React 透過第三方 UI 元件庫提供表格元件。React 社群提供了大量的 UI/UX 元件,很難為我們的需求選擇合適的庫。
Bootstrap UI 庫是開發者的熱門選擇之一,並且被廣泛使用。React Bootstrap (https://react-bootstrap.github.io/) 已將幾乎所有 Bootstrap UI 元件移植到 React 庫中,並且對 Table 元件也提供了最佳支援。
本章讓我們學習如何使用 react-bootstrap 庫中的 Table 元件。
Table 元件
Table 元件允許開發者在 Web 應用程式中使用 Bootstrap UI 設計建立簡單的表格。Table 元件接受如下所示的表格標籤:
thead
tbody
tfoot
Table 元件接受少量 props 來自定義表格元件,它們如下:
bordered (布林值) - 為表格和單元格的四面新增邊框。
borderless (布林值) - 刪除表格和單元格四面的邊框。
hover (布林值) - 為表格中的每一行 (tbody) 啟用懸停狀態。
responsive (布林值 | 字串) - 為小型裝置啟用垂直滾動。sm | md | lg | xl 選項為相關裝置啟用響應式。例如,只有在裝置解析度非常小的情況下才會啟用 sm。
size (字串) - 啟用表格的緊湊渲染。可能的選項有 sm、md 等。
striped (布林值 | 字串) - 為所有表格行啟用斑馬條紋。columns 選項也為列新增斑馬條紋。
variant (dark) 使用 dark 值時啟用暗色變體。
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
應用 Table 元件
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 Bootstrap 和 react-bootstrap 庫:
npm install --save bootstrap react-bootstrap
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,建立一個簡單的表格元件 `SimpleTable` (src/Components/SimpleTable.js) 並渲染表格,如下所示:
import { Table } from 'react-bootstrap';
function SimpleTable() {
return (
<Table striped bordered hover>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Age</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>John</td>
<td>25</td>
<td>john.example@tutorialspoint.com</td>
</tr>
<tr>
<td>1</td>
<td>Peter</td>
<td>15</td>
<td>peter.example@tutorialspoint.com</td>
</tr>
<tr>
<td>1</td>
<td>Olivia</td>
<td>23</td>
<td>olivia.example@tutorialspoint.com</td>
</tr>
</tbody>
</Table>
);
}
export default SimpleTable;
這裡我們有:
使用 striped props 建立斑馬紋表格。
使用 bordered props 為表格和單元格啟用邊框。
使用 hover props 啟用懸停狀態。
接下來,開啟 App 元件 (src/App.js),匯入 Bootstrap css 並使用 Bootstrap 按鈕更新內容,如下所示:
import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimpleTable from './Components/SimpleTable'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleTable />
</div>
</div>
</div>
);
}
export default App;
這裡我們有:
使用 import 語句匯入了 Bootstrap 類。
渲染了新的 SimpleTable 元件。
包含了 App.css 樣式。
最後,在瀏覽器中開啟應用程式並檢查最終結果。表格元件將按如下所示渲染:
新增暗色變體和列條紋
讓我們在表格元件中應用暗色變體和列條紋選項,看看它如何更新表格設計。
首先,開啟我們的輪播應用程式,並按如下所示更新 SimpleCarousel 元件:
import { Table } from 'react-bootstrap';
function SimpleTable() {
return (
<Table bordered hover striped="columns" variant="dark">
// ...
這裡我們有:
使用帶有 columns 的 striped props 來啟用基於列的斑馬條紋。
使用帶有 dark 選項的 variant props 來啟用表格設計的暗色變體。
接下來,在瀏覽器中開啟應用程式並檢查最終結果。表格元件將按如下所示渲染列條紋和暗色變體:
總結
Bootstrap 表格元件提供所有必要的選項,以便以簡單、直觀和靈活的方式設計表格。
ReactJS - 使用 Flux 管理狀態
前端應用程式的一個重要功能是狀態管理。React 擁有其自身元件的狀態管理技術。React 狀態管理僅在元件級別工作。即使元件處於父子關係(巢狀元件),一個元件的狀態也不能在另一個元件中訪問。為了克服這個問題,有很多第三方狀態管理庫,例如 redux、mobx 等。
Flux 是有效管理應用程式狀態的一種技術。Flux 由 Facebook 引入,在其 Web 應用程式中廣泛使用。Flux 使用單向資料流模式來提供清晰的狀態管理。讓我們在本節中學習什麼是 Flux 以及如何使用它。
使用 Flux 管理狀態
Flux 使用單向資料流模式。它有四個不同的部分:
Store - 顧名思義,所有業務資料都儲存在 Store 中。Store 執行兩個過程。
Store 將透過從已註冊的 Dispatcher 收集資料來自行更新其資料。Dispatcher 為 Store 提供資料和相關操作。
資料更新後,Store 將發出更改資料事件以通知檢視資料已更改。檢視將偵聽更改事件,並在收到更改事件後透過訪問 Store 中的更新資料來更新其檢視。
Action - Action 只是要處理的操作及其必要資料的表示。檢視將根據使用者互動建立帶有必要資料的 Action 並將其傳送到 Dispatcher。例如,下面提到的有效負載是由檢視(Action 建立者)根據使用者互動建立的,用於新增使用者。
{
actionType: "add",
data: {
name: "Peter"
}
}
上面提到的 Action 將傳遞給 Dispatcher,後者會將資訊傳送給所有已註冊的 Store。Store 將相應地更新資料,並將更改事件傳送給所有已向其註冊的檢視。
Dispatcher - Dispatcher 接收帶有正確有效負載的操作,並將其傳送給所有已註冊的 Store 以進行進一步處理。
View - 檢視根據使用者互動建立操作並將其傳送到 Dispatcher。它向 Store 註冊以獲取更改,一旦它透過事件接收到更改,它將使用新資料更新自身。
為了 Flux 的有效工作,需要初始化一些內容,如下所示:
應用程式應使用適當的操作及其回撥來初始化 Dispatcher。
Store 應該被初始化並向 Dispatcher 註冊以接收資料更新。
檢視應該使用 Dispatcher 和 Store 初始化。檢視應註冊以偵聽 Store 更改(事件)。
Flux 架構的工作流程如下:
使用者互動並在檢視中觸發事件。
檢視處理事件並根據使用者的操作建立 Action。
檢視將 Action 傳送到 Dispatcher。
Dispatcher 將 Action 釋出給所有向其註冊的 Store。
已註冊的 Store 將接收帶有有效負載的 Action。Store 將根據 Action 更新自身。
Store 將向檢視發出更改事件。
偵聽 Store 更改的檢視將使用更新的資料更新前端。
應用 Flux
讓我們建立一個新的 React 應用程式,在本節中學習如何在其中應用 Flux 概念。首先,建立一個新的 React 應用程式並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用 npm 安裝 Flux 包,如下所示:
npm install flux --save
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。接下來,建立一個 Flux Dispatcher,`Dispatcher` (src/Flux/Dispatcher.js),如下所示:
import {Dispatcher} from "flux";
export default new Dispatcher();
在這裡,我們從 Flux 包中建立了一個新的 Dispatcher。接下來,建立操作(和操作建立者)`UserActions` (src/Flux/UserActions.js),如下所示:
import dispatcher from "./Dispatcher";
export const USER_ACTIONS = {
ADD: 'addUser'
};
export function addUser(userName) {
dispatcher.dispatch({
type: USER_ACTIONS.ADD,
value: {
name: userName
}
})
}
這裡:
`USER_ACTIONS.ADD` 是一個常量,用於引用使用者的新增操作。
addUser() 方法用於建立包含使用者資料的 action,並將建立的 action 分派給排程器。
接下來,建立一個名為 UserStore 的儲存(src/Flux/UserStore.js),如下所示:
import dispatcher from "./Dispatcher";
import {EventEmitter} from "events";
import * as UserActions from "./UserActions";
class UserStore extends EventEmitter {
constructor() {
super();
this.users = [];
}
handleActions(action) {
switch (action.type) {
case UserActions.USER_ACTIONS.ADD: {
this.users.push(action.value);
this.emit("storeUpdated");
break;
}
default: {
}
}
}
getUsers() {
return this.users;
}
}
const userStore = new userStore();
dispatcher.register(userStore.handleActions.bind(userStore));
export default userStore;
這裡:
UserStore 繼承自 EventEmitter 以發出更改事件。
handleActions 從排程器檢索使用者詳細資訊並更新自身(this.users)。
handleActions 發出儲存更新事件,通知檢視儲存已更新。
getUsers() 方法將返回當前使用者列表資訊。
接下來,建立一個使用者輸入元件 UserInput 元件,用於獲取新的使用者資訊,如下所示:
import React from "react";
import * as UserActions from "./UserActions";
export default class ButtonComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
username: ''
}
}
onButtonClick = () => {
UserActions.addUser(this.state.username)
};
render() {
return (
<div>
<input name="username" onChange={(e) => this.setState({username: e.target.value})}/>
<button onClick={() => this.onButtonClick()}>Add user</button>
</div>
);
}
}
這裡:
建立一個輸入元素,用於從使用者處獲取新的使用者資料。
新增一個按鈕,用於將使用者資訊提交到 UserActions 的 addUser() 方法。
addUser 將更新使用者資料並將其與正確的 action 型別一起傳送到排程器。排程器將使用 action 型別呼叫儲存。儲存將更新使用者列表並通知所有已註冊的檢視。
接下來,建立一個使用者列表元件 UserList 元件,用於顯示儲存中可用的使用者,如下所示:
import React from "react";
import UserStore from "./UserStore";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
users: UserStore.getUsers()
}
}
componentDidMount() {
UserStore.on("storeUpdated", this.updateUserList);
}
componentWillUnmount() {
UserStore.removeListener("storeUpdated", this.updateUserList);
}
updateUserList = () => {
this.setState({users: UserStore.getUsers()})
};
render() {
return (
<ul>{
this.state.users && this.state.users.length > 0 &&
this.state.users.map((items) => <li>{items.name}</li>)
}
</ul>
);
}
}
這裡:
componentDidMount 透過 UserStore.on 方法註冊儲存事件 (storeUpdated)。
componentWillUnmount 透過 UserStore.removeListener 方法登出儲存事件 (storeUpdated)。
updateUserList 從儲存獲取最新的使用者資料並更新自身儲存。
render 方法從其狀態 (this.state.users) 渲染使用者列表。
接下來,開啟 App 元件 (src/App.js),並使用 UserInput 和 UserList 元件,如下所示:
import './App.css'
import React, { Suspense, lazy } from 'react';
import UserInput from './Flux/UserInput';
import UserList from './Flux/UserList';
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<UserInput />
<UserList />
</div>
</div>
</div>
);
}
export default App;
這裡:
UserInput 用於從使用者處獲取資訊。
UserList 將從儲存獲取最新的使用者列表並渲染它。
最後,在瀏覽器中開啟應用程式並檢查最終結果。最初,使用者列表將為空。一旦使用者輸入使用者名稱並提交,下面的列表將顯示更新後的使用者列表,如下所示:
總結
Flux 是一種簡單、單向的狀態管理模式,適用於 React 應用程式。它有助於降低 React 應用程式的複雜性。它以透明的方式連線檢視和儲存透過排程器。React 社群增強了 Flux 模式併發布了許多成熟的狀態管理庫(如 Redux),這些庫功能更強大且易於使用。
ReactJS - 測試
測試是確保任何應用程式中建立的功能都符合業務邏輯和編碼規範的過程之一。React 建議使用 React 測試庫 來測試 React 元件,並使用 jest 測試執行器來執行測試。react-testing-library 允許隔離檢查元件。
可以使用以下命令在應用程式中安裝它:
npm install --save @testing-library/react @testing-library/jest-dom
建立 React 應用程式
Create React app 預設配置了 React 測試庫 和 jest 測試執行器。因此,測試使用 Create React App 建立的 React 應用程式只需一條命令即可完成。
cd /go/to/react/application npm test
npm test 命令類似於 npm build 命令。兩者都會在開發人員更改程式碼時重新編譯。一旦在命令提示符中執行該命令,它就會發出以下問題。
No tests found related to files changed since last commit. Press `a` to run all tests, or run Jest with `--watchAll`. Watch Usage › Press a to run all tests. › Press f to run only failed tests. › Press q to quit watch mode. › Press p to filter by a filename regex pattern. › Press t to filter by a test name regex pattern. › Press Enter to trigger a test run.
按下 a 將嘗試執行所有測試指令碼,最後總結結果,如下所示:
Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 4.312 s, estimated 12 s Ran all test suites. Watch Usage: Press w to show more.
自定義應用程式中的測試
讓我們在本節中使用 Rollup bundler 編寫一個自定義 React 應用程式,並使用 React 測試庫 和 jest 測試執行器對其進行測試。
首先,按照“建立 React 應用程式”一章中的說明,使用 Rollup 捆綁器建立一個新的 React 應用程式 react-test-app。
接下來,安裝測試庫。
cd /go/to/react-test-app npm install --save @testing-library/react @testing-library/jest-dom
接下來,在您喜歡的編輯器中開啟應用程式。
接下來,在 src/components 資料夾下建立一個檔案 HelloWorld.test.js,為 HelloWorld 元件編寫測試並開始編輯。
接下來,匯入 React 庫。
import React from 'react';
接下來,匯入測試庫。
import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom';
接下來,匯入我們的 HelloWorld 元件。
import HelloWorld from './HelloWorld';
接下來,編寫一個測試以檢查文件中是否存在“Hello World”文字。
test('test scenario 1', () => {
render(<HelloWorld />);
const element = screen.getByText(/Hello World/i);
expect(element).toBeInTheDocument();
});
完整的測試程式碼如下所示:
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import HelloWorld from './HelloWorld';
test('test scenario 1', () => {
render(<HelloWorld />);
const element = screen.getByText(/Hello World/i);
expect(element).toBeInTheDocument();
});
接下來,安裝 jest 測試執行器(如果系統中尚未安裝)。
npm install jest -g
接下來,在應用程式的根資料夾中執行 jest 命令。
jest
接下來,在應用程式的根資料夾中執行 jest 命令。
PASS src/components/HelloWorld.test.js √ test scenario 1 (29 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 5.148 s Ran all test suites.
ReactJS - CLI 命令
React 有自己的命令列介面 (CLI) 命令。但是,這些 CLI 命令目前僅用於使用命令列建立 React 應用程式的可透過版本。這將包含一個預設模板作為其設計,因此所有以此方式建立的 React 應用程式都將具有很強的 一致性,因為它們都具有相同的結構。
React 中的基本 CLI 命令
讓我們在本節中學習 Create React App 命令列應用程式中可用的基本命令。
建立一個新應用程式
Create React App 提供多種建立 React 應用程式的方法。
使用 npx 指令碼。
npx create-react-app <react-app-name> npx create-react-app hello-react-app
使用 npm 包管理器。
npm init react-app <react-app-name> npm init react-app hello-react-app
使用 yarn 包管理器。
yarn init react-app <react-app-name> yarn init react-app hello-react-app
選擇模板
Create React App 使用預設模板建立 React 應用程式。模板指的是具有某些內建功能的初始程式碼。npm 包伺服器上有數百個具有許多高階功能的模板。Create React App 允許使用者透過 -template 命令列開關選擇模板。
create-react-app my-app --template typescript
以上命令將使用 npm 伺服器上的 cra-template-typescript 包建立 React 應用程式。
安裝依賴項
可以使用普通的 npm 或 yarn 包命令安裝 React 依賴項包,因為 React 使用 npm 和 yarn 推薦的專案結構。
使用 npm 包管理器。
npm install --save react-router-dom
使用 yarn 包管理器。
yarn add react-router-dom
執行應用程式
可以根據專案中使用的包管理器使用 npm 或 yarn 命令啟動 React 應用程式。
使用 npm 包管理器。
npm start
使用 yarn 包管理器。
yarn start
要在安全模式(HTTPS)下執行應用程式,請設定環境變數 HTTPS 並將其設定為 true,然後再啟動應用程式。例如,在 Windows 命令提示符 (cmd.exe) 中,以下命令設定 HTTPS 並以 HTTPS 模式啟動應用程式:
set HTTPS=true && npm start
ReactJS - 構建和部署
讓我們在本節中學習如何進行 React 應用程式的生產構建和部署。
構建
完成 React 應用程式的開發後,需要將應用程式捆綁並部署到生產伺服器。讓我們在本節中學習可用於構建和部署應用程式的命令。
只需一個命令即可建立應用程式的生產版本。
npm run build
> expense-manager@0.1.0 build path\to\expense-manager
> react-scripts build
Creating an optimized production build...
Compiled with warnings.
File sizes after gzip:
41.69 KB build\static\js\2.a164da11.chunk.js
2.24 KB build\static\js\main.de70a883.chunk.js
1.4 KB build\static\js\3.d8a9fc85.chunk.js
1.17 KB build\static\js\runtime-main.560bee6e.js
493 B build\static\css\main.e75e7bbe.chunk.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
npm install -g serve
serve -s build
Find out more about deployment here:
https://cra.link/deployment
構建應用程式後,該應用程式可在 build/static 資料夾下找到。
預設情況下,profiling 選項處於停用狀態,可以透過 -profile 命令列選項啟用。-profile 將在程式碼中包含效能分析資訊。效能分析資訊可以與 React DevTools 一起使用來分析應用程式。
npm run build -- --profile
部署
構建應用程式後,可以將其部署到任何 Web 伺服器。讓我們在本節中學習如何部署 React 應用程式。
本地部署
可以使用 serve 包進行本地部署。讓我們首先使用以下命令安裝 serve 包:
npm install -g server
要使用 serve 啟動應用程式,請使用以下命令:
cd /go/to/app/root/folder serve -s build
預設情況下,serve 使用埠 5000 提供應用程式服務。可以在 https://:5000 檢視應用程式。
生產部署
可以透過將 build/static 資料夾下的檔案複製到生產應用程式的根目錄來輕鬆完成生產部署。它將在所有 Web 伺服器(包括 Apache、IIS、Nginx 等)上執行。
相對路徑
預設情況下,生產版本是在假設應用程式將託管在 Web 應用程式的根資料夾中建立的。如果需要將應用程式託管在子資料夾中,則在 package.json 中使用以下配置,然後構建應用程式。
{ ... "homepage": "http://domainname.com/path/to/subfolder", ... }
ReactJS - 示例
在本節中,讓我們透過應用在本教程中學到的概念來建立一個示例支出管理器應用程式。一些概念列在下面:
React 基礎知識(元件、jsx、props 和狀態)
使用 react-router 的路由器
Http 客戶端程式設計(Web API)
使用 Formik 的表單程式設計
使用 Redux 的高階狀態管理
Async/await 程式設計
功能
我們示例支出管理器應用程式的一些功能包括:
列出伺服器上的所有支出
新增支出專案
刪除支出專案
這裡:
ReactJS - Hooks 簡介
在 React 16.8 之前,函式元件只是無狀態元件。要向元件新增狀態,我們需要將函式元件轉換為基於類的元件。此外,函式元件沒有操作元件生命週期事件的選項。為了在函式元件中啟用狀態和生命週期事件,React 引入了一個名為 Hooks 的新概念。
Hooks 是普通的 JavaScript 函式,可以訪問使用/應用它的元件的狀態和生命週期事件。通常,hook 以 use 關鍵字開頭。React 附帶了一些內建 hook,並且也允許建立自定義 hook。
內建 hook
讓我們瞭解 React 中可用的 hook 列表及其基本用法。
useState - 用於操作元件的狀態。
useReducer - 使用 reducer 概念的 useState hook 的高階版本。
useEffect - 用於掛接到元件的生命週期。
useLayoutEffect - 類似於 useEffect,但在所有 DOM 變異之後或 DOM 將要繪製到螢幕上之前同步觸發。
useContext - 提供對元件內上下文提供程式的訪問。
useMemo - 用於返回變數/函式的記憶化版本,該版本僅根據提供的預定義依賴集進行更改。這將減少昂貴計算的重新計算次數,並提高應用程式的效能。
useCallback - 返回回撥函式的記憶化版本,該版本僅根據提供的預定義依賴集進行更改。
useRef - 基於 React ref 物件提供對原始 DOM 節點的訪問。
useImperativeHandle - 用於將子元件中的基於 ref 的值暴露給父元件。
useDeferredValue - 用於延遲值,類似於去抖動或節流以延遲更新。
useDebugValue - 用於在 React DevTools 中顯示自定義 hook 的標籤。
useTransition - 用於識別轉換的掛起狀態。
useId - 用於為應用程式中的元素建立唯一 ID。
應用 hook
讓我們學習如何透過建立應用程式在函式元件中使用 hook。
使用 create-react-app 建立一個 React 應用程式,並使用以下命令啟動應用程式
create-react-app myapp cd myapp npm start
接下來,讓我們建立一個新的函式元件 HelloWorld (src/components/HelloWorld.js),它呈現一個輸入元素,並根據使用者在輸入元素中輸入的資料呈現問候訊息。
import { useState } from 'react';
export default function HelloWorld() {
const [name, setName] = useState("World")
return (
<div style={{ textAlign: "center", padding: "5px" }}>
<input id="name" name="name"
value={name}
onChange={(e) => setName(e.target.value)} />
<div>Hello {name}</div>
</div>
)
}
這裡:
useState是一個Hook,它接收一個初始值並返回一個狀態和一個更新狀態的函式。它接收World作為初始值,並返回一個包含兩個元素的陣列:a)狀態的初始值(name)和 b)更新狀態的函式(setName)。使用的語法是陣列解構語法,用於將陣列值獲取並設定到name和setName變數中。
input是一個React輸入元素,附加了一個onChange事件。onChange事件透過event.target.value獲取使用者更新的值,並使用setName函式將其設定到當前狀態中。
每當使用者更新輸入時,onChange事件就會觸發並更新狀態,這反過來會觸發元件的render函式。
接下來,讓我們將元件應用到我們的應用程式(App.js)中,如下所示:
import './App.css';
import HelloWorld from './components/HelloWorld';
function App() {
return (
<HelloWorld />
);
}
export default App;
最後,開啟瀏覽器並透過更改輸入值來檢查結果。訊息會在輸入更改時更新,如下所示:
Hook的優勢
與基於類的元件相比,函式元件結合Hook使用時具有許多優勢。它們如下:
Hook易於理解,並能快速開始編碼。
在大型應用程式中,可以將應用程式的複雜性保持在最低限度。在基於類的元件中,隨著專案的增長,複雜性(狀態管理和處理生命週期事件)也會增長。
類(元件)中的this對於JavaScript程式設計初學者來說很難理解。由於函式元件和Hook不依賴於this,因此開發人員可以快速開始使用React進行編碼,而無需陡峭的學習曲線。
有狀態邏輯可以在元件之間輕鬆重用。
函式元件可以與基於類的元件一起使用,這使得它很容易在任何規模的現有專案中採用。
與基於類的元件相比,函式元件可以用幾行程式碼編寫。
Hook的缺點
Hook是建立元件的替代方法,它也有一些缺點。它們如下:
Hook只能在頂級呼叫,應避免在條件、迴圈或巢狀函式內使用。
Hook是專門的功能,它們可能並不適合某些情況,我們可能不得不恢復到基於類的元件。
React處理Hook的內部機制而不公開核心進行最佳化,這使得它不太靈活,不適合某些場景。
總結
Hook是相對較新的建立元件的方式。大量的專案仍在使用基於類的元件。將這些專案中的元件從基於類轉換為基於函式在實踐上是不可能的,我們必須接受它。相反,我們可以分階段轉換應用程式。
ReactJS - 使用 useState
useState是一個基本的React Hook,它允許函式元件維護自身的狀態並根據狀態更改重新渲染自身。useState的簽名如下:
const [ <state>, <setState> ] = useState( <initialValue> )
其中,
initialValue - 狀態的初始值。狀態可以用任何型別指定(數字、字串、陣列和物件)。
state - 表示狀態值的變數。
setState - 表示由useState返回的更新狀態函式的函式變數。
setState函式的簽名如下:
setState( <valueToBeUpdated> )
其中,valueToBeUpdated是要更新狀態的值。設定和更新使用者名稱示例用法如下:
// initialize the state
const [name, setName] = useState('John')
// update the state
setName('Peter)
功能
useState 的顯著特點如下:
函式引數 - 它接受一個函式(返回初始狀態)而不是初始值,並且只在元件的初始渲染期間執行一次該函式。如果初始值的計算代價很高,這將有助於提高效能。
const [val, setVal] = useState(() => {
var initialValue = null
// expensive calculation of initial value
return initialValue
})
驗證先前值 - 它檢查狀態的當前值和先前值,只有當它們不同時,React才會渲染其子元素並觸發效果。這將提高渲染效能。
// ...
setName('John') // update the state and rerender the component
// ...
// ...
setName('John') // does not fire the rendering of the children because the value of the state have not changed.
// ...
批次處理多個狀態更新 - React會在內部批次處理多個狀態更新。如果必須立即執行多個狀態更新,則可以使用React提供的特殊函式flushSync,它會立即重新整理所有狀態更改。
flushSync(() => setName('Peter'))
應用狀態Hook
讓我們建立一個登入表單元件,並使用useState Hook維護表單的值。
首先,使用以下命令建立並啟動一個React應用程式:
create-react-app myapp cd myapp npm start
接下來,在元件資料夾下(src/components/LoginForm.js)建立一個React元件LoginForm。
import { useState } from 'react';
export default function LoginForm() {
// render code
}
接下來,使用useState Hook建立兩個狀態變數username和password,如下所示:
import { useState } from 'react';
export default function LoginForm() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
// render code
}
接下來,建立一個函式來驗證登入資料,如下所示:
import { useState } from 'react';
export default function LoginForm() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
let isEmpty = (val) => {
if(val == null || val == '') {
return true;
} else {
return false;
}
}
let validate = (e) => {
e.preventDefault()
if(!isEmpty(username) && !isEmpty(password)) {
alert(JSON.stringify({
username: username,
password: password
}))
} else {
alert("Please enter username / password")
}
}
// render code
}
這裡,isEmpty是一個函式,用於檢查資料是否存在或為空。
接下來,使用兩個輸入欄位渲染一個登入表單,並使用狀態變數(username和password)、狀態更新方法(setUsername和setPassword)和驗證方法來處理表單。
import { useState } from 'react';
export default function LoginForm() {
return (
<div style={{ textAlign: "center", padding: "5px" }}>
<form name="loginForm">
<label for="username">Username: </label>
<input id="username" name="username" type="text"
value={username}
onChange={(e) => setUsername(e.target.value)} />
<br />
<label for="password">Password: </label>
<input id="password" name="password" type="password"
value={password}
onChange={(e) => setPassword(e.target.value)} />
<br />
<button type="submit" onClick={(e) => validate(e)}>Submit</button>
</form>
</div>
)
}
這裡:
onChange使用Hook返回的狀態設定函式。
onClick使用validate函式進行驗證並顯示使用者輸入的資料。
LoginForm元件的完整程式碼如下:
import { useState } from 'react';
export default function LoginForm() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
let isEmpty = (val) => {
if(val == null || val == '') {
return true;
} else {
return false;
}
}
let validate = (e) => {
e.preventDefault()
if(!isEmpty(username) && !isEmpty(password)) {
alert(JSON.stringify({
username: username,
password: password
}))
} else {
alert("Please enter username / password")
}
}
return (
<div style={{ textAlign: "center", padding: "5px" }}>
<form name="loginForm">
<label for="username">Username: </label>
<input id="username" name="username" type="text"
value={username}
onChange={(e) => setUsername(e.target.value)} />
<br />
<label for="password">Password: </label>
<input id="password" name="password" type="password"
value={password}
onChange={(e) => setPassword(e.target.value)} />
<br />
<button type="submit" onClick={(e) => validate(e)}>Submit</button>
</form>
</div>
)
}
接下來,更新根應用程式元件App.js,如下所示:
import './App.css';
import HelloWorld from './components/HelloWorld';
import LoginForm from './components/LoginForm';
function App() {
return (
<LoginForm />
);
}
export default App;
接下來,開啟瀏覽器並檢查應用程式。應用程式將使用狀態變數收集使用者輸入的資料,並使用validate函式對其進行驗證。如果使用者輸入正確的資料,它將顯示資料,如下所示:
否則,它將丟擲錯誤,如下所示:
物件作為狀態
在基於類的狀態管理中,setState方法支援狀態物件的區域性更新。例如,讓我們考慮一下登入表單資料作為物件儲存在狀態中。
{
username: 'John',
password: 'secret'
}
使用setState更新使用者名稱只會更新狀態物件中的使用者名稱,並保留密碼欄位。
this.setState({
username: 'Peter'
})
在Hook中,setData(useState返回的函式)將更新整個物件,如下所示:
// create state
const [data, setDate] = useState({
username: 'John',
password: 'secret'
})
// update state - wrong
setData({
username: 'Peter'
})
更新後的狀態沒有密碼欄位,如下所示:
{
username: 'Peter'
}
為了解決這個問題,我們可以使用javascript中的擴充套件運算子,如下所示:
setData({
...data,
username: 'Peter'
})
讓我們透過轉換LoginForm元件建立一個新元件,並使用物件狀態變數,如下所示:
import { useState } from 'react';
export default function LoginFormObject() {
const [data, setData] = useState({})
let isEmpty = (val) => {
if(val == null || val == '') {
return true;
} else {
return false;
}
}
let validate = (e) => {
e.preventDefault()
if(!isEmpty(data.username) && !isEmpty(data.password)) {
alert(JSON.stringify(data))
} else {
alert("Please enter username / password")
}
}
return (
<div style={{ textAlign: "center", padding: "5px" }}>
<form name="loginForm">
<label for="username">Username: </label>
<input id="username" name="username" type="text"
value={data.username}
onChange={(e) => setData( {...data, username: e.target.value} )} />
<br />
<label for="password">Password: </label>
<input id="password" name="password" type="password"
value={data.password}
onChange={(e) => setData({...data, password: e.target.value})} />
<br />
<button type="submit" onClick={(e) => validate(e)}>Submit</button>
</form>
</div>
)
}
這裡:
狀態儲存在物件(data)中。
setData由useState Hook返回,並用作狀態更新函式。
data.*語法用於獲取狀態的詳細資訊。
…data擴充套件運算子與setData函式一起使用來更新狀態。
總結
useState Hook是在函式元件中進行狀態管理的一種簡單易用的方法。useState可以用於處理狀態中的單個值或多個值。它同時支援基本資料型別和複雜物件。它允許使用多個狀態設定函式(set*),並在內部進行批次處理以簡化流程。由於引入了useState Hook,函式元件最終得到了改進,可以執行任何功能(從無狀態到有狀態)。
ReactJS - 使用 useEffect
React提供useEffect在元件中執行副作用。一些副作用如下:
從外部資源獲取資料並更新渲染的內容。
渲染後更新DOM元素。
訂閱
使用計時器
記錄
在基於類的元件中,這些副作用是使用生命週期元件完成的。因此,useEffect Hook是下面提到的生命週期事件的替代效果。
componentDidMount - 首次渲染完成後觸發。
componentDidUpdate - 由於prop或狀態更改而更新渲染後觸發。
componentWillUnmount - 元件銷燬期間解除安裝渲染內容後觸發。
讓我們在本節學習如何使用effect Hook。
useEffect的簽名
useEffect的簽名如下:
useEffect( <update function>, <dependency> )
其中,更新函式的簽名如下:
{
// code
return <clean up function>
}
這裡:
更新函式 - 更新函式是在每個渲染階段後要執行的函式。這對應於componentDidMount和componentDidUpdate事件。
依賴項 - 依賴項是一個數組,其中包含函式所依賴的所有變數。指定依賴項對於最佳化effect Hook非常重要。通常情況下,更新函式在每次渲染後都會被呼叫。有時沒有必要在每次渲染時都渲染更新函式。讓我們考慮一下我們從外部資源獲取資料並在渲染階段之後更新它,如下所示:
const [data, setDate] = useState({})
const [toggle, setToggle] = useState(false)
const [id, setID] = useState(0)
useEffect( () => {
fetch('/data/url/', {id: id}).then( fetchedData => setData(fetchedData) )
})
// code
每當data和toggle變數更新時,元件都會重新渲染。但是正如您所看到的,我們不需要在每次更新toggle狀態時都執行定義的效果。為了解決這個問題,我們可以傳遞一個空依賴項,如下所示:
const [data, setDate] = useState({})
const [toggle, setToggle] = useState(false)
const [id, setID] = useState(0)
useEffect( () => {
fetch('/data/url/', { id: id }).then( fetchedData => setData(fetchedData) )
}, [])
上面的程式碼只會在第一次渲染後執行一次效果。儘管它會修復問題,但效果必須在每次id更改時執行。為了實現這一點,我們可以將id作為效果的依賴項包含在內,如下所示:
const [data, setDate] = useState({})
const [toggle, setToggle] = useState(false)
const [id, setID] = useState(0)
useEffect( () => {
fetch('/data/url/', { id: id }).then( fetchedData => setData(fetchedData) )
}, [id])
這將確保效果只有在修改id後才會重新執行。
清理函式 - 清理函式用於在使用訂閱函式和計時器函式時清理工作,如下所示:
const [time, setTime] = useState(new Date())
useEffect(() => {
let interval = setInterval(() => {
setTime(new Date())
}, 1000)
return () => clearInterval(interval)
}, [time])
讓我們在後面的章節建立一個完整的應用程式來了解清理函式。
effect Hook 的特性
effect Hook的一些顯著特性如下:
React允許在函式元件中使用多個effect Hook。這將幫助我們為每個副作用編寫一個函式,並將其設定為單獨的效果。
每個Hook都將按照宣告的順序執行。開發人員應確保正確宣告效果的順序。
依賴項功能可用於提高副作用的效能和正確執行。
清理函式可以防止記憶體洩漏和不必要的事件觸發。
使用effect獲取資料
在本節中,讓我們建立一個應用程式,它將從外部資源獲取資料,並使用useEffect Hook對其進行渲染。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,在元件資料夾下(src/components/NameList.js)建立一個React元件NameList。
function NameList() {
return <div>names</div>
}
export default NameList
在這裡,NameList元件的目的是展示流行的常用名稱列表。
接下來,更新根元件App.js以使用新建立的NameList元件。
import NameList from "./components/NameList";
function App() {
return (
<div style={{ padding: "5px"}}>
<NameList />
</div>
);
}
export default App;
接下來,建立一個json檔案names.json(public/json/names.json),並以json格式儲存流行的名稱,如下所示。
[
{
"id": 1,
"name": "Liam"
},
{
"id": 2,
"name": "Olivia"
},
{
"id": 3,
"name": "Noah"
},
{
"id": 4,
"name": "Emma"
},
{
"id": 5,
"name": "Oliver"
},
{
"id": 6,
"name": "Charlotte"
},
{
"id": 7,
"name": "Elijah"
},
{
"id": 8,
"name": "Amelia"
},
{
"id": 9,
"name": "James"
},
{
"id": 10,
"name": "Ava"
},
{
"id": 11,
"name": "William"
},
{
"id": 12,
"name": "Sophia"
},
{
"id": 13,
"name": "Benjamin"
},
{
"id": 14,
"name": "Isabella"
},
{
"id": 15,
"name": "Lucas"
},
{
"id": 16,
"name": "Mia"
},
{
"id": 17,
"name": "Henry"
},
{
"id": 18,
"name": "Evelyn"
},
{
"id": 19,
"name": "Theodore"
},
{
"id": 20,
"name": "Harper"
}
]
接下來,建立一個新的狀態變數 data,用於在 NameList 元件中儲存流行的名字,如下所示:
const [data, setData] = useState([])
接下來,建立一個新的狀態變數 isLoading,用於儲存載入狀態,如下所示:
const [isLoading, setLoading] = useState([])
接下來,使用 fetch 方法從 json 檔案獲取流行的名字,並將其設定到 useEffect 鉤子內部的 data 狀態變數中。
useEffect(() => {
setTimeout(() => {
fetch("json/names.json")
.then( (response) => response.json())
.then( (json) => { console.log(json); setLoading(false); setData(json); } )
}, 2000)
})
這裡我們有:
使用 setTimeout 方法模擬載入過程。
使用 fetch 方法獲取 json 檔案。
使用 json 方法解析 json 檔案。
使用 setData 將從 json 檔案解析出的名字設定到 data 狀態變數中。
使用 setLoading 設定載入狀態。
接下來,使用 map 方法渲染名字。在獲取資料期間,顯示載入狀態。
<div>
{isLoading && <span>loading...</span>}
{!isLoading && data && <span>Popular names: </span>}
{!isLoading && data && data.map((item) =>
<span key={item.id}>{item.name} </span>
)}
</div>
這裡我們有:
使用 isLoading 顯示載入狀態。
使用 data 變數顯示流行名字列表。
NameList 元件的完整原始碼如下:
import { useState, useEffect } from "react"
function NameList() {
const [data, setData] = useState([])
const [isLoading, setLoading] = useState([])
useEffect(() => {
setTimeout(() => {
fetch("json/names.json")
.then( (response) => response.json())
.then( (json) => { console.log(json); setLoading(false); setData(json); } )
}, 2000)
})
return (
<div>
{isLoading && <span>loading...</span>}
{!isLoading && data && <span>Popular names: </span>}
{!isLoading && data && data.map((item) =>
<span key={item.id}>{item.name} </span>
)}
</div>
)
}
export default NameList
接下來,開啟瀏覽器並檢查應用程式。它將顯示載入狀態,2 秒後,它將獲取 json 資料並顯示流行的名字,如下所示:
DOM 操作
useEffect 鉤子可用於使用 DOM 及其方法操作文件。它確保其中的程式碼只在 DOM 準備好後執行。讓我們修改我們的名字列表應用程式,並使用 DOM 操作更新頁面的標題。
首先,開啟 NameList 元件,並根據載入狀態新增文件標題,如下所示:
useEffect(() => {
if(isLoading)
document.title = "Loading popular names..."
else
document.title = "Popular name list"
setTimeout(() => {
fetch("json/names.json")
.then( (response) => response.json())
.then( (json) => { console.log(json); setLoading(false); setData(json);} )
}, 2000)
})
在這裡,我們使用了 DOM 物件 document.title 來更新頁面的標題。
最後,開啟瀏覽器並檢查文件標題是如何透過 DOM 操作更新的。
清理函式
useEffect 可用於在元件從頁面文件解除安裝期間移除清理函式,例如 clearInterval、removeEventListener 等。這將防止記憶體洩漏並提高效能。為此,我們可以建立自己的清理函式並將其從 useEffect 回撥引數返回。
讓我們修改我們的名字列表應用程式,使用 setInterval 代替 setTimeout,然後使用 clearInterval 在解除安裝元件期間移除設定的回撥函式。
首先,開啟 NameList 元件並更新 useEffect 部分,如下所示:
useEffect(() => {
if(isLoading)
document.title = "Loading popular names..."
else
document.title = "Popular name list"
let interval = setInterval(() => {
setLoading(true)
fetch("json/names.json")
.then( (response) => response.json())
.then( (json) => { console.log(json); setLoading(false); setData(json);} )
}, 5000)
return () => { clearInterval(interval) }
})
這裡我們有:
使用 setInterval 每 5 秒更新一次流行的名字。
在清理函式中使用 clearInterval 在解除安裝元件期間移除 setInterval。
最後,開啟瀏覽器並檢查應用程式的行為。我們將看到資料每 5 秒更新一次。當元件解除安裝時,clearInterval 將在後臺被呼叫。
總結
useEffect 是函式元件的一個重要特性,它使元件能夠使用生命週期事件。它幫助函式元件提供具有可預測性和最佳化效能的豐富功能。
ReactJS - 使用 useContext
Context 是 React 中一個重要的概念。它提供了一種將資訊從父元件傳遞到其所有子元件(任何巢狀級別)的能力,而無需在每一級都透過 props 傳遞資訊。Context 將使程式碼更易讀且更易於理解。Context 可用於儲存資訊,這些資訊不會改變或幾乎不會改變。Context 的一些用例如下:
應用程式配置
當前已認證使用者資訊
當前使用者設定
語言設定
應用程式/使用者主題/設計配置
React 提供了一個特殊的鉤子 useContext,用於在函式元件中訪問和更新 Context 資訊。讓我們在本節學習 Context 及其對應的鉤子。
Context 如何工作?
在理解 useContext 鉤子之前,讓我們回顧一下 Context 的基本概念及其工作原理。Context 包含四個部分:
建立新的 Context
在根元件中設定 Context 提供者
在我們需要 Context 資訊的元件中設定 Context 消費者
訪問 Context 資訊並在渲染方法中使用它
讓我們建立一個應用程式來更好地理解 Context 及其用法。讓我們建立一個全域性 Context 來維護應用程式根元件中的主題資訊,並在我們的子元件中使用它。
首先,使用以下命令建立並啟動一個應用程式:
create-react-app myapp cd myapp npm start
接下來,在 components 資料夾下建立一個元件 HelloWorld (src/components/HelloWorld.js)
import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
render() {
return <div>Hello World</div>
}
}
export default HelloWorld
接下來,建立一個新的 Context (src/ThemeContext.js) 用於維護主題資訊。
import React from 'react'
const ThemeContext = React.createContext({
color: 'black',
backgroundColor: 'white'
})
export default ThemeContext
這裡:
使用 React.createContext 建立一個新的 Context。
Context 被建模為具有樣式資訊的 objects。
設定文字顏色和背景的初始值。
接下來,更新根元件 App.js,包含 HelloWorld 元件和主題提供者,併為主題 Context 設定初始值。
import './App.css';
import HelloWorld from './components/HelloWorld';
import ThemeContext from './ThemeContext'
function App() {
return (
<ThemeContext.Provider value={{
color: 'white',
backgroundColor: 'green'
}}>
<HelloWorld />
</ThemeContext.Provider>
);
}
export default App;
這裡使用了 ThemeContext.Provider,這是一個非視覺元件,用於設定主題 Context 的值,以便在其所有子元件中使用。
接下來,在 HelloWorld 元件中包含一個 Context 消費者,並使用 HelloWorld 元件中的主題資訊設定 hello world 訊息的樣式。
import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{
( {color, backgroundColor} ) =>
(<div style={{
color: color,
backgroundColor: backgroundColor }}>
Hello World
</div>)
}
</ThemeContext.Consumer>)
}
}
export default HelloWorld
這裡我們有:
使用 ThemeContext.Consumer,這是一個非視覺元件,提供對當前主題 Context 詳細資訊的訪問。
使用函式表示式在 ThemeContext.Consumer 內獲取當前 Context 資訊。
使用物件解構語法獲取主題資訊並將值設定為 color 和 backgroundColor 變數。
使用主題資訊透過 style 屬性設定元件的樣式。
最後,開啟瀏覽器並檢查應用程式的輸出。
useContext 的簽名
useContext 的簽名如下:
let contextValue = useContext( <contextName> )
這裡:
contextName 指的是要訪問的 Context 的名稱。
contextValue 指的是所引用 Context 的當前值。
使用鉤子訪問 Context 的示例程式碼如下:
const theme = useContext(ThemContext)
透過鉤子使用 Context
讓我們更新我們的應用程式,並使用 Context 鉤子代替 Context 消費者。
首先,將 HelloWorld 元件轉換為函式元件。
import React from "react";
function HelloWorld() {
return <div>Hello World</div>
}
export default HelloWorld
接下來,透過 useContext 鉤子訪問 Context 的當前值。
import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
let theme = useContext(ThemeContext)
return <div>Hello World</div>
}
export default HelloWorld
接下來,更新渲染函式以使用透過 Context 獲取的主題資訊。
import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
let theme = useContext(ThemeContext)
return (
<div style={{
color: theme.color,
backgroundColor: theme.backgroundColor }}>
Hello World
</div>
)
}
export default HelloWorld
這裡我們有:
使用 useContext 訪問 ThemeContext Context 資訊。
使用 ThemeContext 資訊設定文字的背景顏色和顏色。
最後,開啟瀏覽器並檢查應用程式的輸出。
更新 Context
在某些情況下,需要更新 Context 資訊。例如,我們可能會提供一個選項,讓使用者更改主題資訊。當用戶更改主題時,Context 應該被更新。更新 Context 將重新渲染所有子元件,這將更改應用程式的主題。
React 提供了一個選項,可以使用 useState 和 useContext 鉤子來更新 Context。讓我們更新我們的應用程式以支援主題選擇。
首先,更新根元件 App.js 並使用 useState 鉤子管理主題資訊,如下所示:
import './App.css'
import { useState } from 'react'
import HelloWorld from './components/HelloWorld'
import ThemeContext from './ThemeContext'
function App() {
let initialTheme = {
color: 'white',
backgroundColor: 'green'
}
const [theme, setTheme] = useState(initialTheme)
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<HelloWorld />
</ThemeContext.Provider>
);
}
export default App;
這裡我們有:
使用 useState 鉤子在根元件的狀態中設定主題資訊。
主題更新函式 useTheme(由 useState 返回)也作為主題資訊的一部分包含在 Context 中。
接下來,更新 HelloWorld 元件以獲取儲存在 Context 中的主題資訊。
import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
let { theme, setTheme } = useContext(ThemeContext)
return (<div style={{
color: theme.color,
backgroundColor: theme.backgroundColor }}>
<div>Hello World</div>
</div>)
}
export default HelloWorld
接下來,為使用者提供一個選項,可以透過下拉選項更改主題。
import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
let { theme, setTheme } = useContext(ThemeContext)
return (<div style={{
color: theme.color,
backgroundColor: theme.backgroundColor }}>
<div>
<select value={theme.backgroundColor}>
<option value="green">Green</option>
<option value="red">Red</option>
</select>
<div>Hello World</div>
</div>)
}
export default HelloWorld
這裡我們有:
添加了一個帶有兩個選項的下拉框,綠色和紅色。
使用當前主題值設定下拉框的當前值 value={theme.backgroundColor}。
接下來,每當使用者透過 onChange 事件更改主題時,更新 Context。
import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
let { theme, setTheme } = useContext(ThemeContext)
return (<div style={{
color: theme.color,
backgroundColor: theme.backgroundColor }}>
<div>
<select value={theme.backgroundColor}
onChange = {
(e) => {
setTheme({
...theme,
backgroundColor: e.target.value
})
}} >
<option value="green">Green</option>
<option value="red">Red</option>
</select>
</div>
<div>Hello World</div>
</div>)
}
export default HelloWorld
這裡我們有:
將 onChange 事件附加到下拉框。
在事件處理程式內使用 setTheme 函式,並將主題的背景顏色更新為使用者選擇的顏色。
根元件和 HelloWorld 元件的完整程式碼如下:
import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
let { theme, setTheme } = useContext(ThemeContext)
return (<div style={{
color: theme.color,
backgroundColor: theme.backgroundColor }}>
<div>
<select value={theme.backgroundColor}
onChange= {
(e) => {
setTheme({
...theme,
backgroundColor: e.target.value
})
}
} >
<option value="green">Green</option>
<option value="red">Red</option>
</select>
</div>
<div>Hello World</div>
</div>)
}
export default HelloWorld
接下來,開啟瀏覽器並檢查應用程式。
當用戶選擇不同的背景顏色時,它將更新 Context,因此,它將重新渲染元件,並顯示新的主題,如下所示:
總結
Context 降低了在 React 應用程式中維護全域性資料的複雜性。Context 鉤子透過簡化訪問和更新(透過 useState)Context 來進一步降低複雜性。
ReactJS - useRef
React 會隨著元件狀態的變化自動發出 HTML 元素。這極大地簡化了 UI 開發,因為更新元件的狀態就足夠了。傳統上,直接訪問 DOM 元素以更新元件的 UI 是很常見的。
有時我們可能需要回退到直接訪問 DOM 元素並更新元件的 UI。React ref 在這種情況下提供了幫助。它提供對 DOM 元素的直接訪問。此外,它確保元件與 React 虛擬 DOM 和 HTML DOM 順利配合工作。
React 提供了一個函式 createRef 用於在基於類的元件中建立 ref。函式元件中 createRef 的對應項是 useRef 鉤子。讓我們在本節學習如何使用 useRef。
useRef 鉤子的簽名
useRef 的目的是返回一個可變物件,該物件將在重新渲染之間持續存在。useRef 的簽名如下:
<refObj> = useRef(<val>)
這裡:
val 是要為返回的可變物件 refObj 設定的初始值。
refObj 是鉤子返回的物件。
為了將 DOM 物件自動附加到 refObj,應將其設定為元素的 ref 屬性中,如下所示:
<input ref={refObj} />
要訪問附加的 DOM 元素,請使用 refObj 的 current 屬性,如下所示:
const refElement = refObj.current
應用 ref 鉤子
讓我們在本節學習如何透過建立一個 React 應用程式來應用 useRef。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,在元件資料夾下(src/components/RefInput.js)建立一個 React 元件 RefInput。
function RefInput() {
return <div>Hello World</div>
}
export default RefInput
接下來,更新根元件 (App.js) 以使用我們新建立的元件。
import RefInput from "./components/RefInput";
function App() {
return (
<div style={{ padding: "5px"}}>
<RefInput />
</div>
);
}
export default App;
接下來,為 RefInput 元件新增計數器功能,如下所示:
import {useState} from 'react'
function RefInput() {
const [count, setCount] = useState(0)
const handleClick = () => setCount(count + 1)
return (
<div>
<div>Counter: {count} <button onClick={handleClick}>+</button></div>
</div>
)
}
export default RefInput
這裡我們有:
使用 **useState** 鉤子來處理計數器狀態變數 (count)。
在 JSX 中渲染計數器狀態變數。
新增一個按鈕並附加一個點選處理程式事件 (handleClick),該事件將使用 setCount 方法遞增計數器。
接下來,新增一個輸入欄位,並根據使用者在輸入欄位中輸入的值顯示問候訊息,如下所示:
import {useState, useRef} from 'react'
function RefInput() {
const [count, setCount] = useState(0)
const inputRef = useRef(null)
const labelRef = useRef(null)
console.log("RefInput is (re)rendered")
const handleClick = () => setCount(count + 1)
const handleChange = () => labelRef.current.innerText = inputRef.current.
value == "" ? "World" : inputRef.current.value
return (
<div>
<div>Counter: {count} <button onClick={handleClick}>+</button></div>
<div style={{ paddingTop: "5px"}}>
<label>Enter your name: </label><input type="text" name="username"
ref={inputRef} onChange={handleChange}/>
<br />
<div>Hello, <span ref={labelRef}></span></div>
</div>
</div>
)
}
export default RefInput
這裡我們有:
建立一個 ref,inputRef 來表示輸入元素,並透過 ref 屬性將其附加到相關元素。
建立一個另一個 ref,labelRef 來表示問候訊息元素,並透過 ref 屬性將其附加到相關元素。
將事件處理程式 handleChange 附加到輸入元素。事件處理程式使用 inputRef ref 獲取問候訊息,並使用 labelRef ref 更新訊息。
接下來,在瀏覽器中開啟應用程式並輸入您的姓名。應用程式將更新問候訊息,如下所示。
檢查您的控制檯,您會注意到元件沒有重新渲染。因為 React 只有在狀態發生變化時才會重新渲染,而 ref 不會進行任何狀態更改,所以元件不會重新渲染。
接下來,單擊“+”按鈕。它將透過重新渲染元件來更新計數器,因為狀態 (count) 發生了變化。如果您仔細觀察,您會發現訊息保持不變。這種行為的原因是 ref 值在 React 的渲染之間得以保留。
useRef 的用例
useRef 的一些用例如下:
**訪問 JavaScript DOM API** — JavaScript DOM API 提供豐富的功能來操作應用程式的 UI。當應用程式功能需要訪問 JavaScript DOM API 時,可以使用 useRef 來檢索原始 DOM 物件。檢索原始 DOM 物件後,應用程式可以使用 DOM API 訪問所有功能。DOM API 的一些示例如下:
聚焦輸入元素
選擇文字
使用媒體播放 API 播放音訊或影片
**命令式動畫** — Web 動畫 API 透過指令式程式設計而不是宣告式程式設計提供豐富的動畫功能。要使用 Web 動畫 API,我們需要訪問原始 DOM。
**與第三方庫整合** — 由於第三方庫需要訪問原始 DOM 才能實現其功能,因此必須使用 useRef 從 React 獲取 DOM 引用並將其提供給第三方庫。
總結
儘管 React 開發 UI 的方式簡單易用,但在某些情況下,基於 DOM API 開發 UI 也有其自身的優勢。useRef 鉤子非常適合這些場景,並提供簡單幹淨的 API 來直接訪問 DOM 元素及其 API。
ReactJS - 使用 useReducer
*useReducer* 鉤子是 useState 鉤子的高階版本。眾所周知,useState 的目的是管理狀態變數。useState 返回一個函式,該函式接受一個值並使用給定值更新狀態變數。
// counter = 0 const [counter, setCounter] = useState(0) // counter = 1 setCounter(1) // counter = 2 setCounter(2)
**useReducer** 鉤子接受一個 reducer 函式以及初始值,並返回一個排程程式函式。Reducer 函式將接受初始狀態和一個動作(特定場景),然後提供根據動作更新狀態的邏輯。排程程式函式接受動作(和相應的細節)並使用提供的動作呼叫 reducer 函式。
例如,*useReducer* 可用於根據遞增和遞減動作更新計數器狀態。遞增動作將計數器狀態遞增 1,遞減動作將計數器狀態遞減 1。
讓我們在本節中學習如何使用 *useReducer* 鉤子。
useReducer 鉤子的簽名
*useReducer* 鉤子的簽名如下:
const [<state>, <dispatch function>] = useReducer(<reducer function>, <initial argument>, <init function>);
這裡:
**state** 表示要在狀態中維護的資訊
**reducer** 函式是一個 JavaScript 函式,用於根據動作更新狀態。
以下是 reducer 函式的語法:
(<state>, <action>) => <updated state>
其中:
**state** — 當前狀態資訊
**action** — 要執行的動作(應具有執行動作的有效負載)
**updated state** — 更新後的狀態
**initial argument** — 表示狀態的初始值
**init function** — 表示初始化函式,可用於設定狀態的初始值/重置狀態的當前值。如果需要計算初始值,則可以使用 *init 函式*。否則,可以跳過該引數。
應用 reducer 鉤子
讓我們建立一個 React 應用程式來管理待辦事項集合。首先,我們將使用 useState 實現它,然後將其轉換為 use *useReducer*。透過使用這兩個鉤子實現應用程式,我們將瞭解 useReducer 比 useState 的優勢。此外,我們可以根據情況明智地選擇鉤子。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,在元件資料夾下(src/components/TodoList.js)建立一個 React 元件 TodoList。
function TodoList() {
return <div>Todo List</div>
}
export default TodoList
接下來,更新根元件 *App.js* 以使用新建立的 *TodoList* 元件。
import logo from './logo.svg';
import './App.css';
import TodoList from './components/TodoList';
function App() {
return (
<div style={{ padding: "5px"}}>
<TodoList />
</div>
);
}
export default App;
接下來,建立一個變數 todoData 來管理待辦事項列表,並使用 *useState* 鉤子將其設定為狀態。
const [todoData, setTodoData] = useState({
action: '',
items: [],
newItem: null,
id: 0
})
這裡:
**action** 用於表示當前要應用於當前待辦事項列表 (items) 的動作,*add* & *delete*。
**items** 是一個用於儲存當前待辦事項列表的陣列。
**newItem** 是一個用於表示當前待辦事項的物件。該物件將有兩個欄位,*id* 和 *todo*。
**id** 是在刪除動作期間要刪除的當前專案的 ID。
**useState** 用於獲取和設定待辦事項列表 (todoData)。
接下來,渲染當前的待辦事項列表 (todoData.items),以及一個刪除按鈕和一個輸入文字欄位,用於輸入新的待辦事項。
<div>
<p>List of Todo list</p>
<ul>
{todoData.items && todoData.items.map((item) =>
<li key={item.id}>{item.todo} <span><button onClick={(e) =>
handleDeleteButton(item.id, e)}>Delete</button></span></li>
)}
<li><input type="text" name="todo" onChange={handleInput} />
<button onClick={handleAddButton}>Add</button></li>
</ul>
</div>
這裡:
從狀態變數 todoData 渲染當前的待辦事項列表。
為使用者渲染一個輸入欄位以輸入新的待辦事項,並附加 *onChange* 事件處理程式 handleInput。事件處理程式將使用使用者在輸入欄位中輸入的資料更新 todo 狀態 (todoData) 中的 newItem。
為使用者渲染一個按鈕,用於將新輸入的待辦事項新增到當前待辦事項列表中,並附加 onClick 事件處理程式 handleAddButton。事件處理程式將當前/新的待辦事項新增到 todo 狀態。
為待辦事項列表中的每個專案渲染一個按鈕,並附加 *onClick* 事件處理程式 handleDeleteButton。事件處理程式將從 todo 狀態中刪除相應的待辦事項。
接下來,實現 handleInput 事件處理程式,如下所示:
const handleInput = (e) => {
var id = 0
if(todoData.newItem == null) {
for(let i = 0; i < todoData.items.length; i++) {
if(id < todoData.items[i].id) {
id = todoData.items[i].id
}
}
id += 1
} else {
id = todoData.newItem.id
}
let data = {
actions: '',
items: todoData.items,
newItem: {
id: id,
todo: e.target.value
},
id: 0
}
setTodoData(data)
}
這裡我們有:
使用使用者輸入的資料 (e.target.value) 更新 newItem.todo
建立並設定新專案的 ID。
接下來,實現 handleDeleteButton 事件處理程式,如下所示:
const handleDeleteButton = (deleteId, e) => {
let data = {
action: 'delete',
items: todoData.items,
newItem: todoData.newItem,
id: deleteId
}
setTodoData(data)
}
在這裡,處理程式設定要刪除的待辦事項的 ID (deleteid) 和 todo 狀態中的 delete 動作
接下來,實現 handleAddButton 事件處理程式,如下所示:
const handleAddButton = () => {
let data = {
action: 'add',
items: todoData.items,
newItem: todoData.newItem,
id: 0
}
setTodoData(data)
}
在這裡,處理程式設定狀態中的新專案和 add 動作
接下來,實現 add 動作,如下所示:
if(todoData.action == 'add') {
if(todoData.newItem != null) {
let data = {
action: '',
items: [...todoData.items, todoData.newItem],
newItem: null,
id: 0
}
setTodoData(data)
}
}
在這裡,新專案新增到 todo 狀態中現有的列表 (todoData.items) 中。
接下來,實現 delete 動作,如下所示:
if(todoData.action == 'delete' && todoData.id != 0) {
var newItemList = []
for(let i = 0; i < todoData.items.length; i++) {
if(todoData.items[i].id != todoData.id) {
newItemList.push(todoData.items[i])
}
}
let data = {
action: '',
items: newItemList,
newItem: null,
id: 0
}
setTodoData(data)
}
在這裡,將從待辦事項列表 (todoData.items) 中刪除指定 ID 的專案。
元件的完整原始碼如下:
import { useState } from "react"
function TodoList() {
const [todoData, setTodoData] = useState({
action: '',
items: [],
newItem: null,
id: 0
})
if(todoData.action == 'add') {
if(todoData.newItem != null) {
let data = {
action: '',
items: [...todoData.items, todoData.newItem],
newItem: null,
id: 0
}
setTodoData(data)
}
}
if(todoData.action == 'delete' && todoData.id != 0) {
var newItemList = []
for(let i = 0; i < todoData.items.length; i++) {
if(todoData.items[i].id != todoData.id) {
newItemList.push(todoData.items[i])
}
}
let data = {
action: '',
items: newItemList,
newItem: null,
id: 0
}
setTodoData(data)
}
const handleInput = (e) => {
var id = 0
if(todoData.newItem == null) {
for(let i = 0; i < todoData.items.length; i++) {
if(id < todoData.items[i].id) {
id = todoData.items[i].id
}
}
id += 1
} else {
id = todoData.newItem.id
}
let data = {
action: '',
items: todoData.items,
newItem: {
id: id,
todo: e.target.value
},
id: 0
}
setTodoData(data)
}
const handleDeleteButton = (deleteId, e) => {
let data = {
action: 'delete',
items: todoData.items,
newItem: todoData.newItem,
id: deleteId
}
setTodoData(data)
}
const handleAddButton = () => {
let data = {
action: 'add',
items: todoData.items,
newItem: todoData.newItem,
id: 0
}
setTodoData(data)
}
return (
<div>
<p>List of Todo list</p>
<ul>
{todoData.items && todoData.items.map((item) =>
<li key={item.id}>{item.todo} <span><button onClick={(e) => handleDeleteButton(item.id, e)}>Delete</button></span></li>
)}
<li><input type="text" name="todo" onChange={handleInput} /><button onClick={handleAddButton}>Add</button></li>
</ul>
</div>
)
}
export default TodoList
在這裡,應用程式使用 **useState** 鉤子來實現功能。
接下來,開啟瀏覽器並新增/刪除待辦事項。應用程式將按如下所示執行:
使用 useReducer
讓我們使用 *useReducer* 重新實現該功能
首先,在元件資料夾下(src/components/TodoReducerList.js)建立一個 React 元件 TodoReducerList。
function TodoReducerList() {
return <div>Todo List</div>
}
export default TodoReducerList
接下來,更新根元件 *App.js* 以使用新建立的 *TodoReducerList* 元件。
import './App.css';
import TodoReducerList from './components/TodoReducerList';
function App() {
return (
<div style={{ padding: "5px"}}>
<TodoReducerList />
</div>
);
}
export default App;
接下來,實現一個 reducer 函式 todoReducer,它將接收兩個引數,如下所示:
當前待辦事項列表 (items)
操作 (action.type) 以及與操作相關的信(action.payload)。對於 add 操作 (action.type),有效負載 (action.payload) 將包含新的待辦事項;對於 delete 操作 (action.type),它將包含要刪除的待辦事項的 ID
reducer 將將相關操作應用於待辦事項列表,並返回修改後的待辦事項列表,如下所示:
function todoReducer(items, action) {
// action = { type: 'add / delete', payload: 'new todo item'}
let newTodoList = []
switch (action.type) {
case 'add':
var id = 0
for(let i = 0; i < items.length; i++) {
if(id < items[i].id) {
id = items[i].id
}
}
action.payload.id = id + 1
newTodoList = [...items, action.payload]
break;
case 'delete':
for(let i = 0; i < items.length; i++) {
if(items[i].id != action.payload.id) {
newTodoList.push(items[i])
}
}
break;
default:
throw new Error()
}
return newTodoList
}
這裡我們有:
使用 switch 語句處理操作。
*Added* 操作將有效負載新增到現有的待辦事項列表。
*deleted* 操作從現有的待辦事項列表中刪除有效負載中指定的專案
最後,函式返回更新後的待辦事項列表
接下來,在新建立的 **TodoReducerList** 元件中使用新建立的 reducer,如下所示:
const [items, dispatch] = useReducer(todoReducer, [])
在這裡,**useReducer** 接收兩個引數:a) reducer 函式和 b) 當前待辦事項列表,並返回當前待辦事項列表和一個排程程式函式 *dispatch*。應使用相關有效負載資訊使用 add 或 delete 操作呼叫排程程式函式 (dispatch)。
接下來,為輸入文字欄位(新的待辦事項)和兩個按鈕(新增和刪除)建立處理程式函式,如下所示:
const [todo, setTodo] = useState('')
const handleInput = (e) => {
setTodo(e.target.value)
}
const handleAddButton = () => {
dispatch({
type: 'add',
payload: {
todo: todo
}
})
setTodo('')
}
const handleDeleteButton = (id) => {
dispatch({
type: 'delete',
payload: {
id: id
}
})
}
這裡我們有:
使用 **useState** 管理使用者輸入 (todo)
使用 **dispatch** 方法在相關的處理程式中處理 *add* 和 *delete* 操作
在處理程式中傳遞特定於操作的有效負載。
接下來,更新 render 方法,如下所示:
<div>
<p>List of Todo list</p>
<ul>
{items && items.map((item) =>
<li key={item.id}>{item.todo} <span><button onClick={(e) =>
handleDeleteButton(item.id)}>Delete</button></span></li>
)}
<li><input type="text" name="todo" value={todo} onChange={handleInput} />
<button onClick={handleAddButton}>Add</button></li>
</ul>
</div>
元件的完整原始碼以及 reducer 函式如下所示:
import {
useReducer,
useState
} from "react"
function todoReducer(items, action) {
// action = { type: 'add / delete', payload: 'new todo item'}
let newTodoList = []
switch (action.type) {
case 'add':
var id = 0
for(let i = 0; i < items.length; i++) {
if(id < items[i].id) {
id = items[i].id
}
}
action.payload.id = id + 1
newTodoList = [...items, action.payload]
break;
case 'delete':
for(let i = 0; i < items.length; i++) {
if(items[i].id != action.payload.id) {
newTodoList.push(items[i])
}
}
break;
default:
throw new Error()
}
return newTodoList
}
function TodoReducerList() {
const [todo, setTodo] = useState('')
const [items, dispatch] = useReducer(todoReducer, [])
const handleInput = (e) => {
setTodo(e.target.value)
}
const handleAddButton = () => {
dispatch({
type: 'add',
payload: {
todo: todo
}
})
setTodo('')
}
const handleDeleteButton = (id) => {
dispatch({
type: 'delete',
payload: {
id: id
}
})
}
return (
<div>
<p>List of Todo list</p>
<ul>
{items && items.map((item) =>
<li key={item.id}>{item.todo} <span><button onClick={(e) =>
handleDeleteButton(item.id)}>Delete</button></span></li>
)}
<li><input type="text" name="todo" value={todo} onChange={handleInput} />
<button onClick={handleAddButton}>Add</button></li>
</ul>
</div>
)
}
export default TodoReducerList
接下來,開啟瀏覽器並檢查輸出。
我們可以清楚地理解,與純 useState 實現相比,*useReducer* 實現簡單、易用且易於理解。
總結
*useReducer* 鉤子引入了狀態管理中的 reducer 模式。它鼓勵程式碼重用,並提高元件的可讀性和可理解性。總的來說,useReducer 是 React 開發人員工具包中必不可少且強大的工具。
ReactJS - useCallback
**useCallback** 鉤子類似於 *useMemo* 鉤子,它提供記憶函式而不是值的的功能。由於回撥函式是 JavaScript 程式設計中不可或缺的一部分,並且回撥函式是透過引用傳遞的,因此 React 提供了一個單獨的鉤子 **useCallback** 來記憶回撥函式。理論上,可以使用 useMemo 鉤子本身來完成 *useCallback* 的功能。但是,useCallback 提高了 React 程式碼的可讀性。
*useCallback* 鉤子的簽名
*useCallback* 鉤子的簽名如下:
const <memoized_callback_fn> = useCallback(<callback_fn>, <dependency_array>);
在這裡,*useCallback* 接受兩個輸入並返回一個記憶的回撥函式。輸入引數如下:
callback_fn − 需要記憶化的回撥函式。
dependency_array − 儲存回撥函式依賴的變數。
useCallback 鉤子的輸出是回撥函式 `callback_fn` 的記憶化版本。 `useCallback` 鉤子的用法如下:
const memoizedCallbackFn = useCallback(() => {
// code
}, [a, b])
注意 − `useCallback(callback_fn, dependency_array)` 等價於 `useMemo(() => callback_fn, dependency_array)`。
應用 useCallback
讓我們學習如何透過建立一個 React 應用來應用 `useCallback` 鉤子。
首先,使用 `create-react-app` 命令建立一個並啟動一個 React 應用,如下所示:
create-react-app myapp cd myapp npm start
接下來,在 `components` 資料夾下建立一個元件 `PureListComponent` (`src/components/PureListComponent.js`)。
import React from "react";
function PureListComponent() {
return <div>List</div>
}
export default PureListComponent
接下來,更新根元件,使用 `PureListComponent` 元件,如下所示:
import PureListComponent from './components/PureListComponent';
function App() {
return (
<div style={{ padding: "5px" }}>
<PureListComponent />
</div>
);
}
export default App;
接下來,開啟 PureListComponent.js 並新增兩個 props:
要在元件中顯示的專案列表
一個回撥函式,用於在控制檯中顯示使用者點選的專案
import React from "react";
function PureListComponent(props) {
const items = props['items'];
const handleClick = props['handleClick']
console.log("I am inside the PureListComponent")
return (
<div>
{items.map((item, idx) =>
<span key={idx} onClick={handleClick}>{item} </span>
)}
</div>
)
}
export default React.memo(PureListComponent)
這裡我們有:
使用 items props 獲取專案列表
使用 handleClick 獲取點選事件的處理程式
使用 `React.memo` 包裝元件以記憶化元件。由於該元件對於給定的輸入集將渲染相同的輸出,因此在 React 中被稱為 `PureComponent`。
接下來,讓我們更新 App.js 並使用 PureListComponent,如下所示:
import React, {useState, useEffect} from 'react';
import PureListComponent from './components/PureListComponent';
function App() {
// array of numbers
var listOfNumbers = [...Array(100).keys()];
// callback function
const handleCallbackFn = (e) => { console.log(e.target.innerText) }
const [currentTime, setCurrentTime] = useState(new Date())
useEffect(() => {
let interval = setInterval(() => {
setCurrentTime(new Date())
}, 1000)
return () => clearInterval(interval)
}, [currentTime])
return (
<div style={ { padding: "5px" } }>
<PureListComponent items={listOfNumbers} handleClick={handleCallbackFn}/>
<div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
</div>
);
}
export default App;
這裡我們包含了一個狀態 currentTime,並使用 setInterval 每秒更新一次,以確保元件每秒重新渲染。
我們可能認為 PureListComponent 不會每秒重新渲染,因為它使用了 React.memo。但是,它會重新渲染,因為 props 的值是引用型別。
接下來,更新根元件並使用 useMemo 和 useCallback 來保留陣列和回撥函式,如下所示:
import React, {useState, useEffect, useCallback, useMemo} from 'react';
import PureListComponent from './components/PureListComponent';
function App() {
// array of numbers
const listOfNumbers = useMemo(() => [...Array(100).keys()], []);
// callback function
const handleCallbackFn = useCallback((e) => console.log(e.target.innerText), [])
const [currentTime, setCurrentTime] = useState(new Date())
useEffect(() => {
let interval = setInterval(() => {
setCurrentTime(new Date())
}, 1000)
return () => clearInterval(interval)
}, [currentTime])
return (
<div style={ { padding: "5px" } }>
<PureListComponent items={listOfNumbers} handleClick={handleCallbackFn}/>
<div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
</div>
);
}
export default App;
這裡我們有:
使用 useMemo 保留 items 陣列
使用 useCallback 保留 handleClick 回撥函式
最後,在瀏覽器中檢查應用程式,它將不會每秒重新渲染 `PureListComponent`。
useCallback 的用例
useCallback 鉤子的一些用例如下:
具有函式 props 的純函式元件
useEffect 或其他將函式作為依賴項的鉤子
在去抖動/節流或類似操作期間使用函式
優點
useCallback 鉤子的優點如下:
易於使用的 API
易於理解
提高應用程式的效能
缺點
從技術上講,useCallback 沒有缺點。但是,在應用程式中大量使用 useCallback 會帶來以下缺點。
降低應用程式的可讀性
降低應用程式的可理解性
應用程式除錯複雜
開發人員需要深入瞭解 JavaScript 語言才能使用它
總結
useCallback 易於使用並提高效能。同時,useCallback 應該只在絕對必要時使用。不應在每種情況下都使用它。一般規則是檢查應用程式的效能。如果需要改進,那麼我們可以檢查使用 useCallback 是否有助於提高效能。如果我們得到肯定的答覆,那麼我們可以使用它。否則,我們可以將其留給 React 來改進和最佳化應用程式的效能。
鉤子:useMemo
React 提供了一套核心內建鉤子來簡化開發並構建高效的應用程式。應用程式的一個重要特性是良好的效能。每個應用程式都將具有一定的複雜計算邏輯,這將降低應用程式的效能。我們有兩種選擇來應對複雜的計算。
改進複雜計算的演算法並使其加快。
僅在絕對必要時才呼叫複雜計算。
useMemo 鉤子透過提供僅在絕對必要時才呼叫複雜計算的選項來幫助我們提高應用程式的效能。useMemo 保留/記憶化複雜計算的輸出,並返回它,而不是重新計算複雜計算。useMemo 將知道何時使用記憶化值以及何時重新運行復雜計算。
useMemo 鉤子的簽名
useMemo 鉤子的簽名如下:
const <output of the expensive logic> = useMemo(<expensive_fn>, <dependency_array>);
這裡,useMemo 接受兩個輸入並返回一個計算值。
輸入引數如下:
expensive_fn − 執行耗時的邏輯並將輸出返回以進行記憶化。
dependency_array − 儲存耗時邏輯依賴的變數。如果陣列的值發生更改,則必須重新執行耗時邏輯並更新值。
useMemo 鉤子的輸出是 expensive_fn 的輸出。useMemo 將透過執行函式或獲取記憶化值來返回輸出。
useMemo 鉤子的用法如下:
const limit = 1000;
const val = useMemo(() => {
var sum = 0;
for(let i = 1; i <= limit; i++) {
sum += i;
}
return sum;
}, [limit])
這裡,求和邏輯將在開始時執行一次,以及每當 limit 值更改/更新時。在此期間,記憶化值由 useMemo 鉤子返回。
應用 memo 鉤子
讓我們透過在一個 React 應用中應用鉤子來深入學習 useMemo 鉤子。
首先,使用 `create-react-app` 命令建立一個並啟動一個 React 應用,如下所示:
create-react-app myapp cd myapp npm start
接下來,在 `components` 資料夾下建立一個元件 `Sum` (`src/components/Sum.js`)。
import React from "react";
function Sum() {
return <div>Sum</div>
}
export default Sum
接下來,使用我們的 Sum 元件更新根元件,如下所示:
import './App.css';
import Sum from './components/Sum';
function App() {
return (
<div>
<Sum />
</div>
);
}
export default App;
接下來,開啟 Sum.js 並新增一個狀態來表示當前時間,並透過 `setInterval` 更新當前時間,如下所示:
import React, {
useState,
useEffect,
useMemo
} from "react";
function Sum() {
let [currentTime, setCurrentTime] = useState(new Date())
useEffect(() => {
let interval = setInterval(() => setCurrentTime(new Date()), 1000)
return () => { clearInterval(interval) }
})
const limit = 1000;
var sum = 0;
for(let i = 1; i <= limit; i++) {
sum += i;
}
return (
<div style={ {padding: "5px" } }>
<div>Summation of values from <i>1</i> to <i>{limit}</i>:
<b>{sum}</b></div>
<div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
</div>
)
}
export default Sum
這裡我們有:
使用 setState 使用 `useState` 鉤子在狀態中設定當前時間。它返回兩個變數;一個狀態變數 currentTime 和一個函式 setCurrentTime 用於更新狀態變數 currentTime。
使用 useEffect 鉤子使用 `setInterval` 函式以 1 秒的規則間隔更新時間。我們定期更新時間以確保元件定期重新渲染。
使用 clearInterval 在元件解除安裝時移除更新時間功能。
透過 JSX 在文件中顯示 sum 和 currentTime 值
Sum 元件每秒重新渲染一次以更新 currentTime。在每次渲染期間,即使沒有必要,求和邏輯也會執行。
接下來,讓我們引入 useMemo 鉤子來防止在重新渲染之間重新計算求和邏輯,如下所示:
import React, { useState, useEffect, useMemo } from "react";
function Sum() {
let [currentTime, setCurrentTime] = useState(new Date())
useEffect(() => {
let interval = setInterval(() => setCurrentTime(new Date()), 1000)
return () => { clearInterval(interval) }
})
const limit = 1000;
const sum = useMemo(() => {
var sum = 0;
for(let i = 1; i <= limit; i++) {
sum += i;
}
return sum;
}, [limit])
return (
<div style={ {padding: "5px" } }>
<div>Summation of values from <i>1</i> to <i>{limit}</i>: <b>{sum}</b></div>
<div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
</div>
)
}
export default Sum
在這裡,我們將求和邏輯轉換為一個函式,並將其傳遞給 useMemo 鉤子。這將防止在每次重新渲染時重新計算求和邏輯。
limit 設定為依賴項。這將確保在 limit 值更改之前不會執行求和邏輯。
接下來,新增一個輸入來更改 limit,如下所示:
import React, {
useState,
useEffect,
useMemo
} from "react";
function Sum() {
let [currentTime, setCurrentTime] = useState(new Date())
let [limit, setLimit] = useState(10);
useEffect(() => {
let interval = setInterval(() => setCurrentTime(new Date()), 1000)
return () => { clearInterval(interval) }
})
const sum = useMemo(() => {
var sum = 0;
for(let i = 1; i <= limit; i++) {
sum += i;
}
return sum;
}, [limit])
return (<div style={ {padding: "5px" } }>
<div><input value={limit} onChange={ (e) => { setLimit(e.target.value) }} /></div>
<div>Summation of values from <i>1</i> to <i>{limit}</i>: <b>{sum}</b></div>
<div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
</div>)
}
export default Sum
這裡我們有:
使用 useState 鉤子管理 limit 變數。useState 鉤子將返回兩個變數;一個狀態變數 limit 和一個函式變數 setLimit 用於更新 limit 變數。
添加了一個新的輸入元素,允許使用者為 limit 變數輸入新值
添加了一個事件 onChange,以便在使用者更新時更新 limit 變數。
當用戶在輸入元素中輸入新值時,
onChange 事件將被觸發。
onChange 事件將呼叫 setLimit 函式。
limit 變數將由 setLimit 函式更新。
由於狀態變數 limit 已更新,Sum 元件將重新渲染。
由於 limit 變數已更新,useMemo 將重新執行邏輯,並將新的值記憶化並將其設定為 sum 變數。
render 函式將使用 sum 變數的新值並將其渲染。
最後,開啟瀏覽器並檢查應用程式。
保留引用
讓我們在本節中瞭解如何結合引用資料型別使用 useMemo 鉤子。一般來說,變數的資料型別分為值型別和引用型別。值型別在棧中管理,它們作為值在函式之間傳遞。但是,引用資料型別在堆中管理,並使用其記憶體地址或簡單的引用在函式之間傳遞。
值型別如下:
數字
字串
布林值
null
undefined
引用資料型別如下:
陣列
物件
函式
一般來說,即使兩個變數的值相同,具有引用資料型別的變數也不容易比較。例如,讓我們考慮兩個具有值型別的變數和另外兩個具有引用資料型別的變數,並按如下所示進行比較:
var a = 10 var b = 10 a == b // true var x = [10, 20] var y = [10, 20] x == y // false
即使 x 和 y 在值方面相同,它們也指向記憶體中的不同位置。useMemo 可用於正確記憶化具有引用資料型別的變數。
useMemo(x) == useMemo(y) // true
讓我們建立一個新的 React 應用程式,以更好地理解使用 useMemo 處理引用資料型別。
首先,使用 `create-react-app` 命令建立一個並啟動一個 React 應用,如下所示:
create-react-app myapp cd myapp npm start
接下來,在 `components` 資料夾下建立一個元件 `PureSumComponent` (`src/components/PureSumComponent.js`)。
import React from "react";
function PureSumComponent() {
return <div>Sum</div>
}
export default PureSumComponent
接下來,使用 PureSumComponent 元件更新根元件,如下所示:
import './App.css';
import PureSumComponent from './components/PureSumComponent';
function App() {
return (
<div style={{ padding: "5px" }}>
<PureSumComponent />
</div>
);
}
export default App;
接下來,開啟 PureSumComponent.js 並新增一個 props `range` 來表示數字範圍,如下所示:
import React from "react";
function PureSumComponent(props) {
const range = props['range'];
const start = range[0];
const end = range[1];
console.log('I am inside PureSumComponent component')
var sum = 0;
for(let i = start; i <= end; i++) {
sum += i;
}
return (
<div>
<div>Summation of values from <i>{start}</i> to
<i>{end}</i>: <b>{sum}</b></div>
</div>
)
}
export default React.memo(PureSumComponent)
這裡:
使用 range props 計算值的範圍之和。range 是一個包含兩個數字的陣列,表示範圍的開始和結束。
透過 JSX 在文件中顯示 start、end 和 sum 值
使用 `React.memo` 包裝元件以記憶化元件。由於該元件對於給定的輸入集將渲染相同的輸出,因此在 React 中被稱為 PureComponent。
接下來,讓我們更新 App.js 並使用 PureSumComponent,如下所示:
import React, {useState, useEffect} from 'react';
import PureSumComponent from './components/PureSumComponent';
function App() {
var input = [1,1000]
const [currentTime, setCurrentTime] = useState(new Date())
useEffect(() => {
let interval = setInterval(() => {
setCurrentTime(new Date())
}, 1000)
return () => clearInterval(interval)
}, [currentTime])
return (
<div style={ {padding: "5px" } }>
<PureSumComponent range={input}/>
<div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
</div>
);
}
export default App;
在這裡,我們包含了一個狀態 currentTime,並使用 setInterval 每秒更新一次,以確保元件每秒重新渲染。
我們可能認為 PureSumComponent 不會每秒重新渲染,因為它使用了 React.memo。但是,它會重新渲染,因為 props 是一個數組,並且它將每秒建立一個新的引用。您可以透過檢查控制檯來確認這一點。
接下來,更新根元件並使用 useMemo 來保留 range 陣列,如下所示:
import React, {useState, useEffect, useMemo} from 'react';
import PureSumComponent from './components/PureSumComponent';
function App() {
const input = useMemo(() => [1, 1000], [])
const [currentTime, setCurrentTime] = useState(new Date())
useEffect(() => {
let interval = setInterval(() => {
setCurrentTime(new Date())
}, 1000)
return () => clearInterval(interval)
}, [currentTime])
return (
<div style={ {padding: "5px" } }>
<PureSumComponent range={input}/>
<div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
</div>
);
}
export default App;
在這裡,我們使用了 useMemo 來保留 range 陣列
最後,在瀏覽器中檢查應用程式,它將不會每秒重新渲染 PureSumComponent。
優點
useMemo 鉤子的優點如下:
易於使用的 API
易於理解
提高應用程式的效能
缺點
從技術上講,useMemo 沒有缺點。但是,在應用程式中大量使用 useMemo 會帶來以下缺點。
降低應用程式的可讀性
降低應用程式的可理解性
應用程式除錯複雜
開發人員需要深入瞭解 JavaScript 語言才能使用它
總結
通常情況下,React 會內部最佳化應用程式並提供高效能。但某些場景下,我們可能需要介入並進行自定義最佳化。`useMemo` 鉤子函式在這種情況下提供了一個簡單的解決方案,可以提升 React 應用程式的效能。總而言之,只有在絕對必要時才使用 `useMemo` 鉤子函式。否則,就將最佳化和高效能的提供留給 React 本身。
ReactJS - 自定義 Hooks
鉤子函式是函式元件不可或缺的一部分。它們可以用來增強函式元件的功能。React 提供了一些內建鉤子函式。儘管內建鉤子函式功能強大,可以實現任何功能,但專用鉤子函式仍然是必要的,並且需求量很大。
React 理解了開發者的這種需求,並允許透過現有的鉤子函式建立新的自定義鉤子函式。開發者可以從函式元件中提取特殊的功能,並將其建立為一個單獨的鉤子函式,可以在任何函式元件中使用。
本章讓我們學習如何建立自定義鉤子函式。
建立自定義鉤子函式
讓我們建立一個具有無限滾動功能的新 React 函式元件,然後從函式元件中提取無限滾動功能,並建立一個自定義鉤子函式。建立自定義鉤子函式後,我們將嘗試修改原始函式元件以使用我們的自定義鉤子函式。
實現無限滾動功能
元件的基本功能是簡單地透過生成虛擬待辦事項列表來顯示它。當用戶滾動時,元件將生成一組新的虛擬待辦事項列表,並將其附加到現有列表中。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,在元件資料夾下(src/components/TodoList.js)建立一個 React 元件 TodoList。
function TodoList() {
return <div>Todo List</div>
}
export default TodoList
接下來,更新根元件 *App.js* 以使用新建立的 *TodoList* 元件。
import TodoList from './components/TodoList';
function App() {
return (
<div style={{ padding: "5px"}}>
<TodoList />
</div>
);
}
export default App;
接下來,建立一個計數狀態變數來維護要生成的待辦事項數量。
const [count, setCount] = useState(100)
這裡:
初始要生成的專案數量為 100。
`count` 是用於維護待辦事項數量的狀態變數。
接下來,建立一個數據陣列來維護生成的虛擬待辦事項,並使用 `useMemo` 鉤子函式保留引用。
React.useMemo(() => {
for (let i = 1; i <= count; i++) {
data.push({
id: i,
todo: "Todo Item " + i.toString()
})
}
}, [count]);
這裡:
使用 `useMemo` 限制每次渲染時待辦事項的生成。
使用 `count` 作為依賴項,在計數更改時重新生成待辦事項。
接下來,為滾動事件附加一個處理程式,並在使用者移動到頁面末尾時更新要生成的待辦事項計數。
React.useEffect(() => {
function handleScroll() {
const isBottom =
window.innerHeight + document.documentElement.scrollTop
=== document.documentElement.offsetHeight;
if (isBottom) {
setCount(count + 100)
}
}
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [])
這裡我們有:
使用 `useEffect` 鉤子函式確保 DOM 準備好附加事件。
為滾動事件附加了一個事件處理程式 `scrollHandler`。
當元件從應用程式中解除安裝時,移除事件處理程式。
在滾動事件處理程式中使用 DOM 屬性發現使用者到達頁面底部。
一旦使用者到達頁面底部,計數就會增加 100。
一旦計數狀態變數發生變化,元件就會重新渲染,並載入更多待辦事項。
最後,按如下所示渲染待辦事項:
<div>
<p>List of Todo list</p>
<ul>
{data && data.map((item) =>
<li key={item.id}>{item.todo}</li>
)}
</ul>
</div>
元件的完整原始碼如下:
import React, { useState } from "react"
function TodoList() {
const [count, setCount] = useState(100)
let data = []
React.useMemo(() => {
for (let i = 1; i <= count; i++) {
data.push({
id: i,
todo: "Todo Item " + i.toString()
})
}
}, [count]);
React.useEffect(() => {
function handleScroll() {
const isBottom =
window.innerHeight + document.documentElement.scrollTop
=== document.documentElement.offsetHeight;
if (isBottom) {
setCount(count + 100)
}
}
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [])
return (
<div>
<p>List of Todo list</p>
<ul>
{data && data.map((item) =>
<li key={item.id}>{item.todo}</li>
)}
</ul>
</div>
)
}
export default TodoList
接下來,開啟瀏覽器並執行應用程式。當用戶到達頁面底部時,應用程式將無限地附加新的待辦事項,如下所示:
實現 `useInfiniteScroll` 鉤子函式
接下來,讓我們嘗試透過從現有元件中提取邏輯來建立一個新的自定義鉤子函式,然後將其用於單獨的元件中。建立一個新的自定義鉤子函式 `useInfiniteScroll` (src/Hooks/useInfiniteScroll.js),如下所示
import React from "react";
export default function useInfiniteScroll(loadDataFn) {
const [bottom, setBottom] = React.useState(false);
React.useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
React.useEffect(() => {
if (!bottom) return;
loadDataFn();
}, [bottom]);
function handleScroll() {
if (window.innerHeight + document.documentElement.scrollTop
!= document.documentElement.offsetHeight) {
return;
}
setBottom(true)
}
return [bottom, setBottom];
}
這裡我們有:
使用 `use` 關鍵字命名自定義鉤子函式。這是以 `use` 關鍵字開頭命名自定義鉤子函式的常用做法,它也是 React 識別該函式是自定義鉤子函式的提示。
使用一個狀態變數 `bottom` 來判斷使用者是否到達頁面底部。
使用 `useEffect` 在 DOM 可用後註冊滾動事件。
使用一個泛型函式 `loadDataFn` 在元件中建立鉤子函式時提供。這將能夠為載入資料建立自定義邏輯。
在滾動事件處理程式中使用 DOM 屬性來跟蹤使用者滾動位置。當用戶到達頁面底部時,`bottom` 狀態變數會發生變化。
返回 `bottom` 狀態變數的當前值和用於更新 `bottom` 狀態變數的函式 (`setBottom`)。
接下來,建立一個新的元件 `TodoListUsingCustomHook` 來應用新建立的鉤子函式。
function TodoListUsingCustomHook() {
return <div>Todo List</div>
}
export default TodoListUsingCustomHook
接下來,更新根元件 `App.js` 以使用新建立的 `TodoListUsingCustomHook` 元件。
import TodoListUsingCustomHook from './components/TodoListUsingCustomHook';
function App() {
return (
<div style={{ padding: "5px"}}>
<TodoListUsingCustomHook />
</div>
);
}
export default App;
接下來,建立一個狀態變數 `data` 來管理待辦事項列表。
const [data, setData] = useState([])
接下來,建立一個狀態變數 `count` 來管理要生成的待辦事項列表的數量。
const [data, setData] = useState([])
接下來,應用無限滾動自定義鉤子函式。
const [bottom, setBottom] = useInfiniteScroll(loadMoreData)
這裡,`bottom` 和 `setBottom` 由 `useInfiniteScroll` 鉤子函式公開。
接下來,建立生成待辦事項列表的函式 `loadMoreData`。
function loadMoreData() {
let data = []
for (let i = 1; i <= count; i++) {
data.push({
id: i,
todo: "Todo Item " + i.toString()
})
}
setData(data)
setCount(count + 100)
setBottom(false)
}
這裡:
根據 `count` 狀態變數生成待辦事項。
將 `count` 狀態變數增加 100。
使用 `setData` 函式將生成的待辦事項列表設定為 `data` 狀態變數。
使用 `setBottom` 函式將 `bottom` 狀態變數設定為 `false`。
接下來,生成初始待辦事項,如下所示:
const loadData = () => {
let data = []
for (let i = 1; i <= count; i++) {
data.push({
id: i,
todo: "Todo Item " + i.toString()
})
}
setData(data)
}
React.useEffect(() => {
loadData(count)
}, [])
這裡:
初始待辦事項是根據初始 `count` 狀態變數生成的。
使用 `setData` 函式將生成的待辦事項列表設定為 `data` 狀態變數。
使用 `useEffect` 確保在 DOM 可用後生成資料。
最後,按如下所示渲染待辦事項:
<div>
<p>List of Todo list</p>
<ul>
{data && data.map((item) =>
<li key={item.id}>{item.todo}</li>
)}
</ul>
</div>
元件的完整原始碼如下所示:
import React, { useState } from "react"
import useInfiniteScroll from '../Hooks/useInfiniteScroll'
function TodoListUsingCustomHook() {
const [data, setData] = useState([])
const [count, setCount] = useState(100)
const [bottom, setBottom] = useInfiniteScroll(loadMoreData)
const loadData = () => {
let data = []
for (let i = 1; i <= count; i++) {
data.push({
id: i,
todo: "Todo Item " + i.toString()
})
}
setData(data)
}
function loadMoreData() {
let data = []
for (let i = 1; i <= count; i++) {
data.push({
id: i,
todo: "Todo Item " + i.toString()
})
}
setData(data)
setCount(count + 100)
setBottom(false)
}
React.useEffect(() => {
loadData(count)
}, [])
return (
<div>
<p>List of Todo list</p>
<ul>
{data && data.map((item) =>
<li key={item.id}>{item.todo}</li>
)}
</ul>
</div>
)
}
export default TodoListUsingCustomHook
最後,開啟瀏覽器並檢查輸出。當用戶到達頁面底部時,應用程式將無限地附加新的待辦事項,如下所示:
行為與 TodoList 元件相同。我們已經成功地從元件中提取了邏輯,並用它建立了我們的第一個自定義鉤子函式。現在,可以在任何應用程式中使用此自定義鉤子函式。
總結
自定義鉤子函式是一個現有的功能,用於將可重用的邏輯從函式元件中分離出來,並允許在不同的函式元件中真正重用它。
ReactJS - 可訪問性
輔助功能 (a11y) 是以這樣的方式設計 Web 應用程式:該應用程式將對所有人都是可訪問的,並支援輔助技術來為終端使用者讀取應用程式的內容。
React 支援 Web 應用程式輔助功能的各個方面。讓我們看看 React 在本章中如何支援輔助功能。
ARIA (aria-*) 屬性
WAI-ARIA(Web Accessibility Initiative - Accessible Rich Internet Applications)是一種標準,它指定了構建完全可訪問的 JavaScript 小部件的方法。它提供了一組大量的 HTML 屬性 (aria-*) 來支援輔助功能。React 支援其元件中的所有這些屬性。通常情況下,React 將 HTML 屬性限制為駝峰式命名法,但對於輔助功能屬性,它應該採用短橫線命名法、lisp 命名法或 HTML 文件中的原樣。
例如,以下程式碼顯示瞭如何使用 HTML 輔助功能屬性。
<input
type="text"
aria-label={labelText}
aria-required="true"
name="name"
/>
這裡:
`aria-label` 用於指定輸入元素的標籤。
`aria-required` 用於指定必須填寫輸入。
請注意,這些屬性按原樣使用(採用短橫線命名法)。
語義化 HTML
透過應用語義化 HTML(article、section、navigation 等)標籤來編碼 Web 文件可以提高文件的可訪問性。在 React 中,有些情況下我們使用塊(`div`)只是為了滿足 React 框架。例如,React 不支援在其渲染程式碼中使用多個標籤。為了克服此限制,開發人員可以使用父標籤(`div`)將多個標籤作為子標籤。
function ShowItems({ data }) {
return (
<div>
<dt>{data.title}</dt>
<dd>{data.description}</dd>
</div>
);
}
React 提供了 `Fragment` 元件來解決這種情況。我們可以只替換 `Fragment` 來代替 `div`,如下所示:
function ShowItems({ data }) {
return (
<Fragment>
<dt>{data.title}</dt>
<dd>{data.description}</dd>
</Fragment>
);
}
表單
每個輸入都應該有標籤,並且標籤應該具有描述性,以便理解輸入元素。React 提供了一個特殊的屬性 `htmlFor` 來指定特定描述的輸入元素。開發人員可以使用它來建立可訪問的表單。
<label htmlFor="firstName">Firstname:</label> <input id="firstName" type="text" name="name"/>
鍵盤支援
鍵盤支援是建立可訪問 Web 應用程式的必要條件。一些需要鍵盤支援的功能包括:
**焦點** - React 提供了一個名為 Ref 的概念來訪問原始 DOM 元素。當應用程式需要原始訪問 DOM 元素時,可以使用 `Ref` 和 `Forwarding Ref` 來管理原始 DOM 元素。
**跳過連結** - 跳過導航連結是支援輔助功能的必要功能。它們允許使用者在僅使用鍵盤訪問應用程式時一次跳過所有導航。這可以使用智慧錨標籤來完成,React 完全支援這些標籤。
<body> <a href="#maincontent">Skip to main content</a> ... <main id="maincontent"> ... </main>
**滑鼠和指標功能** - 為了建立一個真正可訪問的應用程式,所有功能都應該可以透過鍵盤訪問。具有高階滑鼠和指標互動的元件應進行更改以適應僅鍵盤的互動。React 提供所有事件處理邏輯,可將預設的基於滑鼠的 UI 修改為基於鍵盤的 UI。
Aria 元件
React 社群提供了許多具有完全輔助功能支援的元件。可以直接使用它們,無需任何修改。它們會自動使應用程式可訪問。一些具有 aria 支援的第三方元件如下:
`react-aria` - `react-aria` 提供了一組大量的具有完全輔助功能支援的 React 元件。
`react-modal` - `react-modal` 提供具有 aria 支援的模態元件。
`react-aria-modal` - `react-aria-modal` 是另一個具有 aria 支援的模態元件。
`react-select` - `react-select` 提供具有 aria 支援的選擇元件。
`react-dropdown-aria` - `react-dropdown-aria` 提供具有 aria 支援的下拉元件。
`react-aria-menubutton` - `react-aria-menubutton` 提供具有 aria 支援的選單按鈕元件。
`react-aria-tabpanel` - `react-aria-tabpanel` 提供具有 aria 支援的選項卡面板元件。
總結
React 提供了許多功能來建立完全可訪問的、支援 aria 的 Web 應用程式。建立可訪問的應用程式始終是一項挑戰,而 React 透過現成的元件以及從頭開始編寫可訪問應用程式的核心功能來減輕一些負擔。
ReactJS - 程式碼分割
捆綁是前端應用程式中的一個重要階段。它將所有前端程式碼及其依賴項捆綁到一個大型捆綁包 (bundle.js) 中。最終捆綁包的大小由捆綁器進行最佳化。一些流行的捆綁器包括 webpack、parcel 和 rollup。在大多數情況下,最終捆綁包都沒問題。如果最終捆綁的程式碼很大,則可以指示捆綁器將程式碼分成多個項,而不是單個大塊。
讓我們學習如何提示捆綁器分割程式碼並將其分別捆綁。
動態匯入
動態匯入指示捆綁器分割程式碼。動態匯入基本上是根據需要獲取所需的模組。執行普通匯入的程式碼如下所示:
import { round } from './math';
console.log(round(67.78));
相同的程式碼可以動態匯入,如下所示:
import("./math").then(math => {
console.log(math.round(67.78);
});
React Lazy 元件
React 提供了一個函式 `React.lazy` 來動態匯入元件。通常,我們知道,React 元件將按如下方式匯入:
import MyComponent from './MyComponent';
要使用 `React.lazy()` 函式動態匯入上述元件,如下所示:
const MyComponent = React.lazy(() => import('./MyComponent'));
The imported component should be wrapped into a Suspense component to use it in the application.
import React, { Suspense } from 'react';
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
</div>
);
}
Suspense 元件用於在載入原始元件期間載入臨時 UI。Suspense 元件包含一個 fallback 屬性來指定回退 UI。回退 UI 可以是任何 React 元素。有時,動態元件由於網路問題或程式碼錯誤而可能載入失敗。我們可以使用錯誤邊界來處理這些情況,如下所示:
import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';
const MyComponent = React.lazy(() => import('./MyComponent'));
const AnotherComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<MyComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
這裡:
MyErrorBoundary 包裹在 Suspense 元件周圍。
如果載入 MyComponent 時出現任何錯誤,則 MyErrorBoundary 會處理錯誤並回退到其元件中指定的通用 UI。
應用程式碼分割的最佳場景之一是路由。路由可以用來應用程式碼分割,如下所示:
import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
);
這裡:
所有路由(元件)都使用 React.lazy() 功能載入。
由於所有路由(Home 和 About)都是透過動態匯入載入的,因此每個路由只會在初始化時載入必要的元件,而不是所有元件。
React.lazy() 只支援預設匯出。在 React 中,我們可以透過指定動態名稱而不是 default 關鍵字來匯出元件,如下所示:
export const MyComponent = /* ... */;
為了使其在 React.lazy() 中可用,我們可以使用 default 關鍵字重新匯出元件,如下所示:
export { MyLazyComponent as default } from "./MyComponent.js";
然後,我們可以像往常一樣匯入它:
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));
應用懶載入
讓我們建立一個新的 React 應用來學習如何在這一節中應用程式碼分割。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,建立一個簡單的 Hello 元件,Hello (src/Components/Hello.js),並渲染一條簡單的訊息,如下所示:
import React from "react";
class Hello extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>Hello, {this.props.name}</div>
);
}
}
export default Hello;
在這裡,我們使用了 name 屬性來使用給定的名稱渲染 Hello 訊息。
接下來,建立一個簡單的元件,SimpleErrorBoundary (src/Components/SimpleErrorBoundary.js),並在錯誤期間渲染回退 UI 或子元件,如下所示:
import React from "react";
class SimpleErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log(error);
console.log(errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Please contact the administrator.</h1>;
}
return this.props.children;
}
}
export default SimpleErrorBoundary;
這裡:
hasError 是一個初始化值為 false 的狀態變數。
getDerivedStateFromError 在發生錯誤時更新錯誤狀態。
componentDidCatch 將錯誤記錄到控制檯。
render 將根據應用程式中的錯誤渲染錯誤 UI 或子元件。
接下來,開啟 App 元件 (src/App.js),並透過 React.lazy() 載入 Hello 元件,如下所示:
import './App.css'
import React, { Suspense, lazy } from 'react';
import SimpleErrorBoundary from './Components/SimpleErrorBoundary';
const Hello = lazy(() => import('./Components/Hello'));
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleErrorBoundary>
<Suspense fallback="<div>loading...</div>">
<Hello name="Peter" />
</Suspense>
</SimpleErrorBoundary>
</div>
</div>
</div>
);
}
export default App;
這裡我們有:
從 React 包中匯入了 lazy 和 Suspense 元件。
使用 Hello 元件,並將其用 Suspense 和 SimpleErrorBoundary 元件包裹。
最後,在瀏覽器中開啟應用程式並檢查最終結果。懶載入在前端沒有任何可見的變化。它將像往常一樣渲染 Hello 元件,如下所示:
總結
程式碼分割有助於透過僅載入特定頁面中使用的必要元件來最佳化大型應用程式。Suspense 和錯誤邊界元件可用於處理動態載入元件時發生的意外錯誤。
ReactJS - Context
Context 是 React 中一個重要的概念。它提供了一種將資訊從父元件傳遞到其所有子元件(任何巢狀級別)的能力,而無需在每一級都透過 props 傳遞資訊。Context 將使程式碼更易讀且更易於理解。Context 可用於儲存資訊,這些資訊不會改變或幾乎不會改變。Context 的一些用例如下:
應用程式配置
當前已認證使用者資訊
當前使用者設定
語言設定
應用程式/使用者主題/設計配置
讓我們在本節學習如何建立上下文及其用法。
Context 如何工作?
讓我們學習上下文的
基本概念及其工作原理。上下文有四個部分:建立新的 Context
在根元件中設定 Context 提供者
在我們需要 Context 資訊的元件中設定 Context 消費者
訪問 Context 資訊並在渲染方法中使用它
讓我們建立一個應用程式來更好地理解 Context 及其用法。讓我們建立一個全域性 Context 來維護應用程式根元件中的主題資訊,並在我們的子元件中使用它。
首先,使用以下命令建立並啟動一個應用程式:
create-react-app myapp cd myapp npm start
接下來,在 components 資料夾下建立一個元件 HelloWorld (src/components/HelloWorld.js)
import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
render() {
return <div>Hello World</div>
}
}
export default HelloWorld
接下來,建立一個新的 Context (src/ThemeContext.js) 用於維護主題資訊。
import React from 'react'
const ThemeContext = React.createContext({
color: 'black',
backgroundColor: 'white'
})
export default ThemeContext
這裡:
使用 React.createContext 建立一個新的 Context。
Context 被建模為具有樣式資訊的 objects。
設定文字顏色和背景的初始值。
接下來,更新根元件 App.js,包含 HelloWorld 元件和主題提供者,併為主題 Context 設定初始值。
import './App.css';
import HelloWorld from './components/HelloWorld';
import ThemeContext from './ThemeContext'
function App() {
return (
<ThemeContext.Provider value={{
color: 'white',
backgroundColor: 'green'
}}>
<HelloWorld />
</ThemeContext.Provider>
);
}
export default App;
這裡使用了 ThemeContext.Provider,這是一個非視覺元件,用於設定主題 Context 的值,以便在其所有子元件中使用。
接下來,在 HelloWorld 元件中包含一個 Context 消費者,並使用 HelloWorld 元件中的主題資訊設定 hello world 訊息的樣式。
import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{({color, backgroundColor} ) =>
(<div style={{
color: color,
backgroundColor: backgroundColor }}>
Hello World
</div>)
}
</ThemeContext.Consumer>
)
}
}
export default HelloWorld
這裡:
使用了 ThemeContext.Consumer,這是一個非可視元件,提供對當前主題上下文詳細資訊的訪問。
使用函式表示式在 ThemeContext.Consumer 內獲取當前 Context 資訊。
使用物件解構語法獲取主題資訊並將值設定為 color 和 backgroundColor 變數。
使用主題資訊使用 style 屬性為元件設定樣式。
最後,開啟瀏覽器並檢查應用程式的輸出。
總結
上下文降低了在 React 應用程式中維護全域性資料的複雜性。它提供了一個簡潔的提供者和使用者概念,並簡化了上下文的實現。
ReactJS - 錯誤邊界
通常,在不影響應用程式 UI 的情況下捕獲錯誤並處理錯誤非常具有挑戰性。React 引入了一個稱為錯誤邊界的概念,用於在 UI 渲染期間捕獲錯誤,在後臺處理它,並在前端應用程式中顯示回退 UI。這不會影響 UI 應用程式,因為它使用替代 UI(如警告訊息)而不是損壞的頁面來處理。
讓我們在本節學習什麼是錯誤邊界以及如何在我們的應用程式中應用它。
錯誤邊界的概念
錯誤邊界是具有特殊功能的普通 React 元件,用於捕獲元件樹中任何位置發生的全部錯誤。在處理過程中捕獲到的錯誤可以被記錄,然後可以向用戶顯示指定錯誤的替代使用者介面。
透過實現兩個函式,可以將基於類的普通 React 元件升級為支援錯誤邊界的元件。
static getDerivedStateFromError() − 這是一個靜態函式,將在應用程式中發生錯誤時呼叫。
static getDerivedStateFromError(error) {
return { hasError: true };
}
在這裡,hasError 是一個自定義狀態變數,它指定應用程式有一些錯誤,可以在隨後的渲染中使用它來顯示回退 UI 而不是普通 UI。
componentDidCatch() − 這是一個元件生命週期事件,在元件樹中發生錯誤時會呼叫它,並帶有錯誤資訊。
componentDidCatch(error, errorInfo) {
// log the error in console or store it somewhere using a log service
}
一旦元件升級為處理錯誤,它就可以像普通元件一樣在應用程式中的任何位置使用。讓我們考慮元件的名稱為 SimpleErrorBoundary,則可以使用它,如下所示:
<SimpleErrorBoundary> <MyComponent /> </SimpleErrorBoundary>
這裡:
React 將捕獲 MyComponent 元件中發生的任何錯誤,並將其傳送到 SimpleErrorBoundary 元件以進行進一步處理。
React 不會捕獲所有錯誤,除了以下情況下發生的錯誤:
事件處理程式 − 事件處理程式是普通的 JavaScript 函式,可以使用 try/catch 並自行渲染回退 UI,可能不需要錯誤邊界提示。
非同步程式碼,如 setTimeout。
伺服器端渲染。錯誤邊界專門用於前端應用程式。
錯誤邊界元件本身發生的錯誤。
應用錯誤邊界
讓我們建立一個新的 React 應用來學習如何在這一節中應用錯誤邊界。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟 App.css (src/App.css) 並刪除所有 CSS 類。然後,建立一個簡單的元件,SimpleErrorBoundary (src/Components/SimpleErrorBoundary.js),並在錯誤期間渲染回退 UI 或子元件,如下所示:
import React from "react";
class SimpleErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log(error);
console.log(errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Please contact the administrator.</h1>;
}
return this.props.children;
}
}
export default SimpleErrorBoundary;
這裡:
hasError 是一個初始化值為 false 的狀態變數。
getDerivedStateFromError 在發生錯誤時更新錯誤狀態。
componentDidCatch 將錯誤記錄到控制檯。
render 將根據應用程式中的錯誤渲染錯誤 UI 或子元件。
接下來,建立一個簡單的元件,Hello (src/Components/Hello.js),並渲染一條簡單的文字訊息,如下所示:
import React from "react";
class Hello extends React.Component {
constructor(props) {
super(props)
}
render() {
if(this.props.name == "error") {
throw('Invoked error')
}
return (
<div>Hello, {this.props.name}</div>
);
}
}
export default Hello;
這裡我們有:
使用 name 屬性顯示問候語。
如果給定的名稱為 error,則丟擲錯誤。
接下來,開啟 App 元件 (src/App.js),並使用 SimpleErrorBoundary 元件,如下所示:
import './App.css'
import React from 'react';
import SimpleErrorBoundary from './Components/SimpleErrorBoundary'
import Hello from './Components/Hello'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleErrorBoundary>
<Hello name="error" />
</SimpleErrorBoundary>
</div>
</div>
</div>
);
}
export default App;
這裡我們有:
從 React 包中匯入了 SimpleErrorBoundary 元件。
使用 SimpleErrorBoundary 元件,並將 error 作為 name 屬性的值。
最後,在瀏覽器中開啟應用程式並檢查最終結果。
在開發者工具中開啟控制檯,您將看到錯誤資訊,如下所示:
Invoked error
SimpleErrorBoundary.js:17 {
componentStack: '\n at Hello (https://:3000/static/js/bun…09:5)\n at
div\n at div\n at div\n at App'
}
總結
錯誤邊界是安全且有價值的元件,用於處理前端應用程式中的意外錯誤。如果沒有錯誤邊界,將很難隱藏錯誤並獲取有關發生的錯誤的有價值的除錯資訊。
ReactJS - 轉發 Refs
Ref 是一種轉義方法,可以直接操作 DOM,而不會影響狀態更改更新元件。Ref 可以應用於 DOM 元素,但是要將 Ref 應用於 React 元件並在元件內部深處獲取 DOM 元素,則轉發 Ref 是最佳選擇。轉發 Ref 允許元件從頂級元件接收 ref 並將其進一步傳遞到下一級元件,以便獲取 DOM 元素。
讓我們在本節學習如何在 React 中使用轉發 ref。
forwardRef 方法的簽名
forwardRef 的簽名如下:
const new-component = React.forwardRef(fn)
其中 fn 的簽名如下:
(props, ref) => {
// renders a react component by attaching the ref and returns it
}
使用 forwardRef 的一個簡單示例如下:
const MyInput = React.forwardRef((props, ref) => (
<input type="text" ref={ref} value={props.value} />
));
const myRef = React.createRef();
<MyInput ref={myRef} value="Hi" />
這裡:
MyInput 從頂級元件獲取 ref 並將其傳遞到底層的 input 元素。
myRef 分配給 MyInput 元件。
MyInput 元件將 myRef 傳遞到底層的 input 元素。
最後,myRef 指向 input 元素。
在元件中應用 forwardRef
讓我們透過開發一個應用程式來學習 forwardRef 的概念。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟 App.css (src/App.css) 並刪除所有 CSS 類。然後,建立一個簡單的元件,SimpleForwardRef (src/Components/SimpleForwardRef.js),如下所示:
import React from "react";
const SimpleForwardRef = React.forwardRef((props, ref) => (
<input type="text" ref={ref} value={props.value} />
));
export default SimpleForwardRef
這裡我們有:
使用 forwardRef 將 ref 傳遞給 input 元素。
input 元素使用 ref 屬性來設定 ref 值。
接下來,開啟 App 元件 (src/App.js) 並使用 SimpleForwardRef 元件更新內容,如下所示:
import './App.css'
import React, { useEffect } from 'react';
import SimpleForwardRef from './Components/SimpleForwardRef'
function App() {
const myRef = React.createRef();
useEffect(() => {
setTimeout(() => {
myRef.current.value = "Hello"
}, 5000)
})
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleForwardRef ref={myRef} value="Hi" />
</div>
</div>
</div>
);
}
export default App;
這裡:
myRef 使用 createRef 方法建立,並將其傳遞到 SimpleForwardRef 元件。
myRef 代表由 SimpleForwardRef 元件渲染的 input 元素。
useEffect 將透過 myRef 訪問 input 元素,並嘗試將 input 的值從 hi 更改為 Hello。
最後,開啟瀏覽器中的應用程式。input 的值將在 5 秒後更改為 Hello,如下所示:
總結
轉發 ref 增強了 ref 概念,使其可以在 React 應用程式中的任何位置使用。可以使用轉發 ref 概念訪問和操作任何 DOM 元素(可能位於元件層次結構中的任何級別深處)。
ReactJS - Fragments
Fragment 是一個簡單的解決方案,用於對多個 React 元素進行分組,而無需在 DOM 中新增額外的標記。
在 React 中,元件的 render 方法允許返回一個 React 元素。對於元件要返回多個元素,需要將這些元素包裝到一個通用容器元素中。例如,要返回多個 p 元素,應該將其包裝在一個 div 元素中,如下所示:
<div> <p>One</p> <p>Two </p> </div>
對於大多數場景,包裝一個通用的根元素是可以的,但是某些場景需要特殊處理。它們如下所示:
某些元素,例如 li、td 等,可能不能包裝在通用元素周圍。
當元件只需要返回部分元素列表時(最終將在父元件中包裝)。
讓我們編寫一個簡單的元件來更好地理解我們的問題。該元件的功能是返回客戶的部分列表,如下所示:
<li>John</li> <li>Peter</li>
使用 create-react-app 建立一個新應用程式並啟動應用程式。
create-react-app myapp cd myapp npm start
在 components 資料夾下建立一個元件 ListOfCustomer (src/components/ListOfCustomer.js)
import React from 'react'
class ListOfCustomer extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log(this.props)
var names = this.props['names'];
var namesList = names.map(
(name, index) => <li>{name}</li>
)
return <ul>{namesList}</ul>
}
}
export default ListOfCustomer
在這裡,元件迴圈遍歷 names 屬性並將它渲染成一個 li 元素列表。
現在,在我們的根元件 (App.js) 中使用該元件。
import ListOfCustomer from './components/ListOfCustomer';
function App() {
var names = ["John", "Peter"]
return (
<ul>
<ListOfCustomer names={names} />
</ul>
);
}
export default App;
這將導致渲染多個 ul,如下所示:
<ul><ul><li>John</li><li>Peter</li></ul></ul>
讓我們更改元件以使用 React.Fragment
import React, { Fragment } from 'react'
class ListOfCustomer extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log(this.props)
var names = this.props['names'];
var namesList = names.map(
(name, index) => <li>{name}</li>
)
return <React.Fragment>{namesList}</React.Fragment>
}
}
export default ListOfCustomer
現在,我們的元件渲染有效的 HTML 文件。
<ul><li>John</li><li>Peter</li></ul>
帶鍵的 Fragment
在上面的示例中,React 在開發者控制檯中丟擲一個警告,如下所示:
Warning: Each child in a list should have a unique "key" prop. Check the render method of `ListOfCustomer`. See https://react.com.tw/link/warning-keys for more information. li ListOfCustomer@https://:3000/main.4bbe8fa95c723e648ff5.hot-update.js:26:10 ul App render - ListOfCustomer.js:9
正如警告所示,React 要求列表中的每個元素都具有唯一的鍵 (key)。它使用鍵來識別哪些元素髮生了更改、新增或刪除。React.Fragment 允許透過其 key 屬性傳遞鍵。React 將在內部使用它來僅渲染列表中修改的項。讓我們更改程式碼並在 React.Fragment 中新增 key,如下所示:
import React, { Fragment } from 'react'
class ListOfCustomer extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log(this.props)
var names = this.props['names'];
var namesList = names.map(
(name, index) => <li key={index}>{name}</li>
)
return <React.Fragment>{namesList}</React.Fragment>
}
}
export default ListOfCustomer
這將消除錯誤。
簡寫語法
React.Fragment 有一種替代的簡寫語法,它易於使用且易於閱讀。
<> JSX element </>
讓我們更改程式碼以適應簡寫語法。更新後的程式碼如下:
import React, { Fragment } from 'react'
class ListOfCustomer extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log(this.props)
var names = this.props['names'];
var namesList = names.map(
(name, index) => <li key={index}>{name}</li>
)
return <>{namesList}</>
}
}
export default ListOfCustomer
ReactJS - 高階元件 (Higher-Order Components)
由於 React 元件是透過組合(一個元件巢狀在另一個元件內部)而不是透過繼承來相互連線的,因此在一個 React 元件中使用的邏輯不會直接共享到另一個元件。React 社群提供了多個選項來在元件之間共享邏輯,其中一個選項是高階元件 (HOC)。HOC 本身並不是 React API,而是一種無副作用的設計模式。
本章我們將學習如何使用高階元件。
如何使用高階元件
基本上,HOC 是一個函式,它接收一個 React 元件作為輸入,然後基於輸入元件建立一個新的 React 元件,並返回新建立的(包裝的)元件。例如,HOC 函式可以接收一個純資料渲染元件作為輸入,然後返回一個新元件,該元件將具有資料獲取功能和使用輸入元件的資料渲染功能。
讓我們一步一步地看看如何使用 HOC 並在兩個元件之間共享邏輯。讓我們考慮從外部 URL 獲取和渲染資料的情況。
建立一個 HOC 函式,根據功能包含一個或多個輸入引數。
HOC 函式的第一個引數應該是具有次要邏輯的 React 元件(例如,資料渲染邏輯)。
HOC 函式的第二個引數應根據我們的需求定義。對於我們的資料獲取場景,資料 URL 是獲取資料所需的資訊。因此,我們應該將其作為 HOC 函式的第二個引數。
function createHOC(WrappedComponent, url) {
// create new component using WrappedComponent
}
如果確實需要,HOC 函式可以有任意數量的引數。
在 HOC 函式內建立一個新的元件,在其 componentDidMount 事件中支援主要邏輯(例如,使用第二個 URL 引數進行資料獲取邏輯)。
function createFetchHOC(WrappedComponent, url) {
class DataFetcher extends React.Component {
componentDidMount() {
fetch(url)
.then((response) => response.json())
.then((data) => {
this.setState({
data: data
});
});
}
}
}
透過傳遞從 componentDidMount 事件中獲取的資料來渲染輸入元件。
function createFetchHOC(WrappedComponent, url) {
class DataFetcher extends React.Component {
render() {
return (
<WrappedComponent data={this.state.data} {...this.props} />
)
}
}
}
返回新建立的元件。
function createFetchHOC(WrappedComponent, url) {
class DataFetcher extends React.Component {
}
return DataFetcher;
}
透過組合 DataFetcher (createFetchHOC) 和 Wrapped 元件建立一個新元件。
const UserListWithFetch = createFetchHOC( UserList, "users.json" );
最後,根據需要在任何地方使用新元件。
<UserListWithFetch />
應用 HOC 元件
讓我們透過應用 HOC 元件建立一個新的應用程式。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,建立一個新的 HOC 函式,如下所示:
import React from 'react';
function createFetchHOC(WrappedComponent, url) {
class DataFetcher extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentDidMount() {
fetch(url)
.then((response) => response.json())
.then((data) => {
this.setState({
data: data
});
});
}
render() {
return (
<WrappedComponent data={this.state.data} {...this.props} />
)
}
}
return DataFetcher;
}
export default createFetchHOC;
接下來,在 public 資料夾中建立一個檔案 users.json (public/users.json) 來儲存使用者資訊。我們將嘗試使用 FetchRenderProps 元件獲取它並在我們的應用程式中顯示它。
[{"id":1,"name":"Fowler","age":18},
{"id":2,"name":"Donnell","age":24},
{"id":3,"name":"Pall","age":26}]
接下來,在 public 資料夾中建立一個檔案 todo_list.json (public/todo_list.json) 來儲存待辦事項列表資訊。我們將嘗試使用 FetchRenderProps 元件獲取它並在我們的應用程式中顯示它。
[{"id":1,"title":"Learn JavaScript","is_done":true},
{"id":2,"title":"Learn React","is_done":true},
{"id":3,"title":"Learn Typescript","is_done":false}]
接下來,建立一個新的元件 UserList (src/Components/UserList.js) 來渲染使用者,如下所示:
import React from "react";
class UserList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<>
<ul>
{this.props.data && this.props.data.length && this.props.data.map((item) =>
<li key={item.id}>{item.name}</li>
)}
</ul>
</>
)
}
}
export default UserList;
在這裡,我們使用了 data 屬性來渲染使用者列表。
接下來,建立一個新的元件 TodoList (src/Components/TodoList.js) 來渲染待辦事項,如下所示:
import React from "react";
class TodoList extends React.Component {
constructor(props) {
super(props);
this.todos = this.props.data
}
render() {
return (
<>
<ul>
{this.props.data && this.props.data.length && this.props.data.map(
(item) =>
<li key={item.id}>{item.title} {item.is_done && <strong>Done</strong>}</li>
)}
</ul>
</>
)
}
}
export default TodoList;
在這裡,我們使用了 data 屬性來渲染待辦事項列表。
接下來,建立一個新的元件 SimpleHOC 透過單個 HOC 元件渲染使用者列表和待辦事項列表。
import React from "react";
import UserList from "./UserList";
import TodoList from "./TodoList";
import createFetchHOC from "./createFetchHOC";
const UserListWithFetch = createFetchHOC(
UserList,
"users.json"
);
const TodoListWithFetch = createFetchHOC(
TodoList,
"todo_list.json"
);
class SimpleHOC extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<>
<UserListWithFetch />
<TodoListWithFetch />
</>
)
}
}
export default SimpleHOC;
這裡我們有:
透過組合 TodoList 和 DataFetcher 元件建立了 UserListWithFetch 元件。
透過組合 Users 和 DataFetcher 元件建立了 TodoListWithFetch 元件。
接下來,開啟 App.js 並使用 SimpleHOC 元件更新它。
import './App.css'
import React from 'react';
import SimpleHOC from './Components/SimpleHOC'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleHOC />
</div>
</div>
</div>
);
}
export default App;
最後,在瀏覽器中開啟應用程式並檢查最終結果。應用程式將按如下所示進行渲染:
總結
高階元件是共享元件之間邏輯的有效方法。它廣泛用於許多第三方元件,並取得了良好的成功率,並且是共享 React 領域邏輯的經過時間考驗的方法。
ReactJS - 與其他庫整合
儘管 React 提供了建立完整 Web 應用程式的所有必要功能,但由於用其他庫編寫的遺留系統、從其他框架遷移等原因,與其他庫整合是必須的。React 可以與其他庫共存,並提供與其他系統一起使用的必要基礎設施。
本章我們將學習如何在此章節中使用 React 元件與其他庫(如 jQuery、Backbone 等)一起使用。
基於 CreateRoot 的整合
React 使用來自 ReactDOMClient 模組的 createRoot() 方法將其自身附加到主 HTML 文件。createRoot() 不會干擾 HTML 文件,除了附加的元素。開發人員可以利用此行為在同一文件中混合多個庫。
讓我們看看如何透過將 React 應用程式附加到單獨的元素來在一個文件中整合 jQuery 和 React 元件。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,在元件資料夾下建立一個 React 元件 Hello (src/components/Hello.js)
import React from "react";
class Hello extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>Hello, {this.props.name}</div>
);
}
}
export default Hello;
接下來,開啟 index.html (public/index.html) 並新增一個新的容器 (jquery-root),如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="https://code.jquery.com/jquery-3.6.1.slim.min.js"></script>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div style="padding: 10px;">
<div id="root"></div>
<div id="jquery-root"></div>
</div>
<script>
$(document).ready(function() {
$("#jquery-root").text("Hello, from jQuery")
})
</script>
</body>
</html>
這裡:
jQuery 庫透過 CDN 連結。
它像傳統那樣透過 $(document).ready 方法初始化。
並用於使用 jQuery 選擇器 (#jquery-root) 和 text 方法追加訊息。
接下來,開啟 index.js (src/index.js) 並將我們的 hello 元件附加到根容器中,如下所示:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Hello from './Components/Hello';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Hello name="from React" />
</React.StrictMode>
);
reportWebVitals();
這裡:
React 應用程式使用 createRoot() 方法附加。
將 Hello 元件渲染到 HTML 文件中的根元素中。
最後,在瀏覽器中開啟應用程式並檢查結果。React 和 jQuery 庫都將發出 hello 訊息,如下所示:
基於 Ref 的整合
一般來說,React 不知道其他庫執行的任何 DOM 操作。因此,要將 React 與其他庫一起使用,React 不應執行任何 DOM 操作,而應將所有更改轉發到其他庫。
眾所周知,React 提供了一個名為 Ref 的轉義口,用於操作 DOM 元素而不受狀態更改的影響/受狀態更改的影響。開發人員可以利用這些功能來建立其他庫的包裝 React 元件,並在 React 應用程式中使用它。在 React 元件中使用其他庫的標準步驟如下:
建立一個 React 元件並渲染一個空的 div
render() {
return <div />
}
將 ref 附加到渲染的 div,如下所示:
render() {
return <div ref={el => this.el = el} />
}
在 componentDidMount() 生命週期事件中使用附加的 ref 操作 dom 元素,如下所示:
componentDidMount() {
this.$el = $(this.el);
this.$el.somePlugin(); // create dom
// call this.$el.pluginAPI() as necessary
}
在 componentWillUnmount() 生命週期事件中使用附加的 ref 銷燬 dom 元素,如下所示:
componentWillUnmount() {
this.$el.somePlugin('destroy'); // destroy dom
// this.$el.destroyAPI() to remove the element from the dom
}
在下一節中,我們將應用這些技術將 jQuery 外掛整合到應用程式中。
JQuery slick 外掛整合
讓我們嘗試將 slick jQuery 外掛 (https://github.com/kenwheeler/slick) 整合到 React 元件中。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,安裝 slick jQuery 外掛。
npm install jquery slick-carousel --save
接下來,將 slick 外掛包 (css 和資產) 中的 slick 資料夾複製到應用程式的 public 資料夾中。slider 資料夾的內容如下所示:
. ├── ajax-loader.gif ├── config.rb ├── fonts │ ├── slick.eot │ ├── slick.svg │ ├── slick.ttf │ └── slick.woff ├── slick-theme.css ├── slick-theme.less ├── slick-theme.scss ├── slick.css ├── slick.js ├── slick.less ├── slick.min.js └── slick.scss
接下來,建立一個簡單的元件 ReactSlick (src/Components/ReactSlick.js),如下所示:
import React from "react";
import $ from 'jquery';
import slick from 'slick-carousel';
class ReactSlick extends React.Component {
componentDidMount() {
this.$el = $(this.el);
this.$el.slick();
}
componentWillUnmount() {
this.$el.slick('destroy');
}
render() {
return (
<div>
<div ref={el => this.el = el}>
{this.props.children}
</div>
</div>
);
}
}
export default ReactSlick;
這裡:
渲染一個帶有子元素的 div(子元素來自 props)。
將 ref 附加到 div 元素。
在 componentDidMount() 生命週期事件中使用 ref 將外掛附加到元素。
在 componentWillUnmount() 生命週期事件中使用 ref 從元素中刪除外掛。
接下來,開啟 App 元件 (src/App.js) 並使用 ReactSlick 元件更新內容,如下所示:
import ReactSlick from './Components/ReactSlick';
function App() {
return (
<ReactSlick>
<div className="box"><h1>1</h1></div>
<div className="box"><h1>2</h1></div>
<div className="box"><h1>3</h1></div>
<div className="box"><h1>4</h1></div>
</ReactSlick>
);
}
export default App;
這裡:
渲染 ReactSlick 元件。
使用四個帶有數字 (1,2,3 和 4) 的 div 作為滑塊。
接下來,開啟 App.css (src/App.css) 並刪除所有 CSS 類。然後,開啟 index.html (public/index.html) 並新增必要的樣式,如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" type="text/css" href="slick/slick.css"/>
<link rel="stylesheet" type="text/css" href="slick/slick-theme.css"/>
<style>
#root {
margin: auto;
padding-left: 25px;
padding-top: 25px;
width: 300px;
color: gray;
font-family:Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
padding-left : 25px;
text-align: center;
}
.box {
background-color: skyblue;
text-align: center;
color: white
}
</style>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div style="background-color: white; margin: 2px;">
<div id="root"></div>
</div>
</body>
</html>
這裡我們有:
更新根元件的樣式。
更新滑塊的樣式 (.box)。
包含 slick 外掛的特定樣式 (slick/slick.css 和 slick/slick-theme.css)。
最後,在瀏覽器中開啟應用程式。jQuery slick 滑塊將透過 React 元件渲染,如下所示:
總結
React 提供了多種方法在同一個專案中使用 React 和其他庫。每種整合方法都簡單有效。開發人員應避免使用其他庫,除非在不可避免的情況下,例如遺留應用程式、遷移應用程式等。
ReactJS - 效能最佳化
React 在內部處理應用程式的效能並在每個機會上對其進行最佳化。作為開發人員,我們可以做某些事情來獲得應用程式的最大效能。本章我們將瞭解一些從 React 庫中獲得最大效能的技術。
效能最佳化的技巧
一些效能最佳化的技巧如下:
使用生產版本 - React 有兩種模式:開發模式和生產模式。開發模式是為開發人員準備的,開發模式為開發人員提供了許多有用的功能,以便更好地瞭解應用程式內部並除錯應用程式。這將降低應用程式速度。為了獲得最佳效能,啟用生產模式並部署應用程式。
CDN 使用 CDN 來連結 React 庫的應用程式應使用 React 庫的生產版本,如下所示:
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
Create React App - 使用 create-react-app CLI 建立的應用程式可以使用以下命令建立應用程式的生產版本。
npm run build
Brunch - 使用 brunch 的應用程式應安裝 terser-brunch 外掛,然後呼叫生產版本以獲得高效和高效能的程式碼。
npm install --save-dev terser-brunch brunch build -p
Rollup - 使用 rollup 的應用程式應安裝 commonjs、replace 和 terser 外掛並對其進行配置以獲得最佳生產版本。
npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser
使用 React DevTools - React 為所有瀏覽器提供擴充套件程式作為開發工具。安裝擴充套件程式後,瀏覽器的開發者工具部分將顯示 React 的專用部分。React 擴充套件程式提供的工具之一是 Profiler(React DevTool Profiler)。可以在將應用程式部署到生產環境之前對其進行分析和最佳化。
視窗技術 - 如果要在前端顯示的資料量很大,則應用程式的效能將立即受到影響。一種方法是透過分頁和其他類似技術僅顯示一小部分資料。如果這些技術不可行,React 建議使用視窗技術,該技術將一次自動渲染一小部分資料。我們將在本章後面學習如何應用視窗技術。
避免協調 (shouldComponentUpdate) - 協調是提高 React 應用程式效能的強大技術。但是,協調需要一些時間才能執行並在我們的應用程式中應用它。跳過渲染(以及隨後的協調)將提高效能。React 提供了一個 API,shouldComponentUpdate,以便向 React 核心提示是否應該跳過或繼續渲染。以下程式碼將跳過應用程式的渲染。
shouldComponentUpdate(nextProps, nextState) {
return false;
}
元件可以分析其當前狀態和 props 與其更新的狀態和 props,並確定是否可以跳過渲染部分。
純元件 − 無需編寫 `shouldComponentUpdate`,只需透過擴充套件 React.PureComponent 來編寫純元件版本。如果給定的輸入相同,純元件通常會發出相同的輸出。純元件將進行淺比較並跳過協調。但是,存在一個問題。如果更改不是淺層更改,則 React 會跳過更新和渲染。要解決此問題,只需透過可見變異來完成更改,如下所示:
// non-mutation (wrong way to code)
const words = this.state.words;
words.push('john');
this.setState({words: words});
// mutated version (right way to code)
this.setState(state => ({
words: state.words.concat(['marklar'])
}));
這裡:
在程式碼的第一個版本中,物件未發生變異。因此,與舊物件的比較成功,並跳過了協調。
在程式碼的第二個版本中,物件發生了變異,並在比較過程中會被捕獲。
應用視窗技術
在本節中,讓我們建立一個新的 React 應用程式,透過應用視窗技術來渲染大型使用者列表。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 Bootstrap 和 react-bootstrap 庫:
npm install --save react-window
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,在 `public` 資料夾下建立一個名為 `users.json` 的檔案,並使用以下使用者資訊填充它:
[
{
"id":1,
"name":"Fowler",
"age":18
},
{
"id":2,
"name":"Donnell",
"age":24
},
{
"id":3,
"name":"Pall",
"age":26
},
{
"id":4,
"name":"Christos",
"age":19
},
{
"id":5,
"name":"Dud",
"age":29
},
{
"id":6,
"name":"Rayner",
"age":22
},
{
"id":7,
"name":"Somerset",
"age":31
},
{
"id":8,
"name":"Stavros",
"age":32
},
{
"id":9,
"name":"Cody",
"age":19
},
{
"id":10,
"name":"Sharai",
"age":19
},
{
"id":11,
"name":"Kristo",
"age":28
},
{
"id":12,
"name":"Harvey",
"age":27
},
{
"id":13,
"name":"Christen",
"age":27
},
{
"id":14,
"name":"Hillard",
"age":19
},
{
"id":15,
"name":"Jaine",
"age":32
},
{
"id":16,
"name":"Annabel",
"age":29
},
{
"id":17,
"name":"Hildagarde",
"age":29
},
{
"id":18,
"name":"Cherlyn",
"age":18
},
{
"id":19,
"name":"Herold",
"age":32
},
{
"id":20,
"name":"Gabriella",
"age":32
},
{
"id":21,
"name":"Jessalyn",
"age":32
},
{
"id":22,
"name":"Opal",
"age":31
},
{
"id":23,
"name":"Westbrooke",
"age":27
},
{
"id":24,
"name":"Morey",
"age":22
},
{
"id":25,
"name":"Carleton",
"age":26
},
{
"id":26,
"name":"Cosimo",
"age":22
},
{
"id":27,
"name":"Petronia",
"age":23
},
{
"id":28,
"name":"Justino",
"age":32
},
{
"id":29,
"name":"Verla",
"age":20
},
{
"id":30,
"name":"Lanita",
"age":18
},
{
"id":31,
"name":"Karlik",
"age":23
},
{
"id":32,
"name":"Emmett",
"age":22
},
{
"id":33,
"name":"Abran",
"age":26
},
{
"id":34,
"name":"Holly",
"age":23
},
{
"id":35,
"name":"Beverie",
"age":23
},
{
"id":36,
"name":"Ingelbert",
"age":27
},
{
"id":37,
"name":"Kailey",
"age":30
},
{
"id":38,
"name":"Ralina",
"age":26
},
{
"id":39,
"name":"Stella",
"age":29
},
{
"id":40,
"name":"Ronnica",
"age":20
},
{
"id":41,
"name":"Brucie",
"age":20
},
{
"id":42,
"name":"Ryan",
"age":22
},
{
"id":43,
"name":"Fredek",
"age":20
},
{
"id":44,
"name":"Corliss",
"age":28
},
{
"id":45,
"name":"Kary",
"age":32
},
{
"id":46,
"name":"Kaylee",
"age":21
},
{
"id":47,
"name":"Haskell",
"age":25
},
{
"id":48,
"name":"Jere",
"age":29
},
{
"id":49,
"name":"Kathryne",
"age":31
},
{
"id":50,
"name":"Linnea",
"age":21
},
{
"id":51,
"name":"Theresina",
"age":24
},
{
"id":52,
"name":"Arabela",
"age":32
},
{
"id":53,
"name":"Howie",
"age":22
},
{
"id":54,
"name":"Merci",
"age":21
},
{
"id":55,
"name":"Mitchel",
"age":30
},
{
"id":56,
"name":"Clari",
"age":18
},
{
"id":57,
"name":"Laurena",
"age":19
},
{
"id":58,
"name":"Odessa",
"age":30
},
{
"id":59,
"name":"Pippy",
"age":25
},
{
"id":60,
"name":"Wilmar",
"age":23
},
{
"id":61,
"name":"Cherianne",
"age":24
},
{
"id":62,
"name":"Huberto",
"age":25
},
{
"id":63,
"name":"Ariella",
"age":26
},
{
"id":64,
"name":"Lorant",
"age":30
},
{
"id":65,
"name":"Francesca",
"age":25
},
{
"id":66,
"name":"Ingamar",
"age":28
},
{
"id":67,
"name":"Myrta",
"age":27
},
{
"id":68,
"name":"Nicolette",
"age":26
},
{
"id":69,
"name":"Petra",
"age":22
},
{
"id":70,
"name":"Cyrill",
"age":27
},
{
"id":71,
"name":"Ad",
"age":23
},
{
"id":72,
"name":"Denys",
"age":22
},
{
"id":73,
"name":"Karilynn",
"age":23
},
{
"id":74,
"name":"Gunner",
"age":30
},
{
"id":75,
"name":"Falkner",
"age":20
},
{
"id":76,
"name":"Thurston",
"age":19
},
{
"id":77,
"name":"Codi",
"age":30
},
{
"id":78,
"name":"Jacob",
"age":31
},
{
"id":79,
"name":"Gasparo",
"age":26
},
{
"id":80,
"name":"Mitzi",
"age":29
},
{
"id":81,
"name":"Rubetta",
"age":21
},
{
"id":82,
"name":"Clary",
"age":20
},
{
"id":83,
"name":"Oliviero",
"age":24
},
{
"id":84,
"name":"Ranique",
"age":21
},
{
"id":85,
"name":"Shae",
"age":24
},
{
"id":86,
"name":"Woodrow",
"age":20
},
{
"id":87,
"name":"Junia",
"age":31
},
{
"id":88,
"name":"Athene",
"age":26
},
{
"id":89,
"name":"Veriee",
"age":18
},
{
"id":90,
"name":"Rickie",
"age":30
},
{
"id":91,
"name":"Carly",
"age":23
},
{
"id":92,
"name":"Vern",
"age":19
},
{
"id":93,
"name":"Trix",
"age":26
},
{
"id":94,
"name":"Lenore",
"age":20
},
{
"id":95,
"name":"Hanna",
"age":30
},
{
"id":96,
"name":"Dominique",
"age":21
},
{
"id":97,
"name":"Karlotta",
"age":22
},
{
"id":98,
"name":"Levey",
"age":20
},
{
"id":99,
"name":"Dalila",
"age":18
},
{
"id":100,
"name":"Launce",
"age":21
}
]
接下來,建立一個簡單的使用者列表元件,`SimpleWindow` (src/Components/SimpleWindow.js),並透過應用視窗功能來渲染使用者列表,如下所示:
import React from "react";
import { FixedSizeList as List } from 'react-window';
class SimpleWindow extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentDidMount() {
fetch("users.json")
.then((response) => response.json())
.then((data) => {
this.setState({
data: data
});
});
}
render() {
return (
<List
innerElementType="ul"
itemData={this.state.data}
itemCount={this.state.data.length}
itemSize={35}
width={500}
height={300}
>
{
({data, index, style}) => {
return (
<li style={style}>
{data[index].name}
</li>
)
}
}
</List>
)
}
}
export default SimpleWindow
這裡我們有:
將 `FixedSizeList` 元件匯入為 List。
在 `componentDidMount()` 生命週期事件中,使用 fetch 方法從 `users.json` url 獲取使用者列表。
使用 FixedSizeList 元件渲染使用者列表。
FixedSizeList 元件的 `innerElementType` 屬性指的是要在元件內部生成的元素。
`itemData`、`itemCount` 和 `itemSize` 分別指的是專案列表、可用專案的總數以及每個專案的大小。
接下來,開啟 App 元件 (src/App.js),幷包含 `SimpleWindow` 元件,如下所示:
import './App.css'
import React from 'react';
import SimpleWindow from './Components/SimpleWindow'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleWindow />
</div>
</div>
</div>
);
}
export default App;
這裡:
使用 `import` 語句匯入我們的新元件 `SimpleWindow`。
渲染我們的新 `SimpleWindow` 元件。
最後,在瀏覽器中開啟應用程式並檢查最終結果。表格元件將按如下所示渲染:
總結
React 預設會最佳化應用程式。此外,React 庫在每個版本中都會改進最佳化。除了這些最佳化之外,我們還可以遵循上面討論的技術來從我們的角度提高效能。
ReactJS - Profiler API
效能分析是一種重要的技術,用於收集和顯示特定函式在即時環境中執行所花費的時間。效能分析通常用於查詢應用程式中的效能瓶頸。React 提供了兩種分析 React 應用程式的選項:
Profiler 元件
Profiler DevTools
Profiler 元件
React Profiler 元件只是另一個用於記錄 React 元件的效能資訊的 React 元件。Profiler 元件可以用於 React 元素樹中的任何位置。React 接受多個 Profiler 以及 Profiler 的多層巢狀。讓我們在本節中檢查簽名以及如何在 React 應用程式中應用 Profiler。
Profiler 元件的簽名
Profiler 元件可以巢狀任何 React 元件,並需要兩個 props:
id
Profiler 元件的識別符號
`onRender` 回撥函式
在元件渲染的每個階段呼叫的回撥函式
回撥函式的簽名如下:
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
){
// Do anything with the profiler information
}
一個示例回撥函式實現,用於將效能分析資料記錄到控制檯,如下所示:
const logProfilerData = (id, phase, actualTime, baseTime, startTime, commitTime) => {
console.log(`${id}'s ${phase} phase:`);
console.log(`Actual time: ${actualTime}`);
console.log(`Base time: ${baseTime}`);
console.log(`Start time: ${startTime}`);
console.log(`Commit time: ${commitTime}`);
};
應用 Profiler
讓我們在本節中建立一個新的 React 應用程式,學習如何應用 `Profiler` 元件。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,建立一個簡單的 Hello 元件,Hello (src/Components/Hello.js),並渲染一條簡單的訊息,如下所示:
import React from "react";
class Hello extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>Hello, {this.props.name}</div>
);
}
}
export default Hello;
這裡:
使用 `name` props 來使用給定的名稱渲染 Hello 訊息。
接下來,開啟 `App` 元件 (src/App.js),並使用 Profiler 元件,如下所示:
import './App.css'
import React, { Profiler } from 'react';
import Hello from './Components/Hello'
const logProfilerData = (id, phase, actualTime, baseTime, startTime, commitTime) => {
console.log(`${id}'s ${phase} phase:`);
console.log(`Actual time: ${actualTime}`);
console.log(`Base time: ${baseTime}`);
console.log(`Start time: ${startTime}`);
console.log(`Commit time: ${commitTime}`);
};
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Profiler id="helloWorld" onRender={logProfilerData}>
<Hello name="World" />
</Profiler>
</div>
</div>
</div>
);
}
export default App;
這裡:
從 react 包中匯入 `Profiler` 元件。
使用 `Hello` 元件,並將其用 Profiler 元件包裝。
建立一個回撥函式 `logProfilerData`,並將所有 Profiler 資料輸出到控制檯。
在 Profiler 元件的 `onRender` props 中設定 `logProfilerData` 回撥函式。
最後,在瀏覽器中開啟應用程式並檢查最終結果。它將渲染 Hello 元件,如下所示:
開啟控制檯,您將看到效能分析資訊,如下所示:
helloWorld's mount phase: App.js:7 Actual time: 4.900000000372529 App.js:8 Base time: 1.800000000745058 App.js:9 Start time: 515.7999999988824 App.js:10 Commit time: 525.9000000003725 ... App.js:6 helloWorld's update phase: App.js:7 Actual time: 1 App.js:8 Base time: 0.6999999992549419 App.js:9 Start time: 19836.900000000373 App.js:10 Commit time: 19838.400000000373
Profiler React DevTools
React DevTools 外掛為 Profiler 引入了一個單獨的部分。開發人員可以開啟 Profiler 選項卡,並獲得許多關於應用程式的有用見解。Profiler DevTools 中提供的一些功能如下:
瀏覽提交
過濾提交
火焰圖
排名圖
元件圖
結論
React Profiler 元件和 Profiler DevTools 是分析和最佳化 React 應用程式不可或缺且強大的工具。
ReactJS - Portals
Portals 提供了一種方法,讓元件可以將其子元件渲染到其自身 DOM 層次結構之外的 DOM 節點中。Portal 可用於模式對話方塊、彈出視窗、工具提示等,在這些情況下,首選將父級(渲染元件)和子 DOM 節點(模式對話方塊)渲染到不同的 DOM 節點中。
讓我們在本節中學習 Portal 的工作原理以及如何在我們的應用程式中應用它。
Portal 的概念和用法
讓我們考慮一下主文件中存在兩個 DOM 節點,如下所示:
<div id='root'></div> <div id='modalRoot'></div>
在這裡,根 DOM 節點將附加到主 React 元件。`modalRoot` 將在 React 應用程式需要顯示模式對話方塊時使用,方法是將模式對話方塊附加到 `modalRoot` DOM 節點,而不是將其渲染到它自己的 DOM 元素內。
這將有助於將模式對話方塊與實際應用程式分開。將模式對話方塊與其父 DOM 元素分開,可以使其免受父 DOM 元素樣式的影響。樣式可以單獨應用,因為模式對話方塊、工具提示等在樣式方面與其父級不同。
React 提供了一種特殊的 `createPortal` 方法,該方法位於 ReactDOM 包中,用於建立 Portal。該方法的簽名如下:
ReactDOM.createPortal(child, container)
這裡:
`child` 是模式對話方塊、工具提示等,由父元件渲染。
render() {
return ReactDOM.createPortal(
this.props.children, // modal dialog / tooltips
domNode // dom outside the component
);
}
`container` 是父 DOM 節點之外的 DOM 元素(以上示例中的 `domNode`)。
應用 Portals
讓我們在本節中建立一個新的 React 應用程式,學習如何應用 Portals。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟 App.css (src/App.css),刪除所有 CSS 類,幷包含模式對話方塊的 CSS。
.modal {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: grid;
justify-content: center;
align-items: center;
background-color: rgba(0,0,0,0.2);
}
.modalContent {
padding: 20px;
background-color: #fff;
border-radius: 2px;
display: inline-block;
min-height: 300px;
margin: 1rem;
position: relative;
min-width: 300px;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
justify-self: center;
}
接下來,開啟 `index.html` (public/index.html),並新增一個 DOM 節點來支援 Portals。
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div style="padding: 10px;">
<div id="root"></div>
</div>
<div id="modalRoot"></div>
</body>
</html>
接下來,建立一個簡單的元件 `SimplePortal` (src/Components/SimplePortal.js),並渲染一個模式對話方塊,如下所示:
import React from "react";
import PortalReactDOM from 'react-dom'
const modalRoot = document.getElementById('modalRoot')
class SimplePortal extends React.Component {
constructor(props) {
super(props);
}
render() {
return PortalReactDOM.createPortal(
<div
className="modal"
onClick={this.props.onClose}
>
<div className="modalContent">
{this.props.children}
<hr />
<button onClick={this.props.onClose}>Close</button>
</div>
</div>,
modalRoot,
)
}
}
export default SimplePortal;
這裡:
`createPortal` 用於建立一個新的 Portal 並渲染一個模式對話方塊。
模式對話方塊的內容透過 `this.props.children` 從元件的子元件中獲取。
關閉按鈕操作透過 props 處理,並將由父元件處理。
接下來,開啟 `App` 元件 (src/App.js),並使用 `SimplePortal` 元件,如下所示:
import './App.css'
import React from 'react';
import SimplePortal from './Components/SimplePortal'
class App extends React.Component {
constructor(props) {
super(props);
this.state = { modal: false }
}
handleOpenModal = () => this.setState({ modal: true })
handleCloseModal = () => this.setState({ modal: false })
render() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<div><p>Main App</p></div>
<div>
<button onClick={this.handleOpenModal}>
Show Modal
</button>
{ this.state.modal ? (
<SimplePortal onClose={this.handleCloseModal}>
Hi, I am the modal dialog created using portal.
</SimplePortal>
) : null}
</div>
</div>
</div>
</div>
);
}
}
export default App;
這裡:
匯入 `SimplePortal` 元件。
新增一個按鈕來開啟模式對話方塊。
建立一個處理程式來開啟模式對話方塊。
建立一個處理程式來關閉模式對話方塊,並透過 `onClose` props 將其傳遞給 `SimplePortal` 元件。
最後,在瀏覽器中開啟應用程式並檢查最終結果。
總結
React Portal 提供了一種簡單的方法來訪問和處理元件外部的 DOM。它無需任何額外工作即可實現跨不同 DOM 節點的事件冒泡。
ReactJS - 無 ES6 ECMAScript
根據 Ecma 國際組織的定義,ECMAScript 是一種通用的、與供應商無關的跨平臺程式語言。Ecma 國際組織定義了 ECMAScript 語言的語法、其功能以及語言的各個方面,並將其釋出為 ECMAScript 規範。JavaScript 是 ECMAScript 的流行實現之一,用於瀏覽器中的客戶端程式設計。
ECMAScript 的最新規範是 ECMAScript 2022,最流行的規範是 ECMAScript 2015 語言規範,也稱為 ES6。即使幾乎所有現代瀏覽器都支援 ES6,但舊版瀏覽器對 ES6 的支援仍然不足。現在,在客戶端指令碼中使用 ES6 功能被認為是安全的。
可以使用 ES6 語言規範開發 React 應用程式。React 庫使用的一些核心 ES6 功能是 ES6 類和 ES6 模組。為了支援不允許 ES5 語法的舊版瀏覽器,React 提供了使用 ES5 建立元件的替代語法。
建立 React 類 (create-react-class)
create-react-class 是 React 社群提供的模組,用於在不使用 ES6 語法的情況下建立新元件。此外,我們應該在當前應用程式中安裝 create-react-class 包以使用 ES5 語法。
讓我們使用 create-react-app 建立一個 React 應用程式。
create-react-app myapp
現在,將 create-react-class 包安裝到我們新建立的應用程式中,如下所示:
npm i create-react-class --save
現在,透過執行以下命令來執行應用程式:
cd myapp npm start
讓我們看看如何使用 ES5 (myapp/src/components/ES5/HelloWorldES6.js) 和 ES6 語法 (myapp/src/components/ES5/HelloWorldES6.js) 建立一個簡單的 Hello World 元件,並學習使用 ES5 語法需要做什麼。
ES6 語法中的 HelloWorldES6 元件如下:
import React from 'react'
class HelloWorldES6 extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
export default HelloWorldES6
可以使用以下程式碼在 ES5 語法中建立相同的元件 (myapp/src/components/ES5/HelloWorldES5.js),如下所示:
var createReactClass = require('create-react-class');
var HelloWorldES5 = createReactClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
export default HelloWorldES5;
現在,讓我們在我們的應用程式 (App.js) 中使用該元件,如下所示:
import HelloWorldES5 from "./components/ES5/HelloWorldES5";
import HelloWorldES6 from "./components/ES5/HelloWorldES6";
function App() {
return (
<div>
<HelloWorldES5 name="Peter" />
<HelloWorldES6 name="John" />
</div>
);
}
export default App;
應用程式的輸出如下:
為 props 設定預設值 (getDefaultProps)
讓我們在 ES6 中為 name props 設定預設值。
class HelloWorld extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
HelloWorld.defaultProps = {
name: 'John'
}
可以使用以下 ES5 語法實現相同的功能:
var createReactClass = require('create-react-class');
var HelloWorld = createReactClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
},
getDefaultProps: function() {
return {
name: 'John'
};
}
});
在這裡,getDefaultProps 是一個特殊的函式,用於為元件的 props 設定預設值。
設定初始狀態 (getInitialState)
眾所周知,可以在元件類建構函式中使用 `this.state` 來設定狀態的初始值。這是 ES6 類功能之一。
import React from 'react'
class BookListES6 extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ['C++ Programming', 'Java Programming']
};
}
render() {
return <ol>
{this.state.list && this.state.list.map((item) =>
<li>{item}</li>
)}
</ol>
}
}
export default BookListES6
可以使用 ES5 語法中的 getInitialState 實現相同的功能,如下所示:
var createReactClass = require('create-react-class');
var BookListES5 = createReactClass({
getInitialState: function() {
return {
list: ['React Programming', 'Javascript Programming']
};
},
render: function() {
return <ol>
{this.state.list && this.state.list.map((item) =>
<li>{item}</li>
)}
</ol>
}
});
export default BookListES5;
現在,讓我們在我們的應用程式 (App.js) 中使用該元件,如下所示:
import BookListES6 from "./components/ES5/BookListES6";
import BookListES5 from "./components/ES5/BookListES5";
function App() {
return (
<div>
<BookListES6 />
<BookListES5 />
</div>
);
}
export default App;
應用程式的輸出如下:
事件處理程式的自動繫結
眾所周知,在 React 元件類中定義的事件處理程式方法需要在類建構函式中繫結到 this 物件。虛擬碼如下:
constructor(props) {
super(props);
this.state = {message: 'Hello!'};
// binding of `this` object
this.handleClick = this.handleClick.bind(this);
}
在 ES5 中,不需要繫結,因為該函式預設繫結到 this 物件。
ReactJS - 無 JSX 的 React
讓我們在本節中學習如何使用 createElement 建立 React 元件,而不是預設的 JSX。
什麼是 JSX?
JSX 是一種 JavaScript 擴充套件,用於使用類似於 HTML 的語法建立任意 HTML 元素。這將簡化 HTML 文件的建立,並易於理解文件。React 將 JSX 轉換為包含 React 的 createElement 函式呼叫的 JavaScript 物件,然後再執行它。
它提高了應用程式的效能。此外,React 還允許使用純 createElement 函式建立 HTML 文件,而無需使用 JSX。這使開發人員能夠在 JSX 不太適合的情況下直接建立 HTML 文件。
什麼是 createElement?
React.createElement 是核心 React API,用於生成和渲染 HTML 文件。createElement 方法有三個引數:
元件名稱
作為物件的 props
元件的內部內容/子元件
在這裡,子元件可以指另一個元件,同樣使用 createElement 建立。createElement 可以巢狀到任何級別。使用 React.createElement 建立元件的示例程式碼如下:
React.createElement('h1', null, 'Hello World')
這將渲染下面提到的HTML文件
<h1>Hello World</h1>
工作示例
讓我們建立一個名為BookListUsingCreateElement的元件來學習和理解createElement方法。
首先,使用create-react-app建立一個新的應用程式
create-react-app myapp
然後,在components資料夾下新增一個新的元件BookListUsingCreateElement。初始程式碼如下:
import React from 'react'
class BookListUsingCreateElement extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ['C++ Programming', 'Java Programming', "JavaScript Programming"]
};
}
}
export default BookListUsingCreateElement
這裡,list是元件中最初可用的書籍集合。
現在,讓我們在render函式中使用createElement來渲染書籍,如下所示。
render() {
let content = React.createElement(
'ol',
null,
this.state.list.map(
(item) =>
React.createElement('li', null, item)
)
)
return content;
}
這裡,我們在兩個地方使用了createElement。首先,我們用它建立元件的最頂層,也就是ul HTML元素。其次,我們多次使用createElement根據list中可用的書籍建立li元素。我們使用了map函式來遍歷list中的所有書籍,並使用React.createElement('li', null, item)程式碼為每本書建立一個li元素。
最後,元件的完整程式碼如下
import React from 'react'
class BookListUsingCreateElement extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ['C++ Programming', 'Java Programming', "JavaScript Programming"]
};
}
render() {
let content = React.createElement(
'ol',
null,
this.state.list.map(
(item) =>
React.createElement('li', null, item)
)
)
return content;
}
}
export default BookListUsingCreateElement
讓我們透過App.js使用我們新建立的元件,如下所示:
import BookListUsingCreateElement from "./components/CreateElement/BookListUsingCreateElement";
function App() {
return (
<div>
<BookListUsingCreateElement />
</div>
);
}
export default App;
現在,使用以下命令執行應用程式
npm start
應用程式將在預設瀏覽器中啟動並顯示以下結果
ReactJS - Reconciliation
協調是React庫的內部過程。眾所周知,React應用程式將建立一個虛擬DOM,然後根據虛擬DOM更新應用程式的實際DOM。每當React收到更新請求時,它將首先建立一個虛擬DOM,然後使用不同的diff演算法將虛擬DOM與先前狀態進行比較,只有在絕對必要時,才會更新DOM。
即使diff演算法和更新DOM是React核心的內部過程,瞭解一些內部機制也有助於我們調整應用程式以最大限度地利用React庫。
Diff演算法
讓我們在本節中瞭解React核心應用的一些diff演算法。
相同型別的元素
每當React元件將元素從一種型別更改為另一種型別(例如從div更改為更具體的p)時,整個React虛擬DOM樹都將發生更改,並觸發DOM更新。
<!-- Before --> <div> <Content /> </div> <!-- After --> <p> <Content /> </p>
這裡,整個元素將被更新。
相同型別的DOM屬性。
當元素型別相同時,React將檢查屬性的差異。如果React發現屬性及其值的任何新更改,則它將只更新已更改的屬性。
<!-- Before --> <div className="someClass"> <Content /> </div> <!-- After --> <div className="someOtherClass"> <Content /> </div>
這裡,只有DOM例項的class屬性將被更新。
相同型別的DOM屬性(樣式)。
當元素型別相同且React發現樣式屬性存在差異時,它將只更新樣式的屬性。
<!-- Before -->
<div style={{fontFamily: 'Arial'}} />
<p> ... </p>
</div>
<!-- After -->
<div style={{color: 'red', fontFamily: 'Arial'}} />
<p> ... </p>
</div>
這裡,只有div元素樣式的color屬性將被更新
相同型別的元件元素 - 每當React看到相同型別的React元件時,它將呼叫元件的componentWillUpdate事件和其他更新事件以更新其狀態。然後,它將呼叫元件的render方法,並且演算法會遞迴。
相同型別的子元素集合 - 每當React看到相同型別的子元素集合時,它將按順序檢查元素是否存在差異。因此,如果我們有一個新的第一個子元素,則整個集合將被更新。
<!-- Before --> <ul> <li>Peter</li> <li>Olivia</li> </ul> <!-- After --> <ul> <li>John</li> <li>Peter</li> <li>Olivia</li> </ul>
這裡,所有元素(ul元素的子元素)都將被更新,因為第一個元素(li)已更新。
為了解決這個問題,我們可以引入一個key屬性,如下面的程式碼片段所示。React為此目的有一個特殊的key屬性。
<!-- Before --> <ul> <li key="1">Peter</li> <li key="2">Olivia</li> </ul> <!-- After --> <ul> <li key="3">John</li> <li key="1">Peter</li> <li key="2">Olivia</li> </ul>
總結
React試圖在每次釋出中最佳化diff演算法,以確保更新最小。最小更新意味著應用程式效能更好。瞭解內部機制並遵循最佳實踐進行編碼,我們可以成倍地提高應用程式效能。
ReactJS - Refs 和 DOM
隨著元件狀態的變化,React會自動發出HTML元素。這極大地簡化了UI開發,因為更新元件的狀態就足夠了。但是,傳統上,直接訪問DOM元素是更新元件UI的規範。
有時我們可能也需要回退到直接訪問DOM元素並更新React應用程式中的UI。React ref在這種情況下提供了幫助。它提供對DOM元素的直接訪問。此外,它確保元件與React虛擬DOM和HTML DOM平滑地工作。
React提供了一個函式createRef,用於在基於類的元件中建立ref。讓我們在本節中學習如何使用createRef。
createRef方法的簽名
createRef的目的是返回一個可變物件,該物件將在重新渲染之間持久存在。createRef的簽名如下:
<refObj> = React.createRef()
這裡,refObj是hook返回的物件
為了自動將DOM物件附加到refObj,它應該設定在元素的ref屬性中,如下所示:
<input ref={refObj} />
要訪問附加的DOM元素,請使用refObj的current屬性,如下所示:
const refElement = refObj.current
應用ref
讓我們在本節中學習如何透過建立一個React應用程式來應用createRef。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,在component資料夾(src/components/SimpleRef.js)下建立一個React元件SimpleRef。
import React from "react";
class SimpleRef extends React.Component {
render() {
return (
<div>Hello World</div>
);
}
}
export default SimpleRef;
接下來,開啟App.css(src/App.css)並刪除所有樣式。然後,開啟App元件(src/App.js)並使用我們的新SimpleRef元件更新內容,如下所示:
import './App.css'
import SimpleRef from './Components/SimpleRef'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleRef />
</div>
</div>
</div>
);
}
export default App;
接下來,向SimpleRef元件新增計數器功能,如下所示:
import React from "react";
class SimpleRef extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(
prevState => ({
count: prevState.count + 1
})
)
}
render() {
return (
<div>
<div>Counter: {this.state.count} <button onClick={this.handleClick}>+</button></div>
</div>
)
}
}
export default SimpleRef;
這裡我們有:
使用this.setState處理計數器狀態變數(count)。
在JSX中渲染計數器狀態變數
新增一個按鈕並附加一個點選處理程式事件(this.handleClick),它將使用this.setState方法遞增計數器
接下來,新增一個輸入欄位,並根據使用者在輸入欄位中輸入的值顯示問候訊息,如下所示:
import React from "react";
import { createRef } from "react";
class SimpleRef extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
this.inputRef = createRef()
this.labelRef = createRef()
}
handleClick() {
this.setState(prevState => ({
count: prevState.count + 1
}))
}
handleChange() {
this.labelRef.current.innerText =
this.inputRef.current.value == "" ? "World" : this.inputRef.current.value
}
render() {
return (
<div>
<div>Counter: {this.state.count} <button onClick={this.handleClick}>+</button></div>
<div style={{ paddingTop: "5px"}}>
<label>Enter your name: </label><input type="text" name="username"
ref={this.inputRef} onChange={this.handleChange}/>
<br />
<div>Hello, <span ref={this.labelRef}></span></div>
</div>
</div>
)
}
}
export default SimpleRef;
這裡我們有:
建立一個ref,this.inputRef來表示輸入元素,並透過ref屬性將其附加到相關元素
建立一個ref,this.labelRef來表示問候訊息元素,並透過ref屬性將其附加到相關元素
將事件處理程式this.handleChange附加到輸入元素。事件處理程式使用this.inputRef ref獲取問候訊息,並使用this.labelRef ref更新訊息
接下來,在瀏覽器中開啟應用程式並輸入您的姓名。應用程式將更新問候訊息,如下所示。
檢查您的控制檯,您會注意到元件沒有重新渲染。因為 React 只有在狀態發生變化時才會重新渲染,而 ref 不會進行任何狀態更改,所以元件不會重新渲染。
接下來,單擊“+”按鈕。它將透過重新渲染元件來更新計數器,因為狀態 (count) 發生了變化。如果您仔細觀察,您會發現訊息保持不變。這種行為的原因是 ref 值在 React 的渲染之間得以保留。
createRef的用例
createRef的一些用例如下:
訪問JavaScript DOM API - JavaScript DOM API提供了豐富的功能來操作應用程式的UI。當應用程式功能需要訪問JavaScript DOM API時,可以使用createRef來檢索原始DOM物件。一旦檢索到原始DOM物件,應用程式就可以使用DOM API訪問所有功能。DOM API的一些示例如下:
聚焦輸入元素
選擇文字
使用媒體播放 API 播放音訊或影片
**命令式動畫** — Web 動畫 API 透過指令式程式設計而不是宣告式程式設計提供豐富的動畫功能。要使用 Web 動畫 API,我們需要訪問原始 DOM。
與第三方庫整合 - 由於第三方庫需要訪問原始DOM才能執行其功能,因此必須使用createRef從React獲取DOM引用並將其提供給第三方庫。
總結
即使React開發UI的方式簡單易用,但在某些情況下,基於DOM API開發UI也有其自身的優勢。createRef hook非常適合這些場景,並提供簡單幹淨的API來直接訪問DOM元素及其API。
ReactJS - Render Props
由於React元件透過組合(在一個元件內包含另一個元件)而不是透過繼承進行互連,因此在一個React元件中使用的邏輯不會直接共享到另一個元件。React提供了多個選項來在元件之間共享邏輯,其中一個選項是Render props。Render props基本上是透過其props將具有必要渲染邏輯的函式傳遞給具有核心功能的元件。因此,它被稱為render props。
讓我們在本節中學習如何使用render props。
如何使用render props
讓我們一步一步地瞭解如何使用render props並在兩個元件之間共享邏輯。讓我們考慮從外部URL獲取和渲染資料的場景。
建立一個元件FetcherComponent來從外部URL獲取資料,並建立一個元件FetcherConsumerComponent來使用資料並進行渲染。
建立一個元件FetcherComponent,用於針對給定的URL(props.url)進行資料獲取邏輯。
componentDidMount() {
fetch(this.props.url)
.then((response) => response.json())
.then((data) => {
this.setState({
data: data
});
});
}
現在,更新FetcherComponent,以便核心渲染邏輯將委託給props(this.props.render)。
render() {
return (
<div>
<h2>Fetch react component</h2>
{this.state.data && this.props.render(this.state.data)}
</div>
)
}
這裡:
this.props.render是具有渲染邏輯的函式,將透過其props由任何其他元件傳遞到FetcherComponent。
建立一個元件FetcherConsumerComponent,並透過傳遞獲取資料的渲染邏輯來渲染FetcherComponent。
render() {
return (<FetcherComponent url="users.json" render={(items) => (
<ul>
{items && items.length && items.map((item) =>
<li key={item.id}>{item.name}</li>
)}
</ul>
)} />)
}
這裡:
items是由FetcherComponent元件獲取的資料。
它們被迴圈遍歷併發出HTML無序列表。
我們可以按照本節中定義的步驟在以下部分建立一個可工作的示例。
應用render props
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟App.css(src/App.css)並刪除所有CSS類。然後,建立一個具有資料獲取邏輯的元件FetchRenderProps(src/Components/FetchRenderProps.js),如下所示:
import React from "react";
class FetchRenderProps extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
componentDidMount() {
fetch(this.props.url)
.then((response) => response.json())
.then((data) => {
console.log(data)
this.setState({
data: data
});
});
}
render() {
return (
<div>
<h2>Fetch react component</h2>
{this.state.data && this.props.render(this.state.data)}
</div>
)
}
}
export default FetchRenderProps;
這裡我們有:
在componentDidMount事件中使用fetch javascript方法從外部URL獲取資料。
使用透過props傳遞的render方法渲染獲取的資料。
接下來,在 public 資料夾中建立一個檔案 users.json (public/users.json) 來儲存使用者資訊。我們將嘗試使用 FetchRenderProps 元件獲取它並在我們的應用程式中顯示它。
[{"id":1,"name":"Fowler","age":18},
{"id":2,"name":"Donnell","age":24},
{"id":3,"name":"Pall","age":26}]
接下來,在 public 資料夾中建立一個檔案 todo_list.json (public/todo_list.json) 來儲存待辦事項列表資訊。我們將嘗試使用 FetchRenderProps 元件獲取它並在我們的應用程式中顯示它。
[{"id":1,"title":"Learn JavaScript","is_done":true},
{"id":2,"title":"Learn React","is_done":true},
{"id":3,"title":"Learn Typescript","is_done":false
接下來,建立一個元件SimpleRenderProps(src/Components/SimpleRenderProps.js)來使用FetchRenderProps元件,如下所示:
import React from "react";
import FetchRenderProps from "./FetchRenderProps";
class SimpleRenderProps extends React.Component {
render() {
return (
<>
<FetchRenderProps url="users.json" render={(items) => (
<ul>
{items && items.length && items.map((item) =>
<li key={item.id}>{item.name}</li>
)}
</ul>
)} />
<FetchRenderProps url="todo_list.json" render={(items) => (
<ul>
{items && items.length && items.map((item) =>
<li key={item.id}>{item.title} {item.is_done && <strong>Done</strong>}</li>
)}
</ul>
)} />
</>
)
}
}
export default SimpleRenderProps;
這裡我們有:
使用FetchRenderProps和users.json來獲取和渲染使用者列表
使用FetchRenderProps和todo_list.json來獲取和渲染待辦事項列表
獲取使用者和待辦事項列表都使用了相同的FetchRenderProps元件。
接下來,開啟App.js檔案並渲染一個SimpleRenderProps元件,如下所示:
import './App.css'
import React from 'react';
import SimpleRenderProps from './Components/SimpleRenderProps'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleRenderProps />
</div>
</div>
</div>
);
}
export default App;
最後,在瀏覽器中開啟應用程式並檢查最終結果。應用程式將按如下所示進行渲染:
總結
Render props是在元件之間共享邏輯的有效方法。它在許多第三方元件中被廣泛使用,並取得了良好的成功率,並且是React領域中經過時間考驗的共享邏輯方法。
ReactJS - 靜態型別檢查
由於JavaScript是一種動態型別的語言,因此很難在執行程式碼之前找到型別不匹配錯誤。React透過prop-types包支援對props的型別檢查,這可以用於在開發階段識別屬性的型別不匹配。
程式的其他方面仍然需要一個工具來在開發階段正確識別型別問題。JavaScript有很多靜態型別檢查器工具來處理型別問題。我們將檢查兩個流行的選項,它們可以平滑地整合到React應用程式的工作流程中,並在應用程式的開發階段提示可能的型別錯誤。它們如下:
Flow
TypeScript語言
Flow
Flow是JavaScript的靜態型別檢查器。Flow擴充套件了JavaScript語言以指定型別,並允許在JavaScript程式碼中設定靜態型別註釋。Flow將檢查開發人員在程式碼中設定的靜態型別註釋,並確保使用了正確的型別。否則,它將丟擲錯誤。一個簡單的例子如下:
// @flow
function sum(a: number, b: number) : number {
return a + b;
}
sum(10, 20) // Pass
sum("10", "20") // Fail
這裡,// @flow註釋使flow靜態檢查器能夠分析下面定義的函式。正如你所看到的,函式sum使用Flow語言擴充套件來指定引數的型別。讓我們看看如何在React專案中啟用Flow以及啟用Flow的React專案的開發工作流程。
步驟1 - 使用create-react-app CLI應用程式建立一個新的React專案。
create-react-app myapp
步驟2 - 使用以下命令將Flow新增到專案
cd myapp npm install --save-bin flow-bin
步驟3 - 現在,在package.json檔案的scripts中新增Flow命令
{
// ...
"scripts": {
"flow": "flow",
// ...
},
// ...
}
這將允許我們透過npm執行flow命令
步驟4 - 使用flow命令初始化flow配置,如下所示:
npm run flow init
這將在專案的根目錄建立一個基本flow配置檔案.flowconfig,內容如下。
[ignore] [include] [libs] [lints] [options] [strict]
可以在這裡新增高階flow選項。預設情況下,flow將檢查應用程式中的所有檔案。要忽略node_modules,請在[ignore]選項下新增.*/node_modules/.*。這將指示flow應用程式忽略node_modules資料夾中的所有檔案。
[ignore] .*/node_modules/.* [include] [libs] [lints] [options] react.runtime=automatic [strict]
步驟 5 − 現在,我們已經將流程配置到我們的應用程式中。我們可以將流程註解新增到我們的程式碼中,並使用以下命令對其進行測試
npm run flow
Flow 將檢查我們的程式碼,並在控制檯中顯示如下所示的類似結果:
> myapp@0.1.0 flow /path/to/myapp > flow Launching Flow server for /path/to/myapp Spawned flow server (pid=1629) Logs will go to /private/tmp/flow/zSUserszSbalazSProjectszSArticleszSreact-revision-v2zSworkspacezSmyapp.log Monitor logs will go to /private/tmp/flow/zSUserszSbalazSProjectszSArticleszSreact-revision-v2zSworkspacezSmyapp.monitor_log No errors!
步驟 6 − 現在,在我們的程式碼中使用流程註解是可以的。讓我們在程式碼中新增一個簡單的流程註解,並執行 flow 命令來檢查程式碼的正確性。建立一個簡單的 HelloWorld 元件 (src/components/HelloWorld.js),如下所示:
import React from 'react'
class HelloWorld extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
export default HelloWorld
步驟 7 − 將元件包含到我們的根元件 (App.js) 中,如下所示:
// @flow
import React from "react";
import HelloWorld from "./components/HelloWorld";
function App() : any {
var name: string = 10
return (
<div>
<HelloWorld name={name} />
</div>
)
}
export default App;
步驟 8 − 現在,使用 flow 檢查程式碼,如下所示:
npm run flow
Flow 命令將檢查程式碼並顯示 name 屬性被設定為字串值的錯誤,如下所示。
> myapp@0.1.0 flow /path/to/myapp
> flow
Error ............................................src/App.js:6:22
Cannot assign 10 to name because number [1] is incompatible with string [2]. [incompatible-type]
3│ import HelloWorld from "./components/HelloWorld";
4│
5│ function App() : any {
[2][1]6│ var name: string = 10
7│
8│ return (
9│ <div>
Found 1 error
.....
.....
步驟 9 − 讓我們透過為 name 變數提供字串值來修復錯誤,然後重新執行 flow 命令。
// @flow
import React from "react";
import HelloWorld from "./components/HelloWorld";
function App() : any {
var name: string = "John"
return (
<div>
<HelloWorld name={name} />
</div>
)
}
export default App;
現在,flow 命令將成功執行,並顯示程式碼中沒有問題。
> myapp@0.1.0 flow /path/to/myapp > flow No errors!
步驟 10 − 最後,我們可以執行以下命令來執行應用程式:
npm start
可以使用以下命令建立最佳化的生產版本:
npm run build
TypeScript
TypeScript 是一種對靜態型別具有首要支援的語言。靜態型別使 TypeScript 能夠在編譯時而不是執行時捕獲型別錯誤。TypeScript 支援 JavaScript 的所有語言特性。
因此,對於 JavaScript 開發人員來說,使用 TypeScript 非常容易。React 內建支援 TypeScript。要建立一個 React 專案,只需在使用 create-react-app 建立 React 應用時包含 TypeScript 模板即可。
create-react-app myapp --template typescript
應用程式建立完成後,在 src/components 資料夾下新增一個新的 HelloWorld 元件 (HelloWorld.tsx),如下所示:
// src/components/HelloWorld.tsx
import React from 'react'
class HelloWorld extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
export default HelloWorld
現在,包含 HelloWorld 元件:
import React from "react";
import HelloWorld from "./components/HelloWorld";
function App() : any {
var name: string = 10
return (
<div>
<HelloWorld name={name} />
</div>
)
}
export default App;
這裡,我們故意將 name 的值設定為 10。讓我們執行應用程式並檢查編譯器是否捕獲到錯誤。
npm start
執行命令會丟擲如下所示的錯誤:
...
...
ERROR in src/App.tsx:5:7
TS2322: Type 'number' is not assignable to type 'string'.
3 |
4 | function App() : any {
> 5 | var name: string = 10
| ^^^^
6 |
7 | return (
8 | <div>
...
...
讓我們更改 name 的值並設定為字串值 ("John")。上面的錯誤已修復,但編譯器仍在 HelloWorld 元件中丟擲錯誤,如下所示:
Issues checking in progress...
ERROR in src/App.tsx:9:19
TS2769: No overload matches this call.
Overload 1 of 2, '(props: {} | Readonly<{}>): HelloWorld', gave the following error.
Type '{ name: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & Readonly<{}>'.
Property 'name' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & Readonly<{}>'.
Overload 2 of 2, '(props: {}, context: any): HelloWorld', gave the following error.
Type '{ name: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & Readonly<{}>'.
Property 'name' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & Readonly<{}>'.
7 | return (
8 | <div>
> 9 | <HelloWorld name={name} />
| ^^^^
10 | </div>
11 | )
12 | }
ERROR in src/components/HelloWorld.tsx:9:39
TS2339: Property 'name' does not exist on type 'Readonly<{}>'.
7 | class HelloWorld extends React.Component {
8 | render() {
> 9 | return <h1>Hello, {this.props.name}</h1>
| ^^^^
10 | }
11 | }
12 |
要修復錯誤,應為 HelloWorld 元件提供其屬性的型別資訊。為屬性建立一個新的介面,然後將其包含在 HelloWorld 元件中,如下所示:
import React from 'react'
interface HelloWorldProps {
name: string
}
class HelloWorld extends React.Component<HelloWorldProps> {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
export default HelloWorld
最終,我們的程式碼編譯成功,沒有錯誤,可以透過 `https://:3000/` 檢視。
No issues found.
ReactJS - Strict Mode
React 基於元件、屬性和狀態的概念構建。React 提供相對較少的 API 來建立和更新元件。React 的基本功能足以滿足我們前端應用程式的大部分需求。但是,現代應用程式仍然存在複雜的場景,需要高階前端邏輯。React 提供了許多高階 API,這將幫助我們建立複雜的前端應用程式。高階 API 有一定的成本。高階 API 比較難學習,並在我們的應用程式中應用。
React 提供了一個嚴格模式,它透過突出顯示應用程式開發階段的潛在問題來幫助開發人員正確應用高階 API。眾所周知,React 中的一切都是基於元件的概念構建的,嚴格模式只是一個非可視元件,React.StrictMode。嚴格模式元件的使用也非常簡單。只需使用 React.StrictMode 元件包裝要分析的元件,如下所示:
<React.StrictMode> <OurComplexComponent /> </React.StrictMode>
嚴格模式的另一個功能是在應用程式中使用某些遺留或已棄用的 API(最終會在應用程式中引入錯誤)時丟擲錯誤或警告。
讓我們在本節中學習嚴格模式元件突出顯示的專案列表。
不安全的生命週期使用
React 發現一些舊的生命週期事件在基於非同步的應用程式中是不安全的。它們如下所示:
componentWillMount
componentWillReceiveProps
componentWillUpdate
不安全意味著它會在應用程式中建立難以除錯的錯誤。React 最終會在庫的未來版本中刪除這些不安全生命週期事件。在此之前,開發人員會在使用舊的不安全生命週期事件時收到警告。
遺留 ref API 使用
早期版本的 React 使用基於字串的 ref 管理,後來添加了基於回撥的 ref 管理。由於其穩定性和不易出錯的特性,推薦使用基於回撥的選項。如果我們使用基於字串的選項,嚴格模式將丟擲警告。
最新版本的 React 提供了改進的 ref 管理選項,它易於編寫程式碼且不易出錯。
遺留的 findDOMNode 使用
findDOMNode 幫助搜尋給定類例項的 DOM 節點樹。findDOMNode 的使用已棄用,並提倡使用基於 ref 的 DOM 管理。
副作用
React 有兩個主要階段,渲染和提交。渲染階段涉及大量工作且耗時,而提交階段則簡單快捷。React 引入了併發模式,這將提高渲染階段的效能。併發模式的要求之一是渲染階段使用的生命週期事件不應包含副作用。
React 將嘗試在開發階段使用不同的方法查詢意外的副作用,並將其報告為警告。
遺留上下文 API
遺留上下文 API 易於出錯,不建議使用。它將在未來的版本中刪除。在此之前,嚴格模式將檢測遺留上下文 API 的使用並進行報告。
可重用狀態
具有可重用狀態的元件可以多次掛載和銷燬而不會產生任何副作用,這將有助於提高應用程式的效能。React 庫的未來版本將在保留狀態的同時新增或刪除 UI 部分。React 18 引入了一個嚴格模式功能,它將嘗試解除安裝和重新掛載元件以確保元件具有彈性。此階段的任何問題都將被報告。
ReactJS - Web Components
React 和 Web 元件可以混合在 Web 應用程式中。React 元件可以包含一個或多個 Web 元件,而 Web 元件可以使用 React 元件來建立其內容。React 支援這兩種選項。
在 React 應用程式中使用 Web 元件
讓我們建立一個 Web 元件,然後嘗試將其應用於 React 應用程式。首先,建立一個新的 React 應用程式並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,建立一個簡單的 Web 元件,HelloMessage (public/WebComponents/HelloMessage.js),並新增以下程式碼。此 Web 元件的目的是歡迎使用者(透過在 Web 元件的name 屬性中指定使用者名稱)。
// web component
class HelloMessage extends HTMLElement {
constructor() {
super();
this.name = 'Folks';
}
static get observedAttributes() {
return ['name'];
}
attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue) return;
this[property] = newValue;
}
connectedCallback() {
this.textContent = `Hello ${this.name}!`;
}
}
customElements.define('hello-message', HelloMessage);
這裡:
connectedCallback() 用於建立 Web 元件的內容。
observedAttributes 函式訪問 name 屬性。
attributeChangedCallback 函式更新 name 屬性的值,如果它在應用程式中發生更改。
customElements.define 用於將建立的 Web 元件及其標籤名稱附加到 Web 文件中。
接下來,開啟 index.html (public/index.html) 檔案,並新增 Web 元件,如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="%PUBLIC_URL%/WebComponents/HelloMessage.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div style="padding: 10px;">
<div id="root"></div>
<div style="padding: 10px;">
<hello-message name="John"></hello-message>
</div>
</div>
</body>
</html>
這裡我們有:
在 head 部分包含 Web 元件
在頁面中使用 hello-message Web 元件來展示其用法
接下來,建立一個簡單的元件,SimpleWebComponent (src/Components/SimpleWebComponent.js),並渲染新建立的 Web 元件,如下所示:
import React from "react";
class SimpleWebComponent extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<hello-message name="Peter"></hello-message>
);
}
}
export default SimpleWebComponent;
這裡,Web 元件hello 用在元件的 render 方法中。
接下來,開啟App元件 (src/App.js),並使用SimpleWebComponent元件,如下所示:
import './App.css'
import React from 'react';
import SimpleWebComponent from './Components/SimpleWebComponent'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleWebComponent />
</div>
</div>
</div>
);
}
export default App;
這裡我們有:
從 React 包中匯入SimpleWebComponent元件
使用SimpleWebComponent元件並渲染 hello Web 元件
最後,在瀏覽器中開啟應用程式並檢查最終結果。
總結
React 和 Web 元件以良好的方式相互補充。兩者各有優缺點,可以透過分析其在應用程式中的優缺點,將其用於單個應用程式中。
ReactJS - 日期選擇器
React 透過第三方 UI 元件庫提供表單元件。React 社群提供了大量的 UI/UX 元件,很難為我們的需求選擇合適的庫。
Bootstrap UI 庫是開發人員的流行選擇之一,並且被廣泛使用。React Bootstrap (https://react-bootstrap.github.io/) 已將幾乎所有 Bootstrap UI 元件移植到 React 庫中,並且對日期選擇器元件也具有最佳支援。
讓我們在本節中學習如何在本章中使用react-bootstrap庫中的日期選擇器元件。
什麼是日期選擇器?
日期選擇器允許開發人員輕鬆選擇日期,而不是透過文字框輸入日期,幷包含正確的格式詳細資訊。HTML input 元素具有 type 屬性,用於引用要輸入到元素中的資料型別。其中一種型別是 date。在 input 元素中設定 type 將啟用日期選擇器。
<input type="date">
React Bootstrap 提供Form.Control元件來建立各種輸入元素。開發人員可以使用它來建立日期選擇器控制元件。Form.Control的一些有用屬性如下:
ref (ReactRef) − 用於訪問底層 DOM 節點的 Ref 元素
as (elementType) − 啟用指定除 *<input>* 之外的元素
disabled (boolean) − 啟用/停用控制元件元素
htmlSize (number) − 底層控制元件元素的大小
id (string) − 控制元件元素的 ID。如果此處未指定,則使用父 *Form.Group* 元件的 *controlId*。
IsInValid (boolean) − 啟用/停用與無效資料關聯的樣式
IsValid (boolean) − 啟用/停用與有效資料關聯的樣式
plaintext (boolean) − 啟用/停用輸入並將其呈現為純文字。與 *readonly* 屬性一起使用
readOnly (boolean) − 啟用/停用控制元件的只讀屬性
size (sm | lg) − 輸入元素的大小
type (string) − 要呈現的輸入元素的型別
value (string | arrayOf | number) − 底層控制元件的值。由 *onChange* 事件操作,初始值預設為 *defaultValue* 屬性
bsPrefix (string) − 用於自定義底層 CSS 類的字首
onChange (boolean) − 當觸發 *onChange* 事件時要呼叫的回撥函式
一個簡單的日期控制元件元件可以用作如下所示:
<Form.Group className="mb-3" controlId="password"> <Form.Label>Date of birth</Form.Label> <Form.Control type="date" /> </Form.Group>
應用日期選擇器元件
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 bootstrap 庫:
npm install --save bootstrap react-bootstrap
接下來,開啟 App.css (src/App.css) 並刪除所有 CSS 類。
// remove css classes
接下來,建立一個簡單的日期元件,SimpleDatePicker (src/Components/SimpleDatePicker.js),並渲染一個表單,如下所示:
import { Form, Button } from 'react-bootstrap';
function SimpleDatePicker() {
return (
<Form>
<Form.Group className="mb-3" controlId="name">
<Form.Label>Name</Form.Label>
<Form.Control type="name" placeholder="Enter your name" />
</Form.Group>
<Form.Group className="mb-3" controlId="password">
<Form.Label>Date of birth</Form.Label>
<Form.Control type="date" />
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
);
}
export default SimpleDatePicker;
這裡我們有:
使用Form.Control和 type date 建立日期選擇器控制元件。
使用Form 元件建立一個基本的表單元件。
使用Form.Group對錶單控制元件和標籤進行分組。
接下來,開啟App 元件 (src/App.js),匯入 bootstrap css 並呈現日期選擇器,如下所示:
import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimpleDatePicker from './Components/SimpleDatePicker'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleDatePicker />
</div>
</div>
</div>
);
}
export default App;
這裡:
使用import語句匯入 Bootstrap 類
渲染我們新的SimpleDatePicker元件。
包含 App.css 樣式
最後,在瀏覽器中開啟應用程式並檢查最終結果。日期選擇器將按如下所示呈現:
總結
React Bootstrap 日期選擇器元件提供了建立日期選擇器表單控制元件的必要選項。
ReactJS - Helmet
Web 文件的元資訊對於 SEO 非常重要。文件的元資訊通常在 head 部分使用meta標籤指定。title 標籤也對提供有關文件的元資訊起著重要作用。head 部分也將包含 script 和 style 標籤。Helmet 元件透過提供所有有效的 head 標籤,提供了一種管理文件 head 部分的簡單方法。Helmet 將收集其中指定的所有資訊並更新文件的 head 部分。
讓我們在本節中學習如何使用 Helmet 元件。
安裝 Helmet
在學習 Helmet 的概念和用法之前,讓我們學習如何使用 npm 命令安裝 Helmet 元件。
npm install --save react-helmet
上面的命令將安裝 Helmet 元件,並準備好在我們應用程式中使用。
Helmet 的概念和用法
Helmet 接受所有有效的 head 標籤。它接受純 HTML 標籤,並在文件的 head 部分輸出這些標籤,如下所示:
import React from "react";
import {Helmet} from "react-helmet";
class Application extends React.Component {
render () {
return (
<div className="application">
<Helmet>
<title>Helmet sample application</title>
<meta charSet="utf-8" />
<meta name="description" content="Helmet sample program to understand the working of the helmet component" />
<meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" />
<meta name="author" content="Peter" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Helmet>
// ...
</div>
);
}
};
這裡:
title 用於指定文件的標題
description 元標籤用於指定文件的詳細資訊
keywords 用於指定文件的主要關鍵詞。搜尋引擎將使用它。
author 用於指定文件的作者
viewport 用於指定文件的預設視口
charSet 用於指定文件中使用的字元集。
head 部分的輸出如下所示:
<head> <title>Helmet sample application</title> <meta charSet="utf-8" /> <meta name="description" content="Helmet sample program to understand the working of the helmet component" /> <meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" /> <meta name="author" content="Peter" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> </head>
Helmet 元件可以用於任何其他 React 元件中以更改 header 部分。它也可以巢狀,以便在渲染子元件時更改 header 部分。
<Parent>
<Helmet>
<title>Helmet sample application</title>
<meta charSet="utf-8" />
<meta name="description" content="Helmet sample program to understand the working of the helmet component" />
<meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" />
<meta name="author" content="Peter" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Helmet>
<Child>
<Helmet>
<title>Helmet sample application :: rendered by child component</title>
<meta name="description" content="Helmet sample program to explain nested feature of the helmet component" />
</Helmet>
</Child>
</Parent>
這裡:
子元件中的 Helmet 將更新 head 部分,如下所示:
<head> <title>Helmet sample application :: rendered by child component</title> <meta charSet="utf-8" /> <meta name="description" content="Helmet sample program to explain nested feature of the helmet component" /> <meta name="keywords" content="React, Helmet, HTML, CSS, Javascript"> <meta name="author" content="Peter"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head>
應用 Helmet
讓我們建立一個新的 React 應用程式,在本節中學習如何在其中應用 Helmet 元件。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,建立一個簡單的元件,SimpleHelmet (src/Components/SimpleHelmet.js) 並渲染一個:
import React from "react";
import {Helmet} from "react-helmet";
class SimpleHelmet extends React.Component {
render() {
return (
<div>
<Helmet>
<title>Helmet sample application</title>
<meta charSet="utf-8" />
<meta name="description" content="Helmet sample program to understand the working of the helmet component" />
<meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" />
<meta name="author" content="Peter" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Helmet>
<p>A sample application to demonstrate helmet component.</p>
</div>
)
}
}
export default SimpleHelmet;
這裡我們有:
從 react-helmet 包中匯入 Helmet
在元件中使用 Helmet 來更新 head 部分。
接下來,開啟 App 元件 (src/App.js),並使用 SimpleHelmet 元件,如下所示:
import './App.css'
import React from 'react';
import SimpleHelmet from './Components/SimpleHelmet'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleHelmet />
</div>
</div>
</div>
);
}
export default App;
這裡我們有:
從 React 包中匯入 SimpleHelmet 元件
使用 SimpleHelmet 元件
接下來,開啟 index.html (public/index.html) 檔案並刪除元標籤,如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div style="padding: 10px;">
<div id="root"></div>
</div>
</body>
</html>
這裡:
title 標籤已刪除
描述、主題顏色和視口的meta標籤已刪除
最後,在瀏覽器中開啟應用程式並檢查最終結果。
在開發者工具中開啟原始碼,您將看到如下所示的 HTML 資訊:
<title>Helmet sample application</title> <meta charset="utf-8" data-react-helmet="true"> <meta name="description" content="Helmet sample program to understand the working of the helmet component" data-react-helmet="true"> <meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" data-react-helmet="true"> <meta name="author" content="Peter" data-react-helmet="true"> <meta name="viewport" content="width=device-width, initial-scale=1.0" data-react-helmet="true">
總結
Helmet 元件是一個易於使用的元件,用於管理文件的 head 內容,支援伺服器端和客戶端渲染。
ReactJS - 內聯樣式
React 提供了一種獨特的方式,可以直接在 React 元件中編寫 CSS 並將其用於 JSX 中。這個概念稱為 JS 中的 CSS,與傳統的樣式用法相比,它具有許多優勢。
讓我們學習什麼是內聯樣式以及如何在 React 元件中使用它。
內聯樣式的概念
CSS 使開發人員能夠設計 Web 應用程式的 UI。React 為 CSS 提供了第一類支援,並允許將 CSS 直接匯入到 React 應用程式中。將 CSS 直接匯入 React 元件就像匯入包一樣簡單。
import './App.css'
但是,將 CSS 直接匯入 Web 元件有一個主要缺點,即 全域性名稱空間。如果類名衝突,全域性樣式可能會影響單個元件的樣式。開發人員需要注意為其分配一些字首,以確保不會發生衝突。
另一種方法是允許 JavaScript 管理 CSS,這稱為 JS 中的 CSS。React 允許透過特殊的 CSS 語法在 JSX 中使用 CSS。React 為每個元件提供了一個 style 屬性,可用於指定內聯樣式。內聯樣式應在 JavaScript 物件中提供。該物件應遵循以下規則:
物件的鍵應該是普通 CSS 屬性的駝峰式版本。例如,background-color 應指定為 backgroundColor。
{
backgroundColor: "red"
}
物件的值應該是 CSS 中相應物件鍵的允許值之一,並且應為字串格式。例如,font-size CSS 屬性及其值 (12px) 應按如下方式指定:
{
fontSize: "12px"
}
React 將處理衝突並正確渲染應用程式。
應用內聯樣式
讓我們在本節中學習如何在 React 應用程式中應用內聯樣式。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,開啟 App.css (src/App.css) 並刪除所有 CSS 類。
// remove the css
接下來,建立一個簡單的元件,SimpleStyle (src/Components/SimpleIcon.js),如下所示:
import React from "react";
class SimpleStyle extends React.Component {
displayStyle = {
fontFamily: 'Times New Roman',
fontSize: "24px",
color: "red"
}
render() {
return (
<div>
<div style={this.displayStyle}>
Sample text to understand inline style (object as variable) in React component
</div>
<hr />
<div style={{ fontFamily: 'Arial', fontSize: "24px", color: "grey"}}>
Sample text to understand inline style (object as expression) in React component
</div>
</div>
)
}
}
export default SimpleStyle
這裡我們有:
使用變數 (displayStyle) 樣式化第一個 div。
使用表示式樣式化第二個 div。
接下來,開啟 App 元件 (src/App.js) 並使用 SimpleStyle 元件更新內容,如下所示:
import './App.css'
import React from 'react';
import SimpleStyle from './Components/SimpleStyle'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleStyle />
</div>
</div>
</div>
);
}
export default App;
這裡我們有:
匯入了 SimpleStyle 元件。
使用 SimpleStyle 元件渲染日曆圖示。
最後,在瀏覽器中開啟應用程式。內容將按如下所示渲染:
總結
內聯樣式幫助開發人員快速包含 CSS 樣式,而無需擔心 CSS 樣式的衝突。此外,語法與 CSS 非常相似,這使得開發人員無需太多學習曲線即可輕鬆使用此功能。
ReactJS - PropTypes
JavaScript 是一種動態型別語言。這意味著 JavaScript 不需要宣告或指定變數的型別。在程式執行(執行時)期間,JavaScript 檢查分配給變數的值,然後推斷變數的型別。例如,如果將變數 num 分配給 John,則它推斷 num 的型別為字串。如果將相同的變數 num 分配給 10,則它推斷 num 的型別為數字。
var num = 'John' // The type of `num` is string var num = 10 // The type of `num` is number
動態型別對於指令碼語言來說很好,因為它可以加快開發速度。動態型別的缺點是 JavaScript 引擎在開始執行程式之前不知道變數的型別。這阻止 JavaScript 引擎查詢或識別某些錯誤。例如,下面提到的程式碼不會執行並停止程式。
var num = 10 var name = 'John' var result = num + name // throws error during runtime
React 和型別
在 React 中,每個元件都將具有多個 props,並且每個 props 都應分配給具有正確型別的 value。型別錯誤的元件 props 將導致意外行為。為了更好地理解這個問題,讓我們建立一個新應用程式並建立一個元件,該元件對其屬性求和並顯示結果。
要建立一個新的 React 應用程式,請執行以下命令:
create-react-app myapp
建立應用程式後,在 components 資料夾下建立一個名為 Sum 的新元件 (src/components/PropTypes/Sum.js)
import React from 'react'
class Sum extends React.Component {
render() {
return <p>The sum of {this.props.num1} and {this.props.num2}
is {parseInt(this.props.num1) + parseInt(this.props.num2)}</p>
}
}
export default Sum
在這裡,Sum 元件接受兩個數字 num1 和 num2,並列印這兩個數字的總和。如果為 props 提供數字值,則該元件將工作。但是,如果 sum 元件提供字串,則它將顯示 NaN 作為這兩個屬性的總和。
無法找到為 num1 和 num2 props 提供的值是否格式不正確。讓我們在根元件 (src/App.js) 中使用 Sum 元件,並檢視它的渲染方式。
import Sum from "./components/PropTypes/Sum";
function App() {
var num1 = 10
var num2 = 200
var name1 = "John"
var name2 = "Peter"
return (
<div>
<Sum num1={num1} num2={num2} />
<Sum num1={name1} num2={name2} />
</div>
);
}
export default App;
PropTypes
React 社群提供了一個特殊的包 prop-types 來解決屬性型別不匹配問題。prop-types 允許透過元件內的自定義設定 (propTypes) 指定元件屬性的型別。例如,可以使用 PropTypes.number 選項指定數字型別的屬性,如下所示。
Sum.propTypes = {
num1: PropTypes.number,
num2: PropTypes.number
}
一旦指定了屬性的型別,React 就會在應用程式的開發階段發出警告。讓我們在示例應用程式中包含 propTypes,並檢視它如何幫助捕獲屬性型別不匹配問題。
使用節點包管理器 (npm) 安裝 prop-types 包,如下所示:
npm i prop-types --save
現在,指定Sum元件屬性的型別,如下所示:
import React from 'react'
import PropTypes from 'prop-types'
class Sum extends React.Component {
render() {
return <p>The sum of {this.props.num1} and {this.props.num2}
is {parseInt(this.props.num1) + parseInt(this.props.num2)}</p>
}
}
Sum.propTypes = {
num1: PropTypes.number,
num2: PropTypes.number
}
export default Sum
最後,使用以下命令執行應用程式:
npm start
在您喜歡的瀏覽器中開啟應用程式,並透過開發者工具開啟 JavaScript 控制檯。JavaScript 會發出警告,指出提供了意外型別,如下所示:
propTypes 僅在開發階段有效,以消除由於額外檢查 props 型別而導致的應用程式效能降低。這不會影響生產/上線環境中的應用程式效能。
可用驗證器
prop-types 提供了大量現成的驗證器。它們如下:
PropTypes.array
PropTypes.bigint
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.symbol
PropTypes.node - 可渲染的任何內容
PropTypes.element - React 元件
PropTypes.elementType - React 元件的型別
PropTypes.instanceOf() - 指定類的例項
propTypes.oneOf(['Value1', 'valueN']) - Value 和 ValueN 之一
PropTypes.oneOfType([]) - 例如,PropTypes.oneOfType([PropTypes.number, PropTypes.bigint])
PropTypes.arrayOf() - 例如,PropTypes.arrayOf(PropTypes.number)
PropTypes.objectOf() - 例如,PropTypes.objectOf(PropTypes.number)
PropTypes.func.isRequired
propTypes.element.isRequired
PropTypes.any.isRequired
還可以建立自定義驗證器並用於驗證屬性的值。讓我們假設元件具有一個 email 屬性,其值應為有效的電子郵件地址。然後,可以編寫一個驗證函式並將其附加到 email 屬性,如下所示:
Sum.propTypes = {
num1: PropTypes.number,
num2: PropTypes.number,
email: function(myProps, myPropName, myComponentName) {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(myProps[myPropName])) {
return new Error(
'Invalid prop value `' + myProps[myPropName] + '` supplied to' +
' `' + myComponentName + '/' + myPropName + '`. Validation failed.'
);
}
}
}
這裡:
/^[^\s@]+@[^\s@]+\.[^\s@]+$/ 是一個簡單的正則表示式電子郵件模式。
myProps 代表所有屬性。
myPropName 代表當前正在驗證的屬性。
myComponentName 代表正在驗證的元件的名稱。
類似地,可以使用以下函式簽名建立和使用自定義驗證器來處理陣列和物件屬性
PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) { ... })
ReactJS - BrowserRouter
路由是前端應用程式中的一個重要概念。React 社群提供了一個優秀的路由庫,稱為 React Router。讓我們在本節中學習 React 路由的概念以及如何在 React 應用程式中使用它。
路由概念
路由的主要目的是將給定的 url 匹配到 React 元件並渲染匹配的元件。除了匹配和渲染之外,路由還應為瀏覽器管理歷史記錄,以便在瀏覽器中實現高效的前進和後退導航。
在瞭解路由的工作原理之前,讓我們瞭解一些 React 路由庫的有用元件。
BrowserRouter − BrowserRouter 是頂級元件。它建立一個歷史記錄(導航歷史記錄),將初始位置(表示“使用者所在位置”的路由物件)放入 React 狀態,最後訂閱位置 URL。
<BrowserRouter> <!-- children --> </BrowserRouter>
Routes − Routes 將遞迴其子節點並構建路由配置。它將配置的路由與位置匹配,並渲染第一個匹配的路由元素。
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<Home />} />
<Route path="admin" element={<Admin />}>
<Route path="product" element={<ProductListing />} />
<Route path="category" element={<CategoryListing />} />
<Route index element={<Dashboard />} />
</Route>
</Route>
<Route path="/login" element={<Login />}>
<!-- more nested routes -->
</Route>
<!-- more routes -->
</Routes>
</BrowserRouter>
這裡:
/ 路徑對映到 App 元件。
/ 路徑的索引元件對映到 Home 元件。
/admin 路徑對映到 Admin 元件。
/admin 路徑的索引元件對映到 Dashboard 元件。
/admin/product 路徑與 ProductListing 元件匹配。
/admin/category 路徑與 CategoryListing 元件匹配。
/admin/dashboard 路徑與 Dashboard 元件匹配。
Route − Route 是實際的路由配置。它可以像資料夾一樣巢狀到任何級別。
Outlet − Outlet 元件渲染一組匹配中的下一個匹配項。
function Hello() {
return (
<div>Hello</div>
<Outlet />
)
}
這裡:
將 Outlet 元件放在 hello 元件的底部。
路由器將在 Outlet 元件內渲染下一個匹配項。
Link − Link 元件類似於錨標籤,用於導航目的。使用者單擊它後,它會根據其 to 屬性更改位置
<Link to="/admin" />
navigate() − navigate() 是用於導航目的的 API,類似於 Link 元件。
navigate("/admin")
路由工作流程
讓我們考慮一個 React 應用程式具有五個頁面/元件,如下所示:
首頁 (/)
聯絡 (/contact)
註冊 (/register)
管理員 (/admin)
管理員控制面板 (/admin/dashboard)
一個示例路由配置如下所示:
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<Home />} />
<Route path="contact" element={<Contact />} />
<Route path="register" element={<Register />} />
<Route path="admin" element={<Admin />}>
<Route path="dashboard" element={<AdminDashboard />} />
<Route path="category" element={<CategoryListing />} />
<Route index element={<AdminDashboard />} />
</Route>
</Routes>
</BrowserRouter>
讓我們看看管理員控制面板 url (/admin/dashboard) 如何被 React 路由匹配和渲染。
首先,React 庫將渲染我們的應用程式。由於我們的應用程式將在渲染樹的頂部具有 BrowserRouter,因此它會被呼叫和渲染
BrowserRouter 元件建立歷史記錄,將初始位置放入狀態並訂閱 url
Routes 元件將檢查其所有子元件,構建路由配置,最後渲染第一個匹配項 (/admin =>)
Admin 元件將被渲染。它將具有一個 Outlet 元件,如下所示:
function Admin() {
return (
<div>
<!-- Admin content -->
</div>
<Outlet />
)
}
Outlet 元件在其自身內渲染下一個匹配項 (/admin/dashboard =>)
使用者可能會單擊儀表盤中的連結(Link 元件),例如“/admin/category”
Link 元件呼叫 navigate("/admin/category") 方法
歷史記錄(物件)更改 url 並通知 BrowserRouter
由於 BrowserRouter 元件已訂閱 url,因此 BrowserRouter 元件將被重新渲染,並且整個過程將重複(從 2 開始)
如何應用路由
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 React 路由庫:
npm install --save react-router-dom
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,在 src 下建立一個名為 Pages 的資料夾,並建立一個新的主頁元件 Home (src/Pages/Home.js),並渲染簡單的主頁內容,如下所示:
function Home() {
return <h3>Home</h3>
}
export default Home
接下來,建立一個新的問候頁面元件 Greeting (src/Pages/Greeting.js),並渲染簡單的問候訊息,如下所示:
function Greeting() {
return <h3>Hello World!</h3>
}
export default Greeting;
接下來,開啟 App.js 檔案並渲染 BrowserRoutes 元件,如下所示:
import './App.css'
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Layout from './Pages/Layout';
import Home from './Pages/Home';
import Greeting from './Pages/Greeting';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="greet" element={<Greeting />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
這裡:
BrowserRouter 是主元件。它將具有路由設定作為其子元件,並根據路由設定渲染整個應用程式。
Routes 是主要的路由元件。它儲存單個路由設定的列表。
Route 是實際的路由元件,它具有 Web 路徑 (/home) 和元件 (Home) 之間的對映。
Route 可以巢狀以支援巢狀路徑。
在路由中定義的對映如下:
/ 對映到 Layout 元件。Layout 元件將在下一步建立。
/home 對映到 Home 元件,並且巢狀在 / 路徑下。
/greet 對映到 Greet 元件,並且巢狀在 / 路徑下。
接下來,建立一個 Layout 元件,Layout (src/Pages/Layout.js)。Layout 元件的目的是顯示整個應用程式以及導航連結。它是應用程式的主元件,指向 / 路由。Layout 元件的原始碼如下:
import { Outlet, Link } from "react-router-dom";
function Layout() {
return (
<>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/greet">Greeting</Link>
</li>
</ul>
</nav>
<Outlet />
</>
)
}
export default Layout;
這裡:
匯入了 Link 和 Outlet 元件。
Link 元件用於建立網頁導航連結。
Link 元件的 to 屬性設定為在父 BrowserRouter 元件中定義的路由之一。
使用了路由設定中可用的 / 和 /greet 路由。
Outlet 元件用於底部載入選定的元件。在初始渲染期間,它將載入預設元件 (home)。
使用者點選網頁連結後,它將從 to 屬性獲取路由路徑,並透過路由設定獲取對映的元件。最後,它將在 Outlet 元件內渲染該元件。
接下來,建立一個新元件,PageNotAvailable (src/Pages/PageNotAvailable.js),用於顯示連結與任何路由設定都不匹配時。
import { Link } from "react-router-dom"
function PageNotAvailable() {
return (
<p>The page is not available. Please <Link to=
"/">click here</Link> to go to home page.</p>
)
}
export default PageNotAvailable
這裡,Link 元件用於導航返回主頁。
接下來,更新 App 元件,並在路由設定中包含 PageNotAvailable 元件。
import './App.css'
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Layout from './Pages/Layout';
import Home from './Pages/Home';
import Greeting from './Pages/Greeting';
import PageNotAvailable from './Pages/PageNotAvailable'
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="greet" element={<Greeting />} />
<Route path="*" element={<PageNotAvailable />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
這裡:
當用戶點選連結時,React Router 將按照給定的順序逐一嘗試匹配點選的連結和路由設定。如果匹配到連結,則 React Router 將停止並渲染匹配的元件。
* 模式將匹配所有連結。由於它作為最後一個條目放置,它將匹配所有未定義/未知的連結。
接下來,更新 Layout 元件並新增一個不可用的連結來檢查 PageNotAvailable 元件是否已正確配置。
import { Outlet, Link } from "react-router-dom";
function Layout() {
return (
<>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/greet">Greeting</Link>
</li>
<li>
<Link to="/unknown">Unavailable page</Link>
</li>
</ul>
</nav>
<Outlet />
</>
)
}
export default Layout;
最後,在瀏覽器中開啟應用程式並檢查最終結果。應用程式將按如下所示進行渲染:
使用者可以使用如上輸出所示的導航連結導航到任何頁面。
總結
React Router 易於配置和使用。它沒有那麼多花哨的功能,但具有必要的特性,如連結、出口、路由和路由,可以建立一個具有導航連結的完整 Web 應用程式。
ReactJS - DOM
要執行 React 應用程式,需要將其自身附加到 Web 應用程式的主文件。React 提供了一個模組來訪問並將應用程式附加到文件的 DOM,該模組是 ReactDOM (react-dom)。
本章讓我們學習如何建立一個簡單的 React 元件,並使用 ReactDOM 模組將該元件附加到文件中。
ReactDOM 用法
react-dom 是用於操作文件 DOM 的核心包。react-dom 允許將一個或多個 React 應用程式附加到文件。react-dom 應按如下所示匯入到應用程式中:
import * as ReactDOM from 'react-dom';
react-dom 提供了兩種操作 DOM 的方法,如下所示:
createPortal() - 在 React 應用程式中建立一個門戶。門戶是一個特殊的 React 節點,它使主 React 應用程式能夠將其子節點渲染到其自身 DOM 層次結構之外的 DOM 中。
return ReactDOM.createPortal( this.props.children, // child node domNode // DOM node outside the root element );
讓我們在接下來的章節中更詳細地學習門戶。
flushSync() - 立即重新整理狀態更改並更新 DOM。通常,React 建立一個虛擬 DOM,然後透過分析虛擬 DOM 和真實 DOM 之間的差異來更新真實 DOM。更新頻率由 React 內部確定。flushSync() 會中斷並立即更新更改。
react-dom 提供了兩個子模組,一個用於伺服器端應用程式,另一個用於客戶端應用程式。模組如下:
react-dom/server
react-dom/client
ReactDOMServer
伺服器模組將用於在伺服器上渲染 React 元件,並且可以按如下所示匯入該模組:
import * as ReactDOMServer from 'react-dom/server';
ReactDOMServer 提供的一些方法如下:
renderToPipeableStream() - 將 React 元件渲染到其初始 HTML 並返回管道流。
renderToReadableStream() - 將 React 元件渲染到其初始 HTML 並透過 Promise 返回可讀的 Web 流。
renderToStaticNodeStream() - 將 React 元件渲染到其初始 HTML 並返回一個可讀的 Node.js 流,該流輸出 HTML 字串。它跳過額外的標記,例如 data-reactroot,並且輸出將與 renderToStaticMarkup() 相同。
renderToString() - 將 React 元件渲染到其初始 HTML 並返回 HTML 字串。
renderToStaticMarkup() - 與 renderToString() 相同,只是它跳過額外的標記,例如 data-reactroot。
ReactDOMClient
客戶端模組將在前端開發中廣泛使用,並且可以按如下所示匯入到應用程式中:
import * as ReactDOM from 'react-dom/client';
ReactDOMClient 提供的一些方法如下:
createRoot() - 建立一個根元素以稍後附加和渲染 React 元件。它接受一個 html 元素並返回一個 React 節點。該 React 節點稱為應用程式的根。返回的 React 節點將具有兩種方法,render 用於渲染 React 元件,unmount 用於解除安裝 React 元件。
const root = createRoot(container);
root.render(element); // where element = document.getElementById('root-id')
root.umount();
hydrateRoot() - 與 createRoot() 相同,但它與 react-dom/server 模組結合使用以潤滑在伺服器上渲染的 React 元件。
應用 ReactDOMClient
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,在元件資料夾下建立一個 React 元件 Hello (src/components/Hello.js)。
import React from "react";
class Hello extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>Hello, {this.props.name}</div>
);
}
}
export default Hello;
接下來,開啟 index.html (public/index.html) 並新增一個新的容器 (root2),如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div style="padding: 10px;">
<div id="root"></div>
<div id="root2"></div>
</div>
</body>
</html>
接下來,開啟 index.js (src/index.js) 並將我們的 Hello 元件附加到 root 和 root2 容器中,如下所示:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Hello from './Components/Hello';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Hello name="Main root container" />
</React.StrictMode>
);
const root2 = ReactDOM.createRoot(document.getElementById('root2'));
root2.render(
<React.StrictMode>
<Hello name="Another root container" />
</React.StrictMode>
);
reportWebVitals();`
最後,在瀏覽器中開啟應用程式並檢查結果。React 元件將附加到兩個根元素,如下所示:
總結
ReactDOM 提供了在客戶端和伺服器環境中透過將 React 元件附加到 HTML 文件來建立 React 應用程式入口點的方法。
ReactJS - 走馬燈
React 透過第三方 UI 元件庫提供輪播元件。React 社群提供了大量的 UI/UX 元件,選擇滿足我們需求的正確庫很困難。Bootstrap UI 庫是開發人員的流行選擇之一,並且被廣泛使用。React Bootstrap (https://react-bootstrap.github.io/) 已將幾乎所有 Bootstrap UI 元件移植到 React 庫,並且對 Carousel 元件也有最佳支援。
本章讓我們學習如何使用來自 react-bootstrap 庫的 Carousel 元件。
什麼是輪播?
Carousel 本質上是一個幻燈片,它透過一系列內容迴圈播放,並具有豐富的動畫支援。它接受一系列影像作為其主要內容。它還接受每個幻燈片的標題內容。它具有按鈕/指示器,可以從當前內容導航到下一個/上一個內容。暫停和顯示內容的時間持續時間可以根據需要進行配置。
Carousel 元件
Carousel 元件允許開發人員在 Web 應用程式中使用 Bootstrap 設計建立簡單的輪播。Carousel 元件接受兩個元件:
Carousel.Item
Carousel 元件接受多個 Carousel.Item 專案。每個 Carousel.Items 都是一個幻燈片,可以接受影像。示例程式碼如下:
<Carousel>
<Carousel.Item>
<img
className="d-block w-100"
src="react_bootstrap_carousel_slide1.png"
alt="First slide"
/>
</Carousel.Item>
<Carousel.Item>
<img
className="d-block w-100"
src="react_bootstrap_carousel_slide2.png"
alt="Second slide"
/>
</Carousel.Item>
</Carousel>
Carousel.Caption
Carousel.Caption 是一個特殊的元件,用於顯示關於幻燈片的簡短描述,它應該包含在 Carousel.Item 元件內。示例程式碼如下:
<Carousel.Item>
<img
className="d-block w-100"
src="react_bootstrap_carousel_slide1.png"
alt="First slide"
/>
<Carousel.Caption>
<h3>React Bootstrap</h3>
<p>React component library providing bootstrap components</p>
</Carousel.Caption>
</Carousel.Item>
Carousel 元件接受少量道具來配置功能,它們如下:
controls (布林值)
啟用/停用控制元件,例如上一個/下一個按鈕
keyboard (布林值)
啟用鍵盤控制
touch (布林值)
啟用/停用觸控控制
indicators (布林值)
啟用/停用輪播底部的指示器
nextIcon (React 節點)
選項以自定義自定義下一個圖示
nextLabel (字串)
選項以自定義下一個標籤
prevIcon (React 節點)
選項以自定義自定義上一個圖示
prevLabel (字串)
選項以自定義上一個標籤
interval (數字)
兩個幻燈片之間暫停和播放的時間持續時間
activeIndex (數字)
表示要顯示的幻燈片的索引號
slide (布林值)
啟用/停用自動幻燈片功能
variant (dark)
啟用輪播設計的不同變體。dark 選項將輪播主題從淺色更改為深色
bsPrefix (string)
用於自定義基礎 CSS 類的字首
onSelect (函式)
啟用附加函式以處理 onSelect 事件
onSlide (函式)
啟用附加函式以處理 onSlide 事件
Carousel.Item 元件接受一些道具來配置功能,它們如下:
interval (數字)
單個幻燈片暫停的時間持續時間
bsPrefix (string)
用於自定義基礎 CSS 類的字首
應用 Carousel 元件
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 Bootstrap 和 react-bootstrap 庫:
npm install --save bootstrap react-bootstrap
接下來,建立一個簡單的輪播元件,SimpleCarousel (src/Components/SimpleCarousel.js),並渲染一個輪播,如下所示:
import { Carousel } from 'react-bootstrap';
function SimpleCarousel() {
return (
<Carousel fade indicators={false} controls={false}>
<Carousel.Item>
<img
className="d-block w-100"
src="react_bootstrap_carousel_slide1.png"
alt="First slide"
/>
<Carousel.Caption>
<h3>React Bootstrap</h3>
<p>React component library providing bootstrap components</p>
</Carousel.Caption>
</Carousel.Item>
<Carousel.Item>
<img
className="d-block w-100"
src="react_bootstrap_carousel_slide2.png"
alt="Second slide"
/>
<Carousel.Caption>
<h3>Button</h3>
<p>React Bootstrap button component</p>
</Carousel.Caption>
</Carousel.Item>
<Carousel.Item>
<img
className="d-block w-100"
src="react_bootstrap_carousel_slide3.png"
alt="Third slide"
/>
<Carousel.Caption>
<h3>Carousel</h3>
<p>React bootstrap Carousel component</p>
</Carousel.Caption>
</Carousel.Item>
</Carousel>
);
}
export default SimpleCarousel;
這裡:
匯入了 Carousel 元件並添加了一個 Carousel 元件。
在 Carousel 元件中使用 fade 道具來更改動畫型別
在 Carousel 元件中使用 indicators 道具來刪除指示器
在 Carousel 元件中使用 controls 道具來刪除控制元件
添加了三個 Carousel.Item 專案並使用了三張影像。
在每個 Carousel.Item 元件中添加了 Carousel.Caption 併為每個幻燈片設定了標題。
接下來,開啟 App.css (src/App.css) 並刪除所有樣式。
// remove all default styles
接下來,開啟 App 元件 (src/App.js),匯入 bootstrap css 並使用我們的新輪播元件更新內容,如下所示:
import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimpleCarousel from './Components/SimpleCarousel'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div style={{ width: "400px", height: "400px", backgroundColor: "skyblue" }}>
<SimpleCarousel />
</div>
</div>
</div>
);
}
export default App;
這裡:
使用import語句匯入 Bootstrap 類
渲染了我們的新 SimpleCarousel 元件。
包含 App.css 樣式
最後,在瀏覽器中開啟應用程式並檢查最終結果。輪播元件將按如下所示呈現:
新增控制元件和指示器
讓我們更新我們的元件以包括控制元件以導航到下一個和上一個幻燈片,以及指示器以識別當前幻燈片位置。
首先,開啟我們的輪播應用程式並更新 SimpleCarousel 元件,如下所示:
import { Carousel } from 'react-bootstrap';
function SimpleCarousel() {
return (
<Carousel fade indicators={true} controls={true}>
<Carousel.Item>
// ...
這裡:
使用 indicators 道具啟用指示器
使用 controls 道具啟用控制元件
接下來,在瀏覽器中開啟應用程式並檢查最終結果。輪播元件將使用控制元件和指示器呈現,如下所示:
總結
React-bootstrap Carousel 元件提供了建立乾淨簡單的輪播元件所需的所有選項。
ReactJS - 圖示
Web 圖示是 Web 應用程式中重要的資產。開發人員在多個地方廣泛使用它來更好地視覺化上下文。例如,可以使用選單圖示輕鬆識別選單。Web 圖示有著悠久的歷史,在其漫長的歷史中有多種實現。
最初,圖示是標準大小的簡單影像,例如 24x24、32x32、48x48 等。後來,多個圖示被設計為單個影像,稱為圖示精靈,透過 CSS 定位屬性在網站中使用。然後,字型用於儲存多個圖示並透過 CSS font-family 屬性使用。列表中的最新的是 SVG 圖示。SVG 圖示以 SVG 格式設計和儲存,並在網站中透過 img 標籤或內聯 SVG 選項使用。
React 提供了一個基於社群的圖示庫,稱為 React icons,它提供了來自不同圖示庫的大量圖示集。本章讓我們學習如何使用 React 圖示庫。
React 圖示 (React-icon) 庫
React 圖示庫從不同的供應商收集數千個圖示,並將它們包裝為 React 元件。開發人員可以使用它,就像包含一個 React 元件一樣簡單,以在其專案中使用特定的圖示。React 圖示提供的一些圖示集列表如下:
Bootstrap 圖示
Material Design 圖示
Font Awesome
Devicons
Boxicons
Ant Design 圖示
Github Octicons 圖示
VS Code 圖示
React icons 提供了更多圖示集,您可以在其網站上檢視所有圖示 (https://react-icons.github.io/react-icons/)
安裝 react icons 庫
在 Web 應用中安裝 React icons 庫就像使用 npm 安裝包一樣簡單,如下所示:
npm install react-icons --save
使用 react icon 元件
庫中的每個圖示都有一個相關的 React 元件。開發者可以從 React icon 庫網站找到所需的圖示元件,並在其 Web 應用中使用它。讓我們看看如何使用來自 React icon 庫 Material Design 集的日曆圖示。Material Design 集中日曆圖示元件的名稱是 MdCalendarToday。Material Design 圖示集的包是 react-icons/md。開發者需要匯入該包,並在相關位置使用該元件,如下所示:
import { MdCalendarToday } from "react-icons/md";
// ...
// ...
class SimpleIcon extends React.Component {
render() {
return <div>This icons <MdCalendarToday /> is calendar icon imported from react icon library</div>
}
}
開發者可以透過 CSS 更改圖示的顏色和大小。
class SimpleIcon extends React.Component {
render() {
return <div>This icons <MdCalendarToday style={{backgroundColor: "red", size: "24px"}}/>
is calendar icon imported from react icon library</div>
}
}
應用 react icon 庫
讓我們透過開發一個應用程式來學習 forwardRef 的概念。
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,安裝 react icon 庫,如下所示:
npm install react-icons --save
接下來,開啟 App.css (src/App.css) 並刪除所有 CSS 類。
// remove the css
接下來,建立一個簡單的元件,SimpleIcon (src/Components/SimpleIcon.js),如下所示:
import React from "react";
import { MdCalendarToday } from "react-icons/md";
class SimpleIcon extends React.Component {
render() {
return <div>This icons <MdCalendarToday style={{ color: "red", size: "24px" }} />
is calendar icon imported from react icon library</div>
}
}
export default SimpleIcon
這裡:
匯入了 react-icons/md 庫。
使用了 MdCalendarToday 元件來渲染日曆圖示。
使用了內聯樣式來更改圖示的顏色和大小。
接下來,開啟 App 元件 (src/App.js) 並使用 SimpleIcon 元件更新內容,如下所示:
import './App.css'
import React from 'react';
import SimpleIcon from './Components/SimpleIcon'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleIcon />
</div>
</div>
</div>
);
}
export default App;
這裡:
匯入了 SimpleIcon 元件。
使用了 SimpleIcon 元件來渲染日曆圖示。
最後,在瀏覽器中開啟應用程式。日曆圖示將按如下所示渲染:
總結
React icon 庫幫助開發者從不同的來源收集各種圖示,並將它們放在一個地方,並以簡單易用的方式提供。
ReactJS - 表單元件
React 透過第三方 UI 元件庫提供表單元件。React 社群提供了大量的 UI/UX 元件,選擇滿足我們需求的正確庫是一件很困難的事情。Bootstrap UI 庫是開發者的一種流行選擇,並且被廣泛使用。React Bootstrap (https://react-bootstrap.github.io/) 已將幾乎所有 Bootstrap UI 元件移植到 React 庫中,並且對 form 元件也有最好的支援。
本章讓我們學習如何在 React Bootstrap 庫中使用表單元件。
什麼是表單元件?
表單程式設計是 Web 應用的一個突出特性。它用於從前端收集使用者資訊,然後將其傳遞到伺服器端進行進一步處理。收集到的資訊將在傳送到伺服器之前在前端進行驗證。HTML 具有不同的輸入標籤,如文字、複選框、單選按鈕等,用於從使用者那裡收集不同型別的資訊。
React Bootstrap 提供了幾乎所有基於 Bootstrap 的表單元件。它們如下所示:
Form
Form 元件用於渲染基本的 HTML 表單 (form)。它是最頂層的表單元件。Form 元件的一些有用屬性如下:
ref (ReactRef) - 用於訪問底層 DOM 節點的 ref 元素。
as (elementType) - 允許指定除 *<form>* 之外的其他元素。
validated (boolean) - 指定表單是否正在進行驗證。將值切換為 true 將顯示在表單中設定的驗證樣式。
一個簡單的表單元件可以使用如下所示:
<Form> <!-- Use form control--> </Form>
Form.Control
Form.Control 元件用於透過其 type 屬性渲染各種輸入元素。Form.Control 元件的一些有用屬性如下:
ref (ReactRef) - 用於訪問底層 DOM 節點的 ref 元素。
as (elementType) - 允許指定除 *<input>* 之外的其他元素。
disabled (boolean) - 啟用/停用控制元件元素。
htmlSize (number) - 底層控制元件元素的大小。
id (string) − 控制元件元素的 ID。如果此處未指定,則使用父 *Form.Group* 元件的 *controlId*。
isInValid (boolean) - 啟用/停用與無效資料關聯的樣式。
isValid (boolean) - 啟用/停用與有效資料關聯的樣式。
plaintext (boolean) - 啟用/停用輸入並將其呈現為純文字。需與 *readonly* 屬性一起使用。
readOnly (boolean) - 啟用/停用控制元件的只讀屬性。
size (sm | lg) - 輸入元素的大小。
type (string) - 要渲染的輸入元素的型別。
value (string | arrayOf | number) − 底層控制元件的值。由 *onChange* 事件操作,初始值預設為 *defaultValue* 屬性
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
onChange (boolean) - 當觸發 *onChange* 事件時要呼叫的回撥函式。
一個簡單的表單控制元件元件可以使用如下所示:
<> <Form.Control type="text" size="lg" placeholder="Large text" /> <br /> <Form.Control type="text" placeholder="Normal text" /> <br /> <Form.Control type="text" size="sm" placeholder="Small text" /> </>
Form.Label
Form.Label 元件用於渲染 HTML 標籤元件 (<label>)。Form.Label 元件的一些有用屬性如下:
ref (ReactRef) - 用於訪問底層 DOM 節點的 ref 元素。
as (elementType) - 允許指定除 *<label>* 之外的其他元素。
htmlFor (string) - 用於指定為其建立特定標籤的輸入元素。如果未指定 *htmlFor*,則它將使用頂級 *Form.Group* 元件的 *controlId*。
column (boolean | sm | lg) - 為佈局目的使用 *<Col>* 元件渲染標籤。
visuallyHidden (boolean) - 視覺上隱藏標籤,但允許輔助技術使用。
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
Form.Group
Form.Group 元件用於組合表單控制元件和標籤。它將用於根據其標籤來佈局控制元件。Form.Group 元件的一些有用屬性如下:
ref (ReactRef) - 用於訪問底層 DOM 節點的 ref 元素。
as (elementType) - 允許指定除 *<form>* 之外的其他元素。
controlId (string) - 用於引用控制元件和標籤組的 ID。如果控制元件沒有 *Id* 屬性,它將用作組內表單控制元件的 ID。
一個簡單的表單組以及表單標籤可以使用如下所示:
<Form.Group controlId="formFile" className="mb-3"> <Form.Label>Upload file</Form.Label> <Form.Control type="file" /> </Form.Group>
Form.Text
Form.Text 元件用於顯示錶單控制元件 (<small>) 的幫助訊息。Form.Text 元件的一些有用屬性如下:
ref (ReactRef) - 用於訪問底層 DOM 節點的 ref 元素。
as (elementType) - 允許指定除 *<form>* 之外的其他元素。
muted (boolean) - 應用 *text-muted* 類。
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
一個簡單的表單文字元件可以使用如下所示:
<Form.Label htmlFor="pwd">Password</Form.Label> <Form.Control type="password" id="pwd" aria-describedby="passwordHelpMessage" /> <Form.Text id="passwordHelpMessage" muted> Please set password within 8 - 12 characters long. Use minimum of 1 digit, 1 special character and 1 capital letter. Try to use strong password. </Form.Text>
Form.Select
Form.Select 元件用於渲染選擇元素 (<select>)。Form.Select 元件的一些有用屬性如下:
disabled (boolean) - 啟用/停用控制元件元素。
htmlSize (number) - 底層控制元件元素的大小。
isInValid (boolean) - 啟用/停用與無效資料關聯的樣式。
isValid (boolean) - 啟用/停用與有效資料關聯的樣式。
size (sm | lg) - 輸入元素的大小。
value (string | arrayOf | number) - 底層控制元件的值。由 *onChange* 事件操作,初始值將預設為 *defaultValue* 屬性。
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
onChange (boolean) - 當觸發 *onChange* 事件時要呼叫的回撥函式。
一個簡單的表單選擇元件可以使用如下所示:
<Form.Select aria-label="Select category"> <option value="sm">Small</option> <option value="lg">Large</option> <option value="xl">Extra large</option> </Form.Select>
Form.Check
Form.Check 元件用於在 HTML 表單中渲染複選框 (<input type="checkbox">) 和單選按鈕 (<input type="radio">)。Form.Check 元件的一些有用屬性如下:
ref (ReactRef) - 用於訪問底層 DOM 節點的 ref 元素。
as (elementType) − 啟用指定除 *<input>* 之外的元素
disabled (boolean) - 啟用/停用控制元件元素。
id (string) − 控制元件元素的 ID。如果此處未指定,則使用父 *Form.Group* 元件的 *controlId*。
children (node) - 自定義 *FormCheck* 內容的渲染。
title (string) - 底層 *FormCheckLabel* 的 title 屬性。
type (radio | checkbox | switch) - 要渲染的輸入元素的型別。
value (string | arrayOf | number) - 底層控制元件的值。由 *onChange* 事件操作,初始值將預設為 *defaultValue* 屬性。
label (node) - 控制元件的標籤。
feedback (node) - 在驗證過程中要渲染的反饋訊息。
feedbackTooltip (boolean) - 啟用/停用將反饋訊息顯示為工具提示。
isInValid (boolean) - 啟用/停用與無效資料關聯的樣式。
isValid (boolean) - 啟用/停用與有效資料關聯的樣式。
inline (boolean) - 啟用/停用以水平方式佈局控制元件。
reverse (boolean) - 啟用/停用子元素的反向佈局。
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
bsSwitchPrefix (string) - 用於自定義開關控制元件的底層 CSS 類的字首。
一個簡單的表單檢查元件可以使用如下所示:
<Form.Group controlId="gender" className="mb-3">
<Form.Label>Select your gender</Form.Label>
<div className="mb-3">
<Form.Check
type='radio'
id='Male'
label='Male'
name='gender'
/>
<Form.Check
type='radio'
id='Female'
label='Female'
name='gender'
/>
</div>
</Form.Group>
這裡,Form.Check 分組在 Form.Group 元件下。
Form.Check.Label
Form.Check.Label 元件用於為 Form.Check 元件的底層輸入渲染標籤。它將包含為 Form.Check 元件的子元素。Form.Check.Input 元件的一些有用屬性如下:
htmlFor (string) - 用於指定為其建立特定標籤的輸入元素。如果未指定 *htmlFor*,則它將使用頂級 *Form.Group* 元件的 *controlId*。
bsPrefix (string) − 用於自定義底層 CSS 類的字首
Form.Check.Input
Form.Check.Input 元件用於為 Form.Check 元件渲染底層輸入。它將包含為 Form.Check 元件的子元素。Form.Check.Input 元件的一些有用屬性如下:
as (elementType) - 允許指定除 *<input>* 之外的其他元素。
id (string) - 控制元件元素的 ID。如果此處未指定,則使用父 *Form.Group* 元件的 *controlId*。
type (radio | checkbox | switch) - 要渲染的輸入元素的型別。
isInValid (boolean) - 啟用/停用與無效資料關聯的樣式。
isValid (boolean) - 啟用/停用與有效資料關聯的樣式。
type (radio | checkbox) - 要渲染的輸入元素的型別。
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
一個簡單的表單檢查輸入和標籤元件可以使用如下所示:
<Form.Group controlId="gender" className="mb-3">
<Form.Label>Select your favorite programming language</Form.Label>
<div className="mb-3">
<Form.Check
type='checkbox'
id='java-lang'
name='language'
>
<Form.Check.Input type='checkbox' isValid />
<Form.Check.Label>Java</Form.Check.Label>
</Form.Check>
<Form.Check
type='checkbox'
id='c-lang'
name='language'
>
<Form.Check.Input type='checkbox' isValid />
<Form.Check.Label>C</Form.Check.Label>
</Form.Check>
<Form.Check
type='checkbox'
id='javascript-lang'
name='language'
>
<Form.Check.Input type='checkbox' isValid />
<Form.Check.Label>Javascript</Form.Check.Label>
</Form.Check>
</div>
</Form.Group>
Form.Range
Form.Range 元件用於在 HTML 表單中渲染範圍輸入控制元件。Form.Range 元件的一些有用屬性如下:
disabled (boolean) - 啟用/停用控制元件元素。
id (string) − 控制元件元素的 ID。如果此處未指定,則使用父 *Form.Group* 元件的 *controlId*。
value (string | arrayOf | number) - 底層控制元件的值。由 *onChange* 事件操作,初始值將預設為 *defaultValue* 屬性。
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
一個簡單的表單範圍元件可以使用如下所示:
<Form.Label>Select by range</Form.Label> <Form.Range value="25"/>
InputGroup
InputGroup 元件用於組合多個輸入和文字元件,並以簡單易用的方式建立新的高階元件。InputGroup 元件的一些有用屬性如下:
as (elementType) - 允許指定除 *<div>* 之外的其他元素。
hasValidation (boolean) - 當輸入組同時包含輸入和反饋元素時使用。
size (sm | lg) - 控制元件的大小。它將由元件內部處理。
bsPrefix (字串) - 用於自定義底層 CSS 類的字首。
InputGroup.Text
InputGroup.Text 元件用於在 InputGroup.Text 元件內渲染文字。Form 元件的一些有用屬性如下:
id (string) - 節點的 ID。
一個簡單的輸入組和文字元件可以使用如下所示:
<Form.Group controlId="email" className="mb-3">
<Form.Label>Enter your email address</Form.Label>
<InputGroup className="mb-3">
<Form.Control
aria-label="Email address"
aria-describedby="domain"
/>
<InputGroup.Text id="domain">@tutorialspoint.com</InputGroup.Text>
</InputGroup>
</Form.Group>
應用表單元件
首先,建立一個新的 React 應用程式,並使用以下命令啟動它。
create-react-app myapp cd myapp npm start
接下來,使用以下命令安裝 Bootstrap 和 react-bootstrap 庫:
npm install --save bootstrap react-bootstrap
接下來,開啟 `App.css` (src/App.css) 並刪除所有 CSS 類。
// remove all css classes
接下來,建立一個簡單的表單元件,SimpleForm (src/Components/SimpleForm.js),並渲染一個表單,如下所示:
import { Form, Button } from 'react-bootstrap';
function SimpleForm() {
return (
<Form>
<Form.Group className="mb-3" controlId="email">
<Form.Label>Email address</Form.Label>
<Form.Control type="email" placeholder="Enter email" />
<Form.Text className="text-muted">
This email address will be used for communication purpose.
</Form.Text>
</Form.Group>
<Form.Group className="mb-3" controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control type="password" placeholder="Password" />
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicCheckbox">
<Form.Check type="checkbox" label="Save password" />
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
);
}
export default SimpleForm;
這裡:
使用 Form.Control,型別為 text 和 password,分別獲取使用者名稱和密碼。
使用 Button 元件提交表單。
使用 Form.Group 將表單控制元件元件及其相關的標籤元件組合在一起。
接下來,開啟 App 元件 (src/App.js),匯入 Bootstrap css 並使用 Bootstrap 按鈕更新內容,如下所示:
import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimpleForm from './Components/SimpleForm'
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<SimpleForm />
</div>
</div>
</div>
);
}
export default App;
這裡:
使用import語句匯入 Bootstrap 類
已渲染新的SimpleForm元件。
包含 App.css 樣式
最後,在瀏覽器中開啟應用程式並檢查最終結果。表單將按如下所示呈現:
總結
Bootstrap表單元件提供了設計美觀表單所需的所有選項。它利用Bootstrap CSS框架,並提供易於使用的屬性來對錶單進行大量自定義。