Испускание и обработка глобальных событий с реагированием

Я немного поиграю, чтобы отреагировать на создание кнопки "Добавить в корзину". Вот мой код.

var ProductPurchase = React.createClass({
  handleSubmit: function(e){
    e.preventDefault();
    $.ajax({
      url: "/cart/add.js",
      method: "post",
      dataType: "json",
      data: {
        "id": this.props.variantId,
        "quantity": this.props.quantity,
      },
      success: function(data) {
        // emit cart added event
      }.bind(this),
      error: function(xhr, status, err) {
        // emit error event (cart added)
      }.bind(this)
    });
  },
  getDefaultProps: function(){
    return {
      quantity: 1,
      variantId: 231634908,
      buttonText: "Add to cart"
    }
  },
  render: function() {
    return (
      <div className="productPurchase">
        <form action="/cart/add" method="post" enctype="multipart/form-data" onSubmit={this.handleSubmit}>
          <input type="hidden" name="quantity" value={ this.props.quantity } />
          <input type="hidden" name="id" value={ this.props.variantId } />
          <button type="submit">{this.props.buttonText}</button>
        </form>
      </div>
    );
  }
});

Что мне интересно, так это обработчик ajax. Я почти уверен, что вся суть реакции - это взаимодействие между компонентами, за исключением того, что я не знаю, куда привести эти события. Я мог бы представить пару различных компонентов, таких как индикатор подсчета корзины, если успех или предупреждение об ошибке, если сбой, но я точно не знаю, как использовать их. Это целая точка диспетчеров потока?

Ответы

Ответ 1

Да, это определенно часть точки диспетчеров Flux - или любой эмитент событий, который вы хотели использовать.

Прежде чем вы спуститесь по этому пути, очень просто просто передать обработчики событий в качестве реквизитов без использования Flux или настраиваемых эмитентов событий - так же, как с обработчиками onSubmit, onClick и т.д. для обычных элементов DOM. Затем попросите родителя договориться с настройкой состояния и, возможно, передав его другим детям (через реквизиты).

Итак, в этом случае представьте родительский компонент, который имеет дело с событиями:

var RootComponent = React.createClass({
  handleCartAdded: function(cart) {
    console.log('Got a new cart: ' + cart);
  }
  handleError: function(err) {
    console.error(err)
  }
  render: function() {
    return (
      <ProductPurchase onCartAdded={this.handleCartAdded} onError={this.handleError} />
    )
  }
})

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

  success: function(data) {
    this.props.onCartAdded(data)
  }.bind(this),
  error: function(xhr, status, err) {
    this.props.onError(err)
  }.bind(this)

Более сложным примером может быть передача результата другому дочернему компоненту - но опять же, оставьте его родительским для управления этим:

var RootComponent = React.createClass({
  handleCartAdded: function(cart) {
    this.setState({cart: cart})
  }
  handleError: function(err) {
    console.error(err)
  }
  render: function() {
    return (
      <div>
        <ProductPurchase onCartAdded={this.handleCartAdded} onError={this.handleError} />
        <CartSummary cart={this.state.cart} />
      </div>
    )
  }
})

Таким образом, компоненты отделены друг от друга - и данные/функции могут быть переданы только четким контрактом (реквизитом).

Этот простой стиль обработки событий намного более явчен и легче отлаживается - поэтому я бы действительно использовал только архитектуру стиля Flux, если ваше приложение становится очень сложным и/или у вас есть много компонентов, которые все должны общаются друг с другом сложным образом.

Ответ 2

Flux используется для развязки программных конструкций (включая вызовы AJAX).

Вот диаграмма из Flux Docs

Flux Architecture Diagram

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

Как использовать диспетчер? (Упрощенный подход без использования магазинов и ActionCreators)

  • Если на этот вызов AJAX влияет другая часть приложения, вам не следует делать этот вызов AJAX с этого компонента, скорее переместите вызов AJAX в новый файл и функцию. Например, используя CommonJS,

    // CartApiUtils.js
    module.exports = {
        addToCart: function(item){
            // AJAX call
        }
    }
    
  • Создайте AppDispatcher (который распространен во всем приложении) с помощью Flux Dispatcher class

    var appDispatcher = new Dispatcher();
    
  • В функции addToCart() при успешном ответе AJAX отправьте событие с помощью AppDispatcher:

    appDispatcher.dispatch({
        actionType: 'cart.newItemAdded',
        data: dataFromAjax
    });
    
  • В вашем приложении везде, где вы хотите использовать это событие, вы можете просто зарегистрировать функцию для dispacher.

    appDispatcher.register(function(payload){
        if(payload.actionType === 'cart.newItemAdded'){
            // payload.data contains the data
        }
    });
    

Это упрощенный подход. В более нормализованной структуре и более крупном приложении вы должны использовать Stores (что-то вроде уровня модели MVC, но не идентичного) и ActionCreator, где любое взаимодействие на уровне представления является действием пользователя и любым ответом вызова AJAX также становится действием с сервера.

Правило Thumb заключается в том, что Views должны быть заполнены (или обновлены) из магазинов, а магазины должны обновляться в событиях диспетчера.