二十七、脚手架配置代理
一、背景:为什么要配置代理?
在 React 开发中,前端默认运行在 localhost:3000,后端服务运行在 localhost:5000,由于浏览器的同源策略,前端直接请求后端会出现跨域问题。
配置代理的目的,就是让前端请求 "假装" 是同源请求,从而绕过跨域限制。
二、方法一:package.json 配置代理
1. 配置方式
在项目根目录的 package.json 中,直接添加一行配置:
js
{
"proxy": "http://localhost:5000"
}
2. 核心说明
- 工作原理 :当请求
localhost:3000上不存在的资源时,会自动转发给配置的目标地址(localhost:5000),优先匹配前端资源,匹配不到再走代理。 - 优点:配置极简,前端请求时不需要加任何前缀,直接写接口路径即可。
- 缺点 :只能配置单个代理,无法同时代理多个后端服务。
三、方法二:setupProxy.js 配置多代理(http-proxy-middleware)
1. 适用场景
需要同时代理多个后端服务(比如同时对接 localhost:5000 和 localhost:5001),需要更灵活的控制。
2. 配置步骤
步骤 1:创建配置文件
在 src/ 目录下新建 setupProxy.js 文件。
步骤 2:编写代理规则
js
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
// 代理 /api1 开头的请求到 localhost:5000
app.use(
proxy('/api1', {
target: 'http://localhost:5000', // 目标后端地址
changeOrigin: true, // 修改请求头的host字段
pathRewrite: {'^/api1': ''} // 去掉请求前缀,保证后端收到正常路径
})
)
// 代理 /api2 开头的请求到 localhost:5001
app.use(
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
3. 关键配置项详解
| 配置项 | 作用说明 |
|---|---|
target |
后端服务的目标地址,代理会把请求转发到这里 |
changeOrigin |
控制请求头中的 host 字段- true:服务器收到的 host 为目标地址(localhost:5000)- false:服务器收到的 host 为前端地址(localhost:3000)建议始终设为 true,避免部分服务器拒绝非同源 host 的请求 |
pathRewrite |
重写请求路径,比如把 /api1/user 转为 /user,去掉代理前缀 |
4. 优缺点
-
优点:
- 支持配置多个代理,可以同时对接多个后端服务。
- 可以灵活控制哪些请求走代理,哪些不走。
-
缺点 :配置相对繁琐,前端请求接口时必须加上代理前缀(如
/api1/xxx)。
二十八、消息订阅与发布


订阅消息
js
const token = PubSub.subscribe('消息名', (msg, data) => {})
发布消息
js
PubSub.publish('消息名', 数据)
取消订阅
js
PubSub.unsubscribe(token) // 推荐
PubSub.unsubscribe('消息名')
二十九、路由的基本使用
一、React 路由的基本使用(v5 风格)
这是早期 React Router v5 的标准流程,也是很多教材里的经典写法:
-
明确页面结构
把界面拆成「导航区」和「展示区」,导航区用来跳转,展示区用来渲染对应页面。
-
用
<Link>标签替代<a>标签// 原生 a 标签会刷新页面,Link 只会更新路由,不刷新 <Link to="/xxxx">Demo</Link> -
用
<Route>标签做路径匹配// path 对应 to 的路径,component 指定要渲染的组件 <Route path="/xxxx" component={Demo} /> -
根组件必须用路由容器包裹
在
App最外层,必须用<BrowserRouter>或<HashRouter>包裹,否则路由无法工作。<BrowserRouter>:使用history模式,URL 干净无#<HashRouter>:使用hash模式,URL 带#,兼容性更好
二、路由组件 vs 一般组件
这是 React Router 里一个很关键的区分,面试常考:
| 对比维度 | 一般组件 | 路由组件 |
|---|---|---|
| 写法 | 直接写组件标签:<Demo /> |
配置在 <Route> 里:<Route path="/demo" component={Demo} /> |
| 存放位置 | 通常放在 src/components(通用可复用组件) |
通常放在 src/pages/views(页面级组件) |
接收到的 props |
只有写标签时传入的属性 | 自动接收到 3 个固定属性:history、location、match |
三、路由组件特有的 3 个 props
这是路由组件最核心的特性,也是编程式导航的基础:
1. history(控制路由跳转)
包含 5 个常用方法:
go(n):前进 / 后退 n 步(go(1)前进,go(-1)后退)goBack():后退一步goForward():前进一步push(path, state):跳转到指定路径,会留下历史记录(可回退)replace(path, state):跳转到指定路径,不留下历史记录(无法回退)
2. location(当前路由信息)
包含当前页面的路径、参数等信息:
pathname:当前路径(如/about)search:URL 查询参数(如?id=1)state:路由跳转时传递的状态数据
3. match(路由匹配信息)
包含路由的匹配结果,常用于获取动态参数:
params:动态路由参数(如/user/:id中的id)path:路由配置里的path值(如/about)url:当前匹配到的完整 URL(如/about)
四、NavLink与封装NavLink
-
NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
-
标签体内容是一个特殊的标签属性
-
通过this.props.children可以获取标签体内容
五、Switch的使用
- 通常情况下,path和component是一一对应的关系。
-
- Switch可以提高路由匹配效率(单一匹配)。
六、解决多级路径刷新页面样式丢失的问题
- public/index.html 中 引入样式时不写 ./ 写 / (常用)
- public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
- 使用HashRouter
七、路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:
<Route exact={true} path="/about" component={About}/>、 - 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
八、Redirect的使用
-
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
-
具体编码:
jsx<Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch>
九、嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
十、向路由组件传递参数
-
params参数
- 路由链接(携带参数):
<Link to='/demo/test/tom/18'>详情</Link> - 注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/> - 接收参数:
const {name,age} = this.props.match.params
- 路由链接(携带参数):
-
search参数 - 路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'>详情</Link>- 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/> - 接收参数:
const {search} = this.props.location - 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
- 注册路由(无需声明,正常注册即可):
-
state参数
- 路由链接(携带参数):
<Link to={``{path:'/demo/test',state:{name:'tom',age:18}}}>详情</Link> - 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/> - 接收参数:
this.props.location.state- 备注:刷新也可以保留住参数
- 路由链接(携带参数):
十一、编程式路由导航 :借助this.props.history对象上的API操作路由跳转、前进、后退
- this.props.history.push()
- this.props.history.replace()
- this.props.history.goBack()
- this.props.history.goForward()
- this.props.history.go()
十二、withRouter
jsx
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
handleGoHome = () => {
// 被 withRouter 包装后,组件就有了 this.props.history
this.props.history.push('/home')
}
render() {
return (
<div>
<button onClick={this.handleGoHome}>返回首页</button>
</div>
)
}
}
// 导出时用 withRouter 包装组件
export default withRouter(Header)
-
关键说明:
withRouter是一个高阶组件(HOC),接收一个组件,返回一个新的增强组件。- 被包装后的组件,会额外获得
history、location、match三个属性,和路由组件的props完全一致。 - 它是 React Router v5 的 API,在 v6 中已被废弃,改用
useNavigate、useLocation等 Hooks 实现相同功能。
十三、BrowserRouter和HashRouter的区别
一、最直观区别
-
BrowserRouter
路径不带 #
例:
http://xxx/home -
HashRouter
路径带 #
例:
http://xxx/#/home
二、底层原理
- BrowserRouter :使用 H5 history API (
pushState、replaceState) - HashRouter :使用 URL 哈希值(hash) ,
#后面的内容不会发给服务器
三、刷新页面是否会报错
- BrowserRouter :刷新会 404,需要后端配置 nginx 兜底
- HashRouter :刷新不会报错,兼容性最好
四、是否向服务器发送请求
- BrowserRouter:会
- HashRouter :
#后面的内容不会发给服务器
五、兼容性
- BrowserRouter:支持 IE10+
- HashRouter :支持所有浏览器,兼容性最好
六、项目推荐使用
- 正式项目、追求美观 :用 BrowserRouter
- 本地开发、不想配后端 :用 HashRouter
- 前端静态博客、纯前端项目 :HashRouter 最稳
三十、redux的工作流程
- 组件通过
dispatch发送一个 Action - Reducer 接收 Action,根据类型计算新 State
- Store 更新 State
- 所有用到该 State 的组件自动重新渲染
三十一、求和案例
1.求和案例_redux精简版
(1). 去除Count组件自身的状态
(2). src下建立: - redux - store.js - count_reducer.js
(3). store.js: 1). 引入redux中的createStore函数,创建一个 store
2). createStore调用时要传入一个为其服务的reducer
3). 记得暴露store对象
(4). count_reducer.js:
1). reducer的本质是一个函数,接收:preState, action,返 回加工后的状态
2). reducer有两个作用:初始化状态,加工状态
3). reducer被第一次调用时,是store自动触发的, 传递的preState是undefined, 传递的action是:{type:'@@REDUX/INIT_a.2.b.4}
(5). 在index.js中监测store中状态的改变,一旦发生改变重新渲染
!CAUTION
备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
2.求和案例_redux完整版
新增文件:
-
count_action.js专门用于创建action对象 -
constant.js放置防止由于编码疏忽写错action中的type
3.求和案例_redux异步action版
(1). 明确:延迟的动作不想交给组件自身,想交给action
(2). 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回(非必须)。
(3). 具体编码:
1). yarn add redux-thunk,并配置在store中
2). 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
3). 异步任务有结果后,分发一个同步的action去真正操作数据。
(4). 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。
三十二、对react-reduct的理解

4.求和案例_react-redux基本使用
(1). 明确两个概念:
1). UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
2). 容器组件:负责和redux通信,将结果交给UI组件。
(2). 如何创建一个容器组件------靠react-redux 的 connect函数 connect(mapStateToProps, mapDispatchToProps)(UI组件)
- mapStateToProps:映射状态,返回值是一个对象
- mapDispatchToProps:映射操作状态的方法,返回值是一个对象
(3). 备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
(4). 备注2:mapDispatchToProps也可以是一个对象
5.求和案例_react-redux优化
(1). 容器组件和UI组件整合一个文件
(2). 无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。
(3). 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
(4). mapDispatchToProps也可以简单的写成一个对象
(5). 一个组件要和redux"打交道"要经过那几步?
(1). 定义好UI组件---不暴露
(2). 引入connect生成一个容器组件,并暴露,写法如下:
jsx
connect(
state => ({key:value}), // 映射状态
{key:xxxxxAction} // 映射操作状态的方法
)(UI组件)
(3)在 UI 组件中通过this.props.xxxxxxx读取和操作状态
三十二、添加用户案例和求和案例放在同一个页面的做法
1.reducer文件夹里的代码
jsx
import { ADD_PERSON } from '../constant'
// 初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){
const {type,data} = action
switch (type) {
case ADD_PERSON: // 若是添加一个人
return [data,...preState]
default:
return preState
}
}

2.store文件里的代码

3.count组件里的代码


4.person组件里的代码





6.求和案例_react-redux数据共享版
(1). 定义一个Person组件,和Count组件通过redux共享数据。
(2). 为Person组件编写:reducer、action,配置constant常量。
(3). 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
(4). 交给store的是总reducer,最后注意在组件中取出状态的时候,记得"取到位"。
三十三、纯函数 知识点
1. 定义
一类特殊的函数:只要输入(实参)相同,就必定得到相同的输出(返回值)。
2. 必须遵守的约束
-
不得改写参数数据
不能直接修改传入的参数,必须返回新的数据副本。
-
不会产生任何副作用
不包含网络请求、文件读写、输入输出设备操作等会改变外部状态的行为。
-
不能调用不纯的方法
不能使用
jsxDate.now()jsxMath.random()等会导致输出不确定的方法。
3. 与 Redux 的关联
Redux 中的 reducer 函数必须是一个纯函数,这样才能保证状态的变化是可预测、可追溯的。