ant-design/docs/blog/picker.zh-CN.md
二货爱吃白萝卜 e3063f244d
docs: blog picker (#50936)
* chore: blog picker

* docs: trigger update

* docs: add ref faq

* docs: update
2024-09-23 11:09:46 +08:00

4.8 KiB
Raw Permalink Blame History

title date author
为什么禁用日期这么难? 2024-09-23 zombieJ

在 antd 中DatePicker 的 issue 非常多。一来是由于日期选择需求的常见性,另一来是针对业务的需求会有各种各样的禁用选择组合。今天,我们就来聊聊 disabledDate 这个 API。

disabledDate 不能控制时间

如其名,disabledDate 用于对日期进行禁用。所以当使用 DateTimePicker 时,时间部分并不会被 disabledDate 进行控制。而是需要通过 disabledTime 方法进行控制。这似乎有点反直觉,为什么需要用两个 API 管理呢?

如何确定日期能选?

从直觉上看,一个日期是否禁用我们只需要将当前日期执行一次 disabledDate 即可知道。但是,如果当我们把面板切换成月份面板的时候,我们怎么知道当前月份是否可选呢?我们必须要对该月下的每个日期进行一次 disabledDate 才能确定该月下有可选日期,因此该月才能选择。

从目前看,这个逻辑似乎没问题。一个月 30 来天,月份面板 12 个月,最坏情况下,我们也只需要遍历 365 次就能知道 12 个月份是不可选的。但是以此类推,当面板切换为年、十年时。我们需要遍历的次数就会呈指数级增长,导致严重的性能问题(#39991)。

所以在 DatePicker 重构后,disabledDate 提供了额外的参数 info.type,告知提供的 date 对象来自于哪个 Panel。这样开发者就可以根据 Panel 来提供 disabled 信息,从而避免恐怖的调用循环。

但是作为兜底DatePicker 会对当前 Panel 单元的第一天和最后一天进行 disabledDate 的调用。这样可以确保在常见的范围禁用场景下,仍能满足需求。

disabledTime

回到时间禁用,这是相同的问题。如果我们通过 disabledDate 进行时间维度的禁用,那么在 DateTimePicker 下该天是否可选,我们就需要对该天的每一秒进行 disabledDate 校验。在最坏情况下,一天是否可选就需要校验 86400 次。Date Panel 就需要执行 ~200 万次。显然这是不可接受的。

所以对于时间维度,我们提供了 disabledTime 方法。这个方法相比 disabledDate 会要求提供更细细粒度的时间禁用信息:

type DisabledTime = (now: Dayjs) => {
  disabledHours?: () => number[];
  disabledMinutes?: (selectedHour: number) => number[];
  disabledSeconds?: (selectedHour: number, selectedMinute: number) => number[];
  disabledMilliseconds?: (
    selectedHour: number,
    selectedMinute: number,
    selectedSecond: number,
  ) => number[];
};

(时间选择面板的每个单位都相当于日期面板的 Panel后者通过前者单位的信息来推出当前禁用单位

一些例子

在了解了上下文后,我们会发现 disabledDatedisabledTime 虽然设计是合理的,但是却有些偏底层。在业务中进行使用会比较麻烦,我们来看几个例子(当然,在业务中你需要考虑通过 HOC 来进行封装):

工作时间

暂时不考虑节假日的情况,我们选择工作日的 9:00 ~ 17:00 为可选时间:

const disabledDate = (date, info) => {
  if (info.type === 'date') {
    return date.day() === 0 || date.day() === 6;
  }
  return false;
};

const disabledTime = () => ({
  disabledHours: () => {
    return Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour < 9 || hour > 17);
  },
});

时间日期范围

在 DatePicker 中有 minDatemaxDate 用于限制日期的选择范围,但是如果它们仅限于日期的限制。现在,假设我们有种场景需要带有时间的日期范围选择,比如 2024-01-01 09:00:00 ~ 2024-01-02 17:00:00,那么我们可以这样做:

const disabledDate = (date, info) => {
  if (info.type === 'date') {
    return date.isBefore('2024-01-01', 'day') || date.isAfter('2024-01-02', 'day');
  }
  return !date.isSame('2024-01-01', info.type);
};

const disabledTime = (date) => {
  if (date.isSame('2024-01-01', 'day')) {
    return {
      disabledHours: () => Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour < 9),
    };
  }

  if (date.isSame('2024-01-02', 'day')) {
    return {
      disabledHours: () => Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour > 17),
    };
  }

  // 只需要考虑开始和结束时间,范围外的本身已经被 `disabledDate` 禁用了
  return {};
};

总结

通过 disabledDatedisabledTime,我们可以对日期和时间进行更细粒度的控制,以实现不同的业务需求。通过以上示例,相信你已经对这两个 API 有了更深入的了解。在实际业务中,你可以根据具体需求,结合这两个 API 来实现更多的功能。