React高阶组件

343 阅读2分钟

一、什么是高阶组件

⾼阶组件(HOC)是 React 中⽤于复⽤组件逻辑的⼀种⾼级技巧。HOC ⾃身不是 React API 的⼀部分,它是⼀种基于 React 的组合特性⽽形成的设计模式。 简单点说,就是组件作为参数,返回值也是组件的函数,它是纯函数,不会修改传⼊的组件,也不会使 ⽤继承来复制其⾏为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作⽤。

二、为什么使用HOC

  1. 抽取重复代码,实现组件复⽤:相同功能组件复⽤

  2. 条件渲染,控制组件的渲染逻辑(渲染劫持):权限控制。

  3. 捕获/劫持被处理组件的⽣命周期,常⻅场景:组件渲染性能追踪、⽇志打点。

三、HOC实现⽅式

3.1 属性代理

使⽤组合的⽅式,将组件包装在容器上,依赖⽗⼦组件的⽣命周期关系

  1. 返回stateless的函数组件
  2. 返回class组件 操作props
// 可以通过属性代理,拦截⽗组件传递过来的props并进⾏处理。 

// 返回⼀个⽆状态的函数组件 
function HOC(WrappedComponent) {
    const newProps = { type: 'HOC' }
    return props => <WrappedComponent {...props} {...newProps} />
}

// 返回一个有状态的class组件
function HOC(WrappedComponent) {
    return class extends React.Component {
       render() {
          const newProps = { type: 'HOC' }
          return <WrappedComponet {...this.props} {...newProps}>
       }
    }
}
  • 抽象state
function HOC(WrappedComponent) {
    return class extends React.Component {
      constructor(props) {
        super(props)
        this.state = {
          name: ''
        }
        this.onChange = this.onChange.bind(this)
      }
      onChange(event) {
        this.setState({
          name: event.target.value,
        })
      }
      render() {
        const newProps = {
          name: {
            value: this.state.name,
            onChange: this.onChange,
         },
       };
        return <WrappedComponent {...this.props} {...newProps} />;
     }
    }
}
  • 条件渲染
function HOC(WrappedComponent) {
  return (props) => {
    <div>
      {
        props.isShow ? (<WrappedComponent {...props} />) : <div>暂无数据</div>
      }
    </div>
  }
}
3.2 反向继承

⽤⼀个函数接受⼀个组件作为参数传⼊,并返回⼀个继承了该传⼊组件的类组件,且在返回组件的 render() ⽅法中返回 super.render() ⽅法

const HOC = (WrappedComponent) => {  
return class extends WrappedComponent {    
    render() {     
        return super.render();  
    } 
   } 
 }
  1. 允许HOC通过this访问到原组件,可以直接读取和操作原组件的state/ref等

  2. 可以通过super.render()获取传⼊组件的render,可以有选择的渲染劫持

  3. 劫持原组件⽣命周期⽅法

function HOC(WrappedComponent) {
  const didMount = WrappedComponent.prototype.componentDidMount
  return class HOC extends WrappedComponent {
    async componentDidMount() {
      if(didMount) {
        await didMount.apply(this)
      }
      this.setState({ number: 2 })
    }
    render() {
      return super.render()
    }
  }
}
  1. 条件渲染
const HOC = (WrappedComponent: any) => {
    return class extends WrappedComponent {
        render() {
            if(this.props.isRender) {
                return super.render()
            }else {
                return <div>暂无数据</div>
            }
        }
    }
}

3.3 实际用例
  1. 电影列表

功能:通过自定义接口给目标组件传递数据

components/hoc.jsx

export const withFetchingHOC = (WrappedComponent, fetchingMethod, defaultProps) => {
    return class extends Component {
        state = {
            data: []
        }
        async componentDidMount() {
            const data = await fetchingMethod()
            this.setState({
                data
            })
        }
        render() {
            return <WrappedComponent
                data={this.state.data}
                {...defaultProps}
                {...this.props}
            ></WrappedComponent>
        }
    }
}

components/test.tsx

interface test1Props {
    data: Array<String>
}

export default function test1(props: test1Props) {
    let data = props.data
    return (<>
        {
            data.map((item, index) => {
                return (
                <div key={index}>
                    { item }
                </div>)
            })
        }
    </>)
}

utils/index.ts

export const fetchMovieListByType = (type: string) => {
    return () => new Promise((resolve, reject) => {
        let actionArr = ['蜘蛛侠', '钢铁侠', '蝙蝠侠']
        let likeArr = ['泰坦尼克号', '怦然心动']
        let comicArr = ['海贼王', '电锯人', '火影忍者']
        if(type === 'action') {
            resolve(actionArr)
        }else if(type === 'like') {
            resolve(likeArr)
        }else {
            resolve(comicArr)
        }
    })
}

app.tsx



import { withFetchingHOC } from './components/hoc'
import { fetchMovieListByType } from './utils/index'
import Test from './components/test'
import './App.css'
let HocTest = withFetchingHOC(Test, fetchMovieListByType('like'), {})
let HocTest2 = withFetchingHOC(Test, fetchMovieListByType('action'), {})
function App() {

  return (
    <div>
      <HocTest></HocTest>
      <div className='split'></div>
      <HocTest2></HocTest2>
    </div>
  )
}

export default App


效果:

image.png