本文将带你将一个使用 React + Zustand 作为前端、NestJS 作为后端的全栈项目,通过 Docker 容器化,并解决开发与生产环境中的跨域问题,让你轻松掌握项目打包发布的标准化流程。
1. 项目结构与前端跨域解决方案
项目根目录包含前端 todos 和后端 todos-backend 两个子项目。前端使用 Vite 构建,采用 Zustand 管理状态。
跨域问题与解决思路
由于浏览器同源策略,前端(开发端口 5173)直接请求后端(端口 3000)会报跨域错误。解决方案通常有两种:
- 开发环境代理 :在
vite.config.ts中配置代理,将/api请求转发到后端服务。 - 生产环境 CORS:后端 NestJS 开启 CORS,允许指定域名或全部来源。
前端请求封装与拦截器
在 request.ts 中创建 Axios 实例,统一设置 baseURL 为 /api,并利用请求拦截器注入 Token,响应拦截器统一处理数据与错误。
typescript
javascript
// src/api/request.ts
import axios from 'axios';
const service = axios.create({
baseURL: '/api', // 配合 Nginx 或开发代理
timeout: 10000,
});
// 请求拦截:注入 token
service.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截:直接返回 data,并全局捕获异常
service.interceptors.response.use(
(response) => response.data,
(error) => {
console.error('API Error:', error.message);
// 可在此处统一处理 401 跳转登录等
return Promise.reject(error);
}
);
export default service;
2. 后端 NestJS 跨域配置与接口开发
NestJS 后端在 main.ts 中启用 CORS,确保生产环境前端可正常请求接口。
typescript
javascript
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 开启跨域,根据实际部署环境限制 origin
app.enableCors({
origin: '*', // 生产环境建议替换为具体域名
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
});
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
同时,后端编写简单的 Todo 相关接口(示例略),并导出 API 供前端调用。
3. 前端状态管理(Zustand)与异步操作
使用 Zustand 创建 TodoStore,集中管理待办列表数据及操作。
typescript
typescript
// src/store/todoStore.ts
import { create } from 'zustand';
import { type Todo } from '../types/todo';
import { fetchTodos as getTodos, createTodo } from '../api/todos';
interface TodoStore {
todos: Todo[];
fetchTodos: () => Promise<void>;
addTodo: (title: string) => Promise<void>;
}
export const useTodoStore = create<TodoStore>((set, get) => ({
todos: [],
fetchTodos: async () => {
const res = await getTodos();
set({ todos: res.data }); // 注意:后端返回结构需规范
},
addTodo: async (title: string) => {
const res = await createTodo(title);
set((state) => ({ todos: [...state.todos, res.data] }));
},
}));
在前端组件 TodoList.tsx 中,调用 Store 中的方法发起请求,实现页面交互。
tsx
javascript
// src/components/TodoList.tsx
import React, { useEffect } from 'react';
import { useTodoStore } from '../store/todoStore';
const TodoList: React.FC = () => {
const { todos, fetchTodos, addTodo } = useTodoStore();
useEffect(() => {
fetchTodos();
}, []);
const handleAdd = () => {
const title = prompt('Enter todo title:');
if (title) addTodo(title);
};
return (
<div>
<h1>Todo List</h1>
<button onClick={handleAdd}>Add Todo</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
);
};
export default TodoList;
4. Docker 容器化配置与镜像构建
Dockerfile 好比一份"标准操作手册",通过指令描述应用环境,确保在任何机器上运行结果一致。
前端 Dockerfile
dockerfile
sql
# 前端 Dockerfile 示例
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 使用 nginx 提供静态文件服务(生产推荐)
FROM nginx:alpine
COPY --from=0 /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
后端 Dockerfile
dockerfile
sql
# 后端 Dockerfile 示例
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/main.js"]
构建与运行
bash
yaml
# 构建镜像
docker build -t todos-frontend .
docker build -t todos-backend .
# 运行容器(可通过 docker-compose 统一编排)
docker run -p 80:80 todos-frontend
docker run -p 3000:3000 todos-backend
实际项目中,通常使用 docker-compose.yml 将前后端、数据库、Nginx 等统一编排,实现一键部署。
5. 总结
通过本文的实践,你将掌握:
- 如何通过代理和 CORS 解决全栈项目跨域问题;
- 使用 Zustand 结合 Axios 进行高效状态管理与请求封装;
- 编写 Dockerfile 将前后端项目容器化,实现标准化部署。
容器化是现代应用交付的基石,结合 CI/CD 流程,可以极大提升开发与运维效率。快去为你的项目写一份"Dockerfile 食谱"吧!