这种写法提案貌似被取消了,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
让我看看,这几月没见,变成什么样了。
1 2 3
| 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的可视化项目创建平台中,勾选这几项就行。如图所示,我并没有打算写保姆教程。
![创建项目](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAAaADAAQAAAABAAAAAQAAAADa6r/EAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII=)
路由模式,主要分为两种一种是带#
的还有一种就是不带的,看个人喜好,我不讨厌这个#
。默认就是带的。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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| { "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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| var webpack = require('webpack')
module.exports = { devServer: { proxy: { '^/api': { target: 'http://localhost:8001', ws: true, changeOrigin: true } } }, configureWebpack: { plugins: [ new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', 'windows.jQuery': 'jquery' }) ] } }
|
axios简单的封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| 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, });
axiosInstance.interceptors.request.use( (config: AxiosRequestConfig) => { console.log(config); removePending(config) addPending(config) config.headers.sa_token = 'ken' return config; }, (error: any) => { console.log('请求错误拦截' + error);
return Promise.reject(error); } )
axiosInstance.interceptors.response.use( (response: AxiosResponse) => { removePending(response) return response; }, (error: any) => { if (axios.isCancel(error)) { console.log('repeated request: ' + error.message) } else { error.data = {} error.data.msg = '请求超时或服务器异常,请检查网络或联系管理员!' ElMessage.error(error.data.msg) } return Promise.reject(error) } );
const pending = new Map()
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.set(url, cancel) } }) }
const removePending = (config: AxiosRequestConfig) => { const url = [ config.method, config.url, qs.stringify(config.params), qs.stringify(config.data) ].join('&') if (pending.has(url)) { const cancel = pending.get(url) cancel(url) pending.delete(url) } }
export const clearPending = () => { for (const [url, cancel] of pending) { cancel(url) } pending.clear() }
export default axiosInstance
|
路由的简单配置(Vue-router)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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', 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import { createStore, Store } from 'vuex'
declare module '@vue/runtime-core' { interface Photo { src: string; } interface State { dark: boolean, count: number, user: { name: string, age: number, photoList: Array<Photo> } }
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
1 2 3 4 5 6 7 8 9 10
| 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')
|
封
![少女马尾辫 夜晚 电脑 听音乐](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAAaADAAQAAAABAAAAAQAAAADa6r/EAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII=)