Обработка результата Redux Saga в представлении, из которого исходит вызов действия

Я новичок в Redux Saga, исходя из Redux Thunk. В некоторых ситуациях мне нужно знать, завершился ли вызов API неудачно или успешно из представления, из которого я вызвал действие. С Redux Thunk я бы сделал что-то вроде следующего.

Мой создатель компонентов и действий будет выглядеть так:

class MyComponent extends Component {

  componentDidMount() {
    this.props.actions.fetchItems()
    .then(result => {
        if (result.status === 'OK') {
            console.log('Request succeeded, take particular action in this view')
        }
        else {
            console.log('Request failed, take other action in this view')
        }
    })
  }

  render() {
    return (
        <div>My view</div>
    )
  }
}


const mapStateToProps = (state, ownProps) => {
    return {
        items: state.items,
    }
}

const mapDispatchToProps = dispatch => {
    return {
        actions: bindActionCreators({
            ...actions,
        }, dispatch),
    }
}


export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(MyComponent)



import * as t from './actionTypes'
import {CALL_API} from '../api'

const xhrGetItems = (params) => (dispatch, getState) => {
    const action = {
        [CALL_API]: {
            type: t.XHR_ITEMS_FETCH,
            endpoint: `http://www.example.com/myendpoint`,
            method: 'get',
        }
    }
    return dispatch(action)
}

Промежуточное ПО моего API перехватывает все действия со свойством CALL_API, использует библиотеку ajax для выполнения соответствующего вызова, а затем возвращает выполненное обещание. Мой редуктор настроен на обработку каждого из возможных состояний вызова API (успех, сбой, ожидание). Все это время я все еще могу проверить результат звонка в моем представлении, где он произошел.

Итак, мой вопрос: как это можно сделать с помощью Redux Saga? Прямо сейчас мое промежуточное ПО Saga API делает все, что должно, но когда я вызываю fetchItems(), на мой взгляд, результатом является простой объект JS, поэтому я не могу проверить, удалось ли это.

Возможно, я тоже делаю это совершенно неправильно. Любые предложения очень ценятся.


person Murcielago    schedule 16.05.2017    source источник


Ответы (1)


Обычный шаблон для redux и redux-saga — создать 3 действия для вызова API. В вашем случае я бы создал:

  • LIST_ITEMS_START
  • LIST_ITEMS_SUCCEEEDED
  • LIST_ITEMS_FAILED

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

function* watchListItemsStart() {
  yield takeLatest(LIST_ITEMS_START, listItemsStartFlow)
}

function* listItemsStartFlow() {
  try {
    const result = yield call(api.getItems)

    if (result.status === 'OK') {
      yield put(LIST_ITEMS_SUCCEEDED, items)
    } else {
      throw new Error(result.error)
    }
  } catch (error) {
    yield put(LIST_ITEMS_FAILED, error)
  }
}

Саги полностью абстрагировались от побочного эффекта API. Редуктор может сосредоточиться на управлении состоянием:

switch (action.type) {
  case LIST_ITEMS_START:
    return {
      ...state,
      isLoading: true,
      error: null,
      items: [],
    }
  case LIST_ITEMS_SUCCEEDED:
    return {
      ...state,
      isLoading: false,
      items: action.payload.items,
    }
  case LIST_ITEMS_FAILED:
    return {
      ...state,
      isLoading: false,
      error: action.payload.error.getMessage(),
    }
}

Теперь у вас есть все необходимое в вашем магазине, которое можно выбрать и отреагировать в компоненте.

class MyComponent extends Component {
  componentDidMount() {
    this.props.fetchItems()
  }

  render() {
    const { isLoading, items, error } = this.props

    // Here you can react to the different states.

    return (
      <div>My view</div>
    )
  }
}

connect(state => ({
  isLoading: itemsSelectors.isLoading(state),
  items: itemsSelectors.getItems(state),
  error: itemsSelectors.getError(state),
}), {
  fetchItems: actions.fetchItems
})(MyComponent)

Важным моментом здесь является то, что ваш компонент становится очень тупым. Он просто получает реквизиты и не обрабатывает результат вызова API (здесь нет promise.then).

Я надеюсь, что этот ответ поможет вам.

person Alex    schedule 16.05.2017
comment
Спасибо за ваш ответ @Alex. То, что вы описали, в основном то, что я настроил. Мой вопрос о том, как сделать компонент несколько умным. Вариант использования для этого может быть следующим: Пользователь отправляет данные через вызов API. При успешном запросе перенаправить пользователя на другую страницу/компонент (например, с помощью реактивного маршрутизатора browserHistory.push('/myPath')). Обычно я бы вызывал это перенаправление внутри компонента. Есть ли лучший способ сделать это, что я не рассматриваю? - person Murcielago; 17.05.2017
comment
Основываясь на том, что вы описали и что я настроил в настоящее время, мне нужно будет сделать это, проверив componentWillReceiveProps(nextProps) this.props.isLoading && !nextProps.isLoading, а затем предпринять соответствующие действия в соответствии с nextProps.error (например, перейти к новому компоненту в случае успеха или показать сообщение об ошибке при ошибке). Это просто кажется длинным обходным путем для чего-то простого. Можете ли вы прокомментировать лучшие практики для этого конкретного варианта использования? - person Murcielago; 17.05.2017
comment
Все ваши побочные эффекты, такие как маршрутизация, вызовы API и т. д., должны уйти в сагу. Таким образом, в саге, где я проверяю if (result.status === 'OK'), вы можете указать логику маршрутизатора. Вы значительно облегчите себе жизнь, если компоненты будут !не умными, а тупыми - person Alex; 17.05.2017
comment
Еще раз спасибо @Alex. Знаете ли вы какие-либо проекты/примеры реального мира, которые доступны для открытого просмотра? Реальный пример из Документы redux-saga довольно хороши, но не касаются каких-либо действий post/put/patch/delete. Любые предложения приветствуются! - person Murcielago; 17.05.2017
comment
Я только что нашел шаблонный проект React от Макса Штойбера, который может помочь. Он использует саги. Вы можете найти описанный мной шаблон здесь: github.com/react-boilerplate/react-boilerplate/blob/master/app/ - person Alex; 18.05.2017
comment
нет обещания. тогда .... это моя проблема, скажем, у меня есть чистая установка redux-saga, которая позволяет создавать пользователя, затем я хочу использовать этот возвращенный идентификатор пользователя, чтобы связать действие, скажем, создать проект, как Я делаю это с помощью redux-saga, не создавая действие «создать пользователя и проект»? - person edencorbin; 01.04.2018
comment
Возможно, я что-то пропустил, но я не понимаю, как это работает, например. есть более одной кнопки. Разве нет способа саги сообщить компоненту, что фактическое асинхронное действие, которое он начал, завершено? - person Augustin Riedinger; 24.04.2018