簡體   English   中英

組件中的React-Redux狀態與存儲中的狀態不同

[英]React-Redux state in the component differs from the state in the store

我正在用React和Redux構建一個應用程序。

我有一個Account組件,該componentWillMountcomponentWillMount方法中的服務器中獲取數據。 在提取數據時,該組件必須顯示“正在加載”文本,因此我已將“ isFetching”屬性添加到帳戶精簡器中。 從服務器獲取數據時,此屬性設置為true。

問題在於,在獲取數據時, render方法中“ isFetching”屬性的值為false,而同時store.getState().account.isFetching值為true(因為必須)。 這將導致異常,因為this.props.isFetching為false,因此代碼試圖在仍從服務器加載data同時顯示this.props.data.protectedString (因此為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

代碼看起來相同,但是由於某些模塊的新版本,我更改了一些位置。

使用react&redux獲取數據時,您應該始終問自己兩個問題:

  1. 我的數據仍然有效嗎?
  2. 我當前正在獲取數據嗎?

您已經通過使用isFetching回答了第二個問題,但是第一個問題仍然存在,這就是導致您出現問題的原因。 您應該做的是在化didInvalidate中使用didInvalidatehttps://github.com/reactjs/redux/blob/master/docs/advanced/AsyncActions.md

使用didInvalidate您可以通過分派INVALIDATE_ACCOUNT類的操作輕松檢查數據是否有效,並在需要時使它們無效。 由於您尚未提取數據,因此默認情況下您的數據無效。

(獎勵)何時可能使數據無效的一些示例:

  • 最后獲取日期是> 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的帳戶減少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

  • 說明:數據無效&&無法獲取->去獲取數據

3.函數調用:accountFetchData(1)

  • 解密:通知減速器您當前正在獲取,然后異步獲取數據
  • 發貨: { type: ACCOUNT_FETCH_DATA_REQUEST }

4.帳戶減少器

  • 說明:減速器已收到調度通知,並相應地修改了它們的值
  • { isFetching: true, didInvalidate: false, data: null }{ isFetching: true, didInvalidate: false, data: null }

5.第二次渲染

  • 說明:由於Account reducer已更改,因此在渲染中耗時一秒
  • { isFetching: true, didInvalidate: false, data: null }{ isFetching: true, didInvalidate: false, data: null }
  • 渲染: <Loading />

6.調用的函數:accountFetchData(2)

  • 說明:數據最終從步驟3中獲取
  • 調度:{類型:ACCOUNT_RECEIVE_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我不確定這是一種干凈的方法。 假設用戶將轉到/ account URL。 然后轉到其他URL。 然后回到/ account。 雖然將第二次從服務器加載數據,但是isFetching將為true,並且必須顯示“正在加載”文本,但是“ data”變量將不為null,因為它將包含來自先前請求的數據。 因此,將顯示舊數據,而不是“加載”。

使用didInvalidate值,因為組件將知道您的數據是否有效,所以沒有無限重訪的風險。

componentDidMount ,要重新獲取的條件將為false,因為其值為以下{ isFetching: false, didInvalidate: false } 然后不重新引用。

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

獎勵:但是,請注意didInvalidate的數據緩存問題。

人們對此問題的討論不多,但是您將開始問這個問題:“我的數據無效時開始嗎?” (=我什么時候應該提取?)

減速器

如果可以的話,從長遠來看,讓我重構您的reducer代碼。

您的異徑管將更加模塊化,並且易於維護。

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

這段代碼可以更簡潔,但更容易閱讀:)

您的三元陳述不轉換嗎? 您的渲染函數具有以下功能:

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

而您的化簡器中的initialState是這樣的:

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

掛載時將默認為this.props.data.protectedString

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM