vue 2.x 迁移 typescript 踩坑

2,883 阅读2分钟

依赖

dependencies

vue-class-component
vue-property-decorator
vue-tsx-support
vuex-class

vue-tsx-support 是用来增强 VuePropsEvent 等类型的

不使用 vue-tsx-support

class Demo extends Vue {
    @Prop() demo: string
}

在外部是无法感知这个 props 类型的 使用 vue-tsx-support

interface Props {
    demo: string
}
class Demo extends tsx.Component<Props> implements Props {
    @Prop() demo: string
}

这种写法外部是可以感知需要传入的 Props 的类型的

devDependencies

@typescript-eslint/eslint-plugin
@typescript-eslint/parser
@vue/eslint-config-typescript
eslint-plugin-import
eslint-plugin-typescript
ts-loader
typescript

还有一些 @types 需要添加可自行查找(缺什么 types 会报错提示)

eslint

eslint 中需要添加对 ts、tsx 的支持,同时在迁移的过程中还会存在 js 和 vue 主要这两个地方需要注意(添加对 ts 的支持)

plugins: [ 'typescript', '@typescript-eslint' ],
parserOptions: { parser: "@typescript-eslint/parser", "ecmaFeatures": { "jsx": true }, "ecmaVersion": 2018, "sourceType": "module", }

同时需要在你的 workspace 的 vscode setttings.json 中增加对 ts 和 tsx 的验证

"typescript.tsdk": "node_modules/typescript/lib",
"eslint.validate": [
    "javascript",
    "javascriptreact",
    {
        "language": "typescript",
        "autoFix": true
    },
    {
        "language": "typescriptreact",
        "autoFix": true
    }
],
"eslint.options": { //指定vscode的eslint所处理的文件的后缀 "extensions": [ ".js", ".ts", ".tsx", ".vue" ] }

同时存在 vue 和 jsx

在我们改造过程中不可避免的会遇到在写 jsx 的时候会应用到之前的一些 vue 的组件,而这些组件有没有被全局 install,这个时候我们可以先如下处理

@Component({
 components: {
    DemoVue,
    },
})
class Demo extends Vue {
    render() {
        return (<demo-vue />)
    }
}

对于 template 中的一些修饰符,我们在 jsx 中该怎么处理,比 xxx.sync 修饰符,sync 只是一个语法糖,我们可以在组件的 on 中进行监听,如下

@Component({
    components: {
        DemoVue,
    },
})
class Demo extends Vue {
  render() {
    return (<demo-vue on={{'update:Click': () => {}}} />)
  }
}

同时我们需要区分 attrs 和 props , props 可以直接在组件上进行传递,attrs 需要放到 attrs 属性下,如下

@Component({
    components: {
        DemoVue,
    },
})
class Demo extends Vue {
    render() {
        return (<demo-vue on={{'update:Click': () => {}}} propsA attrs={{attrsA: 'xxx'}} />)
    }
}

对于一些第三方库没有 decorator ,该怎么办,我们可以借助 vue-class-component 中的 createDecorator 来造一个简易的轮子,如下 apollo 和 metaInfo

export function ApolloDes() {
    return function(_t: Vue, key: string, desc: any) {
        createDecorator(options => {
            options.apollo = desc.value()
        })(_t, key)
    }
}

export function MetaInfoDes() {
    return function (_target: Vue, key: string, desc: any) {
        createDecorator((options) => {
            options.metaInfo = desc.value
        })(_target, key)
    }
}
使用方式
class Demo extends Vue {
@MetaInfoDes()
metaInfo() {
    return {
        title: 'xxx',
    }
}

@ApolloDes
apollo() {
    return {
        reqA: {
            query: xxxx
        }
    }
}
render() {
    return (<demo-vue on={{'update:Click': () => {}}} propsA attrs={{attrsA: 'xxx'}} />)
}
}

一些·奇怪的 bug

如下例

class Demo extends Vue {
    test = {name: 'xxx'}
    render() {
        const { test } = this
        return (
            <test props={test}/>
        )
    }

}

上述代码会在编译的运行的时候将组件 test 编译为 <xxxx /> ,理解之后也不奇怪,因为 test 也是一个变量,只不过 这个组件被全局注册我们没在这 应用,刚好组件和变量 test 重名,而且 test 变量具有 name 属性,运行时,test 的 name 就会被编译成组件名了,组件和 test 都是变量,只不过组件这个变量在 Vue 中被全局注册了,而且这个组件名也是小写,lint 工具不会去检查,就造成了一种错觉。