近两天更新完基本内容,后续长期更新,建议关注收藏点赞。
目录
- React 项目创建
- JSX 基础
- 组件基础(props、事件、children)
- 状态管理基础(useState、useEffect、组件通信)
- 条件渲染与列表渲染
- 表单处理与受控组件
- React Router v6 路由系统
- 全局状态管理(Redux Toolkit vs Zustand)
- 项目架构实战(Axios、鉴权、路由拦截)
- Tailwind CSS + 动态样式 + 组件封装
- 用户权限系统设计(角色控制、按钮权限、菜单权限)
- 性能优化与引用管理(useRef / useMemo / useCallback)
- 自定义 Hooks
- Redux 异步与中间件 (createAsyncThunk、logger )
- 项目架构设计 (文件结构、目录规范、alias 设置)
- 样式系统
- 项目打包部署 + Vite 构建优化上线
- 进阶【后续更新】
React 项目创建
用 Vite + TypeScript
npm create vite@latest react-demo -- --template react-ts
cd react-demo
npm install
npm run dev
react-demo/
├─ src/
│ ├─ App.tsx # 主组件
│ ├─ main.tsx # 入口文件(渲染 App)
│ └─ vite-env.d.ts # Vite 的类型声明
├─ public/ # 静态资源
├─ index.html # HTML 模板
├─ vite.config.ts # 构建配置
JSX 基础
JSX 是 React 独有的语法扩展,它长得像 HTML,但其实是 JavaScript
语法 | 示例 |
---|---|
标签必须闭合 | <img src="..." /> |
表达式用 {} | <h1>{1 + 1}</h1> |
条件渲染 | {isLogin ? 'Welcome' : 'Please login'} |
列表渲染 | {items.map(item => <li>{item}</li>)} |
样式写法 | <div style={{ color: 'red' }} /> |
class 要写成 className | <div className="box" /> |
function Hello() {
return <h1>Hello, React!</h1>
}
//等价于
React.createElement('h1', null, 'Hello, React!')
//练习:写在App.tsx中
组件基础(props、事件、children)
每个组件就是一个函数(返回 JSX)
多个组件组合成完整页面
React 项目本质是“组件树”
function Welcome() {
return <h1>欢迎来到 React!</h1>
}
//使用组件 直接写 <组件名 />
function App() {
return (
<div>
<Welcome />
</div>
)
}
- 响应事件
function Button() {
const handleClick = () => {
alert('按钮被点击了!')
}
return <button onClick={handleClick}>点击我</button>
}
function App() {
return (
<div>
<Welcome name="小红" />
<Button />
</div>
)
}
组件通信
- 父传子——传参props
React 中组件通过 props 来传递参数(和 Vue 的 props 一样)
父组件传参,子组件接收
function App() {
return <Welcome name="小明" />
}
function Welcome(props: { name: string }) {
return <h1>Hello, {props.name}!</h1>
}
//解构简写
function Welcome({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>
}
- 父传子——children插槽 (和 Vue 的 slot 类似)
React 中组件可以“嵌套内容”,通过 props.children 实现。
function Card(props: { children: React.ReactNode }) {
return <div className="card">{props.children}</div>
}
function App() {
return (
<Card>
<h2>标题</h2>
<p>这是正文内容</p>
</Card>
)
}
- 子传父——传事件函数
function Parent() {
const handleClick = (msg: string) => {
alert('收到子组件消息:' + msg)
}
return <Child onSend={handleClick} />
}
function Child({ onSend }: { onSend: (msg: string) => void }) {
return <button onClick={() => onSend('hello!')}>发消息</button>
}
- 兄弟组件通信
方法一:提升状态到共同的父组件
方法二:使用 全局状态管理(Zustand、Redux、Context 等) - 全局通信Context API
- 使用场景:
当前主题、用户信息、登录状态、语言、权限等全局变量
跨多层组件传值(比如 Layout → Sidebar → MenuItem) - 对比
- 使用场景:
特点 | Context API | Zustand / Redux |
---|---|---|
状态管理 | 简单全局状态 | 复杂状态管理 |
使用场景 | 小项目、轻量通信 | 大型项目、模块化 |
性能优化 | 手动优化(memo) | 自动优化 / 中间件支持 |
学习成本 | 低 | 中等偏高(Zustand较低) |
//创建上下文
// context/UserContext.tsx
import { createContext } from 'react'
export const UserContext = createContext<{ name: string }>({ name: '游客' })
//在顶层组件中提供上下文
// App.tsx
import { UserContext } from './context/UserContext'
function App() {
return (
<UserContext.Provider value={{ name: '张三' }}>
<Child />
</UserContext.Provider>
)
}
//在任意子组件中消费
// Child.tsx
import { useContext } from 'react'
import { UserContext } from './context/UserContext'
function Child() {
const user = useContext(UserContext)
return <div>用户名:{user.name}</div>
}
状态管理基础(useState、useEffect、组件通信)
React 是响应式框架,UI 会随着 state 变化自动更新。
- useState:状态管理的核心
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
//useState(初始值) 返回 [值, 修改值的方法]
//每次调用 setCount() 会重新渲染组件
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>加一</button>
</div>
)
}
- useEffect:副作用处理
像 Vue 的 onMounted、watchEffect,React 用 useEffect。
//组件挂载时执行一次(相当于 Vue 的 mounted)
import { useEffect } from 'react'
function App() {
useEffect(() => {
console.log('组件已挂载')
}, []) // 空数组:只执行一次
return <h1>Hello</h1>
}
//监听某个值的变化(相当于 watch)
useEffect(() => {
console.log('count 变了', count)
}, [count]) // 依赖 count
条件渲染与列表渲染
- 条件渲染(if 判断)
React 不支持 v-if,用 JS 表达式解决,一个是三元表达式(最常用),一个是逻辑与。
const isLogin = true
return (
<div>
{isLogin ? <p>欢迎回来</p> : <p>请先登录</p>}
</div>
)
{isLogin && <p>你已经登录</p>}
- 列表渲染(map())
和 Vue 的 v-for 类似,React 用 .map() 遍历数组。
const fruits = ['🍎', '🍌', '🍇']
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
)
//使用key的原因:React 用 key 来追踪每个元素,提高性能;通常用唯一 id 或 index。
- 特殊情况处理(如列表为空)
{items.length === 0 ? <p>暂无数据</p> : items.map(...)}
{items.length === 0 && <p>暂无数据</p>}
- 整合例子
- 为什么用setList而不是list.push(‘新的事项’)?
因为 React 的状态不能直接修改原数组,需要返回一个 新引用的数组,这样 React 才能检测到变化,重新渲染组件。
在 React 中,状态更新函数(如 setList)是由 useState 钩子生成的,用来更新组件内部的状态。 - React 中的状态更新函数(如 setList、setState)为啥是异步的
为了批处理更新,只渲染一次组件,提高性能
- 为什么用setList而不是list.push(‘新的事项’)?
function TodoList() {
const [list, setList] = useState(['学习 React'])
const addItem = () => {
setList([...list, '新的事项']) // 添加
}
const removeItem = (index: number) => {
setList(list.filter((_, i) => i !== index)) // 删除
}
return (
<div>
<ul>
{list.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => removeItem(index)}>删除</button>
</li>
))}
</ul>
<button onClick={addItem}>添加事项</button>
</div>
)
}
表单处理与受控组件
- 受控组件
React 要求我们手动管理表单的值(不像 Vue 用 v-model):
输入框的值受 useState 控制
事件中用 setXxx() 更新值 - 组件使用
表单控件 | 受控写法 |
---|---|
输入框 | value + onChange |
复选框 | checked + onChange |
单选框 | value + checked + onChange |
下拉框 | value + onChange |
//文本输入框(input[type=text])
function FormDemo() {
const [name, setName] = useState('')
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="请输入姓名"
/>
<p>你好,{name}</p>
</div>
)
}
//复选框(checkbox)
const [isAgree, setIsAgree] = useState(false)
<input
type="checkbox"
checked={isAgree}
onChange={(e) => setIsAgree(e.target.checked)}
/>
<span>我已阅读协议</span>
//单选框(radio)
const [gender, setGender] = useState('male')
<label>
<input
type="radio"
value="male"
checked={gender === 'male'}
onChange={(e) => setGender(e.target.value)}
/>
男
</label>
<label>
<input
type="radio"
value="female"
checked={gender === 'female'}
onChange={(e) => setGender(e.target.value)}
/>
女
</label>
//下拉框(select)
const [city, setCity] = useState('beijing')
<select value={city} onChange={(e) => setCity(e.target.value)}>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>
React Router v6 路由系统
npm install react-router-dom
- 设置路由容器(BrowserRouter)
!
是 TypeScript 语法中的一种 非空断言(Non-null Assertion),它的意思是告诉 TypeScript:“我很确定这里不会是 null,你不用报错。”
//在 main.tsx 或入口文件包裹一层
import { BrowserRouter } from 'react-router-dom'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<App />
</BrowserRouter>
)
- 定义路由表(Routes + Route)
//1. App.tsx 或 App.jsx 文件中写(小项目)
//在 main.tsx 或 index.tsx 中包裹 <BrowserRouter>
import { Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
)
}
//2. 单独建一个 router/index.tsx 文件(大型项目推荐)
// 在App.tsx 中使用
- 页面跳转
//使用 <Link /> 跳转(类似 Vue 的 <router-link>)
//用于“JSX模板”中跳转
import { Link } from 'react-router-dom'
function Navbar() {
return (
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于我们</Link>
</nav>
)
}
export default Navbar
// 编程式跳转(useNavigate)用于“逻辑代码”中跳转
import { useNavigate } from 'react-router-dom'
function HomePage() {
const navigate = useNavigate()
const goToAbout = () => {
navigate('/about')
}
return <button onClick={goToAbout}>跳转到关于页</button>
}
- 动态路由 & 参数(URL Params)
<Route path="/user/:id" element={<User />} />
//在组件中获取参数:
import { useParams } from 'react-router-dom'
const { id } = useParams()
- 嵌套路由(父子页面结构)
作用:页面布局复用、保持父结构不变,仅替换中间区域
类似于Tab 页切换,父组件保持不变(比如顶部标题、导航栏、外层布局),子组件根据路径切换(比如内容区域变化)
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
//子组件用 <Outlet /> 占位
import { Outlet } from 'react-router-dom'//出口
export default function Dashboard() {
return (
<div>
<h1>仪表盘</h1>
<Outlet />//Outlet 就是“内容切换区域” 嵌套页面的渲染出口
</div>
)
}
- 导航守卫模拟(useEffect + useNavigate + localStorage)
比如没登录的就要跳转到登录页
// 在 Layout 或根组件中
import { useEffect } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
export default function Layout() {
const location = useLocation()
const navigate = useNavigate()
useEffect(() => {
const token = localStorage.getItem('token')
if (!token && location.pathname !== '/login') {
navigate('/login') // 模拟“导航守卫”
}
}, [location.pathname])
//第二个参数是依赖数组(dependency array) 根据其变化才响应执行useEffect函数
return (
<div>
{/* 页面内容 */}
<Outlet />
</div>
)
}
全局状态管理(Redux Toolkit vs Zustand)
两种主流方案:Redux Toolkit(企业级)、Zustand(轻量化)
- 为什么需要全局状态?
在以下情况中,你就该用全局状态:
跨组件共享数据(如:用户登录信息、购物车、权限等)
页面刷新保持状态(结合 localStorage)
避免 props 一层层传递 - 对比
项目 | Redux Toolkit | Zustand |
---|---|---|
学习成本 | 中等(函数多) | 极低(像写普通 JS) |
推荐场景 | 企业级、大型项目 | 中小项目、轻型应用 |
TS 支持 | ✅ 非常好 | ✅ 很好 |
DevTools | ✅(需中间件配置) | ✅ 内置支持 |
Redux Toolkit
npm install @reduxjs/toolkit react-redux
概念 | 类比 | 解释 |
---|---|---|
state | 仓库的数据 | 比如:当前用户是谁、是否登录 |
reducer | 管理员 | 只能通过它修改数据 |
action | 修改请求单 | 要写明“修改类型”和“修改内容” |
dispatch(action) | 把请求单交给管理员 | 派发请求,触发状态更新 |
- reducer 和reducers
reducer:一个函数,合并所有 reducers 的功能,它会接收当前的 state 和 action,然后返回新的 state。用于 替代 store 中的 reducer 函数。
reducers:通常在 createSlice 或其他地方用于简化代码结构。是 Redux Toolkit 特有的概念,它是一个对象。包含多个处理不同状态更新的函数。- 为什么 export default userSlice.reducer,而不是 reducers
createSlice 自动生成的reducer,在 Redux store 中使用的应该是合成后的 reducer 函数,而不是单个的 reducers 对象。
reducer 函数:是createSlice 自动从 reducers 对象中派生出来的单一函数,它会负责合并所有的 reducer 函数,并且自动处理 state 的更新,返回更新后的状态。
userSlice.reducers:包含了 login 和 logout 等多个 reducer 函数,每个函数负责一个特定的状态更新。
userSlice.reducer:是一个合成后的 reducer 函数,实际上是 userSlice.reducers 对象的默认合并版本。
- 为什么 export default userSlice.reducer,而不是 reducers
- 创建状态模块(slice)
名称 | 作用 | 示例 |
---|---|---|
reducers | 定义状态如何被修改 | login(state, action) |
actions | 自动生成的 action 创建器函数 | login({name: '张三'}) |
reducer | 是最终导出的 reducer 函数 | export default userSlice.reducer |
// store/userSlice.ts
import { createSlice } from '@reduxjs/toolkit'
const userSlice = createSlice({
name: 'user',
initialState: { name: '游客', isLogin: false },
reducers: {
login(state, action) {
state.name = action.payload.name
state.isLogin = true
},
logout(state) {
state.name = '游客'
state.isLogin = false
},
//Redux Toolkit 允许你“修改” state,因为内部使用了 immer 库处理不可变数据。
},
})
export const { login, logout } = userSlice.actions
//Redux Toolkit 确实自动生成了 actions,
//但如果你在组件或外部文件里想用这些 action,
//必须手动 export 出来才能用。
export default userSlice.reducer
- 配置 store
// store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './userSlice'
const store = configureStore({
reducer: {
user: userReducer,
},
})
/*
总目的:获取 Redux store 的根状态类型(RootState),自动推导类型,保证类型安全
store.getState 是 Redux store 提供的一个方法,
用来获取当前 Redux store 中的整个状态。
它会返回一个包含所有 reducer 状态的对象。
ReturnType<T> 是 TypeScript 提供的一个工具类型,它可以提取一个函数类型 T 的返回值类型。
在这个例子中,T 就是 typeof store.getState,
它返回的是 store.getState 函数的返回值类型,也就是 RootState。
*/
export type RootState = ReturnType<typeof store.getState>
export default store
- 全局挂载 store(入口文件)
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<App />
</Provider>
)
- 组件中使用状态(useSelector + useDispatch)
useSelector:Redux 提供的 Hook,用于从全局 store 中读取数据。
useDispatch:用于派发 action,触发 reducer 改变状态。
RootState 是整个 Redux store 状态的类型(一般由 ReturnType
import { useSelector, useDispatch } from 'react-redux'
import { RootState } from '../store'
import { login, logout } from '../store/userSlice'
//从 userSlice 模块中导入 login 和 logout 这两个 action creator。
function UserInfo() {//组件定义
const user = useSelector((state: RootState) => state.user)//读取状态
const dispatch = useDispatch()
//获取 Redux 的 dispatch 方法,用来派发 action
return (
<div>
<p>用户名:{user.name}</p>
{user.isLogin ? (
<button onClick={() => dispatch(logout())}>退出</button>
) : (
<button onClick={() => dispatch(login({ name: '张三' }))}>登录</button>
)}
</div>
)
}
Zustand
npm install zustand
- 创建状态
// store/useUserStore.ts
import { create } from 'zustand'
export const useUserStore = create((set) => ({
name: '游客',
isLogin: false,
login: (name: string) => set({ name, isLogin: true }),
logout: () => set({ name: '游客', isLogin: false }),
}))
- 使用状态
import { useUserStore } from '../store/useUserStore'
function UserInfo() {
const { name, isLogin, login, logout } = useUserStore()
return (
<div>
<p>用户名:{name}</p>
{isLogin ? (
<button onClick={logout}>退出</button>
) : (
<button onClick={() => login('李四')}>登录</button>
)}
</div>
)
}
项目架构实战(Axios、鉴权、路由拦截)
- Axios
npm install axios
支持拦截器
// utils/request.ts
import axios from 'axios'
const request = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
})
// 请求拦截器:自动添加 token
request.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// 响应拦截器:处理错误统一提示
request.interceptors.response.use(
(res) => res.data,
(err) => {
alert('请求出错:' + err.response?.data?.message || err.message)
return Promise.reject(err)
}
)
//可选链操作符(Optional Chaining) ES2020 的语法
//一层一层的去看其属性是否存在
export default request
// api/user.ts
import request from '../utils/request'
export function loginApi(data: { username: string; password: string }) {
return request.post('/login', data)
}
export function getUserInfo() {
return request.get('/user/info')
}
- 登录鉴权:保存 Token + 用户信息
//登录成功后保存 token
const onLogin = async () => {
const res = await loginApi({ username, password })
localStorage.setItem('token', res.token)
navigate('/dashboard')
}
//获取用户信息(全局状态)
const res = await getUserInfo()
const setUser = useUserStore((state) => state.setUser)
setUser(res.data) // 存入 Zustand / Redux
const dispatch = useDispatch()
dispatch(setUser(res.data)) // 存入 Redux 状态
//Zustand里面这么写的
import { create } from 'zustand'
const useUserStore = create((set) => ({
user: null,
setUser: (data) => set({ user: data })
}))
//Redux Toolkit则这么写
const userSlice = createSlice({
name: 'user',
initialState: { user: null },
reducers: {
setUser(state, action) {
state.user = action.payload
}
}
})
export const { setUser } = userSlice.actions
- 路由守卫(拦截未登录)
React 没有 vue-router 的 beforeEach,推荐用“高阶组件”实现权限判断。
//创建一个权限路由组件
// components/AuthRoute.tsx
import { Navigate } from 'react-router-dom'
export default function AuthRoute({ children }: { children: JSX.Element }) {
const token = localStorage.getItem('token')
return token ? children : <Navigate to="/login" />
}
//使用
<Route
path="/dashboard"
element={
<AuthRoute>
<Dashboard />
</AuthRoute>
}
/>
Tailwind CSS + 动态样式 + 组件封装
- 定义
原子 CSS 工具 Tailwind,用于快速构建响应式、可维护的美观界面。通过类名来应用样式,而不需要写自定义的 CSS。 - 安装
npm init -y #创建一个项目
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
# 还可通过CDN引入 这种方法适合小项目
# 创建配置文件
npx tailwindcss init
创建一个 tailwind.config.js 文件和 postcss.config.js 文件。
可以自定义 Tailwind 的配置文件,调整颜色、间距、字体等。你可以在 tailwind.config.js 文件中进行配置。
//配置 tailwind.config.js
/** @type {import('tailwindcss').Config}
这个注释作用:typescript类型声明、自动补全提示*/
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
brand: '#3490dc',
},/*使用 bg-brand 来设置背景色为你自定义的 #3490dc*/
},
},
plugins: [],
}
在项目中创建一个 CSS 文件(如 styles.css),然后导入 Tailwind 的基本样式
/* styles.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 在 main.tsx 中引入*/
import './index.css'
配置构建工具,将 Tailwind 转换成最终的 CSS 文件(你可以使用工具如 PostCSS 或 Vite 来处理这一步)。
PostCSS 配置:适用于传统的构建工具,可以细粒度控制构建过程,适合需要自定义构建配置的项目。
Vite 配置:更现代化,具有极高的构建性能,尤其适用于前端项目,自动处理 CSS 并支持热重载。
使用 PostCSS 配置 Tailwind->CSS【不推荐】
PostCSS 是一个强大的工具,支持插件化的 CSS 处理。通过 PostCSS,你可以在构建过程中将 Tailwind 样式转化成最终的 CSS 文件。
注意:现代根本不需要单独用PostCSS单独构建,而是:把 Tailwind + PostCSS 集成到构建工具(如 Vite、Webpack、Next.js、Nuxt、Parcel 等)里自动处理。
npm init -y # 初始化项目
npm install tailwindcss postcss autoprefixer
npx tailwindcss init # 生成 tailwind.config.js
创建配置文件:生成 Tailwind 和 PostCSS 的配置文件
// postcss.config.js
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'), // 自动添加前缀
],
};
创建 Tailwind 的入口文件:在项目中创建一个 CSS 文件(如 src/styles/tailwind.css),并在其中引入 Tailwind 的基础样式:
/* src/styles/tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
在 package.json 中配置一个构建脚本,利用 PostCSS 来处理这个 CSS 文件,生成最终的 CSS 输出文件。
// package.json里添加
"scripts": {
"build": "postcss src/styles/tailwind.css -o dist/styles.css"
}
执行构建:运行以下命令,PostCSS 会处理 Tailwind CSS 文件并生成最终的 styles.css 文件。构建后的 dist/styles.css 文件会包含 Tailwind 样式,最终你可以将它链接到你的 HTML 文件中。
npm run build
使用 Vite 配置 Tailwind->CSS
Vite 是一个现代化的构建工具,它具有很好的性能,并且支持 Tailwind CSS 配置得非常简单。
使用 Vite,其实已经默认内置了 PostCSS 支持,你只要正常安装 Tailwind 并配置它,它会自动通过 PostCSS 插件系统来处理,无需你手动搭配。但如果你想更明确地控制 PostCSS 的行为,也可以显式地配置它。
npm init vite@latest my-project
cd my-project
npm install
npm install tailwindcss postcss autoprefixer
npx tailwindcss init
初始化 Tailwind 配置,配置 tailwind.config.js
// tailwind.config.js
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}", // 这里指定你的 HTML 或 JavaScript 文件位置
],
theme: {
extend: {},
},
plugins: [],
};
创建 Tailwind 的入口文件 并引入其样式
/* src/assets/styles/tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
一般只需要在 JavaScript 入口文件(比如 main.js 或 main.ts)中引入样式即可。
import './index.css'; // 假设你的 Tailwind CSS 入口文件是 index.css
,引入到js 就相当于引入到关联它的html中了
<!-- index.html 引入样式
不需要手动在 HTML 中引入 CSS 文件
-->
<head>
<link rel="stylesheet" href="/src/assets/styles/tailwind.css">
</head>
使用 Vite 启动开发服务器,在浏览器中实时查看 Tailwind 样式的效果。Vite 会自动处理 CSS 文件,包括 Tailwind 的转换过程,并在浏览器中即时显示结果。
npm run dev #热重载、即时更新
npm run build #生产环境打包
使用
- 常用类名
类别 | 类名 | 描述 |
---|---|---|
布局 | container | 设置元素为响应式容器 |
flex | 设置元素为 flexbox 布局容器 | |
grid | 设置元素为网格布局容器 | |
block | 设置元素为块级元素 | |
inline | 设置元素为行内元素 | |
justify-center | 水平居中对齐(flex 或 grid 布局中) | |
items-center | 垂直居中对齐(flex 布局中) | |
space-x-{size} | 设置元素之间的水平间距 | |
space-y-{size} | 设置元素之间的垂直间距 | |
尺寸 | w-{size} | 设置宽度,如 w-1/2 , w-full |
h-{size} | 设置高度,如 h-12 , h-full | |
max-w-{size} | 设置最大宽度 | |
min-h-screen | 设置最小高度为整个屏幕的高度 | |
间距 | m-{size} | 设置外边距(四个方向) |
p-{size} | 设置内边距(四个方向) | |
mt-{size} | 设置上外边距 | |
mb-{size} | 设置下外边距 | |
pl-{size} | 设置左内边距 | |
pr-{size} | 设置右内边距 | |
字体 | text-{color} | 设置文字颜色,如 text-red-500 |
font-{weight} | 设置字体粗细,如 font-bold , font-light | |
text-{size} | 设置字体大小,如 text-xl , text-2xl | |
leading-{size} | 设置行高 | |
tracking-{size} | 设置字间距 | |
背景 | bg-{color} | 设置背景颜色,如 bg-blue-500 |
bg-cover | 设置背景图片完全覆盖元素 | |
bg-center | 设置背景图片居中显示 | |
边框 | border | 添加边框 |
border-{color} | 设置边框颜色 | |
border-{size} | 设置边框宽度,如 border-2 | |
rounded | 添加圆角,如 rounded-lg | |
border-t | 只设置上边框 | |
响应式设计 | sm:{class} | 在屏幕宽度大于等于 640px 时应用该样式 |
md:{class} | 在屏幕宽度大于等于 768px 时应用该样式 | |
lg:{class} | 在屏幕宽度大于等于 1024px 时应用该样式 | |
xl:{class} | 在屏幕宽度大于等于 1280px 时应用该样式 | |
其他常用类 | opacity-{value} | 设置元素透明度,如 opacity-50 |
shadow | 添加阴影,如 shadow-lg | |
overflow-hidden | 隐藏超出元素边界的内容 | |
z-{index} | 设置元素的 z-index | |
状态类 | hover:{class} | 悬停时应用的类,如 hover:bg-blue-500 |
focus:{class} | 聚焦时应用的类,如 focus:ring-2 | |
active:{class} | 按下时应用的类,如 active:bg-red-500 | |
disabled | 设置元素为禁用状态(用于表单元素或按钮等) | |
过渡和动画 | transition | 添加过渡效果,如 transition-all |
duration-{time} | 设置过渡动画的持续时间,如 duration-300 | |
ease-{timing} | 设置过渡动画的节奏函数,如 ease-in-out | |
transform | 启用 CSS 变换(如旋转、缩放) | |
scale-{value} | 设置元素缩放,如 scale-110 | |
rotate-{deg} | 设置元素旋转角度,如 rotate-45 |
在 Vue 或 React 中,动态样式是指根据变量、状态、条件等来动态控制元素的样式。
用户权限系统设计(角色控制、按钮权限、菜单权限)
类型 | 示例 |
---|---|
页面权限 | 未登录不能访问 /admin ,普通用户不能访问 /setting |
菜单权限 | 普通用户菜单只有“首页”和“个人中心”,管理员菜单有更多功能 |
按钮权限 | 有“编辑权限”的用户才能看到“编辑按钮” |
- 设置权限模型(角色 + 权限码)
//登陆接口res:
{
user: {
name: '张三',
role: 'admin',
permissions: ['user:add', 'user:edit', 'menu:view']
}
}
//保存权限数据(Zustand 示例)
// store/useAuthStore.ts
import { create } from 'zustand'
export const useAuthStore = create((set) => ({
role: '', // 'admin' | 'user'
permissions: [] as string[],//初始值是一个空数组 且 只能放string类型元素
setAuth: (data: { role: string; permissions: string[] }) =>
set(data),
}))
//路由权限控制 带角色检查的路由守卫
import { useAuthStore } from '../store/useAuthStore'
import { Navigate } from 'react-router-dom'
export default function RoleRoute({
children,
allowedRoles,
}: {
children: JSX.Element
allowedRoles: string[]
}) {
const { role } = useAuthStore()
return allowedRoles.includes(role) ? children : <Navigate to="/403" />
}
<Route
path="/admin"
element={
<RoleRoute allowedRoles={['admin']}>
<AdminPage />
</RoleRoute>
}
/>
//菜单权限控制(动态渲染)
const allMenus = [
{ name: '首页', path: '/', code: 'home:view' },
{ name: '用户管理', path: '/users', code: 'user:view' },
]
function MenuList() {
const { permissions } = useAuthStore()
const visibleMenus = allMenus.filter((m) =>
permissions.includes(m.code)
)
return (
<ul>
{visibleMenus.map((m) => (
<li key={m.path}>
<Link to={m.path}>{m.name}</Link>
</li>
))}
</ul>
)
}
//按钮权限控制(最常见) 没权限时不可见
function DeleteButton() {
const { permissions } = useAuthStore()
if (!permissions.includes('user:delete')) return null
return (
<button className="bg-red-500 text-white px-2 py-1 rounded">
删除用户
</button>
)
}
性能优化与引用管理(useRef / useMemo / useCallback)
- useRef
主要用于持有对 DOM 元素的引用,或者存储某些需要在组件重新渲染时保留的值。 - useMemo
用于性能优化,可以缓存值,避免不必要的重新计算。 - useCallback
用于缓存函数,避免在每次渲染时重新创建函数。
const inputRef = useRef<HTMLInputElement>(null)//初始值null
const focusInput = () => {
inputRef.current?.focus()
/*
?. 是可选链操作符,意味着如果 inputRef.current 不是 null,才会执行 focus()
如果 inputRef.current 是 null,则什么也不做,避免发生错误。
*/
}
return <input ref={inputRef} />
/*
这段代码的意思是:执行focusInput函数可以在任何时候将焦点设置到引用的这个input上
*/
const expensiveComputation = useMemo(() => {
return heavyCalculation(value)
}, [value]) //[value] 是 依赖数组
const memoizedCallback = useCallback(() => {
// 执行某些操作
}, [dependencies])
自定义 Hooks
提炼逻辑,提高复用性
- hooks定义及作用
在 Hooks 出现之前,你想在组件中使用“状态”或“生命周期方法”(如 componentDidMount)只能用 class 组件 写法。 class 太重、不易复用、逻辑混乱,所以 React 团队就发明了 Hooks。
函数组件 + Hooks 的写法,简单清爽。React Hooks 让你能在不写 class 的情况下使用状态、生命周期、DOM 引用、性能优化等功能。
//无hooks
class MyComponent extends React.Component {
state = { count: 0 }
componentDidMount() {
console.log('组件挂载了');
}
render() {
return <div>{this.state.count}</div>;
}
}
//有hooks
import { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0); // 状态
useEffect(() => {
console.log('组件挂载了'); // 生命周期
}, []);
return <div>{count}</div>;
}
- 常见的hooks
Hook | 作用 |
---|---|
useState | 添加本地状态(替代 class 的 this.state ) |
useEffect | 副作用处理,如生命周期函数、数据请求等(替代 componentDidMount/DidUpdate/WillUnmount ) |
useRef | 获取 DOM 引用或存储可变值(不触发更新) |
useContext | 跨组件共享全局数据(状态管理) |
useMemo | 结果缓存,优化性能 |
useCallback | 函数缓存,避免不必要的重渲染 |
useReducer | 复杂状态管理(类似 Redux) |
//自定义 useFetch Hook
function useFetch(url: string) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchData = async () => {
const response = await fetch(url)
const result = await response.json()
setData(result)
setLoading(false)
}
fetchData()//调用
}, [url])//依赖url
return { data, loading }
}
Redux 异步与中间件 (createAsyncThunk、logger )
- createAsyncThunk
用于创建异步操作,结合 Redux 来处理 API 请求。- 有什么用?对比直接用fetch+hooks
中大型项目使用,统一管理复杂异步逻辑、状态、错误处理,更可维护、更清晰、更好调试。
- 有什么用?对比直接用fetch+hooks
功能 |
---|
自动创建三种状态(pending / fulfilled / rejected) |
自动处理 loading / error / data |
支持中间件(日志、调试工具) |
多组件共享请求状态 |
与 Redux DevTools 配合追踪异步流程 |
可以复用请求逻辑 |
单元测试更方便 |
// store/userSlice.ts
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId: string) => {
const response = await fetch(`/api/user/${userId}`)
return response.json()
}
)
// 定义异步 action
export const fetchUser = createAsyncThunk('user/fetch', async (id: string) => {
const response = await fetch(`/api/user/${id}`);
return await response.json();
});
// 在 slice 中响应状态
const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
- logger 中间件
logger 是一个 Redux 中间件,它的作用是:便于调试
在每次 action 被派发(dispatch)时,自动在控制台打印出:
当前的状态(state)
你派发的 action 是什么
派发后的新状态(next state)
注意:logger 只建议在 开发环境 使用,生产环境会影响性能和暴露数据。
//一般写在:src/app/store.ts 文件中
import { applyMiddleware } from 'redux'
import logger from 'redux-logger'
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
})
//还需要在 main.tsx(或 index.tsx)里引入这个 store,并注入给应用
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './app/store'; // 👈 引入刚才配置的 store
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider store={store}> {/* 👈 用 Provider 注入 */}
<App />
</Provider>
</React.StrictMode>
);
项目架构设计 (文件结构、目录规范、alias 设置)
src/
├── assets/ // 静态资源
├── components/ // 共享组件
├── features/ // 功能模块(User、Product)
├── hooks/ // 自定义 Hooks
├── store/ // Redux 或 Zustand 状态管理
├── styles/ // 样式文件(CSS、SCSS)
├── pages/ // 页面模块
└── utils/ // 工具函数、API
- 配置 alias(Vite 配置)
为什么要配置它?简化路径、便于维护
// vite.config.ts
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components'),
'@hooks': path.resolve(__dirname, 'src/hooks'),
},
},
})
样式系统
Tailwind CSS、CSS Modules【不推荐】、shadcn/ui 组件库
- shadcn/ui 是基于 Tailwind CSS 构建的 UI 组件库,可以快速集成到你的项目中。
项目打包部署 + Vite 构建优化上线
- Vite 基础配置
//package.json
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
npm run build
//生成 dist/ 文件夹,包含打包好的 index.html + assets 静态资源。
- 多环境变量配置(env)
#在根目录添加环境文件
.env # 默认开发环境
.env.development #仅开发环境生效(vite dev)
.env.production # 生产环境(vite build)
.env.staging # 测试环境
#每个文件中定义变量必须以 VITE_ 开头:
# .env
VITE_API_BASE=http://localhost:3000/api
# .env.production
VITE_API_BASE=https://api.example.com
npm run dev # 使用 .env.development
npm run build # 使用 .env.production
// 在代码里使用
//所有变量都通过 import.meta.env 访问。
const baseURL = import.meta.env.VITE_API_BASE_URL;
console.log(baseURL);
if (import.meta.env.MODE === 'development') {
console.log('这是开发环境');
}
//可以在 vite.config.ts 中也使用这些变量
export default defineConfig({
base: process.env.VITE_BASE_URL || '/',
});
/*
base 属性 是用来指定你的应用程序在部署时的基础路径
设置了 base,Vite 会自动为静态资源加上这个前缀
*/
- 环境变量有啥用?
- 不同环境请求不同的接口
- 动态设置页面标题 / 域名 / 图标等
- 控制功能开关(如是否开启调试日志)
- 配置第三方服务密钥(如 Firebase、Sentry 等)
- 如果直接用变量常量代替, 容易忘记修改、公用一个配置不安全、不能适配不同环境
// .env.development
VITE_API_URL=http://localhost:3000/api
// .env.production
VITE_API_URL=https://api.production.com
VITE_SITE_TITLE=我的博客
VITE_DEBUG=true
VITE_FIREBASE_KEY=xxx
VITE_SENTRY_DSN=xxx
axios.get(import.meta.env.VITE_API_URL + '/users');
document.title = import.meta.env.VITE_SITE_TITLE;
if (import.meta.env.VITE_DEBUG === 'true') {
console.log('调试信息:xxxxx');
}
- 构建优化配置(vite.config.ts)
在 Vite 中,打包优化(如代码分包、压缩等)通常需要借助 插件 和 配置,尤其是借助 vite-plugin 和内建的 Rollup 配置。Vite 本身基于 Rollup 进行打包,因此它的优化过程与 Rollup 的优化机制紧密相关。
- 代码分包(Code Splitting)
Vite 默认支持 代码分包,当你使用多个页面或动态导入模块时,Vite 会自动按需分包。 Vite 默认开启了 Rollup 的代码分割,可以直接在项目中使用,不需要额外配置。如果你的应用是多页面应用(MPA)或者使用了多个路由,Vite 会自动根据页面或路由拆分代码。
异步组件: 使用 Vue 或 React 的异步组件,也会自动实现代码分割。 - 压缩优化(Code Minification)
Vite 在生产模式下会自动启用 Terser 插件来压缩代码。这是一个用于 JavaScript 压缩的工具,可以帮助减小文件大小,提高加载速度。
默认情况下,在 vite build 时,Vite 会自动使用 Terser 进行 JavaScript 压缩。如果你需要定制压缩行为,可以修改 vite.config.ts 中的 Rollup 配置。
// vite.config.ts
export default defineConfig({
build: {
terserOptions: {
compress: {
drop_console: true, // 去掉 console.log
},
},
},
});
- 图片压缩和优化
使用 Vite 插件对图片进行优化,比如 vite-plugin-imagemin,减少图片的大小,提升加载速度。
npm install vite-plugin-imagemin --save-dev
// vite.config.ts
import viteImagemin from 'vite-plugin-imagemin';
export default defineConfig({
plugins: [
viteImagemin({
gifsicle: { optimizationLevel: 3 },
optipng: { optimizationLevel: 7 },
mozjpeg: { quality: 80 },
}),
],
});
- CSS 优化(压缩、提取)
Vite 会自动提取和压缩 CSS 文件。你可以进一步定制 CSS 优化
CSS 提取: Vite 会默认提取所有的 CSS 到单独的文件中。
CSS 压缩: 使用 cssnano 等工具进行进一步压缩。通过配置 postcss 插件实现。
// vite.config.ts
export default defineConfig({
css: {
postcss: {
plugins: [
require('cssnano')({ preset: 'default' }), // CSS 压缩
],
},
},
});
- 动态加载(Lazy Loading)和预加载(Preloading)
- 缓存优化(Cache)
配置缓存策略来优化生产构建时的缓存行为,例如通过设置文件名哈希值来强制浏览器更新缓存 - 第三方插件进一步优化
vite-plugin-pwa:可以将应用程序打包为 PWA(Progressive Web App),支持离线缓存。
vite-plugin-compression:自动压缩构建后的文件(如 gzip、brotli)以减少网络传输时的体积。
import ViteCompression from 'vite-plugin-compression';
export default defineConfig({
plugins: [ViteCompression()],
});
- Tree Shaking 和死代码消除(Dead Code Elimination)
Vite 默认启用 Tree Shaking 和 Rollup 的死代码消除,这意味着未使用的代码不会被打包进最终构建中,进一步减少包的体积。
- Nginx 本地部署
# 构建
npm run build
# 假设你已经安装了 nginx
# 拷贝 dist 文件夹到 nginx/html
cp -r dist/* /usr/share/nginx/html
#nginx.conf 配置
server {
listen 80;
server_name yourdomain.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
#try_files $uri /index.html 是为了支持 React Router 前端路由
}
}
进阶【后续更新】
表单系统构建 使用 React Hook Form + Yup 校验
React Query 入门 (请求缓存、分页、依赖请求、状态处理 )
动态加载 + 异步组件 (React.lazy、Suspense、代码拆分)
单元测试:Vitest、React Testing Library、覆盖率
国际化:i18next
动画:Framer Motion
微前端方案:Module Federation
SSR:Next.js 简介与迁移建议
多环境配置与部署优化 .env、Vite 插件、构建优化、CDN