XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • 3个提升Vue性能的写法
  • 重读Vue文档
  • moment.js给定时间获取自然月、周的时间轴
  • Vue虚拟dom与Diff算法
  • 深入响应式原理
  • Echart样例
  • 我的Vue指令库
  • 滚动到底部加载更多
  • 实现一键换肤
  • 手写Vue数据劫持
  • vue-router核心原理与手写实现
  • computed和watch
  • vue数组为什么不是响应式
  • v-for为什么不能用index做key
  • webpack loader和plugin
  • keep-alive组件原理
  • vue插槽进化
  • Vue多层嵌套组件
  • vue生命周期hook
  • vue监听dom元素的resize事件
  • 前端打包需要gzip压缩吗
  • 实现水波浪进度球
  • Vue
XingYun
2022-07-25
目录

Vue3-Doc-Typescript

# 1. TypeScript with Composition API

# 1.1 Typing Component Props

Using <script setup>
When using <script setup>, the defineProps() macro supports inferring the props types based on its argument:

<script setup lang="ts">
  const props = defineProps({
    foo: { type: String, required: true },
    bar: Number
  })

  props.foo // string
  props.bar // number | undefined
</script>
1
2
3
4
5
6
7
8
9

This is called "runtime declaration", because the argument passed to defineProps() will be used as the runtime props option.

However, it is usually more straightforward to define props with pure types via a generic type argument:

<script setup lang="ts">
  const props = defineProps<{
    foo: string
    bar?: number
  }>()
</script>
1
2
3
4
5
6

This is called "type-based declaration". The compiler will try to do its best to infer the equivalent runtime options based on the type argument. In this case, our second example compiles into the exact same runtime options as the first example.

You can use either type-based declaration OR runtime declaration, but you cannot use both at the same time.

We can also move the props types into a separate interface:

<script setup lang="ts">
  interface Props {
    foo: string
    bar?: number
  }

  const props = defineProps<Props>()
</script>
1
2
3
4
5
6
7
8

# 1.2 Typing Component Emits

In <script setup>, the emit function can also be typed using either runtime declaration OR type declaration:

<script setup lang="ts">
  // runtime
  const emit = defineEmits(['change', 'update'])

  // type-based
  const emit = defineEmits<{
    (e: 'change', id: number): void
    (e: 'update', value: string): void
  }>()
</script>
1
2
3
4
5
6
7
8
9
10

The type argument should be a type literal with Call Signatures. The type literal will be used as the type of the returned emit function. As we can see, the type declaration gives us much finer-grained control over the type constraints of emitted events.

# 1.3 Typing ref()

Refs infer the type from the initial value:

import { ref } from 'vue'

// inferred type: Ref<number>
const year = ref(2020)

// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'
1
2
3
4
5
6
7

Sometimes we may need to specify complex types for a ref's inner value. We can do that by using the Ref type:

import { ref } from 'vue'
import type { Ref } from 'vue'

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

year.value = 2020 // ok!
1
2
3
4
5
6

Or, by passing a generic argument when calling ref() to override the default inference:

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

year.value = 2020 // ok!
1
2
3
4

If you specify a generic type argument but omit the initial value, the resulting type will be a union type that includes undefined:

// inferred type: Ref<number | undefined>
const n = ref<number>()
1
2

# 1.3 Typing reactive()

reactive() also implicitly infers the type from its argument:

import { reactive } from 'vue'

// inferred type: { title: string }
const book = reactive({ title: 'Vue 3 Guide' })
1
2
3
4

To explicitly type a reactive property, we can use interfaces:

import { reactive } from 'vue'

interface Book {
title: string
year?: number
}

const book: Book = reactive({ title: 'Vue 3 Guide' })
1
2
3
4
5
6
7
8

It's not recommended to use the generic argument of reactive() because the returned type, which handles nested ref unwrapping, is different from the generic argument type.

# 1.4 Typing computed()

computed() infers its type based on the getter's return value:

import { ref, computed } from 'vue'

const count = ref(0)

// inferred type: ComputedRef<number>
const double = computed(() => count.value * 2)

// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')
1
2
3
4
5
6
7
8
9

You can also specify an explicit type via a generic argument:

const double =
  computed <
  number >
  (() => {
    // type error if this doesn't return a number
  })
1
2
3
4
5
6

# 1.5 Typing Event Handlers

When dealing with native DOM events, it might be useful to type the argument we pass to the handler correctly. Let's take a look at this example:

<script setup lang="ts">
  function handleChange(event) {
    // `event` implicitly has `any` type
    console.log(event.target.value)
  }
</script>

<template>
  <input type="text" @change="handleChange" />
</template>
1
2
3
4
5
6
7
8
9
10

Without type annotation, the event argument will implicitly have a type of any. This will also result in a TS error if "strict": true or "noImplicitAny": true are used in tsconfig.json. It is therefore recommended to explicitly annotate the argument of event handlers. In addition, you may need to explicitly cast properties on event:

function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
1
2
3

# 1.6 Typing Template Refs

Template refs should be created with an explicit generic type argument and an initial value of null:

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

  const el = ref<HTMLInputElement | null>(null)

  onMounted(() => {
    el.value?.focus()
  })
</script>

<template>
  <input ref="el" />
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13

Note that for strict type safety, it is necessary to use optional chaining or type guards when accessing el.value. This is because the initial ref value is null until the component is mounted, and it can also be set to null if the referenced element is unmounted by v-if.

# 1.7 Typing Component Template Refs

Sometimes you might need to annotate a template ref for a child component in order to call its public method. For example, we have a MyModal child component with a method that opens the modal:

<!-- MyModal.vue -->
<script setup lang="ts">
  import { ref } from 'vue'

  const isContentShown = ref(false)
  const open = () => (isContentShown.value = true)

  defineExpose({
    open
  })
</script>
1
2
3
4
5
6
7
8
9
10
11

In order to get the instance type of MyModal, we need to first get its type via typeof, then use TypeScript's built-in InstanceType utility to extract its instance type:

<!-- App.vue -->
<script setup lang="ts">
  import MyModal from './MyModal.vue'

  const modal = ref<InstanceType<typeof MyModal> | null>(null)

  const openModal = () => {
    modal.value?.open()
  }
</script>
1
2
3
4
5
6
7
8
9
10

Note if you want to use this technique in TypeScript files instead of Vue SFCs, you need to enable Volar's Takeover Mode.

上次更新: 2023/04/05, 09:41:10
最近更新
01
JavaScript-test
07-20
02
二维码的原理
07-20
03
利用ChatGPT优化代码
07-20
更多文章>
Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式