引言
在当今快速发展的互联网时代,前端开发已经成为构建现代Web应用的核心环节。从简单的静态页面到复杂的单页应用(SPA),前端技术栈不断演进,开发流程也日益规范化。本文将为您详细解析一个完整的前端项目从零搭建到上线的全流程,涵盖项目初始化、技术选型、开发实践、测试优化、部署上线等关键环节。无论您是初学者还是有一定经验的开发者,都能从中获得实用的指导。
一、项目规划与技术选型
1.1 明确项目需求
在开始编码之前,首先需要明确项目的目标和需求。例如,我们要开发一个任务管理应用,主要功能包括:
- 用户注册与登录
- 任务的创建、编辑、删除和标记完成
- 任务分类和搜索
- 数据持久化(本地存储或后端API)
1.2 技术选型
根据项目需求,选择合适的技术栈。以下是一个常见的现代前端技术组合:
- 框架/库:React(或Vue、Angular)
- 状态管理:Redux(或MobX、Vuex)
- 路由:React Router
- 构建工具:Webpack(或Vite)
- 样式方案:CSS Modules 或 Tailwind CSS
- HTTP客户端:Axios
- 测试框架:Jest + React Testing Library
- 代码规范:ESLint + Prettier
- 版本控制:Git
选择理由:
- React 生态成熟,社区活跃,适合构建大型应用。
- Redux 提供可预测的状态管理,适合复杂应用。
- Webpack 是模块化打包的行业标准,支持代码分割和懒加载。
- Jest 是 React 官方推荐的测试框架,与 React Testing Library 配合使用可以编写可靠的测试。
二、项目初始化
2.1 环境准备
确保本地开发环境已安装:
- Node.js(推荐 LTS 版本)
- npm 或 yarn(本文使用 yarn)
- Git
2.2 创建项目
使用 Create React App(CRA)快速搭建项目基础结构。CRA 预配置了 Webpack、Babel、Jest 等工具,无需手动配置。
npx create-react-app task-manager
cd task-manager
2.3 项目结构规划
一个清晰的项目结构有助于维护和扩展。建议的目录结构如下:
task-manager/
├── public/ # 静态资源
├── src/
│ ├── components/ # 可复用组件
│ ├── pages/ # 页面级组件
│ ├── store/ # Redux 相关
│ ├── services/ # API 服务
│ ├── utils/ # 工具函数
│ ├── styles/ # 全局样式
│ ├── App.js # 根组件
│ └── index.js # 入口文件
├── .env # 环境变量
├── .eslintrc.js # ESLint 配置
├── .prettierrc # Prettier 配置
├── package.json
└── README.md
2.4 配置代码规范
安装 ESLint 和 Prettier,确保代码风格一致。
yarn add --dev eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react
创建 .eslintrc.js:
module.exports = {
extends: [
'react-app',
'react-app/jest',
'plugin:prettier/recommended',
],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
},
};
创建 .prettierrc:
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 80
}
在 package.json 中添加脚本:
{
"scripts": {
"lint": "eslint src/**/*.js",
"format": "prettier --write src/**/*.js"
}
}
三、核心功能开发
3.1 状态管理(Redux)
安装 Redux 和 React-Redux:
yarn add redux react-redux @reduxjs/toolkit
创建 Redux store。使用 Redux Toolkit 简化 Redux 的使用。
store/index.js:
import { configureStore } from '@reduxjs/toolkit';
import tasksReducer from './tasksSlice';
export const store = configureStore({
reducer: {
tasks: tasksReducer,
},
});
store/tasksSlice.js:
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
tasks: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
};
const tasksSlice = createSlice({
name: 'tasks',
initialState,
reducers: {
addTask: (state, action) => {
state.tasks.push(action.payload);
},
toggleTask: (state, action) => {
const task = state.tasks.find(t => t.id === action.payload);
if (task) {
task.completed = !task.completed;
}
},
deleteTask: (state, action) => {
state.tasks = state.tasks.filter(t => t.id !== action.payload);
},
},
});
export const { addTask, toggleTask, deleteTask } = tasksSlice.actions;
export default tasksSlice.reducer;
在 index.js 中提供 store:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
3.2 组件开发
3.2.1 任务列表组件
components/TaskList.js:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { toggleTask, deleteTask } from '../store/tasksSlice';
const TaskList = () => {
const tasks = useSelector(state => state.tasks.tasks);
const dispatch = useDispatch();
const handleToggle = (id) => {
dispatch(toggleTask(id));
};
const handleDelete = (id) => {
dispatch(deleteTask(id));
};
return (
<ul className="task-list">
{tasks.map(task => (
<li key={task.id} className={task.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={task.completed}
onChange={() => handleToggle(task.id)}
/>
<span>{task.title}</span>
<button onClick={() => handleDelete(task.id)}>删除</button>
</li>
))}
</ul>
);
};
export default TaskList;
3.2.2 任务表单组件
components/TaskForm.js:
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTask } from '../store/tasksSlice';
const TaskForm = () => {
const [title, setTitle] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (title.trim()) {
const newTask = {
id: Date.now(),
title: title.trim(),
completed: false,
};
dispatch(addTask(newTask));
setTitle('');
}
};
return (
<form onSubmit={handleSubmit} className="task-form">
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="输入任务标题"
/>
<button type="submit">添加任务</button>
</form>
);
};
export default TaskForm;
3.3 路由管理
安装 React Router:
yarn add react-router-dom
配置路由。假设我们有两个页面:首页和关于页面。
App.js:
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Navbar from './components/Navbar';
function App() {
return (
<Router>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
}
export default App;
pages/Home.js:
import React from 'react';
import TaskForm from '../components/TaskForm';
import TaskList from '../components/TaskList';
const Home = () => {
return (
<div className="home-page">
<h1>任务管理器</h1>
<TaskForm />
<TaskList />
</div>
);
};
export default Home;
3.4 API 服务集成
假设后端提供了 RESTful API,我们需要集成数据持久化。使用 Axios 发送 HTTP 请求。
安装 Axios:
yarn add axios
创建 API 服务:
services/api.js:
import axios from 'axios';
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:3000/api';
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器,添加认证 token
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器,处理错误
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
const { status, data } = error.response;
console.error(`API Error: ${status} - ${data.message}`);
// 可以在这里添加全局错误处理,比如显示通知
}
return Promise.reject(error);
}
);
export const getTasks = () => api.get('/tasks');
export const createTask = (task) => api.post('/tasks', task);
export const updateTask = (id, task) => api.put(`/tasks/${id}`, task);
export const deleteTask = (id) => api.delete(`/tasks/${id}`);
更新 Redux 以使用 API:
store/tasksSlice.js(更新部分):
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { getTasks, createTask, updateTask, deleteTask } from '../services/api';
// 异步 action
export const fetchTasks = createAsyncThunk('tasks/fetchTasks', async () => {
const response = await getTasks();
return response.data;
});
export const addTaskAsync = createAsyncThunk('tasks/addTask', async (task) => {
const response = await createTask(task);
return response.data;
});
export const toggleTaskAsync = createAsyncThunk('tasks/toggleTask', async (id, task) => {
const response = await updateTask(id, task);
return response.data;
});
export const deleteTaskAsync = createAsyncThunk('tasks/deleteTask', async (id) => {
await deleteTask(id);
return id;
});
const tasksSlice = createSlice({
name: 'tasks',
initialState,
reducers: {
// 同步 reducers...
},
extraReducers: (builder) => {
builder
.addCase(fetchTasks.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchTasks.fulfilled, (state, action) => {
state.status = 'succeeded';
state.tasks = action.payload;
})
.addCase(fetchTasks.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
})
.addCase(addTaskAsync.fulfilled, (state, action) => {
state.tasks.push(action.payload);
})
.addCase(toggleTaskAsync.fulfilled, (state, action) => {
const index = state.tasks.findIndex(t => t.id === action.payload.id);
if (index !== -1) {
state.tasks[index] = action.payload;
}
})
.addCase(deleteTaskAsync.fulfilled, (state, action) => {
state.tasks = state.tasks.filter(t => t.id !== action.payload);
});
},
});
export default tasksSlice.reducer;
在组件中使用异步 action:
components/TaskList.js(更新部分):
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchTasks, toggleTaskAsync, deleteTaskAsync } from '../store/tasksSlice';
const TaskList = () => {
const tasks = useSelector(state => state.tasks.tasks);
const status = useSelector(state => state.tasks.status);
const dispatch = useDispatch();
useEffect(() => {
if (status === 'idle') {
dispatch(fetchTasks());
}
}, [status, dispatch]);
const handleToggle = (id) => {
const task = tasks.find(t => t.id === id);
if (task) {
const updatedTask = { ...task, completed: !task.completed };
dispatch(toggleTaskAsync(id, updatedTask));
}
};
const handleDelete = (id) => {
dispatch(deleteTaskAsync(id));
};
if (status === 'loading') {
return <div>加载中...</div>;
}
if (status === 'failed') {
return <div>加载失败,请重试</div>;
}
return (
<ul className="task-list">
{tasks.map(task => (
<li key={task.id} className={task.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={task.completed}
onChange={() => handleToggle(task.id)}
/>
<span>{task.title}</span>
<button onClick={() => handleDelete(task.id)}>删除</button>
</li>
))}
</ul>
);
};
export default TaskList;
四、样式与UI优化
4.1 使用 Tailwind CSS
Tailwind CSS 是一个实用优先的 CSS 框架,可以快速构建现代 UI。
安装 Tailwind CSS:
yarn add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
配置 tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
在 src/index.css 中添加 Tailwind 指令:
@tailwind base;
@tailwind components;
@tailwind utilities;
现在,我们可以使用 Tailwind 的类来美化组件。例如,更新 TaskList 组件:
// 在组件中使用 Tailwind 类
return (
<ul className="space-y-3 mt-4">
{tasks.map(task => (
<li key={task.id} className={`flex items-center p-3 rounded-lg shadow-sm ${
task.completed ? 'bg-green-100 border-green-300' : 'bg-white border-gray-200'
} border`}>
<input
type="checkbox"
checked={task.completed}
onChange={() => handleToggle(task.id)}
className="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
/>
<span className={`flex-1 ml-3 ${task.completed ? 'line-through text-gray-500' : 'text-gray-900'}`}>
{task.title}
</span>
<button
onClick={() => handleDelete(task.id)}
className="ml-2 px-3 py-1 text-sm text-red-600 hover:text-red-800 hover:bg-red-50 rounded"
>
删除
</button>
</li>
))}
</ul>
);
4.2 响应式设计
确保应用在不同设备上都能良好显示。使用 Tailwind 的响应式前缀。
例如,调整导航栏:
components/Navbar.js:
import React from 'react';
import { Link } from 'react-router-dom';
const Navbar = () => {
return (
<nav className="bg-white shadow-md">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center">
<Link to="/" className="text-xl font-bold text-gray-800">
Task Manager
</Link>
</div>
<div className="flex items-center space-x-4">
<Link to="/" className="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
首页
</Link>
<Link to="/about" className="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
关于
</Link>
</div>
</div>
</div>
</nav>
);
};
export default Navbar;
五、测试
5.1 单元测试
使用 Jest 和 React Testing Library 编写组件测试。
安装测试依赖(CRA 已内置):
yarn add --dev @testing-library/react @testing-library/jest-dom @testing-library/user-event
编写 TaskForm 组件的测试:
components/TaskForm.test.js:
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import TaskForm from './TaskForm';
import tasksReducer from '../store/tasksSlice';
// 创建一个测试用的 store
const renderWithProviders = (
ui,
{
preloadedState = {},
store = configureStore({
reducer: { tasks: tasksReducer },
preloadedState,
}),
...renderOptions
} = {}
) => {
const Wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
};
describe('TaskForm', () => {
it('renders input and button', () => {
renderWithProviders(<TaskForm />);
expect(screen.getByPlaceholderText('输入任务标题')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '添加任务' })).toBeInTheDocument();
});
it('dispatches addTask action on form submit', () => {
const { store } = renderWithProviders(<TaskForm />);
const input = screen.getByPlaceholderText('输入任务标题');
const button = screen.getByRole('button', { name: '添加任务' });
fireEvent.change(input, { target: { value: 'New Task' } });
fireEvent.click(button);
const state = store.getState();
expect(state.tasks.tasks).toHaveLength(1);
expect(state.tasks.tasks[0].title).toBe('New Task');
});
it('does not dispatch action for empty input', () => {
const { store } = renderWithProviders(<TaskForm />);
const button = screen.getByRole('button', { name: '添加任务' });
fireEvent.click(button);
const state = store.getState();
expect(state.tasks.tasks).toHaveLength(0);
});
});
5.2 集成测试
编写集成测试,测试多个组件的交互。
pages/Home.test.js:
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import Home from './Home';
import tasksReducer from '../store/tasksSlice';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
// Mock API 服务
const server = setupServer(
rest.get('/api/tasks', (req, res, ctx) => {
return res(
ctx.json([
{ id: 1, title: 'Task 1', completed: false },
{ id: 2, title: 'Task 2', completed: true },
])
);
}),
rest.post('/api/tasks', (req, res, ctx) => {
return res(ctx.json({ id: 3, ...req.body }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
const renderWithProviders = (ui) => {
const store = configureStore({
reducer: { tasks: tasksReducer },
});
return render(<Provider store={store}>{ui}</Provider>);
};
describe('Home Page Integration', () => {
it('loads and displays tasks', async () => {
renderWithProviders(<Home />);
expect(screen.getByText('加载中...')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('Task 1')).toBeInTheDocument();
expect(screen.getByText('Task 2')).toBeInTheDocument();
});
});
it('adds a new task', async () => {
renderWithProviders(<Home />);
await waitFor(() => {
expect(screen.getByText('Task 1')).toBeInTheDocument();
});
const input = screen.getByPlaceholderText('输入任务标题');
const button = screen.getByRole('button', { name: '添加任务' });
fireEvent.change(input, { target: { value: 'New Task' } });
fireEvent.click(button);
await waitFor(() => {
expect(screen.getByText('New Task')).toBeInTheDocument();
});
});
});
六、性能优化
6.1 代码分割与懒加载
使用 React 的 React.lazy 和 Suspense 实现路由级别的代码分割。
App.js(更新部分):
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar';
// 懒加载页面组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
function App() {
return (
<Router>
<Navbar />
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
}
export default App;
6.2 使用 React.memo 避免不必要的重渲染
对于纯展示组件,使用 React.memo 进行优化。
components/TaskItem.js:
import React from 'react';
import { useDispatch } from 'react-redux';
import { toggleTaskAsync, deleteTaskAsync } from '../store/tasksSlice';
const TaskItem = React.memo(({ task }) => {
const dispatch = useDispatch();
const handleToggle = () => {
const updatedTask = { ...task, completed: !task.completed };
dispatch(toggleTaskAsync(task.id, updatedTask));
};
const handleDelete = () => {
dispatch(deleteTaskAsync(task.id));
};
return (
<li className={`flex items-center p-3 rounded-lg shadow-sm ${
task.completed ? 'bg-green-100 border-green-300' : 'bg-white border-gray-200'
} border`}>
<input
type="checkbox"
checked={task.completed}
onChange={handleToggle}
className="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
/>
<span className={`flex-1 ml-3 ${task.completed ? 'line-through text-gray-500' : 'text-gray-900'}`}>
{task.title}
</span>
<button
onClick={handleDelete}
className="ml-2 px-3 py-1 text-sm text-red-600 hover:text-red-800 hover:bg-red-50 rounded"
>
删除
</button>
</li>
);
});
export default TaskItem;
然后在 TaskList 中使用 TaskItem:
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchTasks } from '../store/tasksSlice';
import TaskItem from './TaskItem';
const TaskList = () => {
const tasks = useSelector(state => state.tasks.tasks);
const status = useSelector(state => state.tasks.status);
const dispatch = useDispatch();
useEffect(() => {
if (status === 'idle') {
dispatch(fetchTasks());
}
}, [status, dispatch]);
if (status === 'loading') {
return <div>加载中...</div>;
}
if (status === 'failed') {
return <div>加载失败,请重试</div>;
}
return (
<ul className="space-y-3 mt-4">
{tasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
</ul>
);
};
export default TaskList;
6.3 使用 Webpack Bundle Analyzer 分析打包体积
安装 Webpack Bundle Analyzer:
yarn add --dev webpack-bundle-analyzer
在 package.json 中添加脚本:
{
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"build": "react-scripts build"
}
}
运行 yarn analyze 可以查看打包后的文件大小,帮助识别和优化大体积依赖。
七、测试与质量保证
7.1 持续集成(CI)
使用 GitHub Actions 或 GitLab CI 自动化测试和构建。
GitHub Actions 示例(.github/workflows/ci.yml):
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run lint
run: yarn lint
- name: Run tests
run: yarn test --watchAll=false
- name: Build
run: yarn build
7.2 代码审查
使用 GitHub Pull Requests 进行代码审查。确保:
- 代码符合规范
- 测试覆盖
- 文档更新
八、部署上线
8.1 构建生产版本
运行构建命令生成优化后的静态文件:
yarn build
这将生成 build 目录,包含压缩、优化后的 HTML、CSS 和 JS 文件。
8.2 选择部署平台
8.2.1 部署到静态托管服务(如 Vercel、Netlify)
Vercel 部署:
- 安装 Vercel CLI:
npm i -g vercel - 登录:
vercel login - 部署:
vercel --prod
Netlify 部署:
- 登录 Netlify 网站,连接 GitHub 仓库。
- 设置构建命令:
yarn build - 设置发布目录:
build - 点击部署。
8.2.2 部署到云服务器(如 AWS S3 + CloudFront)
- 创建 S3 存储桶,启用静态网站托管。
- 上传
build目录中的文件。 - 配置 CloudFront 分发,指向 S3 存储桶。
- 设置自定义域名(可选)。
8.3 环境变量管理
在部署时,可能需要不同的环境变量(如 API 地址)。使用 .env.production 文件:
REACT_APP_API_BASE_URL=https://api.yourdomain.com
在构建时,CRA 会自动使用 .env.production 中的变量。
8.4 监控与错误追踪
集成错误监控服务,如 Sentry。
安装 Sentry:
yarn add @sentry/react @sentry/tracing
在 index.js 中初始化:
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
Sentry.init({
dsn: 'https://your-sentry-dsn@sentry.io/project-id',
integrations: [new BrowserTracing()],
tracesSampleRate: 1.0,
});
九、维护与迭代
9.1 版本管理
使用 Git 进行版本控制。遵循语义化版本(SemVer):
- 主版本号:重大变更
- 次版本号:向后兼容的功能新增
- 修订号:向后兼容的问题修复
9.2 依赖更新
定期更新依赖以获取安全修复和新功能。使用 yarn upgrade-interactive 或 npm outdated。
9.3 性能监控
使用 Lighthouse 或 Web Vitals 监控性能指标。
在 CI 中集成 Lighthouse:
- name: Run Lighthouse
run: |
npm install -g lighthouse
lighthouse http://localhost:3000 --output=json --output-path=./lighthouse-report.json
十、总结
本文详细解析了前端项目从零搭建到上线的全流程,包括:
- 项目规划与技术选型:明确需求,选择合适的技术栈。
- 项目初始化:环境准备、项目结构、代码规范配置。
- 核心功能开发:状态管理、组件开发、路由、API 集成。
- 样式与UI优化:使用 Tailwind CSS 和响应式设计。
- 测试:单元测试和集成测试。
- 性能优化:代码分割、React.memo、打包分析。
- 质量保证:CI/CD、代码审查。
- 部署上线:构建、选择平台、环境变量、监控。
- 维护与迭代:版本管理、依赖更新、性能监控。
通过遵循这些步骤,您可以构建一个高质量、可维护、可扩展的前端应用。记住,前端开发是一个持续学习和优化的过程,不断实践和总结经验是提升技能的关键。
附录:常用命令速查
# 创建项目
npx create-react-app my-app
# 安装依赖
yarn add package-name
# 开发服务器
yarn start
# 运行测试
yarn test
# 构建生产版本
yarn build
# 代码检查
yarn lint
# 代码格式化
yarn format
# 分析打包体积
yarn analyze
希望这份指南能帮助您顺利开展前端项目开发!
