Отправьте действия надлежащим образом - PullRequest
6 голосов
/ 20 сентября 2019

Пожалуйста, отметьте Редактировать

Я пытаюсь реализовать саги в своем приложении.

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

В настоящее время мое приложение работает следующим образом:

У меня есть контейнеры , в которых есть mapStateToProps, mapDispatchToProps.

const mapStateToProps = state => {
  return {
    someState: state.someReducer.someReducerAction,
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators({someAction, someOtherAction, ...}, dispatch)
};

const something = drizzleConnect(something, mapStateToProps, mapDispatchToProps);

export default something;

и затем у меня есть действия , например:

import * as someConstants from '../constants/someConstants';

export const someFunc = (someVal) => (dispatch) => {
    someVal.methods.someMethod().call().then(res => {
        dispatch({
            type: someConstants.FETCH_SOMETHING,
            payload: res
        })

    })
}

и редукторы , как показано ниже:

export default function someReducer(state = INITIAL_STATE, action) {
    switch (action.type) {
        case types.FETCH_SOMETHING:
            return ({
                ...state,
                someVar: action.payload
            });

Я объединяю редукторы с redux's combReducers и экспортирую их как один редуктор, который затем импортирую в свой магазин.

Поскольку я использую морось, моя rootSaga выглядит так:

import { all, fork } from 'redux-saga/effects'
import { drizzleSagas } from 'drizzle'

export default function* root() {
  yield all(
    drizzleSagas.map(saga => fork(saga)),
  )
}

Итак, теперь, когда я хочу обновить реквизит, внутри componentWillReceiveProps компонента, я делаю: this.props.someAction()

Хорошо, это работает, но я знаю, что это ненадлежащим образом.По сути, это худшее, что я мог сделать.

Итак, теперь я думаю, что мне следует сделать:

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

Но моя проблема в том, как эти саги должны быть написаны.

Возможно ли, что вы можете дать мнепример, основанный на действиях, редукторах и контейнерах, о которых я упоминал выше?

Редактировать:

Мне удалось следовать указаниям apachuilo.

ИтакПока что я сделал следующие настройки:

Действия выглядят так:

export const someFunc = (payload, callback) => ({
            type: someConstants.FETCH_SOMETHING_REQUEST,
            payload,
            callback
})

и редукторы , например:

export default function IdentityReducer(state = INITIAL_STATE, {type, payload}) {
    switch (type) {
        case types.FETCH_SOMETHING_SUCCESS:
            return ({
                ...state,
                something: payload,
            });
...

Я также создал someSagas :

...variousImports

import * as apis from '../apis/someApi'

function* someHandler({ payload }) {
    const response = yield call(apis.someFunc, payload)

    response.data
        ? yield put({ type: types.FETCH_SOMETHING_SUCCESS, payload: response.data })
        : yield put({ type: types.FETCH_SOMETHING_FAILURE })
}

export const someSaga = [
    takeLatest(
        types.FETCH_SOMETHING_REQUEST,
        someHandler
    )
]

, а затем обновил rootSaga :

import { someSaga } from './sagas/someSagas'

const otherSagas = [
  ...someSaga,
]

export default function* root() {
  yield all([
    drizzleSagas.map(saga => fork(saga)),
    otherSagas
  ])
}

Также,API выглядит следующим образом:

export const someFunc = (payload) => {
    payload.someFetching.then(res => {
        return {data: res}
    }) //returns 'data' of undefined but just "return {data: 'something'} returns that 'something'

Итак, я бы хотел обновить мои вопросы:

  1. Мои API зависят отсостояние магазина.Как вы, наверное, поняли, я создаю приложение.Итак, Drizzle (промежуточное ПО, которое я использую для доступа к блокчейну), должно быть инициировано, прежде чем я вызову API и верну информацию компонентам.Таким образом,

    a.Попытка чтения состояния с помощью getState () возвращает пустые контракты (контракты, которые еще не «готовы») - поэтому я не могу получить информацию - мне не нравится читать состояние из магазина, но ...

    б.Передача состояния через компонент (this.props.someFunc (someState) возвращает мне Cannot read property 'data' of undefined Самое смешное, что я могу console.log состояния (кажется, все в порядке) и пытаясь просто `вернуть {data: 'someData'} реквизиты получают данные.

  2. Должен ли я запускать this.props.someFunc (), например, для componentWillMount ()? Это правильный способ обновления реквизитов?

Извините за очень длинный пост, но я хотел быть точным.

Правка для 1b : Э-э, так много правок :) Я решил проблему с помощьюнеопределенное разрешение.Просто нужно было написать API так:

export function someFunc(payload)  {

    return payload.someFetching.then(res => {
            return ({ data: res })   
    }) 
}

1 Ответ

1 голос
/ 20 сентября 2019

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

Вот полезная статья, которую я прочитал, когда придумал свое решение.Был еще один, и если я смогу его найти - я добавлю его сюда.

https://medium.com/@TomasEhrlich/redux-saga-factories-and-decorators-8dd9ce074923

Это базовая настройка, которую я использую для проектов.Пожалуйста, обратите внимание, что я использую файл саги sagЯ приведу пример использования без него.Вы можете создать что-то по пути, чтобы помочь вам уменьшить этот шаблон.(может быть, даже что-то, чтобы помочь справиться с вашим сценарием опроса).

Я так ненавижу шаблон.Я даже создал инструмент, который я использую с моими API golang, для автоматической генерации некоторых из этих шаблонов путем обхода конечных точек документа / маршрутизатора swagger.

Редактировать: Добавлен пример контейнера.

пример компонента

import React, { Component } from 'react'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { getResource } from '../actions/resource'

const mapDispatchToProps = dispatch =>
  bindActionCreators(
    {
      getResource
    },
    dispatch
  )

class Example extends Component {
  handleLoad = () => {
    this.props.getResource({
      id: 1234
    })
  }

  render() {
    return <button onClick={this.handleLoad}>Load</button>
  }
}

export default connect(
  null,
  mapDispatchToProps
)(Example)

пример action / resource.js

import { useDispatch } from 'react-redux'

const noop = () => {}
const empty = []

export const GET_RESOURCE_REQUEST = 'GET_RESOURCE_REQUEST'
export const getResource = (payload, callback) => ({
  type: GET_RESOURCE_REQUEST,
  payload,
  callback,
})

// I use this for projects with hooks!
export const useGetResouceAction = (callback = noop, deps = empty) => {
  const dispatch = useDispatch()

  return useCallback(
    payload =>
      dispatch({ type: GET_RESOURCE_REQUEST, payload, callback }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [dispatch, ...deps]
  )
}

Достаточно простой файл редукционного действия.

пример redurs / resource.js

export const GET_RESOURCE_SUCCESS = 'GET_RESOURCE_SUCCESS'

const initialState = {
  resouce: null
}

export default (state = initialState, { type, payload }) => {
  switch (type) {
    case GET_RESOURCE_SUCCESS: {
      return {
        ...state,
        resouce: payload.Data,
      }
    }
}

Fairlyстандартная схема редуктора - ОБРАТИТЕ ВНИМАНИЕ, здесь используется _SUCCESS вместо _REQUEST.Это важно.

пример saga / resouce.js

import { takeLatest } from 'redux-saga/effects'

import { GET_RESOUCE_REQUEST } from '../actions/resource'

// need if not using the util
import { GET_RESOURCE_SUCCESS } from '../reducers/resource'

import * as resouceAPI from '../api/resource'

import { composeHandlers } from './sagaHandlers'

// without the util
function* getResourceHandler({ payload }) {
    const response = yield call(resouceAPI.getResouce, payload);

    response.data
      ? yield put({ type: GET_RESOURCE_SUCCESS, payload: response.data })
      : yield put({
          type: "GET_RESOURCE_FAILURE"
        });
  }

export const resourceSaga = [
  // Example that uses my util
  takeLatest(
    GET_RESOUCE_REQUEST,
    composeHandlers({
      apiCall: resouceAPI.getResouce
    })
  ),
  // Example without util
  takeLatest(
    GET_RESOUCE_REQUEST,
    getResourceHandler
  )
]

Пример файла саги для некоторого ресурса.Вот где я соединяю вызов API с вызовом редуктора в массиве для каждой конечной точки для повторного использования.Это тогда распространяется по корневой саге.Иногда вы можете захотеть использовать takeEvery вместо takeLatest - все зависит от варианта использования.

пример saga / index.js

import { all } from 'redux-saga/effects'

import { resourceSaga } from './resource'

export const sagas = [
  ...resourceSaga,
]

export default function* rootSaga() {
  yield all(sagas)
}

Простая корневая сага, немного похожая на rootreducer.

util saga / sagaHandlers.js

export function* apiRequestStart(action, apiFunction) {
  const { payload } = action

  let success = true
  let response = {}
  try {
    response = yield call(apiFunction, payload)
  } catch (e) {
    response = e.response
    success = false
  }

  // Error response
  // Edit this to fit your needs
  if (typeof response === 'undefined') {
    success = false
  }

  return {
    action,
    success,
    response,
  }
}

export function* apiRequestEnd({ action, success, response }) {
  const { type } = action
  const matches = /(.*)_(REQUEST)/.exec(type)
  const [, requestName] = matches

  if (success) {
    yield put({ type: `${requestName}_SUCCESS`, payload: response })
  } else {
    yield put({ type: `${requestName}_FAILURE` })
  }

  return {
    action,
    success,
    response,
  }
}

// External to redux saga definition -- used inside components
export function* callbackHandler({ action, success, response }) {
  const { callback } = action
  if (typeof callback === 'function') {
    yield call(callback, success, response)
  }

  return action
}

export function* composeHandlersHelper(
  action,
  {
    apiCall = () => {}
  } = {}
) {
  const { success, response } = yield apiRequestStart(action, apiCall)

  yield apiRequestEnd({ action, success, response })

  // This callback handler is external to saga
  yield callbackHandler({ action, success, response })
}

export function composeHandlers(config) {
  return function*(action) {
    yield composeHandlersHelper(action, config)
  }
}

Это очень сокращенная версия моего обработчика утилит saga.Это может быть много, чтобы переварить.Если вы хотите полную версию, я посмотрю, что я могу сделать.Мой полный обрабатывает такие вещи, как автоматическое создание тостов на API успех / ошибка и перезагрузка определенных ресурсов при успехе.Есть что-то для загрузки файлов.И еще одна вещь для обработки любой странной внутренней логики, которая может случиться (редко используйте это).

...