Vue.js3 + Vite + TypeScript 开发项目

安装Vite

npm init vite@latest

vite创建.png

初始目录结构.png

安装eslint

  • npm install eslint --save-dev

  • npx eslint --init (初始化eslint配置)

  • 添加 npm script 验证脚本

"scripts": {
    ...
  "lint": "eslint src/**/*.{js,jsx,vue,ts,tsx} --fix",
}
规则.png

替换为vue3规则.png

编辑器集成eslint

  • 禁用Vetur
  • 安装eslint插件
  • 安装Volar插件

配置 git commit hook

npx mrm@2 lint-staged

"lint-staged": {
    "*.{js,jsx,vue,ts,tsx}": [
      "npm run lint",
      // "git add" 之前的版本需要手动把 lint 过程中修改的代码手动 add,新版本不需要了
    ]
  }

vite-plugin-eslint

npm install vite-plugin-eslint --save-dev

  • vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintplugin from 'vite-plugin-eslint'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(),eslintplugin({'配置参数'})]
})

commit 提交规范

Commit message 和 Change log 编写指南
Install commitlint

提交type.png

Vue3 ts支持

  • vue文件添加ts声明
<script lang="ts">
  • 要让 TypeScript 正确推断 Vue 组件选项中的类型,需要使用 defineComponent 全局方法定义组件:
import { defineComponent } from 'vue'

const Component = defineComponent({
  // 已启用类型推断
})

//如果你使用的是[单文件组件](https://v3.cn.vuejs.org/guide/single-file-component.html),则通常会被写成:
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  // 已启用类型推断
})
</script>

vue3中ts相关写法

Vue3 中的<script setup>语法

vue3支持三种写法

  • Option Api
  • Composition API
  • <script setup> (Composition API 的语法糖)

defineProps,defineEmits,defineExpose,withDefaults 为全局的 可以不import 直接使用

eslint配置 .eslintrc.js 中配置则不会对此进行引入校验

module.exports = {
  globals: {
    defineProps: 'readonly',
    defineEmits: 'readonly',
    defineExpose: 'readonly',
    withDefaults: 'readonly'
  }
}

script-setup用法
该连接为github仓库连接 可能网络问题打不开

script-setup用法 一个中文博客

配置转换JSX和TSX

vite中JSX

  • npm i @vitejs/plugin-vue-jsx --save-dev
    vite.config.ts配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx({
      //配置选项
    })
  ]
})

使用

<template>
 <abc/>
</template>
<script setup lang="tsx">
 const abc = <h1>abc</h1>
</script>

单独一个组件文件 text.tsx

//函数式组件
export default () => {
  return (
    <div>
      <h1>text函数式组件</h1>
    </div>
  )
}

//需要有状态的组件 options API
import { defineComponent } from '@vue/runtime-core'

export default defineComponent({
  props:{
    msg:{
      type:String
    }
  },
  render(){
    return (
      <div>{ this.msg }</div>
    )
  }
})

//需要有状态的组件 组合式 API
import { defineComponent,ref } from '@vue/runtime-core'

interface PropsType {
  msg:string
}
export default defineComponent({
  props:{
    msg:{
      type:String
    }
  },
  setup(){
    const count = ref (0)
    return (props:PropsType ) => (
      <div>
        <p>{props.msg}</p>
        <p>{count}</p>
      </div>
    )
  }
})

初始化VueRouter

  • 安装vue-router

npm install vue-router@4

// src\router\index.ts
import { createRouter, RouteRecordRaw, createWebHashHistory } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    component: () => import('../views/home/index.vue')
  },
  {
    path: '/login',
    component: () => import('../views/login/index.vue')
  }
]

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

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

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

初始化 Vuex

  • 安装Vuex

npm install vuex@next --save

// src\store\index.ts
import { createStore } from 'vuex'

const store = createStore({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
  modules: {}
})

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

createApp(App)
  .use(router)
  .use(store)
  .mount('#app')
vuex.png

usestore-组合式函数类型声明

结合ts优化后

//store/index.ts
import { createStore, useStore as baseUseStore, Store } from 'vuex'
import { InjectionKey } from 'vue'

export interface State {
  count: number
}

// 定义 injection key
export const key: InjectionKey<Store<State>> = Symbol('store')

export const store = createStore<State>({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  }
})

// 定义自己的 `useStore` 组合式函数 //调用该 useStore 可以推断出state属性及类型
export function useStore() {
  return baseUseStore(key)
}

export default store
// vuex.d.ts
import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'
import { State } from './store/index'

declare module '@vue/runtime-core' {
  // 声明自己的 store state
  // interface State {
  //   count: number
  // }

  // 为 `this.$store` 提供类型声明
  interface ComponentCustomProperties {
    $store: Store<State>
  }
}

配置模块路径别名

vite不自带@等路径别名

import xxx from '@/views/xxx.vue'  //不支持src别名@符  所以要手动配置
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 注意:在 ts 模块中加载 node 核心模块需要安装 node 的类型补充模块:npm i -D @types/node
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  ...
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  }
})
// tsconfig.json   
//所有以@开头的都指向src
{
  "compilerOptions": {
    ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  ...
}

使用

// js
import xxx from '@/api/xxx.ts'

// html
<img src="@/assets/logo.png">

// css
@import url("@/styles/index.scss");
background: url("@/assets/logo.png");

CSS样式管理

vite css配置 文档
Vite 也同时提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持。没有必要为它们安装特定的 Vite 插件,但必须安装相应的预处理器依赖:

# .scss and .sass
npm install -D sass

# .less
npm install -D less

# .styl and .stylus
npm install -D stylus

如果是用的是单文件组件,可以通过 <style lang="sass">(或其他预处理器)自动开启。

  • 注意事项:

● Vite 为 Sass 和 Less 改进了 @import 解析,以保证 Vite 别名也能被使用。
● 另外,url() 中的相对路径引用的,与根文件不同目录中的 Sass/Less 文件会自动变基以保证正确性。
● 由于 Stylus API 限制,@import 别名和 URL 变基不支持 Stylus。
● 你还可以通过在文件扩展名前加上 .module 来结合使用 CSS modules 和预处理器,例如 style.module.scss。

深度作用操作符新语法::deep(<inner-selector>)
样式目录结构

variables.scss # 全局 Sass 变量
mixin.scss # 全局 mixin
common.scss # 全局公共样式
transition.scss # 全局过渡动画样式
index.scss # 组织统一导出

全局样式都写在 src/styles 目录下,每个页面自己对应的样式都写在自己的 .vue 文件之中。

// index.scss
@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './common.scss';

然后在 main.ts 中导入 index.scss:

//main.ts  这里仅仅是加载了全局样式,并不能实现在组件内直接使用全局变量样式
import "./styles/index.scss"

在单独组件里使用全局变量 需要单独加载

//xxx.vue
<style lang="scss" scoped>
@import '@/styles/variables.scss';
.font{
  color:$color;
}
</style>

要想在全局单文件中不用单独引入就可以使用scss全局变量 需要配置使用全局样式
配置使用全局样式变量
css-preprocessoroptions
vite.config.ts

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx({
      // 配置选项
    })
  ],
  resolve: {
    alias: {
      '@': path.join(__dirname, 'src')//绝对路径
    }
  },
  css: {
    preprocessorOptions: {
      // 给 sass-loader 传递选项
      sass: {
        // @/ 是 src/ 的别名
        // 所以这里假设你有 `src/variables.sass` 这个文件
        // 注意:在 sass-loader v8 中,这个选项名是 "prependData"
        additionalData: `@import "@/styles/variables.scss"`
      },
      // 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效
      // 因为 `scss` 语法在内部也是由 sass-loader 处理的
      // 但是在配置 `prependData` 选项的时候
      // `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号
      // 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置
      scss: {
        additionalData: `@import "~@/variables.scss";`
      },
      // 给 less-loader 传递 Less.js 相关选项
      less: {
        // http://lesscss.org/usage/#less-options-strict-units `Global Variables`
        // `primary` is global variables fields name
        globalVars: {
          primary: '#fff'
        }
      }
    }
  }
})

自动注册全局组件

glob-impor
全局组件放在 components 目录下

- components
    + foo
    + index.vue
    + index.ts
  + bar
    + index.vue
    + index.ts
- main.ts
// main.ts

const app = createApp(App)

const modules = import.meta.globEager('./components/**/index.ts')

for (const path in modules) {
  app.use(modules[path].default)
}
// components/foo/index.ts

import { App } from '@vue/runtime-dom'
import Component from './index.vue'

export default {
  install (app: App) {
    app.component('Foo', Component)
  }
}
// components/bar/index.ts

import { App } from '@vue/runtime-dom'
import Component from './index.vue'

export default {
  install (app: App) {
    app.component('Bar', Component)
  }
}

服务端交互

**基于axios封装请求模块

  • 安装axios

npm i axios
基础配置

//src/utils/request.ts
import axios from 'axios'

const request = axios.create({
  baseURL: '' //基础请求路径
})

// 请求拦截器
request.interceptors.request.use(
  config => {
    // 统一设置用户身份 Token
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// 响应拦截器
request.interceptors.response.use(
  response => {
    // 统一处理响应错误,例如 token 无效、服务端异常等
    return response
  },
  err => {
    return Promise.reject(err)
  }
)

export default request
/**
 * 公共基础接口封装
 */
import request from '@/utils/request'

export const getLoginInfo = () => {
  return request({
    method: 'GET',
    url: '/login/info'
  })
}
/**
 * 组件中使用
 */
import { getLoginInfo } from '@/api/common'
import { onMounted } from '@vue/runtime-core'

onMounted(() => {
  getLoginInfo().then(res => {
    console.log(res)
  })
})

多环境 baseURL

环境变量和模式

# .env.development
# 开发模式下加载的环境变量
VITE_API_BASEURL=http://a.com
# .env.production

# 生产模式下加载的环境变量
VITE_API_BASEURL=http://b.com
// src\utils\request.ts

const request = axios.create({
  // localhost:8080/xxx
  // abc.com/xxx
  // test.com/xxx
  baseURL: import.meta.env.VITE_API_BASEURL
})
//env.d.ts中对类型声明
/// <reference types="vite/client" />

declare module '*.vue' {
  import { DefineComponent } from 'vue'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>
  export default component
}

interface ImportMetaEnv {
  VITE_API_BASEURL: string
  // 更多环境变量...
}

跨域问题

推荐方案

  • 开发环境
    • 在服务端配置 CORS。
    • 配置开发服务器代理,比如 vite-server.proxy
  • 生产环境
    • 在服务端配置 CORS。
    • 配置生产服务器代理,比如 nginx。

CORS
CORS 全称为 Cross Origin Resource Sharing(跨域资源共享)。这种方案对于前端来说没有什么工作量,和正常发送请求写法上没有任何区别,工作量基本都在后端(其实也没啥工作量,就是配置一些 HTTP 协议)。

服务器代理
可能有些后端开发人员觉得配置 CORS 麻烦不想搞,那纯前端也是有解决方案的。

在开发模式下可以下使用开发服务器的 proxy 功能,比如 vite - server.proxy

但这种方法在生产环境是不能使用的。在生产环境中需要配置生产服务器(比如 nginx、Apache 等)进行反向代理。在本地服务和生产服务配置代理的原理都是一样的,通过搭建一个中转服务器来转发请求规避跨域的问题。


图片.png
//vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      // 字符串简写写法
      '/foo': 'http://localhost:4567',
      // 选项写法
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
      // 正则表达式写法
      '^/fallback/.*': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/fallback/, '')
      },
      // 使用 proxy 实例
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        configure: (proxy, options) => {
          // proxy 是 'http-proxy' 的实例
        }
      }
    }
  }
})

初始化 Element Plus

npm install element-plus --save
Element Plus快速开始

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容