為什么使用 React 以及前端框架
工作原理
React 通過構建虛擬 DOM(Virtual DOM)來高效管理界面。當組件的狀態或屬性發生變化時,React 會重新渲染生成新的虛擬 DOM,并通過 Diff 算法找出新舊虛擬 DOM 樹之間的差異,最終僅將發生變化的部分同步到真實 DOM 中。這種方式避免了不必要的 DOM 操作,從而提升性能。
CDN 引入
在不使用打包工具(如 Vite、Webpack、Create React App)的前提下,你可以通過 CDN 直接引入 React 和 ReactDOM,然后在 HTML 文件中使用 React。
<!-- React 和 ReactDOM CDN(必須使用 development 版本) -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script><!-- Babel(用于解析 JSX) -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
隨后在 script
標簽添加 React 代碼,JSX 是不是瀏覽器原生支持的語法,所以必須通過 Babel 來轉譯 <App />
這樣的語法。
<script type="text/babel">// 定義一個簡單組件function App() {return <h1>Hello, React + CDN!</h1>;}// 渲染組件const root = ReactDOM.createRoot(document.getElementById('example'));root.render(<App />);
</script>
項目 | CDN 地址 | 用途 |
---|---|---|
React 核心庫 | https://unpkg.com/react@18/umd/react.development.js | 提供 React 全局對象,支持定義組件等功能 |
ReactDOM | https://unpkg.com/react-dom@18/umd/react-dom.development.js | 提供 ReactDOM 全局對象,支持將組件渲染到 DOM 上 |
Babel | https://unpkg.com/@babel/standalone/babel.min.js | 讓瀏覽器在運行時解析 JSX |
由于 unpkg 提供的 CDN 在國內沒有節點,可以使用其他鏡像的 CDN 提供 react 框架代碼
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js"></script>
[!NOTE]
在
file:///
協議下,瀏覽器會出于安全考慮 禁止腳本發出網絡請求或模塊加載;因此如果需要預覽,至少需要開啟 VSCode 的 Live Server 預覽插件。
NPM 腳手架
腳手架方法創建項目有兩種方法,一種是
通過 NPM 腳手架方式創建 React 項目需要 Node.js 環境,首先要確保本地安裝了對應環境,版本應該在 16 以上
npm -v # 檢查 node.js 環境版本
通過 Vite 創建 React 項目
npm create vite@latest
#npm create vite@4.1.0
隨后輸入項目名稱,選擇框架、語言后創建即可。創建完成后需要進入項目文件夾隨后安裝所有第三方依賴
cd react-demo # 這里換成剛剛創建的項目名稱文件夾
npm install # 安裝所有第三方庫
最后如果需要運行,可以直接運行前端服務器
npm run dev
隨后進入給出的地址即可,一般是 http://localhost:5173
[!NOTE]
編譯項目
npm build
隨后在 build 文件夾中找到編譯后的文件即可
基本項目結構
創建項目后,應該有以下文件
- node_modules: 存放第三方庫的文件夾,一般被加入到 gitignore 中
- public: 公共資源,比如圖片和視頻等
- src: 前端網站的源代碼
- App.tsx 作為初始項目的組件
- index.css
- App.css 使用 Vite 構建時自帶給的樣式文件,后期一般自己定義
- index.html: 項目入口
- package.json: 對這個 node 項目的一般信息和設置等
[!NOTE]
注意,除了基本的 JavaScript(.js文件)和 TypeScript(.ts文件),還有對應的擴展文件分別為 .jsx 和 .tsx
React 快速入門
創建組件
React 組件是構建 React 應用的基本單位,組件可以分為函數組件和類組件。
React 應用程序是由 組件 組成的。一個組件是 UI(用戶界面)的一部分,它擁有自己的邏輯和外觀。組件可以小到一個按鈕,也可以大到整個頁面,組件的設計讓整個 UI 結構化,并且可以復用一些常用組件。React 組件現在比較流行與用返回標簽的 JavaScript 函數來編寫,這樣更加輕便邏輯更加簡單:
創建一個 Message 組件,在 src
文件夾下創建 Message.tsx
function Message() {// JSX: JavaScript XMLreturn <h1>Hello, World!</h1>;
}export default Message;
在這里,似乎是在 JavaScript 中返回了一個 html 標簽,但事實上,這里返回的是 JavaScript XML,屬于 JavaScript 擴展的語法代碼。實際上,這個代碼會先轉換成普通 JS 代碼,再渲染到 HTML 前端中。
在這里,可以使用 <Message></Message>
來調用組件,而給出的代碼使用的是自閉合標簽,讓組件標簽更加清晰。
[!NOTE]
React 組件必須以大蛇式或帕斯卡(PascalCasing)命名,而 HTML 標簽是小寫字母,兩者予以區分。
你可以在這個工具網站看看過程 babeljs.io/repl
,babel 就是在 CDN 引用方法中提到的解析工具。
<h1>hello world</h1>
import { jsx as _jsx } from "react/jsx-runtime";
/*#__PURE__*/_jsx("h1", {children: "hello world"
});
可以看出,擴展語法并不是簡單的寫入前端代碼,只是將 js 的渲染更改的更加簡單。
編寫完組件之后,需要將這個組件作為默認對象從其中導出,這樣在其他代碼中就可以復用這個組件。export default
關鍵字指定了文件中的主要組件。如果對 JavaScript 某些語法不熟悉,可以參考 MDN 和 javascript.info。
應用組件,在 App.tsx 中重新編寫一個利用 Message 組件來打印 hello world 的頁面。
import Message from './Message';function App() {return <div><Message /></div>;
}export default App;
隨后運行網站,直接訪問給出地址,就可以看到定義在 Message 中的 helloword 信息。
在 tsx 代碼中,可以通過變量來動態修整顯示信息,創建變量的方法與 js 類似,而在標簽內顯示變量需要用花括號括起來。
function Message() {const name = "Cacciatore";return <h1>Hello, {name}!</h1>;
}
export default Message;
引入樣式
首先通過 npm 為本項目下載前端樣式框架
npm install bootstrap # Bootstrap
隨后在 main.tsx
中可以看到默認引用了 index.css
現在更改成之前下載的框架,修改后如下。
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import 'bootstrap/dist/css/bootstrap.css'
import App from './App.tsx'createRoot(document.getElementById('root')!).render(<StrictMode><App /></StrictMode>,
)
這樣就直接導入了 Boostrap 作為前端樣式框架
現在創建一個新的組件,比如創建一個列表組件,首先在 src 文件夾創建一個 components 文件夾用于存放所有組件代碼,這樣子更加方便項目的源代碼管理。
function ListGroup() {return (<ul className="list-group"><li className="list-group-item">123</li><li className="list-group-item">321</li><li className="list-group-item">abc</li><li className="list-group-item">xyz</li></ul>);
}export default ListGroup;
這是一個創建列表的組件,首先利用一般的 HTML 語法創建了一個列表,其次將元素的類賦予 Bootstrap 樣式預先定義的類,這樣子就可以生成一個帶有定義樣式的列表組件,將其載入到 App.tsx 后即可在前端查看。
[!NOTE]
如果你使用 VSCode 作為 IDE,你可能會知道它自帶了格式化文檔的功能,讓代碼直接格式化成符合縮進的樣子,快捷鍵
ctrl + shift + I
,除了自帶的格式化樣式,還可以安裝插件 Prettier ,這個插件提供了更好的格式化功能,當下載好后第一次去格式化文檔 IDE 會讓你設置使用普通的還是 Prettier 提供的進行格式化。
class
在 js 中屬于關鍵字,因此在這里的返回標簽應該使用className
作為類的引用。
組件函數返回與碎片
組件函數只能返回一個根元素,如果說,在上面的列表,我們想要添加一個 h1 元素作為這個列表的名稱,是不可行的,因為這樣將包含一個 h1 元素和 ul 列表元素,這是因為在上文提到,這里返回的不是一個簡單的 HTML 前端代碼,這些會被轉換成 js 代碼,因此必須只有一個根元素作為參數然后渲染。如果通過在這兩個元素外套一個 div 元素將他們包裹,一起返回這一個外面的 div 元素給 react, 雖然可以解決這個問題,但是這里多出一個 div 元素,只是為了讓參數正確是不必要的,而且會增加文件結構的復雜度。因此,這里引入碎片來解決這個問題。
import { Fragment } from "react";function ListGroup() {return (<Fragment><h1>List group</h1><ul className="list-group"><li className="list-group-item">123</li><li className="list-group-item">321</li><li className="list-group-item">abc</li><li className="list-group-item">xyz</li></ul></Fragment>);
}export default ListGroup;
當然,你也可以直接使用空標簽來使用碎片
<><h1>List group</h1><ul className="list-group"><li className="list-group-item">123</li><li className="list-group-item">321</li><li className="list-group-item">abc</li><li className="list-group-item">xyz</li></ul>
</>
標記內的動態渲染
在組件的返回中,作為標記無法使用其他 js 代碼,因此需要通過用 {} 對標記進行動態渲染
import { Fragment } from "react";function ListGroup() {const cities = ["New York", "Los Angeles", "Chicago", "Houston", "Phoenix"];return (<Fragment><h1>List group</h1><ul className="list-group">{cities.map((city) => (<li className="list-item" key={city}>{city}</li>))}</ul></Fragment>);
}export default ListGroup;
或者,在返回前定義好需要顯示的內容,避免對返回的標記的結構進行太大的破壞
import { Fragment } from "react";function ListGroup() {let cities = ["New York", "Los Angeles", "Chicago", "Houston", "Phoenix"];cities = [];const message = cities.length === 0 ? "No cities found" : null;return (<Fragment><h1>List group</h1>{message}<ul className="list-group">{cities.map((city) => (<li className="list-item" key={city}>{city}</li>))}</ul></Fragment>);
}export default ListGroup;
或者,你可以使用邏輯符號
cities.length === 0 && <p>No city found</p>
當前者條件為真時,將會返回第二個元素