vue2老旧项目强行使用typescript

142 阅读2分钟

相信很多同行也有这个问题

  • 就记得组件的好像有一个方法,但是方法具体的名字记得很模糊,然而这个组件又是很长很大几千行,去找方法都要找好久☻,这个时候就会想要是有提示就好了🙃

vue2对于typescript的支持不是很友好,但是也提供了一个class类形式的编写方式接入typescript,不过这里不讨论这个,而是就是旧的vue项目,不是用的class形式编写的项目。

用的字面量形式定义的vue组件我也想要提示🥺

试过好几种方法,最后有一种虽然写起来不是很好,但是也搞定了问题😄

代码

webpack配置

其实这个是在vue.config.js中的chainWebpack方法中

image.png

config.module
  .rule('ts')
  .test(/.(ts|tsx)$/)
  .exclude.add(resolve('node_modules'))
  .end()
  .use('babel-loader')
  .loader('babel-loader')
  .end()
  .use('ts-loader')
  .loader('ts-loader')
  .options({
    appendTsSuffixTo: [/.vue$/],// 额外添加vue的支持
    transpileOnly: true // 只用于编译
  })
  .end()

vue组件

这是我对vue-treeselect的一个包装

<template>
  <VueTreeselect
    v-bind="$attrs"
    v-on="$listeners"
    class="vueTreeSelectMini vueTreeSelectSmall"
    v-model="syncValue"
    :options="options"
    :placeholder="placeholder"
    :normalizer="normalizerCom"
    :noChildrenText="noChildrenText"
    :noOptionsText="noOptionsText"
    :noResultsText="noResultsText"
    :valueConsistsOf="valueConsistsOf"
    :appendToBody="appendToBody"
    :zIndex="zIndex"
    @input="inputCom"
    @open="openCom"
  >
    <template #after-list v-if="$slots.afterList">
      <slot name="after-list"></slot>
    </template>
    <template #before-list v-if="$slots.beforeList">
      <slot name="before-list"></slot>
    </template>
    <template #option-label="params" v-if="$slots.optionLabel">
      <slot name="option-label" v-bind="params"></slot>
    </template>
    <template #value-label="params" v-if="$slots.valueLabel">
      <slot name="value-label" v-bind="params"></slot>
    </template>
  </VueTreeselect>
</template>

<script lang="ts">
import VueTreeselect from '@riophae/vue-treeselect'
// 导入treeSelect样式
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import {PropType} from "vue/types/options";
import typeOfUtils from "../../utils/type-of-utils";
import PopupManager from "element-ui/lib/utils/popup/popup-manager";
import {Props, PropsDefault} from './Type'

export default {
  name: "TreeSelect",
  components: {
    VueTreeselect
  },
  props: {
    value: {
      type: [String, Number, Array],
      default: () => ''
    },
    options: {
      type: Array,
      default: () => []
    },
    props: {
      type: Object as PropType<Props>,
      default: (): Props => PropsDefault
    },
    normalizer: Function,
    loadOptions: Function,
    placeholder: {
      type: String,
      default: '请选择'
    },
    noChildrenText: {
      type: String,
      default: '暂无子节点'
    },
    noOptionsText: {
      type: String,
      default: '没有可用选项'
    },
    noResultsText: {
      type: String,
      default: '未匹配到结果'
    },
    valueConsistsOf: {
      type: String as PropType<'ALL'>,
      default: 'ALL'
    },
    appendToBody: {
      type: Boolean,
      default: true
    },
  },
  computed: {
    syncValue: {
      set(val) {
        this.$emit('input', val)
      },
      get() {
        return this.value
      }
    },
  },
  data() {
    return {
      zIndex: undefined
    }
  },
  methods: {
    normalizerCom(node) {
      if (this.normalizer) {
        return this.normalizer(node)
      }
      const props = {
        ...PropsDefault,
        ...this.props
      }
      if (!this.loadOptions) {
        if (typeOfUtils.isEmpty(node[props.children])) {
          return {
            id: node[props.value],
            label: node[props.label],
            children: undefined
          }
        }
      }
      return {
        id: node[props.value],
        label: node[props.label],
        children: node[props.children],
      }
    },
    inputCom(val) {
      this.$emit('input', val)
    },
    openCom(e) {
      if (this.appendToBody) {
        this.zIndex = PopupManager.nextZIndex()
      }
      this.$emit('open', e)
    }
  }
}
</script>

<style lang="scss">
.vue-treeselect__portal-target.vue-treeselect.vue-treeselect--searchable.vue-treeselect--open-below.vue-treeselect--append-to-body{
  min-width: 500px;
}
.el-form-item{
  &.el-form-item--small{
    /* small */
    .vueTreeSelectSmall .vue-treeselect__control {
      height: 32px;
    }
    .vueTreeSelectSmall .vue-treeselect__control .vue-treeselect__value-container {
      height: 32px;
      display: block;
    }
    .vueTreeSelectSmall
    .vue-treeselect__control
    .vue-treeselect__value-container
    .vue-treeselect__placeholder {
      font-size: 13px;
    }
    .vueTreeSelectSmall
    .vue-treeselect__control
    .vue-treeselect__value-container
    .vue-treeselect__single-value {
      line-height: 32px;
      font-size: 13px;
    }
  }
  &.el-form-item--mini{
    /* mini */
    .vueTreeSelectMini .vue-treeselect__control {
      height: 26px;
    }
    .vueTreeSelectMini .vue-treeselect__control .vue-treeselect__value-container {
      height: 26px;
      display: block;
    }
    .vueTreeSelectMini
    .vue-treeselect__control
    .vue-treeselect__value-container
    .vue-treeselect__placeholder {
      font-size: 12px;
    }
    .vueTreeSelectMini
    .vue-treeselect__control
    .vue-treeselect__value-container
    .vue-treeselect__single-value {
      line-height: 26px;
      font-size: 12px;
    }
  }
}

//.vue-treeselect{
//  .vue-treeselect__control{
//    background-color: $--input-bg-color;
//    border: 1px solid $--popper-border-color;
//  }
//  .vue-treeselect__single-value{
//    color: $--input-font-color;
//  }
//}
//.vue-treeselect--open-below .vue-treeselect__menu{
//  background-color: $--popper-bg-color;
//  color: $--popper-font-color;
//  .vue-treeselect__option--highlight{ // 高亮
//    background-color: $--popper-bg-color-hover;
//    color: $--popper-font-color-hover;
//    .vue-treeselect__option-arrow-container{
//      svg{ // 箭头颜色
//        color: $--popper-font-color-hover;
//      }
//    }
//  }
//}
//.vue-treeselect--single{
//  .vue-treeselect__option--selected{
//    background-color: $--tree-select-bg-color-hover;
//    color: #409EFF;
//    &:hover{
//      background-color: $--popper-bg-color-hover;
//    }
//  }
//}
</style>

类型文件

// @ts-nocheck
// vue类型文件,用于提示
/**
 * 用法,这个文件一定要单独写,不然会编译进去
 * import type {IndexType} from "./indexVueType";
 * const test = {} as typeof IndexType
 *
 * 这样就不会编译到代码
 *
 *
 *
 * 如果需要ref也有提示
 * 在计算属性里面写一个tip板的
 * $refsTip(){
 *       return this.$refs
 *     }
 */
import Vue from 'vue'
import Index from './index.vue'

const v = Vue.extend(Index)
export const IndexType = new v()

主要看这里

image.png

效果

组件中引入

<template>
  <div>
    <TreeSelect ref="TreeSelectRef"></TreeSelect>
  </div>
</template>

<script lang="ts">
// @ts-nocheck
import TreeSelect from "../local_modules/ZhiXinLib/components/TreeSelect/index.vue";
export default {
  name: 'EmptyHome',
  components: {TreeSelect},

  data(){
    return{
    }
  },
  created() {

  },
  methods:{
  }
}
</script>

<style scoped lang="scss">

</style>

组件中引入这个不需要那个类型定义文件,不过好像只有在webstorm中才会有提示

image.png

ts文件引入

  • 这个是在组件内部使用类型文件

image.png

  • 这个是在纯ts中使用

image.png

这些都是没有问题的,不过我也就是在webstorm上测试了,vscode作为亲儿子应该也支持吧,要是不支持就尴尬了🤣

问题

既然多了一个文件这个文件的代码会不会执行,先上结论不会哈

vite启动

vite是不需要配置typescript的,直接可以用

在类型文件中插入一个输出语句

image.png

控制台并没有输出这个语句

image.png

这里我们删掉type,建议不要删除

image.png

控制台也是没有输出的

image.png

webpack启动

webpack启动也是同样的效果,我就不截图了

原理

  • 这个就是利用vue.extend和实例化

也就是这个样子

const v = Vue.extend(Index)
export const IndexType = new v()

这个index就是vue组件

  • 这里一个很重要的点,就是typeof,不加这个是会报错的
  • 因为都是用的ts类型,所以在编译过程中,这个文件引入最后就没了,所以这个文件不会被执行,可以放心大胆的用
  • 这个文件的头部一定要加上//@ts-nocheck,不让ts去检测这个文件,不然会报红(IED给你爆红)

最后

欢迎关注公众号致心空间:O(∩_∩)O😁

致心空间