Vue3+Vite+Ts 项目实战 01 Vite 创建项目、ESLint+TS+GitCommit配置、Vue3新特性介绍

前置声明:

  • Vue3 和 Vite 还在持续更新,中文文档仍未全部完善,该笔记的内容在未来可能过时,建议多参考英文文档,使用最新版本。
  • 本案例使用的后端 API 服务,基于 Express 搭建,使用 json 文件管理数据,Git 仓库和使用文档
  • 本案例旨在学习 Vite 和 Vue3 搭建配置应用,并没有开发完整的业务功能。

项目初始化

使用 Vite 创建项目

Vite 官方中文文档 (vitejs.dev)

官方声明:Vite 需要 Node.js 版本 >= 12.0.0。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。

本例使用时 Node.js 12 版本执行 build 命令报错,于是改用 Node.js 16 版本,切换版本记得重新 npm install 避免有依赖没有更新。

# 创建
npm init vite@latest
√ Project name: ... shop-admin # 项目名称
√ Select a framework: » vue # 选择框架
√ Select a variant: » vue-ts # 选择 vue 或 vue-ts

cd ./shop-admin
git init
npm install
npm run dev

访问 http://localhost:3000/

初始目录结构说明

├─ public # 存放不需要编译构建的静态资源
│   └─ favicon.ico
├─ src # 存放需要编译构建的文件
│   ├─ assets
│   │   └─ logo.png # 需要编译构建的静态资源
│   ├─ components
│   │   └─ HelloWorld.vue
│   ├─ App.vue
│   ├─ env.d.ts # ts 类型声明
│   └─ main.ts # 启动入口文件
├─ .gitignore # git 忽略文件
├─ index.html # 单页文件的模板文件
├─ package-lock.json
├─ package.json
├─ README.md
├─ tsconfig.json # ts 配置文件
├─ tsconfig.node.json
└─ vite.config.ts # vite 配置文件

package.json

{
    
    
  "scripts": {
    
    
    // 启动开发服务器
    "dev": "vite",
    // 构建生产环境产物:校验 ts 类型,通过后执行 vite 打包命令
    "build": "vue-tsc --noEmit && vite build",
    // 本地预览生产构建产物:以前需要将打包文件配置到 nginx 等服务器中才能预览,vite 简化了这个流程
    "preview": "vite preview"
  },
}

关于构建失败的原因

本笔记编写时,运行 npm run build 会报大量错误:

Cannot access ambient const enums when the '--isolatedModules' flag is provided.

原因是 Vite 官方建议配置 TypeScript 的编译器选项:isolatedModulestrue

{
    
    
  "compilerOptions": {
    
    
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    // "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"]
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{
    
     "path": "./tsconfig.node.json" }]
}

由于这个导致的编译报错,Issue 并没有得到解决,暂时只能先将其设置为 false 或删除这个选项。

添加自定义目录结构

src 目录下添加一些文件夹:

├─ api # API 接口封装
├─ styles # 全局样式
├─ utils # 工具模块
├─ plugins # 插件
├─ views # 路由页面
├─ router # 路由模块
├─ store # vuex 容器模块
├─ layout # 公共布局组件
└─ composables # 项目中提取出来的组合式 API 函数模块

代码规范和 ESLint

在 Vite 创建的项目中默认没有集成 ESLint,并且目前官方也没有任何和 ESLint 相关的内容,所以需要手动集成配置 ESLint。

基础配置

# 安装 eslint(当前版本 8.5.0)
npm i eslint -D
# 初始化 eslint 配置文件
npm init @eslint/config

# 如何使用 ESLint
? How would you like to use ESLint? ...
# 检查语法 找到问题 强制代码规范
> To check syntax, find problems, and enforce code style
# 项目中使用的 JS 模块规范
√ What type of modules does your project use? · esm
# 前端框架
√ Which framework does your project use? · vue
# 是否使用 TS
√ Does your project use TypeScript? · No / Yes
# 代码运行环境
√ Where does your code run? · browser
# 代码规范
? How would you like to define a style for your project? ...
# 使用一个流行的代码规范
> Use a popular style guide
> Standard: https://github.com/standard/standard
# 配置文件生成 js 类型的文件
√ What format do you want your config file to be in? · JavaScript
# ...

生成的 eslint 配置文件:

// .eslintrc.js
module.exports = {
    
    
  env: {
    
    
    browser: true,
    es2021: true
  },
  extends: [
    'plugin:vue/essential',
    'standard'
  ],
  parserOptions: {
    
    
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module'
  },
  plugins: [
    'vue',
    '@typescript-eslint'
  ],
  rules: {
    
    
  }
}

本人基于个人习惯,修改了两条规则:

rules: {
    
    
  // 要求或禁止函数圆括号之前有一个空格
  'space-before-function-paren': [2, {
    
    
    anonymous: 'always',
    named: 'never',
    asyncArrow: 'always'
  }],
  // 要求组件名(文件名)必须是多单词的
  'vue/multi-word-component-names': 0
}

package.json 中添加验证脚本:

"scripts": {
    
    
  ...
  // 检查代码和自动修复
  "lint": "eslint src/**/*.{js,jsx,vue,ts,tsx} --fix"
},

npm run lint 运行 eslint 验证:

# App.vue 和 components/HelloWorld.vue 验证失败,template 只能包含一个根节点
error  The template root requires exactly one element  vue/no-multiple-template-root

这个规则适用于 Vue 2,Vue 3 没有这个限制。查看 eslint 配置文件中使用的插件 plugin:vue/assential,来自 eslint-plugin-vue 包,查看 eslint-plugin-vue\lib 目录(官方介绍):

├─ base.js
├─ essential.js # 当前配置的验证规则
├─ no-layout-rules.js
├─ recommended.js
├─ strongly-recommended.js
# 以上是 Vue2 的验证规则
# 以下是 Vue3 的验证规则
├─ vue3-essential.js
├─ vue3-recommended.js
└─ vue3-strongly-recommended.js

将 eslint 配置为 Vue 3 的验证规则(本文使用最严格的):

// .eslintrc.js
module.exports = {
    
    
  ...
  extends: [
    // 'plugin:vue/essential',
    // 使用 vue3 规则
    'plugin:vue/vue3-strongly-recommended',
    'standard'
  ],
  ...
}

编译宏和 defineProps、defineEmits、no-undef 规则警告

再次运行 npm run lint

# HelloWorld.vue 报错
error  'defineProps' is not defined    no-undef

definePropsdefineEmits 是 Vue3 定义的编译宏(compiler macros),只能在 <script setup> 中(最外层)使用,它们不需要导入,在处理 <script setup> 会被编译掉。

但在 eslint 检查的时候被当作未被定义的变量报错,可以显示的在文件中导入:

import {
    
     defineProps, defineEmits } from 'vue'

或将它们声明为 eslint 检查时的全局变量:

// .eslintrc.js
module.exports = {
    
    
  globals: {
    
    
    defineProps: "readonly",
    defineEmits: "readonly",
    defineExpose: "readonly",
    withDefaults: "readonly"
  },
  ...
}

也可以使用 eslint-plugin-vue 官方解决办法(源码与上面配置全局变量一样):

// .eslintrc.js
module.exports = {
    
    
  env: {
    
    
    browser: true,
    es2021: true,
    // 添加:
    'vue/setup-compiler-macros': true
  },
  ...
}

如果 vscode 报错Environment key "vue/setup-compiler-macros" is unknown,请检查 eslint-plugin-vue 下是否有这个规则,如果没有则可能版本太老,可以更新版本(本文安装的版本是8.5.0)。

再次运行 npm run lint,没有报错了。

编辑器集成

编辑器集成主要实现两个功能:

  1. 如何看到不符合规范的错误提示
  2. 如何按照项目中的 ESLint 规则要求进行格式化

实现步骤:

1、卸载/禁用 vetur 插件(Vue2 插件)

2、安装 volar 插件(Vue Language Features - 相当于支持 Vue3 的 vetur,且支持 TypeScript 提示)

3、安装 ESLint 插件

只要安装并启用了这个插件,就会自动查找项目中的 eslint 配置规范,并给出验证提示。

同时 ESLint 插件提供了格式化工具,但是需要手动配置才可以,打开文件-首选项-设置(快捷键 Ctrl+,),找到扩展-ESLint,勾选 Format: Enable,启用 ESLint 格式化工具:

在这里插入图片描述

也可以直接修改 vscode 的 settings.json,添加 "eslint.format.enable": true

然后继续配置 eslint.validate 选项指定 eslint 可以检查的文件类型(默认只检查 .js.jsx 文件):

{
    
    
  "eslint.format.enable": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "vue"
  ],
  ...
}

接着将文件格式化工具选为 ESLint,打开要格式化的文件,右键代码视图-使用…格式化文档-配置默认格式化程序…,选择 ESLint。

配置完成后,就可以使用 Alt+Shift+F 格式化文档了。

PS:安装、启用 ESLint 插件,修改配置文件,都会重新向 IDE 注册,可能会导致延迟甚至不显示,最好改动之后重载一下:Ctrl+P,键入 >reload

配置 git pre-commit hook

配置 pre-commit 钩子,将 lint 命令加入到开发构建流程,在 git 提交之前执行 lint 验证,防止不符合规范的代码提交到 git 仓库。

使用工具:okonet/lint-staged

npx mrm@2 lint-staged
# 会安装两个 npm 包
#	husky - 提供 git 钩子功能,拦截 git 命令
#	lint-staged - 获取 git 暂存区中的代码进行lint验证

执行完成后,会修改 package.json

1、安装了两个 npm 包

  • husky - 提供 git 钩子功能
  • lint-staged - 获取 git 暂存区中的代码进行lint验证

2、添加一个脚本

安装依赖后执行 prepare,执行 huskey 命令,安装钩子,确保每个 clone 项目的人一旦安装依赖就会将 husky 钩子初始化到本地,从而保证每个人在提交 git 之前都能执行 lint 验证。

npx mrm@2 lint-staged 执行完成后也会执行 husky install,所以要在 .git 所在目录下执行,否则会报错找不到 .git

husky install 会初始化 husky 的钩子:

  1. 通过配置 .git/config 中的 hooksPath 修改 hooks 目录(默认 .git/hooks
  2. 在项目根目录下创建 .husky 文件夹,用于存放自定义钩子执行文件
"scripts": {
    
    
  ...
  "prepare": "husky install"
},

3、添加了 lint-staged 配置

可以在此基础上修改,自定义自己的 lint 命令:

"lint-staged": {
    
    
  // 执行 eslint 命令验证 js 文件
  "*.js": "eslint --cache --fix"
}

修改为:

"lint-staged": {
    
    
  // 提交指定文件时执行 lint 脚本进行验证,如果有 fix 修复的内容会自动 git add
  "*.{js,jsx,vue,ts,tsx}": [
    "npm run lint"
  ]
}

现在执行 git commit 命令时,会先进行 lint 检查暂存区的代码,如果有代码不符合规范,则不会执行 git commit

注意:保证团队都会执行 pre-commit 钩子的前提是:

  1. 执行 husky install 初始化 .husky 文件夹和配置 git hooks 目录地址
  2. 项目包含 .husky/pre-commit 钩子执行文件,所以要确保这个文件会提交到 git 仓库

在开发和构建时进行代码规范校验

其实就是将 ESLint 集成到 vite 开发的编译构建过程中,以提供实时的 ESLint 验证。

当前 Vite 还没有提供 ESLint 相关的插件,可以自己开发一个插件,也可以使用别人开发好的。

官网导航 Links - Awesome Vite 列出了一些 Vite 相关的优质资源,推荐使用 gxmari007/vite-plugin-eslint

# 安装
npm install vite-plugin-eslint --save-dev

vite 配置文件中加载插件:

// vite.config.ts
import {
    
     defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint'

// https://vitejs.dev/config/
export default defineConfig({
    
    
  plugins: [
    vue(),
    eslintPlugin({
    
    
      /* 配置选项 */
      // 禁用 eslint 缓存
      cache: false
    })
  ]
})

cache 缓存功能建议关掉,因为有时将验证失败的代码修复后,eslint 可能仍会读取缓存中的结果,而且 eslint 只会验证当前修改的文件,而不是全部文件,所以不使用缓存影响不大。

重新启动 npm run dev

现在开发阶段的编译构建代码时,命令行工具和页面都会验证并提示失败信息。而且在构建生产环境时(npm run build)也会验证。

Git Commit 规范

提交规范介绍

参考:Commit message 和 Change log 编写指南 - 阮一峰

Git 每次提交代码,都要写 Commit message 说明本次提交的具体含义。

git commit -m 'hello world'

Git 本身并没有要求 Commit message 的格式,如果随意编写,当有一天你需要在历史记录中通过 Commit message 检索历史中的一次提交记录,就会很麻烦。

统一团队 Git Commit message 标准,便于后续代码 review,版本发布以及日志自动化生成等等,详细参阅阮一峰的文章。

目前最流行的是 Angular提交规范

相关工具:

  1. 撰写工具(commitizen) - 帮助编写符合规范的 Commit message。(当习惯规范后就不需要这个工具了)
  2. 验证工具(commitlint) - 配置 git hooks,在提交前使用工具验证 Commit message 是否符合规范
  3. 日志生成工具(conventional-changelog) - 通过 git 元数据生成日志,一般开源项目会使用

配置 commitlint 验证工具

# windows 下安装 cli 和 常用规范
npm install --save-dev @commitlint/config-conventional @commitlint/cli
# 配置 commitlint 使用常用规范
# 建议手动创建 commitlint.config.js 文件并填充内容,命令可能创建的并不是 utf8 编码的文件,eslint 会报错 `Parsing error: Invalid character`
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

# 安装和初始化 husky(上面配置 eslint 校验钩子时已经完成)
# npm i husky -D
# npx husky install

# 创建 commit-msg 钩子
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

如果使用命令无法创建 ./husky/commit-msg 文件,可以手动创建并填充以下内容:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no -- commitlint --edit $1

执行提交命令测试:

git add .
git commit -m 'commitlint 配置巴拉巴拉巴拉'

验证失败:

在这里插入图片描述

规范 message:

git commit -m 'chore: commitlint 配置巴拉巴拉巴拉'

验证成功:

在这里插入图片描述

Vite 中的 TS 环境说明

Vite 在创建项目时已经配置好了 TypeScript 环境,详细参考官方文档

下面列出需要注意的几点。

TypeScript 类型检查

Vite 仅执行 .ts 文件的转译工作,并 执行任何类型检查。

let count: number = 100
count = 200
// 不会报错
count = 'hello'
  • dev开发阶段,Vite 假设 IDE 配置了类型检查功能,Vite 本身不负责这个任务。
  • build构建阶段,会通过 vue-tsc --noEmit 命令进行类型检查

由于官方 vue-tsc(使用 esbuild 将 TypeScript 转译到 JavaScript)还不支持监听功能,所以暂时没有很好的插件支持开发阶段进行实时类型验证。

类型声明

Vite 创建项目时会生成一个 src/env.d.ts 类型声明文件(之前的版本是两个文件 shimes-vue.d.tsvite-env.d.ts):

/// <reference types="vite/client" />

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>
  export default component
}

第一行用于添加客户端代码环境中的类型声明,为了保险起见,建议按照官方说明,将 vite/client 添加到 tsconfig 中的 compilerOptions.types 下:

{
    
    
  "compilerOptions": {
    
    
    "types": ["vite/client"]
  }
}

后面的内容是为 .vue 资源文件添加类型声明,因为 TypeScript 默认不识别 .vue 文件,所以在使用 TypeScript 的项目中导入 Vue 组件,必须在后面加上 .vue 后缀。

Vue 3 中的 TS 支持

Vue 官方文档《搭配 TypeScript 使用 Vue》中介绍了如何将 TypeScript 集成到项目中、推荐配置等一些 Vite 创建项目时已经完成的内容,下面提取几个关于 TS 使用的部分。

1、在单文件组件中使用 TypeScript,需要在 <script> 标签上加上 lang="ts" 属性。如果使用 JSX 则添加 lang="tsx"

2、为了让 TypeScript 正确地推导出组件选项内的类型,我们需要通过 defineComponent() 这个全局 API 来定义组件。

import {
    
     defineComponent } from 'vue'

export default defineComponent({
    
    
  // 启用了类型推导
  props: {
    
    
    name: String,
    msg: {
    
     type: String, required: true }
  },
  data() {
    
    
    return {
    
    
      count: 1
    }
  },
  mounted() {
    
    
    this.name // 类型:string | undefined
    this.msg // 类型:string
    this.count // 类型:number
  }
})

3、当没有结合 <script setup> 使用组合式 API 时,defineComponent() 也支持对传递给 setup() 的 prop 的推导

import {
    
     defineComponent } from 'vue'

export default defineComponent({
    
    
  // 启用了类型推导
  props: {
    
    
    message: String
  },
  // 不需要声明 props 参数的类型
  setup(props) {
    
    
    props.message // 类型:string | undefined
  }
})

4、Vue3 提供 PropType 辅助工具用来标记更复杂的 prop 类型

import {
    
     defineComponent, PropType } from 'vue'

interface Book {
    
    
  title: string
  author: string
  year: number
}

export default defineComponent({
    
    
  props: {
    
    
    book: {
    
    
      // 提供相对 `Object` 更确定的类型
      type: Object as PropType<Book>,
      required: true
    },
    // 也可以标记函数
    callback: Function as PropType<(id: number) => void>
  },
  mounted() {
    
    
    this.book.title // string
    this.book.year // number

    // TS Error: argument of type 'string' is not
    // assignable to parameter of type 'number'
    this.callback?.('123')
  }
})

5、ref 会根据初始化时的值推导其类型,也可以手动传入类型。

<template>
  <h1 ref="title">
    {
   
   { msg }}
  </h1>
  <HelloWorld
    ref="hellowWorld"
    msg="Hello Vue 3 + TypeScript + Vite"
  />
</template>

<script lang="ts">
import {
      
       defineComponent, onMounted, ref } from 'vue'
import HelloWorld from '../components/HelloWorld.vue'

export default defineComponent({
      
      
  components: {
      
       HelloWorld },
  setup () {
      
      
    // 自动推断
    const msg = ref('Hello H1')

    // 指定 HTML 元素类型
    const title = ref<HTMLHeadElement>()

    // 指定实例类型
    const hellowWorld = ref<InstanceType<typeof HelloWorld>>()

    onMounted(() => {
      
      
      console.log(title.value?.innerHTML)
      console.log(hellowWorld.value?.$props.msg)
    })

    return {
      
      
      msg,
      title,
      hellowWorld
    }
  }
})
</script>

6、reactive 与 ref 一样,computed 计算属性也会自动推断类型。

7、原生 DOM 事件处理函数建议为 event 添加类型标注

<script setup lang="ts">
function handleChange(event: Event) {
      
      
  console.log((event.target as HTMLInputElement).value)
}
</script>

<template>
  <input type="text" @change="handleChange" />
</template>

Vue 3 中的 <script setup> 语法

Vue 3 支持三种写法编写组件的逻辑:

选项式 API

选项式 API 就是 Vue2 采用的风格

<template>
  <h1>{
   
   { msg }}</h1>
  <button
    type="button"
    @click="increment"
  >
    count is: {
   
   { count }}
  </button>
</template>

<script lang="ts">
import {
      
       defineComponent } from 'vue'

export default defineComponent({
      
      
  name: 'HelloWorld',
  props: {
      
      
    msg: {
      
      
      type: String,
      default: ''
    }
  },
  data () {
      
      
    return {
      
      
      count: 0
    }
  },
  mounted () {
      
      
    console.log('Mounted')
  },
  methods: {
      
      
    increment () {
      
      
      this.count++
    }
  }
})
</script>

组合式 API

组合式 API 可以将相关的逻辑封装到一起,以便提取为单独的模块

<script lang="ts">
import {
      
       defineComponent, ref } from 'vue'

export default defineComponent({
      
      
  name: 'HelloWorld',
  props: {
      
      
    msg: {
      
      
      type: String,
      default: ''
    }
  },
  setup () {
      
      
    const count = ref(0)
    const increment = () => {
      
      
      count.value++
    }

    return {
      
      
      count,
      increment
    }
  }
})
</script>

<script setup>

使用组合式 API 编写的业务增多后会发现代码大量集中在 setup() 函数中,一些简单的业务需要编写更多的代码,基于这个原因,Vue 3 后来又推出了 <script setup> 语法,它是组合式 API 的语法糖。

<!--
  1. 可以认为 <script setup> 中的代码都会包裹在 setup() 函数中
  2. 并且只能使用组合式 API
  3. 不需要 export 导出对象
  4. 声明的变量和 props 会自动暴露出来,不需要 return
-->
<script setup lang="ts">
import {
      
       ref } from 'vue'

/* 定义 props */
const props = defineProps({
      
      
  msg: {
      
      
    type: String,
    default: ''
  }
})
console.log(props.msg)
// 模板中也可以通过 props.msg 访问
// 建议:变量取名 props,模板中使用 props 访问,便于阅读

/* 定义对外发布的自定义事件 */
const emit = defineEmits(['increment'])

/* 定义 data 和 methods */
const count = ref(0)
const increment = () => {
      
      
  count.value++

  // 对外发布事件
  emit('increment')
}

</script>

导入的组件可以直接使用,会自动推断组件的名字:

<script setup>
import MyComponent from './MyComponent.vue'
import Foo from './Bar.vue'
</script>

<template>
  <MyComponent />
  <Foo />
</template>

支持顶层的 await

<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

可以与 <script> 一起使用,<script setup> 中的内容会转化成 export default {} 代码与 <script> 中的代码合并在一起。

<script>
// 代码会追加到 <script setup> 生成的 export default {} 代码前
runSideEffectOnce()

// 如果也导出了对象,则会和 <script setup> 生成的 export default {} 导出的对象合并在一起
export default {
      
      
  inheritAttrs: false,
  customOptions: {
      
      }
}
</script>

<script setup>
// ...
</script>

更多内容请参考官方文档。

<script setup> 中的编译宏

<script setup> 语法糖中使用的 defineProps() 和 defineEmits(),以及 defineExpose()withDefaults())不需要单独 import 就可以使用(且只能在 <script setup> 中使用)。

它们实际上被定义成了编译宏,可以理解为已经内置的 API。

单文件组件在编译 <script setup> 时会自动处理,将它们替换成对应的代码。

ESLint 默认不识别它们,可以配置 ESLint 的 globals,将它们作为全局 API 识别(参考前面的《编译宏和 defineProps、defineEmits、no-undef 规则警告》)。

配置转换 JSX 和 TSX

官方文档:渲染函数 & JSX | Vue.js (vuejs.org)

Vue3 可以通过官方插件 @vue/babel-plugin-jsx 提供 JSX 支持,详细使用请查看文档。

Vite 创建的项目默认不支持 JSX,需要单独配置。

Vite 官方提供 @vitejs/plugin-vue-jsx 插件配置 JSX 支持,内部实际上就是使用的 @vue/babel-plugin-jsx

# 安装插件
npm i -D @vitejs/plugin-vue-jsx

配置插件:

// vite.config.ts
import {
    
     defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint'
import vueJsx from '@vitejs/plugin-vue-jsx'

// https://vitejs.dev/config/
export default defineConfig({
    
    
  plugins: [
    vue(),
    eslintPlugin({
    
    
      /* 配置选项 */
      // 禁用 eslint 缓存
      cache: false
    }),
    vueJsx({
    
    
      /* 配置选项 */
    })
  ]
})

测试:

<!-- src\components\HelloWorld.vue -->
<template>
  <comp />
</template>

<script setup lang="tsx">
const comp = <h1>Hello World</h1>
</script>

// src\components\Foo.tsx
// 选项式 API
import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    msg: {
      type: String,
      required: true
    }
  },
  render () {
    return <h2>{this.msg}</h2>
  }
})

// src\components\Bar.tsx
// 组合式 API
import { defineComponent } from 'vue'

interface PropsType {
  msg: string
}

export default defineComponent({
  props: {
    msg: {
      type: String,
      required: true
    }
  },
  setup() {
    // 官方还没有未这种方式的 props 参数添加类型推断
    // 需要手动声明 TS 类型
    return (props: PropsType) => <h2>{props.msg}</h2>
  }
})

<!-- src\App.vue -->
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
import Foo from './components/Foo'
import Bar from './components/Bar'
</script>

<template>
  <HelloWorld />
  <Foo msg="Hello Foo" />
  <Bar msg="Hello Bar" />
</template>

Vite 中配置别名

Vite 创建的项目默认没有配置 @ 别名,需要手动配置。

// vite.config.ts
...
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
    
    
  ...
  resolve: {
    
    
    // https://vitejs.dev/config/#resolve-alias
    alias: [
      {
    
    
        find: '@',
        replacement: path.join(__dirname, 'src')
      }
    ]
  }
})

此时 import path from 'path' 可能报错:

在这里插入图片描述

这是因为 path 是 node.js 模块,遵循 CommonJS 规范,没有默认导出 export default,实际上 Babel 在转换的时候为这类模块自动添加了 module.exports.default,IDE 发出提示只是类型检查没有识别。

可以根据提示,配置 TypeScript 的 allowSyntheticDefaultImportstrue,是其识别这类模块导入操作。

注意 vite.config.ts 的 TypeScript 配置在 tsconfig.node.json 中:

// tsconfig.node.json
{
    
    
  "compilerOptions": {
    
    
    "composite": true,
    "module": "esnext",
    "moduleResolution": "node",
    // 允许处理默认 import
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}

PS:也可以通过安装 @types/node 来识别。

现在可以这样导入 .vue 文件:

// JS 中使用
import HelloWorld from '@/components/HelloWorld.vue'

// HTML 中使用
<img src="@/assets/logo.png">
    
// css 中使用
background: url(@/assets/logo.png);

但是导入 .tsx 文件,TypeScript 会报错:

// 找不到模块“@/components/Foo”或其相应的类型声明。
import Foo from '@/components/Foo' // 省略了 .tsx 后缀

// 导入路径不能以“.tsx”扩展名结束。考虑改为导入“@/components/Bar.js”。
import Bar from '@/components/Bar.tsx'

所以还需要配置 TypeScript 的导入映射(baseUrlpaths):

// tsconfig.json
{
    
    
  "compilerOptions": {
    
    
    ...

    // https://www.typescriptlang.org/tsconfig#paths
    // 必须定义 baseUrl
    "baseUrl": ".",
    // paths 设置相对于 baseUrl 的一系列映射路径
    "paths": {
    
    
      "@/*": ["src/*"]
    }
  },
  ...
}

至于使用 .tsx 显式扩展名报错的问题,不使用扩展名即可。

关于 Vite 中导入 .vue 组件必带后缀的问题

Vite 默认不会忽略 .vue 扩展名,虽然可以通过配置忽略,但官方不建议这样做,理由是**“会影响 IDE 和类型支持”**,参考 resolve.extensions

尤大在 Vite Issue #178 中也声明,导入 .vue 文件不能省略后缀本就是**“故意设计”**的,并且在下一个主要版本中,还将停止在 Vue CLI 中支持无扩展名的 Vue 导入。

在这里插入图片描述

所以建议导入 Vue 组件的时候还是带着 .vue 扩展名。

猜你喜欢

转载自blog.csdn.net/u012961419/article/details/124299803