目录
React项目结构介绍
my-app/
├── public/ # 静态资源文件夹
│ ├── index.html # HTML 模板
│ ├── favicon.ico # 网站图标
│ └── assets/ # 静态资源(图片、字体等)
├── src/
│ ├── assets/ # 存放项目中的静态资源(如图片、字体等)
│ ├── components/ # 可复用的 UI 组件
│ ├── hooks/ # 自定义 Hook
│ ├── pages/ # 页面组件(对应不同的路由)
│ ├── routes/ # 路由配置文件
│ │ └── index.js # 路由配置文件(定义页面组件的路径映射)
│ ├── services/ # 与 API 交互的服务层(如数据获取、身份验证)
│ ├── store/ # 状态管理(如 Redux 或 Context API)
│ ├── utils/ # 工具函数
│ ├── App.js # 根组件
│ ├── index.js # React 入口文件
│ └── styles/ # 全局样式或主题文件
├── .gitignore # Git 忽略文件
├── package.json # 项目信息和依赖管理
├── README.md # 项目说明文件
└── package-lock.json / yarn.lock # 锁定依赖的版本
一、Redux基础使用
1. 项目初始化
要先创建一个 React 项目,并且安装 Redux 和 React-Redux。
npx create-react-app redux-demo
cd redux-demo
npm install redux react-redux @reduxjs/toolkit
2. 定义 Slice(使用 Redux Toolkit)
在src/features/counter/counterSlice.js
文件中定义一个计数器的 slice。
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
1. reducers
的作用
在 Redux Toolkit 中,reducers
是 createSlice
函数的核心配置项,用于定义状态修改逻辑。它的主要作用是:
- 自动生成 Action creators 和 Action types:无需手动编写 action 常量和 action 创建函数。
- 简化 Redux 逻辑:允许使用 "mutating" 语法(如
state.value += 1
),底层通过 Immer 库自动转换为不可变更新。 - 集中管理状态逻辑:将 action 和 reducer 放在同一个文件中,提高代码可维护性。
counterSlice.js
文件中:
increment
、decrement
、incrementByAmount
都是action creators。- 每个函数内部的
state
参数是当前状态的副本,修改后会自动生成新的状态。
3. 配置 Store
在src/store.js
文件中配置 store。
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
4. 应用 Store 到 React
在src/index.js
文件中使用 Provider 包裹应用。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { store } from './store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
5. 在组件中使用状态
在src/App.js
组件中使用 Redux 状态。
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './features/counter/counterSlice';
function App() {
// 获取状态
const count = useSelector((state) => state.counter.value);
// 获取dispatch函数
const dispatch = useDispatch();
return (
<div className="App">
<h1>计数器: {count}</h1>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
);
}
export default App;
6. 异步操作(Thunk)
若需要进行异步操作,可使用 createAsyncThunk。
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
// 创建异步thunk
export const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId) => {
const response = await axios.get(`/api/users/${userId}`);
return response.data;
}
);
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state) => {
state.loading = 'pending';
})
.addCase(fetchUserById.fulfilled, (state, action) => {
state.loading = 'idle';
state.entities.push(action.payload);
});
},
});
export default usersSlice.reducer;
核心概念总结
- Store:它是应用状态的容器,一个应用只能有一个 store。
- Reducer:这是一个纯函数,负责处理 action 并返回新的状态。
- Action:它是一个描述状态变化的对象,通过 dispatch 来触发。
- Selector:用于从 store 中选择特定的状态。
- useSelector:这是一个 React Hook,能让组件从 store 中读取状态。
- useDispatch:这也是一个 React Hook,可让组件派发 action。
二、React路由介绍
安装 React Router
首先,你需要安装 react-router-dom
,这是 React Router 为 Web 应用提供的包。
npm install react-router-dom
1. 基础概念
- BrowserRouter: 使用 HTML5 的 history API(pushState、replaceState 和 popstate 事件)来保持 UI 同步。
- Routes: 路由容器,用于包裹多个
Route
组件。 - Route: 定义了路径与组件之间的映射关系,当 URL 匹配时显示对应的组件。
- Link: 用于创建导航链接,不会导致页面刷新。
- NavLink: 类似于
Link
,但可以添加样式以指示当前激活的导航项。 - Navigate(v6+): 提供编程式导航的能力。
- useParams, useSearchParams, useNavigate, useLocation: 这些 hooks 可以让你访问路由参数、查询参数、执行导航操作以及获取当前的位置信息。
2. 基本使用示例
在 App.jsx
中设置基础路由
// src/App.jsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="*" element={<NotFoundPage />} /> {/* 404 页面 */}
</Routes>
</Router>
);
}
export default App;
创建简单的页面组件
// src/pages/HomePage.jsx
import React from 'react';
function HomePage() {
return <h1>Home Page</h1>;
}
export default HomePage;
路由导航
1. 使用 <Link><NavLink>
组件进行导航
<Link>
是 React Router 中用于路由导航的最基础组件,它通过创建一个 <a>
标签,帮助用户在不同的路由之间进行跳转。<Link>
组件会自动拦截浏览器的默认行为,避免页面的完全刷新,保持单页应用(SPA)的特性。
<NavLink>
是 <Link>
的一个增强版,它用于自动添加激活样式,帮助你标识当前访问的页面。你可以使用 activeClassName
(React Router v5)或者 className
(React Router v6)来定义激活链接的样式。
在 React Router v6 中,
activeClassName
已被className
属性替代,className
会根据是否为当前活动路由自动添加特定的样式。
// src/components/Navigation.jsx
import React from 'react';
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><NavLink to="/about">About</NavLink></li>
</ul>
</nav>
);
}
export default Navigation;
编程式导航
1. 使用 useNavigate
实现编程式导航
在 React Router v6 中,useNavigate
是用来执行编程式导航的钩子。它返回一个函数,可以用来触发路由的跳转。你可以在事件处理函数、生命周期钩子中,或根据某些条件动态地进行路由跳转。
基本用法
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate(); // 获取导航函数
const handleClick = () => {
navigate('/about'); // 跳转到 /about 路径
};
return <button onClick={handleClick}>Go to About</button>;
}
在上面的例子中,当用户点击按钮时,navigate('/about')
会将用户导航到 /about
路由。
2. navigate
方法的参数
navigate
函数可以接收两个参数:
目标路径 (
to
): 这是跳转的目标路径,可以是相对路径或绝对路径。选项 (
options
): 这是一个可选对象,允许你配置更多导航行为。常用的选项包括:replace
:指定是否替换当前历史记录(类似浏览器的window.location.replace()
)。如果设为true
,跳转后当前历史记录将被替换,用户不能按浏览器的“后退”按钮返回。state
:你可以通过state
传递一些状态数据,跳转时携带。relative
:指定相对路径跳转。
示例:使用 replace
和 state
import { useNavigate } from 'react-router-dom';
function SubmitForm() {
const navigate = useNavigate();
const handleSubmit = () => {
// 跳转并替换历史记录
navigate('/thank-you', { replace: true, state: { fromForm: true } });
};
return <button onClick={handleSubmit}>Submit</button>;
}
在这个例子中,navigate('/thank-you', { replace: true, state: { fromForm: true } })
:
会跳转到
/thank-you
页面。通过
replace: true
来替换当前历史记录,避免用户按“后退”按钮返回到表单页。通过
state
参数将一些额外的状态数据传递给目标页面。
3. 相对路径导航
React Router v6 支持相对路径导航,允许你在同一层级的路径上执行跳转。你可以使用相对路径来避免硬编码目标路径。相对路径跳转 是一种基于当前路由上下文的路径跳转方式。它并不依赖于指定一个完整的绝对路径,而是基于当前所在的路由(父路由)计算跳转的目标路径。这种方式通常用于跳转到 当前路由的子路由 或与当前路径相关的目标路径。
路由层级示意图
/
├── /login // 登录页
├── /register // 注册页
├── /home // 首页
└── /dashboard // 仪表盘
├── /user/:id // 用户资料页面
│ ├── /user/:id/edit // 编辑资料页面
│ └── /user/:id/settings // 用户设置页面
├── /articles // 文章列表页
│ ├── /articles/new // 创建文章页面
│ └── /articles/:id/edit // 编辑文章页面
└── /publish // 发布文章页面
示例:相对路径跳转
import { useNavigate } from 'react-router-dom';
function UserProfile() {
const navigate = useNavigate();
const goToEditPage = () => {
navigate('edit'); // 相对路径跳转,跳转到 /user/:id/edit
};
return <button onClick={goToEditPage}>Edit Profile</button>;
}
在上面的例子中,navigate('edit')
是相对路径跳转,它会将用户跳转到 /user/:id/edit
,其中 :id
是当前页面的动态参数。
4. 编程式导航与条件判断
编程式导航经常与条件判断结合使用,可以根据某些业务逻辑决定是否进行跳转。例如,当用户登录状态发生变化时,或者表单验证通过后,跳转到另一个页面。
示例:根据条件判断进行导航
import { useNavigate } from 'react-router-dom';
function LoginPage() {
const navigate = useNavigate();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const handleLogin = (credentials) => {
// 假设验证通过
if (credentials.username === 'user' && credentials.password === 'password') {
setIsAuthenticated(true);
navigate('/dashboard'); // 登录成功后跳转到 dashboard 页面
} else {
alert('Invalid credentials');
}
};
return (
<div>
<button onClick={() => handleLogin({ username: 'user', password: 'password' })}>
Login
</button>
</div>
);
}
在这个例子中,用户点击登录按钮后,handleLogin
函数会根据条件判断登录是否成功。如果成功,调用 navigate('/dashboard')
来跳转到 /dashboard
页面。
5. 返回上一页
通过 navigate()
,你不仅可以跳转到指定的页面,还可以通过传递负数的参数来模拟浏览器的“后退”功能。
示例:返回上一页
import { useNavigate } from 'react-router-dom';
function GoBackButton() {
const navigate = useNavigate();
const goBack = () => {
navigate(-1); // 返回上一页,相当于浏览器的 back 按钮
};
return <button onClick={goBack}>Go Back</button>;
}
在上面的例子中,navigate(-1)
会使用户返回到历史记录栈中的上一页,相当于浏览器的 "Back" 按钮。
6. 跳转到首页或其他默认页面
navigate()
还可以用来重定向用户到首页或其他默认页面。
示例:跳转到首页
import { useNavigate } from 'react-router-dom';
function LogoutButton() {
const navigate = useNavigate();
const handleLogout = () => {
// 清除用户登录信息
localStorage.removeItem('authToken');
navigate('/'); // 登出后跳转到首页
};
return <button onClick={handleLogout}>Logout</button>;
}
在这个例子中,用户点击登出按钮后,navigate('/')
会将用户重定向到首页。
7. 编程式导航与路由守卫
在某些情况下,你可能希望在进行导航之前检查某些条件,如用户是否已经登录,或者是否有权限访问某些页面。如果没有满足条件,可以直接跳转到登录页或错误页面。
示例:路由守卫与编程式导航
import { useNavigate } from 'react-router-dom';
function ProtectedPage() {
const navigate = useNavigate();
const isAuthenticated = localStorage.getItem('authToken');
if (!isAuthenticated) {
navigate('/login'); // 如果用户未认证,则重定向到登录页
return null;
}
return <div>Protected Content</div>;
}
在上面的例子中,ProtectedPage
组件会在渲染之前检查用户是否已认证。如果没有认证,调用 navigate('/login')
重定向用户到登录页面。
总结
编程式导航在 React Router 中非常重要,它允许你在应用中通过代码控制路由跳转,适用于动态场景或根据条件触发跳转。常见的编程式导航方式如下:
useNavigate
钩子:通过navigate()
函数实现导航。跳转到指定路径:
navigate('/path')
。替换历史记录:
navigate('/path', { replace: true })
。传递状态数据:
navigate('/path', { state: data })
。相对路径跳转:
navigate('sub-path')
。返回上一页:
navigate(-1)
。
编程式导航的常见应用场景包括表单提交后跳转、登录验证、路由守卫、动态跳转等。