目录
项目搭建步骤
本地开发环境
脚手架构建项目
关联Git仓库
strictMode 严格模式
路由配置
路由传参
路由守卫
数据绑定
生命周期
父子组件通信
redux持久化
安装Sass
安装postcss-pxtorem(移动端项目)
安装axios
环境变量
本地代理
项目部署
nginx配置
jenkins自动化部署
自由风格配置
流水线配置
灰度部署方案
项目搭建步骤
本地开发环境
安装nodejs,node版本建议大于16.0.0
Download | Node.js
脚手架构建项目
官网:快速入门 – React
npx create-react-app my-app
关联Git仓库
初始化后的项目关联远程git仓库
strictMode 严格模式
index.js 中包裹了一层 React.strictMode,开发环境严格模式,用于校验页面方法,在开发模式会调用两次页面方法,建议删除
root.render(
// <React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
// </React.StrictMode>
)
路由配置
1、安装react-router-dom
npm i react-router-dom -S
2、新增 / src / router / index.js,可配置路由懒加载
import { lazy } from 'react'
import { Navigate } from 'react-router'
// 非懒加载的路由,组件及引入的css会打包到全局
import Index from '../views/index'
import Login from '../views/login'
// 以下为懒加载路由,访问到路由才会加载页面代码,组件及引入的css也是访问时才加载生效
const My = lazy(() => import('../views/my'))
const Profile = lazy(() => import('../views/my/profile'))
const Order = lazy(() => import('../views/my/order'))
//创建路由
const routes = [
{
path: '/',
meta: {
title: '首页',
},
exact: true,
element: <Index />,
},
{
path: '/login',
element: <Login />,
meta: {
title: '登录',
},
},
{
path: '/my',
element: <My />,
exact: true,
meta: {
title: '我的',
},
children: [
{
path: '/my/profile',
element: <Profile />,
meta: {
title: '我的-主页',
},
},
{
path: '/my/order',
element: <Order />,
meta: {
title: '我的-订单',
},
}
]
},
{
// 未匹配到全部跳转首页
path: '*',
element: <Navigate to="/" />,
},
]
export default routes
3、修改index.js
...
import { BrowserRouter } from 'react-router-dom'
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
)
4、修改App.js
import {useRoutes} from "react-router-dom"
import router from "./router/index"
function App() {
return useRoutes(router)
}
export default App;
路由传参
1、search方式
// 传参页面
<Link to={`/my/order?id=123`}>订单</Link>
// 接收参数页面
import { useSearchParams } from "react-router-dom"
export default function Order() {
// 获取params参数
const [search] = useSearchParams()
const id = search.get('id')
return (
<div>订单id为:{ id }</div>
)
}
2、动态路由方式
// 路由配置 /router/index.js
path: '/my/order/:id'
// 传参页面
<Link to={`/my/order/123`}>订单</Link>
// 接收参数页面
import { useParams } from "react-router"
export default function Order() {
// 获取params参数
const params = useParams()
const id = params.id
return (
<div>订单id为:{ id }</div>
)
}
路由守卫
1、新建 / src / router / beforeEnter.js
import { useLocation, useNavigate, useRoutes } from 'react-router-dom'
import { useEffect } from 'react'
const BeforeEnter = ({ routers }) => {
// 1.在路由数组中找当前页面路由的对应路由项
const fineRouter = (routes, path) => {
for (let item of routes) {
if (item.path === path) return item
if (item.children) {
// 如果有子路由,查找子路由
// 注意:1)因为传入的path是当前完整路径,子路由的path也需要设置完整的路径,例如:/my/order,而不是 order
// 2)不可配置动态参数路由,例如:/product/:id
return fineRouter(item.children, path)
}
}
return null // 没有找到录音配置,返回null,由judgeRouter方法跳转404页面
}
// 2.路由守卫判断
const judgeRouter = (location, navigate) => {
const { pathname } = location
// 2.1路由数组找路由项
const findRoute = fineRouter(routers, pathname)
// 2.2没找到,说明没有这个路由,直接404
if (!findRoute) {
navigate('/404')
return
}
// 2.3更新页面标题
if (findRoute?.meta?.title) {
document.title = findRoute.meta.title
}
if (findRoute.auth) {
// 用户未登陆,挑战登陆页面
if (!localStorage.getItem('user')) navigate('/login')
}
}
// 3.基于useEffect监听页面路由改变。然后组件重新加载,又重新校验权限。
const navigate = useNavigate()
const location = useLocation()
const router = useRoutes(routers)
useEffect(() => {
// 路由守卫判断
judgeRouter(location, navigate)
})
return router
}
export default BeforeEnter
2、修改App.js
import BeforeEnter from "./router/beforeEnter"
import router from "./router/index"
function App() {
return <BeforeEnter routers={router} />
}
export default App;
数据绑定
1、表单数据
import { useNavigate } from 'react-router-dom'
export default function Login() {
const navigate = useNavigate()
// 声明账号、密码变量
let account, password
// form提交
function loginSubmit(e) {
// 阻止form提交事件
if (e) e.preventDefault()
// 通过ref方法获取到输入框内容
if (account.value === 'xxx' && password.value === 'xxx') {
// 登录成功本地存储登录状态
localStorage.setItem('isLogin', true)
// 登录成功跳转我的页面
navigate('/my', {
replace: true
})
}
}
return (
<div>
<h4>登录页面</h4>
<form onSubmit={loginSubmit}>
<p>账号</p>
<input type="text" ref={(input) => (account = input)} />
<p>密码</p>
<input type="password" ref={(input) => (password = input)} />
<button type="submit">提交</button>
</form>
</div>
)
}
2、页面渲染
通过useState方法声明变量,变量更新时才能同步渲染到页面
import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
export default function ProductList() {
const navigate = useNavigate()
// 声明商品列表数据
const [productList, setProductList] = useState([])
// 添加商品
// 注意:不可以对原变量productList做push操作
const addProduct = () => {
const list = productList.concat([
{
id: 1,
name: '新商品'
}
])
setProductList(list)
}
// 删除商品
// 注意:不可以对原变量productList做splice操作
const deleteItem = (product) => {
setProductList(productList.filter((item) => item.id !== product.id))
}
// 跳转商品详情
const goDetail = (item) => {
navigate('/productDetail?id=' + item.id)
}
return (
<div>
<h4>商品列表页</h4>
<button onClick={() => addProduct()}>添加商品</button>
<ul>
{productList.map((item, index) => {
return (
<li key={index}>
<h5>商品名称:{item.name}</h5>
<button onClick={() => goDetail(item)}>跳转详情页</button>
<button onClick={() => deleteItem(item)}>删除</button>
</li>
)
})}
</ul>
</div>
)
}
生命周期
使用useEffect方法,第一个参数传入一个方法,第二个可选参数传入[],如果不写第二个参数,则页面更新就会执行
import React, { useState, useEffect } from 'react'
export default function ProductList() {
const [productList, setProductList] = useState([]);
// 获取商品列表接口
const getList = async () => {
const res = await React.$http({
url: '/m1/2235670-0-default/article/list',
})
setProductList(res.productList)
}
// useEffect hook,可作为生命周期方法使用
// 第二个可选参数传入[],进入页面只执行一次
useEffect(()=>{
console.log('进入列表页')
getList()
}, [])
return (
<div>...</div>
)
}
父子组件通信
1、父组件
import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
// 引入子组件
import ProductItem from '../components/productItem'
export default function ProductList() {
const navigate = useNavigate()
const [productList, setProductList] = useState([]);
// 声明删除商品方法,注册子组件时传入
const deleteItem = (product) => {
setProductList(productList.filter(item => item.id !== product.id))
}
// 声明跳转方法,注册子组件时传入
const goDetail = (item) => {
navigate('/productDetail?id='+item.id)
}
return (
<div>
<h4>商品列表页</h4>
<ul>
{
productList.map((item, index) => {
return (
<li key={index}>
<ProductItem item={ item } goDetail={ goDetail } deleteItem={ deleteItem } />
</li>
)
})
}
</ul>
</div>
)
}
2、子组件
// 接收父组件传入的变量和方法,可直接调用
export default function ProductItem({ item, goDetail, deleteItem }) {
return (
<div className="flex product-item">
<div>
<h5>{ item.name }</h5>
<button onClick={() => goDetail(item)}>查看详情</button>
<button onClick={() => deleteItem(item)}>删除</button>
</div>
</div>
)
}
redux持久化
1、新建 src / store / reducer.js,配置state数据
// 定义state数据
const defaultState = {
inputValue: 0, // 输入框内容
isLogin: false // 登录状态
}
function reducer(state = defaultState, action) {
const newState = JSON.parse(JSON.stringify(state)) // 深度拷贝state
switch (action.type) {
case 'changeInput':
newState.inputValue = action.value
break
case 'changeIsLogin':
newState.isLogin = action.value
break
default:
}
return newState
}
export default reducer
2、新建 src / store / index.js,配置state数据
// 因为 redux已经不推荐使用 createStore()创建数据仓库了,如果我们继续使用,需要引入具有标识的 legacy_createStore
import { legacy_createStore as createStore } from 'redux';
// 持久化插件
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import reducer from './reducer';
// 配置持久化插件
const persistConfig = {
key: 'root',
storage,
// blacklist: ['inputValue'], // 黑名单,例:inputValue不会被持久化
whitelist: ['isLogin'] // 白名单,例:只有isLogin会被持久化
}
const persistedReducer = persistReducer(persistConfig, reducer)
// 创建数据存储仓库
const store = createStore(persistedReducer,
// 浏览器redux devTools添加以下配置
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
const persistor = persistStore(store);
export {
store,
persistor
}
3、修改index.js,全局配置redux
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
// redux
import { store, persistor } from './store'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
{/* 持久化redux,在路由懒加载时,无法获取数据,所以添加Suspense Loading */}
<Suspense fallback={<h2>Loading..</h2>}>
{/* history模式路由 */}
<BrowserRouter>
<App />
</BrowserRouter>
</Suspense>
</PersistGate>
</Provider>
);
4、页面使用
import { useDispatch, useSelector } from "react-redux"
export default function Pdp() {
// 声明输入框
let textValue
// 获取redux中的inputValue数据
const inputValue = useSelector(state => state.inputValue)
// 修改redux数据
const dispatch = useDispatch()
const changeInputFun = () => {
dispatch({ type: 'changeInput', value: textValue.value })
}
return (
<div>
<span>redux值为:{inputValue}</span>
<input ref={(input) => (textValue = input)} />
<button onClick={() => changeInputFun()}>修改</button>
</div>
)
}
安装Sass
npm i sass -D
使用:将css文件换为scss文件即可
安装postcss-pxtorem(移动端项目)
移动端适配,兼容不同分辨率的手机
npm i @craco/craco postcss-pxtorem -D
npm i amfe-flexible -S
使用craco启动服务,项目根目录新增 craco.config.js
module.exports = {
style: {
postcss: {
mode: 'extends',
loaderOptions: {
postcssOptions: {
ident: 'postcss',
plugins: [
[
'postcss-pxtorem',
{
rootValue: 75, // 根元素字体大小
propList: ['*'],
minPixelValue: 2 // 最小px为2,如果设置1px则不转rem
}
]
]
}
}
}
}
}
在index.js引入amfe-flexible
import 'amfe-flexible'
修改package.json的script命令,将 react-scripts 替换为 craco
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
}
安装axios
官网:axios
npm i axios -S
封装axios方法,新建/ src / utils / request.js文件
import axios from 'axios'
const service = axios.create({
// process.env.NODE_ENV 判断是否为本地环境
baseURL: process.env.NODE_ENV === 'development' ? '/api' : 'https://www.baidu.com',
timeout: 30000
})
/* 请求拦截器 */
service.interceptors.request.use(
(config) => {
// 登录状态鉴权
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
return config
},
(error) => {
return Promise.reject(error)
}
)
/* 响应拦截器 */
service.interceptors.response.use(
(response) => {
const { code, message, data } = response.data
// 根据自定义错误码判断请求是否成功
if (code === '0') {
// 将组件用的数据返回
return data
} else {
// 处理业务错误。
return Promise.reject(new Error(message))
}
},
(error) => {
// 处理 HTTP 网络错误
let message = ''
// HTTP 状态码
const status = error.response?.status
switch (status) {
case 401:
message = 'token 失效,请重新登录'
// 这里可以触发退出的 action
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求地址错误'
break
case 500:
message = '服务器故障'
break
default:
message = '网络连接故障'
}
console.log(message)
return Promise.reject(error)
}
)
/* 导出封装的请求方法 */
export const http = (options) => {
return new Promise((resolve, reject) => {
// 方法一:get和delete请求,将请求体转换为queryString形式拼接在url
// if (!options.method || options.method === 'get' || options.method === 'delete') {
// if (options.data) {
// options.url = options.url + '?' + new URLSearchParams(options.data).toString()
// }
// }
// 方法二:get和delete请求,使用params参数传参,post和put使用data参数
const serviceOpt = {}
if (!options.method || options.method === 'get' || options.method === 'delete') {
serviceOpt.params = options.data
} else {
serviceOpt.data = options.data
}
service({
method: options.method || 'get',
url: options.url,
...serviceOpt
})
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
使用
import { http } from '@/utils/request'
const res = await http({
url: '/get/product/list',
// method: 'get',
data: {
id: 1
}
})
console.log(res)
环境变量
1、获取系统环境变量
const NODE_ENV = process.env.NODE_ENV
2、获取自定义环境变量
在create-react-app中获取npm自定义变量,可以使用process.env对象。在package.json中设置变量后,可以在代码中使用process.env.VARIABLE_NAME来获取变量的值。
"scripts": {
"start-test": "REACT_APP_ENV=test react-scripts start",
"start-dev": "REACT_APP_ENV=dev react-scripts start"
}
在业务代码中获取,比如:/ src / index.js
const ENV = process.env.REACT_APP_ENV;
注意:变量名必须以REACT_APP_开头,这是create-react-app的约定
本地代理
新建 / src / setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
createProxyMiddleware('/api', {
// 使用 /api 前缀代理配置
target: 'https://mock.apifox.cn', // 跨域地址
changeOrigin: true, // 控制服务器收到的请求头中host的值
pathRewrite: {
'^/api': '',
},
})
)
}
项目部署
nginx配置
Apache,Nginx部署vue/react项目_apached发布 react_不求甚解bc的博客-CSDN博客
jenkins自动化部署
自由风格配置
jenkins部署vue/react项目_jenkins部署vue项目_不求甚解bc的博客-CSDN博客
jenkins分环境部署vue/react项目_不求甚解bc的博客-CSDN博客
流水线配置
jenkins流水线部署H5项目_jenkins部署h5_不求甚解bc的博客-CSDN博客
灰度部署方案
web前端灰度部署_不求甚解bc的博客-CSDN博客