前言

eg:代表代码对照 若文章有误,欢迎读者留言反馈

传送门

问题:在项目中某些情况下,子元素样式会受父元素样式的影响
传送门作用:让当前元素逃离父元素,去想去的地方【一般是body】
使用场景:

  1. overflow: hidden
  2. 父组件z - index值太小,逃离父组件
  3. fixed需要放在body第一层级
  4. loading 弹框 全局组件 也应该放在最外面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class PortalDemo extends Component {
render() {
return (
<div>
<div>
<div>
{/* 当前div逃离当前父元素,然后去body下面,参数1 将要逃离的子元素,参数2 逃离到哪里去 */}
{/* 注意:样式和功能不变,只是最终的渲染位置跑到指定的位置。 */}
{
ReactDOM.createPortal(<div className='portal-class'>
<h3>我是标题</h3>
<div>我是内容</div>
</div>, document.body)
}
</div>
</div>
</div>
)
}
}

React中的Axios

Axios发请求

  1. react中通过npm来安装axios插件
  2. npm i -S axios
  3. 引入`axios包

挂载后,发送请求【组件渲染完成就开始发送请求,将请求到的数据渲染】

get方法请求[第一参数为请求接口地址,第二参数为对象,param形参名随意设定,但是里面params固定,简写{ params: id: 11, name: ‘aa’ }]

1
2
3
// 语法格式:
// axios.get(url, param: { params: { id: 11, name: 'aa' }}).then(res => { // 数据操作(res.data就是返回的数据) })
axios.get('https://api.i-lynn.cn/ip').then(res => this.setState({obj: res.data}))

post方法请求[第一参数为请求接口地址,第二参数为对象,简写{ firstName: ‘black’ }]

1
2
3
// 语法格式:
// axios.post(url, data: { firstName: 'black' }).then(res => { // 数据操作(res.data就是返回的数据) })
axios.post('https://api.i-lynn.cn/ip').then(res => this.setState({ obj: res.data }))

对象写法【常规写法】

1
2
3
4
5
6
7
8
9
// get方式
axios({ method: 'get', url: 'https://api.i-lynn.cn/ip', params: {}, headers: {} }).then(res => {
this.setState({obj: res.data})
})

// post方式【后面data不一定非得data,如果data不行就使用params】
axios({ method: 'post', url: 'https://api.i-lynn.cn/ip', data: {}, headers: {} }).then(res => {
this.setState({obj: res.data})
})

post方式也能使用params,拼接到url后面,具体原因分析:http://www.qianduanheidong.com/blog/article/319066/b5ef11c3754262a378ec/

eg1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import React, { Component } from 'react'

// Axios发请求
// 1. react中通过npm来安装axios插件
// 2. npm i -S axios
// 3. 引入axios包
import axios from 'axios'

class AxiosDemo extends Component {
state = {
obj: {}
}
// 挂载后,发送请求【组件渲染完成就开始发送请求,将请求到的数据渲染】
componentDidMount() {
// get方法请求[第一参数为请求接口地址,第二参数为对象,param形参名随意设定,但是里面params固定,简写{ params: id: 11, name: 'aa' }]
// axios.get(url, param: { params: { id: 11, name: 'aa' }}).then(res => { // 数据操作(res.data就是返回的数据) })
// axios.get('https://api.i-lynn.cn/ip').then(res => this.setState({obj: res.data}))
// post方法请求[第一参数为请求接口地址,第二参数为对象,简写{ firstName: 'black' }]
// axios.post(url, data: { firstName: 'black' }).then(res => { // 数据操作(res.data就是返回的数据) })
// axios.post('https://api.i-lynn.cn/ip').then(res => this.setState({ obj: res.data }))
// 对象写法【常规写法】
// axios({ method: 'get', url: 'https://api.i-lynn.cn/ip', params: {}, headers: {} }).then(res => {
// this.setState({obj: res.data})
// })
axios({ method: 'post', url: 'https://api.i-lynn.cn/ip', data: {}, headers: {} }).then(res => {
this.setState({obj: res.data})
})
}
render() {
return (
<div>
<h3>Axios发请求</h3>
<p>{this.state.obj.area}</p>
<p>{this.state.obj.country}</p>
<p>{this.state.obj.ip}</p>
</div>
)
}
}

export default AxiosDemo

post方式也能使用params,拼接到url后面,具体原因分析:http://www.qianduanheidong.com/blog/article/319066/b5ef11c3754262a378ec/

反向代理

反向代理用途:解决浏览器跨域问题
同源策略 http://localhost:8080 请求 https://api.i-lynn.cn/ip 协议域名端口号有一个不同就报跨域错误。
如何解决跨域:

  1. 前端: 反向代理
  2. 后端: cors 反向代理
  3. 服务器: linux 正向 反向代理

React的反向代理

  1. 安装代理插件 npm i -S http-proxy-middleware
  2. src目录下,创建一个setupProxy.js文件【该文件不需要引入到哪里,配置完就可以使用了】
  3. 开始配置反向代理

setupProxy.js文件配置代码如下[可以配置多个代理,例如/bpi]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { createProxyMiddleware: proxy } = require('http-proxy-middleware')

module.exports = app => {
// 第一个参数api接口的别名
app.use('/api', proxy({
target: 'https://api.i-lynn.cn', // 被代理的域名[我们真实请求接口地址的域名]
changeOrigin: true, // 开启反向代理
pathRewrite: { // 重定向[与接口别名保存一致]
'^/api': ''
}
}))
// app.use('/bpi', proxy({
// target: 'https://www.baidu.com', // 被代理的域名[我们真实请求接口地址的域名]
// changeOrigin: true, // 开启反向代理
// pathRewrite: { // 重定向[与接口别名保存一致]
// '^/bpi': ''
// }
// }))
}
// 配置完之后使用/api替换域名

如何使用[以get为例]:

1
2
3
4
5
6
7
8
componentDidMount(){
// 以get为例
axios.get('/api/ip').then(res=>{
this.setState({
obj:res.data
})
})
}

没做反向代理前

没做反向代理前

做了反向代理后

做了反向代理后

异步组件

  1. 异步引入组件[执行时不加载,需要用到的时候加载]
1
const Child = React.lazy(() => import('./Child'))
  1. 对比一下vue异步组件
1
components: { Child: () => import('./Child') }
  1. 异步组件使用[使用React.Suspense标签包裹异步组件标签占位符]

fallback属性可选,fallback是异步引入的loading销毁,fallback可以放一些loading好看的效果,刷新几下可以看到效果

1
2
3
<React.Suspense fallback={<h2>------loading--------</h2>}>
<Child />
</React.Suspense>
  1. 对比同步组件[直接使用]
1
2
// 同步组件使用
<ChildDemo />

SCU性能优化

shouldComponentUpdate(简称SCU

react里面父组件刷新,他所有子组件都会自动刷新,SCU可以解决某个子组件依赖数据不发生变化,而对其不刷新

SCU 一定要每次都用吗?—— 需要的时候才优化,没有特殊要求,可以先完成功能为主,后期再优化

num的变化,当前组件要刷新,但是Child组件依赖数据没有变化,所以Child组件可以不用刷新,性能提升的点。

模拟场景:父组件里放一个num、arr,当我们更改num,父组件更新子组件无条件更新,而子组件依赖于arr,我们对子组件做性能优化(让其不更新):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 父组件
state = {
num: 0,
arr: [1,2,3,4], // arr是对象,就1层
}

addHandle = () => {
this.setState({
num: this.state.num+1
})
}
render() {
return (
<div>
<h3>SCU单性能优化</h3>
<p>{this.state.num}</p>
<button onClick={this.addHandle}>点击+1</button>
<Child1 arr={this.state.arr}/>
</div>
)
}

我们要对Child1子组件做SCU,性能优化,this.props.arr数组没有改变,Child1组件中的render就不用执行。

  1. 需要用到shouldComponentUpdate(nextProps, nextState)
  2. SCUrender的开关,默认返回是true,默认情况下render是要被渲染的。
  3. nextProps是最新的props,并不等于this.props,所以我们可以判断nextPropsthis.props是否相等就可以判断出数据有没有变化。
  4. nextState就是最新state.并不等于this.state这两个也可以进行SCU的比较。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 子组件
export default class Child extends Component {
// SCU是render的开关,默认返回是true,默认情况下render是要被渲染的。
// nextProps是最新的props,并不等于this.props,所以我们可以判断nextProps和this.props是否相等就可以判断出数据有没有变化。
// nextState就是最新state.并不等于this.state 这两个也可以进行scu的比较。
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.arr === this.props.arr) { //对象的1层的比较值,==是可以比较的
return false // 不进行重新渲染
}
return true // 默认值,默认进行渲染
}
render() {
console.log('Child1子组件渲染了')
return (
<div>Child1
<ul>{
this.props.arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}</ul>
</div>
)
}
}

上面只是浅比较,深比较【递归,借助第三方模块】

  1. 安装插件:npm i -S lodash
  2. 子组件需要做SCU引入import _ from 'lodash'
  3. lodash工具类 _.isEqual(obj1,obj2)递归比较对象的值是否相等 相等返回true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 父组件
state = {
num: 0,
person: {
name: '小明',
jobs: ['前端', '后端'] // 多层的对象
}
}

addHandle = () => {
this.setState({
num: this.state.num+1
})
}
render() {
return (
<div>
<h3>SCU单性能优化</h3>
<p>{this.state.num}</p>
<button onClick={this.addHandle}>点击+1</button>
<Child3 person={this.state.person}/>
</div>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 子组件
import _ from 'lodash'
export default class Child2 extends Component {
shouldComponentUpdate(nextProps, nextState) {
//nextProps.person this.props.person 比较这两个对象的值是不是发生改变。递归
// lodash 工具类 _.isEqual(obj1,obj2)递归比较对象的值是否相等 相等返回true
var b = _.isEqual(nextProps.person, this.props.person)
if (b) return false // 不进行重新渲染
return true // 默认值,默认进行渲染
}
render() {
console.log('child2更新了')
return (
<div>Child2
<p>{this.props.person.jobs}</p>
</div>
)
}
}

类组件还自带一个浅比较的SCU,继承PureComponent来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 父组件
state = {
num: 0,
arr: [1,2,3,4] // arr是对象,就1层
}
addHandle = () => {
this.setState({
num: this.state.num+1
})
}
render() {
return (
<div>
<h3>SCU单性能优化</h3>
<p>{this.state.num}</p>
<button onClick={this.addHandle}>点击+1</button>
<Child3 arr={this.state.arr}/>
</div>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 子组件【注意引入的是PureComponent】
import React, { PureComponent } from 'react'

// 类组件自带一个浅比较的SCU,继承PureComponent来实现,注意:数据的浅比较
export default class Child3 extends PureComponent {
render() {
console.log('Child3子组件渲染了')
return (
<div>Child3
<ul>{
this.props.arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}</ul>
</div>
)
}
}

函数组件可以使用memo高阶组件来实现浅比较的SCU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 父组件
state = {
num: 0,
arr: [1,2,3,4] // arr是对象,就1层
}
addHandle = () => {
this.setState({
num: this.state.num+1
})
}
render() {
return (
<div>
<h3>SCU单性能优化</h3>
<p>{this.state.num}</p>
<button onClick={this.addHandle}>点击+1</button>
<Child4 arr={this.state.arr}/>
</div>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 子组件【注意引入的是高阶组件memo】
import React, { memo } from 'react'
// 函数组件可以使用memo高阶组件来实现浅比较的scu
// 高阶组件,组件作为参数,组件作为返回值,当前组件就叫高阶组件。
function Child5(props) {
console.log('Child4 更新了')
return <div>Child4
<ul>{
props.arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}</ul>
</div>
}
export default memo(Child4)

函数组件的深比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 父组件
state = {
num: 0,
jobs: ['前端', '后端'] // 多层的对象
}
addHandle = () => {
this.setState({
num: this.state.num+1
})
}
render() {
return (
<div>
<h3>SCU单性能优化</h3>
<p>{this.state.num}</p>
<button onClick={this.addHandle}>点击+1</button>
<Child5 person={this.state.person}/>
</div>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { memo } from 'react'
import _ from 'lodash'
// 函数组件深比较案例
const Child6 = (props) => {
console.log('Child5更新了')
return (
<div>Child5
<p>{props.person.jobs}</p>
</div>
)
}
// 用来比较nextProps中的person 和 props.person两个的值
function dispatchObj(nextPros, props) {
var b = _.isEqual(nextPros.person, props.person) //b true代表相等
// b===true返回true b===false返回false
if (b) {
return true
} else {
return false
}
}
// memo如果进行深度比较,参数2就是我们的比较函数
export default memo(Child5, dispatchObj)

SCU小结:

  • 类组件自带一个浅比较继承自PureComponent,不需要我们去做判断,同时如果对象只有一层我们也可以使用shouldComponentUpdate直接使用===比较,深比较依旧是可以借助递归或工具类lodash
  • 函数组件需要通过memo高阶组件来进行浅比较,不需要我们去做判断,深比较需要借助memo高阶组件和lodash

HOC高阶组件【函数组件】

  1. 高阶组件一般是函数组件,参数是组件,返回值也还是一个组件。
  2. 高阶组件是为了提取公共功能用的,公共逻辑。高阶组件写越得越多说明能力越强,质量越高。
  3. 封装高阶组件,创建函数组件,传入一个组件作为参数,return一个类组件,这个类组件相当于穿插到了父子组件之间
  4. 使用:引入高阶组件,把高阶组件当函数用就行了【哪个组件需要这个高阶组件功能就往哪里引入,调用】

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 父组件
import React, { Component } from 'react'
import Child from './Child'

class HOCDemo extends Component {
state = {
arr: [1, 2, 3, 4, 5]
}
render() {
return (
<div>
<h3>父组件</h3>
<Child arr={this.state.arr} />
</div>
)
}
}

export default HOCDemo

开始封装高阶组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from 'react'

// hoc 高阶组件
// 高阶组件一般是函数组件,把组件作为参数传递进去,返回值还是一个组件
// 高阶组件是为了提取公共功能、公共逻辑用的。高阶组件写越多说明能力越强,质量越高。

// 1. 封装高阶组件,创建函数组件,传入一个组件作为参数,return一个类组件,这个类组件相当于穿插到了父子组件之间
// 2. 使用:引入高阶组件,把高阶组件当函数用就行了【哪个组件需要这个高阶组件功能就往哪里引入,调用】

const HOC = (COM) => {
return class newCom extends Component {
render() {
return (
<div>
{/* {...this.props} 承上启下 高阶组件必须携带,否则出现父子通信中断的bug */}
<COM {...this.props}/>
</div>
)
}
}
}

export default HOC

子组件调用高阶组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component } from 'react'

// 引入高阶组件
import HOC from './HOC'

class Child extends Component {
render() {
return (
<div>
<h3>子组件</h3>
<ul>
{
this.props.arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}
}

// 调用高阶组件
export default HOC(Child)

开启装饰器模式,使用高阶组件语法糖@

装饰器模式的配置步骤:

  1. 配置装饰器支持
    在当前项目根目录下面创建一个名称为config-overrides.js文件,对webpack进行配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // react 配置文件
    const path = require("path")
    const {override,addDecoratorsLegacy,disableEsLint,addWebpackAlias} = require("customize-cra")
    // 配置项 覆盖webpack某些配置
    module.exports = override(
    disableEsLint(), // 在webpack中禁用eslint
    addDecoratorsLegacy(), // 开启装饰器
    addWebpackAlias({ // 路径别名配置
    ["@"]: path.resolve(__dirname, "./src"),
    })
    )
  2. npm i -D customize-cra react-app-rewired

  3. 到package.json中的script命令中修改”start”: “react-app-rewired start”

会出现的bug:
对装饰器的实验支持功能在将来的版本中可能更改。在 “tsconfig” 或 “jsconfig” 中设置 “experimentalDecorators” 选项以删除此警告。ts(1219)(https://www.cnblogs.com/Annely/p/14613567.html)

  1. npm i @babel/plugin-proposal-decorators -D
  2. 项目根目录下创建babel.config.js 或者 .babelrc.js,复制粘贴如下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    module.exports = {
    presets: [
    ["@babel/preset-env"], // ES语法转换
    ],
    plugins: [
    ['@babel/plugin-proposal-decorators', { 'legacy': true }]
    ]
    };
  3. vscode的设置里搜索Experimental Decorators打勾如下图

img

开启装饰器之后,使用语法糖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 高阶组件语法糖
import React, { Component } from 'react'

// 引入高阶组件
import HOC from './HOC'
// 装饰器模式 语法糖
// 调用高阶组件
@HOC
class Child extends Component {
render() {
return (
<div>
<h3>子组件</h3>
<ul>
{
this.props.arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}
}


export default HOC(Child)