Vue3加TS使用Class写法

这种写法提案貌似被取消了,GitHub上也停留在rc1版本了,已经一年左右没有提交代码了,确实用Class写Vue确实有点奇怪,现在有更优的选择,如<script setup>写法,博客里面对Vuex,以及Vue-Router的封装还可以用,Class的写法不提倡使用了。

总在盼望,总在失望,日子还不都这样。代码也需要雅俗共赏,博客亦如此。

鄙人不善前端,但是又会一点,近日后端写的乏味,便拎起前端端详端详,一日不见如隔三秋的Vue,让我感觉到生疏,Vue3的语法奇奇怪怪,官网上的写法,我是并不能很理解,或者说就是不易于后端程序员理解,加上Ts这个JS的类型系统语言,让我深深的感觉到,眉眼不如初,让我怎敢相认。

但是光说不练,是很难体会到其中的玄妙的,我便开始了时隔好多月的Vue3重逢之旅。

打开VsCode,打开终端,输入vue create vue3_demo。一顿框选,主要记得要选择Vue3以及TS的支持。之后,看着进度条,不停在变动,嘴角疯狂上扬,邪魅一笑,还是内味。

进度条加载完毕,VsCode,文件-> 将文件夹添加到工作区,打开终端,yarn run serve,回车,嘴角继续疯狂上扬,

绿色的美好提示Compiled successfully,项目成功启动,复制url, 浏览器打开,回来了,他们都回来了。

娴熟的打开Home.vue让我看看,这几月没见,变成什么样了。

import { Vue } from 'vue-class-component'

export default class Home extends Vue {}

看到了这两行代码,让我陷入了深思,嗯,这啥玩意,最佳实践嘛,本着实践的态度,试试基本的文档中的新语法,不过有一说一我甚至不知道,新语法的代码我甚至不知道往哪里写,这tm不是欺负老实人嘛。程序员嘛,喜欢学习,搜索vue-class-component,原来是以类的方式写代码啊,Over,Java入门的我写起TS到也不觉得生疏,语法虽有区别,但理念都是一样的。声明变量时需要声明属性,定义方法时需要规定入参格式和返回值格式。况且Ts更多的时候只是一种约束,并不是一种规则。这句话很重要,还记得SpringBoot的一个经典理论,约束大于规则。编程的哲学是通用的。

大致知道了怎么写之后,容我新起一行,坑就如约而至,父子组件传值的时候,我不知道props在什么地方写,还有监听属性等等。这些问题待我一一解答

最佳实践

创建项目

Vue3加Ts必须,VueRouter和Vuex,一般来说项目中都会用到,就都加上。

我建议不启用代码质量检查和ESlint,原因,Vue3和ts能很好的兼容,在vue-class-component的加持下,实际项目中可能百分之九十九的代码都是使用TS编写,Ts天生就有静态检查,再使用EsLint,在开发中反倒是有点徒增功耗。

在Vue的可视化项目创建平台中,勾选这几项就行。如图所示,我并没有打算写保姆教程。
创建项目

路由模式,主要分为两种一种是带#的还有一种就是不带的,看个人喜好,我不讨厌这个#。默认就是带的。css预处理器编译,一般都是选用node-sass。就选这个就行了。

第三方库和CSS组件

第三方组件:element-plus这个组件库在写后台管理系统的时候比较好用,更多的时候是提供一个组件,并不能用来写样式。而且我是打算写一个差不多的前台内容系统,考虑到暗黑模式的兼容,我并不能直接开箱即用里面的组件。

CSS框架:Windi CSS,被称作为下一代的CSS框架,天然支持暗黑模式,弥补上面组件库样式的不足,而且写起来很舒服。

Jquery:JQ这个会引起一点争议,不要提到Jquery就是什么过时啦,都是上个世纪的东西了。Jq一直在更新,Jq适配了Ts, Jq的兼容性很好,JQuery和Vue能一起使用。虽说感觉会很low,但是能抓到老鼠的猫就是好猫,主要原因还是,鄙人不善前端!!。

为了实现一个多行文本输入高度自适应,嗯,就是很尴尬的那种。挺难的,Element实现方式的源码我看了,有点看不懂,直接用Jquery能实现,但是不完美,就是为了追求我认为的完美,挺难的。

Axios:用来通信,性能优异,好处多多。

安装

packegt.json


{
  "name": "ql_blog",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
  "dependencies": {
    "@element-plus/icons": "^0.0.11",
    "@types/jquery": "^3.5.6",
    "axios": "^0.21.1",
    "core-js": "^3.6.5",
    "element-plus": "^1.1.0-beta.8",
    "jquery": "^3.6.0",
    "vue": "^3.0.0",
    "vue-class-component": "^8.0.0-0",
    "vue-router": "^4.0.0-0",
    "vuex": "^4.0.0-0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-typescript": "~4.5.0",
    "@vue/cli-plugin-vuex": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^8.0.2",
    "typescript": "~4.1.5",
    "vue-cli-plugin-windicss": "~1.0.4"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

引入Jquery

项目根目录中有的话添加里面的内容即可,没有的话直接新建即可。

vue.config.js

var webpack = require('webpack')

// 这里只列一部分,具体配置参考文档
module.exports = {
  // 开发服务器代理
  devServer: {
    proxy: {
      '^/api': {
        target: 'http://localhost:8001',
        ws: true,
        changeOrigin: true
      }
    }
  },
  configureWebpack: {
    plugins: [
      // 引入Jquery
      new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
        'windows.jQuery': 'jquery'
      })
    ]
  }
}

axios简单的封装

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import qs from 'qs'
import { ElMessage } from 'element-plus'

let axiosInstance: AxiosInstance = axios.create({
  baseURL: '/api',
  // 延迟
  timeout: 3000,
});

// axios实例拦截请求
axiosInstance.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    console.log(config);
    removePending(config) // 在请求开始前,对之前的请求做检查取消操作
    addPending(config) // 将当前请求添加到 pending 中
    config.headers.sa_token = 'ken'
    return config;
  },
  (error: any) => {
    console.log('请求错误拦截' + error);

    return Promise.reject(error);
  }
)

// axios实例拦截响应
axiosInstance.interceptors.response.use(
  (response: AxiosResponse) => {
    removePending(response)
    return response;
  },
  // 请求失败
  (error: any) => {
    if (axios.isCancel(error)) {
      console.log('repeated request: ' + error.message)
    } else {
      // handle error code
      // 错误抛到业务代码
      error.data = {}
      error.data.msg = '请求超时或服务器异常,请检查网络或联系管理员!'
      ElMessage.error(error.data.msg)
    }
    return Promise.reject(error)
  }
);

// 声明一个 Map 用于存储每个请求的标识 和 取消函数
const pending = new Map()
/**
 * 添加请求
 * @param {Object} config 
 */
const addPending = (config: AxiosRequestConfig) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
    if (!pending.has(url)) { // 如果 pending 中不存在当前请求,则添加进去
      pending.set(url, cancel)
    }
  })
}
/**
 * 移除请求
 * @param {Object} config 
 */
const removePending = (config: AxiosRequestConfig) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  if (pending.has(url)) { // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
    const cancel = pending.get(url)
    cancel(url)
    pending.delete(url)
  }
}

/**
 * 清空 pending 中的请求(在路由跳转时调用)
 */
export const clearPending = () => {
  for (const [url, cancel] of pending) {
    cancel(url)
  }
  pending.clear()
}

export default axiosInstance

路由的简单配置(Vue-router)

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    redirect: '/home'
  },
  {
    path: '/home/:type',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    alias: ['/home'],
    props: true
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import('@/views/About.vue')
  }
]

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

// 全局前置拦截
router.beforeEach((to, from, next) => {
  console.log('全局前置拦截')
  console.log(to)
  console.log(from)

  next()
})
export default router

全局的状态管理(Vuex)

import { createStore, Store } from 'vuex'

declare module '@vue/runtime-core' {
  interface Photo {
    src: string;
  }
  // declare your own store states
  interface State {
    dark: boolean,
    count: number,
    user: {
      name: string,
      age: number,
      photoList: Array<Photo>
    }
  }

  // provide typings for `this.$store`
  interface ComponentCustomProperties {
    $store: Store<State>
  }
}
export default createStore({
  state: {
    dark: localStorage.getItem('dackModel') === undefined ? false : JSON.parse(localStorage.getItem('dackModel') + ''),
    count: 112,
    user: {
      name: '',
      age: 0,
      photoList: [{
        src: 'test'
      }]
    }
  },
  getters: {

  },
  mutations: {
    switchDarkMode(state) {
      state.dark = !state.dark
      if (state.dark) {
        $('html').addClass('dark')
        localStorage.setItem('dackModel', JSON.stringify(true))
      } else {
        $('html').removeClass('dark')
        localStorage.setItem('dackModel', JSON.stringify(false))
      }
    }
  },
  actions: {
  },
  modules: {
  }
})

项目的main.ts

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from './router'
import 'windi.css'
import store from './store'

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

少女马尾辫 夜晚 电脑 听音乐


Vue3加TS使用Class写法
https://wangijun.com/2021/09/01/vue-01/
作者
无良芳
发布于
2021年9月1日
许可协议