Vue 3 + TypeScript 的一些实践

890 阅读3分钟

最近在用 Vue 3 + TypeScript 写代码项目了。来说说自己的一些感受:本身 JavaScript 是弱语言类型,在简单易上手的同时,也带来了类型混乱的问题。TypeScript 应运而生,可以看成是 JavaScript 的超集,强制语言类型的 “JavaScript”。在写 TypeScript 代码的时候,要先定义类型;用写好的类型来定义变量这样达到强类型的目的。

关于 TypeScript 入门的文章:juejin.cn/post/690313…
关于 Vue 3 的入门文章:juejin.cn/post/690818…

ref reactive 传入类型

interface LeaveInfoForm {
  userId: number | undefined,
  startDate: string,
  endDate: string,
  attendanceEventTypeCode: string | undefined,    
}

interface DatePartEnum{
  code: string
  name: string
}

const switchOptions = ref<DatePartEnum[]>([])

const formState = reactive<LeaveInfoForm>({
  userId: undefined,
  startDate: '',
  endDate: '',
  attendanceEventTypeCode: undefined,
})

ref reactive 的泛型传入具体的类型后,在使用的时候一方面增强 VS Code 的提示,另一方面利用到了 typescript 的类型检查,避免类型错误。

枚举类型的应用

image.png

在项目中,会遇到上述 tab 进行切换,可以使用枚举类型增强代码的可读性

enum EventType{
    StatutoryAnnualLeave = 11,
    WelfareLeave = 12,
    FullPaySickLeave = 13,
    TransferVacation = 14,
}

switch (holidaysRecordParams.typeCode) {
    case EventType.StatutoryAnnualLeave:
    break
    ...
    default:
    break
}

编写项目的声明文件

编写自己项目的声明文件主要是将一些全局类型暴露出来,方便统一管理,同时减少重复代码的编写。

image.png

  1. 声明文件的位置:可以在项目任何地方,并且该位置在 tsconfig.json 的 include 或 files 有配置编译路径

image.png

  1. declare 声明的类型、变量是全局的,需要在 .eslintrc.js 中添加进去

image.png

defineAsyncComponent 导入动态组件在 keep-alive 缓存组件的问题

const Home = defineAsyncComponent(() => import('../Home/Home.vue'))

image.png

Home 组件的 name 已经不是 Home 组件中定义的 homeNoKeep 了,而是 AsyncComponentWrapper 我们知道 keep-alive 的 exclude/include 属性是根据组件的 name 来识别的,所以下面这么写并不能实现 Home 组件的不缓存,原因在于 Home 组件的 name 已经被修改了。

解决方法:手动修改 Home 组件的 name 属性:

const Home: any = defineAsyncComponent(() => import('../Home/Home.vue'))
Home.name = 'homeNoKeep'

这样才能实现 Home 组件的不缓存,满足业务需求

Composition Api

在移动项目中需要实现一个向上滑动隐藏筛选条件,向下滑动出现筛选条件的功能。这里将手指的上滑和下滑事件抽出来:

// useMouseMove.ts

import { reactive, onMounted, onBeforeUnmount } from 'vue'

const MIN_DISTANCE = 10
interface Position{
  x: number
  y: number
}
type FnArgus = ()=> void
function useMouseMove(onMoveDown: FnArgus = () => {}, onMoveUp: FnArgus = () => {}) {
  const startPoint = reactive<Position>({
    x: 0,
    y: 0,
  })

  const touchStart = (evt: TouchEvent) => {
    startPoint.x = evt.targetTouches[0].pageX
    startPoint.y = evt.targetTouches[0].pageY
  }
  const touchMove = (evt: TouchEvent) => {
    const dis: number = evt.targetTouches[0].pageY - startPoint.y
    if (dis > 0) {
      if (Math.abs(dis) > MIN_DISTANCE) {
        onMoveDown()
      }
    } else if (Math.abs(dis) > MIN_DISTANCE) {
      onMoveUp()
    }
  }

  onMounted(() => {
    document.addEventListener('touchstart', touchStart, false)
    document.addEventListener('touchmove', touchMove, false)
  })

  // 卸载事件
  onBeforeUnmount(() => {
    document.removeEventListener('touchstart', touchStart, false)
    document.removeEventListener('touchmove', touchMove, false)
  })
}

export default useMouseMove

使用方式:

// 手指滑动事件
import useMouseMove from '~/hooks/useMouseMove'
setup(){
    const onMouseDown = () => {
    
    }
    const onMouseUp = () => {
    }
    useMouseMove(onMouseDown, onMouseUp)
}

Provide 和 Inject 使用

Provide 和 Inject 的使用和 Vuex 状态管理还是不太一样的。Vuex 管理的数据在页面的各个组件都可以使用,但是 Provide 和 Inject 有层级的概念。只有在父组件 Provide 数据,在子组件中才可以 Inject 获取到。如果两个组件没有父子或祖孙(即上下级)的关系的话,Provide 是失效的。

在最顶层组件使用 Provide 注册数据,可实现同 Vuex 类似的数据管理功能。cloud.tencent.com/developer/a… 较为清晰的介绍了 Provide 和 Inject (TypeScript)版的实现数据管理的方式。由于项目中用到的数据并不需要在最顶层去管理,所以对上述方法进行了一点点修改。如下:


// src/context/dates.ts
import { provide, inject, computed, Ref } from '@vue/composition-api';
import { DateItem } from '~/components/Calendar/Calendar.ts'

export type DatesContext = {
  dates: Ref<DateItem[]>
  datesList: Ref<DateItem[]>
  setDates: (value: DateItem[]) => void
  updateDate: (value: DateItem) => void
}

export const DateSymbol = 'DATE_SYMBOL'

// Provide 数据
export const useDateListProvide = (dates: Ref<DateItem[]>)=>{
  // dates 所有日期
  // 只显示当月日期
  const datesList = computed(() => dates.value.filter(item => item.type === 'current'))
  const setDates = (value: DateItem[]) => {
    dates.value = value
  }

  const updateDate = (value: DateItem) => {
    dates.value.every(item => {
      if (item.date === value.date) {
        item.id = value.id
        item.tagName = value.tagName
        item.selVisible = value.selVisible
        item.popVisible = value.popVisible
        return false
      }
      return true
    })
  }

  provide<DatesContext>(DateSymbol, {
    dates,
    datesList,
    setDates,
    updateDate,
  })  
}

// Inject 获取
export const useDateListInject = ()=>{
  const dateContext = inject<DatesContext>(DateSymbol);
  if (!dateContext) {
    throw new Error(`useDateListInject must be used after useDateListProvide`);
  }
  return dateContext;  
}

先记录这么多吧,欢迎大家留言补充。