Как вы обновляете состояние с помощью функции get с избыточностью? Значения не обновляются правильно на множестве - PullRequest
1 голос
/ 05 апреля 2019

В настоящее время я получаю некоторые балансы с помощью метода getBalances с избыточностью.Когда приложение инициализирует, оно устанавливает «баланс» в JSON информации, однако, когда я снова вызываю getBalances, оно не переустанавливает баланс (не знаю почему).

Так что сейчас я вручнуюпытаясь обновить сальдо, вызвав метод getBalances, а затем установив результат этого на сальдо, однако я бегу к стенам.

Все, что я хотел бы сделать, это снова получить getBalances и просто установить это на сальдо,однако я не уверен, как бы я сделал это в приставке.

// Sequence of events (all of these are in different files of course)    

// Action Call
export const getBalances = exchange => 
action(actionTypes.GET_BALANCES.REQUEST, { exchange })

// API Call
export const getBalances = ({ userId, exchange }) =>
API.request(`/wallets/${userId}/${exchange}`, 'GET')

Полная сага

          import { fork, takeEvery, takeLatest, select, put, call, throttle } from 'redux-saga/effects'
          import { NavigationActions } from 'react-navigation'

          import * as actionTypes from '../action-types/exchanges.action-types'
          import * as API from '../api'

          import { storeType } from '../reducers'
          import { async, delay } from './asyncSaga'
          import { asyncAction } from './asyncAction'

          let getBalanceCount = 0

          export function* getBalances(action) {
          getBalanceCount++
          const state: storeType = yield select()
          yield fork(async, action, API.getBalances, {
            exchange: state.exchanges.selectedExchange._id,
            userId: state.auth.userId,
          })
          if (getBalanceCount > 1) {
            getBalanceCount--
            return
          }
          yield delay(10000)
          if (state.auth.token && state.auth.status === 'success')
            yield put({ type: action.type, payload: {} })
          /*
          if (state.auth.token && state.auth.status === 'success' && state.auth.phoneVerified)
            yield put({ type: action.type, payload: {} }) */
          }

          export function* getExchanges(action) {
          const state: storeType = yield select()
          yield fork(async, action, API.getExchanges, { userId: state.auth.userId })
          }

          export function* getExchangesSuccess(action) {
          const state: storeType = yield select()
          if (state.exchanges.exchanges.length > 0) {
            yield put({ type: actionTypes.GET_BALANCES.REQUEST, payload: {} })
          }
          }

          export function* addExchange(action) {
          const state: storeType = yield select()
          yield fork(async, action, API.addExchange, { ...action.payload, userId: state.auth.userId })
          }

          export function* addExchangeSuccess(action) {
          yield put(
            NavigationActions.navigate({
              routeName: 'wallets',
              params: { transition: 'slideToTop' },
            }),
          )
          }

          export function* updatePrices(action) {
          const async = asyncAction(action.type)
          const state = yield select()
          try {
            const res = yield call(API.getSymbolPriceTicker)
            yield put(async.success(res))
          } catch (error) {
            yield put(async.failure(error))
          }
          yield delay(10000)
          if (state.auth.token && state.auth.status === 'success' && state.auth.phoneVerified)
            yield put({ type: action.type, payload: {} })
          }

          export function* updateDaily(action) {
          const async = asyncAction(action.type)
          try {
            const res = yield call(API.getdayChangeTicker)
            yield put(async.success(res))
          } catch (error) {
            yield put(async.failure(error))
          }
          }

          export function* getFriendExchange(action) {
          yield fork(async, action, API.getExchanges, { userId: action.payload.userId })
          }

          export function* selectExchange(action) {
          yield put({ type: actionTypes.GET_BALANCES.REQUEST, payload: {} })
          }

          export function* exchangesSaga() {
          yield takeEvery(actionTypes.GET_SYMBOL_PRICE_TICKER.REQUEST, updatePrices)
          yield takeEvery(actionTypes.GET_DAY_CHANGE_TICKER.REQUEST, updateDaily)
          yield takeLatest(actionTypes.GET_FRIEND_EXCHANGES.REQUEST, getFriendExchange)
          yield takeLatest(actionTypes.GET_BALANCES.REQUEST, getBalances)
          yield takeLatest(actionTypes.GET_EXCHANGES.REQUEST, getExchanges)
          yield takeLatest(actionTypes.GET_EXCHANGES.SUCCESS, getExchangesSuccess)
          yield takeLatest(actionTypes.ADD_EXCHANGE.REQUEST, addExchange)
          yield takeLatest(actionTypes.ADD_EXCHANGE.SUCCESS, addExchangeSuccess)
          yield takeLatest(actionTypes.SELECT_EXCHANGE, selectExchange)
          }

Полный обменный редуктор

    import { mergeDeepRight } from 'ramda'
    import {
      GET_BALANCES,
      GET_EXCHANGES,
      SELECT_EXCHANGE,
      GET_SYMBOL_PRICE_TICKER,
      GET_DAY_CHANGE_TICKER,
      GET_FRIEND_EXCHANGES,
      ADD_EXCHANGE,
    } from '../action-types/exchanges.action-types'
    import { LOG_OUT, VALIDATE_TOKEN } from '../action-types/login.action-types'
    import { ExchangeService } from '../constants/types'

    // Exchanges Reducer

    export type exchangeState = {
      status: string
      _id: string
      label: string
      displayName: string
      dayChangeTicker: any
      symbolPriceTicker: any
      balances: any,
    }

    export type exchangesState = {
      status: string
      selectedExchange: exchangeState
      addExchange: {
        status: string,
      }
      exchanges: Array<ExchangeService>
      friendExchanges: Array<ExchangeService>,
    }

    const initialExchangeState: exchangeState = {
      status: 'pending',
      _id: '',
      label: '',
      displayName: null,
      dayChangeTicker: {},
      symbolPriceTicker: {},
      balances: {},
    }

    const initialState: exchangesState = {
      status: 'pending',
      selectedExchange: {
        status: 'pending',
        _id: '',
        label: '',
        displayName: null,
        dayChangeTicker: {},
        symbolPriceTicker: {},
        balances: {},
      },
      addExchange: {
        status: 'pending',
      },
      exchanges: [],
      friendExchanges: [],
    }

    export default (state = initialState, action) => {
      switch (action.type) {
        case SELECT_EXCHANGE:
        case GET_SYMBOL_PRICE_TICKER.SUCCESS:
        case GET_DAY_CHANGE_TICKER.SUCCESS:
        case GET_BALANCES.REQUEST:
        case GET_BALANCES.SUCCESS:
        case GET_BALANCES.FAILURE:
          return { ...state, selectedExchange: selectedExchangeReducer(state.selectedExchange, action) }

        case GET_EXCHANGES.REQUEST:
        case GET_FRIEND_EXCHANGES.REQUEST:
          return { ...state, status: 'loading' }

        case GET_EXCHANGES.SUCCESS:
          if (action.payload.exchanges.length > 0) {
            return mergeDeepRight(state, {
              exchanges: action.payload.exchanges,
              selectedExchange: { ...action.payload.exchanges[0] },
              status: 'success',
            })
          }
          return { ...state, status: 'success' }

        case GET_FRIEND_EXCHANGES.SUCCESS:
          return { ...state, friendExchanges: action.payload.exchanges, status: 'success' }

        case GET_EXCHANGES.FAILURE:
        case GET_FRIEND_EXCHANGES.FAILURE:
          return { ...state, message: action.payload.message, status: 'failure' }

        case LOG_OUT.SUCCESS:
        case VALIDATE_TOKEN.FAILURE:
          return initialState

        case ADD_EXCHANGE.REQUEST:
          return { ...state, addExchange: { status: 'loading' } }

        case ADD_EXCHANGE.SUCCESS:
          return { ...state, addExchange: { status: 'success' } }

        case ADD_EXCHANGE.FAILURE:
          return { ...state, addExchange: { status: 'failure' } }

        default:
          return state
      }
    }

    const selectedExchangeReducer = (state = initialExchangeState, action) => {
      switch (action.type) {
        case SELECT_EXCHANGE:
          if (action.payload.exchange) {
            return { ...state, ...action.payload.exchange }
          }
          return initialExchangeState

        case GET_SYMBOL_PRICE_TICKER.SUCCESS:
          const symbolPriceTicker = action.payload.data.data.reduce((result, ticker) => {
            result[ticker.symbol] = ticker.price
            return result
          }, {})
          return { ...state, symbolPriceTicker }

        case GET_DAY_CHANGE_TICKER.SUCCESS:
          const dayChangeTicker = action.payload.data.data.reduce((result, ticker) => {
            result[ticker.symbol] = ticker.priceChangePercent
            return result
          }, {})
          return { ...state, dayChangeTicker }

        // Get selected exchange's balances
        case GET_BALANCES.REQUEST:
          return { ...state, status: 'loading' }

        case GET_BALANCES.SUCCESS:
          return {
            ...state,
            balances: action.payload.balances,
            status: 'success',
          }

        case GET_BALANCES.FAILURE:
          return { ...state, balances: [], message: action.payload.message, status: 'failure' }

        default:
          return state
      }
    }

Вызов физической функции(fetchData - это моя попытка переназначить exchange.balances ...)

    // this.props.selectExchange(exchange) just selects the exchange then calls a GET_BALANCES.REQUEST
    fetchData = (exchange) => {
      const { selectedExchange } = this.props.exchanges
      // const { exchanges } = this.props
      // //console.log('TesterTesterTester: ' + JSON.stringify(this.props.selectExchange(exchange)))
      // console.log('Test:' + JSON.stringify(this.props.getBalances(exchange.balances)))
      // let vari = JSON.stringify(this.props.getBalances(exchange.balances))
      // let newVari = JSON.parse(vari.slice(45, vari.length-2))
      // exchange.balances = newVari
      // console.log('Old Values: ' + JSON.stringify(exchange.balances))
      console.log('Testt: ' + JSON.stringify(this.props.selectExchange(exchange.balances1)))
      this.props.selectExchange(exchange.balances1)
      console.log('This exchange after: ' + selectedExchange)
      console.log('This is the balances: '+ JSON.stringify(selectedExchange.balances1))
      exchange.balances = selectedExchange.balances1
      console.log('Another one: ' + JSON.stringify(exchange.balances))
      selectedExchange.balances1 = []

      this.setState({ refreshing: false })
    }

    renderExchange = (exchange, index) => {
      const { refreshing } = this.state
      const { selectedExchange } = this.props.exchanges
      const { symbolPriceTicker, dayChangeTicker } = selectedExchange

      // I'm trying to alter exchange.balances

      if (refreshing) {
        this.fetchData(exchange)
      }

      return (
        <View style={screenStyles.container}>
          <ExchangeBox
            balances={exchange.balances}
            displayName={exchange.label}
            symbolPriceTicker={symbolPriceTicker}
            exchangeIndex={index}
            onSend={this.onSend}
          />
          <View style={screenStyles.largerContainer}>
            {symbolPriceTicker && dayChangeTicker && exchange.balances && (
              <ScrollView
                style={screenStyles.walletContainer}
                horizontal={true}
                showsHorizontalScrollIndicator={false}
                decelerationRate={0}
                snapToInterval={100} //your element width
                snapToAlignment={'center'}
              >
                {Object.keys(exchange.balances).map(
                  symbol =>
                    COIN_INFO[symbol] &&
                    symbolPriceTicker[`${symbol}USDT`] && (
                      <CoinContainer
                        key={symbol}
                        symbol={symbol}
                        available={exchange.balances[symbol].free}
                        price={symbolPriceTicker[`${symbol}USDT`]}
                        dayChange={dayChangeTicker[`${symbol}USDT`]}
                      />
                    ),
                )}
              </ScrollView>
            )}
          </View>
        </View>
      )
    }

После этого я обнаружил, что exchange.balances не собирал значения, потому что .balances был расширением JSON JSONобмен.Я попытался создать все экземпляры весов в другом месте (например, в балансах редуктора1), и это не сильно помогло при попытке обновления.

Вот еще один вызов весов в типах .ts

  export type ExchangeService = {
    _id: string
    label: string
    displayName: string
    balances: any,
  }

Большое вам спасибо @Dylan за то, что прошли через это со мной

1 Ответ

0 голосов
/ 06 апреля 2019

Как уже говорилось в комментариях:

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

Вместо этого, при использовании Redux с React, вы должны почти полностью полагаться наRedux для управления вашим состоянием.Ваши компоненты React должны использоваться только для отправки действий Redux и отображения входящих данных в соответствии со следующим потоком данных:

  1. Компонент отправляет действие в хранилище.
  2. Действие обрабатываетсявашими саги и редукторами для обновления состояния в хранилище.
  3. Redux обновляет реквизиты, предоставляемые компоненту через connect().
  4. Обновленные реквизиты вызывают повторную визуализацию компонента.
  5. Обновленное состояние теперь доступно компоненту через this.props в вашей функции render() в цикле рендеринга.

Поскольку это относится к вашему компоненту, ваш *Функция 1023 *, вероятно, будет упрощена примерно до следующего:

fetchData = exchange => {
    this.props.selectExchange(exchange);
    // ...any additional action dispatches required to fetch data.
}

Если ваши редукторы и саги написаны правильно (как они выглядят), то ваше состояние Redux будет обновляться асинхронно.Когда обновление будет завершено, реквизиты ваших компонентов будут обновлены, и будет запущен повторный рендеринг.Затем в вашей функции render() все данные, которые вы выводите из состояния, должны быть получены из this.props.Делая это, вы в значительной степени гарантируете актуальность дисплея:

render() {
    const exchange = this.props.selectedExchange;

    return (
        <View style={screenStyles.container}>
            <ExchangeBox
                balances={exchange.balances}
                displayName={exchange.label}
                // ... more props
            />
            //... more components
        </View>
    );
}

На этом этапе ваш компонент настроен с простым и идиоматическим потоком данных Redux.Если с этого момента вы столкнетесь с какими-либо проблемами с обновлениями состояния, вы можете начать поиск проблем в ваших саг / редукторах.

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


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

yield delay(10000)
if (state.auth.token && state.auth.status === 'success')
    yield put({ type: action.type, payload: {} })

Я предполагаю, что цель этого состоит в том, чтобы обновлять balances каждые 10 секунд после того, как сага была первоначально запущена.Я также предполагаю, что у вас есть getBalancesCount, чтобы ограничить количество экземпляров цикла getBalances одновременно.Давайте рассмотрим, как это происходит:

  • Первоначальная отправка -> yield takeLatest(actionTypes.GET_BALANCES.REQUEST, getBalances) начинается getBalances.
  • getBalances попаданий getBalanceCount++, поэтому getBalanceCount == 1
  • getBalances повторяется из-за put({ type: action.type, payload: {} })
  • getBalances хитов getBalanceCount++, поэтому getBalanceCount == 2
  • getBalances хитов if (getBalanceCount > 1), удовлетворяет условию,уменьшает getBalanceCount до 1 и выходит.

Теперь я предполагаю, что yield fork(async, action, API.getBalances...) в конечном итоге отправляет GET_BALANCES.SUCCESS в asyncSaga, поэтому он будет продолжать работать каждый раз, когда вы отправляете GET_BALANCES.REQUEST из-за пределов саги.

Вы можете исправить логику для getBalancesCount.Однако нам вообще не нужен счетчик, чтобы ограничить количество одновременно работающих getBalances.Это уже встроено в takeLatest:

Каждый раз, когда действие отправляется в магазин.И если это действие соответствует pattern, takeLatest запускает новую задачу saga в фоновом режиме. Если задача saga была запущена ранее (для последнего действия, отправленного перед фактическим действием), и если эта задача все еще выполняется, задача будет отменена .

(См .: https://redux -saga.js.org / docs / api / )

Так что все, что вам действительно нужно сделать, это удалить свою собственную логику:

export function* getBalances(action) {
    const state: storeType = yield select()
    yield fork(async, action, API.getBalances, {
        exchange: state.exchanges.selectedExchange._id,
        userId: state.auth.userId,
    })

    yield delay(10000)
    if (state.auth.token && state.auth.status === 'success')
        yield put({ type: action.type, payload: {} })
    }
}

Кроме того, повторение саги, направляя то же действие из саги, является своего рода анти-паттерном.while(true) имеет тенденцию быть более идиоматичным, несмотря на то, что выглядит странно:

export function* getBalances(action) {
    while(true) {
        const state: storeType = yield select()
        yield fork(async, action, API.getBalances, {
            exchange: state.exchanges.selectedExchange._id,
            userId: state.auth.userId,
        })

        yield delay(10000);
        if (!state.auth.token || state.auth.status !== 'success')
           return;
        }
    }
}

Хотя, если у вас есть другие вещи, потребляющие GET_BALANCES.REQUEST по какой-то причине, это может не сработать для вас.В этом случае я бы использовал отдельные действия.(РЕДАКТИРОВАТЬ: я перечитал ваш редуктор, и вы действительно используете действие, чтобы установить состояние loading. В этом случае ваш подход, вероятно, в порядке.)

...