前言
第一次用vue3的api和ts写,还在摸索中,有地方写的不规范的,可交流,共同进步
项目介绍,功能很简单
- 显示所有时间日期
- 上一年上个月,下个月下一年
预览地址
写完后的感受和收获
- ts很香,但我用的不熟练;我平时会看看
vue-next
的源码,既能学新api还能学ts,一举两得呢;当然其他优质代码也可以 - vue3.0新的api很强大;例如就是
reative, ref, watcher, watchEffect...
,选择太多了,给开发者的组合方式也就多了,现在最缺的应该是最佳实践了。 - 最大感受就是之前基于组件的分离,现在还可以基于数据或者方法层面上的抽离,例如我试着写了一个
useComputedDay
和useReducerDate
,有点意思
怎么写日历
我的思路是:
- 需要一个输入的日期,默认是当前日期;这个值可以是传入的
props
,也可以是state
组件自身的状态,(看你喜欢) - 需要知道基于当前日期的年份
- 需要知道基于当前日期的月份
- 需要知道基于当前日期的几号
- 需要知道基于当前日期的当月最大天数
- 需要知道基于当前日期的上一个月最大天数
- 需要知道基于当前日期的当月一号是星期几
这些值都是基于当前日期的,所以大家会想到可以用一个computed
来关联起来,反正我这么想了,😄
有了这些值做什么能?
- 我先画一个7*6的大方块,默认索引是0-41
- 我知道当前日期一号是星期几,就能和上面的索引关联起来,在往后填日子,填到什么时候,就是当月最大天数的量,剩下的就是下一个月的1号还是到索引结束,
- 同理头部,当前日期一号是星期几,之前的可以根据上一月最大天数与索引关联起来,填上去
举个例子🌰
- 今天是2020/4/28;星期2;4月1号是星期三;最大天数是30天;上一个月的最大天数是31天;索引是
index
- 4月份的所有日子1-30怎么填入里格子中呢?当
index - 3 >= 0
就往小格子里填入index - 3 + 1
;直到(30 + 3 - 1) < index
时,😯;剩余头尾 - 尾部触发条件就是
(30 + 3 - 1) < index
,填入index - (30 + 3 - 1)
- 剩下就是头部了,只要填入
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,个人感觉数据,方法也能抽离,除了️组件抽离(只是我个人的想法,可以交流)
- 第一个想法💡是:能不能把基于输入日期的值拿出来溜溜呢? 对于那写当前年份,当前月份等,数据可以抽离成到一个文件中,取个好听的名字叫做
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
改变了所有的都会改变
- 第二个想法💡是:能不能把控制上下年月的方法也搞到外面去呢?,例如整个
dispatch
,dispatch
怎么写呢?可以抽离出来写成类似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'})"></i>
<i class="iconfont textItem" @click="dispatch({type: 'preMonth'})"></i>
<p class="textItem">
{{state.currentYear}} 年 {{state.currentMonth}} 月 {{state.currentDate}} 日
</p>
<i class="iconfont textItem " @click="dispatch({type: 'nextMonth'})"></i>
<i class="iconfont textItem" @click="dispatch({type: 'nextYear'})"></i>
</div>
const dispatch = useReducerDate(dayStr)
很简单吧,香不香。
- 第三个想法💡(刚加的)这个
useReducerDate
还能在提取一下写个类似useReducer
,写个可复用的函数
export const useReducer = (state: Ref<any>, reducer: (state: Ref<any>, action: action) => any) => {
return function dispatch(val: action) {
reducer(state, val)
}
}
- 主要功能就完成了
源码
总结
- 主要体会并不是日历怎么实现,而是在vue3.0的背景下,如何更好的基于新的api去实现功能,布局自己的目录结构;api太多了,玩法组合太多了
- 期待大家的写法和想法💡,对交流学习共同进步