Vue3官网-工具(十九)TypeScript 支持(Vue CLI)

Vue3官网-工具(十九)TypeScript 支持(Vue CLI)

文章目录

  • Vue3官网-工具(十九)TypeScript 支持(Vue CLI)
    • 1. TypeScript 支持
      • NPM 包中的官方声明
      • 推荐配置
      • Webpack 配置
      • 开发工具
        • 项目创建
        • 编辑器支持
      • 定义 Vue 组件
      • 与 Options API 一起使用
        • 为 `globalProperties` 扩充类型
        • 注解返回类型
        • 注解 Props
        • 注解 emit
      • 与组合式 API 一起使用
        • 类型声明 `refs`
        • 为模板引用定义类型
        • 类型声明 `reactive`
        • 类型声明 `computed`
        • 为事件处理器添加类型
    • 2. 生产环境部署
      • 开启生产环境模式
        • 不使用构建工具
        • 使用构建工具
      • 预编译模板
      • 提取组件CSS
      • 跟踪运行时错误

总结:

  • Vue CLI
    • https://cli.vuejs.org/zh/guide/

1. TypeScript 支持

Vue CLI 提供内置的 TypeScript 工具支持。

NPM 包中的官方声明

随着应用的增长,静态类型系统可以帮助防止许多潜在的运行时错误,这就是为什么 Vue 3 是用 TypeScript 编写的。这意味着在 Vue 中使用 TypeScript 不需要任何其他工具——它具有一等公民支持。

推荐配置

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    // 这样就可以对 `this` 上的数据属性进行更严格的推断
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node"
  }
}

请注意,必须包含 strict: true (或至少包含 noImplicitThis: true,它是 strict 标志的一部分) 才能在组件方法中利用 this 的类型检查,否则它总是被视为 any 类型。

参见 TypeScript 编译选项文档查看更多细节。

Webpack 配置

如果你使用自定义 Webpack 配置,需要配置 ’ ts-loader ’ 来解析 vue 文件里的 <script lang="ts"> 代码块:

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
        },
        exclude: /node_modules/,
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      }
      ...

开发工具

项目创建

Vue CLI 可以生成使用 TypeScript 的新项目,开始:

# 1. Install Vue CLI, 如果尚未安装
npm install --global @vue/cli@next

# 2. 创建一个新项目, 选择 "Manually select features" 选项
vue create my-project-name

# 3. 如果已经有一个不存在TypeScript的 Vue CLI项目,请添加适当的 Vue CLI插件:
vue add typescript

请确保组件的 script 部分已将语言设置为 TypeScript:

<script lang="ts">
  ...
</script>

或者,如果你想将 TypeScript 与 JSX render 函数结合起来:

<script lang="tsx">
  ...
</script>

编辑器支持

对于使用 TypeScript 开发 Vue 应用程序,我们强烈建议使用 Visual Studio Code,它为 TypeScript 提供了很好的开箱即用支持。如果你使用的是单文件组件 (SFCs),那么可以使用很棒的 Volar extension,它在 SFCs 中提供了 TypeScript 推理和许多其他优秀的特性。

WebStorm 还为 TypeScript 和 Vue 提供现成的支持。

定义 Vue 组件

要让 TypeScript 正确推断 Vue 组件选项中的类型,需要使用 defineComponent 全局方法定义组件:

import { defineComponent } from 'vue'

const Component = defineComponent({
  // 已启用类型推断
})

如果你使用的是单文件组件,则通常会被写成:

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  // 已启用类型推断
})
</script>

与 Options API 一起使用

TypeScript 应该能够在不显式定义类型的情况下推断大多数类型。例如,对于拥有一个数字类型的 count property 的组件来说,如果你试图对其调用字符串独有的方法,会出现错误:

const Component = defineComponent({
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    const result = this.count.split('') // => Property 'split' does not exist on type 'number'
  }
})

如果你有一个复杂的类型或接口,你可以使用 type assertion 对其进行指明:

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

const Component = defineComponent({
  data() {
    return {
      book: {
        title: 'Vue 3 Guide',
        author: 'Vue Team',
        year: 2020
      } as Book
    }
  }
})

globalProperties 扩充类型

Vue 3 提供了一个 globalProperties 对象,用来添加可以被任意组件实例访问的全局 property。例如一个插件想要注入一个共享全局对象或函数。

// 用户定义
import axios from 'axios'
const app = Vue.createApp({})
app.config.globalProperties.$http = axios
// 验证数据的插件
export default {
  install(app, options) {
    app.config.globalProperties.$validate = (data: object, rule: object) => {
      // 检查对象是否合规
    }
  }
}

为了告诉 TypeScript 这些新 property,我们可以使用模块扩充 (module augmentation)。

在上述示例中,我们可以添加以下类型声明:

import axios from 'axios'
declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $http: typeof axios
    $validate: (data: object, rule: object) => boolean
  }
}

我们可以把这些类型声明放在同一个文件里,或一个项目级别的 *.d.ts 文件 (例如在 TypeScript 会自动加载的 src/typings 文件夹中)。对于库/插件作者来说,这个文件应该被定义在 package.jsontypes property 里。

确认声明文件是一个 TypeScript 模块

为了利用好模块扩充,你需要确认你的文件中至少有一个顶级的 importexport,哪怕只是一个 export {}

在 TypeScript 中,任何包含一个顶级 importexport 的文件都被视为一个“模块”。如果类型声明在模块之外,该声明会覆盖而不是扩充原本的类型。

关于 ComponentCustomProperties 类型的更多信息,请参阅其在 @vue/runtime-core 中的定义及其 TypeScript 测试用例学习更多。

注解返回类型

由于 Vue 声明文件的循环特性,TypeScript 可能难以推断 computed 的类型。因此,你可能需要注解计算属性的返回类型。

import { defineComponent } from 'vue'

const Component = defineComponent({
  data() {
    return {
      message: 'Hello!'
    }
  },
  computed: {
    // 需要注解
    greeting(): string {
      return this.message + '!'
    },

    // 在使用 setter 进行计算时,需要对 getter 进行注解
    greetingUppercased: {
      get(): string {
        return this.greeting.toUpperCase()
      },
      set(newValue: string) {
        this.message = newValue.toUpperCase()
      }
    }
  }
})

注解 Props

Vue 对定义了 type 的 prop 执行运行时验证。要将这些类型提供给 TypeScript,我们需要使用 PropType 指明构造函数:

import { defineComponent, PropType } from 'vue'

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

const Component = defineComponent({
  props: {
    name: String,
    id: [Number, String],
    success: { type: String },
    callback: {
      type: Function as PropType<() => void>
    },
    book: {
      type: Object as PropType<Book>,
      required: true
    },
    metadata: {
      type: null // metadata 的类型是 any
    }
  }
})

WARNING

由于 TypeScript 中的设计限制,当它涉及到为了对函数表达式进行类型推理,你必须注意对象和数组的 validatordefault 值:

import { defineComponent, PropType } from 'vue'

interface Book {
  title: string
  year?: number
}

const Component = defineComponent({
  props: {
    bookA: {
      type: Object as PropType<Book>,
      // 请务必使用箭头函数
      default: () => ({
        title: 'Arrow Function Expression'
      }),
      validator: (book: Book) => !!book.title
    },
    bookB: {
      type: Object as PropType<Book>,
      // 或者提供一个明确的 this 参数
      default(this: void) {
        return {
          title: 'Function Expression'
        }
      },
      validator(this: void, book: Book) {
        return !!book.title
      }
    }
  }
})

注解 emit

我们可以为触发的事件注解一个有效载荷。另外,所有未声明的触发事件在调用时都会抛出一个类型错误。

const Component = defineComponent({
  emits: {
    addBook(payload: { bookName: string }) {
      // perform runtime 验证
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // 类型错误!
      })
      this.$emit('non-declared-event') // 类型错误!
    }
  }
})

与组合式 API 一起使用

setup() 函数中,不需要将类型传递给 props 参数,因为它将从 props 组件选项推断类型。

import { defineComponent } from 'vue'

const Component = defineComponent({
  props: {
    message: {
      type: String,
      required: true
    }
  },

  setup(props) {
    const result = props.message.split('') // 正确, 'message' 被声明为字符串
    const filtered = props.message.filter(p => p.value) // 将引发错误: Property 'filter' does not exist on type 'string'
  }
})

类型声明 refs

Refs 根据初始值推断类型:

import { defineComponent, ref } from 'vue'

const Component = defineComponent({
  setup() {
    const year = ref(2020)

    const result = year.value.split('') // => Property 'split' does not exist on type 'number'
  }
})

有时我们可能需要为 ref 的内部值指定复杂类型。我们可以在调用 ref 重写默认推理时简单地传递一个泛型参数:

const year = ref<string | number>('2020') // year's type: Ref<string | number>

year.value = 2020 // ok!

TIP

如果泛型的类型未知,建议将 ref 转换为 Ref<T>

为模板引用定义类型

有时你可能需要为一个子组件标注一个模板引用,以调用其公共方法。例如我们有一个 MyModal 子组件,它有一个打开模态的方法:

import { defineComponent, ref } from 'vue'
const MyModal = defineComponent({
  setup() {
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)
    return {
      isContentShown,
      open
    }
  }
})

我们希望从其父组件的一个模板引用调用这个方法:

import { defineComponent, ref } from 'vue'
const MyModal = defineComponent({
  setup() {
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)
    return {
      isContentShown,
      open
    }
  }
})
const app = defineComponent({
  components: {
    MyModal
  },
  template: `
    <button @click="openModal">Open from parent</button>
    <my-modal ref="modal" />
  `,
  setup() {
    const modal = ref()
    const openModal = () => {
      modal.value.open()
    }
    return { modal, openModal }
  }
})

它可以工作,但是没有关于 MyModal 及其可用方法的类型信息。为了解决这个问题,你应该在创建引用时使用 InstanceType

setup() {
  const modal = ref<InstanceType<typeof MyModal>>()
  const openModal = () => {
    modal.value?.open()
  }
  return { modal, openModal }
}

请注意你还需要使用可选链操作符或其它方式来确认 modal.value 不是 undefined。

类型声明 reactive

当声明类型 reactive property,我们可以使用接口:

import { defineComponent, reactive } from 'vue'

interface Book {
  title: string
  year?: number
}

export default defineComponent({
  name: 'HelloWorld',
  setup() {
    const book = reactive<Book>({ title: 'Vue 3 Guide' })
    // or
    const book: Book = reactive({ title: 'Vue 3 Guide' })
    // or
    const book = reactive({ title: 'Vue 3 Guide' }) as Book
  }
})

类型声明 computed

计算值将根据返回值自动推断类型

import { defineComponent, ref, computed } from 'vue'

export default defineComponent({
  name: 'CounterButton',
  setup() {
    let count = ref(0)

    // 只读
    const doubleCount = computed(() => count.value * 2)

    const result = doubleCount.value.split('') // => Property 'split' does not exist on type 'number'
  }
})

为事件处理器添加类型

在处理原生 DOM 事件的时候,正确地为处理函数的参数添加类型或许会是有用的。让我们看这个例子:

<template>
  <input type="text" @change="handleChange" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  setup() {
    // `evt` 将会是 `any` 类型
    const handleChange = evt => {
      console.log(evt.target.value) // 此处 TS 将抛出异常
    }
    return { handleChange }
  }
})
</script>

如你所见,在没有为 evt 参数正确地声明类型的情况下,当我们尝试获取 <input> 元素的值时,TypeScript 将抛出异常。解决方案是将事件的目标转换为正确的类型:

const handleChange = (evt: Event) => {
  console.log((evt.target as HTMLInputElement).value)
}

2. 生产环境部署

INFO

如果你使用 Vue CLI,下面的大多数提示都是默认启用的。此部分仅当你使用自定义构建设置时才相关。

开启生产环境模式

在开发期间,Vue 提供了许多警告,以帮助你处理常见的错误和隐患。但是,这些警告字符串在生产环境中会变得无意义,并且会增大应用程序的负担。此外,有一些警告检查还会产生些许的运行时开销,在生产模式下可以避免这些开销。

不使用构建工具

如果你正在使用完整的构建版本,即直接通过脚本标签引入 Vue,而不使用构建工具,那么请确保在生产环境中使用压缩版。这可以在安装指南中找到。

使用构建工具

当使用 Webpack 或 Browserify 这样的构建工具时,生产环境模式将由 Vue 的源代码中的 process.env.NODE_ENV 决定,默认为开发模式。这两种构建工具都提供了重写这个变量以启用 Vue 的生产模式的方法,并且在构建过程中警告将被压缩工具删除。Vue CLI 已经为你预先配置了这个,不过了解它的工作原理会更好:

Webpack

在 Webpack 4+,你可以使用 mode 选项:

module.exports = {
  mode: 'production'
}

Browserify

  • 将当前的环境变量 NODE_ENV 设置为 "production" 作为运行的打包命令。它告诉 vueify 避免引入热重载和开发相关的代码。

  • 将一个全局的 envify 转换应用到你的包中。这使得压缩工具可以删除包裹在环境变量条件块中的Vue源代码中的所有警告。例如:

    NODE_ENV=production browserify -g envify -e main.js | uglifyjs -c -m > build.js
    

    1

  • 或者,利用 Gulp 使用 envify:

    // Use the envify custom module to specify environment variables
    const envify = require('envify/custom')
    
    browserify(browserifyOptions)
      .transform(vueify)
      .transform(
        // Required in order to process node_modules files
        { global: true },
        envify({ NODE_ENV: 'production' })
      )
      .bundle()
    
  • 或者,利用 Grunt 和 grunt-browserify 使用 envify:

    // Use the envify custom module to specify environment variables
    const envify = require('envify/custom')
    
    browserify: {
      dist: {
        options: {
          // Function to deviate from grunt-browserify's default order
          configure: (b) =>
            b
              .transform('vueify')
              .transform(
                // Required in order to process node_modules files
                { global: true },
                envify({ NODE_ENV: 'production' })
              )
              .bundle()
        }
      }
    }
    

Rollup

使用 @rollup/plugin-replace:

const replace = require('@rollup/plugin-replace')

rollup({
  // ...
  plugins: [
    replace({
      'process.env.NODE_ENV': JSON.stringify( 'production' )
    })
  ]
}).then(...)

预编译模板

当使用 DOM 内模板或 JavaScript 内模板字符串时,将动态地执行从模板到渲染函数的编译。在大多数情况下,这已经足够快了,但是如果应用程序对性能敏感,最好避免这样做。

预编译模板最简单的方法是使用单文件组件——相关的构建设置自动为你执行预编译,所以构建代码包含已经编译的渲染函数,而不是原始的模板字符串。

如果你正在使用 Webpack,并且更喜欢将 JavaScript 和模板文件分开,你可以使用 vue-template-loader,它还可以在构建步骤中将模板文件转换为 JavaScript 渲染函数。

提取组件CSS

当使用单文件组件时,组件内部的 CSS 会通过 JavaScript 以 <style> 标签的形式被动态注入。这有一个小的运行时成本,如果你使用服务器端渲染,它将导致“无样式内容的闪现” 。将所有组件的 CSS 提取到同一个文件中可以避免这些问题,还可以更好地压缩和缓存 CSS。

参考各自的构建工具文档,看看它是如何做的:

  • Webpack + vue-loader (vue-cli webpack 模板已经预先配置了这个)
  • Browserify + vueify
  • Rollup + rollup-plugin-vue

跟踪运行时错误

如果在组件渲染期间发生运行时错误,它将被传递到全局的 app.config.errorHandler 配置函数,如果它已经被设置。将这个钩子与错误跟踪服务如 Sentry 一起使用可能是一个好主意,它为 Vue 提供了一个官方集成。

热门文章

暂无图片
编程学习 ·

C语言二分查找详解

二分查找是一种知名度很高的查找算法&#xff0c;在对有序数列进行查找时效率远高于传统的顺序查找。 下面这张动图对比了二者的效率差距。 二分查找的基本思想就是通过把目标数和当前数列的中间数进行比较&#xff0c;从而确定目标数是在中间数的左边还是右边&#xff0c;将查…
暂无图片
编程学习 ·

GMX 命令分类列表

建模和计算操作命令&#xff1a; 1.1 . 创建拓扑与坐标文件 gmx editconf - 编辑模拟盒子以及写入子组(subgroups) gmx protonate - 结构质子化 gmx x2top - 根据坐标生成原始拓扑文件 gmx solvate - 体系溶剂化 gmx insert-molecules - 将分子插入已有空位 gmx genconf - 增加…
暂无图片
编程学习 ·

一文高效回顾研究生课程《数值分析》重点

数值分析这门课的本质就是用离散的已知点去估计整体&#xff0c;就是由黑盒子产生的结果去估计这个黑盒子。在数学里这个黑盒子就是一个函数嘛&#xff0c;这门课会介绍许多方法去利用离散点最大化地逼近这个函数&#xff0c;甚至它的导数、积分&#xff0c;甚至微分方程的解。…
暂无图片
编程学习 ·

在职阿里5年,一个28岁女软测工程师的心声

简单的先说一下&#xff0c;坐标杭州&#xff0c;14届本科毕业&#xff0c;算上年前在阿里巴巴的面试&#xff0c;一共有面试了有6家公司&#xff08;因为不想请假&#xff0c;因此只是每个晚上去其他公司面试&#xff0c;所以面试的公司比较少&#xff09; ​ 编辑切换为居中…
暂无图片
编程学习 ·

字符串左旋c语言

目录 题目&#xff1a; 解题思路&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 总代码&#xff1a; 题目&#xff1a; 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符…
暂无图片
编程学习 ·

设计模式--观察者模式笔记

模式的定义与特点 观察者&#xff08;Observer&#xff09;模式的定义&#xff1a;指多个对象间存在一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式&#xf…
暂无图片
编程学习 ·

睡觉突然身体动不了,什么是睡眠痽痪症

很多朋友可能有这样的体验&#xff0c;睡觉过程中突然意识清醒&#xff0c;身体却动弹不了。这时候感觉非常恐怖&#xff0c;希望旁边有一个人推自己一下。阳光以前也经常会碰到这样的情况&#xff0c;一年有一百多次&#xff0c;那时候很害怕晚上到来&#xff0c;睡觉了就会出…
暂无图片
编程学习 ·

深入理解C++智能指针——浅析MSVC源码

文章目录unique_ptrshared_ptr 与 weak_ptrstd::bad_weak_ptr 异常std::enable_shared_from_thisunique_ptr unique_ptr 是一个只移型别&#xff08;move-only type&#xff0c;只移型别还有std::mutex等&#xff09;。 结合一下工厂模式&#xff0c;看看其基本用法&#xff…
暂无图片
编程学习 ·

@TableField(exist = false)

TableField(exist false) //申明此字段不在数据库存在&#xff0c;但代码中需要用到它&#xff0c;通知Mybatis-plus在做写库操作是忽略它。,.
暂无图片
编程学习 ·

Java Web day15

第十二章文件上传和下载 一、如何实现文件上传 要实现Web开发中的文件上传功能&#xff0c;通常需要完成两步操作&#xff1a;一.是在Web页面中添加上传输入项&#xff1b;二是在Servlet中读取上传文件的数据&#xff0c;并保存到本地硬盘中。 需要使用一个Apache组织提供一个…
暂无图片
编程学习 ·

【51nod 2478】【单调栈】【前缀和】小b接水

小b接水题目解题思路Code51nod 2478 小b接水 题目 输入样例 12 0 1 0 2 1 0 1 3 2 1 2 1输出样例 6解题思路 可以发现最后能拦住水的都是向两边递减高度&#xff08;&#xff1f;&#xff09; 不管两个高积木之间的的积木是怎样乱七八糟的高度&#xff0c;最后能用来装水的…
暂无图片
编程学习 ·

花了大半天写了一个UVC扩展单元调试工具

基于DIRECTSHOW 实现的&#xff0c;用的是MFC VS2019. 详见&#xff1a;http://www.usbzh.com/article/detail-761.html 获取方法 加QQ群:952873936&#xff0c;然后在群文件\USB调试工具&测试软件\UVCXU-V1.0(UVC扩展单元调试工具-USB中文网官方版).exe USB中文网 USB中文…
暂无图片
编程学习 ·

贪心(一):区间问题、Huffman树

区间问题 例题一&#xff1a;区间选点 给定 N 个闭区间 [ai,bi]请你在数轴上选择尽量少的点&#xff0c;使得每个区间内至少包含一个选出的点。 输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 输入格式 第一行包含整数 N&#xff0c;表示区间数。 接下来 …
暂无图片
编程学习 ·

C语言练习实例——费氏数列

目录 题目 解法 输出结果 题目 Fibonacci为1200年代的欧洲数学家&#xff0c;在他的着作中曾经提到&#xff1a;「若有一只免子每个月生一只小免子&#xff0c;一个月后小免子也开始生产。起初只有一只免子&#xff0c;一个月后就有两只免子&#xff0c;二个月后有三只免子…
暂无图片
编程学习 ·

Android开发(2): Android 资源

个人笔记整理 Android 资源 Android中的资源&#xff0c;一般分为两类&#xff1a; 系统内置资源&#xff1a;Android SDK中所提供的已经定义好的资源&#xff0c;用户可以直接拿来使用。 用户自定义资源&#xff1a;用户自己定义或引入的&#xff0c;只适用于当前应用的资源…
暂无图片
编程学习 ·

零基础如何在短时间内拿到算法offer

​算法工程师是利用算法处理事物的职业 算法&#xff08;Algorithm&#xff09;是一系列解决问题的清晰指令&#xff0c;也就是说&#xff0c;能够对一定规范的输入&#xff0c;在有限时间内获得所要求的输出。 如果一个算法有缺陷&#xff0c;或不适合于某个问题&#xff0c;执…
暂无图片
编程学习 ·

人工智能:知识图谱实战总结

人工智能python&#xff0c;NLP&#xff0c;知识图谱&#xff0c;机器学习&#xff0c;深度学习人工智能&#xff1a;知识图谱实战前言一、实体建模工具Protegepython&#xff0c;NLP&#xff0c;知识图谱&#xff0c;机器学习&#xff0c;深度学习 人工智能&#xff1a;知识图…
暂无图片
编程学习 ·

【无标题】

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…