Расширенное руководство React по компонентам высшего порядка

внешний интерфейс алгоритм React.js Redux
Расширенное руководство React по компонентам высшего порядка

Дорога далека, и я буду ходить вверх и вниз, чтобы найти ее. - Цюй Юань "Ли Сао"

написать впереди

Компоненты более высокого порядка не являются частью API RACT, но технология, полученная из повторного использования компонентной логики

Компонент более высокого порядка (HOC) представляет собой передовую технику в реакцию для повторного использования компонентной логики. HOCS не являются частью API RACT API. Они являются шаблон, который возникает из композиционной природы реагирования.

Прежде чем обсуждать компоненты более высокого порядка, давайте поговорим о функциях более высокого порядка.

Функции высшего порядка

Я написал статью раньшеКаррирование функций и анализ исходного кода промежуточного ПО Redux и applyMiddleware, о том, что такое функции высшего порядка, я уже говорил в этой статье. Давайте рассмотрим их еще раз. Так называемые функции высшего порядка это:

  • Функции могут использоваться в качестве параметров
  • Функции могут использоваться как возвращаемые значения

Такие как:

const debounce = (fn, delay) => {
    let timeId = null
    return () => {
        timeId && clearTimeout(timeId)
        timeId = setTimeout(fn, delay)
    }
}

Существует множество применений функций высшего порядка, функции устранения дребезга, регулирования функции, функции связывания, каррирования функции, сопоставления, затем функции Promise и т. д.

компоненты более высокого порядка

Определение компонентов высшего порядка аналогично определению функций высшего порядка, но обратите внимание на следующее:

  • передать компонент
  • вернуть новый компонент
  • это функция

Похоже ли это на функции более высокого порядка, да?

Короче говоря, компонент более высокого порядка — это компонент React, который упаковывает (Wrapped) входящий и после серии обработок возвращает относительно улучшенный (Enhanced) компонент.

Функция подключения в react-redux — хорошее применение компонентов высокого порядка.

Как написать компонент более высокого порядка

Вот пример, который поможет вам написать свои собственные компоненты более высокого порядка.

Теперь у нас есть два компонента: один из них – компонент UI Demo, который используется для отображения текста, а другой – withHeader, который принимает компонент и возвращает новый компонент, но добавляет заголовок к входящему компоненту. Этот withHeader имеет более высокий порядок. компоненты

class Demo extends Component {
  render() {
    return (
      <div>
        我是一个普通组件1
      </div>
    )
  }
}

const withHeader = WrappedComponent => {
  class HOC extends Component {
    render() {
      return (
        <div>
          <h1 className='demo-header'>我是标题</h1>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
  return HOC
}

const EnhanceDemo = withHeader(Demo)

Результат выглядит следующим образом:

Компонент HOC — это компонент более высокого порядка, который упаковывает входящий демонстрационный компонент и добавляет к нему заголовок.

Предположим, есть три компонента Demo, Demo1, Demo2 и Demo3, разница между ними в том, что содержимое компонентов разное (это удобно для демонстрации), все они обёрнуты HOC, и получается, что названия компонентов упакованные компоненты являются HOC. В настоящее время нам нужно различать три компонента более высокого порядка после упаковки,

Добавить статическое свойство displayName в HOC

const getDisplayName = component => {
  return component.displayName || component.name || 'Component'
}

const withHeader = WrappedComponent => {
  class HOC extends Component {
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`
    render() {
      return (
        <div>
          <h1 className='demo-header'>我是标题</h1>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
  return HOC
}

Посмотрите, названия трех компонентов высшего порядка разные, но мы хотим, чтобы заголовок не был жестко запрограммирован, а мог передаваться динамически? Конечно можно, мы можем использовать каррирование реализации функции, мы можем улучшить withHeader

const withHeader = title => WrappedComponent => {
  class HOC extends Component {
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`
    render() {
      return (
        <div>
          <h1 className='demo-header'>{title}</h1>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
  return HOC
}

const EnhanceDemo1 = withHeader('Demo1')(Demo1)
const EnhanceDemo2 = withHeader('Demo2')(Demo2)
const EnhanceDemo3 = withHeader('Demo3')(Demo3)

Результат выглядит следующим образом:

Мы можем использовать декораторы ES7, чтобы сделать наш текст более лаконичным.

@withHeader('Demo1')
class Demo1 extends Component {
  render() {
    return (
      <div>
        我是一个普通组件1
      </div>
    )
  }
}

@withHeader('Demo2')
class Demo2 extends Component {
  render() {
    return (
      <div>
        我是一个普通组件2
      </div>
    )
  }
}

@withHeader('Demo3')
class Demo3 extends Component {
  render() {
    return (
      <div>
        我是一个普通组件3
      </div>
    )
  }
}

class App extends Component {
  render() {
    return (
      <Fragment>
        <Demo1 />
        <Demo2 />
        <Demo3 />
      </Fragment>
    )
  }
}

Что насчет декоратора и как его использовать, каждый может сам проверить, в конце моей статьи специально написано, чтобы объяснить это еще раз

Пока что мы освоили, как написать компонент более высокого порядка, но это не закончено

Реализация двух компонентов более высокого порядка

Вот два способа реализации компонентов более высокого порядка:

  • Риелтером
  • обратное наследование

Риелтером

Атрибутный прокси — самый распространенный способ.Пример, упомянутый выше, основан на этом способе, но мы можем написать более совершенный.Мы можем настроить некоторые атрибуты в HOC, а затем передать их в пакет вместе с вновь сгенерированными атрибутами компонентов, как следует:

const withHeader = title => WrappedComponent => {
  class HOC extends Component {
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`
    render() {
      const newProps = {
        id: Math.random().toString(36).substring(2).toUpperCase()
      }
      return (
        <div>
          <h1 className='demo-header'>{title}</h1>
          <WrappedComponent {...this.props} {...newProps}/>
        </div>
      )
    }
  }
  return HOC
}

@withHeader('标题')
class Demo extends Component {
  render() {
    return (
      <div style={this.props}>
        { this.props.children }
      </div>
    )
  }
}

class App extends Component {
  render() {
    return (
      <Fragment>
        <Demo color='blue'>我是一个普通组件</Demo>
      </Fragment>
    )
  }
}

Дисплей выглядит следующим образом:

Вышеупомянутые компоненты высокого порядка и компоненты-оболочки были улучшены.Атрибут id может быть сгенерирован внутри компонента высокого порядка и передан в компонент-оболочку, в то время как внешняя часть компонента высокого порядка также может принимать атрибуты и передавать их в компонент-оболочку. завернутый компонент.

обратное наследование

Давайте внесем некоторые изменения в приведенный выше пример:

Мы позволяем HOC наследовать wrapComponent, чтобы наш HOC имел свойства и методы, определенные в wrapComponent, такие как состояние, свойства, функции жизненного цикла.

const withHeader = title => WrappedComponent => {
  class HOC extends WrappedComponent {
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`
    render() {
      return (
        <div>
          <h1 className='demo-header'>{title}</h1>
          { super.render() }
        </div>
      )
    }
    componentDidMount() {
      this.setState({
        innerText: '我的值变成了2'
      })
    }
  }
  return HOC
}

Обратите внимание, что функция рендеринга в HOC должна использовать ключевое слово super для вызова функции рендеринга родительского класса.

Соответствующие демонстрационные компоненты также были изменены.

@withHeader('标题')
class Demo extends Component {
  constructor(props) {
    super(props)
    this.state = {
      innerText: '我的初始值是1'
    }
  }
  render() {
    return (
      <div style={this.props}>
        { this.state.innerText }
      </div>
    )
  }
}

class App extends Component {
  render() {
    return (
      <Fragment>
        <Demo color='blue'></Demo>
      </Fragment>
    )
  }
}

Окончательное отображение выглядит следующим образом:

Обратите внимание, что внутри высокоуровневого компонента нет демонстрационного компонента, вместо него используется нативный HTML-тег.Вы можете сравнить рисунок выше, чтобы увидеть разницу.Почему нет демонстрационного компонента? Потому что мы вызываем функцию рендеринга родительского класса вместо того, чтобы напрямую использовать компонент React.

Потому что этот способ заключается в том, чтобы позволить нашему HOC наследовать WrappedComponent, другими словами, WrappedComponent наследуется HOC, поэтому это называется обратным наследованием.

легкая яма

После того, как с двумя методами наследования покончено, поговорим об ошибках в написании высокоуровневых компонентов, на это тоже нужно обратить внимание официальной документации.

Не используйте компоненты более высокого порядка в функциях рендеринга

Это требует некоторого понимания алгоритма diff.Если компонент, возвращенный из рендеринга, равен компоненту, возвращенному предыдущей функцией рендеринга, React итеративно обновит поддерево до нового поддерева с помощью алгоритма diff. Если не равно, предыдущее поддерево будет полностью выгружено.

А если мы будем использовать в функции рендеринга компоненты более высокого порядка, то каждый раз будет генерироваться новый экземпляр компонента более высокого порядка, так что соответствующее дерево компонентов будет каждый раз выгружаться заново.Конечно, будут проблемы с производительностью, но не только проблемы с производительностью, это также приведет к потере всего состояния и дочерних элементов компонента, что является фатальным

Статические методы необходимо копировать вручную

Когда мы определяем статические методы в WrappedComponent, их нельзя найти в экземплярах компонентов более высокого порядка.

const withHeader = title => WrappedComponent => {
  class HOC extends Component {
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`
    render() {
      return (
        <div>
          <h1 className='demo-header'>{title}</h1>
          <WrappedComponent>
            { this.props.children }
          </WrappedComponent>
        </div>
      )
    }
  }
  return HOC
}

class Demo extends Component {
  static hello() {
    console.log('22')
  }
  render() {
    return (
      <div style={this.props}>
        { this.props.children }
      </div>
    )
  }
}

const WithHeaderDemo = withHeader('标题')(Demo)
WithHeaderDemo.hello()

У нас есть два решения:

  • Вручную скопируйте статический метод WrappedComponent в компонент более высокого порядка.
  • Использовать подъемникNonReactStatic

Первый метод требует от нас знать, какие статические методы существуют на WrappedComponent, что имеет определенные ограничения Обычно мы используем второй метод, но нам нужно установить стороннюю библиотеку: hoist-non-react-statics

import hoistNonReactStatic from 'hoist-non-react-statics'

const withHeader = title => WrappedComponent => {
  class HOC extends Component {
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`
    render() {
      return (
        <div>
          <h1 className='demo-header'>{title}</h1>
          <WrappedComponent>
            { this.props.children }
          </WrappedComponent>
        </div>
      )
    }
  }
  hoistNonReactStatic(HOC, WrappedComponent)
  return HOC
}

Так вы не получите ошибку

Реф не пройдет

Компоненты более высокого порядка могут передавать в обернутый компонент все свойства, кроме ref, потому что ref не является реальным свойством, React обрабатывает его специально, если вы добавляете ref к элементу компонента, созданного компонентом более высокого порядка, применяете его, Тогда ref указывает на самый внешний экземпляр компонента-контейнера, а не на компонент-оболочку.

Просто посмотрите на пример

class App extends Component {
  render() {
    const WrappedComponentRef = React.createRef()
    this.WrappedComponentRef = WrappedComponentRef
    return (
      <Fragment>
        <WithHeaderDemo color='blue' ref={WrappedComponentRef}>
          33333
        </WithHeaderDemo>
      </Fragment>
    )
  }
  componentDidMount() {
    console.log(this.WrappedComponentRef.current)
  }
}

Полученная печатная информация выглядит следующим образом:

Наше первоначальное намерение состояло в том, чтобы передать ссылку на WrappedComponent, завернутый во внутренний слой, и напечатанный результат действительно является HOC внешнего слоя.Давайте посмотрим на информацию дерева компонентов React.

ref, как свойство HOC, не передается внутрь. Я думаю, это должно быть связано с тем, что React специально обработал ref. Как это решить? Просто, просто измените имя, я использую _ref

const withHeader = title => WrappedComponent => {
  class HOC extends Component {
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`
    render() {
      return (
        <div>
          <h1 className='demo-header'>{title}</h1>
          <WrappedComponent ref={this.props._ref}>
            { this.props.children }
          </WrappedComponent>
        </div>
      )
    }
  }
  hoistNonReactStatic(HOC, WrappedComponent)
  return HOC
}

class App extends Component {
  render() {
    const WrappedComponentRef = React.createRef()
    this.WrappedComponentRef = WrappedComponentRef
    return (
      <Fragment>
        <WithHeaderDemo color='blue' _ref={WrappedComponentRef}>
          33333
        </WithHeaderDemo>
      </Fragment>
    )
  }
  componentDidMount() {
    console.log(this.WrappedComponentRef.current)
  }
}

Посмотрим на результаты нашей печати.

Случай соответствующего дерева компонентов React

Идеальное решение!

наконец

Эта статья лишь кратко знакомит с написанием высокоуровневых компонентов и двумя методами их реализации, а также с некоторыми ямами, которых следует избегать.Если вы хотите иметь более систематическое представление о высокоуровневых компонентах, рекомендуется прочитатьКомпоненты высшего порядка для официальной документации React, и вы можете прочитать исходный код: например, connect of react-redux, Form.create antd

Путь к изучению React очень долгий

Ваша награда - мотивация для моего письма

微信
支付宝