Vue3 + Vite2 + Ts 项目搭建

879 阅读6分钟

使用vite创建项目

npm init @vitejs/app [project-name] -- --template

或

yarn yarn create @vitejs/app [project-name] --template

这里我才用yarn 的方式安装,选择vue -> vue-ts,进入项目安装依赖并启动即可。

代码规范(可跳过)

安装Eslint及插件

# eslint 安装
yarn add eslint --dev

# vue插件安装 eslint默认规则的补充
yarn add eslint-plugin-vue --dev

# prettier插件安装 解决prettier和eslint的规则冲突
yarn add eslint-plugin-prettier --dev

# ts插件安装 eslint默认规则的补充
yarn add @typescript-eslint/eslint-plugin --dev

# ts解析器安装 替换默认解析器
# Eslint默认的解析器无法识别部分TS代码
yarn add @typescript-eslint/parser --dev

# 解决ESLint中的样式规范和prettier中样式规范的冲突
# 以prettier的样式规范为准,ESLint 中的样式规范自动失效
yarn add eslint-config-prettier --dev

配置.eslintrc.js

项目的根目录下创建一个 .eslintrc.js,内容如下:

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2021: true,
  },
  parser: 'vue-eslint-parser',
  // 扩展规则
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier',
  ],
  parserOptions: {
    ecmaVersion: 12,
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
    },
  },
  // 注册插件
  plugins: ['vue', '@typescript-eslint', 'prettier'],
  // 规则 根据自己需要增加
  rules: {
    'no-var': "error",
    '@typescript-eslint/consistent-type-definitions': [
        "error",
        "interface"
    ]
  }
}

配置 .eslintignore

项目的根目录下创建一个 .eslintignore,内容如下:

# 忽略以下目录的eslint校验
node_modules
dist

安装prettier

yarn add prettier --dev

配置 .prettier.js

项目的根目录下创建一个 .prettierrc.js,内容如下:

// 根据自己需要修改
module.exports = {
  tabWidth: 2,
  semi: false,
  singleQuote: true,
}

配置 .prettierignore

# 忽略以下目录的代码格式化
node_modules
dist

package.json

新增两条脚本,分别做eslint校验和代码格式化

{
  "script": {
    ...
    "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
    "prettier": "prettier --write ."
  }
}

配置后,跑下命令检验成果,如果规则没生效,尝试重启vscode

#eslint校验
yarn lint

#代码自动格式化
yarn prettier

配置husky

husky 可以为git客户端增加git hooks,比如在这里需要用到的pre-commit

在提交commit的时候,做一个eslint校验代码格式化的操作

为了避免这个步骤耗时过长,引入lint-staged,一个仅仅过滤出 Git 代码暂存区文件(被 git add 的文件)的工具,仅对暂存区文件做代码校验和格式化

# 通过mrm安装lint-staged,这一步会同时把husky拉下来
# 并在package.json生成初始配置
# 执行之前,确保项目已经git初始化,否则会报.git不存在
yarn add mrm --dev
npx mrm lint-staged

安装完查看package.json

"scripts": {
    ...
    "prepare": "husky install"
},
"devDependencies": {
    ...
    "husky": ">=6",
    "lint-staged": ">=10",
    "mrm": "^3.0.10",
},
"lint-staged": {...}

这里我们修改默认配置,以支持自动代码校验和格式化

/* 新增 */
"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
},
/* 修改 */
"lint-staged": {
    "*.{ts,tsx,vue,js,jsx}": [
      "yarn lint",
      "prettier --write",
      "git add"
    ]
}

至此代码规范配置完成,尝试修改不符合规则(爆红)的代码,然后提交,看看代码是否被格式化

配置vite.config.ts

# 安装@types/node引入node声明文件
# 让ts编译器识别node的模块和变量
yarn add @types/node --dev

基础配置

/* eslint-disable camelcase */
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  base: './', // 打包路径
  server: { // 开发配置
    host: '0.0.0.0',
    port: 8080,
    open: true,
    https: false,
    proxy: {},
  },
  plugins: [vue()], // 插件
})

修改项目目录下的tsconfig.node.json

/* 该文件被tsconfig.json引用,用于声明node的编译选项 */
{
  "compilerOptions": {
    /* 新增 allowSyntheticDefaultImports ,使node支持通过import引入commonjs模块 */
    "allowSyntheticDefaultImports": true, 
    "composite": true,
    "module": "esnext",
    "moduleResolution": "node"
  },
  "include": ["vite.config.ts"]
}

配置文件引用别名 alias

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

const resolve = (path: string) => join(__dirname, path)

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    // 根据需要新增
    alias: {
      '@': resolve('src'),
    },
  }
})

修改tsconfig.json

{
  "compilerOptions": {
    ...
    /* 使ts编译器识别引用别名 */
    "baseUrl": ".",
    "paths": {
      "@/*":["src/*"]
    }
  }
}

配置scss预处理器

# 安装sass 和 sass-loader
# vite原生支持sass预处理器,我们只需要安装依赖即可
yarn add sass --dev
yarn add sass-loader --dev

配置全局scss变量 在src/assets/style下创建index.scss

vite.config.ts中配置

css: {
    preprocessorOptions: {
      scss: {
        additionalData: '@import "@/assets/style/index.scss";',
      },
    },
},

在项目中直接引用index.scss中声明的变量即可,无需重复引用

vue-router

# 安装vue-router@4
yarn add vue-router@4

根目录下创建router/index.ts

// Vue Router不再是一个类,而是一组函数
// 具体查看官方文档
import {
    createRouter, // 创建路由
    createWebHistory, // history模式
    RouteRecordRaw // 路由类型
} from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'index',
    // vite下,组件路径必须完整,带上vue后缀
    component: () => import('@/views/index.vue'),
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
})

export default router
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')
// App.vue

<template>
  <router-view />
</template>

axios请求封装

yarn add axios 安装axios

根目录下创建service目录

封装请求

// service/http/index.ts
// 封装请求

import axios from 'axios'
import { AxiosHttp } from './types'
import initDefaults from './defaults'
import initInterceptors from './interceptors'

initDefaults(axios) // 全局配置
initInterceptors(axios) // 拦截

// 请求函数组
const http: AxiosHttp = {
  $get(url, params) {
    return axios.get(url, { params }).then((res) => res.data)
  },
  $post(url, params) {
    return axios.post(url, { params }).then((res) => res.data)
  },
}

export default http
// service/http/default.ts
// 请求配置

import { Axios } from 'axios'

const initDefaults = (axios: Axios) => {
  axios.defaults.baseURL = ''
  axios.defaults.timeout = 10000
  axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
}

export default initDefaults
// service/http/interceptors.ts
// 拦截

import { Axios, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'

const initInterceptors = (axios: Axios) => {
  axios.interceptors.request.use(
    requestInterceptors,
    (err: AxiosError): AxiosError => err
  )
  axios.interceptors.response.use(
    responseInterceptors,
    (err: AxiosError): AxiosError => err
  )
}

const requestInterceptors = (
  config: AxiosRequestConfig<any>
): AxiosRequestConfig<any> => {
  console.log('requese interceptors')
  // do something...
  return config
}

const responseInterceptors = (
  response: AxiosResponse<any>
): AxiosResponse<any> => {
  console.log('response interceptors')
  // do something...
  return response
}

export default initInterceptors
// service/http/types.ts
// 类型接口定义

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

export interface AxiosHttp {
  $get<T>(url: string, params?: Object): Promise<HttpResponse<T>>
  $post<T>(url: string, params?: Object): Promise<HttpResponse<T>>
}

接口api

// service/api/testApi/index.ts
// 测试接口

import http from '@/service/http'
import { TestApi } from './types'

const testApi: TestApi = {
  test: (params) => http.$post('/test', params),
}

export default testApi
// service/http/testApi/types.ts
// 接口参数类型定义

interface TestParams {
  keyword: string
}

export interface TestApi {
  test: (params: TestParams) => Promise<any>
}

Pinia

yarn add pinia

main.ts中增加

import { createPinia } from 'pinia'

app.use(createPinia())

根目录下创建store目录

// store/age.ts

import { defineStore } from 'pinia'
import { useNameStore } from './name'

export const useAgeStore = defineStore('ageStore', {
  state: () => ({ age: 18 }),
  getters: {
    nextAge: (state) => state.age + 1,
    chakAge() {
      // 引用其他store的getter
      const nameStore = useNameStore()
      return nameStore.text + this.nextAge // 通过this引用当前store的其他getter和state
    },
  },
  // 更改状态的方法,支持同步和异步
  actions: {
    increment() {
      this.age++
    },
  },
})
// store/name

import { defineStore } from 'pinia'

export const useNameStore = defineStore('nameStore', {
  state: () => ({ name: 'chak' }),
  getters: {
    text: (state) => state.name + '的年龄是',
    myText() {
      return this.text
    },
  },
})

组件内使用

import { useAgeStore } from '@/store/age'

const ageStore = useAgeStore()
console.log(ageStore.chakAge) // chak的年龄是19

当在getter中声明形参state而不使用时,在函数体通过this只能访问到state而无法访问到其他getter,同时在组件里使用的时候,这个store所有getter都无法获取,依旧只能获取到state

不知道这是piniabug,还是我使用不当导致的。pinia版本是2.0.11,有清楚的小伙伴可以评论区告诉我一下谢谢!!

Element-plus

# 安装element-plus
yarn add element-plus

# 组件自动引入
yarn add unplugin-vue-components unplugin-auto-import --dev

为了减少项目体积,采用按需引入,官方推荐的自动引入的方法,主要用到两个插件,需要在vite中配置

unplugin-vue-components On-demand components auto importing for Vue.

组件自动引入

unplugin-auto-import Auto import APIs on-demand for Vite, Webpack, Rollup and esbuild. With TypeScript support. Powered by unplugin.

模块自动引入

// element-plus 按需引入配置
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

plugins: [
    ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
],

VueUse

VueUse is a collection of utility functions based on Composition API.

基于 Composition API 的实用函数集合

# 安装
yarn add @vueuse/core
// 组件中使用
import { useMouse } from '@vueuse/core'

const { x, y } = useMouse()
console.log(x.value)
console.log(y.value)

VueUse有丰富的工具函数和用法,本文不做探究

完成

至此大致的框架就已经搭建完毕,根据自己需要再进行增加或删改,之后会搭建后台,基础组件封装等,篇幅有限,另有其他文章介绍,文章有误的地方还请指正,谢谢!!

参考

部分内容参考自 mp.weixin.qq.com/s/3es6ryoTH…