【问题标题】:React/Redux: State is updated in Redux object, but React component doesn't re-renderReact/Redux:状态在 Redux 对象中更新,但 React 组件不会重新渲染
【发布时间】:2025-06-06 19:25:02
【问题描述】:

试图浏览类似的问题,但没有发现类似的问题。

我正在尝试在我的应用程序中按名称和数量实现排序,此事件在此组件中触发:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { sortByExpenseName, sortByExpenseAmount } from '../actions/expensesFilters';

class ExpensesListFilter extends Component {
  onSortByExpenseName = () => {
    this.props.sortByExpenseName();
  };

  onSortByExpenseAmount = () => {
    this.props.sortByExpenseAmount();
  }

  render() {
    return (
      <div>
        <span>Expense Name</span>
        <button onClick={this.onSortByExpenseName}>Sort me by name</button>
        <button onClick={this.onSortByExpenseAmount}>Sort me by amount</button>
      </div>
    )
  }
}

const mapDispatchToProps = (dispatch) => ({
  sortByExpenseName: () => dispatch(sortByExpenseName()),
  sortByExpenseAmount: () => dispatch(sortByExpenseAmount()),
});

export default connect(null, mapDispatchToProps)(ExpensesListFilter);

为此,我使用以下选择器:

export default (expenses, { sortBy }) => {
  return expenses.sort((a, b) => {
    if (sortBy === 'name') {
      return a.name < b.name ? 1 : -1;
    } else if (sortBy === 'amount') {
      return parseInt(a.amount, 10) < parseInt(b.amount, 10) ? 1 : -1;
    }
  });
};

我在 mapStateToProps 函数中为我的 ExpensesList 组件运行这个选择器:

import React from 'react';
import { connect } from 'react-redux';
import ExpensesItem from './ExpensesItem';

// my selector
import sortExpenses from '../selectors/sortExpenses';


const ExpensesList = props => (
  <div className="content-container">
    {props.expenses && props.expenses.map((expense) => {
        return <ExpensesItem key={expense.id} {...expense} />;
    }) }
  </div>
);

// Here I run my selector to sort expenses
const mapStateToProps = (state) => {
  return {
    expenses: sortExpenses(state.expensesData.expenses, state.expensesFilters),
  };
};

export default connect(mapStateToProps)(ExpensesList);

此选择器更新我的过滤器缩减器,这会导致我的应用状态更新:

import { SORT_BY_EXPENSE_NAME, SORT_BY_EXPENSE_AMOUNT } from '../actions/types';

const INITIAL_EXPENSE_FILTER_STATE = {
  sortBy: 'name',
};

export default (state = INITIAL_EXPENSE_FILTER_STATE, action) => {
  switch (action.type) {
    case SORT_BY_EXPENSE_NAME:
      return {
        ...state,
        sortBy: 'name',
      };
    case SORT_BY_EXPENSE_AMOUNT:
      return {
        ...state,
        sortBy: 'amount',
      };
    default:
      return state;
  }
};

排序事件导致我的状态更新,下面我的费用减少器中的费用数组被更新并按选择器排序,但是在我的状态中的费用数组更新后,ExpensesList 组件不会重新呈现。 我希望我的 ExpensesList 组件做的是使用排序后的费用数组重新渲染并对列表中的 ExpensesItem 组件进行排序。 它失败的原因可能是什么?很确定我错过了一些重要的东西,但不知道是什么。我的开支减少者:

import { FETCH_EXPENSES } from '../actions/types';

const INITIAL_STATE = {};

export default (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case FETCH_EXPENSES:
      return {
        ...state,
        expenses: action.expenses.data,
      };
    default:
      return state;
  }
};

所有这些组件都是这个父组件的子组件:

import React from 'react';
import ExpensesListFilter from './ExpensesListFilter';
import ExpensesList from './ExpensesList';

const MainPage = () => (
  <div className="box-layout">
    <div className="box-layout__box">
      <ExpensesListFilter />
      <ExpensesList />
    </div>
  </div>
);

export default MainPage;

App.js 文件(我在其中运行 startExpenseFetch)

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import 'normalize.css/normalize.css';

import AppRouter, { history } from './routers/AppRouter';
import configureStore from './store/configureStore';
import LoadingPage from './components/LoadingPage';
import { startExpenseFetch } from './actions/expensesData';
import './styles/styles.scss';

const store = configureStore();

const jsx = (
  <Provider store={store}>
    <AppRouter />
  </Provider>
);

let hasRendered = false;

const renderApp = () => {
  if (!hasRendered) {
    ReactDOM.render(jsx, document.getElementById('app'));
    hasRendered = true;
  }
};

store.dispatch(startExpenseFetch()).then(() => {
  renderApp();
});

ReactDOM.render(<LoadingPage />, document.getElementById('app'));

其余文件:

ExpenseItem 组件:

import React from 'react';

const ExpenseItem = ({ amount, name }) => (
  <div>
    <span>{name}</span>
    <span>{amount}</span>
  </div>
);

export default ExpenseItem;

动作创建者:

费用数据.js

import axios from 'axios';
import { FETCH_EXPENSE } from './types';

// no errors here
const ROOT_URL = '';

export const fetchExpenseData = expenses => ({
  type: FETCH_EXPENSE,
  expenses,
});

export const startExpenseFetch = () => {
  return (dispatch) => {
    return axios({
      method: 'get',
      url: `${ROOT_URL}`,
    })
      .then((response) => {
        dispatch(fetchExpenseData(response));
        console.log(response);
      })
      .catch((error) => {
        console.log(error);
      });
  };
};

expensesFilters.js

import { SORT_BY_EXPENSE_NAME, SORT_BY_EXPENSE_AMOUNT } from './types';

export const sortByExpenseName = () => ({
  type: SORT_BY_EXPENSE_NAME,
});

export const sortByExpenseAmount = () => ({
  type: SORT_BY_EXPENSE_AMOUNT,
});

configureStores.js 文件

import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import expensesDataReducer from '../reducers/expensesData';
import expensesFilterReducer from '../reducers/expensesFilters';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export default () => {
  const store = createStore(
    combineReducers({
      expensesData: expensesDataReducer,
      expensesFilters: expensesFilterReducer,
    }),
    composeEnhancers(applyMiddleware(thunk))
  );
  return store;
};

AppRouter.js 文件

import React from 'react';
import { Router, Route, Switch, Link, NavLink } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import MainPage from '../components/MainPage';
import NotFoundPage from '../components/NotFoundPage';

export const history = createHistory();

const AppRouter = () => (
  <Router history={history}>
    <div>
      <Switch>
        <Route path="/" component={MainPage} exact={true} />
        <Route component={NotFoundPage} />
      </Switch>
    </div>
  </Router>
);

export default AppRouter;

【问题讨论】:

  • 你的 ExpansesList 是父组件吗?如果不是在哪个父组件中调用它?
  • @HemadriDasari ExpansesList 是 ExpensesItem 的父组件,选择器在该组件中运行。 ExpensesList 也有一个*父组件:
  • @HemadriDasari 将立即更新它
  • 我看不到您在 ExpensesList 中使用 rates 的位置,并将其映射到 props。它应该导致问题吗?
  • 好的。要获取费用,您将调用一个行动电话。它在哪里?我看到你的 ExpensesList 正在检查费用道具,所以我想知道这个获取费用操作调用是否被触发。我想它应该在您的 ExpensesList 的父组件中。如果是这样,请在触发获取费用操作调用的位置发布该代码。

标签: javascript reactjs redux


【解决方案1】:

您对选择器的调用没有错字吗? :)

// Here I run my selector to sort expenses
const mapStateToProps = (state) => {
  return {
    expenses: sortExpenses(state.expensesData.expenses, state.expnsesFilters),
  };
};

state.expnsesFilters 看起来应该是 state。expensesFilters

这是您应该让您的sortExpenses 选择器自己抓取它需要的状态部分并自行完成工作的原因之一。您可以对其进行隔离测试并避免此类错误。

【讨论】:

  • 哦,不,原始代码很好,只是我在这里复制错字。它实际上在第一次渲染时运行并排序一切正常。但是当我点击排序按钮时,vue 层不会重新渲染。需要再次挖掘它,很确定我身边的一些愚蠢的错误是必不可少的。感谢您的提示:)
【解决方案2】:

我找到了它发生的原因,在我的选择器中我正在改变我的应用程序的状态。我没有从中返回一个新数组,而是更改了旧数组,这并没有触发我的 vue 层重新渲染。修复了它,它现在可以工作了。

【讨论】: