vue3+ts在项目中的实际开发总结要时长温故

430 阅读4分钟

项目的简单介绍

  1. 使用vue3 typescript pina ElementPlus vueuse vue-router
  2. 使用vite构建工具,yarn包管理工具,node版本 14.5.7
  3. 使用了 pinia-plugin-persist 做store数据的本地持久化管理
  4. 使用到了封装成hook形式的Echarts
  5. 数据请求使用基本的axios封装。
  6. 使用webSocket长链接协议。

用到的知识点封装等

1. pinia 数据持久化方案pinia-plugin-persist

下面demo简化ts使用了any

  1. 在main.ts中初始化pinia等。
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'
const pinia = createPinia()
pinia.use(piniaPersist)
app.use(pinia)
  1. 在store index.ts文件中
import { createPinia } from 'pinia'
export * from './modules'
const pinia = createPinia()
export default pinia
  1. 每个store模块分开所以在 modules中新建不同模块的store
import { defineStore } from 'pinia'
export const useCategory = defineStore('category', {
  persist: {
    enabled: true,
  },
  state: (): { currentCategory: {}; category: {} } => ({
    category: {},
    currentCategory: {},
  }),
  getters: {},
  actions: {
    setCategory(data: any) {
      this.category = data
    },
    setCurrentCategory(data: any) {
      this.currentCategory = data
    },
  },
})
  1. 调用方法
import { useCategory } from '@/store/modules/category'
const store = useCategory()
// 设置数据
store.setCategory(data)
// 获取数据
store.category

2. vue3中Echarts的使用

  1. 引入封装好的hook
import { useDebounceFn } from '@vueuse/core'
import * as echarts from 'echarts'
import { ref, onMounted, onBeforeUnmount, markRaw } from 'vue'
import useTimeout from './useTimeout'
interface TypeParams {
  id: string
  option: any
  mouseover?: any
  mouseout?: any
  isInit?: boolean
  isResize?: boolean
  width?: number | string
  height?: number | string
}
export default ({ id, option, mouseover, mouseout, isInit = true, isResize = true, width, height }: TypeParams) => {
  const myChart = ref<echarts.ECharts | null>(null)
  const initOption = ref(option)
  const { timeout } = useTimeout()
  // 初始化chart
  const chartInit = (): void => {
    destroyChart()
    const el = document.getElementById(id) as HTMLDivElement
    if (!myChart.value && el) {
      el.style.width = width ? `${width}${typeof width === 'number' ? 'px' : ''}` : '100%'
      el.style.height = height ? `${height}${typeof height === 'number' ? 'px' : ''}` : '100%'
      // Uncaught TypeError: Cannot read properties of undefined (reading 'type')
      // 解决图例点击的时候报错,或者缩放的时候报错
      // 在实例化echart时,将其指定为非响应式的即可
      myChart.value = markRaw(echarts.init(el))
      chartSetOption()
      myChart.value && mouseover && myChart.value.on('mouseover', mouseover)
      myChart.value && mouseout && myChart.value.on('mouseout', mouseout)
    }
  }
  // 设置option
  const chartSetOption = (option: any = initOption.value): void => {
    myChart.value && myChart.value.setOption(option)
  }
  // 销毁
  const destroyChart = (): void => {
    myChart.value && myChart.value.off('mouseover')
    myChart.value && myChart.value.off('mouseout')
    myChart.value && myChart.value.clear()
    timeout(() => {
      myChart.value = null
    })
  }
  // 设置series数据
  const setData = (data: any[] = []): void => {
    option.series = data
    chartSetOption(option)
  }
  // 设置x轴数据
  const setXData = (data: any[] = []): void => {
    option.xAxis.data = data
    destroyChart()
  }
  // 重置
  const chartResize = (): void => {
    myChart.value && myChart.value.resize()
  }
  // 窗口缩放重新渲染
  window.addEventListener(
    'resize',
    useDebounceFn(() => isResize && timeout(chartResize), 1000)
  )
  onMounted(() => isInit && chartInit())
  // 销毁
  onBeforeUnmount(() => {
    // chart.dispose()
    window.removeEventListener('resize', chartResize)
    destroyChart()
  })
  return {
    chartInit,
    setXData,
    setData,
    destroyChart,
  }
}
  1. 使用如下
// 需要注意的地方 option需要使用computed返回
const { chartInit } = useEcharts({ id: 'detailChart', option, width: props.width, height: props.height, isInit: false })
watch(
  () => props.data,
  (value) => {
    if (value.xaxis || value.yaxis) {
      chartInit()
    }
  },
  {
    deep: true,
    immediate: true,
  }
)

3. 遇到过的问题

  1. 判断图片是否加载成功
排查问题的时候,有些时候会存在一些图片损坏也要注意留意。
1. 最简单的方案是使用 elementplus中自带的
<el-image
  :src="item.image"
  :preview-src-list="item.image"
  fit="cover"
  class="table-image"
  preview-teleported
>
  <template #error>
    <img class="errImg slotImg" src="@/assets/images/default.png" alt="" />
  </template>
</el-image>
2. 可以利用img标签的失败回调。

<img
  :id="el.id"
  :src="item.image"
  class="carousel-image"
  @error="loadFail(el.id)"
/>
const loadFail = (id: string) => {
  const image: any = document.getElementById(id)
  image.src = new URL(`../../assets/images/default.png`, import.meta.url).href
  image.className = 'defaultImg'
  image.οnerrοr = null
}
  1. 动态引入图片的问题
1. 在vite中没有办法使用 require来引入动态图片。可能很重要的一个问题是图片本身就是损坏的。
2. 使用
new URL(`../../assets/images/default.png`, import.meta.url).href
可以避免打包的时候文件路径不对。
  1. 本地开发的时候组件切换没有问题,线上出现 Cannot read properties of null (reading 'insertBefore')这种问题。
1. v-if导致,当值为false的时候操作了其中的DOM,简单的办法使用v-show
使用了watchEffect,同时使用了同一个组件,导致值相互影响。
2. el-table中需要注意
el-table-column渲染时报错,比如某些值为null
  1. reactive的使用需要注意
1. const data = reactive({
    result: []
})
不要直接 const data = reactive({}) 然后给data赋值
2. 解构会失去响应式需要注意
const { result } = data
result = xxxx
可以使用toRefs或者toRef
const { result } = toRefs(data)
  1. 动态路由引入的问题
使用 @引入的路由出现的问题解决方案有很多种,但是实际可能还是vite版本的问题目前探索下来在2.9.10版本未发现问题
  1. 打包需要做的某些优化
import viteCompression from 'vite-plugin-compression'
import Components from 'unplugin-vue-components/vite'
import AutoImport from 'unplugin-auto-import/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
vite.config.ts中的plugin使用到了一下
VueSetupExtend(),
AutoImport({
  // @ts-ignore
  resolvers: [ElementPlusResolver()],
}),
Components({
  resolvers: [ElementPlusResolver()],
}),
Icons({ compiler: 'vue3', autoInstall: true }),
viteCompression({
  //生成压缩包gz
  verbose: true,
  disable: false,
  threshold: 10240,
  algorithm: 'gzip',
  ext: '.gz',
}),

// build的一些打包配置

build: {
  sourcemap: mode !== 'production',
  // minify: 'esbuild', // 启用这个下面的才会生效,打包速度会慢很多
  terserOptions: {
    compress: {
      drop_console: true,
      drop_debugger: true,
    },
  },
  // 取消计算文件大小,加快打包速度
  reportCompressedSize: false,
  // 文件按照不同的文件夹打包
  rollupOptions: {
    output: {
      chunkFileNames: 'js/[name]-[hash].js',
      entryFileNames: 'js/[name]-[hash].js',
      assetFileNames: '[ext]/[name]-[hash].[ext]',
    },
  },
},