你如何使用 redux get 函数更新一些状态?值未在片场正确更新 [英] How do you update some state with a redux get function? Values not updating correctly on set
问题描述
我目前正在通过 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:
- 组件向商店分派一个动作.
- 该操作由您的 saga 和 reducer 处理以更新 store 中的状态.
- Redux 通过
connect()
更新提供给组件的 props. - 更新后的 props 会触发组件的重新渲染.
- 更新的状态现在可通过
this.props
在触发的渲染周期中的render()
函数中提供给组件.
- Component dispatches an action to the store.
- The action is processed by your sagas and reducers to update the state in the store.
- Redux updates the props being provided to the component via
connect()
. - The updated props trigger a re-render of the component.
- The updated state is now available to the component via
this.props
in yourrender()
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 offgetBalances
. getBalances
hitsgetBalanceCount++
, sogetBalanceCount == 1
getBalances
is repeated, due toput({ type: action.type, payload: {} })
getBalances
hitsgetBalanceCount++
, sogetBalanceCount == 2
getBalances
hitsif (getBalanceCount > 1)
, satisfies the condition, decrementsgetBalanceCount
to1
and exits.
现在,我假设 yield fork(async, action, API.getBalances...)
最终在 asyncSaga
GET_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 newsaga
task in the background. If asaga
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屋!