• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

最详细的搭建 vue3+ts+vite项目的教程和步骤

武飞扬头像
Luke
帮助620

项目基于以下相关技术搭建

  • vue3
  • typescript
  • vite
  • pnpm
  • vue-router
  • pinia
  • axios

初始化项目

兼容性注意

Vite 需要 Node.js 版本 >= 14.18.0。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。

执行命令创建项目

pnpm create vite my-vue-app --template vue-ts

引入eslint prettier stylelint husky

引入eslintprettier以保证代码的质量与代码的统一风格。

这里笔者就把该项目中使用的.eslintrc.js.preitterrc.js.stylelintrc.js配置文件放出来

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
    // 必须放在最后面
    'plugin:prettier/recommended'
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module'
  },
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    // "off" or 0 - 关闭规则
    // "warn" or 1 - 将规则视为一个警告
    // "error" or 2 - 将规则视为一个错误
    eqeqeq: 2 // 强制使用 === 和 !==
  }
}
// .prettierrc.js
module.exports = {
  // 一行的字符数,如果超过会进行换行,默认为80
  printWidth: 80,
  // 一个tab代表几个空格数,默认为80
  tabWidth: 2,
  // 是否使用tab进行缩进,默认为false,表示用空格进行缩减
  useTabs: false,
  // 字符串是否使用单引号,默认为false,使用双引号
  singleQuote: true,
  // 行位是否使用分号,默认为true
  semi: false,
  // 是否使用尾逗号,有三个可选值"<none|es5|all>"
  trailingComma: 'none',
  // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
  bracketSpacing: true
}
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-prettier',
    'stylelint-config-recommended-less',
    'stylelint-config-standard-vue'
  ],
  plugins: ['stylelint-order'],
  // 不同格式的文件指定自定义语法
  overrides: [
    {
      files: ['**/*.(less|css|vue|html)'],
      customSyntax: 'postcss-less'
    },
    {
      files: ['**/*.(html|vue)'],
      customSyntax: 'postcss-html'
    }
  ],
  ignoreFiles: [
    '**/*.js',
    '**/*.jsx',
    '**/*.tsx',
    '**/*.ts',
    '**/*.json',
    '**/*.md',
    '**/*.yaml'
  ],
  rules: {
    'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
    'selector-pseudo-element-no-unknown': [
      true,
      {
        ignorePseudoElements: ['v-deep']
      }
    ],
    'selector-pseudo-class-no-unknown': [
      true,
      {
        ignorePseudoClasses: ['deep']
      }
    ],
    // 指定样式的排序
    'order/properties-order': [
      'position',
      'top',
      'right',
      'bottom',
      'left',
      'z-index',
      'display',
      'justify-content',
      'align-items',
      'float',
      'clear',
      'overflow',
      'overflow-x',
      'overflow-y',
      'padding',
      'padding-top',
      'padding-right',
      'padding-bottom',
      'padding-left',
      'margin',
      'margin-top',
      'margin-right',
      'margin-bottom',
      'margin-left',
      'width',
      'min-width',
      'max-width',
      'height',
      'min-height',
      'max-height',
      'font-size',
      'font-family',
      'text-align',
      'text-justify',
      'text-indent',
      'text-overflow',
      'text-decoration',
      'white-space',
      'color',
      'background',
      'background-position',
      'background-repeat',
      'background-size',
      'background-color',
      'background-clip',
      'border',
      'border-style',
      'border-width',
      'border-color',
      'border-top-style',
      'border-top-width',
      'border-top-color',
      'border-right-style',
      'border-right-width',
      'border-right-color',
      'border-bottom-style',
      'border-bottom-width',
      'border-bottom-color',
      'border-left-style',
      'border-left-width',
      'border-left-color',
      'border-radius',
      'opacity',
      'filter',
      'list-style',
      'outline',
      'visibility',
      'box-shadow',
      'text-shadow',
      'resize',
      'transition'
    ]
  }
}

配置commitlint校验提交信息

安装依赖

pnpm add @commitlint/cli @commitlint/config-conventional -D

添加commitlint.config.js配置文件

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

添加commit-msggithook钩子

cat <<EEE > .husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm --no -- commitlint --edit "${1}"
EEE

使钩子可执行

chmod a x .husky/commit-msg

最后

现在提交信息就会进行commit信息的校验

提交信息的规则可以查看官方文档

上面的配置是采用官方的配置(config-conventional),更多配置可查看官方文档,还可以安装@commitlint/prompt-cli进行交互式commit信息。

配置别名

vite.config.ts中增加一下配置

下面是以@为例,如果需要添加其他额外的别名,请自行添加即可

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
 import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
 resolve: {
   alias: {
     '@': resolve(__dirname, './src')
   }
 },
  plugins: [vue()]
})

修改tsconfig.json配置,使vscode可正确提示别名路径

例如:输入@/,vscode就会提示该别名路径下对应的目录与文件,但是在ts文件下不会提示.vue文件,暂时还没找到问题所在todo

image.png

如果配置了其他别名,可在tsconfig.json中的compilerOptions.paths中增加配置。

{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "skipLibCheck": true,
   "baseUrl": "./",
   "paths": {
     "@/*": ["./src/*"]
   }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

引入vue-router

安装依赖

pnpm add vue-router

新建src/router/routes/index.ts

路由规则统一在src/router/routes/index.ts暴露出去。

import type { RouteRecordRaw } from 'vue-router'

// 获取modules中的路由规则
// https://cn.vitejs.dev/guide/features.html#glob-import
const modules = import.meta.globEager('./modules/**/*.ts')

// modules routes
const routes: RouteRecordRaw[] = []

Object.keys(modules).forEach((key) => {
  const modulesRoutes = modules[key].default || {}

  let modRoutesList = []
  if (Array.isArray(modulesRoutes)) {
    modRoutesList = [...modulesRoutes]
  } else {
    modRoutesList = [modulesRoutes]
  }

  routes.push(...modRoutesList)
})

// 根目录
const rootRoute: RouteRecordRaw = {
  path: '/',
  name: 'root',
  redirect: '/home'
}

// 404页面
const notFoundPage: RouteRecordRaw = {
  // vue-router@4的变化,舍弃*通配符
  // 官方文档:https://next.router.vuejs.org/zh/guide/migration/index.html#删除了-(星标或通配符)路由
  path: '/:pathMatch(.*)*',
  name: '404',
  component: () => import('@/views/common/404Page.vue')
}

export default [rootRoute, ...routes, notFoundPage]

新建src/router/routes/modules

该目录下放各个业务模块的路由规则,例如common.ts就放登录、首页这些页面路由

import type { RouteRecordRaw } from 'vue-router'

// 路由规则
const routes: RouteRecordRaw[] = [
  {
    path: '/home',
    name: 'home',
    component: () => import('@/views/HomePage.vue')
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/LoginPage.vue')
  }
]

export default routes

新建src/router/index.ts

import { createRouter, createWebHashHistory } from 'vue-router'
import type { App } from 'vue'

import routes from './routes/index'

const router = createRouter({
  // vueRouter@3版本的mode改成了history,hash模式配置createWebHashHistory,history模式配置createWebHistory
  history: createWebHashHistory(),
  routes
})

/**
 * 路由初始化函数
 * @param app
 */
export const setupRouter = (app: App<Element>) => {
  app.use(router)
}

export default router

改造main.ts,引入router

import { createApp } from 'vue'
import App from './App.vue'
import { setupRouter } from './router'

/**
 * 定义启动初始化函数
 */
const bootstrap = () => {
  const app = createApp(App)

  // 安装初始化路由
  setupRouter(app)

  app.mount('#app')
}

// 启动
bootstrap()

引入pinia

安装依赖

pnpm add pinia

新建src/store/index.ts

import { createPinia } from 'pinia'
import type { App } from 'vue'

const store = createPinia()

export const setupStore = (app: App<Element>) => {
  app.use(store)
}

export default store

src/main.ts中安装pinia

import { createApp } from 'vue'
import App from './App.vue'
import { setupRouter } from './router'
import { setupStore } from './store'

/**
 * 定义启动初始化函数
 */
const bootstrap = () => {
  const app = createApp(App)

  // 安装初始化store
  setupStore(app)

  // 安装初始化路由
  setupRouter(app)

  app.mount('#app')
}

// 启动
bootstrap()

使用pinia

新建src/types/store.d.ts

export interface UserInfo {
  userName: string
  userId: string | number
}

增加别名配置

vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
     '#': resolve(__dirname, './src/types')
    }
  },
  plugins: [vue()]
})

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "skipLibCheck": true,
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"],
     "#/*": ["src/types/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

user模块为例

新建src/store/modules/user.ts

import { defineStore } from 'pinia'
import { UserInfo } from '#/store'

interface State {
  userInfo: UserInfo
  token: string
}

const useUserStore = defineStore('userStore', {
  state(): State {
    return {
      userInfo: {
        userName: '',
        userId: ''
      },
      token: ''
    }
  },
  actions: {
    changeUserName(userInfo: UserInfo) {
      this.userInfo = userInfo
    }
  }
})

export default useUserStore

src/components/HelloWorld.vue中使用

<script setup lang="ts">
import { ref } from 'vue'
import useUserStore from '@/store/modules/user'

defineProps<{ msg: string }>()

const count = ref(0)

const userStore = useUserStore()
userStore.changeUserName({
  userName: 'haha',
  userId: 123
})
</script>

<template>
  <h1>userStore: {{ userStore.userInfo.userName }}</h1>
  <h1>{{ msg }}</h1>

  <p>
    Recommended IDE setup:
    <a href="https://code.visualstudio.com/" target="_blank">VS Code</a>
     
    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
  </p>

  <p>See <code>README.md</code> for more information.</p>

  <p>
    <a href="https://vitejs.dev/guide/features.html" target="_blank">
      Vite Docs
    </a>
    |
    <a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
  </p>

  <button type="button" @click="count  ">count is: {{ count }}</button>
  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
</template>

<style scoped>
a {
  color: #42b983;
}

label {
  margin: 0 0.5em;
  font-weight: bold;
}

code {
  background-color: #eee;
  padding: 2px 4px;
  border-radius: 4px;
  color: #304455;
}
</style>

引入axios

安装依赖

pnpm add axios

配置请求的baseUrl为环境变量

增加环境变量文件.env.development.env.production

定义环境变量VITE_API_BASE_URL,可根据项目自行修改

.env.development

VITE_API_BASE_URL = "/"

.env.production

VITE_API_BASE_URL = "/"

新增的环境变量声明

src/env.d.ts中增加以下声明

interface ImportMetaEnv {
  readonly VITE_API_BASE_URL: string
}

增加该配置,在使用import.meta.env会有ts的校验与提示

增加别名配置

vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
      '#': resolve(__dirname, './src/types'),
     utils: resolve(__dirname, './src/utils'),
     api: resolve(__dirname, './src/api')
    }
  },
  plugins: [vue()]
})

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "skipLibCheck": true,
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"],
      "#/*": ["src/types/*"],
     "utils/*": ["src/utils/*"],
     "api/*": ["src/api/*"]
    }
  }
}

新增src/types/axios.d.ts声明文件

新增以下接口声明,为axios响应的data的结构,各位可根据自己的项目实际情况进行修改

export interface CustomResponseType<T> {
  code: number
  message: string
  data: T
}

配置axios

新建src/utils/http/index.ts,以下为笔者简单的配置,具体的还是要根据项目的情况来修改配置

import axios, { AxiosRequestConfig, AxiosError } from 'axios'
import type { CustomResponseType } from '#/axios'

const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 30 * 1000,
  // 请求是否携带cookie
  withCredentials: true
})

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 可以处理token等
    return config
  },
  (err) => {
    return Promise.reject(err)
  }
)

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    const { status } = response
    if (status < 200 || status >= 300) {
      // 统一处理http错误,或者处理后抛到业务代码 TODO
    }

    return response
  },
  (err) => {
    const { status } = err.response

    // 根据返回的http状态码做不同的处理,比如错误提示等 TODO
    switch (status) {
      case 401:
        // 鉴权失败
        break
      case 403:
        // 没有权限
        break
      case 500:
        // 服务端错误
        break

      default:
        break
    }

    return Promise.reject(err)
  }
)

// 封装一层以更好的统一定义接口返回的类型
const request = <T>(
  config: AxiosRequestConfig
): Promise<CustomResponseType<T>> => {
  return new Promise((resolve, reject) => {
    service
      .request<CustomResponseType<T>>(config)
      .then((res) => {
        resolve(res.data)
      })
      .catch((err: Error | AxiosError) => {
        reject(err)
      })
  })
}

export default request

以请求用户信息为例

新建src/api/user/index.ts

如果该模块的接口比较多,可把枚举响应数据的声明放到单独的文件中维护,即下面的enum Apiinterface UserInfo

使用的时候,在页面中直接引入该方法调用即可

import request from 'utils/http'

// api枚举
enum Api {
  Login = '/login'
}

// 用户信息
interface UserInfo {
  userName: string
}

/**
 * 登录
 */
export const accountLogin = () => {
  return request<UserInfo>({
    url: Api.Login,
    method: 'get'
  })
}

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanefic
系列文章
更多 icon
同类精品
更多 icon
继续加载