常用前端组件date-picker(四)

221 阅读6分钟

经过上述的流程后,在useEffect中拿到计算好的selectValue,年、月、日所对应的slide以及初始化index,并使用useState进行赋值后,openPicker已经可以正确的显示出来对应的信息。接下来要做的就是年份和月份变更时触发的函数yearSlideChange及monthSlideChange。

yearSlideChange

拿到切换后的对应swiper值

当滑动swiper进行年份切换时,首先我们要拿到切换后的值,进而用切换后的值看是否对其他slide有所影响。

这里当前年份、当前月份、当前天数都要拿到,因为例如2022-06-26的一年前为2021-06-26,如果用户选择了2022-03-15随后切换到2021年,那么明显21年的3月15日月份和天分都不在时间范围内,需要重新进行变更。

所以第一步要拿到切换后三个swiper的当前值,这里就用到了初始化好的swiper实例。

  /**
     * 年份slide改变事件
     * @param {Swiper} swiper 年份swiper
     */
    function yearSlideChange(swiper) {
        const currentYear = yearSlide[swiper.activeIndex].value;
        const currentMonth = monthSlide[monthSwiper.activeIndex].value;
        const currentDay = daySlide[daySwiper.activeIndex].value;
        console.log('----yearSlideChange切换后的日期----', currentYear, currentMonth, currentDay);
    }

设置好之后你打开浏览器然后openPicker,发现页面报错了。

Cannot read properties of null (reading 'activeIndex')

解决读不到activeIndex的bug

这个报错的含义明显是此时的swiper还没有初始化,还是开始赋值的null。可是根据swiper的用法,在属性中添加了onSwiper={setYearSwiper},为什么swiper还是为null?

在我查阅资料自己又大量试错后发现了问题所在。

  • 如果你不使用swiper变量,随便输出点东西,这个日志会被打印两次
  • 但是当你将年份选择为最小年即index === 0时,这个日志只会被打印一次

于是我发现了问题所在,那就是swiper默认的index应该是0,当你做swiper初始化时将swiper的index由0变为其他值时会触发yearSlideChange事件。

也就是说当index不为0时做初始化,会默认调用一次yearSlideChange。

而报出来的错误就是在内部为index切换值时,此时swiper还未完全初始化,所以爆出了这个错误。

因此我们需要定义一个变量,用来表示当index不为0时yearSlideChange是第二次触发,这个时候才会真正的执行里面的内容,而第一次不会。

这里我定义了一个yearFlag和一个monthFlag,在openPicker后采用异步的方式将他们设置为true。因为同步的方式可能会将当时的flag值保存到对应的yearSlideChange中进而在事件中并拿不到更新后的值,还会报错。

// 是否是swiper内部由index=0变为index=init时所触发的slideChange事件
const [yearFlag, setYearFlag] = useState(false);
const [monthFlag, setMonthFlag] = useState(false);

   /**
     * 年份slide改变事件
     * @param {Swiper} swiper 年份swiper
     */
    function yearSlideChange(swiper) {
        if(yearIndex !== 0 && !yearFlag) return;
        const currentYear = yearSlide[swiper.activeIndex].value;
        const currentMonth = monthSlide[monthSwiper.activeIndex].value;
        const currentDay = daySlide[daySwiper.activeIndex].value;
        console.log('----yearSlideChange切换后的日期----', currentYear, currentMonth, currentDay);
    }
    
    // 月份同理

对拿到的年、月、日进行处理

查找当前月是否在更新后的monthSlide中

在拿到更新后的年月日后,处理方式与初始化swiper类似,也要对年份进行判断,在最小年、最大年、普通年分别进行处理。

同时这里还产生一个问题,就是当切换年份时如果切换前和切换后的月份slide不同,但是切换前后的monthIndex不变,就会导致切换年份后虽然切换前的月份在当前monthSlide中,但是当前的月份值依然会变化。

例如 2022-06-26的2年前,如果初始值时2020-08-20,他当时的monthSlide为6-12,monthIndex为2。当他切换到2021年时,monthSlide变为1-12,此时如果monthIndex不变的话,显示的月份值就会变为3月份。

严重影响的体验,因此我写了一个函数用来查找当前月份是否在更新后的monthSlide中,如果在的话就返回其对应的index,如果不在就返回-1。

/**
 * 将dateSlide中每一个日期提取出来组成数组
 * @param {Array} dateSlide 日期数组
 * @param {string} date 日期
 * @returns {number} 日期所对应的index
 */
function findDateIndex(dateSlide, date) {
    let arr = dateSlide.map(dateObj => dateObj.value);
    let index = arr.findIndex(d => d === date);
    return index;
}

设置月份和天数slide及index

在拿到对应swiper的当前值后,对年份的当前值进行判断,按照初始化时的逻辑分为最小年、最大年、普通年来求得对应的月份slide。

然后再通过findDateIndex函数判断是否在更新后的月份中,如果在的话就维持当前月份,如果不在就讲当前monthIndex置为0。

由于三种年份的切换会导致月份slide的切换,而月份的切换又可能会导致天数的变化。因为当切换到最小年最小月和最大年最大月时,天数不再是从1到当月最后一天。

所以在月份变更后还要用相同的方式在对天数进行处理。

// yearSlideChange事件
    function yearChangeFun(currentYear, currentMonth, currentDay) {
        let monthSlideList = [];
        let monthActiveIndex = 0;
        let daySlideList = [];
        let dayActiveIndex = 0;
        if(currentYear === minYear) {
            // 当前选中年是最小年
            monthSlideList = generateSlide('month', 12, minMonth);
            const monthIndex = findDateIndex(monthSlideList, currentMonth);
            //  当前选中月不存在或当前月为首月时,默认为首月
            if(monthIndex === -1 || monthIndex === 0) {
                monthActiveIndex = 0;
                daySlideList = generateSlide('day', maxDays(minYear, minMonth), minDay);
                const dayIndex = findDateIndex(daySlideList, currentDay);
                // 当前选中天数不在天数数组中时默认置为第一天,否则就是当天
                dayActiveIndex = dayIndex === -1 ? 0 : dayIndex;
            } else {
                // 当前月份在最小年的月份数组内且不是首月,那么月份为本月,天数则从0到月底
                monthActiveIndex = monthIndex;
                daySlideList = generateSlide('day', maxDays(currentYear, currentMonth));
                const dayIndex = findDateIndex(daySlideList, currentDay);
                dayActiveIndex = dayIndex === -1 ? daySlideList.length - 1 : dayIndex;
            }
        } else if(currentYear === maxYear) {
            // // 当前选中年是最大年
            monthSlideList = generateSlide('month', maxMonth, 0);
            const lastIndex = monthSlideList.length - 1;
            const monthIndex = findDateIndex(monthSlideList, currentMonth);
            if(monthIndex === -1 || monthIndex === lastIndex) {
                // 当前月不在最大年所拥有月份的数组中或是尾月
                monthActiveIndex = lastIndex;
                daySlideList = generateSlide('day', maxDay, 0);
                const dayIndex = findDateIndex(daySlideList, currentDay);
                dayActiveIndex = dayIndex === -1 ? daySlideList.length - 1 : dayIndex;
            } else {
                // 当前月份在最大年的月份数组内且不是最大月,那么月份为本月,天数则从0到月底
                monthActiveIndex = monthIndex;
                daySlideList = generateSlide('day', maxDays(currentYear, currentMonth));
                const dayIndex = findDateIndex(daySlideList, currentDay);
                dayActiveIndex = dayIndex === -1 ? daySlideList.length - 1 : dayIndex;
            }
        } else {
            // 由最小年最大年切换到普通年时,月份产生的变化
            monthSlideList = generateSlide('month');
            const monthIndex = findDateIndex(monthSlideList, currentMonth);
            monthActiveIndex = monthIndex === -1 ? 0 : monthIndex;
            daySlideList = generateSlide("day", maxDays(currentYear, currentMonth));
            const dayIndex = findDateIndex(daySlideList, currentDay);
            dayActiveIndex = dayIndex === -1 ? 0 : dayIndex;
        }
        return {
            monthSlideList,
            monthActiveIndex,
            daySlideList,
            dayActiveIndex,
        }
    }

monthSlideChange

月份的改变会引起天数的变化,而天数的变化又和年份有关,所以仍然要根据三种年份来判断月份改变时的天数。同时也要使用findDateIndex来判断天数是否存在。

逻辑与月份相似,此处代码就省略了。