组件中的React-Redux状态与商店中的状态不同

时间:2017-10-23 23:18:58

标签: javascript reactjs redux react-redux

我正在使用React和Redux构建应用程序。

我有一个Account组件,可以使用componentWillMount方法从服务器获取数据。在获取数据时,组件必须显示“加载”文本,因此我已将“isFetching”属性添加到帐户缩减器。从服务器获取数据时,此属性设置为true。

问题是,在获取数据时,render方法中“isFetching”属性的值为false,同时store.getState().account.isFetching的值为true(如肯定是)。这会导致异常,因为this.props.isFetching为false,因此代码正在尝试显示this.props.data.protectedString,而data仍在从服务器加载(因此它为null)。

我假设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);

帐户减速机:

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;
  }
}

操作:

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

这就是我创建商店的方式:

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

在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;

我的代码基于此示例 - https://github.com/joshgeller/react-redux-jwt-auth-example

代码看起来一样,但由于某些模块的新版本,我改变了一些地方。

2 个答案:

答案 0 :(得分:4)

当你使用react&amp; amp;取数据时,你应该总是问自己这两个问题。终极版:

  1. 我的数据是否仍然有效?
  2. 我目前是否正在提取数据?
  3. 您已使用isFetching回答了第二个问题,但第一个问题仍然存在,这就是造成问题的原因。 您应该做的是在减速器中使用didInvalidatehttps://github.com/reactjs/redux/blob/master/docs/advanced/AsyncActions.md

    使用didInvalidate,您可以轻松检查数据是否有效,并在需要时通过调度INVALIDATE_ACCOUNT等操作使其无效。 由于您尚未提取数据,因此默认情况下您的数据无效。

    (额外奖励)您可能会使数据无效的一些示例:

    • 最后一次获取日期是&gt; X分钟
    • 您修改了一些数据并需要再次获取此数据
    • 其他人修改了此数据,您通过Websockets
    • 收到失效操作

    以下是渲染的外观:

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

    以下是您的帐户缩减器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;
      }
    }
    

    在新生命周期之下:

    <强> 1。首先渲染

    • 描述:尚未发生提取
    • 减速器:{ isFetching: false, didInvalidate: true, data: null }
    • 渲染:<Loading />

    <强> 2。 componentDidMount

    • 说明:数据无效&amp;&amp;没有提取 - &gt; go goetch data

    第3。调用的函数:accountFetchData(1)

    • 描述:通知您当前正在提取的Reducer,然后异步获取数据
    • 派遣:{ type: ACCOUNT_FETCH_DATA_REQUEST }

    <强> 4。帐户减速机

    • 描述:减速器会收到调度通知并相应地修改其值
    • 减速器:{ isFetching: true, didInvalidate: false, data: null }

    <强> 5。第二次渲染

    • 描述:因为帐户缩减器已更改
    • ,所以在渲染中返回第二个
    • 减速器:{ isFetching: true, didInvalidate: false, data: null }
    • 渲染:<Loading />

    <强> 6。名为的函数:accountFetchData(2)

    • 描述:最终从步骤3
    • 获取数据
    • 急件: {   类型:ACCOUNT_RECEIVE_DATA,   有效载荷:{data} }

    <强> 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我不确定这是一个干净的方法。假设用户将转到/帐户URL。然后到其他一些URL。然后回到/帐户。当数据将第二次从服务器加载时,isFetching将为true并且必须显示“加载”文本,但“data”变量将不为null,因为它将包含来自先前请求的数据。因此,不会“加载”,而是显示旧数据。

    使用didInvalidate值,不存在无限重新获取的风险,因为组件会知道您的数据是否有效。

    componentDidMount中,重新获取的条件将为false,因为值为以下{ isFetching: false, didInvalidate: false }。然后没有重新获取。

    if (this.props.didInvalidate && !this.props.isFetching)
    

    奖励:但请注意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;
      }
    }
    

    操作

    我将添加失效函数,以便更容易理解我在组件中调用的函数。 (注意:我没有重命名你的功能,但你应该注意命名)

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

    <强>组件

    您必须在某个时候使数据无效。我认为您的帐户数据在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>
        );
      }
    }
    

    这段代码可以更干净,但阅读方式更清晰:)

答案 1 :(得分:2)

你的三元陈述是否已切换?你的渲染功能有这个:

{this.props.isFetching === true ? <h3>LOADING</h3> : <div>{this.props.data.protectedString}</div> }

并且你的reducer中的initialState是这样的:

const initialState = {
  data: null,
  isFetching: false
};

在mount上会立即默认为this.props.data.protectedString