news 2026/5/23 21:11:59

23. 与 Vue 集成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
23. 与 Vue 集成

23. 与 Vue 集成

1. 概述

Vue 3 使用 TypeScript 重写,提供了出色的 TypeScript 支持。Vue 的组合式 API(Composition API)与 TypeScript 配合使用,可以提供完整的类型推断和类型安全。

┌─────────────────────────────────────────────────────────────┐ │ TypeScript + Vue 3 集成 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 组件类型 │ │ ├── defineComponent:组件定义函数 │ │ ├── defineProps:Props 定义 │ │ ├── defineEmits:事件定义 │ │ └── defineExpose:暴露属性 │ │ │ │ 响应式类型 │ │ ├── ref:响应式引用 │ │ ├── reactive:响应式对象 │ │ ├── computed:计算属性 │ │ └── watch:侦听器 │ │ │ │ 组合式 API 类型 │ │ ├── provide / inject:依赖注入 │ │ ├── template refs:模板引用 │ │ ├── lifecycle hooks:生命周期钩子 │ │ └── composables:组合函数 │ │ │ └─────────────────────────────────────────────────────────────┘

2. 项目初始化

2.1 创建 Vue + TypeScript 项目

# 使用 Vite(推荐)npmcreate vite@latest my-vue-app ----templatevue-ts# 使用 Vue CLInpminstall-g@vue/cli vue create my-vue-app# 选择 TypeScript# 使用 create-vue(官方推荐)npmcreate vue@latest# 选择 TypeScript 支持

2.2 tsconfig.json 配置

{"compilerOptions":{"target":"ES2020","useDefineForClassFields":true,"module":"ESNext","lib":["ES2020","DOM","DOM.Iterable"],"skipLibCheck":true,"moduleResolution":"bundler","allowImportingTsExtensions":true,"resolveJsonModule":true,"isolatedModules":true,"noEmit":true,"jsx":"preserve","strict":true,"noUnusedLocals":true,"noUnusedParameters":true,"noFallthroughCasesInSwitch":true},"include":["src/**/*.ts","src/**/*.tsx","src/**/*.vue"],"references":[{"path":"./tsconfig.node.json"}]}

2.3 环境声明文件

// env.d.ts/// <reference types="vite/client" />declaremodule'*.vue'{importtype{DefineComponent}from'vue';constcomponent:DefineComponent<{},{},any>;exportdefaultcomponent;}

3. 组件定义

3.1 使用 defineComponent

<script lang="ts"> import { defineComponent } from 'vue'; // 定义 Props 类型 interface Props { title: string; count?: number; } // 定义组件 export default defineComponent({ name: 'MyComponent', props: { title: { type: String, required: true }, count: { type: Number, default: 0 } }, setup(props: Props) { // props 自动推断类型 console.log(props.title); return () => ( <div> <h1>{props.title}</h1> <p>Count: {props.count}</p> </div> ); } }); </script>

3.2 组合式 API 语法(推荐)

<script setup lang="ts"> import { ref, computed } from 'vue'; // 定义 Props interface Props { title: string; initialCount?: number; } const props = withDefaults(defineProps<Props>(), { initialCount: 0 }); // 定义 Emits interface Emits { (e: 'update:count', value: number): void; (e: 'submit', data: { name: string; age: number }): void; } const emit = defineEmits<Emits>(); // 响应式状态 const count = ref(props.initialCount); const name = ref<string>(''); // 计算属性 const doubleCount = computed(() => count.value * 2); // 方法 const increment = () => { count.value++; emit('update:count', count.value); }; const handleSubmit = () => { emit('submit', { name: name.value, age: count.value }); }; // 暴露给父组件的方法 defineExpose({ reset: () => { count.value = props.initialCount; } }); </script> <template> <div> <h1>{{ props.title }}</h1> <p>Count: {{ count }}</p> <p>Double: {{ doubleCount }}</p> <input v-model="name" placeholder="Enter name" /> <button @click="increment">Increment</button> <button @click="handleSubmit">Submit</button> </div> </template>

4. Props 类型定义

4.1 基础 Props

<script setup lang="ts"> // 基础类型 interface Props { // 必需属性 title: string; // 可选属性 count?: number; // 联合类型 status: 'pending' | 'success' | 'error'; // 对象类型 user: { id: number; name: string; email: string; }; // 数组类型 items: string[]; // 函数类型 onClick: (value: string) => void; } // 使用 withDefaults 设置默认值 const props = withDefaults(defineProps<Props>(), { count: 0, items: () => [] }); // 复杂 Props 定义 interface User { id: number; name: string; } interface FormProps { user: User; onSubmit?: (data: User) => void; onCancel?: () => void; } const formProps = defineProps<FormProps>(); </script>

4.2 泛型 Props

<script setup lang="ts" generic="T"> // 泛型组件 defineProps<{ items: T[]; renderItem: (item: T, index: number) => string; }>(); // 使用 // <List :items="users" :renderItem="(user) => user.name" /> </script>

5. Emits 类型定义

<script setup lang="ts"> // 基础 Emits const emit = defineEmits<{ (e: 'update', value: string): void; (e: 'delete', id: number): void; (e: 'change', value: string, oldValue: string): void; }>(); // 使用对象语法 const emitObj = defineEmits({ update: (value: string) => typeof value === 'string', delete: (id: number) => typeof id === 'number', change: (value: string, oldValue: string) => { return typeof value === 'string' && typeof oldValue === 'string'; } }); // 使用 const handleUpdate = () => { emit('update', 'new value'); }; </script>

6. 响应式类型

6.1 ref 和 reactive

<script setup lang="ts"> import { ref, reactive } from 'vue'; // ref 类型推断 const count = ref(0); // Ref<number> const message = ref('Hello'); // Ref<string> // 显式指定 ref 类型 const user = ref<User | null>(null); const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle'); // reactive 类型 interface FormData { username: string; email: string; age: number; } const form = reactive<FormData>({ username: '', email: '', age: 0 }); // 使用泛型 const list = ref<User[]>([]); // 只读 ref const readonlyCount = readonly(count); </script>

6.2 computed

<script setup lang="ts"> import { ref, computed } from 'vue'; const firstName = ref('John'); const lastName = ref('Doe'); // 只读计算属性 const fullName = computed(() => `${firstName.value} ${lastName.value}`); // 可写计算属性 const fullNameWritable = computed({ get: () => `${firstName.value} ${lastName.value}`, set: (value: string) => { const [first, last] = value.split(' '); firstName.value = first; lastName.value = last; } }); // 显式类型 const doubleCount = computed<number>(() => count.value * 2); </script>

6.3 watch

<script setup lang="ts"> import { ref, watch, watchEffect } from 'vue'; const count = ref(0); const user = ref<User | null>(null); // 监听单个 ref watch(count, (newVal, oldVal) => { console.log(`Count changed from ${oldVal} to ${newVal}`); }); // 监听多个 watch([count, user], ([newCount, newUser], [oldCount, oldUser]) => { console.log('Multiple values changed'); }); // 深度监听 watch(user, (newUser, oldUser) => { console.log('User changed'); }, { deep: true }); // 立即执行 watch(count, (newVal) => { console.log(`Count is ${newVal}`); }, { immediate: true }); // watchEffect watchEffect(() => { console.log(`Count is ${count.value}`); }); </script>

7. 模板引用

<script setup lang="ts"> import { ref, onMounted } from 'vue'; // DOM 元素引用 const inputRef = ref<HTMLInputElement | null>(null); // 组件实例引用 import ChildComponent from './ChildComponent.vue'; const childRef = ref<InstanceType<typeof ChildComponent> | null>(null); onMounted(() => { // 操作 DOM inputRef.value?.focus(); // 调用子组件方法 childRef.value?.reset(); }); // 动态引用(v-for) const itemRefs = ref<HTMLElement[]>([]); </script> <template> <input ref="inputRef" type="text" /> <ChildComponent ref="childRef" /> <div v-for="item in items" :key="item.id" :ref="(el) => itemRefs.push(el as HTMLElement)" > {{ item.name }} </div> </template>

8. 生命周期钩子

<script setup lang="ts"> import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'; onBeforeMount(() => { console.log('Before mount'); }); onMounted(() => { console.log('Mounted'); }); onBeforeUpdate(() => { console.log('Before update'); }); onUpdated(() => { console.log('Updated'); }); onBeforeUnmount(() => { console.log('Before unmount'); }); onUnmounted(() => { console.log('Unmounted'); }); </script>

9. Provide / Inject

<!-- Parent.vue --> <script setup lang="ts"> import { provide, ref } from 'vue'; // 定义注入的类型 interface ThemeContext { theme: 'light' | 'dark'; toggleTheme: () => void; } const theme = ref<'light' | 'dark'>('light'); const toggleTheme = () => { theme.value = theme.value === 'light' ? 'dark' : 'light'; }; provide('theme', { theme, toggleTheme }); provide('count', ref(0)); </script> <template> <ChildComponent /> </template>
<!-- Child.vue --> <script setup lang="ts"> import { inject } from 'vue'; // 定义注入的类型 interface ThemeContext { theme: 'light' | 'dark'; toggleTheme: () => void; } // 注入并指定默认值 const themeContext = inject<ThemeContext>('theme'); const count = inject<number>('count', 0); // 带默认值 // 必需注入 const requiredTheme = inject<ThemeContext>('theme'); if (!requiredTheme) { throw new Error('Theme context not provided'); } // 使用 const { theme, toggleTheme } = requiredTheme; </script> <template> <div> <p>Current theme: {{ theme }}</p> <button @click="toggleTheme">Toggle Theme</button> </div> </template>

10. 组合式函数(Composables)

// composables/useCounter.tsimport{ref,computed}from'vue';interfaceUseCounterOptions{initialValue?:number;step?:number;min?:number;max?:number;}interfaceUseCounterReturn{count:Readonly<Ref<number>>;step:Ref<number>;doubleCount:ComputedRef<number>;increment:()=>void;decrement:()=>void;setCount:(value:number)=>void;reset:()=>void;}exportfunctionuseCounter(options:UseCounterOptions={}):UseCounterReturn{const{initialValue=0,step=1,min=-Infinity,max=Infinity}=options;constcount=ref(initialValue);constcurrentStep=ref(step);constdoubleCount=computed(()=>count.value*2);constincrement=()=>{constnewValue=count.value+currentStep.value;count.value=Math.min(newValue,max);};constdecrement=()=>{constnewValue=count.value-currentStep.value;count.value=Math.max(newValue,min);};constsetCount=(value:number)=>{count.value=Math.min(Math.max(value,min),max);};constreset=()=>{count.value=initialValue;};return{count:readonly(count),step:currentStep,doubleCount,increment,decrement,setCount,reset};}// composables/useFetch.tsimport{ref,shallowRef,typeRef}from'vue';interfaceUseFetchOptions{immediate?:boolean;onError?:(error:Error)=>void;}interfaceUseFetchReturn<T>{data:Ref<T|null>;error:Ref<Error|null>;loading:Ref<boolean>;execute:()=>Promise<void>;}exportfunctionuseFetch<T=unknown>(url:Ref<string>|string,options:UseFetchOptions={}):UseFetchReturn<T>{const{immediate=true,onError}=options;constdata=shallowRef<T|null>(null);consterror=ref<Error|null>(null);constloading=ref(false);consturlRef=typeofurl==='string'?ref(url):url;constexecute=async()=>{loading.value=true;error.value=null;try{constresponse=awaitfetch(urlRef.value);if(!response.ok)thrownewError('Fetch failed');constjson=awaitresponse.json();data.value=json;}catch(err){error.value=errinstanceofError?err:newError('Unknown error');onError?.(error.value);}finally{loading.value=false;}};if(immediate){execute();}return{data,error,loading,execute};}
<!-- 使用组合式函数 --> <script setup lang="ts"> import { useCounter } from './composables/useCounter'; import { useFetch } from './composables/useFetch'; // 使用计数器 const { count, increment, decrement, doubleCount, reset } = useCounter({ initialValue: 10, step: 2, min: 0, max: 20 }); // 使用数据获取 const { data, loading, error, execute } = useFetch<User[]>('/api/users'); </script> <template> <div> <h2>Counter</h2> <p>Count: {{ count }}</p> <p>Double: {{ doubleCount }}</p> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="reset">Reset</button> <h2>Users</h2> <div v-if="loading">Loading...</div> <div v-else-if="error">Error: {{ error.message }}</div> <ul v-else> <li v-for="user in data" :key="user.id">{{ user.name }}</li> </ul> <button @click="execute">Refresh</button> </div> </template>

11. 完整示例:Todo 应用

<!-- TodoApp.vue --> <script setup lang="ts"> import { ref, computed } from 'vue'; // ============ 类型定义 ============ interface Todo { id: number; text: string; completed: boolean; createdAt: Date; } type FilterType = 'all' | 'active' | 'completed'; // ============ 状态 ============ const todos = ref<Todo[]>([ { id: 1, text: 'Learn Vue 3', completed: false, createdAt: new Date() }, { id: 2, text: 'Learn TypeScript', completed: false, createdAt: new Date() } ]); const filter = ref<FilterType>('all'); const newTodoText = ref(''); // ============ 计算属性 ============ const filteredTodos = computed(() => { switch (filter.value) { case 'active': return todos.value.filter(t => !t.completed); case 'completed': return todos.value.filter(t => t.completed); default: return todos.value; } }); const activeCount = computed(() => todos.value.filter(t => !t.completed).length); const completedCount = computed(() => todos.value.filter(t => t.completed).length); // ============ 方法 ============ const addTodo = () => { if (newTodoText.value.trim()) { todos.value.push({ id: Date.now(), text: newTodoText.value.trim(), completed: false, createdAt: new Date() }); newTodoText.value = ''; } }; const toggleTodo = (id: number) => { const todo = todos.value.find(t => t.id === id); if (todo) { todo.completed = !todo.completed; } }; const deleteTodo = (id: number) => { todos.value = todos.value.filter(t => t.id !== id); }; const clearCompleted = () => { todos.value = todos.value.filter(t => !t.completed); }; // ============ 生命周期 ============ // 保存到本地存储 import { watch } from 'vue'; watch(todos, (newTodos) => { localStorage.setItem('todos', JSON.stringify(newTodos)); }, { deep: true }); // 加载本地存储数据 const stored = localStorage.getItem('todos'); if (stored) { try { todos.value = JSON.parse(stored); } catch (e) { console.error('Failed to load todos'); } } </script> <template> <div class="todo-app"> <h1>Todo App</h1> <!-- 添加表单 --> <form @submit.prevent="addTodo"> <input v-model="newTodoText" type="text" placeholder="Add a new todo..." /> <button type="submit">Add</button> </form> <!-- 过滤按钮 --> <div class="filters"> <button v-for="f in ['all', 'active', 'completed']" :key="f" :class="{ active: filter === f }" @click="filter = f as FilterType" > {{ f }} </button> </div> <!-- 待办列表 --> <ul> <li v-for="todo in filteredTodos" :key="todo.id" :class="{ completed: todo.completed }" > <input type="checkbox" :checked="todo.completed" @change="toggleTodo(todo.id)" /> <span>{{ todo.text }}</span> <button @click="deleteTodo(todo.id)">Delete</button> </li> </ul> <!-- 统计 --> <div class="stats"> <span>{{ activeCount }} items left</span> <button v-if="completedCount > 0" @click="clearCompleted" > Clear completed ({{ completedCount }}) </button> </div> </div> </template> <style scoped> .completed { text-decoration: line-through; opacity: 0.6; } .filters button.active { font-weight: bold; color: blue; } </style>

12. 总结

类型用途示例
defineComponent组件定义defineComponent({ ... })
defineProps<T>()Props 定义defineProps<{ title: string }>()
defineEmits<T>()事件定义defineEmits<{ (e: 'click'): void }>()
withDefaults默认值withDefaults(defineProps<Props>(), { count: 0 })
ref<T>响应式引用const count = ref<number>(0)
reactive<T>响应式对象const state = reactive<State>({ ... })
computed<T>计算属性const double = computed(() => count.value * 2)
watch侦听器watch(count, (newVal) => {})
inject<T>注入const data = inject<T>('key')
InstanceType<typeof Comp>组件实例类型const ref = ref<InstanceType<typeof Child>>()

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 21:06:28

2026国内政务数据安全平台排名评析:基于AI降噪、全链路、动态性

一、概要&#xff1a;政务场景下数据安全泛监测的核心价值与排名逻辑提示&#xff1a;本文立足政务数字化转型背景&#xff0c;围绕“AI降噪、全链路、动态”三大核心特性&#xff0c;结合相关法规与政务合规要求&#xff0c;对主流政务数据安全管理方案进行排名评析&#xff0…

作者头像 李华
网站建设 2026/5/23 21:03:05

知识竞赛抢答器使用培训:选手必知的5个技巧

知识竞赛抢答器使用培训&#xff1a;选手必知的5个技巧抢占先机 精准出击 让每一次按键都成为胜利的起点&#x1f3af; 引言知识竞赛中&#xff0c;抢答环节往往是决定胜负的关键。熟练掌握抢答器的使用技巧&#xff0c;不仅能帮助选手抢占先机&#xff0c;还能避免因操作失误…

作者头像 李华
网站建设 2026/5/23 21:01:31

分布式系统开发实战:从核心原理到主流平台应用指南

1. 项目概述&#xff1a;从单机到集群的必然之路十年前&#xff0c;我刚入行时&#xff0c;大部分应用还跑在单台服务器上。一个Tomcat&#xff0c;一个MySQL&#xff0c;就能撑起一个不小的业务。但随着用户量从几百涨到几百万&#xff0c;事情开始变得棘手。半夜被报警电话叫…

作者头像 李华
网站建设 2026/5/23 20:59:37

iMLite AI Map 2.1:嵌入式离线地图如何赋能智能穿戴独立导航

1. 项目概述&#xff1a;当智能穿戴“断网”后&#xff0c;如何实现精准导航&#xff1f;作为一名在智能硬件和嵌入式系统领域摸爬滚打了十多年的从业者&#xff0c;我见过太多“伪智能”产品。它们功能花哨&#xff0c;但一离开手机或网络&#xff0c;就立刻变成一块“砖”。尤…

作者头像 李华