React/reflux, как правильно выполнять асинхронные вызовы

Недавно я начал изучать ReactJS, но меня смущают асинхронные вызовы.

Допустим, у меня есть страница входа с полями пользователя/пароля и кнопкой входа. Компонент выглядит так:

var Login = React.createClass({

    getInitialState: function() {
        return {
            isLoggedIn: AuthStore.isLoggedIn()
        };
    },

    onLoginChange: function(loginState) {
        this.setState({
            isLoggedIn: loginState
        });
    },

    componentWillMount: function() {
        this.subscribe = AuthStore.listen(this.onLoginChange);
    },

    componentWillUnmount: function() {
        this.subscribe();
    },

    login: function(event) {
        event.preventDefault();
        var username = React.findDOMNode(this.refs.email).value;
        var password = React.findDOMNode(this.refs.password).value;
        AuthService.login(username, password).error(function(error) {
            console.log(error);
        });
    },

    render: function() {

        return (
                <form role="form">
                    <input type="text" ref="email" className="form-control" id="username" placeholder="Username" />
                    <input type="password" className="form-control" id="password" ref="password" placeholder="Password" />
                    <button type="submit" className="btn btn-default" onClick={this.login}>Submit</button>
                </form>
        );
    }
});

AuthService выглядит так:

module.exports = {
    login: function(email, password) {
        return JQuery.post('/api/auth/local/', {
            email: email,
            password: password
        }).success(this.sync.bind(this));
    },

    sync: function(obj) {
        this.syncUser(obj.token);
    },

    syncUser: function(jwt) {
        return JQuery.ajax({
            url: '/api/users/me',
            type: "GET",
            headers: {
                Authorization: 'Bearer ' + jwt
            },
            dataType: "json"
        }).success(function(data) {
            AuthActions.syncUserData(data, jwt);
        });
    }
};

Действия:

var AuthActions = Reflux.createActions([
  'loginSuccess',
  'logoutSuccess',
  'syncUserData'
]);

module.exports = AuthActions;

И хранить:

var AuthStore = Reflux.createStore({
    listenables: [AuthActions],

    init: function() {
        this.user = null;
        this.jwt = null;
    },

    onSyncUserData: function(user, jwt) {
        console.log(user, jwt);
        this.user = user;
        this.jwt = jwt;
        localStorage.setItem(TOKEN_KEY, jwt);
        this.trigger(user);
    },

    isLoggedIn: function() {
        return !!this.user;
    },

    getUser: function() {
        return this.user;
    },

    getToken: function() {
        return this.jwt;
    }
});

Итак, когда я нажимаю кнопку входа в систему, поток выглядит следующим образом:

Component -> AuthService -> AuthActions -> AuthStore

Я напрямую вызываю AuthService с помощью AuthService.login.

У меня вопрос, правильно ли я делаю?

Должен ли я использовать действие preEmit и делать:

var ProductAPI = require('./ProductAPI')
var ProductActions = Reflux.createActions({
  'load',
  'loadComplete',
  'loadError'
})

ProductActions.load.preEmit = function () {
     ProductAPI.load()
          .then(ProductActions.loadComplete)
          .catch(ProductActions.loadError)
}

Проблема в том, что preEmit делает обратный вызов компонента более сложным. Я хотел бы научиться правильно и найти, где разместить бэкэнд-вызовы со стеком ReactJS/Reflux.


person Deepsy    schedule 12.06.2015    source источник


Ответы (3)


Я также использую Reflux и использую другой подход для асинхронных вызовов.

В vanilla Flux асинхронные вызовы помещаются в действия.

введите здесь описание изображения

Но в Reflux асинхронный код лучше всего работает в магазинах (по крайней мере, по моему скромному мнению):

введите здесь описание изображения

Итак, в вашем случае, в частности, я бы создал действие под названием «вход», которое будет запускаться компонентом и обрабатываться хранилищем, которое запустит процесс входа в систему. Как только рукопожатие завершится, хранилище установит новое состояние в компоненте, которое позволит ему узнать, что пользователь вошел в систему. Тем временем (например, пока this.state.currentUser == null) компонент может отображать индикатор загрузки.

person damianmr    schedule 12.06.2015
comment
Спасибо за ответ. Что вы думаете о stackoverflow.com/questions/28012356/ этот подход actions.init.listen? Потому что я провел исследование, и многие люди делают это по-вашему, но также многие люди делают это в действии. Поэтому я в замешательстве и не могу решить, какой шаблон использовать. - person Deepsy; 14.06.2015
comment
Ну, есть различия в том, как люди его используют, потому что для Reflux нет правильного пути. Это действительно зависит от того, как вы хотите смоделировать свое приложение. Если бы мне нужно было действие, подобное «инициализации», я бы все равно делал запросы, а также помещал весь код инициализации в хранилище. Возможно, я мог бы даже создать InitializationStore. - person damianmr; 15.06.2015
comment
Кроме того, создание действий, которые делают много вещей, может добавить связи с тем, что на самом деле означает действие. Например, такое действие, как вход в систему с вызовом AJAX, не должно вызываться чаще одного раза в секунду, но если у вас есть весь код ajax, вам придется сохранить какое-то состояние для действия, чтобы оно не вызывало ajax вызывается более одного раза, пока не завершится первый. Если бы у вас был вызов API в магазине, он мог бы легко справиться с этим. Кроме того, другие магазины могут быть заинтересованы в том, чтобы узнать, что пользователь запросил вход в систему, независимо от успеха вызова API. - person damianmr; 15.06.2015
comment
Ага. Просто имеет смысл поставить бэкенд-доступ в магазины. См. также: stackoverflow.com/a/28101529/1775026 - person wle8300; 01.08.2015

Для Reflux вам действительно следует взглянуть на https://github.com/spoike/refluxjs#asynchronous-actions.

Краткая версия того, что там описано:

  1. Не используйте хук PreEmit

  2. Используйте асинхронные действия

var MyActions = Reflux.createActions({
  "doThis" : { asyncResult: true },
  "doThat" : { asyncResult: true }
});

Это создаст не только действие makeRequest, но и действия doThis.completed, doThat.completed, doThis.failed и doThat.failed.

  1. (Необязательно, но предпочтительно) используйте обещания для вызова действий
MyActions.doThis.triggerPromise(myParam)
  .then(function() {
    // do something
    ...
    // call the 'completed' child
    MyActions.doThis.completed()
  }.bind(this))
  .catch(function(error) {
    // call failed action child
    MyActions.doThis.failed(error);
  });

Недавно мы переписали все наши действия и хуки preEmit в соответствии с этим шаблоном, и нам нравятся результаты и результирующий код.

person Björn Boxstart    schedule 17.06.2015

Я также обнаружил, что асинхронность с рефлюксом немного сбивает с толку. С необработанным потоком из facebook я бы сделал что-то вроде этого:

var ItemActions = {
  createItem: function (data) {
    $.post("/projects/" + data.project_id + "/items.json", { item: { title: data.title, project_id: data.project_id } }).done(function (itemResData) {
      AppDispatcher.handleViewAction({
        actionType: ItemConstants.ITEM_CREATE,
        item: itemResData
      });
    }).fail(function (jqXHR) {
      AppDispatcher.handleViewAction({
        actionType: ItemConstants.ITEM_CREATE_FAIL,
        errors: jqXHR.responseJSON.errors
      });
    });
  }
};

Таким образом, действие выполняет запрос ajax и вызывает диспетчер по завершении. Я тоже не очень разбирался в шаблоне preEmit, поэтому вместо этого я просто использовал обработчик в магазине:

var Actions = Reflux.createActions([
  "fetchData"
]);

var Store = Reflux.createStore({
  listenables: [Actions],

  init() {
    this.listenTo(Actions.fetchData, this.fetchData);
  },

  fetchData() {
    $.get("http://api.com/thedata.json")
      .done((data) => {
        // do stuff
      });
  }
});

Я не очень хорошо делаю это из магазина, но, учитывая, как рефлюкс абстрагирует действия и будет постоянно запускать обратный вызов listenTo, меня это устраивает. Немного проще объяснить, как я также устанавливаю данные обратного вызова в хранилище. По-прежнему держит его однонаправленным.

person agmcleod    schedule 12.06.2015