You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Redux Toolkit中Moment对象序列化问题及DateRangePicker报错求助

解决Redux中Moment对象序列化与DateRangePicker兼容问题

问题复现

  1. 初始状态使用Moment对象,触发Redux序列化检查错误:

    import moment from "moment";
    
    const filtersDefaultState = {
     text: "",
     sortBy: "date",
     startDate: moment().startOf("month"),
     endDate: moment().endOf("month"),
    };
    

    报错:Error: A non-serializable value was detected in the state, in the path: filters.startDate

  2. 添加忽略路径配置后,Action中又出现序列化错误:

    import { configureStore, createSlice } from "@reduxjs/toolkit";
    import expensesReducer from "../reducers/expenses";
    import filtersReducer from "../reducers/filters";
    
    const defaultMiddlewareConfig = {
      serializableCheck: {
        ignoredPaths: ["filters.startDate", "filters.endDate"],
      }
    };
    
    export default () => {
      const store = configureStore({
        reducer: {
          expenses: expensesReducer,
          filters: filtersReducer,
        },
        middleware: (getDefaultMiddleware) =>
          getDefaultMiddleware(defaultMiddlewareConfig),
      });
      return store;
    };
    

    报错:react_devtools_backend.js:2655 A non-serializable value was detected in an action, in the path: startDate. Value 和 A non-serializable value was detected in an action, in the path: endDate. Value

  3. 将Moment转为ISO字符串存储,过滤逻辑触发类型错误:

    import moment from "moment";
    
    const filtersDefaultState = {
      text: "",
      sortBy: "date",
      startDate: moment().startOf("month").toISOString(true),
      endDate: moment().endOf("month").toISOString(true),
    };
    

    报错:Uncaught TypeError: startDate.isSameOrBefore is not a function 和 Uncaught TypeError: endDate.isSameOrAfter is not a function,对应过滤逻辑:

    import moment from "moment";
    
    export default (expenses, { text, sortBy, startDate, endDate }) => {
      return expenses
        .filter((expense) => {
          const creadtedAtMoment = moment(expense.creadtedAt);
    
          const startDateMatch = startDate
            ? startDate.isSameOrBefore(creadtedAtMoment, "day")
            : true;
    
          const endDateMatch = endDate
            ? endDate.isSameOrAfter(creadtedAtMoment, "day")
            : true;
          const textMatch = expense.description
            .toLowerCase()
            .includes(text.toLowerCase());
    
          return startDateMatch && endDateMatch && textMatch;
        })
        .sort((a, b) => {
          if (sortBy === "date") {
            return a.createdAt < b.createdAt ? 1 : -1;
          } else if (sortBy === "amount") {
            return a.amount < b.amount ? 1 : -1;
          }
        });
    };
    
  4. DateRangePicker组件直接使用Redux状态中的值,存在格式不匹配问题:

    import React from "react";
    import { DateRangePicker } from "react-dates";
    import { connect } from "react-redux";
    import {
      setEndDate,
      setStartDate,
      setTextFilter,
      sortByAmount,
      sortByDate,
    } from "../actions/filters";
    
    class ExpenseListfilters extends React.Component {
      state = {
        calenderFocuesd: null,
      };
      onDatesChange = ({ startDate, endDate }) => {
        this.props.dispatch(setStartDate(startDate));
        this.props.dispatch(setEndDate(endDate));
      };
      onFocusChange = (calenderFocuesd) => {
        this.setState(() => ({ calenderFocuesd }));
      };
      render() {
        return (
          <div>
            <input
              text="text"
              value={this.props.filters.text}
              onChange={(e) => {
                this.props.dispatch(setTextFilter(e.target.value));
              }}
            />
            <select
              value={this.props.filters.sortBy}
              onChange={(e) => {
                if (e.target.value === "date") {
                  this.props.dispatch(sortByDate());
                } else if (e.target.value) {
                  this.props.dispatch(sortByAmount());
                }
              }}
            >
              <option value="date">Date</option>
              <option value="amount">Amount</option>
            </select>
            <DateRangePicker
              startDate={this.props.filters.startDate}
              endDate={this.props.filters.endDate}
              onDatesChange={this.onDatesChange}
              focusedInput={this.state.calenderFocuesd}
              onFocusChange={this.onFocusChange}
              showClearDates={true}
              numberOfMonths={1}
              isOutsideRange={() => false}
              startDateId="MyDatePickerStart"
              endDateId="MyDatePickerEnd"
            />
          </div>
        );
      }
    }
    
    const mapStateToProps = (state) => {
      return {
        filters: state.filters,
      };
    };
    
    export default connect(mapStateToProps)(ExpenseListfilters);
    

解决方案

核心思路:Redux状态只存储可序列化的原始值(ISO字符串或时间戳),在需要使用Moment方法或组件适配时,做格式转换

1. 状态层存储ISO字符串

保持初始状态为ISO字符串:

import moment from "moment";

const filtersDefaultState = {
  text: "",
  sortBy: "date",
  startDate: moment().startOf("month").toISOString(true),
  endDate: moment().endOf("month").toISOString(true),
};

2. 修改过滤逻辑,将字符串转为Moment对象

在调用Moment方法前,先把Redux中的ISO字符串转为Moment实例:

import moment from "moment";

export default (expenses, { text, sortBy, startDate, endDate }) => {
  return expenses
    .filter((expense) => {
      const creadtedAtMoment = moment(expense.creadtedAt);
      // 把字符串转为Moment对象
      const startDateMoment = startDate ? moment(startDate) : null;
      const endDateMoment = endDate ? moment(endDate) : null;

      const startDateMatch = startDateMoment
        ? startDateMoment.isSameOrBefore(creadtedAtMoment, "day")
        : true;

      const endDateMatch = endDateMoment
        ? endDateMoment.isSameOrAfter(creadtedAtMoment, "day")
        : true;
      const textMatch = expense.description
        .toLowerCase()
        .includes(text.toLowerCase());

      return startDateMatch && endDateMatch && textMatch;
    })
    .sort((a, b) => {
      if (sortBy === "date") {
        return a.createdAt < b.createdAt ? 1 : -1;
      } else if (sortBy === "amount") {
        return a.amount < b.amount ? 1 : -1;
      }
    });
};

3. 适配DateRangePicker组件

组件中需要把Redux的ISO字符串转为Moment对象传给Picker,同时在回调中把Moment对象转为ISO字符串再dispatch:

import React from "react";
import { DateRangePicker } from "react-dates";
import { connect } from "react-redux";
import moment from "moment";
import {
  setEndDate,
  setStartDate,
  setTextFilter,
  sortByAmount,
  sortByDate,
} from "../actions/filters";

class ExpenseListfilters extends React.Component {
  state = {
    calenderFocuesd: null,
  };
  onDatesChange = ({ startDate, endDate }) => {
    // 把Moment对象转为ISO字符串再dispatch
    this.props.dispatch(setStartDate(startDate?.toISOString(true)));
    this.props.dispatch(setEndDate(endDate?.toISOString(true)));
  };
  onFocusChange = (calenderFocuesd) => {
    this.setState(() => ({ calenderFocuesd }));
  };
  render() {
    // 把Redux中的ISO字符串转为Moment对象
    const startDate = this.props.filters.startDate ? moment(this.props.filters.startDate) : null;
    const endDate = this.props.filters.endDate ? moment(this.props.filters.endDate) : null;

    return (
      <div>
        <input
          type="text"
          value={this.props.filters.text}
          onChange={(e) => {
            this.props.dispatch(setTextFilter(e.target.value));
          }}
        />
        <select
          value={this.props.filters.sortBy}
          onChange={(e) => {
            if (e.target.value === "date") {
              this.props.dispatch(sortByDate());
            } else if (e.target.value === "amount") {
              this.props.dispatch(sortByAmount());
            }
          }}
        >
          <option value="date">Date</option>
          <option value="amount">Amount</option>
        </select>
        <DateRangePicker
          startDate={startDate}
          endDate={endDate}
          onDatesChange={this.onDatesChange}
          focusedInput={this.state.calenderFocuesd}
          onFocusChange={this.onFocusChange}
          showClearDates={true}
          numberOfMonths={1}
          isOutsideRange={() => false}
          startDateId="MyDatePickerStart"
          endDateId="MyDatePickerEnd"
        />
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    filters: state.filters,
  };
};

export default connect(mapStateToProps)(ExpenseListfilters);

4. 移除序列化忽略配置

现在状态和Action都是可序列化的,恢复默认Redux Toolkit配置:

import { configureStore } from "@reduxjs/toolkit";
import expensesReducer from "../reducers/expenses";
import filtersReducer from "../reducers/filters";

export default () => {
  const store = configureStore({
    reducer: {
      expenses: expensesReducer,
      filters: filtersReducer,
    },
  });
  return store;
};

补充说明

  • 如果觉得ISO字符串不够轻量,也可以用时间戳(moment().valueOf())存储,转换逻辑类似,用moment(timestamp)转回对象即可
  • 确保所有操作日期的Action都只传递序列化值,避免直接传入Moment对象

内容的提问来源于stack exchange,提问作者XED

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:00:01