You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue 3迁移时Props类型为unknown触发TS2322报错问题

Vue 3 迁移构建Props类型识别为unknown问题

问题现象

参考Vue 3迁移构建指南将应用从Vue 2升级至Vue 3过程中,遇到Props类型识别异常:Linter判定所有Props类型为unknown,但模板本身未提示任何错误。

复现代码

  • Prop定义:
cancelText: {
    type: String as PropType<string>,
    default: "",
},
  • 计算属性中调用Prop:
cancelButtonText(): string {
    return this.cancelText || this.$t("PRODUCT.ACTION_BAR.BACK");
},

异常表现

  • 编辑器内悬浮到对应变量上时可正确显示类型,编辑器本身类型识别逻辑正常:
    类型识别截图
  • 启动应用服务时,终端抛出如下TS错误:
TS2322: Type 'unknown' is not assignable to type 'string'.

报错截图:
报错截图

当前环境配置

初步判断问题为依赖版本不兼容,或Vue 3对应Lint规则未正确配置,当前项目配置如下:

依赖版本

"dependencies": {
    "@fortawesome/fontawesome-pro": "^5.15.3",
    "@fortawesome/fontawesome-svg-core": "^1.2.35",
    "@fortawesome/free-brands-svg-icons": "5.14.0",
    "@fortawesome/pro-regular-svg-icons": "^5.15.3",
    "@fortawesome/vue-fontawesome": "^2.0.2",
    "@sentry/browser": "^6.6.0",
    "@sentry/integrations": "^6.6.0",
    "@sentry/tracing": "^6.6.0",
    "@vue/compat": "3.2.36",
    "ant-design-vue": "^2.2.8",
    "axios": "^0.21.1",
    "jest": "^26.6.3",
    "normalize.css": "^8.0.1",
    "register-service-worker": "^1.7.1",
    "uuid": "^8.3.2",
    "vue": "^3.2.36",
    "vue-cookies": "^1.8.1",
    "vue-flag-icon": "^1.0.6",
    "vue-i18n": "^9.1.10",
    "vue-router": "^4.0.15",
    "vue3-touch-events": "^4.1.0",
    "vuex": "^4.0.2"
},
"devDependencies": {
    "@playwright/test": "^1.15.2",
    "@vue/cli-plugin-pwa": "^4.5.13",
    "@vue/cli-plugin-typescript": "^5.0.4",
    "@vue/cli-service": "^4.5.13",
    "@vue/compiler-sfc": "^3.2.36",
    "eslint": "^8.16.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-playwright": "^0.9.0",
    "eslint-plugin-vue": "^9.1.0",
    "html-loader": "^1.3.2",
    "jest": "^26.6.3",
    "less": "^3.13.0",
    "less-loader": "^7.3.0",
    "node-sass": "^5.0.0",
    "sass-loader": "^10.2.0",
    "typescript": "~4.7.2"
}

ESLint配置

module.exports = {
    env: {
        browser: true,
        es2020: true,
        node: true,
    },
    extends: [
        "plugin:playwright/playwright-test",
        "plugin:vue/base",
        "plugin:vue/essential",
        "plugin:vue/strongly-recommended",
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier",
    ],
    parser: "vue-eslint-parser",
    parserOptions: {
        sourceType: "module",
        parser: {
            js: false,
            ts: "@typescript-eslint/parser",
            "<template>": false,
        },
    },
    plugins: ["vue", "@typescript-eslint"],
    rules: {
        "no-else-return": "error",
        "no-var": "error",
        "prefer-const": "error",
        "prefer-arrow-callback": "error",
        "no-console": "error",
        "no-unused-vars": "off",
        "@typescript-eslint/no-unnecessary-type-assertion": "warn",
        "@typescript-eslint/explicit-module-boundary-types": "off",
        "@typescript-eslint/no-unused-vars": [
            "warn",
            {
                vars: "all",
                args: "after-used",
                ignoreRestSiblings: false,
                argsIgnorePattern: "^_",
                varsIgnorePattern: "^_",
                caughtErrorsIgnorePattern: "^_",
            },
        ],
        "vue/no-unused-properties": [
            "warn",
            {
                groups: ["props", "data", "computed", "methods", "setup"],
                deepData: true,
                ignorePublicMembers: true,
            },
        ],
        "vue/html-self-closing": [
            "warn",
            {
                html: {
                    void: "always",
                },
                svg: "always",
                math: "always",
            },
        ],
        "vue/multiline-html-element-content-newline": "off",
        "vue/html-closing-bracket-newline": "off",
        "vue/singleline-html-element-content-newline": "off",
        "vue/html-indent": "off",
        "vue/max-attributes-per-line": "off",
        "vue/this-in-template": ["error", "never"],
        "vue/attributes-order": [
            "error",
            {
                order: [
                    "DEFINITION",
                    "LIST_RENDERING",
                    "CONDITIONALS",
                    "RENDER_MODIFIERS",
                    "GLOBAL",
                    "UNIQUE",
                    "TWO_WAY_BINDING",
                    "OTHER_DIRECTIVES",
                    "OTHER_ATTR",
                    "EVENTS",
                    "CONTENT",
                ],
                alphabetical: false,
            },
        ],
        "vue/order-in-components": [
            "error",
            {
                order: [
                    "el",
                    "name",
                    "parent",
                    "functional",
                    ["delimiters", "comments"],
                    ["components", "directives", "filters"],
                    "extends",
                    "mixins",
                    "inheritAttrs",
                    "model",
                    ["props", "propsData"],
                    "data",
                    "computed",
                    "watch",
                    "LIFECYCLE_HOOKS",
                    "methods",
                    ["template", "render"],
                    "renderError",
                ],
            },
        ],
    },
    overrides: [
        {
            files: ["**/*.ts"],
            parser: "@typescript-eslint/parser",
            plugins: ["@typescript-eslint"],
            parserOptions: {
                tsconfigRootDir: __dirname,
                project: "tsconfig.json",
            },
            extends: [
                "plugin:@typescript-eslint/recommended",
            ],
            rules: {
                "@typescript-eslint/no-explicit-any": ["warn", { ignoreRestArgs: false }],
            },
        },
        {
            files: ["**/tests/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
            env: {
                jest: true,
            },
        },
    ],
};

问题根因

该异常由两个配置问题共同导致:

  • 使用@vue/compat迁移构建时,未在TS配置中做类型别名映射,TS默认读取原生Vue3类型定义,和兼容构建的运行时逻辑不匹配
  • ESLint解析规则仅对纯.ts文件开启了TS类型检查,.vue文件内的脚本块未关联tsconfig配置,导致Props类型无法被正确推导,默认回退为unknown

修复步骤

  1. 修改项目根目录tsconfig.json,添加路径别名映射,将vue的类型指向@vue/compat,同时确保.vue文件被纳入类型检查范围:
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "paths": {
      "vue": ["@vue/compat"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
  1. 调整ESLint配置,删除overrides中针对.ts文件单独指定parser的冗余配置,全局配置TS解析器关联tsconfig,让.vue文件内的TS代码也能获得正确类型推导:
// 全局parserOptions修改为
parserOptions: {
    sourceType: "module",
    parser: "@typescript-eslint/parser",
    tsconfigRootDir: __dirname,
    project: "./tsconfig.json",
    extraFileExtensions: [".vue"]
},
// 删除overrides中针对ts文件单独指定parser的冗余配置块
  1. 所有组件选项必须用defineComponent包裹,这是Vue3下TS正确推导组件内this类型的必要条件,示例代码:
import { defineComponent, PropType } from 'vue'
export default defineComponent({
    props: {
        cancelText: {
            type: String as PropType<string>,
            default: "",
        },
    },
    computed: {
        cancelButtonText(): string {
            return this.cancelText || this.$t("PRODUCT.ACTION_BAR.BACK");
        }
    }
})
  1. 删除node_modules目录和包管理器锁文件,重新安装依赖,重启IDE的TS服务和本地开发服务,报错即可消除。

内容的提问来源于stack exchange,提问作者Jonas Tamoševičius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 03:09:41