1. react高阶组件
1.1 高阶组件的概念
高阶组件(Higher Order Component,简称:HOC ): 是 React 中用于重用组件逻辑的高级技术, 它本身不是react中的组件, 而是一个函数, 这个函数接受一个react组件作为参数,并返回一个新组件, 实现了对原有组件的增强和优化, 可以对原有组件中的state, props和逻辑执行增删改操作, 一般用于代码重用和组件增强优化
概述:高阶组件是一个函数,而不是组件,但其参数和返回值都是组件
1.2 高阶组件的使用场景
1.2.1 需要代码重用时
react如果有多个组件都用到同一端逻辑,这是就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中,从而减少代码的逻辑重复。
1.2.2 需要组件增强优化时
比如我们在项目中使用的组件有些不是自己写的,而是从网上撸下来的,但是第三方写的组件可能比较复杂,有时不能完全满足需求,且第三方组件不易修改,此时也可以用高阶组件。在不修改原始组件的前提下,对组件添加满足实际开发需求的功能。
一般来说,高阶组件在的父组件,会对原始组件模板做增强优化
return (
<div>
<nav><h3>这是高阶组件增加的导航栏</h3></nav>
<OldCom/>
</div>
)
1.3 高阶组件的实现方式
1.3.1 属性代理
流程:通过创建新组件来包裹原始组件,把原始组件作为新组件的子组件渲染
功能:可实现对原始组件的
props数据更新
和组件模板更新
示例代码如下:
import React, { Component } from 'react'
function myHoc(OldCom){
return class NewCom extends Component {
constructor(props){
super(props);
console.log(props); // 路由信息会传入高阶组件的props中,可以对它进行增删改
}
render() {
return (
// 如果返回一个新模板,相当于把原始组件直接替换了,这就是渲染劫持
return <div><h1>这是订单页的高阶组件</h1></div>
// 如果要返回原始组件模板,把原始组件作为子组件返回即可
// return <OldCom/>
)
}
}
}
export default myHoc // 导出高阶组件这个函数
注意:在构造器中路由信息会传入高阶组件的props中,可以对它进行增删改
由于props是只读的,不能修改,要对其进行深复制,然后再进行修改
有两种深复制方式如下:
// this.tempProps = JSON.parse(JSON.stringify(props))
- 第一种深复制会丢失函数,因为JSON里面不识别函数,所以不能放函数
this.tempProps = {...props}
- 第二种深复制不会丢失函数,但只能深复制第一层。
此处使用第二种方式,对props进行增删改操作:
- 增
this.tempProps.name = "张三"
console.log(this.tempProps);
- 删
delete this.tempProps.match // ES5 删除属性
var { location, ...tempObj} = this.tempProps // ES6 解构删除
this.tempProps = tempObj
console.log(this.tempProps);
- 改
this.tempProps.history.action = "PUSH"
在这里只能操作props,不能操作state,无法调用原始组件的state,如果需要修改state数据,请使用 反向继承 实现
在render()渲染函数中,如果需要修改props,需要传入修改后的深复制品
return (
<div>
<nav><h3>这是高阶组件增加的导航栏</h3></nav>
<OldCom {...this.tempProps}/>
</div>
)
在高阶组件中还可以做登录验证,类似与vue中的路由守卫。
问题:由于路由中配置的是高阶组件,所以路由信息传入到高阶组件中,原始组件中就没有路由信息了,怎么解决?
- 因为此时原始组件中已经没有路由信息了,即没有登录状态,可通过登录状态做一个if判断。若无登录状态,则返回空,并在100秒后跳转到登录页,若有登录状态,则返回原始组件模板页面。
console.log("orderHoc", this.props);
if(this.isLogin){
return <OldCom/>
}else{
setTimeout(()=>{
this.props.history.push("/login")
}, 100)
return ""
}
- 此时需要在render()函数的返回的原始组件中,动态拼接this.props即可传入路由信息
return (
<div>
<nav><h3>这是高阶组件增加的导航栏</h3></nav>
<OldCom {...this.props}/>
</div>
)
1.3.2 反向继承
流程:通过创建新组件继承自原始组件类,把新组件作为子类,原始组件作为父类
功能:可实现对原始组件的
state状态数据更新
和组件模板更新
注意:反向继承中,新组件和原始组件要求必须都是类组件
示例代码如下:
import React, { Component } from 'react'
function myHoc(OldCom){
return class NewCom extends OldCom{
constructor(props){
super(props);
// 因为当前组件类继承于原始组件类,子类可以直接调用父类的数据
console.log("myHoc2", this.state, this.add);
// this.setState({ // 此时未能渲染数据
// count: 100
// })
}
render(){
return <div>
<nav><h3>这是高阶组件中的导航栏</h3></nav>
{super.render()}
</div>
}
}
}
export default myHoc
反向继承中,不能返回原始组件标签,因为原始组件是父类,不能作为子组件渲染
return <OldCom/> // 错误写法
需要使用super调用父类的render渲染函数,渲染父类模板
return super.render()
需要在组件渲染之后更新state数据
componentDidMount = () => {
this.setState({
count: 100,
age: 20
}, ()=>{
this.add()
})
}
- this.setState()为异步更新,可在其回调的箭头函数中调用函数
- 而在构造器中只能调用读取state数据,不能调用setState更新
1.4 高阶组件的渲染劫持
高阶组件的渲染劫持:通过高阶组件把原始组件的模板进行修改和替换
1.4.1 渲染劫持概念
渲染劫持指对一个组件渲染内容的装饰或修改, 一般通过高阶组件来实现, 把一个组件传入高阶组件, 可以对这个组件的模板进行修改后执行渲染, 也可以阻止组件渲染, 并修改组件中的数据和逻辑
1.4.2 渲染劫持的应用
一般用于一些需要登录状态的页面, 在路由请求渲染页面(如订单页)之前, 使用高阶组件判断是否已登录, 如果已登录, 返回订单页模板, 如果没有登录, 返回空, 并跳转到登录页
1.5 高阶组件的实现步骤
1.5.1 新建高阶组件文件 MyHOC.jsx
1.5.2 在文件中创建函数
- 函数的参数是一个组件OldCom, 函数的返回值也是一个组件 NewCom
function myHoc(OldCom){
return class NewCom extends React.Component{
render(){
let newProps = { age: 10, sex: '男' }
return (
<OldCom {...newProps} ></OldCom>
)
}
}
}
属性代理(上)或者(反向继承)
function myHoc (OldCom){
return class NewCom extends OldCom{
componentDidMount() {
this.setState({ name: '李四' })
}
render() {
return super.render()
}
}
}
1.5.3 导出高阶组件函数
export default myHoc
1.5.4 在需要使用高阶组件的原始组件中导入
import MyHOC1 from "./OrderHOC1" // 导入属性代理高阶组件
import MyHOC2 from "./OrderHOC2" // 导入反向继承高阶组件
1.5.5 在导出组件时,使用高阶组件处理之后,再导出
export default MyHOC1(Order) // 属性代理高阶组件处理
// export default MyHOC2(Order) // 反向继承高阶组件处理
原始组件示例代码:
// 这是定义原始组件的页面
import React, { Component } from 'react'
import MyHOC1 from "./OrderHOC1"
import MyHOC2 from "./OrderHOC2"
class Order extends Component {
state = { count: 0 }
add = ()=>{
this.setState({
count: this.state.count + 1
})
}
render() {
console.log("order", this.state, this.props);
return (
<div>
<h1>订单页</h1>
<h2>
{this.state.count}
<button onClick={this.add}>+</button>
</h2>
</div>
)
}
}
export default MyHOC1(Order)
// export default MyHOC2(Order)
2. hooks
hooks语法只适用于函数式组件中,不能用于类组件
2.1 什么是hooks?
hooks是react新版本提供的组合式API语法,类似于vue3组合式API(也叫hooks)
2.2 hooks有什么用?
使函数式组件拥有组件状态和生命周期功能
提供运行效率
避免this指向问题(可以和vue3一样)
2.3 在函数式组件中, 使用hooks语法模拟状态数据的步骤
2.3.1 从react中导入语法函数useState
import React, { useState } from "react"
2.3.2 在函数式组件中, 使用useState创建状态数据
使用useState创建状态数据,参数是默认值,返回值是 数组
数组中第一个值是状态数据的变量名,第二个值是自定义的更新函数,调用更新函数会刷新视图
有三种类型:
- 直接定义单个变量
const [count, setCount] = useState(100)
- 定义多个变量使用对象结构
const [state, setState] = useState({
age: 10,
name: "张三",
roomList: []
})
- 定义数组
const [arr, setArr] = useState([1, 2, 3])
2.3.3 在组件模板中, 直接调用状态名即可{count}
2.3.4 使用useState函数返回的更新函数,修改状态值setCount(count + 1)
- 调用更新函数,更新状态,参数是最新值,修改后自动刷新界面
- 渲染单数据的写法
<button onClick={()=>{
setCount(count + 1)
}}>{count}</button>
- 渲染对象数据的写法
<button onClick={()=>{
state.age ++
// setState(state) // 错误写法
setState({
...state
})
}}>{state.name}: {state.age}
{ // 渲染斗鱼数据
state.roomList.map(item=>{
return <div key={item.room_id}>
<img src={item.room_src} alt="" />
</div>
})
}
</button>
- 修改对象中的状态数据时可以先修改,再拼接上对应的状态名
- 使用useState渲染数组中数据的写法
<button onClick={()=>{
arr[1] ++
// setArr(arr) // 错误写法,不会更新视图
setArr([...arr])
}}>{arr}</button>
- 注意:useState定义的引用类型数据,更新时,需要修改数据的内存地址(深拷贝/深复制),才会更新视图
2.4 使用hooks中的useEffect函数实现函数式组件的生命周期
默认第一个参数是回调函数,当组件初始化完成和状态更新时调用,类似于类组件中的render()渲染函数。
第二个参数可以控制回调的调用时机,是一个数组,可选,数组中是状态名,指定那些状态值更新会触发回调函数
有三种实现方法:
- 监听所有的数据更新
- 如果不加第二个参数,初始化时调用,任何状态更新都会调用
useEffect(()=>{
console.log("组件初始化,或有状态更新ComponentWillUpdate")
})
- 只在初始化时监听调用
- 如果第二个参数是空数组,则只在初始化时调用,状态更新时不会调用
useEffect(()=>{
console.log("组件初始化")
}, [])
- []表示只在初始化时调用,相当于生命周期componentDidMount
可以在这里请求数据,如请求斗鱼数据:
axios.get("/api/RoomApi/live", {
page: 1
}).then(res=>{
console.log(res.data.data);
setState({
age: 25,
name: "计科",
roomList: res.data.data
})
})
注意:请求数据需要写在useEffect函数的回调中,而[]只在初始化时调用,所以不能写在[]中
- 只监听某些数据的更新
- 如果第二个参数数组中有状态名, 则只会在数组中的状态更新时调用
useEffect(()=>{
console.log("组件初始化或count或arr更新")
}, [count, arr])
useEffect()函数的第二个参数是数组时,若里边写状态数据名,则是指定哪些数据更新时可执行回调
3.在react路由6.0中封装withRouter高阶组件
react-router6.0版本废弃了组件内的props路由信息( history, location, match ),6.0之后的版本,组件内默认都没有路由信息,包括withRouter高阶组件也被废弃了,需要使用hooks组合式API导入路由信息。
在react-router6.0中所有的组件都没有路由信息,也没有withRouter,需要使用hooks语法引入路由信息(history, location, match),所以我们可以自己封装一个withRouter高阶组件。
3.1 新建自己封装的withRouter函数文件withRouter.jsx
由于路由数据是在props中,所以使用属性代理方式,实现自封装的高阶组件。
3.2 在文件中导入自定义路由信息
import { useHistory, useLocation, useRouteMatch} from 'react-router-dom'
3.3 创建函数式组件,使用hooks语法
function myHoc(OldCom){
return ()=>{
// 返回组件模板
return <OldCom {...tempProps}/>
}
}
3.4 在返回的箭头函数中,使用hooks提供的组合式API,获取路由信息
const history = useHistory()
const location = useLocation()
const match = useRouteMatch()
3.5 返回组件模板
return <OldCom {...tempProps}/>
3.6 导出封装的高阶组件函数
export default myHoc
3.7 在根组件App.js中导入自己封装的withrouter高阶组件
当使用了hooks新语法,还想使用类组件,就可以使用自己封装的高阶组件来实现。
import withRouter from "./pages/Hooks/withRouter";
3.8 在导出根组件时,使用封装的withRouter包裹组件即可传入路由信息
export default withRouter(App);