文章目录
代理小服务
当我们在本地跑一个react脚手架项目的时候,webpack的devServer会建立一个本地的服务器将项目跑在这上面,然后还会额外开一个小服务器,专门用来作为本地客户端与远程服务端的代理中间人,如下:
如果没有这个本地代理会发生请求的跨域问题:
假设本地前端工程服务器启动地址为http://localhost:3000
,当我们去请求一个接口(例如 http://localhost:5000/a
) 时,服务端接收请求并返回了相应数据,数据回来的时候也是走5000端口,那么react脚手架服务上的axios就会进行拦截,因为跨域了,我们就获取不到这次返回的数据。
跨域相关知识:【网络基础】小白对同源与跨域的初步认识
如果我们配置了本地小代理,那么这个请求首先会在react脚手架服务上找对应地址/a
的资源,找不到会给这个本地小代理,让它帮我们去请求远程服务器。
由于代理服务器和远程服务器对接的都是5000端口,所以他俩不会产生跨域问题,能够正常发送接收数据。
而代理服务器在脚手架服务上,所以对接前端工程服务器的端口也是3000,那么代理接收到的服务器相应数据就能完美的转交给react脚手架服务上了。
由于设置了代理,所以脚手架服务请求的地址需要改为代理的地址,也就是同一个3000端口的地址,那么请求方法的url就从
http://localhost:5000/a
改为http://localhost:3000/a
才能正常的走代理服务器。
如果当前项目地址和请求地址一样,还可以把前面部分省略了直接/a
即可。
接下来简单讲下怎么在react中配置与代理有关的proxy
配置。
package.json中的proxy配置
例如:
"proxy":"http://localhost:5000"
就是给代理服务器指定远程请求地址为http://localhost:5000
,这种配置方式的缺点就是不能配置多个代理。下面来看看怎么配置多个代理。
setupProxy.js代理配置文件
在src下创建配置文件:src/setupProxy.js,这里面写的是CJS规范代码:
const proxy = require('http-proxy-middleware') // 这个库脚手架集成了
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置),否者请求过去的就是http://localhost:5000/api1/xxxx 而不是正确的 http://localhost:5000/xxxx
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀(可以通过全局变量解决)。
借助craco库拓展webpack配置
具体可以看:
介绍:https://www.lingjie.tech/article/2021-01-04/27
库的官方地址:https://github.com/dilanx/craco
也可以看看这个博客【React项目使用craco(由create-react-app创建项目)】
Fragment 根组件
jsx标签必须返回一个根元素,但有时候并不像渲染一个多余的根元素就可以使用这个,原理类似vue的template标签
<Fragment>
<img />
<img />
</Fragment>
还能在遍历中绑定key值,但有且仅有一个key属性
<Fragment key={xxxxxx}>
<img />
<img />
</Fragment>
如果不需要绑定key还有个超级简写的方法
<>
<img />
<img />
</>
按需打包引入
import React, {lazy } from 'react';
const Demo = lazy(()=>import('./demo')) // 这样打包出来的资源文件名称是哈希值
const Demo = lazy(()=>import(/* webpackChunkName: "demo" */ './demo')) // 这样打包出来的资源文件名称就是我们写的命名了
这种方式连css文件都会顺带分开打包
Suspense
不知道和vue3谁借鉴谁的(doge)
import React, { Component, Suspense } from 'react';
// 在模板中
<Suspense fallback={<div>loading...</div>}> // fallback意思是包裹的组件还没有出来的时候,显示什么
<Zizujian /> // 这种组件通常是按需引入组件或者数据量大加载比较久的
</Suspense>
一般都会封装成一个懒加载组件:
src/components/LazyLoad/index.js:
import React, { Component, lazy, Suspense } from 'react';
export default class Index extends Component {
constructor(props) {
super(props);
this.state = {
};
}
_renderLazy = () => {
let Lazy;
const { component, delay, ...other } = this.props; // 接收自定义组件、延迟加载时间,自定义组件上的自定义参数
if (!component || component.constructor.name !== 'Promise') { // 是否传入组件并且传入的是个组件,不是的话渲染一个出错提示页
Lazy = lazy(() => import('./error'));
} else {
Lazy = lazy(() => {
return new Promise(resolve => {
setTimeout(() => {
resolve(component); // 导出传入的组件
}, delay || 300);
})
});
}
return <Lazy {...other} />
}
render() {
return (
<div>
<Suspense fallback={<div>loading...</div>}>
{this._renderLazy()}
</Suspense>
</div>
)
}
}
src/components/LazyLoad/error.js:
import React, { Component } from 'react';
export default class Error extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
组件引入错误!
</div>
)
}
}
使用:
import LazyLoad from '@/components/LazyLoad'
// 在模板中
<LazyLoad component={import('./lists')} delay={300} name='小明'/>
createPortal
这个就是把react dom插入到一个元素里,常用于构建全局弹窗。附个使用例子:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
export default class CreatePortal extends Component {
constructor(props) {
super(props);
this.body = document.querySelector('body');
this.el = document.createElement('div');
}
componentDidMount() {
this.el.setAttribute('id', 'portal-root');
this.body.appendChild(this.el);
}
componentWillUnmount() {
this.body.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(this.props.children, this.el) // 把react dom插入到创建的元素,然后这个元素又被插入到body元素里
}
}
然后写全局弹窗的组件就可以引入这个组件:
import React, { Component } from 'react';
import CreatePortal from './CreatePortal';
export default class Modal extends Component {
constructor(props) {
super(props);
this.state = {};
}
handleClose = ()=>{ // 点击关闭(暴露)
const { onClose } = this.props;
onClose && onClose();
}
render() {
const { show } = this.props;
return (
<>
{show ? <CreatePortal>
<div>
{this.props.children}
<div onClick={this.handleClose}></div>
</div>
</CreatePortal> : null}
</>
)
}
}
然后就可以使用了:
import React, { Component } from 'react';
import Modal from '@/components/Modal';
import { Button } from 'antd-mobile';
export default class Index extends Component {
constructor(props) {
super(props);
this.state = {
show: false
};
}
handleClick = ()=>{
this.setState({
show: true
})
}
handleClose = ()=>{
this.setState({
show: false
})
}
render() {
return (
<div>
<Button type='primary' onClick={this.handleClick}>modal</Button>
<Modal
show={this.state.show}
onClose={this.handleClose}>modal</Modal>
</div>
)
}
}