今天在使用受控模式的RangePicker时遇到的bug
以下是两段完全一样的代码,唯一区别是前者使用momentjs,后者使用dayjs:
js
import { useState } from "react";
import { DatePicker } from "antd";
import moment from "moment";
const { RangePicker } = DatePicker;
export const ControlledRangePicker = () => {
const [selectedRange, setSelectedRange] = useState([
moment().subtract(24, "hour"),
moment(),
]);
const handleRangeChange = (dates) => {
if (dates) {
setSelectedRange(dates);
}
};
return (
<div>
<RangePicker
value={selectedRange}
onChange={handleRangeChange}
showTime={{
format: "HH:mm",
hourStep: 1,
minuteStep: 15,
}}
format="YYYY-MM-DD HH:mm"
/>
</div>
);
};
import dayjs from "dayjs";
export const ControlledRangePicker2 = () => {
const [selectedRange, setSelectedRange] = useState([
dayjs().subtract(24, "hour"),
dayjs(),
]);
const handleRangeChange = (dates) => {
if (dates) {
setSelectedRange(dates);
}
};
return (
<div>
<RangePicker
value={selectedRange}
onChange={handleRangeChange}
showTime={{
format: "HH:mm",
hourStep: 1,
minuteStep: 15,
}}
format="YYYY-MM-DD HH:mm"
/>
</div>
);
};
会发现前者的日期选择面板一打开 日期就会疯狂增长,控制台重复报错:
js
Warning: Maximum update depth exceeded.
This can happen when a component calls setState inside useEffect,
but useEffect either doesn't have a dependency array,
or one of the dependencies changes on every render.
原因是 Moment 对象是可变对象(mutable),而dayjs是 不可变(immutable)的。 可以用以下代码测试:
js
// moment可变性
const date1 = moment('2023-10-01');
const date2 = date1; // 这不是复制,而是引用同一个对象
// const date2 = date1.clone(); 正确的方式:创建副本
date1.add(1, 'day'); // 修改date1
console.log(date1.format('YYYY-MM-DD')); // 2023-10-02
console.log(date2.format('YYYY-MM-DD')); // 2023-10-02 date2也被改变了
js
// dayjs 不可变性
const date1 = dayjs('2023-10-01');
const date2 = date1;
const date3 = date1.add(1, 'day'); // 返回新的对象,不改变原对象
console.log(date1.format('YYYY-MM-DD')); // 2023-10-01 原对象不变
console.log(date2.format('YYYY-MM-DD')); // 2023-10-01
console.log(date3.format('YYYY-MM-DD')); // 2023-10-02 新对象
React的渲染机制期望状态更新是可控的,但Moment.js的 mutable 特性会让状态变得不可预测 。
而且momentjs体积比dayjs大,antd v4到v5 也是从momentjs 变成了 dayjs。