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

开发DateWithoutTime工具类,是否需为React提供不可变API?

关于React中使用可变DateWithoutTime类的疑问

背景

我正在开发DateWithoutTime类,它的公有方法会直接修改类的私有字段,示例调用代码如下:

this.state.dateWithoutTimeInstance.shiftBySpecificDaysCount({
  daysCount: 5,
  toFuture: true
});

类的完整代码见附录,此处省略部分内容。

在React这类框架中,禁止直接修改state中的对象。我自己的解决方案是基于旧实例创建新实例后再修改,示例代码如下:

let updatedDateWithoutTimeInstance: DateWithoutTime = new DateWithoutTime({
  year: this.state.dateWithoutTimeInstance.year,
  monthNumber__numerationFrom1: this.state.dateWithoutTimeInstance.monthNumber__numerationFrom1,
  dayOfMonth: this.state.dateWithoutTimeInstance.dayOfMonth
});

updatedDateWithoutTimeInstance.shiftBySpecificDaysCount({
  daysCount: 5,
  toFuture: true
});

this.setState((state) => {
  return { dateWithoutTimeInstance: updatedDateWithoutTimeInstance };
});

问题

请问在React中能否直接使用以下代码?

this.state.dateWithoutTimeInstance.shiftBySpecificDaysCount({
  daysCount: 5,
  toFuture: true
});

如果不能,是否需要为DateWithoutTime类提供不可变更新方法?比如类似下面的实现:

this.setState((state) => {
  return { 
    // 返回新实例
    dateWithoutTimeInstance: 
      this.state.dateWithoutTimeInstance.shiftBySpecificDaysCountImmutably({
        daysCount: 5,
        toFuture: true
      })
  };
});

附录:DateWithoutTime类部分代码

import type {
  MonthsNames
} from `@yamato-daiwa/ex-extensions`

import {
    DaysOfWeek,
    Logger,
    InvalidParameterValueError,
    getISO8601StringWithoutTimePart,
    getMonthNameByNumber,
    shiftDateBySpecificDaysCount,
    isNumber,
    isString,
    getMonthNumberByName
} from `@yamato-daiwa/ex-extensions`


class DateWithoutTime {

  protected _nativeDateObject!: Date;

  protected _year!: number;

  protected _monthName!: MonthsNames;
  protected _monthNumber__numerationFrom0!: number;
  protected _monthNumber__numerationFrom1!: number;
  protected _monthNumber__numerationFrom1__2Digits!: string;

  protected _dayOfMonth!: number;
  protected _dayOfMonth__2Digits!: string;

  protected _dayOfWeek!: DaysOfWeek;
  protected _dayOfWeekNumber__numerationFrom0AsSunday!: number;
  protected _dayOfWeekNumber__numerationFrom1AsSunday!: number;
  protected _dayOfWeekNumber__numerationFrom1AsSunday__2Digits!: string;


  public constructor(rawDateTime: number | string | Date | DateWithoutTime.DateDefinition) {

    try {

      if (isNumber(rawDateTime) || isString(rawDateTime) || rawDateTime instanceof Date) {
        this.nativeDateObject = new Date(rawDateTime);
      } else {
        this.nativeDateObject = DateWithoutTime.dateDefinitionToNativeDateObject(rawDateTime);
      }


    } catch (error: unknown) {

      Logger.throwErrorAndLog({
        errorInstance: new InvalidParameterValueError({
          parameterName: "rawDateTime",
          parameterNumber: 1
        }),
        title: InvalidParameterValueError.localization.defaultTitle,
        occurrenceLocation: "DateWithoutTime.constructor(rawDateTime)",
        wrappableError: error
      });

    }

  }


  public shiftBySpecificDaysCount(
    namedParameters: Readonly<
      { daysCount: number; } &
      (
        {
          toFuture: true;
          toPast?: undefined;
        } |
        {
          toPast: true;
          toFuture?: undefined;
        }
      )
    >
  ): this {

    this.nativeDateObject = shiftDateBySpecificDaysCount({
      initialDate: this._nativeDateObject,
      dayCount: namedParameters.daysCount,
      ...namedParameters.toFuture === true ? { toFuture: true } : { toPast: true }
    });

    return this;

  }


  public get year(): number { return this._year; }
  public get monthName(): MonthsNames { return this._monthName; }
  
  // 其他getter方法...

  public get nativeDateObject(): Date { return this._nativeDateObject; }
  protected set nativeDateObject(value: Date) {

    this._nativeDateObject = value;

    this._year = this._nativeDateObject.getFullYear();

    this._monthNumber__numerationFrom0 = this._nativeDateObject.getMonth();
    this._monthNumber__numerationFrom1 = this._monthNumber__numerationFrom0 + 1;
    this._monthNumber__numerationFrom1__2Digits = this._monthNumber__numerationFrom1.toString().padStart(2, "0");

    this._monthName = getMonthNameByNumber({ targetMonthNumber: this._monthNumber__numerationFrom1, numerationFrom: 1 });

    this._dayOfMonth = this._nativeDateObject.getDate();
    this._dayOfMonth__2Digits = this._dayOfMonth.toString().padStart(2, "0");

    this._dayOfWeekNumber__numerationFrom0AsSunday = this._nativeDateObject.getDay();
    this._dayOfWeekNumber__numerationFrom1AsSunday = this._dayOfWeekNumber__numerationFrom0AsSunday + 1;
    this._dayOfWeekNumber__numerationFrom1AsSunday__2Digits =
        this._dayOfWeekNumber__numerationFrom1AsSunday.toString().padStart(2, "0");

    switch (this._dayOfWeekNumber__numerationFrom1AsSunday) {
      case 1: { this._dayOfWeek = DaysOfWeek.sunday; break; }
      case 2: { this._dayOfWeek = DaysOfWeek.monday; break; }
      case 3: { this._dayOfWeek = DaysOfWeek.tuesday; break; }
      case 4: { this._dayOfWeek = DaysOfWeek.wednesday; break; }
      case 5: { this._dayOfWeek = DaysOfWeek.thursday; break; }
      case 6: { this._dayOfWeek = DaysOfWeek.friday; break; }
      default: { this._dayOfWeek = DaysOfWeek.saturday; }
    }

  }


  private static dateDefinitionToNativeDateObject(rawDateTime: Readonly<DateWithoutTime.DateDefinition>): Date {
    return new Date(
      rawDateTime.year,
      ((): number => {

        if ("monthName" in rawDateTime) {
          return getMonthNumberByName({
            targetMonthName: rawDateTime.monthName,
            numerationFrom: 0
          });
        }

        return "monthNumber__numerationFrom1" in rawDateTime ?
            rawDateTime.monthNumber__numerationFrom1 - 1 : rawDateTime.monthNumber__numerationFrom0;

      })(),
      rawDateTime.dayOfMonth
    );
  }

}


namespace DateWithoutTime {

  export type DateDefinition =
    {
      year: number;
      dayOfMonth: number;
    } &
    (
      { monthName: MonthsNames; } |
      { monthNumber__numerationFrom0: number; } |
      { monthNumber__numerationFrom1: number; }
    );

}

内容的提问来源于stack exchange,提问作者Takeshi Tokugawa YD

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 08:25:23