如何使用redux get函数更新某些状态?值在集合上无法正确更新

时间:2019-04-05 04:09:33

标签: reactjs redux redux-saga

我目前正在通过redux getBalances方法获取一些余额。当应用程序初始化时,它将“余额”设置为信息的JSON,但是当我再次调用getBalances时,它不会重新设置余额(不知道为什么)。

现在,我正在尝试通过调用getBalances方法然后将其结果设置为余额来更新余额,但是我遇到了麻烦。

我想要做的就是再次将getBalances设置为平衡,但是我不确定如何在redux中执行此操作。

// 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)
          }

Full Exchange Reducer

    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是exchange JSON的JSON扩展。我尝试将所有余额实例都放在其他位置(例如在reducer balances1中),这在尝试更新时没有太大帮助。

这是type.ts中余额的另一个呼唤

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

非常感谢@Dylan和我一起学习

1 个答案:

答案 0 :(得分:0)

如评论中所述:

您稍微想过如何通过fetchData管理状态。看来您正在尝试分派动作并在相同的渲染周期内使用结果,而使用Redux充其量只能达到不一致的结果。

相反,当将Redux与React一起使用时,您应该几乎完全依赖Redux来处理状态管理。根据以下数据流,您的React组件应仅用于调度Redux操作和显示传入数据:

  1. Component向商店调度一个动作。
  2. 该动作由您的sagas和reducers处理,以更新商店中的状态。
  3. Redux通过connect()更新提供给组件的道具。
  4. 更新的道具会触发组件的重新渲染。
  5. 在触发的渲染周期,通过this.props函数中的render(),组件可以使用更新的状态。

由于这与您的组件有关,因此您的fetchData函数可能会简化为以下形式:

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

如果您的reducers和sagas正确编写(看起来很正确),那么您的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数据流。如果从此时开始遇到状态更新方面的任何问题,则可以开始查看您的sagas / reducer中的问题。

下面是我的原始答案,其中谈到了已发布的sagas的潜在问题,为了完整起见,我将保留该问题。


感谢您所做的修改。花了我一段时间才能理解,因为这种传奇结构确实很不寻常,但是我认为我对这里发生的事情有一个了解。如果我有任何错误的假设,请纠正我。

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

我假设这样做的目的是,一旦传奇开始就每10秒更新一次balances。我还假设您有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时,它将继续工作。 / p>

您可以修正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的东西,那么这可能对您不起作用。在那种情况下,我会使用单独的动作。 (编辑:我重新阅读了您的reducer,实际上您正在使用该操作来设置loading状态。在这种情况下,您的方法可能还不错。)