webpack5+redux/tookit+react-router-dom@6搭建react项目
前言
在工作中,大部分人的开发工作差不多都是基于现有项目的迭代优化,独自从0搭建一个项目的次数很少,为了更好的把控项目,我们需要了解一个完整的项目需要哪些构成和知道如何搭建一个项目;这里基于webpack5+redux/tookit+react-router-dom@6 从0搭建一个React项目,希望对大家搭建项目有所帮助。
初始化项目
1、创建一个项目文件夹,进入根目录 yarn init -y 生成 package.json
yarn init -y
2、安装webpack 打包工具
yarn add webpack webpack-cli -D
3、根据需要构建自己项目的结构
├─src
| ├─api.js
| ├─common.js
| ├─index.html
| ├─index.js // 主入口
| ├─router.js // 路由配置文件
| ├─server.js // 请求封装
| ├─store.js // 全局数据管理文件
| ├─utils.js
| ├─pages // 路由目录
| | ├─route2
| | | └index.js
| | ├─route1
| | | ├─index.js // 入口
| | | ├─index.less // 样式
| | | └routeSlice.js // 数据管理
| | ├─main
| | | ├─index.js
| | | └mainSlice.js
| ├─images // 图片资源
| | └favicon.png
| ├─components // 公共组件
| | └withRouter.js
├─.babelrc
├─.gitignore
├─env.development.js // 开发环境配置
├─env.production.js // 生产环境配置
├─env.test.js // 测试环境配置
├─package.json
├─postcss.config.js // 布局自适应配置
├─README.md
├─webpack.config.js
├─yarn.lock
项目核心功能的构建:(项目依赖管理、页面路由管理、数据管理、权限控制等),废话不多说,上代码
1、项目依赖管理
webpack.config.js
const path = require('path')
const webpack = require('webpack')
const chalk = require('chalk')
const TerserPlugin = require('terser-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const envDevelopment = require('./env.development')
const envProduction = require('./env.production')
const envTest = require('./env.test')
const env = process.env.NODE_ENV === 'production' ? envProduction : process.env.NODE_ENV === 'test' ? envTest : envDevelopment
module.exports = {
mode: ['test', 'production'].includes(process.env.NODE_ENV) ? 'production' : 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, env.OUT_PUT_DIR),
filename: 'assets/[name].[hash].js',
clean: true,
publicPath: ['test', 'production'].includes(process.env.NODE_ENV) ? '/projectName/assets/' : '/'
},
performance: {
hints: 'warning', // 枚举 false关闭
maxEntrypointSize: 100000000, // 最大入口文件大小
maxAssetSize: 100000000, // 最大资源文件大小
assetFilter: function (assetFilename) { // 只给出js文件的性能提示
return assetFilename.endsWith('.js')
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
},
enforceExtension: false,
extensions: ['.js', '.jsx', '.json']
},
module: {
rules: [
{
test: /.(js|jsx)$/,
use: ['babel-loader'],
resolve: {
fullySpecified: false
}
},
{
test: /.(png|jpe?g|svg|gif)$/,
type: 'asset/resource',
generator: {
filename: 'assets/[contenthash][ext][query]'
}
},
{
test: /.(eot|ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'assets/[hash][ext][query]'
}
},
{
test: /.(le|c)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
'css-loader',
'postcss-loader',
{
loader: 'less-loader',
options: {
lessOptions: { javascriptEnabled: true }
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: '*****', // 自定义
template: path.resolve(__dirname, './src/index.html'),
filename: 'index.html'
}),
new CleanWebpackPlugin(), // 清理打包文件
new FriendlyErrorsWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'assets/[name].css'
}),
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
}),
new webpack.DefinePlugin({
'process.env.REQUEST_HOST': JSON.stringify(env.REQUEST_HOST),
'process.env.COMPILE_MODE': JSON.stringify(env.COMPILE_MODE),
'process.env.PUBLIC_PATH': JSON.stringify(env.PUBLIC_PATH)
})
],
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
extractComments: false,
terserOptions: {
keep_classnames: true,
keep_fnames: true,
sourceMap: true
}
}),
new CssMinimizerPlugin({
parallel: true
})
]
},
devtool: process.env.NODE_ENV === 'production' ? 'nosources-source-map' : 'inline-source-map',
devServer: {
proxy: {
'/api': {
target: '*******', // 相关环境地址
changeOrigin: true
}
},
// 当使用[HTML5 History API]时,任意的`404`响应被替代为`index.html`
historyApiFallback: true,
open: true, // 自动打开浏览器
// 默认为true
hot: true,
// 是否开启代码压缩
compress: true,
// 启动的端口
port: 9000
}
}
2、路由管理
router.js
import React from 'react'
import { BrowserRouter, useRoutes } from 'react-router-dom'
import { SpinLoading } from 'antd-mobile'
/** 路由懒加载 */
const lazyLoad = (path) => {
const Element = React.lazy(() => import(`@/pages/${path}`))
return (
<React.Suspense fallback={
<SpinLoading style={{ '--size': '32px', margin: '50px auto' }} /> // 组件加载前loading
}>
<Element />
</React.Suspense>
)
}
const routes = [
{
path: '/',
element: lazyLoad('main'),
children: [
{
index: true, // 默认路径
element: lazyLoad('routeOne')
},
{
path: 'routeTwo',
element: lazyLoad('routeTwo')
}
]
}
]
/** 使用useRoutes创建路由 */
const RouterConfig = () => {
const element = useRoutes(routes)
return element
}
/** 根据目录结构生成路由 */
export const CreateRouter = () => {
return (
<BrowserRouter>
<RouterConfig />
</BrowserRouter>
)
}
main/index.js
import React, { useEffect } from 'react'
import { initDingTalk } from '@/utils'
import { Outlet } from 'react-router-dom'
import withRouter from '@/components/withRouter'
import { saveToken, getLoginInfo } from './mainSlice'
const Main = (props) => {
const {
dispatch,
getStore
} = props
const token = getStore(state => state.main.token)
useEffect(() => {
if (!token) {
initDingTalk().then(token => {
dispatch(saveToken(token))
}).catch(e => {
console.log(e)
})
} else {
dispatch(getLoginInfo())
}
}, [token])
return !!token && <div><Outlet /></div>
}
export default withRouter(Main)
route1/index.js
import React from 'react'
import withRouter from '@/components/withRouter'
const RouteOne = props => {
const {
router,
getStore
} = props
const { navigate } = router
const goToPage = () => navigate('/routeTwo')
const token = getStore((state) => state.main.token)
console.log(token)
return (
<div onClick={() => goToPage()}>
路由1
</div>
)
}
export default withRouter(RouteOne)
3、数据管理:
main/mainSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import APIS from '../../api'
import getJSON from '../../server'
import { JSON_GET_OPTIONS, TEST_TOKEN } from '../../common'
const initialState = {
loading: false,
userInfo: {},
token: process.env.COMPILE_MODE === 'dev' ? TEST_TOKEN : '',
error: null
}
export const getLoginInfo = createAsyncThunk(
'main/getLoginInfo',
async (params = {}) => {
const data = await getJSON(APIS.GET_LOGIN_INFO, params, JSON_GET_OPTIONS)
return data
}
)
const mainSlice = createSlice({
name: 'main',
initialState,
reducers: {
saveToken: (state, action) => {
sessionStorage.setItem('auth.token', action.payload)
state.token = action.payload
}
},
extraReducers: {
[getLoginInfo.pending.type]: (state) => {
state.loading = true
},
[getLoginInfo.fulfilled.type]: (state, action) => {
const data = action.payload
state.loading = false
state.userInfo = data
},
[getLoginInfo.rejected.type]: (state, action) => {
state.loading = false
state.error = action.payload
}
}
})
export const { saveToken } = mainSlice.actions
export default mainSlice.reducer
store.js
import { configureStore } from '@reduxjs/toolkit'
import mainReducer from '../src/pages/main/mainSlice'
import route1Reducer from './pages/routeOne/routeSlice'
export default configureStore({
reducer: {
main: mainReducer,
route1: route1Reducer
}
})
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import VConsole from 'vconsole'
import LoginMock from '@cmschina-oa/login-mock'
import getJSON from '@cmschina-oa/dataservice'
import { CreateRouter } from './router'
import store from './store'
import 'antd-mobile/es/global/global.css'
// import 'antd-mobile/es/global/theme.css'
if (process.env.COMPILE_MODE !== 'prod') {
/* eslint-disable */
const vConsole = new VConsole()
}
if (process.env.COMPILE_MODE === 'dev') {
LoginMock()
}
getJSON.initHttpDTO({
JsonDTO: {
flag: 'code',
message: 'message',
data: 'data',
},
successFlag: [1],
failFlag: [-99, 0],
extraFlagMissions: [{
flag: -1,
mission: (code, res) => {
Toast.show({
content: res?.message,
})
setTimeout(() => {
sessionStorage.removeItem('auth.token')
// 其它处理
}, 1000)
},
}],
badStatus: [
{
status: 401,
callback() {},
},
],
})
const App = () => (
/** 全局注入store */
<Provider store={store}>
<CreateRouter />
</Provider>
)
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
)
withRouter.js (向页面路由中注入:navigate、dispatch、useSelector)
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
export default function withRouter (ComponentElement) {
return props => {
const navigate = useNavigate()
const dispatch = useDispatch()
return <ComponentElement {...props} router={{ navigate }} dispatch={dispatch} getStore={useSelector} />
}
}
结语
权限控制需要根据实际项目来说,这里不做介绍,到此,一个简单的react项目已经搭好,具体细节可以根据实际需求丰富和完善
转载自:https://juejin.cn/post/7269749480760426511