0x01 React 基礎
(1)概述
- React 框架由 Meta(原 Facebook)研發併發布
- 用於構建 Web 和原生互動介面的庫
- 優點:
- 使用元件化的開發方式,效能較優
- 具有豐富生態,並且支援跨平臺
(2)建立開發環境
a. 第一個工程
需要提前安裝並配置好 Node.js v16+、npm v9+ 等工具
-
使用命令
npm install -g create-react-app
安裝建立 React 應用的工具- create-react-app 是由 Webpack 構建的、用於快速建立 React 開發環境的工具
- 以下內容使用的是 React v18.3
-
使用命令
create-react-app react-app
建立一個名為 react-app 的應用 -
使用命令
cd react-app
進入應用目錄-
目錄檔案說明:
-
node_modules:依賴目錄
-
public:靜態資源目錄
- favicon.ico:圖示檔案
- index.html:頁面檔案
-
src:程式碼資源目錄
-
App.js:應用檔案
function App() { return <div className="App">Hello, react!</div>; } export default App;
-
index.js:入口檔案
// React 必要的核心依賴(包) import React from "react"; import ReactDOM from "react-dom/client"; // 匯入應用元件的檔案 import App from "./App"; // 將應用元件渲染到頁面上 const root = ReactDOM.createRoot(document.getElementById("root")); // 將 index.html 中 id 為 root 的標籤建立為 React 根元件 root.render(<App />); // 將 App 元件渲染到根元件中
-
-
.gitignore:Git 忽略配置
-
package.json / package-lock.json:工程配置檔案
-
README.md:工程說明檔案
-
-
業務規範專案目錄
目錄 作用 apis 介面 assets 靜態資源 components 元件 pages 頁面 router 路由 store Redux 狀態管理 utils 工具函式
-
-
使用命令
npm start
執行 React 應用 -
訪問 http://localhost:3000/ 檢視預設 React 應用頁面
b. 開發工具及外掛
- 編輯器建議使用 VSCode,推薦其中新增以下外掛:
- ES7+ React/Redux/React-Native snippets
- ESLint
- Prettier
- Simple React Snippets
- Typescript React code snippets
- VSCode React Refactor
- 瀏覽器中關於 React 除錯的擴充套件:
- 安裝 Chrome 擴充套件
- 如果無法訪問,則可以嘗試該連結:https://www.crx4chrome.com/crx/3068/
- 安裝 Firefox 擴充套件
- 安裝 Edge 擴充套件
- 安裝 Chrome 擴充套件
(3)JSX
a. 概述
- JSX(JavaScript and XML)是 Javascript 語法擴充套件,可以在 Javascript 檔案中書寫類似 HTML 的標籤
- 元件使用 JSX 語法,從而使渲染邏輯和標籤共同存在於元件中
- JSX 看起來和 HTML 很像,但它的語法更加嚴格並且可以動態展示資訊
- JSX 轉化器:https://transform.tools/html-to-jsx
- JSX 的優點在於:既可以使用 HTML 的宣告式模板寫法,還可以使用 JS 的可程式設計能力
- JSX 程式碼需要透過 Babel 進行編譯,藉助 @babel/plugin-transform-react-jsx
b. JS 表示式
-
在 JSX 中,使用
{}
識別 JS 表示式(在 App.js 中編輯)-
傳遞字串
function App() { return <div>{"Hello, react!"}</div>; } export default App;
-
使用變數
const number = 123; function App() { return <div className="App">{number}</div>; } export default App;
-
使用物件
const object = { backgroundColor: "skyblue", color: "white", padding: "2rem", }; function App() { return ( <div className="App" style={object}> Hello, react! </div> ); } export default App;
-
呼叫函式與方法
function getString() { return "Today is "; } function App() { return <div className="App">{getString()} {new Date().toLocaleDateString()}</div>; } export default App;
-
-
if
語句、switch
語句、變數宣告等屬於語句,而非 JS 表示式,因此不能出現在 JSX 的{}
中
c. 列表渲染
- 使用陣列的
map()
方法 - 標籤其中必須設定屬性
key
,其賦值使用獨一無二的數字或字串
const list = [
{ name: "Alex", age: 18 },
{ name: "Bob", age: 20 },
{ name: "Charlie", age: 22 },
];
function App() {
return (
<div className="App">
<ul>
{list.map((item, index) => (
<li key={index}>
{item.name} - {item.age}
</li>
))}
</ul>
</div>
);
}
export default App;
d. 條件渲染
-
使用邏輯與運算子
&&
以及三元表示式?:
實現基礎條件渲染const flag = true; function App() { return ( <div className="App"> 運算子: {flag && <span>flag is true</span>} <br /> 表示式: {!flag ? <span>flag is true</span> : <span>flag is false</span>} </div> ); } export default App;
-
當條件較為複雜時,使用函式來處理
function judge(condition) { if (condition === 0) { return <span>空</span>; } else if (condition === 1) { return <span>唯一</span>; } else { return <span>兩個及以上</span>; } } function App() { return ( <div className="App"> <p>0: {judge(0)}</p> <p>1: {judge(1)}</p> <p>2: {judge(2)}</p> <p>3: {judge(3)}</p> </div> ); } export default App;
e. 事件繫結
-
on[事件名稱] = {事件處理程式}
,如點選事件:function App() { const handleClick = () => { alert("Clicked"); }; return ( <div className="App"> <button onClick={handleClick}>Click Me</button> </div> ); } export default App;
-
事件物件引數
function App() { const handleClick = (e) => { console.dir(e); }; return ( <div className="App"> <button onClick={handleClick}>Click Me</button> </div> ); } export default App;
-
自定義引數
- 需要函式引用,而不能直接呼叫函式
function App() { const handleClick = (value) => { alert(`name is ${value}`); }; return ( <div className="App"> <button onClick={() => handleClick("Alex")}>Click Me</button> </div> ); } export default App;
-
同時傳遞事件物件和自定義引數
function App() { const handleClick = (e, value) => { console.dir(e); alert(`name is ${value}`); }; return ( <div className="App"> <button onClick={(e) => handleClick(e, "Alex")}>Click Me</button> </div> ); } export default App;
(4)元件
a. 元件使用
-
元件是 React 的核心概念之一,是構建使用者介面(UI)的基礎
- 元件是獨立的 UI 片段,一個或多個元件構建成 React 應用
- 元件本質上是可以任意新增標籤的 Javascript 函式
// 自定義元件 function Paragraph() { return <p>This is a paragraph.</p>; } function App() { return ( <div> <h1>This is a heading.</h1> // 使用自定義元件 <Paragraph /> <Paragraph /> <Paragraph /> </div> ); } export default App;
b. 元件樣式
-
元件基礎樣式控制有兩種方案:
-
行內樣式
<div style={{ color: 'red' }}>content</div>
-
class 類名控制
/* index.css */ .content { color: red; }
// App.js import './index.css' function App() { return <div className='content'>content</div> }
- 推薦使用 class 類名控制
-
-
使用 classnames 最佳化類名控制
-
classnames 是 JavaScript 庫,可以透過條件動態控制 class 類名的顯示
-
使用命令
npm install classnames
安裝 -
舉例:
import "./index.css"; import { useState } from "react"; import classNames from "classnames"; function App() { const [flag, setFlag] = useState(false); const handleClick = () => { setFlag(true); }; return ( <div> <p className={classNames("content", { active: flag })}>文字內容</p> <button onClick={handleClick}>點選顯示文字內容</button> </div> ); } export default App;
-
(5)Hooks
- React 鉤子(hook)的使用規則
- 只能在元件中或其他自定義 Hook 中呼叫
- 只能在元件的頂層呼叫,不能巢狀在
if
、for
、其他函式中
a. useState
-
useState 是一個 React Hook,用於向元件新增狀態變數,從而控制並影響元件的渲染結果
- 資料驅動檢視:當狀態變數變化,元件的檢視也會跟著變化
useState()
:一個函式,其返回值是一個陣列
-
語法:
const [var, func] = useState(initValue)
- 基於
useState()
函式的返回值進行解構賦值 var
:狀態變數func
:修改狀態變數的函式方法initValue
:狀態變數初始值
- 基於
-
舉例:
import { useState } from "react"; function App() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; return ( <div> <p>{count}</p> <button onClick={handleClick}>+ 1</button> </div> ); } export default App;
-
狀態不可變規則:即狀態是只讀的,不可被修改,只能被替換
-
簡單型別,如上述案例
-
複雜型別,如物件:
import { useState } from "react"; function App() { const [form, setForm] = useState({ username: "Alex", password: "123456" }); const handleClick = () => { setForm({ ...form, username: "Bob", password: "654321" }); }; return ( <div> <p>使用者名稱: {form.username}</p> <p>密碼: {form.password}</p> <button onClick={handleClick}>修改</button> </div> ); } export default App;
-
-
使用 useState 控制表單狀態
- 使用
onChange
監測輸入的內容是否發生改變,從而修改content
的值
import { useState } from "react"; function App() { const [content, setContent] = useState(""); const handleChange = (value) => { setContent(value); }; return ( <div> <p>輸入的內容: {content}</p> <input type="text" onChange={(e) => { handleChange(e.target.value); }} /> </div> ); } export default App;
- 使用
b. useReducer
-
useRuducer 作用與 useState 類似,用於管理相對複雜的狀態資料
-
舉例:
import { useReducer } from "react"; // 1. 定義 reducer 函式, 根據不同的 action 返回不同的新狀態 function reducer(state, action) { switch (action.type) { case "increment": return state + 1; case "decrement": return state - 1; default: return state; } } function App() { // 2. 呼叫 useReducer 並傳入 reducer 函式和初始值 const [state, dispatch] = useReducer(reducer, 0); return ( <div> {/* 3. 事件觸發 dispatch 並分派 action 物件 */} <button onClick={() => dispatch({ type: "decrement" })}>-1</button> <span>{state}</span> <button onClick={() => dispatch({ type: "increment" })}>+1</button> </div> ); } export default App;
c. useRef
-
useRef 用於在 React 元件中建立和操作 DOM
-
舉例:
import { useRef } from "react"; function App() { const inputRef = useRef(null); // 1. 建立 Ref 物件 function handleClick() { console.dir(inputRef.current); // 3. 獲取 DOM 節點 } return ( <div> <input type="text" ref={inputRef} // 2. 將 Ref 物件與 input 元素繫結 /> <button onClick={handleClick}>獲取 DOM</button> </div> ); } export default App;
d. useEffect
-
useEffect 用於建立由渲染觸發的操作(不是由事件觸發),如傳送 Ajax 請求、更改 DOM 等
-
語法:
useEffect(() => {}, [])
- 回撥函式稱為“副作用函式”,其中是需要執行的操作
- 陣列可選,其中是監聽項,作為空陣列時副作用函式僅在元件渲染完畢之後執行一次
-
舉例:
import { useEffect, useState } from "react"; function App() { const [data, setData] = useState(""); useEffect(() => { async function getData() { const response = await fetch("https://api.ipify.org?format=json"); const data = await response.json(); setData(data.ip); } getData(); }, []); return <div>IP address: {data}</div>; } export default App;
-
監聽項陣列影響副作用函式
監聽項 副作用函式執行時機 無 元件初始渲染和更新時執行 空陣列 僅在初始渲染時執行 特定監聽項 元件初始渲染和監聽項變化時執行 -
無監聽項
import { useEffect, useState } from "react"; function App() { const [count, setCount] = useState(0); useEffect(() => { console.log("副作用函式執行"); }); return ( <div> <span>count: {count}</span> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); } export default App;
-
空陣列
import { useEffect, useState } from "react"; function App() { const [count, setCount] = useState(0); useEffect(() => { console.log("副作用函式執行"); }, []); return ( <div> <span>count: {count}</span> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); } export default App;
-
特定監聽項
import { useEffect, useState } from "react"; function App() { const [count, setCount] = useState(0); useEffect(() => { console.log("副作用函式執行"); }, [count]); return ( <div> <span>count: {count}</span> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); } export default App;
-
-
當元件解除安裝時,需要清理副作用函式
useEffect(() => { // 實現副作用操作邏輯 return () => { // 清除副作用操作邏輯 } },[])
-
舉例:清除定時器
import { useEffect, useState } from "react"; function Child() { useEffect(() => { const timer = setInterval(() => { console.log("定時器"); }, 500); return () => { clearInterval(timer); console.log("清除定時器"); }; }, []); return <div>子元件</div>; } function App() { const [show, setShow] = useState(true); return ( <div> {show && <Child />} <button onClick={() => setShow(false)}>解除安裝子元件</button> </div> ); } export default App;
-
e. useMemo
-
useMemo 用於在元件每次重新渲染的時候快取計算的結果
-
舉例:
import { useMemo, useState } from "react"; function fibonacci(n) { console.log("斐波那契數列計算"); if (n < 3) { return 1; } return fibonacci(n - 2) + fibonacci(n - 1); } function App() { const [count1, setCount1] = useState(0); const [count2, setCount2] = useState(0); const result = useMemo(() => { return fibonacci(count1); }, [count1]); console.log("元件重新渲染"); return ( <div> <button onClick={() => setCount1(count1 + 1)}>count1: {count1}</button> <button onClick={() => setCount2(count2 + 1)}>count2: {count2}</button> {result} </div> ); } export default App;
f. useCallback
-
useCallback 用於在元件多次重新渲染時快取函式
-
舉例:
import { memo, useCallback, useState } from "react"; const Comp = memo(function Comp({ prop }) { console.log("子元件重新渲染"); return ( <input type="number" onChange={(e) => prop(parseInt(e.target.value))} /> ); }); function App() { const [count, setCount] = useState(0); const changeHandler = useCallback((value) => { setCount(value); }, []); return ( <div> {count} <button onClick={() => setCount(count + 1)}>+1</button> <Comp prop={changeHandler} /> </div> ); } export default App;
g. 自定義 Hook
-
自定義 Hook 是以
use
為字首的函式方法 -
透過自定義 Hook 可以實現邏輯的封裝與複用
-
舉例:
import { useState } from "react"; function useToggle() { const [value, setValue] = useState(true); const toggle = () => setValue(!value); return [value, toggle]; } function App() { const [value, toggle] = useToggle(); return ( <div> <button onClick={toggle}>Toggle</button> {value && <div>Div</div>} </div> ); } export default App;
(6)元件通訊
- 元件通訊是指元件之間的資料傳遞
- 不同的元件巢狀方式有不同的資料傳遞方法,如父傳子、子傳父、兄弟間等
a. 父傳子
-
透過 props 實現父傳子元件通訊步驟:
- 父元件傳送資料:在子元件標籤上繫結屬性
- 子元件接受資料:子元件透過
props
引數接收資料
function Child(props) { console.log(props); return <p>Child Comp</p> } function App() { const name = "Alex" return ( <div> <Child name={name} /> </div> ); } export default App;
-
props 可以傳遞任意型別的資料,如數字、字串、陣列、物件、方法、JSX
-
props 是隻讀物件,資料只能在父元件修改
-
當父元件中把內容巢狀在子元件中時,props 會自動新增 children 屬性
function Child(props) { console.log(props); return <p>Child Comp</p> } function App() { return ( <div> <Child> <span>Alex</span> </Child> </div> ); } export default App;
b. 子傳父
-
透過呼叫父元件方法傳遞引數實現子傳父元件通訊
function Child({ onHandle }) { const value = "Alex"; return <button onClick={() => onHandle(value)}>傳送</button>; } function App() { const getValue = (value) => console.log(value); return ( <div> <Child onHandle={getValue} /> </div> ); } export default App;
c. 兄弟間
-
使用狀態提升實現兄弟元件通訊
- 即透過子傳父的方法將子元件 A 的資料傳遞到父元件,再透過父傳子的方法將父元件接收的資料傳遞到子元件 B
import { useState } from "react"; function A({ onHandle }) { const value = "Alex"; return ( <label> A: <button onClick={() => onHandle(value)}>傳送</button> </label> ); } function B(props) { return <div>B: {props.value}</div>; } function App() { const [value, setValue] = useState(""); const getValue = (value) => setValue(value); return ( <div> <A onHandle={getValue} /> <B value={value} /> </div> ); } export default App;
d. 跨層級(任意元件間)
-
使用 Context 機制實現跨層級元件通訊步驟:
- 使用
createContext
方法建立上下文物件 - 在頂層元件中,透過
Provider
傳遞(提供)資料 - 在底層元件中,透過
useContext
方法接收(消費)資料
import { createContext, useContext } from "react"; const ctx = createContext(); function Child() { const name = useContext(ctx); return <span>Child: {name}</span>; } function Parent() { return <Child />; } function App() { const name = "Alex"; return ( <div> <ctx.Provider value={name}> <Parent /> </ctx.Provider> </div> ); } export default App;
- 使用
-
該方法可用於任意元件間進行元件通訊
0x02 Redux
(1)概述
-
Redux 是 React 最常用的集中狀態關聯工具,可以獨立於框架執行
-
快速上手案例:
<!DOCTYPE html> <html lang="en"> <body> <button id="decrement">-1</button> <span>0</span> <button id="increment">+1</button> <script> // 第一步 function reducer(state = { count: 0 }, action) { if (action.type === "DECREMENT") { return { count: state.count - 1 }; } if (action.type === "INCREMENT") { return { count: state.count + 1 }; } return state; } // 第二步 const store = Redux.createStore(reducer); // 第三步 store.subscribe(() => { console.log("資料改變"); document.querySelector("span").textContent = store.getState().count; // 第五步 }); // 第四步 const decrement = document.getElementById("decrement"); decrement.addEventListener("click", () => store.dispatch({ type: "DECREMENT" }) ); const increment = document.getElementById("increment"); increment.addEventListener("click", () => store.dispatch({ type: "DECREMENT" }) ); </script> </body> </html>
- 定義
reducer
函式- 作用:根據不同的
action
物件,返回不同的、新的state
state
:管理資料初始狀態action
:物件 type 標記
- 作用:根據不同的
- 生成
store
例項 - 訂閱資料變化
- 透過
dispatch
提交action
更改狀態 - 透過
getState
方法獲取最新狀態資料並更新到檢視
- 定義
-
在 Chrome 瀏覽器中可以使用 Redux DevTools 外掛對 Redux 進行除錯
(2)結合 React
a. 配置環境
-
在 React 中使用 Redux 前,需要安裝 Redux Toolkit 和 react-redux
-
Redux Toolkit(RTK)是官方推薦編寫 Redux 邏輯的方式,簡化書寫方式,包括:
- 簡化 store 配置方式
- 內建 immer 支援可變式狀態修改
- 內建 thunk 更好地非同步建立
-
react-redux 是用於連結 React 元件和 Redux 的中介軟體
graph LR Redux--獲取狀態-->React元件 --更新狀態-->Redux -
使用命令
npm install @reduxjs/toolkit react-redux
安裝
-
-
安裝完成後,在 src 目錄下新建 store 目錄
graph TB store-->modules & index.js modules-->subStore.js & ...- store 目錄是集中狀態管理的部分
- 其中新建 index.js,是入口檔案,組合子 store 模組
- 其中新建 modules 目錄,用於包括多個子 store 模組
b. 實現 counter
-
在 src/store/modules 中建立 counterStore.js
import { createSlice } from "@reduxjs/toolkit"; const counterStore = createSlice({ name: "counter", initialState: { // 初始狀態資料 count: 0, }, reducers: { // 修改資料的同步方法 increment(state) { state.count++; }, decrement(state) { state.count--; }, }, }); // 解構出建立 action 物件的函式 const { increment, decrement } = counterStore.actions; // 獲取 reducer 函式 const counterReducer = counterStore.reducer; // 匯出建立 action 物件和 reducer 函式 export { increment, decrement }; export default counterReducer;
-
修改 src/store/index.js
import { configureStore } from "@reduxjs/toolkit"; import counterReducer from "./modules/counterStore"; // 建立根 store 來組合子 store 模組 const store = configureStore({ reducer: { counter: counterReducer, }, }); export default store;
-
修改 src/index.js,將 store 匯入 React 元件
import store from "./store"; import { Provider } from "react-redux"; // ... root.render( <Provider store={store}> <App /> </Provider> );
-
修改 src/App.js,在元件中使用 store,透過
useSelector
鉤子函式- 該函式將 store 中的資料對映到元件中
import { useSelector } from "react-redux"; function App() { const { count } = useSelector(state => state.counter); return <div>{count}</div>; } export default App;
-
修改 src/App.js,使用
useDispatch
鉤子函式修改資料- 該函式生成提交 action 物件的 dispatch 函式
import { useDispatch, useSelector } from "react-redux"; import { decrement, increment } from "./store/modules/counterStore"; // 匯入建立 action 物件的方法 function App() { const { count } = useSelector((state) => state.counter); const dispatch = useDispatch(); // 得到 dispatch 函式 return ( <div> {/*呼叫 dispatch 提交 action 物件*/} <button onClick={() => dispatch(decrement())}>-</button> <span>{count}</span> <button onClick={() => dispatch(increment())}>+</button> </div> ); } export default App;
c. 提交 action 傳參
-
以上述案例為例,為了實現點選不同按鈕可以直接把 count 的值修改為指定數字,需要在提交 action 物件時傳遞引數
-
原理:在 reducer 的同步修改方法中新增 action 物件引數,在呼叫
actionCreater
方法時傳參,其中引數會被傳遞到物件的 payload 屬性上 -
修改上述案例:
-
修改 src\store\modules\counterStore.js,建立 change 方法用於修改 count 變數到指定的值
import { createSlice } from "@reduxjs/toolkit"; const counterStore = createSlice({ // ... reducers: { // ... change(state, action) { state.count = action.payload; }, }, }); const { increment, decrement, change } = counterStore.actions; const counterReducer = counterStore.reducer; export { increment, decrement, change }; export default counterReducer;
-
修改 src\App.js,在元件中使用
import { useDispatch, useSelector } from "react-redux"; import { change } from "./store/modules/counterStore"; function App() { const { count } = useSelector((state) => state.counter); const dispatch = useDispatch(); return ( <div> <span>{count}</span> <button onClick={() => dispatch(change(10))}>to 10</button> <button onClick={() => dispatch(change(100))}>to 100</button> </div> ); } export default App;
-
d. 非同步狀態操作
-
非同步修改需要單獨封裝一個函式,其中返回一個新函式,這個新函式可以:
- 封裝非同步請求,並獲取資料
- 呼叫同步
actionCreater
傳入非同步資料生成 action 物件,並使用 dispatch 提交
-
舉例:src\store\modules\channelStore.js
import { createSlice } from "@reduxjs/toolkit"; const channelStore = createSlice({ name: "channel", initialState: { channelList: [], }, reducers: { setChannels(state, action) { state.channelList = action.payload; }, }, }); const { setChannels } = channelStore.actions; const url = ""; const fetchChannelList = () => { return async (dispatch) => { const res = await axios.get(url); dispatch(setChannels(res.data.channels)); }; }; const reducer = channelStore.reducer; export { fetchChannelList }; export default reducer;
0x03 React Router
(1)概述
-
React Router 實現客戶端路由,使響應速度更快
-
建立路由開發環境
-
使用命令
npm install react-router-dom
安裝 React Router -
修改 src\index.js
// ... import { createBrowserRouter, RouterProvider } from "react-router-dom"; // 1. 建立 router 例項物件並配置路由對應關係 const router = createBrowserRouter([ { path: "/login", element: <div>登入頁</div>, }, { path: "/register", element: <div>註冊頁</div>, }, ]); const root = ReactDOM.createRoot(document.getElementById("root")); // 2. 路由繫結 root.render(<RouterProvider router={router} />);
-
使用命令
npm start
啟動專案 -
依次訪問 http://localhost:3000/login 和 http://localhost:3000/register
-
(2)抽象路由模組
-
在 src 目錄下新建 page 目錄,其中包含各個頁面,如 Login/index.js 和 Register/index.js
// src\page\Login\index.js const Login = () => { return <div>登入頁</div>; }; export default Login;
// src\page\Register\index.js const Register = () => { return <div>註冊頁</div>; }; export default Register;
-
在 src 目錄下新建 router 目錄,其中新建 index.js,包含路由配置
import Login from "../page/Login"; import Register from "../page/Register"; import { createBrowserRouter } from "react-router-dom"; const router = createBrowserRouter([ { path: "/login", element: <Login />, }, { path: "/register", element: <Register />, }, ]); export default router;
-
修改 src\index.js
// ... import { RouterProvider } from "react-router-dom"; import router from "./router"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<RouterProvider router={router} />);
-
依次訪問 http://localhost:3000/login 和 http://localhost:3000/register
(3)路由導航
-
路由導航是指多個路由之間需要進行路由跳轉,並且跳轉過程中可能需要傳參通訊
-
主要用兩種導航方式:宣告式導航和程式設計式導航
-
宣告式導航是指在模板中透過
Link
元件指定跳轉的目標路由,如:// src\page\Login\index.js import { Link } from "react-router-dom"; const Login = () => { return ( <div> <p>登入頁</p> <Link to="/register">前往註冊頁</Link> </div> ); }; export default Login;
此時,訪問 http://localhost:3000/login 並點選連結即可跳轉至 http://localhost:3000/register
-
程式設計式導航是指透過
useNavigate
鉤子得到方法,並透過呼叫方法以命令式的形式實現路由跳轉,如:// src\page\Login\index.js import { useNavigate } from "react-router-dom"; const Login = () => { const navigate = useNavigate(); return ( <div> <p>登入頁</p> <button onClick={() => navigate("/register")}>前往註冊頁</button> </div> ); }; export default Login;
此時,訪問 http://localhost:3000/login 並點選按鈕即可跳轉至 http://localhost:3000/register
-
(4)傳遞引數
-
基於程式設計式導航,使用
useSearchParams
鉤子獲取 URI 中的查詢字串,如:-
src\page\Login\index.js
import { useNavigate } from "react-router-dom"; const Login = () => { const navigate = useNavigate(); return ( <div> <p>登入頁</p> <button onClick={() => navigate("/register?phone=138&email=example@site.com")}>前往註冊頁</button> </div> ); }; export default Login;
-
src\page\Register\index.js
import { useSearchParams } from "react-router-dom"; const Register = () => { const [params] = useSearchParams(); let phone = params.get("phone"); let email = params.get("email"); return ( <div> <p>註冊頁</p> <p>手機: {phone}</p> <p>郵箱: {email}</p> </div> ); }; export default Register;
-
-
基於程式設計式導航,使用
useParams
鉤子獲取 URI 中的動態路由引數,如:-
src\router\index.js
// ... { path: "/register/:phone/:email", element: <Register />, }, // ...
-
src\page\Login\index.js
import { useNavigate } from "react-router-dom"; const Login = () => { const navigate = useNavigate(); return ( <div> <p>登入頁</p> <button onClick={() => navigate("/register/138123456/example@site.com")}>前往註冊頁</button> </div> ); }; export default Login;
-
src\page\Register\index.js
import { useParams } from "react-router-dom"; const Register = () => { const params = useParams(); let phone = params.phone; let email = params.email; return ( <div> <p>註冊頁</p> <p>手機: {phone}</p> <p>郵箱: {email}</p> </div> ); }; export default Register;
-
(5)巢狀路由
-
巢狀路由是指在一級路由下內嵌其他路由(二級路由)
-
舉例:
-
建立 src\page\About\index.js
const About = () => { return <div>關於頁</div>; }; export default About;
-
建立 src\page\Blog\index.js
const Blog = () => { return <div>部落格頁</div>; }; export default Blog;
-
建立 src\page\Home\index.js
import { Link, Outlet } from "react-router-dom"; const Home = () => { return ( <div> <p>主頁</p> <Link to="about">關於</Link> <br /> <Link to="blog">部落格</Link> <div style={{ border: "2px solid black", }} > <Outlet /> </div> </div> ); }; export default Home;
-
修改 src\router\index.js,配置巢狀路由
import { createBrowserRouter } from "react-router-dom"; import Home from "../page/Home"; import About from "../page/About"; import Blog from "../page/Blog"; const router = createBrowserRouter([ { path: "/home", element: <Home />, children: [ { path: "about", element: <About /> }, { path: "blog", element: <Blog /> } ] }, ]); export default router;
-
-
預設二級路由:當一級路由顯示時需要某個指定二級路由同時顯示時,需要設定預設二級路由
-
修改 src\router\index.js,配置預設二級路由
// ... children: [ { index: true, element: <About /> }, { path: "blog", element: <Blog /> } ] // ...
-
修改 src\page\Home\index.js
{/* ... */} <Link to="/home">關於</Link> <br /> <Link to="blog">部落格</Link> {/* ... */}
-
(6)通配路由
-
所謂通配路由其實常用於,當訪問的路由不存在時,彈出的 404 頁面
-
舉例:
-
建立 src\page\NotFound\index.js
const NotFound = () => { return <div>404 Not Found</div>; }; export default NotFound;
-
修改 src\router\index.js,配置通配路由
import { createBrowserRouter } from "react-router-dom"; import NotFound from "../page/NotFound"; const router = createBrowserRouter([ { path: "*", element: <NotFound />, }, ]); export default router;
-
(7)路由模式
-
路由模式主要包括 hash 模式和 history 模式,兩者相比:
路由模式 URL 原理 是否需要後端支援 建立方法 hash url/#/route 監聽 hashChange 事件 否 createHashRouter history url/route history 物件 + pushState 事件 是 createBrowerRouter -
路由模式的選擇在 src\router\index.js 中實現,如:
import { createBrowserRouter, createHashRouter } from "react-router-dom"; const routerHash = createHashRouter(); const routerHistory = createBrowserRouter(); export { routerHash, routerHistory };
0x04 React 高階
(1)React.memo
-
React.memo 用於允許元件在 Props 沒有改變的情況下跳過渲染
-
React 預設渲染機制:當父元件重新渲染,則子元件也重新渲染
-
使用
memo
函式包裹生成的快取元件只有在 props 發生改變時重新渲染const MemoComp = memo(function CusComp(props) {})
-
-
舉例:
import { memo, useState } from "react"; const Comp = () => { console.log("子元件重新渲染"); return <div>Comp</div>; }; const MemoComp = memo(function Comp(props) { console.log("快取子元件重新渲染"); return <div>MemoComp</div>; }); function App() { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>change</button> <Comp /> <MemoComp /> </div> ); } export default App;
-
在使用 memo 快取元件後,React 會對每個 prop 使用
Object.is
比較,true
為沒有變化,false
為有變化- 當 prop 是簡單型別(如數字、字串等),
Object.is(3, 3)
返回true
,即沒有變化 - 當 prop 是引用型別(如陣列、物件等),
Object.is([], [])
返回false
,即有變化,因為引用發生變化
import { memo, useMemo, useState } from "react"; const AComp = memo(function Comp({ prop }) { console.log("A 元件重新渲染"); return <div>AComp: {prop}</div>; }); const BComp = memo(function Comp({ prop }) { console.log("B 元件重新渲染"); return <div>BComp: {prop}</div>; }); function App() { const [count, setCount] = useState(0); const list = useMemo(() => { return [1, 2, 3]; }, []); return ( <div> <button onClick={() => setCount(count + 1)}>change</button> <AComp prop={count} /> <BComp prop={list} /> </div> ); } export default App;
- 當 prop 是簡單型別(如數字、字串等),
(2)React.forwardRef
-
React.forwardRef 用於透過使用 ref 暴露 DOM 節點給父元件
-
舉例:
import { forwardRef, useRef } from "react"; const Comp = forwardRef((props, ref) => { return <input type="text" ref={ref} autoFocus />; }); function App() { const compRef = useRef(null); const handleClick = () => { console.log(compRef.current.value); }; return ( <div> <Comp ref={compRef} /> <button onClick={handleClick}>Click</button> </div> ); } export default App;
(3)useInperativeHandle
-
useInperativeHandle 鉤子用於透過 ref 暴露子元件的方法給父元件使用
-
舉例:
import { forwardRef, useImperativeHandle, useRef } from "react"; const Comp = forwardRef((props, ref) => { const compRef = useRef(null); const focusHandle = () => { compRef.current.focus(); }; useImperativeHandle(ref, () => { return { focusHandle, }; }); return <input type="text" ref={compRef} />; }); function App() { const compRef = useRef(null); const handleClick = () => { compRef.current.focusHandle(); }; return ( <div> <Comp ref={compRef} /> <button onClick={handleClick}>Click</button> </div> ); } export default App;
(4)常用第三方包
a. SASS/SCSS
-
SCSS 是一種預編譯 CSS 語言,其檔案字尾名為 .scss,支援一些原生 CSS 不支援的高階用法,如變數使用、巢狀語法等
-
使用命令
npm install sass -D
安裝 SASS,-D
表示僅在開發模式使用,不會打包到生產模式 -
使用 SCSS:
-
修改 src/index.css 為 src/index.scss
body { div { color: red; } }
-
修改 src/index.js
// ... import "./index.scss" // ...
-
修改 src/App.js
function App() { return <div>文字內容</div>; } export default App;
-
b. Ant Design
-
Ant Design(簡稱 AntD)是 React PC 端元件庫,由螞蟻金服出品,內建常用元件
-
使用命令
npm i antd --save
安裝 AntD,--save
表示將模組新增到配置檔案中的執行依賴中 -
使用 AntD:修改 src/App.js
import { Button } from "antd"; function App() { return <Button type="primary">Button</Button>; } export default App;
c. Zustand
-
Zustand 用於狀態管理
-
使用命令
npm i zustand
安裝 Zustand -
使用 Zustand:修改 src/App.js
import { create } from "zustand"; const store = create((set) => { return { count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), }; }); function App() { const { count, increment, decrement } = store(); return ( <div> <button onClick={decrement}>-1</button> <span>{count}</span> <button onClick={increment}>+1</button> </div> ); } export default App;
-
在非同步方面,Zustand 支援直接在函式中編寫非同步邏輯
const store = create((set) => { return { channelList: [], fetchChannelList: async () => { const URL = ""; const res = await axios.get(URL); const data = await res.json(); set({ channelList: data.data.channelList, }); }, }; });
-
當單個 store 較大時,可以透過切片模式進行模組拆分組合,即模組化
const createAStore = create((set) => { return {}; }); const createBStore = create((set) => { return {}; }); const store = create((...a) => ({ ...createAStore(...a), ...createBStore(...a), }));
(5)類元件
a. 概述
-
類元件是透過 JavaScript 中的類來組織元件的程式碼
- 透過屬性
state
定義狀態資料 - 透過方法
setState
修改狀態資料 - 透過方法
render
渲染 JSX
- 透過屬性
-
舉例:
import { Component } from "react"; class Counter extends Component { constructor(props) { super(props); this.state = { count: 0, }; } increment = () => { this.setState({ count: this.state.count + 1 }); }; decrement = () => { this.setState({ count: this.state.count - 1 }); }; render() { return ( <div> <button onClick={this.decrement}>-1</button> <span>{this.state.count}</span> <button onClick={this.increment}>+1</button> </div> ); } } function App() { return <Counter />; } export default App;
b. 生命週期
-
生命週期指元件從建立到銷燬的各個階段,這些階段自動執行的函式稱為生命週期函式
-
常用生命週期函式:
componentDidMount
:元件掛載完成後執行,常用於非同步資料獲取componentWillUnmount
:元件解除安裝時執行,常用於清理副作用方法
-
舉例:
import { Component, useState } from "react"; class Child extends Component { componentDidMount() { console.log("元件掛載完成"); } componentWillUnmount() { console.log("元件即將解除安裝"); } render() { return <div>子元件</div>; } } function App() { const [show, setShow] = useState(true); return ( <div> {show && <Child />} <button onClick={() => setShow(!show)}> {show ? "隱藏" : "顯示"}子元件 </button> </div> ); } export default App;
c. 元件通訊
-
方法與元件通訊類似
- 父傳子:透過 prop 繫結資料
- 子傳父:透過 prop 繫結父元件方法
- 兄弟間:狀態提示,透過父元件做狀態橋接
-
舉例:
import { Component } from "react"; class AChild extends Component { render() { return <div>子元件 A: {this.props.data}</div>; } } class BChild extends Component { render() { return ( <div> <p>子元件 B</p> <button onClick={() => this.props.onGetData(456)}>傳送</button> </div> ); } } class Parent extends Component { state = { data: 123, }; getData = (data) => { console.log(data); }; render() { return ( <div> <p>父元件</p> <AChild data={this.state.data} /> <BChild onGetData={this.getData} /> </div> ); } } function App() { return ( <div> <Parent /> </div> ); } export default App;
0x05 結合 TypeScript
(1)建立開發環境
-
使用命令
npm create vite@latest react-ts-app -- --template react-ts
建立使用 TypeScript 的 React 工程- 首次執行該命令時,需要同意安裝 Vite,選擇 React 框架,選擇 TypeScript
-
使用命令
cd react-ts-app
進入工程目錄 -
使用命令
npm install
安裝必要依賴 -
使用命令
npm run dev
啟動工程 -
工程目錄中,程式碼資原始檔在 src 目錄下,其中:
-
App.tsx:應用檔案
function App() { return <>App</>; } export default App;
-
main.tsx:入口檔案
import ReactDOM from "react-dom/client"; import App from "./App.tsx"; ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
-
vite-env.d.ts:環境配置檔案
/// <reference types="vite/client" />
-
(2)useState
-
React 會根據傳入 useState 的預設值來自動推導資料型別,無需顯式標註
import { useState } from "react"; function App() { const [value, setValue] = useState(0); const change = () => { setValue(100); }; return ( <> {value} <button onClick={() => change()}>Click</button> </> ); } export default App;
-
useState 本身是泛型函式,可以傳入自定義型別
type User = { name: string; age: number; }; const [user, setUser] = useState<User>();
- 限制
useState
函式引數的初始值必須滿足型別User | () => User
- 限制
useState
函式的引數必須滿足型別User | () => User | undefined
- 狀態資料
user
具備User
型別相關型別提示
- 限制
-
當不確定初始值應該為什麼型別時,將 useState 的初始值設為
null
,如:type User = { name: string; age: number; }; const [user, setUser] = useState<User | null>(null);
(3)Props
-
Props 新增型別是在給函式的引數做型別註解,可以使用
type
物件型別或interface
介面,如:type Props = { className: string; style: object; onGetData?: (data: number) => void; }; function Comp(props: Props) { const { className, style, onGetData } = props; const handleClick = () => { onGetData?.(123); }; return ( <div> <div className={className} style={style}> 子元件文字內容 </div> <button onClick={handleClick}>傳送 123</button> </div> ); } function App() { const getData = (data: number) => { console.log(data); }; return ( <> <Comp className="foo" style={{ color: "red" }} onGetData={getData} /> </> ); } export default App;
-
children
是一個比較特殊的 prop,支援多種不同型別資料的輸入type Props = { children: React.ReactNode; }; function Comp(props: Props) { const { children } = props; return <div>{children}</div>; } function App() { return ( <> <Comp> <button>Click</button> </Comp> </> ); } export default App;
(4)useRef
-
可以直接把需要獲取的 DOM 元素的型別,作為泛型引數傳遞給 useRef
import { useEffect, useRef } from "react"; function App() { const inputRef = useRef<HTMLInputElement>(null); useEffect(() => { inputRef.current?.focus(); }, []); return ( <> <input ref={inputRef} /> </> ); } export default App;
-End-