准备:基本的前端环境,nodejs等 ,编辑器采用vscode、技术采用react、状态管理用mobx、路由react-router-dom
创建
生成项目: npx create-react-app project_demo
进入项目目录:cd project_demo
启动项目: yarn start
调整目录
/public
/src
/asserts //静态资源文件
/components //通用组件
/hooks //封装的钩子函数
/pages //页面
/store //mobx状态仓库
/styles //样式文件
/utils //工具类文件夹:token、anxios、Info等
app.js //根组件
index.js //项目入口
index.css //全局样式
核心代码保留
src/index.js文件
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
src/App.js
export default function App() {
return <div>根组件</div>
}
使用scss预处理器
//安装sass
yarn add sass -D //=>D表示只在dev环境下生效
创建全局样式文件index.scss
body{
margin:0;
}
#root{
height:100%
}
创建几个相关页面:例:登录、布局
pages/Login/index.js
const Login = () => {
return <div>login</div>
}
export default Login
pages/Layout/index.js
const Layout = () => {
return <div>layout</div>
}
export default Layout
创建基础路由
//安装路由
yarn add react-router-dom
//配置基础路由,app.js文件
// 导入路由
import { BrowserRouter, Route, Routes } from 'react-router-dom'
// 导入页面组件
import Login from './pages/Login'
import Layout from './pages/Layout'
// 配置路由规则
function App() {
return (
<BrowserRouter> //浏览器路由最外层包裹
<div className="App">
<Routes> //路由外层
<Route path="/" element={<Layout/>}/> //path:路由地址,element:路由地址对应的页面
<Route path="/login" element={<Login/>}/>
</Routes>
</div>
</BrowserRouter>
)
}
export default App
使用antd组件库
//安装
yarn add antd
//src/index.js中引入 *=> 新版本中,似乎可以不用在此处引入也同样可以使用,如需引入则是按照如下
import 'antd/dist/antd.mis.css
import './index.css'
//然后直接再页面中,再引入想要使用的组件即可。 例:
import { Button } from 'antd'
配置别名路径(通过第三方库Craco)
作用:可以通过配置@来简化路径
//安装
yarn add -D @craco/craco
//项目根目录创建:craco.config.js,并配置路径别名
const path = require('path')
module.exports = {
// webpack 配置
webpack: {
// 配置别名
alias: {
// 约定:使用 @ 表示 src 文件所在路径
'@': path.resolve(__dirname, 'src')
}
}
}
//修改package.json脚本命令
// 将 start/build/test 三个命令修改为 craco 方式
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
}
//此时,vscode在输入@的时候还不会显示提示,所以此时需要船舰jsconfig.json,并添加如下配置
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"] //=>告诉编辑器@在路径中的意思
}
}
}
...//省略页面编写代码
封装anxios
utils/request.js
import axios from 'axios'
const request = axios.create({
baseURL: ' ', //服务地址
timeout: 5000
})
// 添加请求拦截器
request.interceptors.request.use((config)=> {
return config
}, (error)=> {
return Promise.reject(error)
})
// 添加响应拦截器
request.interceptors.response.use((response)=> {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response
}, (error)=> {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
export { request }
通过utils中统一管理
utils/index.js
import {request} from './request';
export { request }
配置登录mobx(LoginStore)
/store/login.Store.js
import { removeToken, request, setToken } from "@/utils"
import { makeAutoObservable } from "mobx"
class LoginStore {
token = this.getToken || ''
constructor() {
makeAutoObservable(this)
}
//getToken
login = async ({ mobile, code }) => {
//调用登录接口、存入token
const res = await request.post('/api', {
mobile, code
})
console.log(res)
this.token = res.data
setToken(this.token)
}
loginOut = () => {
removeToken()
}
}
export default LoginStore
统一管理
创建store/index.js文件
import React from "react"
import LoginStore from './login.Store'
class RootStore {
// 组合模块
constructor() {
this.loginStore = new LoginStore()
}
}
// 导入useStore方法供组件使用数据
const StoresContext = React.createContext(new RootStore())
export const useStore = () => React.useContext(StoresContext)
使token持久化
封装utils/token.js
const TOKEN_KEY = 'geek_pc'
const getToken = () => localStorage.getItem(TOKEN_KEY)
const setToken = token => localStorage.setItem(TOKEN_KEY, token)
const clearToken = () => localStorage.removeItem(TOKEN_KEY)
export { getToken, setToken, clearToken }
在调用登录接口的同时,将token设置为接口返回的token数据调用token.js里的setToken
请求拦截器注入token *
将token通过请求拦截器注入到token请求头中
utils/request.js => axios(此处注入token) => .....
//通过config.headers.Authorization = Bearer ${token} 拼接上去
//utils/request.js
http.interceptors.request.use(config => {
// if not login add token
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
路由鉴权
实现非登录用户过滤拦截并跳转到登录页面
封装组件components/AuthRoute/index.js
// 1. 判断token是否存在
// 2. 如果存在 直接正常渲染
// 3. 如果不存在 重定向到登录路由
// 高阶组件:把一个组件当成另外一个组件的参数传入 然后通过一定的判断 返回新的组件
import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'
function AuthRoute ({ children }) {
const isToken = getToken()
if (isToken) {
return <>{children}</>
} else {
return <Navigate to="/login" replace />
}
}
// <AuthComponent> <Layout/> </AuthComponent>
// 登录:<><Layout/></>
// 非登录:<Navigate to="/login" replace />
export { AuthRoute }
src/App.js
import { Router, Route } from 'react-router-dom'
import { AuthRoute } from '@/components/AuthRoute'
import Layout from '@/pages/Layout'
import Login from '@/pages/Login'
function App() {
return (
<Router>
<Routes>
{/* 需要鉴权的路由 */}
<Route path="/*" element={
<AuthRoute>
<Layout />
</AuthRoute>
} />
{/* 不需要鉴权的路由 */}
<Route path='/login' element={<Login />} />
</Routes>
</Router>
)
}
export default App
退出登录后,需要清除token
...
项目打包
1、yarn build =>打包生产的内容会放在根下的build文件夹中
2、npm i -g serve =>提供serve命令,启动打包后的本地服务
serve -s ./build =>在build目录中启动
3、http://localhost:3000
打包体积分析
1、yarn add source-map-explorer =>安装分析打包体积的包
2、在package.json文件的scripts标签中添加
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
}
3、项目打包:yarn build
4、运行分析命令:yarn analyze即可在浏览器中看到包大小
优化配置CDN
通过craco修改webpack配置,实现CDN优化
craco.config.js
// 添加自定义对于webpack的配置
const path = require('path')
const { whenProd, getPlugin, pluginByName } = require('@craco/craco')
module.exports = {
// webpack 配置
webpack: {
// 配置别名
alias: {
// 约定:使用 @ 表示 src 文件所在路径
'@': path.resolve(__dirname, 'src')
},
// 配置webpack
// 配置CDN
configure: (webpackConfig) => {
// webpackConfig自动注入的webpack配置对象
// 可以在这个函数中对它进行详细的自定义配置
// 只要最后return出去就行
let cdn = {
js: [],
css: []
}
// 只有生产环境才配置
whenProd(() => {
// key:需要不参与打包的具体的包
// value: cdn文件中 挂载于全局的变量名称 为了替换之前在开发环境下
// 通过import 导入的 react / react-dom
webpackConfig.externals = {
react: 'React',
'react-dom': 'ReactDOM'
}
// 配置现成的cdn 资源数组 现在是公共为了测试
// 实际开发的时候 用公司自己花钱买的cdn服务器
cdn = {
js: [
'https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js',
],
css: []
}
})
// 都是为了将来配置 htmlWebpackPlugin插件 将来在public/index.html注入
// cdn资源数组时 准备好的一些现成的资源
const { isFound, match } = getPlugin(
webpackConfig,
pluginByName('HtmlWebpackPlugin')
)
if (isFound) {
// 找到了HtmlWebpackPlugin的插件
match.userOptions.cdn = cdn
}
return webpackConfig
}
}
}
同时需要在public/index.html中加入如下代码
<body>
<div id="root"></div>
<!-- 加载第三发包的 CDN 链接 -->
<% htmlWebpackPlugin.userOptions.cdn.js.forEach(cdnURL => { %>
<script src="<%= cdnURL %>"></script>
<% }) %>
</body>
路由懒加载(按需加载)
App导入Suspense组件 => Router内部使用Suspense包裹Routes组件 => 为Suspense提供fallback属性,指定loading => 导入laze函数,改为懒加载方式导入路由
app.js
import { Routes, Route } from 'react-router-dom'
import { HistoryRouter, history } from './utils/history'
import { AuthRoute } from './components/AuthRoute'
// 导入必要组件
import { lazy, Suspense } from 'react'
// 按需导入路由组件
const Login = lazy(() => import('./pages/Login'))
const Layout = lazy(() => import('./pages/Layout'))
const Home = lazy(() => import('./pages/Home'))
const Article = lazy(() => import('./pages/Article'))
const Publish = lazy(() => import('./pages/Publish'))
function App () {
return (
<HistoryRouter history={history}>
<Suspense
fallback={
<div
style={{
textAlign: 'center',
marginTop: 200
}}
>
loading...
</div>
}
>
<Routes>
{/* 需要鉴权的路由 */}
<Route path="/" element={
<AuthRoute>
<Layout />
</AuthRoute>
}>
{/* 二级路由默认页面 */}
<Route index element={<Home />} />
<Route path="article" element={<Article />} />
<Route path="publish" element={<Publish />} />
</Route>
{/* 不需要鉴权的路由 */}
<Route path='/login' element={<Login />} />
</Routes>
</Suspense>
</HistoryRouter>
)
}
export default App