React18
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 的优点在于:既可以使用 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-