你如何使用 redux get 函数更新一些状态?值未在片场正确更新 [英] How do you update some state with a redux get function? Values not updating correctly on set

查看:37
本文介绍了你如何使用 redux get 函数更新一些状态?值未在片场正确更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I currently am fetching some balances via a redux getBalances method. When the app initializes it sets 'balances' to the JSON of information, however when I call getBalances again it doesn't re-set the balances (no idea why).

所以现在,我正在尝试通过调用 getBalances 方法手动更新余额,然后将结果设置为余额,但是我遇到了障碍.

So right now, I'm manually trying to update the balances by calling the getBalances method then setting the result of that to balances, however I'm running into walls.

我想要做的只是再次 getBalances 并仅将其设置为 balances,但是我不确定如何在 redux 中做到这一点.

All I'd like to do is getBalances again and merely set this to balances, however I'm not sure how I'd do this in 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...)

Physical function call (fetchData is my attempt at reassigning 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 扩展.我尝试在其他地方创建所有 balances 实例(例如在 reducer balances1 中),但在尝试更新时没有太大帮助.

After messing with this I found that exchange.balances wasn't grabbing values because the .balances was a JSON extension of the JSON of exchange. I tried making all of the instance of balances elsewhere (like in the reducer balances1) and that didn't help much when trying to update.

这是 types.ts 中的另一个余额调用

Here's another call of balances in types.ts

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

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

Thank you so much @Dylan for walking through this with me

推荐答案

如评论中所述:

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

You're slightly overthinking how you're managing your state via fetchData. It appears you're attempting to dispatch an action and use the results in the same render cycle, which will have inconsistent results at best using Redux.

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

Instead, when using Redux with React, you should almost completely rely on Redux to handle your state management. Your React components should only be used for dispatching Redux actions and displaying incoming data, as per the following data flow:

  1. 组件向商店分派一个动作.
  2. 该操作由您的 saga 和 reducer 处理以更新 store 中的状态.
  3. Redux 通过 connect() 更新提供给组件的 props.
  4. 更新后的 props 会触发组件的重新渲染.
  5. 更新的状态现在可通过 this.props 在触发的渲染周期中的 render() 函数中提供给组件.
  1. Component dispatches an action to the store.
  2. The action is processed by your sagas and reducers to update the state in the store.
  3. Redux updates the props being provided to the component via connect().
  4. The updated props trigger a re-render of the component.
  5. The updated state is now available to the component via this.props in your render() function on the triggered render cycle.

因为这与您的组件有关,所以您的 fetchData 函数可能会简化为如下所示:

As this relates to your component, your fetchData function would probably be simplified to something like this:

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

如果您的 reducer 和 saga 编写正确(它们看起来是这样),那么您的 Redux 状态将异步更新.更新完成后,您的组件道具将更新并触发重新渲染.然后,在您的 render() 函数中,您从状态显示的所有数据都应该从 this.props 派生.通过这样做,您可以在很大程度上保证显示是最新的:

If your reducers and sagas are written correctly (which they appear to be), then your Redux state will be updated asynchronously. When the update is complete, your components props will be updated and a re-render triggered. Then, in your render() function, all data you display from the state should be derived from this.props. By doing this, you largely guarantee that the display is up-to-date:

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 是否存在问题.

At this point, your component is setup with a simple and idiomatic Redux data flow. If you encounter any issues with state updates from this point, you can start looking at your sagas/reducers for issues.

以下是我的原始回答,其中谈到了已发布的传奇的潜在问题,为了完整起见,我将保留这些问题.

Below is my original answer which talked about a potential issue with the posted sagas, which I'll keep for completeness.

感谢澄清编辑.我花了一段时间才理解,因为这个传奇结构真的很不寻常,但我想我对这里发生的事情有一个想法.如果我做出任何错误的假设,请纠正我.

Thanks for the clarifying edits. Took me awhile to understand, because this saga structure is really unusual, but I think I have an idea of what's going on here. Please correct me if I make any wrong assumptions.

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

我认为这样做的目的是在传奇开始后每 10 秒更新一次 balances.我还假设您有 getBalancesCount 来限制 getBalances 一次循环的实例数.让我们来看看这是如何发生的:

I assume the purpose of this is to update the balances every 10 seconds once the saga has been initially kicked off. I'm also assuming you have getBalancesCount to limit the number of instances of getBalances looping at once. Lets walk through how this happens:

  • 初始调度 -> 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 和退出.
  • Initial dispatch -> yield takeLatest(actionTypes.GET_BALANCES.REQUEST, getBalances) kicks off getBalances.
  • getBalances hits getBalanceCount++, so getBalanceCount == 1
  • getBalances is repeated, due to put({ type: action.type, payload: {} })
  • getBalances hits getBalanceCount++, so getBalanceCount == 2
  • getBalances hits if (getBalanceCount > 1), satisfies the condition, decrements getBalanceCount to 1 and exits.

现在,我假设 yield fork(async, action, API.getBalances...) 最终在 asyncSagaGET_BALANCES.SUCCESS>,因此每次您从 saga 外部发送 GET_BALANCES.REQUEST 时它都会继续工作.

Now, I'm assuming yield fork(async, action, API.getBalances...) eventually dispatches GET_BALANCES.SUCCESS in asyncSaga, so it'll continue to work each time you dispatch GET_BALANCES.REQUEST from outside the saga.

您可以修复getBalancesCount 的逻辑.但是,我们根本不需要计数器来限制同时运行的 getBalances 的数量.这已经内置在 takeLatest 中:

You could fix up the logic for getBalancesCount. However, we don't need a counter at all to limit the number of concurrent getBalances running at once. This is already built into takeLatest:

每次将操作分派到商店时.如果此操作与 pattern 匹配,takeLatest 在后台启动一个新的 saga 任务.如果一个 saga 任务之前已经启动(在实际操作之前调度的最后一个操作上),并且如果该任务仍在运行,则该任务将被取消.

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

所以你真正需要做的就是删除你的自定义逻辑:

So all you really have to do is remove your custom logic:

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 内部调度相同的 action 来重复 saga 是一种反模式.while(true) 尽管看起来很奇怪,但往往更加地道:

In addition, repeating a saga by dispatching the same action from within the saga is kind of an anti-pattern. while(true) tends to be more idiomatic despite looking strange:

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 状态.在这种情况下,您的方法可能没问题.)

Though, if you have other things consuming GET_BALANCES.REQUEST for some reason, this might not work for you. In that case, I'd be using separate actions though. ( I re-read your reducer, and you're indeed using the action to set a loading state. In this case, your approach is probably fine.)

这篇关于你如何使用 redux get 函数更新一些状态?值未在片场正确更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆