詳解基于vue-cli3.0如何構(gòu)建功能完善的前端架子
上一篇文章寫了vue和typescript的整合,發(fā)現(xiàn)很多小伙伴對vue-cli構(gòu)建出來的項(xiàng)目很感興趣,所以今天打算寫寫怎么在vue-cli3.0的架子上,在進(jìn)一步完善,整合出具備基礎(chǔ)功能的前端架子,主要包括以下幾個(gè)功能點(diǎn):
- webpack 打包擴(kuò)展
- css:sass支持、normalize.css
- rem布局
- 路由設(shè)計(jì):懶加載、前置檢查、合法性校驗(yàn)
- api 設(shè)計(jì)
- 請求體設(shè)計(jì)-防重復(fù)提交
- vuex狀態(tài)管理
webpack 打包擴(kuò)展
vue-cli3 最大的特點(diǎn)就是 零配置 ,腳手架把webpack相關(guān)的配置都隱藏在@vue\preload-webpack-plugin中,默認(rèn)的配置可以滿足大部分應(yīng)用場景,優(yōu)點(diǎn)是我們可以節(jié)省很多折騰配置的時(shí)間,webpack對于新手來說,還是有點(diǎn)門檻的,這樣一來,新人上手可以更關(guān)注于vue的編碼上。缺點(diǎn)也很明顯,對于想自己進(jìn)行自定義配置的時(shí)候,就會(huì)稍微麻煩些。
查看當(dāng)前webpack的詳細(xì)配置
使用 vue inspect 可以查看到詳細(xì)的配置列表
擴(kuò)展webpack配置
當(dāng)我們想要修改或者擴(kuò)展webpack配置項(xiàng)時(shí),可以在根目錄下新增 vue.config.js 文件,列舉個(gè)我自己寫的簡單小栗子
// webpack 擴(kuò)展
module.exports = {
baseUrl: 'production' === process.env.NODE_ENV ?
'/production-sub-path/' :
'/',
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.tap(options => Object.assign(options, { limit: 500 }));
},
devServer: {
open: 'darwin' === process.platform,
// host: '0.0.0.0',
port: 8088,
https: false,
hotOnly: false,
// proxy: 'https://api.douban.com' // string | Object
proxy: 'http://localhost:3000' // string | Object
},
lintOnSave: false
};
官網(wǎng)Vue.js 開發(fā)的標(biāo)準(zhǔn)工具 的介紹非常詳細(xì),而且還有中文版,非常易懂,
sass支持
<style lang="scss"></style>
<style lang="scss"> @import "./assets/style/app"; </style>
在組件中使用自定義的 functions 和 mixin,我暫時(shí)沒找到全局引用的辦法,只能在需要使用的組件文件中手動(dòng)引用,如下
<style lang="scss">
@import "../assets/style/functions";
@import "../assets/style/mixin";
.rem {
height: px2rem(187.5px); //自定義的函數(shù)
}
.mimi {
@include clearfix(); //自定義的mixin
}
</style>
為了抹平各個(gè)瀏覽器間的差異,我們需要引入 normalize.css
// app.scss @import "./node_modules/normalize.css/normalize"; //引用第三方normalize @import "custom_normalize"; // 自定義的normalize
rem布局
在移動(dòng)端下使用rem布局是個(gè)不錯(cuò)的選擇,既然我們使用里的scss,那么可以使用函數(shù)來簡化我們的重復(fù)計(jì)算的工作。設(shè)計(jì)給到的通常是2倍圖,寬為750px,那么我們可以將基準(zhǔn)設(shè)為 document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px'; 然后寫個(gè)轉(zhuǎn)換函數(shù),如下:
// _functions.scss
@function px2rem($px) {
$rem: 75px;
@return ($px/$rem) + rem;
}
我們在使用的時(shí)候,就可以這么寫
.rem {
height: px2rem(300px); // 2倍圖下的寬是300px,
}
轉(zhuǎn)換成css就是
.rem {
height: 4rem;
}
路由設(shè)計(jì)
主要包括路由懶加載、路由前置檢查、合法性校驗(yàn)邏輯,以下是我寫的一個(gè)簡單路由
import Vue from 'vue';
import Router from 'vue-router';
// 路由懶加載
const getComponent = (name: string) => () => import(`./views/${name}.vue`);
Vue.use(Router);
const router = new Router({
routes: [
{
path: '/',
name: 'home',
component: getComponent('home')
},
{
path: '/about',
name: 'about',
component: getComponent('about'),
meta: {
auth: true
}
},
{
path: '*',
name: 'not_fount',
component: getComponent('notFount')
}
]
});
/**
* 路由前置檢查
*/
router.beforeEach((to, from, next) => {
// 合法性校驗(yàn)
if (to.meta.auth) {
console.log('into auth');
next();
}
next();
});
export default router;
api 設(shè)計(jì)
新建 service 文件夾用于存放api腳本,根據(jù)業(yè)務(wù)模塊來劃分文件,如用戶相關(guān)的api一個(gè)文件、購買相關(guān)的一個(gè)文件, api.ts 是各模塊api的集合,如下
// service/api.ts
export { userApi } from './user';
export { buyApi } from './buy';
// service/user.ts
export const userApi = {
/**
* 獲取用戶數(shù)據(jù)
*/
userInfo: '/node_api/read/userInfo'
};
// service/buy.ts
export const buyApi = {
/**
* 購買
*/
shoping: '/node_api/shop/buy'
};
這么劃分,是為了項(xiàng)目結(jié)構(gòu)和業(yè)務(wù)結(jié)構(gòu)都足夠清晰,同時(shí)可以避免單文件過長的問題。
HTTP請求二次封裝
發(fā)送http我使用的是非常流行的 axios ,我在其基礎(chǔ)上,稍微進(jìn)行簡單的封裝,然后暴露 request 對象供調(diào)用。二次封裝主要是為了解決以下幾個(gè)問題
- 簡化參數(shù),把一些常用參數(shù)都賦默認(rèn)值,簡化外部的使用,使得更加通用和利于排查問題。
- 返回報(bào)文統(tǒng)一處理,我們通常需要對些高頻的場景做相同的處理,如錯(cuò)誤碼、未登錄等場景,可以在它提供的返回響應(yīng)攔截器中,統(tǒng)一處理。
- 防止重復(fù)提交,因?yàn)榫W(wǎng)絡(luò)、后端處理的因素,有時(shí)接口響應(yīng)會(huì)較慢,那么用戶可能會(huì)在非常短的時(shí)間內(nèi),反復(fù)點(diǎn)擊按鈕,在第一次請求未返回的情況下,會(huì)再次發(fā)起新的請求,那么我們可以在axios提供的前置攔截器中搞點(diǎn)事情。關(guān)于防止重復(fù)請求這東東,我在以前的一篇文章有寫過, 前端防止用戶重復(fù)提交-js 感興趣的小伙伴可以看看。
根據(jù)以上幾點(diǎn),下面是我封裝的request文件,思路都比較簡單,就不多說啦
import axios from 'axios';
import qs from 'qs';
const Axios = axios.create({
baseURL: '/',
timeout: 10000,
responseType: 'json',
withCredentials: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}
});
const CancelToken = axios.CancelToken;
const requestMap = new Map();
// 請求前置攔截器
Axios.interceptors.request.use(
config => {
// 防重復(fù)提交
const keyString = qs.stringify(Object.assign({}, { url: config.url, method: config.method }, config.data));
if (requestMap.get(keyString)) {
// 取消當(dāng)前請求
config.cancelToken = new CancelToken((cancel) => {
cancel('Please slow down a little');
});
}
requestMap.set(keyString, true);
Object.assign(config, { _keyString: keyString });
if (config.method === 'post' || config.method === 'put' || config.method === 'delete') {
// 序列化
config.data = qs.stringify(config.data);
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 返回響應(yīng)攔截器
Axios.interceptors.response.use(
res => {
// 重置requestMap
const config: any = res.config;
requestMap.set(config._keyString, false);
if (res.status === 200) {
return res.data;
}
// todo 彈窗提示等
console.log(`request error:${res}`);
},
error => {
return {
code: -1
};
}
);
/**
* @description
* 請求
* @param url
* @param data
* @param method
*/
const request = (url: string, data = {}, method = 'post') => {
return Axios({
method,
url,
data,
params: method.toUpperCase() === 'GET' && data
});
};
export { request };
vuex狀態(tài)管理
這里我根據(jù)業(yè)務(wù)模塊來劃分文件結(jié)構(gòu),如下圖

分為首頁模塊和用戶模塊,每個(gè)模塊都有自己獨(dú)立的 state mutations 等,在 store.ts 中,引入各模塊的文件,如下
import Vue from 'vue';
import Vuex from 'vuex';
import index from './indexModule/index';
import user from './userModule/user';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
user,
index
}
});
大家注意到這里有個(gè) store_types.ts 文件,這個(gè)文件主要是為了搭配ts使用的,文件內(nèi)容如下
export enum UserType {
/**
* 模塊名稱
*/
'MODULE_NAME' = 'user',
/**
* 增加次數(shù)
*/
'ADD_COUNT' = 'addCount',
/**
* 計(jì)算屬性-獲取十倍的值
*/
'GET_TEM_COUNT' = 'getTenCount'
}
在看下組件中的使用方式:
<script lang="ts">
import { UserType } from '@/store/store_types';
import { Component, Prop, Vue, Watch,Emit } from 'vue-property-decorator';
import {
Action,
Getter,
Mutation,
namespace,
State
} from 'vuex-class';
@Component
export default class Test extends Vue {
@State(state => state[UserType.MODULE_NAME].count) public fff!: number;
@Getter(`${UserType.MODULE_NAME}/${UserType.GET_TEM_COUNT}`) public tenCount!: number;
@Mutation(`${UserType.MODULE_NAME}/${UserType.ADD_COUNT}`) public addCount!: any;
}
</script>
雖然這么寫的確有點(diǎn)繞,但有個(gè)好處,我們可以通過注釋清晰知道方法和屬性的說明
小結(jié)
以上是我根據(jù)自己工作中常見的場景來設(shè)計(jì)的,希望能對小伙伴能有幫助,其中設(shè)計(jì)不當(dāng)?shù)牡胤?,歡迎小伙伴們在留言區(qū)一起探討哈~也希望大家多多支持腳本之家。
- vue-cli3.0 特性解讀
- 詳解如何配置vue-cli3.0的vue.config.js
- 一份超級詳細(xì)的Vue-cli3.0使用教程【推薦】
- vue-cli3.0使用及部分配置詳解
- vue-cli3.0 腳手架搭建項(xiàng)目的過程詳解
- vue-cli3.0配置及使用注意事項(xiàng)詳解
- vue-cli3.0+element-ui上傳組件el-upload的使用
- vue-cli3.0 環(huán)境變量與模式配置方法
- 如何基于vue-cli3.0構(gòu)建功能完善的移動(dòng)端架子
- 詳解在vue-cli3.0中自定css、js和圖片的打包路徑
- 使用Vue-cli3.0創(chuàng)建的項(xiàng)目 如何發(fā)布npm包
- vue-cli3.0實(shí)現(xiàn)一個(gè)多頁面應(yīng)用的歷奇經(jīng)歷記錄總結(jié)
相關(guān)文章
對vue2.0中.vue文件頁面跳轉(zhuǎn)之.$router.push的用法詳解
今天小編就為大家分享一篇對vue2.0中.vue文件頁面跳轉(zhuǎn)之.$router.push的用法詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
vue router動(dòng)態(tài)路由下讓每個(gè)子路由都是獨(dú)立組件的解決方案
這篇文章主要介紹了vue router動(dòng)態(tài)路由下讓每個(gè)子路由都是獨(dú)立組件的解決方案,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-04-04
vue3.0父子傳參,子修改父數(shù)據(jù)的實(shí)現(xiàn)
這篇文章主要介紹了vue3.0父子傳參,子修改父數(shù)據(jù)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue中使用Printjs插件實(shí)現(xiàn)打印功能
Print.js 主要是為了幫助我們直接在我們的應(yīng)用程序中打印 PDF 文件,無需離開界面,也無需使用嵌入,這篇文章主要介紹了Vue中使用Printjs插件實(shí)現(xiàn)打印功能,需要的朋友可以參考下2022-08-08
Vue自定義指令學(xué)習(xí)及應(yīng)用詳解
這篇文章主要為大家詳細(xì)介紹了Vue中自定義指令的學(xué)習(xí)以及如何利用Vue制作一個(gè)簡單的學(xué)生信息管理系統(tǒng),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-05-05
vue3+ts+elementui-plus二次封裝彈框?qū)崙?zhàn)教程
這篇文章主要介紹了vue3+ts+elementui-plus二次封裝彈框?qū)崙?zhàn)教程,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
vue 頁面跳轉(zhuǎn)的實(shí)現(xiàn)方式
這篇文章主要介紹了vue 頁面跳轉(zhuǎn)的實(shí)現(xiàn)方式,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下2021-01-01

