你如何使用 redux get 函数更新一些状态?值未在现场正确更新

How do you update some state with a redux get function? Values not updating correctly on set

我目前正在通过 redux getBalances 方法获取一些余额。当应用程序初始化时,它将 'balances' 设置为 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)
          }

完全交换减速器

    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 扩展。我尝试在其他地方制作所有余额实例(比如在 reducer balances1 中),这在尝试更新时没有多大帮助。

这是 types.ts

中的另一个余额调用
  export type ExchangeService = {
    _id: string
    label: string
    displayName: string
    balances: any,
  }

非常感谢@Dylan 和我一起走过这一切

如评论中所述:

您有点想多了如何通过 fetchData 管理您的状态。看来您正在尝试分派一个动作并在同一渲染周期中使用结果,使用 Redux 最多会产生不一致的结果。

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

  1. 组件向商店分派一个动作。
  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.
}

如果您的 reducer 和 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/reducers 问题。

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


感谢您的澄清编辑。我花了一段时间才明白,因为这个 saga 结构真的很不寻常,但我想我知道这里发生了什么。如果我有任何错误的假设,请纠正我。

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

我假设这样做的目的是在 saga 最初启动后每 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...) 最终会在 asyncSaga 中调度 GET_BALANCES.SUCCESS,因此每次您从 saga 外部调度 GET_BALANCES.REQUEST 时它都会继续工作.

您可以修正 getBalancesCount 的逻辑。但是,我们根本不需要一个计数器来限制一次并发的getBalances运行个数。这已经内置于 takeLatest:

Each time an action is dispatched to the store. And if this action matches pattern, takeLatest starts a new saga task in the background. If a saga task was started previously (on the last action dispatched before the actual action), and if this task is still running, the task will be cancelled.

(参见: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: {} })
    }
}

此外,通过从 saga 中调度相同的动作来重复 saga 是一种反模式。 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 状态。在这种情况下,您的方法可能没问题。)