vue3+ts项目自建训练

VScode插件为Volar+Vue(Offical),该文章我自己都觉得写的💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩

Vue Tutorial 快去w3school学习,别看了。

一、项目初始化与配置

1.创建一个文件夹存放vue项目,并进入cmd

2.输入npm create vue@latest创建项目,我使用的是淘宝镜像所以是cnpm

3.输入名字,选择要包含的内容,

使用命令初始化文档

之后就可以访问5173端口的vue页面了

4.配置文件讲解

这是package.json,能够看到整个项目的依赖和脚本配置

复制代码
{
  "name": "vue_test_demo",         // 项目名
  "version": "0.0.0",              // 版本号
  "private": true,                 // 不发布到 npm
  "type": "module",                // 使用 ES 模块(ESM)

  "engines": {
    "node": "^20.19.0 || >=22.12.0" // 支持的 Node.js 版本
  },
  // 脚本
  "scripts": {
    "dev": "vite",                        // 启动开发服务器
    "build": "run-p type-check build-only", // 并行类型检查 + 构建
    "preview": "vite preview",            // 预览生产包
    "build-only": "vite build",           // 仅构建
    "type-check": "vue-tsc --build",      // TypeScript 类型检查
    "lint": "eslint . --fix --cache",     // 自动修复代码规范问题
    "format": "prettier --write src/"     // 格式化代码
  },
  // 线上构建环境的依赖
  "dependencies": {
    "vue": "^3.5.25",             // Vue 3 核心
    "vue-router": "^4.6.3",       // 路由
    "pinia": "^3.0.4"             // 状态管理
  },
  // 开发阶段的依赖
  "devDependencies": {
    "vite": "^7.2.4",                     // 构建工具
    "@vitejs/plugin-vue": "^6.0.2",       // 支持 .vue 文件
    "vue-tsc": "^3.1.5",                  // Vue + TS 类型检查
    "typescript": "~5.9.0",               // TS 编译器
    "@vue/tsconfig": "^0.8.1",            // 官方 TS 配置
    "eslint": "^9.39.1",                  // 代码检查
    "@vue/eslint-config-typescript": "^14.6.0", // ESLint + TS 配置
    "@vue/eslint-config-prettier": "^10.2.0",   // 关闭与 Prettier 冲突的规则
    "eslint-plugin-vue": "~10.5.1",       // Vue 的 ESLint 规则
    "prettier": "3.6.2",                  // 代码格式化
    "npm-run-all2": "^8.0.4",             // 并行运行脚本
    "vite-plugin-vue-devtools": "^8.0.5", // 开发时集成 Vue DevTools
    "@types/node": "^24.10.1",            // Node 类型定义
    "@tsconfig/node24": "^24.0.3",        // Node 24 的 TS 基础配置
    "jiti": "^2.6.1"                      // 运行时模块加载器(用于配置文件等)
  }
}

如果想打包文件,输入npm run build

会生成一个dist文件,最终部署在服务器上

npm run preview指令展示打包构建出来的产物

打包构建过的产物,会比开发时性能更好

二、TypeScript基本配置

配置文件 作用层 说明
tsconfig.app.json 应用层(重点关注) 专用于编译你的 Vue 前端应用代码(如 .vue 组件、main.ts 等),支持 Vue 特性,面向浏览器运行环境。
tsconfig.json 项目根层(总入口) 作为 TypeScript 的默认配置入口,通常不直接定义规则,而是通过 references 引用 appnode 配置,让类型检查覆盖整个项目。
tsconfig.node.json 工具/构建层(开发环境) 用于编译在 Node.js 中运行的代码,比如 vite.config.ts、脚本文件等,不涉及 Vue 组件,面向本地开发工具链。

vue的配置项在app.json的@vue/tsconfig/tsconfig.dom.json里的./tsconfig.json

基本类型与高级类型

  • 基本类型stringnumberbooleannullundefined

  • 联合类型与交叉类型

    • 联合类型:type MyType = string | number;

    • 交叉类型:type MyType = { name: string } & { age: number };

  • 类型别名与接口

    • 类型别名:type MyType = string | number;

    • 接口:interface Person { name: string; age: number; }

  • 泛型与条件类型

    • 泛型:function identity<T>(arg: T): T { return arg; }

    • 条件类型:type IsString<T> = T extends string ? 'Yes' : 'No';

ts不允许类型转换,而js可以

TypeScript 复制代码
// 基础数据类型
let a: number = 1;
let b: string = 'hello';
let c: boolean = true;
let d: null = null;
let e: undefined = undefined;
let f: symbol = Symbol('symbol');
let g: bigint = BigInt(100);
// 引用数据类型
let h: Object = {};
let i: Array<number> = [1,2,3];
let j: Function = function(){};
let k: Date = new Date();
let l: RegExp = /hello/;
// 自定义类型
type OrderId = number;
// 联合类型
type OrderId2 = number | string | bigint;
// 自定义对象类型
type Gender =  'man' | "women"
type Person = {
    name: string;
    age: number;
    info: {
        gender: Gender;
    }
}
type Pig = {
    name: string,
    age:number
    hobby:{
        eat: boolean
    }
}
type Animal = Person & Pig;
const animal: Animal = {
    name: 'tom',
    age: 12,
    info:{
        gender: 'man'
    },
    hobby:{
        eat: true
    }
}
// 泛型
type Person<T> = {
    name: string;
    age: number;
    info: T
};
const person: Person<{hoddy: string[]}> = {
    name: 'ok',
    age: 18,
    info:{
        hoddy: ['eat', 'sleep']
    }
}

type Person2<T extends {hobby: string[]}> = {
    name: string;
    age: number;
    info: T
};
const person2: Person2<{hobby: string[]; b: string}> = {
    name: 'ok',
    age: 18,
    info:{
        hobby: ['eat', 'sleep'],
        b: 'ok'
    }
}
type Person3<T = {hobby: string[]}> = {
    name: string;
    age: number;
    info: T
};
// 三目运算符,确定条件类型
/*
1.继承 2.约束泛型 3.条件类型
*/
class Person {}
class Student extends Person {}
function say<T extends {id: string}>(a: T): T {
    return a;
}
say({id:"1"});
type IsString<T> = T extends String ? true : false;
const isString: IsString<string> = true;
// 类型复用
type Person = {name: string; age: number};
type NamedPerson = Pick<Person, 'name'>;// 提取
type NamedPerson2 = Omit<Person, "age">;// 丢弃
// 类型保护
type Value = string | number;
const value:Value = 'string';
// value.toFixed()
function isString(value: Value): value is string{
    return typeof value === 'string';
}
const s = isString(value) 

在Vue3里的应用场景

HelloWorld.vue:

TypeScript 复制代码
<script setup lang="ts">
import { ref } from 'vue'
//宏定义
defineProps<{
  msg: string
  age: number
}>()
// const emits = defineEmits<{
//     onClick:(e: MouseEvent) => void;
// }>()
defineEmits<{
    Click:(e: MouseEvent) => void;
}>()
</script>

<template>
  <div id="content">
    <h1></h1>
  </div>
</template>

<style scoped>
.content {
  text-align: center;
}
</style>

App.vue:

TypeScript 复制代码
<script setup lang="ts">
import HelloWorld from './view/HelloWorld.vue';
</script>

<template>
  <div id = "content">
    <HelloWorld msg= "'123'" v-bind:age="1" v-on:click=""></HelloWorld>
    <HelloWorld msg= "'123'" :age="1"></HelloWorld>
  </div>
</template>

<style scoped>
.content{
  text-align: center;

}
</style>

HelloWorld2.vue:

TypeScript 复制代码
<script setup lang="ts">
import { ref, reactive } from "vue";
const isOPen = ref<boolean>(false);
const personInfo = ref<{ name: string; age: number }>();
</script>

<template>
    <div id="content">
        <div class="card" @click="isOPen = !isOPen">{{ isOPen ? '开' : '关' }}</div>
        <p>{{ personInfo?.age }}</p>
    </div>
</template>

<style scoped>
#content {
    display: grid;
    place-items: center;
    min-height: 100vh;
}
</style>
TypeScript 复制代码
<script setup lang="ts">
import  HelloWorld2 from './view/HelloWorld2.vue';

// import HelloWorld from './view/HelloWorld.vue';
</script>

<template>
    <!-- <HelloWorld msg= "'123'" v-bind:age="1" v-on:click=""></HelloWorld> -->
    <!-- <HelloWorld msg= "'123'" :age="1"></HelloWorld> -->
    <HelloWorld2>
      ----
    </HelloWorld2>
</template>

<style scoped>
</style>

三、VUE3实战

1.Vue2 VS. Vue3

TypeScript 复制代码
<!-- <script lang="ts">
export default{
    name: '2vs3',
    data(){
        return {
            msg:'welcome to your vus.js app'
        };
    }
}
</script> -->
<script setup lang="ts">
    import {ref} from 'vue';
    // const msg = 'Welcom to UR vue.js app'
    const msg = ref('Welcom to UR vue.js app')
</script>
<template>
     <div id="content">
    {{ msg }}
    <h1 @click="msg += Math.random()">dian</h1>
    </div>
</template>

<style scoped>
#content {
    display: grid;
    place-items: center;
    min-height: 100vh;
}
</style>

从选项式api升级为组合式api,性能有大幅提升,且对数据进行了保护

2.Vue3基础模板

一个vue文件包含

TypeScript 复制代码
<script></script>
<template></template>
<style></style>
TypeScript 复制代码
<script setup lang="ts">
import { reactive, ref } from 'vue';

    // const msg = ref('okkkkk'); // 变成一个状态
    const msg = reactive({value:'soajodija'});
    setTimeout(() =>{
        msg.value = 'Hello, world!';
    }, 2000);

</script>
<template>
    <!-- <div id = 'content'>{{ msg }}</div> -->
     <div id = 'content'>{{ msg.value }}</div>
</template>
<style scoped></style>

一些简单vue功能:

TypeScript 复制代码
<script setup lang="ts">
import { computed, onMounted, onUnmounted, onUpdated, ref, watch, watchEffect, watchPostEffect, watchSyncEffect } from 'vue';

const price = ref(11);
const count = ref(1);
// const total = price.value * count.value;
const total = computed(() => {
    return price.value * count.value;
})
// watch监听数据变化
watch([price, count], ([newP, newC], [oldP, oldC]) =>{
    console.log(newP,newC,oldP,oldC);
    console.log("price or count changed");
    if(price.value > 10){
        alert('oioioi价格贵了');
    }
}); 
watchEffect(() => {
    console.log("watchEffect");
    console.log(price.value, count.value);
})
watchPostEffect(() => {
    console.log("watchPostEffect");
    console.log(price.value, count.value);
})
watchSyncEffect(() => {
    console.log("watchSyncEffect");
    console.log(price.value, count.value);
})
// 组件挂载
onMounted(() =>{
    console.log('组件挂载');
});
// 组件更新
onUpdated(() =>{
    console.log('组件更新')
})
// 组件卸载
onUnmounted(() =>{
    console.log("组件卸载")
})
// 父级给子集传的数据,props - defineProps
// 父级给子集传的事件,emit - defineEmits
// 
const emit = defineEmits<{
    change: [];
    update: [value: number];
}>();
defineProps<{price2: number; count2:number}>();
</script>
<template>
    <div id="content">
        <div>商品价格:<input v-model="price"></input></div>
        <div>商品数量:<input v-model="count"></input></div>
        <!-- <div>总价格:{{ price * count }}</div> -->
        <div>总价格:{{ total }}</div>
        <div>{{ price2 }}</div>
        <div>{{ count2 }}</div>
        <div @click="emit('update', 100)">更新数据</div>
    </div>
</template>
<style></style>

App.vue:

TypeScript 复制代码
<script setup lang="ts">
import HelloWorld2 from './view/HelloWorld2.vue';
import VS3 from './view/2VS3.vue'
import basic from './view/1.basic.vue'
import Computed from './view/2.computed.vue';
import { ref } from 'vue';

const isOpen = ref(true);
const price2 = ref(1);
const count2 = ref(1);
</script>

<template>
  <!-- <HelloWorld msg= "'123'" v-bind:age="1" v-on:click=""></HelloWorld> -->
  <!-- <HelloWorld msg= "'123'" :age="1"></HelloWorld> -->
  <!-- <HelloWorld2>
      ----
    </HelloWorld2>
    <VS3>    </VS3> -->
  <!-- <basic></basic> -->
  <!-- <Computed></Computed> -->
  <Computed v-if="isOpen" :price2="price2" :count2="count2" v-on:change="" v-on:update="(v) => price2 += v">

  </Computed>
  <Computed v-if="isOpen" :price2="price2" :count2="count2" v-on:change="" v-on:update="(v) => price2 += v">

  </Computed>
  <div style="text-align: center;">
    <button @click="isOpen = !isOpen">{{ isOpen ? '关闭' : "打开" }}</button>
    <button @click="price2++">price++</button>
  </div>
</template>

<style>
#content {
  display: grid;
  justify-content: center;
  /* align-items: center; */
  /* place-items: center; */
  /* height: 100vh; */
}
</style>

插槽用法:

TypeScript 复制代码
<script setup lang="ts">

</script>
<template>
    <div id = "content">
        123
        <div>
            <!-- 不确定是什么内容,需要外部传入 -->
            <slot name="header" header = Date() ></slot>
            --------------------------------
        </div>
        <div>
            <!-- 不确定是什么内容,需要外部传入 -->
            <slot></slot>
               --------------------------------
        </div>
        <div>
            <!-- 不确定是什么内容,需要外部传入 -->
            <slot name="footer"></slot>
               --------------------------------
        </div>
    </div>
</template>
TypeScript 复制代码
<script setup lang="ts">
import Slot from './6.slot.vue'
</script>
<template>
    <Slot>
        <template #header="{header}">
            <div>{{ header }}</div>
        </template>
       
        <div v-on:click="console.log($slots)">中间</div>
        <template #footer>
            <div>footer</div>
        </template>

    </Slot>
</template>

动态组件:

TypeScript 复制代码
<script setup lang="ts">
import { h } from 'vue';

// 使用场景:低代码、无代码的物料渲染,多个相同形态的组件需要渲染
const data = [
    { type: 'text', name: ' ok', age: 18 },
    { type: 'image', name: 'no0', age: 20 }
];
const render = (type: string) => {
    switch(type){
        case "text":
            return h('div', 'wksd');
        case 'image':
            return h('img', {src: 'https://www.baidu.com/img.jpg'})
    }
};
// let comp  =null;
// switch(data[0].type){
//     case "text":
//         comp = () => h('div', 'ok');
//         break;
//     case 'image':
//         comp = () => h('img', {scr: "https://www.baidu.com"})
// }
</script>
<template>
    <div id = 'content'>
        <div v-for="value in data">
            <!-- <component :is="123" /> -->
            <component :is="h('div', null  ,'ahha')"></component>
            <component :is="render(value.type)"/>
        </div>
    </div>
</template>

四、Router路由

1.定义router相关逻辑

2.在vue中使用router

如果没有在vite脚手架初期选择router选型,可以自己创建一个router文件夹

同时在package.json中写上

TypeScript 复制代码
"dependencies": {
    "pinia": "^3.0.4",
    "vue": "^3.5.25",
    "vue-router": "^4.6.3"
  },
TypeScript 复制代码
npm install 下载下+

在文件夹中写上index.ts:

TypeScript 复制代码
import About from '@/pages/About.vue'
import Home from '@/pages/Home.vue';
import { h } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: "/", name: 'home', component: h('div', 'home') },
    { path: '/about', name: 'about', component: h('div', 'about') },
    { path: '/About2', name: 'About', component: About },
    { path: '/Home2', name: 'Home', component: Home }
  ],
})
// 全局守卫
router.beforeEach((to, from, next) => {
  console.log('beforeEach', to, from);
  // 权限控制
  if (Math.random() > 0.5) {
    next();
  }else{
    next({name:"Home"})
  }

});

export default router

均为可选项

Home.vue:

TypeScript 复制代码
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();
</script>
<template>
    HOMES
    <RouterLink to="/about2">通过router-link访问about2</RouterLink>
    <button @click="router.push('/about')">通过router.push 仿文about</button>
    <button @click="router.push({path: '/about2', query: {name : 'keyi'}})">通过router.push 仿文about</button>
</template>

About.vue

TypeScript 复制代码
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
route.params
</script>
<template>
    ABOUT
    {{ $route.params }}
    {{ $route.query.name }}
</template>

在main.ts中写上:

TypeScript 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.mount('#app')

APP.vue中加入RouterView标签

五、Pinia管理

未初始化,package.json中自行加入,并创建文件加stores

TypeScript 复制代码
  "dependencies": {
    "pinia": "^3.0.4",
    "vue": "^3.5.25",
    "vue-router": "^4.6.3"
  },

创建composable/useCount.ts(自定义数值):

TypeScript 复制代码
import { ref } from "vue"

export const useCount = () => {
    const count = ref(0);
    return count;
}

在src/stores下单couter.ts文件中写入:

TypeScript 复制代码
import { ref, computed } from 'vue'
import { defineStore } from 'pinia' 
import { useCount } from '@/composable/useCount'
// 定义状态
export const useCounterStore = defineStore('counter', () => {
  const count = useCount();
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

无论是Home.vue还是about.vue全局共享一个数据

TypeScript 复制代码
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { useCounterStore } from '@/stores/counter';
const counterstore = useCounterStore();
const route = useRoute();
route.params
</script>
<template>
    ABOUT
    {{ $route.params }}
    {{ $route.query.name }}
    <div>通过pinia状态得到数据
        {{ counterstore.count }}
        <button @click="counterstore.increment">+1</button>
        <div>{{ counterstore.doubleCount }}</div>
    </div>
</template>

总体项目:

相关推荐
哆啦A梦15882 小时前
商城后台管理系统 02,上传图片实现
前端·javascript·vue.js·elementui
破z晓2 小时前
若依(vue版)集成ArcGIS
前端·vue.js·arcgis
计算机学姐2 小时前
基于SSM的生鲜食品商城系统【2026最新】
java·vue.js·后端·mysql·java-ee·tomcat·mybatis
jinxinyuuuus2 小时前
FIRE之旅 财务计算器:实时交互式建模与前端性能工程
前端·人工智能·算法·自动化
Redundantº2 小时前
vuedraggable前端拖拽插件
前端
snow@li2 小时前
前端:软件开发中的秤砣理论 / 越简单越可靠 / 提炼组织架构 / 精确标定工作 / 有主干有支流 / 不追求繁琐 / 不做表面工作
前端·架构
张毫洁2 小时前
将后端resources中的文件返给前端下载的方法
前端·状态模式
Hao_Harrision2 小时前
50天50个小项目 (React19 + Tailwindcss V4) ✨| RandomChoicePicker(标签生成)
前端·typescript·react·vite7·tailwildcss