一、组件三大属性
每个组件的this上都有三个重要属性:
- state
- props
- refs
1.state
state称为状态,状态驱动着页面的改变
class Person extends Component {
state = {
name="张三",
age:18
}
/*
此处必需写箭头函数
不然按钮处就需要写this.change.bind(this),确保this不丢失
*/
change=()=>{
/*
更新状态是合并,不是覆盖,即传入参数{name:"李四"}
与原装胎对比,不会覆盖原状态中的age
*/
this.state({name:"李四"})
}
render() {
return (
<div>
{<span>{this.state.name}</span>}
<button onClick={this.change}>点我更新状态</button>
</div>
)
}
}
2.props
父组件传递给子组件的参数
class Father extends Component {
state = {
bear:{
x:1,
y:2
}
}
render() {
return (
<div>
<Child x={1} y={2}></Child>
</div>
)
}
}
/*
或者:
将bear对象结构,得到x,y,相当于分别传递
<Child {...this.state.bear}></Child>
*/
子组件中可对接收到的参数进行限制,需引入React库中的PropTypes进行限制
//npm i prop-types
import PropTypes from 'prop-types'
class Child extends Component {
//静态属性
static propTypes={
//支持链式写法
x:PropTypes.string.isrequired
//类型以小写开头,以区别原生,如:string,object,bool
//ps:函数类型:func
}
//默认值
static defaultProps={
x:1
}
render() {
return (
<div>
{this.props.x}
</div>
)
}
}
/*
或者:
Child.propTypes={
x:PropTypes.func
}
*/
3.refs
使用ref可直接拿到组件的原生DOM对象
//有三种写法获得ref
//1.字符串式(不推荐)
class Person extends Component {
render() {
console.log(this.refs)
return (
<div>
<span ref="demo"></span>
</div>
)
}
}
//2.回调函数式
class Person extends Component {
/*
函数式会将ref作为参数传递给回调函数,即c,
可直接写在类实例属性上,即this.test=c,
这样可不用去refs里取
*/
render() {
return (
<div>
<span ref={c=>{this.test=c}}></span>
</div>
)
}
}
二、生命周期
旧生命周期
初始化
- constructor(构造函数)
- componentWillMount(将要挂载)
- render(渲染函数)
- componentDidMount(组件已经挂载)
更新时
- shouldComponentUpdate(控制是否更新)
- componentWillUpdate(组件将要更新)
- render(渲染函数)
- componentDidUpdate(组件已经更新)
卸载时
可由ReactDOM.unmountComponentAtNode()手动触发组件卸载
- componentWillUnmount(组件将要卸载)
注意
- shouldComponentUpdate控制是否允许此次更新,返回true则允许更新,继续后面流程,返回false则中断后面流程,不返回值则报错。
- 组件上的forceUpdate()方法,可强制更新,跳过shouldComponentUpdate控制。
新生命周期
初始化
在constructor和render之间加入getDerivedStateFromProps钩子,用法比较罕见,一般在state全受prop控制时使用,以下摘自官网:
更新时
同初始化时,在shouldComponentUpdate前加入钩子:getDerivedStateFromProps,不过还在componentDidUpdate前加入,getSnapshotBeforeUpdate(更新前获得组件快照),用法也比较罕见,以下摘自官网:
卸载时
同旧的生命周期钩子。
即将废弃的钩子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
使用会出现警告,最好不使用,或者前面带上UNSAFE_前缀来使用。
总结
新的生命周期相交于旧的
废弃了三个不重要的钩子:将要挂载,将要收到参数,将要更新
更新了两个罕见的钩子:获得props驱动的state,更新前获得组件快照
总的来说,重要的钩子有三个:
- render,渲染函数
- componentDidMount,组件挂载完成,一般做发起请求,初始化等工作。
- componentWillUnmount,组件即将卸载,一般做收尾工作。
三、React原理简述
React组成
- react.js,React核心库。
- react-dom.js,提供操作DOM的react核心库,依赖于上述库。
- babel.js,用于解析jsx,将其转换为js代码的库。
Vitual Dom和Diff算法
相较于直接操作较大的真实(与虚拟相对)DOM对象,操作JS里的对象可优化浏览器性能,减少重排和重绘的次数。
故将真实的浏览器DOM对象映射成JS里的对象(虚拟DOM),每次数据更新,驱动视图的改变时,查看虚拟DOM有没有受影响,未受影响,则复用,否则重新渲染。
//每个dom都有一个key值,用以区分每个虚拟DOM
class Person extends Component {
state = {
arr=[
{id:1,name:'老张',age:30},
{id:2,name:'老王',age:20}
]
}
render() {
//此处key值不推荐(不是不可以)使用数组下标index
const {arr}=this.state
return (
<ul>
arr.map((item,index)=>{
return <li key={index}>{item.name}-{item.age}</li>
})
</ul>
)
}
/*
render() {
//此处key值不推荐使用数组下标index
若dom中有一些"未改变"(要复用的DOM)(若正好是输入类,则很危险)
则一些破坏原数组,如:逆序插入
的方法可能导致每个li和input错位
const {arr}=this.state
return (
<ul>
arr.map((item,index)=>{
return <li key={index}> {item.name}-{item.age} <input/></li>
})
</ul>
)
}
*/
}
四、Router路由
1.实现原理
SPA(Single Page APP)
- 只有一个完整页面
- 点击页面中的链接,不会刷新页面,只会做局部更新
原生实现
通过监听Window上的Hash或History的变化,来实现页面的更新,如下:
//方法一,直接使用H5推出的history身上的API
// let history = History.createBrowserHistory()
//方法二,hash值(锚点)
let history = History.createHashHistory()
//历史记录压栈
function push (path) {
history.push(path)
return false
}
//替换掉栈顶的历史记录
function replace (path) {
history.replace(path)
}
//历史记录后退
function back() {
history.goBack()
}
//历史记录前进
function forword() {
history.goForward()
}
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
前进和后退对应浏览器URL左边的:
2.使用
react-router-dom提供组件:
- BrowserRouter、HashRouter,一般包在APP组件外边,决定是哪种路由方式。
- Route 注册路由
- Redirect 重定向
- Link、NavLink、Switch 决定路由跳转
例一:Route和Switch的使用
入口文件:
//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
//引入BrowserRouer路由组件
import { BrowserRouter } from 'react-router-dom'
//引入App
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
App:
import React, { Component } from 'react'
import {NavLink,Route} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
export default class App extends Component {
render() {
return (
<div>
<div>
<Header/>
</div>
<div>
<div>
{/*类似于原生中的a标签 */}
<NavLink to="/about">About</NavLink>
<NavLink to="/home">Home</NavLink>
</div>
<div>
<div>
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
)
}
}
NavLink和Link差别:NavLink可给activeClassName,activeStyle等属性
Switch作用:只渲染一个匹配到的第一个路由,不会渲染多个,如下:
//若无Switch包裹,当路径为/home时以下三个组件皆渲染
<Route path="/home" component={Home}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
//包裹起来确保只渲染匹配到的第一个
<Switch>
<Route path="/home" component={Home}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
例二:精准匹配和模糊匹配
//路由导航
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink>
//注册路由
<Switch>
<Route exact path="/" component={Root} />
<Route exact path="/about" component={About}/>
<Route exact path="/home" component={Home}/>
</Switch>
**exact表示开启路由精准匹配,**如:路径/home/a/b必需精准对应某个路由的path,该路由才能进行渲染。
Route默认是模糊匹配,如:路由Root的path是/home/a/b的字串,匹配上了,优先进行渲染;若未使用Switch组件,则Root和Home一并渲染。
工程中一般都是模糊匹配
例三:重定向
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
<Redirect to="/about" />
</Switch>
重定向组件一般对路由进行兜底,如上,若路径/home和/about都未匹配上,则使用Redirect组件,发现其路径是/about,故匹配About组件。
例四:嵌套路由
App:
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
<Redirect to="/about" />
</Switch>
Home:
<MyNavLink to="/home/news">News</MyNavLink>
<MyNavLink to="/home/message">Message</MyNavLink>
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
3.路由传参
路由组件的props
//每个路由组件上都有history、location、match三个对象
//上面有go、goBack、goForword、push、replace等方法
history:{
//或POP
action:"PUSH",
//路由栈的长度,
length:4
location:{....}
}
//来自hisroty上的location
location:{
hash: ""
key: "t3t15u"
pathname: "/home/message/detail/"
//查询字符串
search: "?id=5&title=消息1"
//通过state传递的参数
state: undefined
}
//一些匹配数据,主要是path和params
match:{
//是否是精准匹配
isExact: true
//params传递的参数
params: {}
path: "/home/message/detail"
url: "/home/message/detail/"
}
例一:传递params参数
<Link to={`/home/message/detail/5/${title}`}>新闻标题</Link>
//声明接受params参数
<Route path="/home/message/detail/:id/:title" component={Detail}/>
路由组件:
// 接收params参数
const {id,title} = this.props.match.params
例二:传递state参数
实际上,route的to写成字符串是一种简写形式,完整形式应该为一个对象,对象有一个key为state
const routerObj={pathname:'/home/message/detail',state:{id:5,title:'参数'}
<Route to={routerObj}/>
路由组件:
// 接收state参数
const {id,title} = this.props.location.state || {}
例三:传递search参数
//查询字符串
<Link to={`/home/message/detail/?id=5&title=${title}`}>新闻标题</Link>
//正常注册路由
<Route path="/home/message/detail" component={Detail}/>
路由组件:
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
路由传参总结
除了params传递参数需要在Route声明接受外,其它的都正常注册,然后组件内部在props上取得参数。
Search查询字符串方法较麻烦,一般不用。
4.高级使用
例一:replace路由
<Link replace to="/home">Home</Link>
replace
表示匹配的路由不是压栈,而是代替栈顶的路由。
例二:编程式路由导航
路由组件:
replaceShow = (id,title)=>{
//replace跳转+携带params参数
//this.props.history.replace(`/home/message/detail/${id}/${title}`)
//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
//replace跳转+携带state参数
this.props.history.replace(`/home/message/detail`,{id,title})
}
pushShow = (id,title)=>{
//push跳转+携带state参数
this.props.history.push(`/home/message/detail`,{id,title})
}
back = ()=>{
//栈顶指针向下移动1位
this.props.history.goBack()
}
forward = ()=>{
//栈顶指针向上移动1位
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
<button onClick={()=>this.pushShow(5,'test')}>跳转</button>
例三:withRouter的使用
一般组件是没有路由组件上面的hisroty等属性和方法的,所以引入了withRouter。
import { withRouter } from "react-router-dom";
class Header extends Component {
render() {
//可以看到history、location、match属性
console.log(this.props)
return (
<div>
Test
</div>
)
}
}
//包裹一层
export default withRouter(Header)
五、组件通信
1.pubsub-js 消息订阅
pubsub-js是一个消息订阅库,可在任意框架中使用。
//下载
npm install pubsub-js --save
//组件中引入
import PubSub from 'pubsub-js'
//订阅方:
//订阅消息,回调的第一个参数为消息名(delete)(饱受诟病)
this.id= PubSub.subscribe('delete', (title,data)=>{})
//组件销毁前要取消订阅
PubSub.unsubscribe(this.id)
//发布方:
//发布消息
PubSub.publish('delete', '我是参数')
2.redux
redux是一个专门用于做状态管理的JS库(不是react插件库),可在任意框架中使用,但一般只在react中使用。
工作流程:
文件结构:
constant中定义枚举类型
/*
该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
actions中一般导出一个action的创建函数
/*
该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from '../constant'
//同步action,就是指action的值为Object类型的一般对象
export const increment = data => ({type:INCREMENT,data})
export const decrement = data => ({type:DECREMENT,data})
//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const incrementAsync = (data,time) => {
return (dispatch)=>{
setTimeout(()=>{
dispatch(increment(data))
},time)
}
}
reducers中创建出reducer
/*
1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREMENT, DECREMENT } from '../constant'
const initState = 0 //初始化状态
//导出reducer
export default function countReducer(preState = initState, action) {
//从action对象中获取:type、data
const { type, data } = action
//根据type决定如何加工数据
switch (type) {
case INCREMENT: //如果是加
return preState + data
case DECREMENT: //若果是减
return preState - data
default:
return preState
}
}
srote一般汇总所有reducer
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))
使用
如在count组件中使用:
//引入store
import Store from '../../redux/store'
//获得数据
Store.getState()
//执行action
/*
dispatch接受一个参数
1.形如{type:'',data:''}的对象*
*/
Store.dispatch(createIncrementAction(value*1))
/*
2.一个函数,若检测到是一个函数,则将dispatch传入
*/
store.dispatch(createIncrementAsyncAction(value*1,500))
3.react-redux
facebook根据redux特性推出的官方插件库
工作流程
使用:
在App的外层套上Provider组件,并传递给Provider组件创建出来的store
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'
ReactDOM.render(
/* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
将普通组件变为容器组件
:
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//定义UI组件
class Count extends Component {
render(){
console.log(this.props)
return (
<div></div>
)
}
}
/*
1.返回一个对象
2.key传递给props的key,value传递给props的value
3.参数state是store中的状态
*/
function mapStateToProps(state){
return {count:state}
}
/*
1.返回一个对象;
2.key传递给props的key,value传递给props的value
3.参数dispatch是store的行为
*/
function mapDispatchToProps(dispatch){
return {
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}
}
/*
connect第一个函数接受两个参数:
1.mapStateToProps
2.mapDispatchToProps
将redux中的数据和操作映射到props上
connect第二个函数接受UI组件
*/
export default connect(mapStateToProps,mapDispatchToProps)(Conut)
多组件多状态
Reducers的入口文件
import {combineReducers} from 'redux'
import count from './count'
import persons from './person'
//在reducer文件夹的入口文件汇总所有的reducer变为一个总的reducer
export default combineReducers({
//此时store的state对象上有count和persons
count,persons
})
Store文件
import {createStore,applyMiddleware} from 'redux'
import reducer from './reducers' //汇总后的reducer
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension,便于开发者工具观察
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
Person的action
import {ADD_PERSON} from '../constant'
//创建增加一个人的action动作对象
export const addPerson = personObj => ({type:ADD_PERSON,data:personObj})
Person的reducer
import {ADD_PERSON} from '../constant'
//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){
// console.log('personReducer@#@#@#');
const {type,data} = action
switch (type) {
case ADD_PERSON:
//此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。
//preState.unshift(data)
//返回新的state
return [data,...preState]
default:
return preState
}
}
Person组件
//引入actions的creator
import {addPerson} from '../../redux/actions/person'
export default connect(
//映射状态:从state上结构出persons和count并传递给props(简写)
({persons,count}) => ({persons,count}),
//映射操作:拿到dispatch
(dispatch)=>({addPerson:personObj => dispatch(addPerson(personObj))}) //可将函数简写成对象:{addPerson}
)(Person)
4.补充
-
每个Reducer必须是纯函数,即对于任意一个参数,任何时间、条件下,都返回唯一确定的值,如:func(1)=2,每次调用func并传入参数1都返回2。纯函数内部一般不调用随机数、时间参数等不确定的值,且不改变参数、不发起网络请求、不调用输入设备
-
Store初始化时会调用一次Reducer,传入的preState为undefined,此时可赋予state一个初值。
-
还有诸如react-redux的数据管理插件,如DVA,Mobx…
六、扩展
1.setState的写法
完整写法
state={
a:1,
b:2
}
function updater(state,props){
//返回新的状态
return {
a:3
}
}
setState(updater,[callback])
简写
//因为updater返回一个对象,所以可直接把对象写成第一个参数
setState({a:3},[callback])
- callback是可选的回调函数,它在render之后调用
- 对象式简写是函数式写法的语法糖
- 使用原则:
- 如果新状态不依赖于原状态 => 使用对象方式
- 如果新状态依赖于原状态 ==> 使用函数方式
- 如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取
2.lazy组件实现懒加载
import {lazy,Suspense} from 'react'
//原写法:const Login=import('@/pages/Login')
//懒加载:接受一个回调函数
const Login =lazy(()=>import('@/pages/Login'))
//通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
3.Hooks
1. React Hook/Hooks是什么?
- Hook是React 16.8.0版本增加的新特性/新语法
- 可以让你在函数组件中使用 state 以及其他的 React 特性
2. 三个常用的Hook
- 状态:State Hook: React.useState()
- 生命周期:Effect Hook: React.useEffect()
- 引用:Ref Hook: React.useRef()
3. State Hook
- State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
语法: const [xxx, setXxx] = React.useState(initValue) - useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数 - setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
4. Effect Hook
-
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
-
React中的副作用操作:
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
-
语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行 -
可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
5. Ref Hook
- Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
- 语法: const refContainer = useRef()
- 作用:保存标签对象,功能与React.createRef()一样
4.Fragment 标签片段
原来的组件必需有个根标签,可使用Fragment来代替根标签,解析时不会解析Fragment。
import {Fragment} from 'react'
<Fragment></Fragment>
//也可使用空标签,但空标签不可拥有key等属性
<></>
5.Context 上下文
理解
一种组件间通信方式, 常用于**【祖组件】与【后代组件】**间通信
使用
import {createContext} from 'react'
//1.创建上下文,上面有Provider和Consumer两个组件
const myContext=createContext()
//2.渲染子组件时在外面包裹一层Provider,通过value属性给后代传递数据
<myContext.Provider value={x:1}>
<Son/>
</myContext.Provider>
//3.后代组件读取
//(1)类中声明读取
static contextType=myContext
const {x}= this.context
//(2)函数与类中均可
<Consumer>{value=>value.x}</Consumer>
注意
在应用开发中一般不用context,一般都用它封装react插件
6.PureComponent 组件优化
问题
以下两个问题导致了组件渲染时效率低下
- 只要执行setState(),即使不改变状态数据, 组件也会重新render()。
- 只要当前组件重新render(), 子组件也会重新render,纵使子组件没有用到父组件的任何数据。
解决思路
只有在state或者props改变时才重新render,可在shouldComponentUpdate中控制,检查是否有变化来更新,但数据多了之后会造成代码冗余难以维护,故引入PureComponent,本质上也是控制了shouldComponentUpdate
使用
import React,{PureComponent} from 'react'
//仅改变了继承的父类
export default class People extends PureComponent{
render(){
return (
<div>People组件</div>
)
}
}
7.render props 插槽
如何向组件内部动态传递带内容的结构
Vue:
<Slot></Slot>
React:
1.使用children props: 直接在组件标签体中写想传递的结构
2.使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
使用
children props
<A>
<B>xxxx</B>
</A>
A组件中通过this.props.children可拿到 <B>xxxx</B>
问题: B组件拿不到A组件的数据
render props
<A render={(data) => <B data={data}></B>} > </A>
A组件:通过this.props.render(数据)传递数据
B组件:通过this.props.data读取A组件传入的数据
8.错误边界
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
export default class Parent extends Component {
state = {
hasError:'' //用于标识子组件是否产生错误
}
//生命周期函数,一旦后代组件报错,就会触发
static getDerivedStateFromError(error){
console.log('@@@',error);
return {hasError:error}
}
componentDidCatch(){
console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
}
9.开发模式配置代理服务器
方法一
在package.json中追加如下配置
"proxy":"https://后端api"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 假设前端devServer在3000端口,开启代理后直接请求3000端口即可,不会引起跨域。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给后端api(优先匹配前端资源)
方法二
-
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
-
编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware') module.exports = function(app) { app.use( proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给后端api) target: 'http://后端api', changeOrigin: true, //控制服务器接收到的请求头中host字段的值 /* changeOrigin设置为true时,服务器收到的请求头中的host为:后端的地址 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000 changeOrigin默认值为false,但我们一般将changeOrigin值设为true */ pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置) }), proxy('/api2', { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2': ''} }) ) }
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。