Practical Guide to Vue 3.2 Setup Sugar, <script setup> with TypeScript, and New Vue APIs
This article walks through preparing a Vue 3 + TypeScript project, explains the concept of setup sugar and its benefits, demonstrates the new compiler‑macro APIs such as defineProps, defineEmits and defineExpose, introduces useful hook APIs, covers Vue 3.2 additions like v‑memo and style‑variable binding, and provides tips for i18n, debugging and common pitfalls.
Vue 3.2 introduces several new features, most notably the <script setup> syntax combined with TypeScript and the Volar extension, which the author calls "truly fragrant". The article shares a practical workflow and common pitfalls discovered while using these features.
Preparation
Create a Vue 3 + TypeScript project with vue-cli .
Disable Vetur in VS Code and install Volar from the marketplace.
Note that Vue 3 does not support IE (including IE 11) because it relies on the Proxy API.
What is Setup Sugar?
Setup sugar adds a special <script> block with the setup attribute, automatically exposing all top‑level bindings to the template.
<template>
<Foo :count="count" @click="inc" />
</template>
<script>
import Foo from './Foo.vue'
import { ref } from 'vue'
export default {
setup() {
const count = ref(1)
const inc = () => { count.value++ }
return { Foo, count, inc }
}
}
</script>The same component can be written more concisely with <script setup> :
<template>
<Foo :count="count" @click="inc" />
</template>
<script setup>
import Foo from './Foo.vue'
import { ref } from 'vue'
const count = ref(0)
const inc = () => { count.value++ }
</script>Composition API vs Options API
The composition API reduces logical coupling by allowing related code (data, methods, watchers, etc.) to be placed together, and enables reusable hooks. However, without disciplined code partitioning, it can become harder to read.
Self‑partition code into sections: imports, reactive state, lifecycle/watch, methods, and exports.
Separate UI into views and reusable components folders.
Extract logic into custom hooks even if they are not reused.
New Compiler‑Macro APIs (define…)
APIs that start with define are compiler macros usable only inside <script setup> . They are not imported and are stripped during compilation.
defineProps : declares component props. Example: defineProps({ name: { type: String, required: false, default: 'Petter' }, userInfo: Object, tags: Array, }) With TypeScript: const props = defineProps<{ foo: string; bar?: number }>()
withDefaults : provides default values for props when using TypeScript declarations. withDefaults(defineProps<{ size?: number; labels?: string[] }>(), { size: 3, labels: () => ['default label'], })
defineEmits : declares emitted events. const emit = defineEmits(['change', 'delete']) emit('change') // With TS: const emit = defineEmits<{ (e: 'change', id: number): void; (e: 'update', value: string): void }>()
defineExpose : manually expose variables or methods from the setup context. const a = 1 const b = ref(2) defineExpose({ a, b })
Hook APIs
useAttrs : returns all component attributes, including class, style, and listeners. const attrs = useAttrs() <component v-bind="attrs" />
useCSSModule , useSlots , useCssVars , useTransitionState , useSSRContext : various utilities for CSS modules, slot access, CSS variable binding, transition state, and server‑side rendering.
Vue 3.2 New Features
v-memo : skips rendering of a subtree when the supplied array values do not change. <div v-memo="[valueA, valueB]"> ... </div>
Experimental v-bind inside <style> to bind component state to CSS variables. .text { color: v-bind(color); font-size: v-bind('font.size'); }
Internationalization (i18n) in Vue 3
Install vue-i18n (e.g., ^9.1.7).
Create a locales folder with JSON files for each language.
Initialize i18n in a separate module: import en from "./en.json" import zhHans from "./zh-cn.json" import zhHant from "./zh-hk.json" import { createI18n } from "vue-i18n" import { judgeLang } from "@/utils/url" const messages = { en, "zh-hans": zhHans, "zh-hant": zhHant } const i18n = createI18n({ locale: judgeLang(), messages }) export default i18n
Use the i18n instance in main.ts and provide a custom hook for injection. export function useProvideI18n(): void { const { t } = useI18n() provide("t", t) } export function useInjectI18n(): (text: string) => string { const t = inject("t") as (text: string) => string return t }
Limitations and Debugging
Component options (e.g., name , inheritAttrs ) cannot be set inside <script setup> ; a normal <script> block is required.
TypeScript and ESLint rules such as @typescript-eslint/no-unused-vars may need to be disabled for setup sugar .
For mobile debugging, vConsole may cause stack overflow; eruda is an alternative, loaded dynamically: export default function debugInit(): void { const script = document.createElement("script") script.type = "text/javascript" script.src = "//cdn.bootcdn.net/ajax/libs/eruda/2.3.3/eruda.js" document.getElementsByTagName("head")[0].appendChild(script) script.onload = function () { window.eruda.init() } }
Using components without a root element is now allowed, but some edge cases (e.g., transition ) may still cause issues.
References
Links to Vue CLI guide, Volar marketplace page, Vue RFC discussions, vue‑i18n documentation, and other RFCs are provided for further reading.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.