Шаблон для управления начальными асинхронными действиями с помощью избыточных саг для извлечения исходных данных? - PullRequest
0 голосов
/ 15 июня 2019

До сих пор я использовал redux-thunk для асинхронных действий. При запуске приложения я использую, чтобы загрузить некоторые данные с какого-либо сервера. Поэтому я создаю асинхронные действия, а затем использую async / await, чтобы узнать, когда они закончатся. Пока асинхронные действия извлекаются, я отображаю заставку. Когда они заканчивают, я запускаю приложение.

Теперь я переключаюсь на редуксовые саги, и я не знаю, как это сделать с ними. Я не могу использовать async / await. Я думал, что в каждом объекте магазина должна быть логическая переменная, которая должна извлекать данные. Тем не менее, я хотел бы знать, есть ли какой-либо шаблон для управления им в чистом виде. Кто-нибудь знает какой-либо шаблон для этой цели?

// example with thunks

import { someAsyncAction, someAsyncAction2 } from './actions';

const initialDispatches = async (store) => {
  await store.dispatch(someAsyncAction());
  await store.dispatch(someAsyncAction2());
};

export default initialDispatches;

Ответы [ 2 ]

0 голосов
/ 16 июня 2019

Я писал о создании структуры поверх redux-saga, чтобы упростить асинхронные операции, предоставив начальное действие, а затем загрузку / успех / ошибки в зависимости от результата операции.Он состоит из 2 частей, сначала синхронизирующих, а затем асинхронных.

Он в основном позволяет декларативно писать ваши редукторы, как объект.Вам нужно только вызвать начальное действие, а сага позаботится обо всем остальном, и ваш пользовательский интерфейс может реагировать на результаты при запуске действий загрузки / успеха / ошибки.Ниже показано, как выглядит редуктор.

const counterAsync = {
  initialState: {
    incrementAsync_result: null,
    incrementAsync_loading: false,
    incrementAsync_success: false,
    incrementAsync_error: false,
  },

  incrementAsync: {
    asyncOperation: incrementAPI,
    action: ({number}) => {
      type: ACTION_INCREMENT_ASYNC,
      payload: {
        number: number
      }
    }
    loading: {
      action: (payload) => {
        return {
          type: ACTION_INCREMENT_ASYNC,
          payload: { ...payload }
        }
      },
      reducer: (state, action) => {
        state.incrementAsync_loading = true
        state.incrementAsync_success = false
        state.incrementAsync_error = false
      }
    },
    success: {
      action: (payload) => {
        return {
          type: ACTION_INCREMENT_ASYNC,
          payload: { ...payload }
        }
      },
      reducer: (state, action) => {
        state.incrementAsync_result = action.payload
        state.incrementAsync_loading = false
        state.incrementAsync_success = true
        state.incrementAsync_error = false
      }
    },
    fail: {
      action: (payload) => {
        return {
          type: ACTION_INCREMENT_ASYNC,
          payload: { ...payload }
        }
      },
      reducer: (state, action) => {
        state.incrementAsync_result = action.payload
        state.incrementAsync_loading = false
        state.incrementAsync_success = false
        state.incrementAsync_error = true
      }
    }
  },
}

Мы используем немного более тяжелую версию этого паттерна в работе, и она намного лучше, чем vanilla redux / saga.

Дайте мне знать, еслиУ вас есть вопросы!

https://medium.com/@m.razajamil/declarative-redux-part-1-49a9c1b43805 https://medium.com/@m.razajamil/declarative-redux-part-2-a0ed084e4e31

0 голосов
/ 15 июня 2019

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

Я приведу вам пример того, как ваша цель может быть достигнута с помощью саги.

Основная идея: иметь отдельную сагу для каждого ресурса (например, я использовал для разделения на функциональные саги) и сагу для инициализации.Тогда основная корневая сага выполнит их все параллельно, и вы сможете запустить сагу инициализации где-то в вашем приложении и позволить всему этому произойти:

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

const {Provider, connect} = ReactRedux;
const {createStore, applyMiddleware} = Redux;
const createSagaMiddleware = ReduxSaga.default;
const {takeEvery, takeLatest} = ReduxSaga;
const {put, call, all, fork} = ReduxSaga.effects;

const initialState = {
    fruits: [],
  vegtables: []
};

const reducer = (state = initialState, action) => {
    switch (action.type) {
    case 'SET_FRUITS':
        return {
        ...state,
        fruits: [
            ...action.payload.fruits
        ]
      }
    case 'SET_VEGTABLES':
        return {
        ...state,
        vegtables: [
            ...action.payload.vegtables
        ]
      }
  }
    return state;
};

//====== VEGTABLES ====== //
async function fetchVegtables() {
    return await new Promise((res) => {
    setTimeout(() => res([
        'Cuecumber',
      'Carrot',
      'LEttuce'
    ]), 3000)
  });
}

function* getVegtables() {
    const vegtables = yield call(fetchVegtables);
  yield put({ type: 'SET_VEGTABLES', payload: { vegtables } })
} 

function* vegtablesSaga() {
    yield takeEvery('GET_VEGTABLES', getVegtables);
}
//====== VEGTABLES ====== //

//====== FRUITS ====== //
async function fetchFruits() {
    return await new Promise((res) => {
    setTimeout(() => res([
        'Banana',
      'Apple',
      'Peach'
    ]), 2000)
  });
}

function* getFruits() {
    const fruits = yield call(fetchFruits);
  console.log(fruits)
  yield put({ type: 'SET_FRUITS', payload: { fruits } })
} 

function* fruitsSaga() {
    yield takeEvery('GET_FRUITS', getFruits);
}
//====== FRUITS ====== //

//====== INIT ====== //
function* initData() {
    yield all([
    put({ type: 'GET_FRUITS' }),
    put({ type: 'GET_VEGTABLES' })
  ]);
}

function* initSaga() {
    yield takeLatest('INIT', initData);
}
//====== INIT ====== //

// Sagas
function* rootSaga() {
  yield all([
    yield fork(initSaga),
    yield fork(fruitsSaga),
    yield fork(vegtablesSaga),
  ]);
}

// Component
class App extends React.Component {
    componentDidMount() {
    this.props.dispatch({ type: 'INIT' });
  }
  render () {
    return (
            <div>
              <div>fruits: {this.props.fruits.join()}</div>
        <div>vegtables: {this.props.vegtables.join()}</div>
            </div>
    );
  }
}

// Store
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
);

sagaMiddleware.run(rootSaga);

const ConnectedApp = connect((state) => ({
    fruits: state.fruits,
  vegtables: state.vegtables
}))(App);

// Container component
ReactDOM.render(
  <Provider store={store}>
    <ConnectedApp />
  </Provider>,
  document.getElementById('root')
);

Как видите, у меня есть два ресурса: фрукты и овощи.Каждый ресурс имеет свою собственную сагу, которая отвечает за отслеживание действий GET , отправленных куда-либо.Каждый из них использует базовые эффекты саги, такие как call, put и т. Д., Для асинхронной выборки ресурсов, а затем отправляет их в хранилище (а затем редуктор обрабатывает их).

Кроме того, Iv'e настроил initSaga , который использует эффект all для параллельного запуска всех саг, извлекающих ресурсы.

Вы можете увидеть весь пример, запущенный здесь:

https://jsfiddle.net/kadoshms/xwepoh5u/17/

...