Изменение реакции на рефакторинг композиции

#javascript #reactjs

#javascript #reactjs

Вопрос:

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

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

 var Styleable = {
  addStyleProperty: function(styleProperty) {
     ...
  },
  ... 
};
var EventEmitter = {
   addEventListener: function(eventName, func) {
   },
   ...
};
 

С помощью mixins пример компонента выглядит следующим образом:

 var Button = React.createClass({
  mixins = [EventEmitter, Styleable],
  ...
});
 

С помощью composition я попытался создать компоненты для Styleable и EventEmitter следующим образом:

 var StylableComponent = React.createClass({
  addStyleProperty: ...
  render: function() {
    React.createElement(this.props.wrappedComponent, ...)??
  }
}
var EventEmitterComponent = ...
 

Но я не знаю, как правильно их использовать. Я читал, что я должен использовать эти компоненты в качестве оболочки, но я не знаю, как этого добиться. Я попытался сделать это, как в функции рендеринга в приведенном выше примере. Как я могу создать экземпляр кнопки, которая имеет функции, аналогичные варианту mixin? Так что я просто передаю требуемые функции следующим образом:

 <Button is={[StyleableComponent, EventEmitterComponent]}/> 
 

Или я ожидаю неправильного поведения от композиции?

Ответ №1:

По сути, вы хотите создать функцию, которая возвращает совершенно новый компонент.

Например, для вашего EventEmitter это будет выглядеть примерно так:

 import eventEmitter from 'your-event-emitter-location';

// This function takes a component as an argument, and returns a component.
function eventEmitterWrapper(ComposedComponent) {
  // This is the brand new "wrapper" component we're creating:
  return class extends Component {
    render() {
      // We want to return the supplied component, with all of its supplied
      // props, but we also want to "add in" our additional behaviour.
      return (
        <ComposedComponent
          {...this.props}
          eventEmitter={eventEmitter}
        />
      );
    }
  }
} 

Чтобы использовать его, вы должны сделать что-то вроде этого:

 import Button from '../Button';
import eventEmitterWrapper from '../../HOCs/event-emitter';

class Home extends Component {
  render() {
    const EventEmitterButton = eventEmitterWrapper(Button);

    return (
      <div>
        <EventEmitterButton onClick={doSomething}>
          Hello there!
        </EventEmitterButton>
      </div>
    )
  }
} 

Наконец, внутри вашей кнопки у вас будет доступ к источнику событий из реквизита:

 const Button = ({ children, onClick, eventEmitter }) => {
  // Let's say, for example, you want to emit an event whenever the
  // button is clicked. You could do something like this:
  const clickHandler = (ev) => {
    onClick(ev);
    
    if (eventEmitter) {
      eventEmitter.emit('click', ev);
    }
  };
  
  return (
    <button onClick={clickHandler}>
      {children}
    <button>
      
  )
} 

ПРИМЕЧАНИЕ: Это надуманный пример. Этот шаблон для меня немного вонючий; Button Слишком много знает о внутренней работе источника событий (например, ему не нужно знать, что у него есть emit метод. Если вы измените способ работы вашего источника событий, у вас будет куча «тупых» компонентов для обновления).

Чтобы исправить этот запах, мне нужно было бы узнать больше о вашем варианте использования, но я бы, скорее всего, заставил класс, возвращаемый eventEmitterWrapper функцией, раскрывать простые специфические методы (такие как «emitEvent», который будет принимать один аргумент события мыши / клавиатуры)

Надеюсь, это иллюстрирует, как использовать компоненты более высокого порядка!

Дополнительная информация:

Комментарии:

1. Я прочитал приведенные источники, поэтому я попытался его реорганизовать. Это работает просто отлично, если есть одна функциональность, которую я хочу добавить, например, EventEmitter. Но если я хочу также добавить стилизуемый, у меня есть подобная цепочка styleableWrapper , которая затем принимает уже завернутый компонент, который для меня вонючий. Я надеюсь, вы поняли суть. У меня также больше нет доступа к функциям, объявленным в. eventEmitterWrapper

2. Компоненты более высокого порядка могут обертывать компоненты более высокого порядка; это совсем не вонючий (это просто композиция функций!). Я думаю, пока нет взаимозависимостей… это становится немного вонючим, если styleableWrapper зависит от eventEmitterWrapper , но похоже, что это не так.

3. Для вашего комментария о том, что у вас нет доступа к функциям, объявленным в eventEmitterWrapper , они должны быть переданы как реквизиты. Поскольку каждый HOC передает все делегированные реквизиты вниз, все, что вы передаете, будет собрано {...this.props} .