实战项目:从零搭建 React 项目
本教程将带你从零开始,使用 Webpack 搭建一个完整的 React + TypeScript 项目。完成本教程后,你将拥有一个可以立即使用的 React 项目模板。
预计时间:30-40 分钟
📋 学习目标
完成本教程后,你将能够:
- ✅ 使用 Webpack 搭建 React 项目
- ✅ 配置 TypeScript 支持
- ✅ 配置开发环境和生产环境
- ✅ 使用 CSS Modules 和 Sass
- ✅ 配置热模块替换(HMR)
- ✅ 优化生产环境构建
第一步:项目初始化
1. 创建项目目录
mkdir react-webpack-app
cd react-webpack-app2. 初始化 package.json
npm init -y3. 安装 React 依赖
# 安装 React
npm install react react-dom
# 安装类型定义(TypeScript)
npm install --save-dev @types/react @types/react-dom4. 安装 Webpack 相关依赖
# Webpack 核心
npm install --save-dev webpack webpack-cli webpack-dev-server
# Webpack 插件
npm install --save-dev html-webpack-plugin
npm install --save-dev mini-css-extract-plugin
npm install --save-dev css-minimizer-webpack-plugin
npm install --save-dev terser-webpack-plugin
npm install --save-dev clean-webpack-plugin
# 配置合并工具
npm install --save-dev webpack-merge5. 安装 Babel 相关依赖
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
npm install --save-dev babel-loader
npm install --save-dev @pmmmwh/react-refresh-webpack-plugin6. 安装 TypeScript
npm install --save-dev typescript7. 安装样式处理依赖
npm install --save-dev style-loader css-loader sass-loader sass
npm install --save-dev postcss postcss-loader autoprefixer第二步:创建项目结构
创建以下目录结构:
react-webpack-app/
├── public/
│ └── index.html # HTML 模板
├── src/
│ ├── index.tsx # 入口文件
│ ├── App.tsx # 根组件
│ ├── components/ # 组件目录
│ │ └── Hello.tsx
│ ├── styles/ # 样式目录
│ │ └── index.scss
│ └── assets/ # 静态资源
├── dist/ # 输出目录(自动生成)
├── tsconfig.json # TypeScript 配置
├── webpack.common.js # 公共配置
├── webpack.dev.js # 开发环境配置
├── webpack.prod.js # 生产环境配置
├── webpack.config.js # 主配置文件
└── package.json
第三步:创建源文件
1. 创建 HTML 模板
public/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React Webpack App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>2. 创建入口文件
src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './styles/index.scss'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)3. 创建根组件
src/App.tsx
import React, { useState } from 'react'
import Hello from './components/Hello'
import styles from './App.module.scss'
const App: React.FC = () => {
const [count, setCount] = useState(0)
return (
<div className={styles.app}>
<h1>React + Webpack + TypeScript</h1>
<Hello name="Webpack" />
<div className={styles.counter}>
<button onClick={() => setCount(count - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
</div>
)
}
export default App4. 创建子组件
src/components/Hello.tsx
import React from 'react'
interface HelloProps {
name: string
}
const Hello: React.FC<HelloProps> = ({ name }) => {
return <h2>Hello, {name}!</h2>
}
export default Hello5. 创建样式文件
src/styles/index.scss
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#root {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}src/App.module.scss
.app {
text-align: center;
padding: 40px;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
h1 {
color: #333;
margin-bottom: 20px;
}
}
.counter {
margin-top: 30px;
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
button {
width: 40px;
height: 40px;
font-size: 20px;
border: 2px solid #667eea;
background: white;
color: #667eea;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #667eea;
color: white;
}
}
span {
font-size: 24px;
font-weight: bold;
min-width: 50px;
}
}第四步:配置 TypeScript
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"jsx": "react-jsx",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}第五步:配置 Webpack
1. 公共配置
webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
entry: './src/index.tsx',
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
]
}
}
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext]'
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html'
})
]
}2. 开发环境配置
webpack.dev.js
const path = require('path')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = {
mode: 'development',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/'
},
devtool: 'eval-source-map',
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
],
plugins: [
'react-refresh/babel' // HMR 支持
]
}
}
},
{
test: /\.(css|scss|sass)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
},
importLoaders: 2
}
},
'postcss-loader',
'sass-loader'
]
}
]
},
plugins: [
new ReactRefreshWebpackPlugin() // React 热更新
],
devServer: {
static: {
directory: path.join(__dirname, 'public')
},
port: 3000,
hot: true,
open: true,
historyApiFallback: true, // SPA 路由支持
compress: true
}
}3. 生产环境配置
webpack.prod.js
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
publicPath: '/',
clean: true
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(css|scss|sass)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
},
importLoaders: 2
}
},
'postcss-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css'
})
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true
}
}
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single'
}
}4. 主配置文件
webpack.config.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = (env, argv) => {
if (argv.mode === 'production') {
return merge(common, require('./webpack.prod.js'))
}
return merge(common, require('./webpack.dev.js'))
}第六步:配置 PostCSS
postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}第七步:配置 package.json 脚本
package.json
{
"name": "react-webpack-app",
"version": "1.0.0",
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production",
"build:analyze": "webpack-bundle-analyzer dist/stats.json"
}
}第八步:运行项目
开发模式
npm run dev浏览器会自动打开 http://localhost:3000,你可以看到你的 React 应用。
生产构建
npm run build构建完成后,dist 目录会包含优化后的文件。
项目特性
✅ TypeScript 支持 - 完整的类型检查
✅ CSS Modules - 样式模块化
✅ Sass 支持 - 使用 Sass 预处理器
✅ 热模块替换 - 开发时自动刷新
✅ 代码分割 - 自动分离第三方库
✅ 生产优化 - 代码压缩和优化
✅ 路径别名 - 使用 @ 代替 src
常见问题
1. HMR 不工作
确保安装了 @pmmmwh/react-refresh-webpack-plugin 并在开发配置中启用。
2. TypeScript 类型错误
检查 tsconfig.json 配置,确保 jsx 设置为 "react-jsx"。
3. CSS Modules 不生效
确保 css-loader 的 modules 选项已启用。
下一步
总结
恭喜!你已经成功搭建了一个完整的 React + TypeScript + Webpack 项目!
关键点:
- 使用
webpack-merge分离开发和生产配置 - 配置 Babel 转译 TypeScript 和 React
- 使用 CSS Modules 实现样式隔离
- 配置 HMR 提升开发体验
- 优化生产环境构建
继续学习,探索更多 Webpack 的强大功能!