基于vue3.0+TypeScript的简易日历

8,039 阅读6分钟

前言

第一次用vue3的api和ts写,还在摸索中,有地方写的不规范的,可交流,共同进步

项目介绍,功能很简单

  1. 显示所有时间日期
  2. 上一年上个月,下个月下一年

预览地址

arhebin.gitee.io/vue-calenda…

写完后的感受和收获

  1. ts很香,但我用的不熟练;我平时会看看vue-next的源码,既能学新api还能学ts,一举两得呢;当然其他优质代码也可以
  2. vue3.0新的api很强大;例如就是reative, ref, watcher, watchEffect...,选择太多了,给开发者的组合方式也就多了,现在最缺的应该是最佳实践了。
  3. 最大感受就是之前基于组件的分离,现在还可以基于数据或者方法层面上的抽离,例如我试着写了一个useComputedDayuseReducerDate,有点意思

怎么写日历

我的思路是:

  1. 需要一个输入的日期,默认是当前日期;这个值可以是传入的props,也可以是state组件自身的状态,(看你喜欢)
  2. 需要知道基于当前日期的年份
  3. 需要知道基于当前日期的月份
  4. 需要知道基于当前日期的几号
  5. 需要知道基于当前日期的当月最大天数
  6. 需要知道基于当前日期的上一个月最大天数
  7. 需要知道基于当前日期的当月一号是星期几

这些值都是基于当前日期的,所以大家会想到可以用一个computed来关联起来,反正我这么想了,😄

有了这些值做什么能?

  1. 我先画一个7*6的大方块,默认索引是0-41
  2. 我知道当前日期一号是星期几,就能和上面的索引关联起来,在往后填日子,填到什么时候,就是当月最大天数的量,剩下的就是下一个月的1号还是到索引结束,
  3. 同理头部,当前日期一号是星期几,之前的可以根据上一月最大天数与索引关联起来,填上去

举个例子🌰

  1. 今天是2020/4/28;星期2;4月1号是星期三;最大天数是30天;上一个月的最大天数是31天;索引是index
  2. 4月份的所有日子1-30怎么填入里格子中呢?当index - 3 >= 0就往小格子里填入index - 3 + 1;直到(30 + 3 - 1) < index时,😯;剩余头尾
  3. 尾部触发条件就是(30 + 3 - 1) < index,填入index - (30 + 3 - 1)
  4. 剩下就是头部了,只要填入31 - (3 - index - 1)

代码如下

<p class="item" v-for="(el, index) in maxDays" :key="index">
  <span :class="['day', el.isActive ? 'isactive' : '']"
    v-if="(index - state.currentMonthFirstDay) >= 0 
      && (state.currentMonthMaxDays + state.currentMonthFirstDay - 1) >= index">
    {{index - state.currentMonthFirstDay + 1}}
  </span>
  <span class="day rest " v-else-if="(state.currentMonthMaxDays + state.currentMonthFirstDay - 1) < index">{{index - (state.currentMonthMaxDays + state.currentMonthFirstDay - 1)}}</span>
  <span class="day rest" v-else>{{state.preMonthMaxDays - (state.currentMonthFirstDay - index - 1)}}</span>
</p>

剩下的就是上一年上个月,下一年下个月

我的思路是写一个dispatch,靠action的type去控制,只要改变当然输入日期就行了。因为依赖已经关联了;(可能一开始会写个最简单一个方法,if-else的判断)

这些都不是重点啦,大家都有自己的思路。但是重点是包括我在内想知道是怎么基于新api写功能

剩下就是怎么写了

盲写,先实现功能再说

<template>
    //html结构
</template>
<script>
    step() {
        const nowDay = new Date().toLocaleDateString().split('/').map(Number)
        const dayStr = ref<number[]>(nowDay) //这边写成数组,之前是字符串,但写字符串有个问题就是年月日改变一个就会触发所以依赖;数组可以单纯改变一个
        //接来利用computed
        //形成依赖
        const date = computed(() => new Date(str.value.join('/')))
        //年
        const currentYear = computed(() => date.value.getFullYear())
        //剩余的值例如currentMonthMaxDays,preMonthMaxDays,currentMonthFirstDay
        ...
        //handleChange改变日期的方法
        const handleChange = () => {
            if () {
                //上一年
            }
            else if() {
                //上个月
            } else if() {
                //下个月
            } else if () {
                //下一年
            }
        }
    }
</script>

当你写完,你会感觉🐸,好长啊。就我个人而言,不怎么喜欢在组件的入口把代码的篇幅搞得很长。

怎么优化呢?

现在基于vue3.0,个人感觉数据,方法也能抽离,除了️组件抽离(只是我个人的想法,可以交流)

  1. 第一个想法💡是:能不能把基于输入日期的值拿出来溜溜呢? 对于那写当前年份,当前月份等,数据可以抽离成到一个文件中,取个好听的名字叫做useComputedDay
export const useComputedDay = (str: Ref<number[]>) => {
    //注入了依赖
    const date = computed(() => new Date(str.value.join('/')))
    //年
    const currentYear = computed(() => date.value.getFullYear())
    //月
    const currentMonth = computed(() => date.value.getMonth() + 1)
    //日
    const currentDate = computed(() => date.value.getDate())
    //星期
    const currentDay = computed(() => date.value.getDay())
    //当月最大天数
    const currentMonthMaxDays = computed(() => getMonthMaxDays(currentYear.value, currentMonth.value))
    //当月星期几
    const currentMonthFirstDay = computed(() => computedFirstDay(currentYear.value, currentMonth.value))
    //上一个月最大天数
    // const preMonthMaxDays = computed(() => new Date(currentYear.value, currentMonth.value - 1, 0).getDate())
    const preMonthMaxDays = computed(() => getMonthMaxDays(currentYear.value, currentMonth.value - 1))
    return {
      date,
      currentYear,
      currentMonth,
      currentDate,
      currentDay,
      currentMonthMaxDays,
      preMonthMaxDays,
      currentMonthFirstDay
    } 
}

怎么用呢?

    const nowDay = new Date().toLocaleDateString().split('/').map(Number)
    const dayStr = ref<number[]>(nowDay) //这边写成数组,之前是字符串,但写字符串有个问题就是年月日改变一个就会触发所以依赖;数组可以单纯改变一个
    const dispatch = useReducerDate(dayStr)
    const {
      date,
      currentYear,
      currentMonth,
      currentDate,
      currentDay,
      currentMonthMaxDays,
      preMonthMaxDays,
      currentMonthFirstDay
    } = useComputedDay(dayStr);

这样的话只要dayStr改变了所有的都会改变

  1. 第二个想法💡是:能不能把控制上下年月的方法也搞到外面去呢?,例如整个dispatchdispatch怎么写呢?可以抽离出来写成类似reducer那样
export const useReducerDate = (state: Ref<number[]>) => {
    return function (action: action) {
        switch(action.type) {
            case 'preYear': {
                const _var = state.value.map(Number)
                _var[0] = _var[0] - 1;
                state.value = _var
                break;
            }
            case 'preMonth': {
                cutMonth(state);
                break;
            } 
            case 'nextYear': {
                const _var = state.value.map(Number)
                _var[0] = _var[0] + 1;
                state.value = _var
                break;
            }
            case 'nextMonth': {
                addMonth(state);
                break;
            } 
            default: {
                state.value = new Date().toLocaleDateString().split('/').map(Number)
            }
                
        }
    }
}

怎么用呢?

<div class="textheader">
    <i class="iconfont textItem" @click="dispatch({type: 'preYear'})">&#xe714;</i>
    <i class="iconfont textItem" @click="dispatch({type: 'preMonth'})">&#xe667;</i>
    <p class="textItem">
      {{state.currentYear}} 年 {{state.currentMonth}} 月 {{state.currentDate}} 日
    </p>
    <i class="iconfont textItem " @click="dispatch({type: 'nextMonth'})">&#xe610;</i>
    <i class="iconfont textItem" @click="dispatch({type: 'nextYear'})">&#xe713;</i>
</div>
const dispatch = useReducerDate(dayStr)

很简单吧,香不香。

  1. 第三个想法💡(刚加的)这个useReducerDate还能在提取一下写个类似 useReducer,写个可复用的函数
export const useReducer = (state: Ref<any>, reducer: (state: Ref<any>, action: action) => any) => {
    return function dispatch(val: action) {
        reducer(state, val)
    }
}
  1. 主要功能就完成了

源码

github.com/coolpail/vu…

总结

  1. 主要体会并不是日历怎么实现,而是在vue3.0的背景下,如何更好的基于新的api去实现功能,布局自己的目录结构;api太多了,玩法组合太多了
  2. 期待大家的写法和想法💡,对交流学习共同进步