use*_*262 1 javascript reactjs redux react-redux
我正在使用 React 和 Redux 构建应用程序。
我有一个 Account 组件,它从componentWillMount方法中的服务器获取数据。在获取数据时,组件必须显示“正在加载”文本,因此我已将“isFetching”属性添加到帐户缩减程序中。从服务器获取数据时,此属性设置为 true。
问题是,在获取数据时,render方法中“isFetching”属性的值为 false,而同时 的值为store.getState().account.isFetchingtrue(必须如此)。这导致例外,因为this.props.isFetching是假的,那么代码是试图展示this.props.data.protectedString,而data仍然被从服务器加载(所以它为空)。
我假设 mapStateToProps 绑定了一些错误的值(可能是初始状态),但我不知道为什么以及如何修复它。
这是我的 AccountView 代码:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionCreators from '../../actions/account';
class AccountView extends React.Component {
componentWillMount() {
const token = this.props.token;
// fetching the data using credentials
this.props.actions.accountFetchData(token);
}
render() {
console.log("store state", window.store.getState().account); // isFetching == true
console.log("componentState", window.componentState); // isFetching == false
return (
<div>
{this.props.isFetching === true ? <h3>LOADING</h3> : <div>{this.props.data.protectedString}</div> }
</div>
);
}
}
const mapStateToProps = (state) => {
window.componentState = state.account;
return {
data: state.account.data,
isFetching: state.account.isFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(actionCreators, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(AccountView);
Run Code Online (Sandbox Code Playgroud)
帐户减少器:
const initialState = {
data: null,
isFetching: false
};
export default function(state = initialState, action) {
switch (action.type) {
case ACCOUNT_FETCH_DATA_REQUEST:
return Object.assign({}, state, {
isFetching: true
});
case ACCOUNT_RECEIVE_DATA:
return Object.assign({}, state, {
data: action.payload.data,
isFetching: false
});
default:
return state;
}
}
Run Code Online (Sandbox Code Playgroud)
行动:
export function accountReceiveData(data) {
return {
type: ACCOUNT_RECEIVE_DATA,
payload: {
data
}
};
}
export function accountFetchDataRequest() {
return {
type: ACCOUNT_FETCH_DATA_REQUEST
};
}
export function accountFetchData(token) {
return (dispatch, state) => {
dispatch(accountFetchDataRequest());
axios({
// axios parameters to fetch data from the server
})
.then(checkHttpStatus)
.then((response) => {
dispatch(accountReceiveData(response.data));
})
.catch((error) => {
//error handling
});
};
}
Run Code Online (Sandbox Code Playgroud)
这就是我创建商店的方式:
import { applyMiddleware, compose, createStore } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import rootReducer from '../reducers';
export default function configureStore(initialState, history) {
// Add so dispatched route actions to the history
const reduxRouterMiddleware = routerMiddleware(history);
const middleware = applyMiddleware(thunk, reduxRouterMiddleware);
const createStoreWithMiddleware = compose(
middleware
);
return createStoreWithMiddleware(createStore)(rootReducer, initialState);
}
Run Code Online (Sandbox Code Playgroud)
在 index.js 中:
import { createBrowserHistory } from 'history';
import { syncHistoryWithStore } from 'react-router-redux';
import configureStore from './store/configureStore';
const initialState = {};
const newBrowserHistory = createBrowserHistory();
const store = configureStore(initialState, newBrowserHistory);
const history = syncHistoryWithStore(newBrowserHistory, store);
// for test purposes
window.store = store;
Run Code Online (Sandbox Code Playgroud)
我的代码基于这个例子 - https://github.com/joshgeller/react-redux-jwt-auth-example
代码看起来相同,但由于某些模块的新版本,我更改了一些地方。
当您使用 react & redux 获取数据时,您应该始终问自己这两个问题:
您已经使用 the 回答了第二个问题,isFetching但第一个问题仍然存在,这就是导致您出现问题的原因。你应该做的是didInvalidate在你的减速器中使用(https://github.com/reactjs/redux/blob/master/docs/advanced/AsyncActions.md)
有了它,didInvalidate您可以轻松地检查您的数据是否有效,并在需要时通过调度类似INVALIDATE_ACCOUNT. 由于您尚未获取数据,因此默认情况下您的数据无效。
(奖励)您可能会使数据无效的一些示例:
这是您的渲染应该是什么样子:
class AccountView extends React.Component {
componentDidMount() { // Better to call data from componentDidMount than componentWillMount: https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/
const token = this.props.token;
// fetching the data using credentials
if (this.props.didInvalidate && !this.props.isFetching) {
this.props.actions.accountFetchData(token);
}
}
render() {
const {
isFetching,
didInvalidate,
data,
} = this.props;
if (isFetching || (didInvalidate && !isFetching)) {
return <Loading />; // You usually have your loading spinner or so in a component
}
return (
<div>
{data.protectedString}
</div>
);
}
}
Run Code Online (Sandbox Code Playgroud)
这是您的帐户减速器didInvalidate:
const initialState = {
isFetching: false,
didInvalidate: true,
data: null,
};
export default function(state = initialState, action) {
switch (action.type) {
case INVALIDATE_ACCOUNT:
return { ...state, didInvalidate: true };
case ACCOUNT_FETCH_DATA_REQUEST:
return {
...state,
isFetching: true,
};
case ACCOUNT_RECEIVE_DATA:
return {
...state,
data: action.payload.data,
isFetching: false,
didInvalidate: false,
};
default:
return state;
}
}
Run Code Online (Sandbox Code Playgroud)
在您的新生命周期下方:
1. 先渲染
{ isFetching: false, didInvalidate: true, data: null }<Loading />2. 组件DidMount
3. 函数调用:accountFetchData (1)
{ type: ACCOUNT_FETCH_DATA_REQUEST }4. 账户缩减器
{ isFetching: true, didInvalidate: false, data: null }5. 第二次渲染
{ isFetching: true, didInvalidate: false, data: null }<Loading />6. 函数调用:accountFetchData (2)
7. 账户缩减器
{ isFetching: false, didInvalidate: false, data: { protectedString: '42: The answer to life' } }8. 第三次渲染
{ isFetching: false, didInvalidate: false, data: { protectedString: '42: The answer to life' } }<div>42: The answer to life</div>希望能帮助到你。
编辑:让我在另一个答案中的一个评论中回答您的问题
@Icepickle 我不确定这是一种干净的方法。假设用户将转到 /account URL。然后到其他一些 URL。然后回到/帐户。虽然数据将第二次从服务器加载,但 isFetching 将为 true 并且必须显示“正在加载”文本,但“数据”变量不会为空,因为它将包含来自前一个请求的数据. 因此,将显示旧数据而不是“加载”。
有了这个didInvalidate值,就没有无限制重新获取的风险,因为组件会知道您的数据是否有效。
在 中componentDidMount,重新获取的条件将为假,因为值如下{ isFetching: false, didInvalidate: false }。那就不用refetch了。
if (this.props.didInvalidate && !this.props.isFetching)
Run Code Online (Sandbox Code Playgroud)
奖励:但是请注意didInvalidate.
人们很少谈论这个问题,但你会开始问这个问题“从我的数据无效时开始?” (= 我什么时候应该重新获取?)
减速机
如果可以的话,让我从长远来看重构您的减速器代码。
您的减速器将更加模块化且易于维护。
import { combineReducers } from 'redux';
export default combineReducers({
didInvalidate,
isFetching,
lastFetchDate,
data,
errors,
});
function lastFetchDate(state = true, action) {
switch (action.type) {
case 'ACCOUNT_RECEIVE_DATA':
return new Date();
default:
return state;
}
}
function didInvalidate(state = true, action) {
switch (action.type) {
case 'INVALIDATE_ACCOUNT':
return true;
case 'ACCOUNT_RECEIVE_DATA':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case 'ACCOUNT_FETCH_DATA_REQUEST':
return true;
case 'ACCOUNT_RECEIVE_DATA':
return false;
default:
return state;
}
}
function data(state = {}, action) {
switch (action.type) {
case 'ACCOUNT_RECEIVE_DATA':
return {
...state,
...action.payload.data,
};
default:
return state;
}
}
function errors(state = [], action) {
switch (action.type) {
case 'ACCOUNT_ERRORS':
return [
...state,
action.error,
];
case 'ACCOUNT_RECEIVE_DATA':
return state.length > 0 ? [] : state;
default:
return state;
}
}
Run Code Online (Sandbox Code Playgroud)
行动
我将只添加失效函数,以便更容易理解我在组件中调用了哪个函数。(注意:我没有重命名你的函数,但你一定要注意命名)
export function invalidateAccount() {
return {
type: INVALIDATE_ACCOUNT
};
}
export function accountReceiveData(data) {
return {
type: ACCOUNT_RECEIVE_DATA,
payload: {
data
}
};
}
export function accountFetchDataRequest() {
return {
type: ACCOUNT_FETCH_DATA_REQUEST
};
}
export function accountFetchData(token) {
return (dispatch, state) => {
dispatch(accountFetchDataRequest());
axios({
// axios parameters to fetch data from the server
})
.then(checkHttpStatus)
.then((response) => {
dispatch(accountReceiveData(response.data));
})
.catch((error) => {
//error handling
});
};
}
Run Code Online (Sandbox Code Playgroud)
成分
您将不得不在某个时候使您的数据无效。我认为您的帐户数据在 60 分钟后将不再有效。
import isBefore from 'date-fns/is_before';
import addMinutes from 'date-fns/add_minutes'
const ACCOUNT_EXPIRATION_MINUTES = 60;
class AccountView extends React.Component {
componentDidMount() {
const token = this.props.token;
// fetching the data using credentials
if (this.props.didInvalidate && !this.props.isFetching) {
this.props.actions.accountFetchData(token);
}
// Below the check if your data is expired or not
if (
!this.props.didInvalidate && !this.props.isFetching &&
isBefore(
addMinutes(this.props.lastFetchDate, ACCOUNT_EXPIRATION_MINUTES), new Date()
)
) {
this.props.actions.invalidateAccount();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.didInvalidate && !nextProps.isFetching) {
nextProps.actions.accountFetchData(token);
}
}
render() {
const {
isFetching,
didInvalidate,
lastFetchDate,
data,
} = this.props;
/*
* Do not display the expired data, the componentDidMount will invalidate your data and refetch afterwars
*/
if (!didInvalidate && !isFetching &&
isBefore(addMinutes(lastFetchDate, ACCOUNT_EXPIRATION_MINUTES), new Date())
) {
return <Loading />;
}
if (isFetching || (didInvalidate && !isFetching)) {
return <Loading />; // You usually have your loading spinner or so in a component
}
return (
<div>
{data.protectedString}
</div>
);
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码可以更清晰,但这样阅读更清晰:)
| 归档时间: |
|
| 查看次数: |
1091 次 |
| 最近记录: |