- 作者:老汪软件技巧
- 发表时间:2024-08-27 04:01
- 浏览量:
诞生背景
本来是为了解决组件逻辑复用的问题,前一个解决方案mixin,虽然能彻底解决逻辑复用问题,但是会造成两个问题:
1.对原有体系的破坏 - 变量名污染;
2.使用过多时候,代码维护可读性都会变差,无法溯源;
因此,react团队提供了高阶组件方案,来代替mixin方案,去解决组件复用问题。
一、什么是高阶组件
相比于函数式编程中,
高阶函数的定义:参数是函数,返回值是新的函数。
高阶组件的定义:参数是组件,返回值是新的组件。
从本质上来看,高阶组件本质上就是一个高阶函数,只不过这个函数接受的参数和返回值都是组件,因此定名为高阶组件。
所以高阶组件,要迅速反应出他的表现形式:一个函数接受一个组件参数,函数体中新定义一个class类(类中的render返回组件参数),函数中返回这个新定义的类
二、高阶组件的本质
本质上是一种Hoc模式,即洋葱模型的一半路径。而每次执行高阶组件函数的时候,就是类似于工厂模式。因为高阶组件函数执行后产出的是一个具有公共逻辑的父组件在外面包着,内部含着原有的子组件。
如下图所示,高阶组件将公共的“组件逻辑”,放在更上一层的组件中(可以理解为父组件)。则导致如下变化:
三、使用高阶组件应用场景3.1 state抽离复用
这里相当于将原有各个子组件中重复定义并更新的state状态的这部分代码,抽离到父组件中,即在父组件定义这个state(通过工厂模式包装子组件)。然后用prop形式传递给子组件,代替了直接在子组件的state定义。
import React from 'react'
import {EventEmitter} from 'events'
// 定义学生类
class Children1 extends React.PureComponent{
constructor(props){
super(props)
this.state = {
list: []
}
}
componentDidMount() {
eventBus.addListener('update', this.update)
}
componentWillUnmount() {
eventBus.removeListener('update', this.update)
}
update = (list) => {
this.setState({
list: list
})
}
render() {
return (
<div>
<p>{this.props.name}p>
<p>{this.props.age}p>
<p>{this.props.country}p>
{
this.props.list.map((name, index) => {
return <p key={index}>{name}p>
})
}
div>
)
}
}
// 定义学生类
class Children2 extends React.PureComponent{
constructor(props){
super(props)
this.state = {
list: []
}
}
componentDidMount() {
eventBus.addListener('update', this.update)
}
componentWillUnmount() {
eventBus.removeListener('update', this.update)
}
update = (list) => {
this.setState({
list: list
})
}
render() {
return (
<div>
<p>{this.props.name}p>
<p>{this.props.age}p>
<p>{this.props.country}p>
{
this.props.list.map(name=>{
return <p key={name}>{name}p>
})
}
div>
)
}
}
class App extends React.PureComponent{
constructor(props){
super(props)
}
render(){
return (
<Provider value={{name:'编程老师', age:28}}>
<Teacher1 country={'中国'}/>
<Teacher2 country={'中国'}/>
<button onClick={()=>{this.btnClick()}}>点击button>
Provider>
)
}
btnClick(){
eventBus.emit('update', ['JavaScript', 'React', 'Node']);
}
}
export default App
上面的代码中就可以看出,子组件对于state的处理有着相同的逻辑,又是定义事件总线,又是调用更新函数,我们是否可以利用高阶组件使其逻辑抽离变得更简洁?
import React from 'react'
import {EventEmitter} from 'events'
// 定义学生类
class Children1 extends React.PureComponent{
render() {
return (
<div>
<p>{this.props.name}p>
<p>{this.props.age}p>
<p>{this.props.country}p>
{
this.props.list.map(name=>{
return <p key={name}>{name}p>
})
}
div>
)
}
}
// 定义学生类
class Children2 extends React.PureComponent{
render() {
return (
<div>
<p>{this.props.name}p>
<p>{this.props.age}p>
<p>{this.props.country}p>
{
this.props.list.map(name=>{
return <p key={name}>{name}p>
})
}
div>
)
}
}
// 定义高阶组件
function EnhancedComponent (WrappedComponent) {
class Teacher extends React.PureComponent{
constructor(props){
super(props)
this.state = {
list: []
}
}
componentDidMount() {
eventBus.addListener('update', this.update)
}
componentWillUnmount() {
eventBus.removeListener('update', this.update)
}
update = (list) => {
this.setState({
list: list
})
}
render() {
return (
<Consumer>{
value =>{
return (
<WrappedComponent {...value} {...this.props} {...this.state}/>
)
}
}Consumer>
)
}
}
return Teacher
}
// 使用高阶组件
const Teacher1 = EnhancedComponent(Children1)
const Teacher2 = EnhancedComponent(Children2)
class App extends React.PureComponent{
constructor(props){
super(props)
}
render(){
return (
<Provider value={{name:'编程老师', age:28}}>
<Teacher1 country={'中国'}/>
<Teacher2 country={'中国'}/>
<button onClick={()=>{this.btnClick()}}>点击button>
Provider>
)
}
btnClick(){
eventBus.emit('update', ['JavaScript', 'React', 'Node'])
}
}
export default App
3.2 拦截生命周期复用
场景:我们常常在组件的生命周期中进行fetch请求,然后进行数据处理,更新视图。如果其他组件也调用相同的fetch接口,那么这部分代码【组件逻辑】就可以抽离进行复用。
高阶组件实现方式:利用父组件的生命周期对子组件的生命周期进行事先拦截,这样提前在父组件进行了fetch请求并处理数据,然后通过props传递给子组件进行渲染。
import React, { PureComponent } from 'react'
// 定义一个页面类 假如此页面有很多数据要渲染
class OriginDetail extends PureComponent {
render() {
return (
<div>
<h2>Detail Pageh2>
<ul>
<li>数据列表1li>
<li>...li>
<li>数据列表1000li>
ul>
div>
)
}
}
// 定义一个高阶函数 能够拦截其生命周期函数 并计算当前页面渲染共用了多长时间
function logRenderTime(OriginComponent) {
return class extends PureComponent {
UNSAFE_componentWillMount() {
this.beginTime = new Date().getTime()
}
componentDidMount() {
this.endTime = new Date().getTime()
const interval = this.endTime - this.beginTime
console.log(`当前${OriginComponent.name}页面花费了${interval}ms渲染完成!`)
}
render() {
return <OriginComponent {...this.props}/>
}
}
}
// 实现高阶函数
const Detail = logRenderTime(OriginDetail)
class App extends PureComponent {
render() {
return (
<div>
<Detail/>
div>
)
}
}
export default App
3.3 代码复用,增强props
import React from 'react'
const UserContext = React.createContext({})
const {Provider, Consumer} = UserContext
// 定义老师类
class Teacher1 extends React.PureComponent{
render() {
return (
<div>
<Children1/>
div>
)
}
}
class Teacher2 extends React.PureComponent{
render() {
return (
<div>
<Children2/>
div>
)
}
}
// 定义学生类
class Children1 extends React.PureComponent{
render() {
return (
<Consumer>{
value =>{
return (
<div>
<p>{value.name}p>
<p>{value.age}p>
div>
)
}
}Consumer>
)
}
}
class Children2 extends React.PureComponent{
render() {
return (
<Consumer>{
value =>{
return (
<div>
<p>{value.name}p>
<p>{value.age}p>
div>
)
}
}Consumer>
)
}
}
class App extends React.PureComponent{
constructor(props){
super(props)
}
render(){
return (
<Provider value={{name:'编程老师', age:28}}>
<Teacher1/>
<Teacher2/>
Provider>
)
}
}
export default App
从上面代码中我们能够看到,在老师类和学生类中存在重复的代码,这样写显然是冗余的,那么我们就可以利用高阶组件复用公共逻辑并实现增强props。
import React from 'react'
const UserContext = React.createContext({})
const {Provider, Consumer} = UserContext
// 定义学生类
class Children1 extends React.PureComponent{
render() {
return (
<Consumer>{
value =>{
return (
<div>
<p>{this.props.name}p>
<p>{this.props.age}p>
div>
)
}
}Consumer>
)
}
}
class Children2 extends React.PureComponent{
render() {
return (
<Consumer>{
value =>{
return (
<div>
<p>{this.props.name}p>
<p>{this.props.age}p>
div>
)
}
}Consumer>
)
}
}
// 定义高阶组件
function EnhancedComponent (WrappedComponent) {
class Teacher extends React.PureComponent{
render() {
return (
<Consumer>{
value =>{
return (
<WrappedComponent name={value.name} age={value.age}/>
)
}
}Consumer>
)
}
}
return Teacher
}
// 使用高阶组件
const Teacher1 = EnhancedComponent(Children1)
const Teacher2 = EnhancedComponent(Children2)
class App extends React.PureComponent{
constructor(props){
super(props)
}
render(){
return (
<Provider value={{name:'编程老师', age:28}}>
<Teacher1/>
<Teacher2/>
Provider>
)
}
}
export default App
3.4 权限控制
import React from 'react'
// 定义用户信息类
class Info extends React.PureComponent{
render() {
return (
<div>用户信息div>
)
}
}
定义用户登录类
class Login extends React.PureComponent{
render() {
return (
<div>用户登录div>
)
}
}
// 定义高阶函数 登录鉴权
function EnhancedComponent (WrappedComponent) {
class Authority extends React.PureComponent{
render() {
if(this.props.isLogin){
return <Info/>
}else{
return <Login/>
}
}
}
return Authority
}
// 实现高阶函数
const AuthorityInfo = EnhancedComponent(Info)
class App extends React.PureComponent{
constructor(props){
super(props)
}
render(){
return (
<AuthorityInfo isLogin={true}/>
)
}
}
export default App
四、高阶组件方案带来的问题
下面我们从两个方面来评估逻辑复用问题的解决方案 - 高阶组件方案
4.1 是否彻底解决问题
的确可以彻底解决组件中的组件逻辑复用问题
4.2 是否带来新问题(1) 给原有体系是否带来破坏
并没有像mixin那样给原有体系带来破坏:即不存在变量污染的问题
(2) 解决方案的使用是否便利
存在着很多逻辑上的嵌套,包裹着很多代码,如果大量的使用高阶组件,对代码调试有一定的困难,对于一些刚入门的 React 新手来讲,也很难读懂代码。因此接下来就迎来了hooks时代。