上一篇文章写了vue和typescript的整合,发现不少小伙伴对vue-cli构建出来的项目很感兴趣,因此今天打算写写怎么在vue-cli3.0的架子上,在进一步完善,整合出具有基础功能的前端架子,主要包括如下几个功能点:javascript
vue-cli3 最大的特色就是零配置
,脚手架把webpack相关的配置都隐藏在@vue\preload-webpack-plugin中,默认的配置能够知足大部分应用场景,优势是咱们能够节省不少折腾配置的时间,webpack对于新手来讲,仍是有点门槛的,这样一来,新人上手能够更关注于vue的编码上。缺点也很明显,对于想本身进行自定义配置的时候,就会稍微麻烦些。css
使用 vue inspect
能够查看到详细的配置列表html
当咱们想要修改或者扩展webpack配置项时,能够在根目录下新增 vue.config.js
文件,列举个我本身写的简单小栗子前端
// webpack 扩展
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
};
复制代码
官网Vue.js 开发的标准工具 的介绍很是详细,并且还有中文版,很是易懂,vue
<style lang="scss"></style>
就能够支持scss语法<style lang="scss">
@import "./assets/style/app";
</style>
复制代码
<style lang="scss">
@import "../assets/style/functions";
@import "../assets/style/mixin";
.rem {
height: px2rem(187.5px); //自定义的函数
}
.mimi {
@include clearfix(); //自定义的mixin
}
</style>
复制代码
// app.scss
@import "./node_modules/normalize.css/normalize"; //引用第三方normalize
@import "custom_normalize"; // 自定义的normalize
复制代码
在移动端下使用rem布局是个不错的选择,既然咱们使用里的scss,那么可使用函数来简化咱们的重复计算的工做。设计给到的一般是2倍图,宽为750px,那么咱们能够将基准设为 document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px';
而后写个转换函数,以下:java
// _functions.scss
@function px2rem($px) {
$rem: 75px;
@return ($px/$rem) + rem;
}
复制代码
咱们在使用的时候,就能够这么写node
.rem {
height: px2rem(300px); // 2倍图下的宽是300px,
}
复制代码
转换成css就是webpack
.rem {
height: 4rem;
}
复制代码
主要包括路由懒加载、路由前置检查、合法性校验逻辑,如下是我写的一个简单路由ios
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) => {
// 合法性校验
if (to.meta.auth) {
console.log('into auth');
next();
}
next();
});
export default router;
复制代码
新建service
文件夹用于存放api脚本,根据业务模块来划分文件,如用户相关的api一个文件、购买相关的一个文件,api.ts
是各模块api的集合,以下web
// service/api.ts
export { userApi } from './user';
export { buyApi } from './buy';
// service/user.ts
export const userApi = {
/** * 获取用户数据 */
userInfo: '/node_api/read/userInfo'
};
// service/buy.ts
export const buyApi = {
/** * 购买 */
shoping: '/node_api/shop/buy'
};
复制代码
这么划分,是为了项目结构和业务结构都足够清晰,同时能够避免单文件过长的问题。
发送http我使用的是很是流行的axios
,我在其基础上,稍微进行简单的封装,而后暴露 request
对象供调用。二次封装主要是为了解决如下几个问题
简化参数,把一些经常使用参数都赋默认值,简化外部的使用,使得更加通用和利于排查问题。
返回报文统一处理,咱们一般须要对些高频的场景作相同的处理,如错误码、未登陆等场景,能够在它提供的返回响应拦截器中,统一处理。
防止重复提交,由于网络、后端处理的因素,有时接口响应会较慢,那么用户可能会在很是短的时间内,反复点击按钮,在第一次请求未返回的状况下,会再次发起新的请求,那么咱们能够在axios提供的前置拦截器中搞点事情。关于防止重复请求这东东,我在之前的一篇文章有写过,前端防止用户重复提交-js 感兴趣的小伙伴能够看看。
根据以上几点,下面是我封装的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 => {
// 防重复提交
const keyString = qs.stringify(Object.assign({}, { url: config.url, method: config.method }, config.data));
if (requestMap.get(keyString)) {
// 取消当前请求
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);
}
);
// 返回响应拦截器
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 };
复制代码
这里我根据业务模块来划分文件结构,以下图
分为首页模块和用户模块,每一个模块都有本身独立的 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
}
});
复制代码
你们注意到这里有个 store_types.ts
文件,这个文件主要是为了搭配ts使用的,文件内容以下
export enum UserType {
/** * 模块名称 */
'MODULE_NAME' = 'user',
/** * 增长次数 */
'ADD_COUNT' = 'addCount',
/** * 计算属性-获取十倍的值 */
'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>
复制代码
虽然这么写的确有点绕,但有个好处,咱们能够经过注释清晰知道方法和属性的说明
以上是我根据本身工做中常见的场景来设计的,但愿能对小伙伴能有帮助,其中设计不当的地方,欢迎小伙伴们在留言区一块儿探讨哈~