Vue3+TypeScript项目(中)顶部导航组件

一、顶部导航Tabbar(静态页面)

1. 创建顶部导航组件:src\layout\tabbar\index.vue

2. 在layout\index.vue引入顶部导航组件

。。。。。。
 
<!-- 顶部导航 -->
    <div class="layout_navigation">
      <Tabbar></Tabbar>
 </div>


。。。。。。


//引入顶部导航组件
import Tabbar from "./tabbar/index.vue";

3.编写顶部导航组件 src\layout\tabbar\index.vue

<template>
  <div class="tabbar">
    <div class="tabbar-left">
      <Breadcrumb />
    </div>
    <div class="tabbar-right">
      <Setting />
    </div>
  </div>
</template>

<script setup lang="ts">
//引入顶部导航左侧组件
import Breadcrumb from "./breadcrumb/index.vue";
//引入顶部导航右侧组件
import Setting from "./setting/index.vue";
</script>

<style lang="scss" >
.tabbar {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: space-between;
  .tabbar-left {
    display: flex;
    align-items: center;
  }
  .tabbar-right {
    display: flex;
    align-items: center;
    img {
      width: 32px;
      height: 32px;
      margin: 0 10px;
      border-radius: 20px;
    }
  }
}
</style>

4.编写顶部导航左侧子组件

src\layout\tabbar\breadcrumb\index.vue 

<template>
  <!-- 顶部左侧的图标 -->
  <el-icon style="margin-right: 10px">
    <Expand></Expand>
  </el-icon>
  <!-- 左侧的面包屑 -->
  <el-breadcrumb separator-icon="ArrowRight">
    <el-breadcrumb-item>权限管理</el-breadcrumb-item>
    <el-breadcrumb-item>用户管理</el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script setup lang="ts">

</script>

<style scoped lang="scss"></style>

 5.编写顶部导航右侧子组件

src\layout\tabbar\setting\index.vue

<template>
    <el-button :icon="Refresh" circle />
        <el-button :icon="FullScreen" circle />
        <el-button :icon="Setting" circle />
        <img src="../../../../public/tx.jpeg" style="width: 24px;height:24px">
        <!-- 下拉菜单 -->
        <el-dropdown>
    <span class="el-dropdown-link">
      Admin
      <el-icon class="el-icon--right">
        <arrow-down />
      </el-icon>
    </span>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item>退出登录</el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
  </template>
  
  <script setup lang="ts">
  import { ArrowRight ,Refresh,FullScreen,Setting} from '@element-plus/icons-vue'
  </script>
  
  <style scoped lang="scss"></style>

效果: 

二、菜单折叠 

1.折叠变量

定义一个折叠变量来判断现在的状态是否折叠。因为这个变量同时给breadcrumb组件以及父组件layout使用,因此将这个变量定义在pinia中

src\store\modules\setting.ts

//小仓库:layout组件相关配置仓库
import { defineStore } from 'pinia'

let useLayOutSettingStore = defineStore('SettingStore', {
  state: () => {
    return {
      fold: false, //用户控制菜单折叠还是收起的控制
    }
  },
})

export default useLayOutSettingStore

layout\index.vue

<template>
  <div class="layout_container">
    <!-- 左侧菜单 -->
    <div class="layout_slier" :class="{ fold: LayOutSettingStore.fold ? true : false }">
      <!-- 左侧Logo -->
      <Logo></Logo>
      <!--展示菜单-->
      <!-- 滚动组件 -->
      <el-scrollbar class="scrollbar">
        <!-- 菜单组件 -->
        <!--动态菜单 -->
        <el-menu
          :collapse="LayOutSettingStore.fold"
          :default-active="$route.path"
          background-color="#001529"
          text-color="white"
          unique-opened
        >
          <Menu :menuList="useStore.menuRoutes"></Menu>
        </el-menu>
      </el-scrollbar>
    </div>
    <!-- 顶部导航 -->
    <div class="layout_navigation" :class="{ fold: LayOutSettingStore.fold ? true : false }">
      <Tabbar></Tabbar>
    </div>
    <!-- 内容展示区域 -->
    <div class="layout_main" :class="{ fold: LayOutSettingStore.fold ? true : false }">
      <Main></Main>
    </div>
  </div>
</template>

<script setup lang="ts">
//引入路由对象
import { useRoute, useRouter } from "vue-router";
//引入左侧logo子组件
import Logo from "./logo/index.vue";
//引入菜单组件
import Menu from "./menu/index.vue";
//引入内容组件
import Main from "./main/index.vue";
//引入顶部导航组件
import Tabbar from "./tabbar/index.vue";
//获取用户相关的小仓库
import useUserStore from "@/store/modules/user";
import useLayOutSettingStore from "@/store/modules/setting";
//获取layout配置相关的仓库
let LayOutSettingStore = useLayOutSettingStore()
let useStore = useUserStore();
//获取路由对象
let $route = useRoute();
</script>

<style scoped lang="scss">
.layout_container {
  width: 100%;
  height: 100vh;
  .layout_slier {
    width: $base-menu-width;
    height: 100vh;
    background: $base-menu-background;
    &.fold {
      width: $base-menu-min-width;
    }
    .scrollbar {
      width: 100%;
      height: calc(100vh - $base-menu-logo-height);
      color: white;
      .el-menu {
        border-right: none; //解决右侧有一条白色的线的问题
      }
    }
  }
  .layout_navigation {
    //fixed表示固定在一个位置
    position: fixed;
    top: 0;
    left: $base-menu-width;
    padding-left: 15px;
    width: calc(100% - $base-menu-width);
    height: $base-navigation-height;
    &.fold {
      width: calc(100vw - $base-menu-min-width);
      left:$base-menu-min-width;
    }
  }
  .layout_main {
    position: absolute;
    width: calc(100% - $base-menu-width);
    height: calc(100vh - $base-navigation-height);
    background: rgb(190, 223, 215);
    left: $base-menu-width;
    top: $base-navigation-height;
    padding-left: 15px;
    overflow: auto; //滚动条
    &.fold {
      width: calc(100vw - $base-menu-min-width);
      left:$base-menu-min-width;
    }
  }
}
</style>

2.面包屑组件

<template>
  <el-icon style="margin-right: 10px" @click="changeIcon">
    <component :is="LayOutSettingStore.fold ? 'Fold' : 'Expand'"></component>
  </el-icon>
  <el-breadcrumb :separator-icon="ArrowRight">
    <el-breadcrumb-item>权限管理</el-breadcrumb-item>
    <el-breadcrumb-item>用户管理</el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script setup lang="ts">
import useLayOutSettingStore from '@/store/modules/setting'
//获取layout配置相关的仓库
let LayOutSettingStore = useLayOutSettingStore()
//点击图标的切换
const changeIcon = () => {
  //图标进行切换
  LayOutSettingStore.fold = !LayOutSettingStore.fold
}
</script>

<style scoped lang="scss"></style>

src\store\modules\setting.ts

//小仓库:layout组件相关配置仓库
import { defineStore } from 'pinia'

let useLayOutSettingStore = defineStore('SettingStore', {
  state: () => {
    return {
      fold: false, //用户控制菜单折叠还是收起的控制
    }
  },
})

export default useLayOutSettingStore

三、顶部面包屑动态展示

1.面包屑组件

src\layout\tabbar\breadcrumb\index.vue

<!--
 * @Author: huks
 * @Date: 2024-03-13 22:35:41
 * @LastEditTime: 2024-03-14 00:04:34
 * @Description: 
-->
<template>
  <!-- 顶部左侧的图标 -->
  <el-icon style="margin-right: 10px" @click="changeIcon">
    <component :is="LayOutSettingStore.fold ? 'Fold' : 'Expand'"></component>
  </el-icon>
  <!-- 左侧的面包屑 -->
  <el-breadcrumb separator-icon="ArrowRight">
    <el-breadcrumb-item v-for="(item,index) in $route.matched" :key="index" v-show="item.meta.title" :to="item.path">
      <!-- 面包屑展示匹配路由的图标 -->
      <el-icon>
        <component :is="item.meta.icon"></component>
      </el-icon>
      <!-- 面包屑展示匹配路由的标题 -->
      <span>{{ item.meta.title }}</span>
    </el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script setup lang="ts">
//引入路由
import { useRoute } from 'vue-router';
//引入小仓库layout配置相关的仓库
import useLayOutSettingStore from '@/store/modules/setting'
//获取路由
let $route = useRoute();
//获取layout配置相关的仓库
let LayOutSettingStore = useLayOutSettingStore()
//点击图标的切换
const changeIcon = () => {
  //图标进行切换
  LayOutSettingStore.fold = !LayOutSettingStore.fold
}
</script>

<style scoped lang="scss"></style>

效果图:

注意:要想面包屑不显示layout,只需把item.meta.title和item.meta.icon改为空

重定向到第一个子组件

四、刷新业务

1.src\store\modules\setting.ts

 2.src\layout\tabbar\setting\index.vue

<template>
  <el-button :icon="Refresh" circle @click="updateRefresh"/>
  <el-button :icon="FullScreen" circle />
  <el-button :icon="Setting" circle />
  <img src="../../../../public/tx.jpeg" style="width: 24px; height: 24px" />
  <!-- 下拉菜单 -->
  <el-dropdown>
    <span class="el-dropdown-link">
      Admin
      <el-icon class="el-icon--right">
        <arrow-down />
      </el-icon>
    </span>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item>退出登录</el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script setup lang="ts">
import { Refresh, FullScreen, Setting } from "@element-plus/icons-vue";
import useLayOutSettingStore from "@/store/modules/setting"
let layoutSettingStore = useLayOutSettingStore();
//刷新按钮的回调
const updateRefresh=()=>{
  layoutSettingStore.refsh = !layoutSettingStore.refsh
}
</script>

<style scoped lang="scss"></style>

3.src\layout\main\index.vue

<template>
  <!-- 路由组件出口的位置 -->
  <router-view v-slot="{ Component }">
    <transition name="fade">
      <!-- 渲染layout一级路由的子路由 -->
      <component :is="Component" v-if="flag" />
    </transition>
  </router-view>
</template>

<script setup lang="ts">
import { ref, watch, nextTick } from "vue";
//使用layout的小仓库
import useLayOutSettingStore from "@/store/modules/setting";
let layoutSettingStore = useLayOutSettingStore();
//控制当前组件是否销毁重建
let flag = ref(true);
//监听仓库内部的数据是否发生改变,如果发生变化,说明用户点击过刷新按钮
watch(
  () => layoutSettingStore.refsh,
  () => {
    //点击刷新按钮:路由组件销毁
    flag.value = false;
    nextTick(() => {
      flag.value = true;
    });
  }
);
</script>

<style lang="scss" scoped>
.fade-enter-from {
  opacity: 0;
}
.fade-enter-active {
  transition: all 0.3s;
}
.fade-enter-to {
  opacity: 1;
}
</style>

五、全屏模式

src\layout\tabbar\setting\index.vue 

<template>
  <el-button :icon="Refresh" circle @click="updateRefresh" />
  <el-button :icon="FullScreen" circle @click="fullScreen" />
  <el-button :icon="Setting" circle />
  <img src="../../../../public/tx.jpeg" style="width: 24px; height: 24px" />
  <!-- 下拉菜单 -->
  <el-dropdown>
    <span class="el-dropdown-link">
      Admin
      <el-icon class="el-icon--right">
        <arrow-down />
      </el-icon>
    </span>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item>退出登录</el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script setup lang="ts">
import { Refresh, FullScreen, Setting } from "@element-plus/icons-vue";
import useLayOutSettingStore from "@/store/modules/setting";
let layoutSettingStore = useLayOutSettingStore();
//刷新按钮的回调
const updateRefresh = () => {
  layoutSettingStore.refsh = !layoutSettingStore.refsh;
};
//全屏模式的回调
//全屏按钮点击的回调
const fullScreen = () => {
  //DOM对象的一个属性:可以用来判断当前是不是全屏的模式【全屏:true,不是全屏:false】
  let full = document.fullscreenElement
  //切换成全屏
  if (!full) {
    //文档根节点的方法requestFullscreen实现全屏
    document.documentElement.requestFullscreen()
  } else {
    //退出全屏
    document.exitFullscreen()
  }
}
</script>

<style scoped lang="scss"></style>

六、登录获取用户信息(TOKEN)

登录之后页面(home)上来就要获取用户信息。并且将它使用到页面中

1.home组件挂载获取用户信息

src\views\home\index.vue

<script setup lang="ts">
//引入组合式API生命周期函数
import { onMounted } from "vue";
//获取仓库
import useUserStore from "@/store/modules/user";
let userStore = useUserStore();
onMounted(() => {
  userStore.userInfo();
});
</script>

2.小仓库中定义用户信息以及type声明

src\store\modules\user.ts

 

src\store\modules\types\type.ts

import type { RouteRecordRaw } from "vue-router";
//定义小仓库数据state类型
export interface UserState{
    token: string | null;
    menuRoutes: RouteRecordRaw[];
    username: string;
    avatar: string
}

 3.请求头添加TOKEN

src\utils\request.ts

//引入用户相关的仓库
import useUserStore from '@/store/modules/user'



。。。。。。



//第二步:request实例添加请求与响应拦截器
request.interceptors.request.use(config => {
     //获取用户相关的小仓库,获取token,登录成功以后携带个i服务器
    const userStore = useUserStore()
    if (userStore.token) {
        config.headers.token = userStore.token
      }
    //config配置对象:headers属性请求头,经常给服务器携带公共参数
    //返回配置对象
    return config;
});

 4.小仓库发请求并且拿到用户信息

src\store\modules\user.ts

//获取用户信息方法
        async userInfo() {
          //获取用户信息进行存储
          let result = await reqUserInfo()
          if (result.code == 200) {
            this.username = result.data.checkUser.username
            this.avatar = result.data.checkUser.avatar
          }
        },

5.更新tabbar的信息(记得先引入并创建实例)

src\layout\tabbar\setting\index.vue

//引入用户相关的仓库
import useUserStore from "@/store/modules/user";
let userStore = useUserStore();

七、退出功能

7.1.退出登录绑定函数,调用仓库函数:

src\layout\tabbar\setting\index.vue

//退出登陆点击的回调
const logout = () => {
  //第一件事:需要项服务器发请求【退出登录接口】(我们这里没有)
  //第二件事:仓库当中和关于用户的相关的数据清空
  userStore.userLogout()
  //第三件事:跳转到登陆页面
  $router.push({ path: '/login', query: { redirect: $route.path } })
}

7.2pinia仓库

 src\store\modules\user.ts

 //退出登录
        userLogout() {
            //当前没有mock接口(不做):服务器数据token失效
            //本地数据清空
            this.token = ''
            this.username = ''
            this.avatar = ''
            REMOVE_TOKEN()
        },

7.3 退出登录,路由跳转

注意:携带的query参数方便下次登陆时直接跳转到当时推出的界面

个人觉得这个功能没什么作用,因为一般都是跳到首页

 src\layout\tabbar\setting\index.vue

//退出登陆点击的回调
const logout = () => {
  //第一件事:需要项服务器发请求【退出登录接口】(我们这里没有)
  //第二件事:仓库当中和关于用户的相关的数据清空
  userStore.userLogout()
  //第三件事:跳转到登陆页面
  $router.push({ path: '/login', query: { redirect: $route.path } })
}

7.4 登录页面进行判断

src\views\login\index.vue

 

八、路由鉴权

1.进度条

1.1.在入口文件main.ts引入

1.2.安装进度条

pnpm i nprogress

1.3.引入并使用 

src\permission.ts

//路由鉴权:鉴权:项目当中路由能不能被访问的权限
import router from '@/router'
import nprogress from 'nprogress'
//引入进度条样式
import 'nprogress/nprogress.css'
//全局前置守卫
router.beforeEach((to: any, from: any, next: any) => {
  //访问某一个路由之前的守卫
  nprogress.start()
  next()
})

//全局后置守卫
router.afterEach((to: any, from: any) => {
  // to and from are both route objects.
  nprogress.done()
})

//第一个问题:任意路由切换实现进度条业务 ----nprogress

效果图:

2.路由鉴权

src\permission.ts

//路由鉴权:鉴权:项目当中路由能不能被访问的权限
import router from '@/router'
import setting from './setting'
import nprogress from 'nprogress'
//引入进度条样式
import 'nprogress/nprogress.css'
//进度条的加载圆圈不要
nprogress.configure({ showSpinner: false })
//获取用户相关的小仓库内部token数据,去判断用户是否登陆成功
import useUserStore from './store/modules/user'
//为什么要引pinia
import pinia from './store'
const userStore = useUserStore(pinia)

//全局前置守卫
router.beforeEach(async (to: any, from: any, next: any) => {
  //网页的名字
  document.title = `${setting.title}-${to.meta.title}`
  //访问某一个路由之前的守卫
  nprogress.start()
  //获取token,去判断用户登录、还是未登录
  const token = userStore.token
  //获取用户名字
  let username = userStore.username
  //用户登录判断
  if (token) {
    //登陆成功,访问login。指向首页
    if (to.path == '/login') {
      next('/home')
    } else {
      //登陆成功访问其余的,放行
      //有用户信息
      if (username) {
        //放行
        next()
      } else {
        //如果没有用户信息,在收尾这里发请求获取到了用户信息再放行
        try {
          //获取用户信息
          await userStore.userInfo()
          next()
        } catch (error) {
          //token过期|用户手动处理token
          //退出登陆->用户相关的数据清空
          userStore.userLogout()
          next({ path: '/login', query: { redirect: to.path } })
        }
      }
    }
  } else {
    //用户未登录
    if (to.path == '/login') {
      next()
    } else {
      next({ path: '/login', query: { redirect: to.path } })
    }
  }
  next()
})

//全局后置守卫
router.afterEach((to: any, from: any) => {
  // to and from are both route objects.
  nprogress.done()
})

//第一个问题:任意路由切换实现进度条业务 ----nprogress
//第二个问题:路由鉴权
//全部路由组件 :登录|404|任意路由|首页|数据大屏|权限管理(三个子路由)|商品管理(4个子路由)

//用户未登录 :可以访问login 其余都不行
//登陆成功:不可以访问login 其余都可以

路由鉴权几个注意点: 

  1. 获取用户小仓库为什么要导入pinia?

  1. 个人理解:之前在app中是不需要导入pinia的,是因为我们这次的文件时写在和main.ts同级的下面,所以我们使用的时候是没有pinia的。而之前使用时app已经使用了pinia了,所以我们不需要导入pina。
  2. 全局路由守卫将获取用户信息的请求放在了跳转之前。实现了刷新后用户信息丢失的功能。

九、 真实接口替代mock接口

接口文档:

Swagger UI

Swagger UI

 9.1修改服务器域名

将.env.development,.env.production .env.test,三个环境文件下的服务器域名写为:

9.2代理跨域

vite.config.ts

import { loadEnv } from 'vite'
。。。。。。
export default defineConfig(({ command, mode }) => {
  //获取各种环境下的对应的变量
  let env = loadEnv(mode, process.cwd())
  return {
    。。。。。。。
    //代理跨域
    server: {
      proxy: {
        [env.VITE_APP_BASE_API]: {
          //获取数据服务器地址的设置
          target: env.VITE_SERVE,
          //需要代理跨域
          changeOrigin: true,
          //路径重写
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },
  }
})

9.3修改api

在这里退出登录有了自己的api

src\api\user\index.ts

//统一管理咱们项目用户相关的接口

import request from '@/utils/request'

import type {

    loginForm,

    loginResponseData,

    userResponseData,

} from './type'

//项目用户相关的请求地址

enum API {
    LOGIN_URL = '/admin/acl/index/login',
    USERINFO_URL = '/admin/acl/index/info',
    LOGOUT_URL = '/admin/acl/index/logout',
}
//登录接口
export const reqLogin = (data: loginForm) =>
    request.post<any, loginResponseData>(API.LOGIN_URL, data)
//获取用户信息

export const reqUserInfo = () =>

    request.get<any, userResponseData>(API.USERINFO_URL)

//退出登录

export const reqLogout = () => request.post<any, any>(API.LOGOUT_URL)

 9.4小仓库(user)

替换原有的请求接口函数,以及修改退出登录函数。以及之前引入的类型显示我们展示都设置为any

src\store\modules\user.ts

//创建用户相关的小仓库
import { defineStore } from 'pinia'
//引入接口
import { reqLogin, reqUserInfo, reqLogout } from '@/api/user'
import type { UserState } from './types/type'
//引入操作本地存储的工具方法
import { SET_TOKEN, GET_TOKEN, REMOVE_TOKEN } from '@/utils/token'
//引入路由(常量路由)
import { constantRoute } from '@/router/routes'

//创建用户小仓库
const useUserStore = defineStore('User', {
  //小仓库存储数据地方
  state: (): UserState => {
    return {
      token: GET_TOKEN(), //用户唯一标识token
      menuRoutes: constantRoute, //仓库存储生成菜单需要数组(路由)
      username: '',
      avatar: '',
    }
  },
  //处理异步|逻辑地方
  actions: {
    //用户登录的方法
    async userLogin(data: any) {
      //登录请求
      const result: any = await reqLogin(data)

      if (result.code == 200) {
        //pinia仓库存储token
        //由于pinia|vuex存储数据其实利用js对象
        this.token = result.data as string
        //本地存储持久化存储一份
        SET_TOKEN(result.data as string)
        //保证当前async函数返回一个成功的promise函数
        return 'ok'
      } else {
        return Promise.reject(new Error(result.data))
      }
    },
    //获取用户信息方法
    async userInfo() {
      //获取用户信息进行存储
      const result = await reqUserInfo()
      console.log(result)

      if (result.code == 200) {
        this.username = result.data.name
        this.avatar = result.data.avatar
        return 'ok'
      } else {
        return Promise.reject(new Error(result.message))
      }
    },
    //退出登录
    async userLogout() {
      const result = await reqLogout()
      if (result.code == 200) {
        //本地数据清空
        this.token = ''
        this.username = ''
        this.avatar = ''
        REMOVE_TOKEN()
        return 'ok'
      } else {
        return Promise.reject(new Error(result.message))
      }
    },
  },
  getters: {},
})
//对外暴露小仓库
export default useUserStore

9.5 退出登录按钮修改

src\layout\tabbar\setting\index.vue

9.6 路由跳转判断条件修改

src\permission.ts

十、接口类型的定义

src\api\user\type.ts

/*
 * @Author: huks
 * @Date: 2024-03-06 00:52:09
 * @LastEditTime: 2024-03-17 00:53:49
 * @Description: 
 */
//登录接口需要携带参数类型
export interface loginFormData {
    username: string
    password: string
  }
  
  //定义全部接口返回数据都有的数据类型
  export interface ResponseData {
    code: number
    message: string
    ok: boolean
  }
  //定义登录接口返回数据类型
  export interface loginResponseData extends ResponseData {
    data: string
  }
  
  //定义获取用户信息返回的数据类型
  export interface userInfoResponseData extends ResponseData {
    data: {
      routes: string[]
      button: string[]
      roles: string[]
      name: string
      avatar: string
    }
  }
  

注意:在src\store\modules\user.ts以及src\api\user\index.ts文件中对发请求时的参数以及返回的数据添加类型定义

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Vue3 + TypeScript 是一种常用的前端开发技术组合,它结合了Vue框架的便捷性和TypeScript的类型检查能力,可以提高项目的可维护性和开发效率。下面是一些Vue3 + TypeScript项目经验的介绍: 1. 项目初始化:使用Vue CLI创建一个Vue3项目,并选择TypeScript作为项目的语言。这样可以自动生成一些基本的配置和文件结构,方便快速开始开发。 2. 类型定义:在Vue3 + TypeScript项目,可以使用TypeScript的类型系统来定义组件的props、data、computed等属性的类型,以及事件的参数类型等。这样可以在编码过程及早发现潜在的类型错误,提高代码的健壮性。 3. 组件开发:在Vue3 + TypeScript项目,可以使用装饰器(decorators)来定义组件选项,例如使用@Prop来定义props属性的类型,使用@Emit来定义事件的参数类型。这样可以更清晰地描述组件的接口和行为。 4. 路由管理:在Vue3 + TypeScript项目,可以使用Vue Router进行路由管理。通过定义路由的类型和参数类型,可以在编码过程获得更好的类型提示和错误检查。 5. 状态管理:在Vue3 + TypeScript项目,可以使用Vuex进行状态管理。通过定义状态的类型和操作的参数类型,可以提高代码的可读性和可维护性。 6. 第三方库的使用:在Vue3 + TypeScript项目,可以使用第三方库,并为其编写类型声明文件,以获得更好的类型检查和代码提示。例如,可以使用axios发送HTTP请求,并为其编写类型声明文件,以确保请求参数和响应数据的类型正确。 7. 单元测试:在Vue3 + TypeScript项目,可以使用Jest等测试框架进行单元测试。通过编写类型安全的测试用例,可以提高代码的质量和可靠性。 8. 构建和部署:在Vue3 + TypeScript项目,可以使用Webpack等构建工具进行项目的打包和优化。通过配置合适的TypeScript编译选项和Webpack插件,可以生成高效的生产环境代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值