React HoC初体验

189 阅读2分钟

HoC是什么?

高阶组件(HoC)是 React 中用于 复用组件逻辑 的一种高级技巧。HoC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。重点是复用逻辑

HoC传送门

不使用HoC

Child1组件:

// Child1组件:监听窗口resize,展示窗口大小
const Child1 = () => {
  const [position, setPosition] = useState({
    x: document.documentElement.clientWidth,
    y: document.documentElement.clientHeight,
  })

  const updatePosition = () => {
    setPosition({
      x: document.documentElement.clientWidth,
      y: document.documentElement.clientHeight,
    })
  }

  useEffect(() => {
    window.addEventListener('resize', updatePosition)
    return () => {
      window.removeEventListener('resize', updatePosition)
    }
  }, [position])
  return (
    <div>
      x:{position.x}
      y:{position.y}
    </div>
  )
}

Child2组件:

// child2组件,也是展示窗口的大小
const Child2 = () => {
  const [position, setPosition] = useState({
    x: document.documentElement.clientWidth,
    y: document.documentElement.clientHeight,
  })

  const updatePosition = () => {
    setPosition({
      x: document.documentElement.clientWidth,
      y: document.documentElement.clientHeight,
    })
  }

  useEffect(() => {
    window.addEventListener('resize', updatePosition)
    return () => {
      window.removeEventListener('resize', updatePosition)
    }
  }, [position])
  return (
    <div>
      x:{position.x}
      y:{position.y}
    </div>
  )
}

展示:

import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  return (
    <div>
      <Child1 />
      <Child2 />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

使用HoC提取相同逻辑

1:提取相同逻辑给包装组件,入参是组件(可以接收多个参数),出参也是组件

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

// 1: 约定采用with开头命名,函数组件内部返回一个类组件
const withResize = (MyComponent) => {
  // 2: 形参首字母大写
  return class Resize extends Component {
    //----------------- 共同的逻辑
    state = {
      x: document.documentElement.clientWidth,
      y: document.documentElement.clientHeight,
    }

    updatePosition = () => {
      this.setState({
        x: document.documentElement.clientWidth,
        y: document.documentElement.clientHeight,
      })
    }

    componentDidMount() {
      window.addEventListener('resize', this.updatePosition)
    }
    componentWillUnmount() {
      window.removeEventListener('resize', this.updatePosition)
    }
    //----------------- 共同的逻辑
    render() {
      //HOC 不会修改传入的组件,也不会使用继承来复制其行为 将数据通过props方式传递
      return <MyComponent {...this.state} />
    }
  }
}

2:修改子组件逻辑,通过props接收包装组件传递的数据

const Child1 = (props) => {
  //通过props接收包装组件传递的数据
  return (
    <div>
      x:{props.x}
      y:{props.y}
    </div>
  )
}

const Child2 = (props) => {
  //通过props接收包装组件传递的数据
  return (
    <div>
      x:{props.x}
      y:{props.y}
    </div>
  )
}

3:接收被包装后的组件并渲染

// 3:接收被包装后的组件并渲染
const Child1WithResize = withResize(Child1)
const Child2WithResize = withResize(Child2)

const App = () => {
  // 4:渲染
  return (
    <div>
      <Child1WithResize />
      <Child2WithResize />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

使用HoC 实现条件渲染

当data为空数组的时候 渲染<Empty/>组件,否则正常渲染列表

import React, { useState } from 'react';
import { Empty, Menu } from 'antd';
import { isArray, isEmpty } from 'lodash';

const defalutData = [
  { label: 'JavaScript', value: 1 },
  { label: 'Vue', value: 2 },
  { label: 'React', value: 3 },
];

export default function ({ value = defalutData, ...others }) {
  const [data, setData] = useState(value);

  const List = ({ data }) =>
    isArray(data) && (
      <Menu style={{ width: 200 }}>
        {data?.map((item, index) => (
          <Menu.Item key={index} value={item?.value}>
            <span style={{ color: 'white' }}>{item?.label}</span>
          </Menu.Item>
        ))}
      </Menu>
    );

  const WithData = (Component) => {
    return function WrapperedComponent({ data, ...others }) {
      // 条件渲染
      if (isEmpty(data)) return <Empty />;
      return <Component data={data} {...others} />;
    };
  };

  const WithDataComponent = WithData(List);

  return <WithDataComponent data={data} {...others} />;
}

image.png