基于vue-cli3.0构建功能完善的前端架子

上一篇文章写了vue和typescript的整合,发现很多小伙伴对vue-cli构建出来的项目很感兴趣,所以今天打算写写怎么在vue-cli3.0的架子上,在进一步完善,整合出具备基础功能的前端架子,主要包括以下几个功能点:

  1. webpack 打包扩展
  2. css:sass支持、normalize.css
  3. rem布局
  4. 路由设计:懒加载、前置检查、合法性校验
  5. api 设计
  6. 请求体设计-防重复提交
  7. vuex状态管理

webpack 打包扩展

vue-cli3 最大的特点就是零配置,脚手架把webpack相关的配置都隐藏在@vuepreload-webpack-plugin中,默认的配置可以满足大部分应用场景,优点是我们可以节省很多折腾配置的时间,webpack对于新手来说,还是有点门槛的,这样一来,新人上手可以更关注于vue的编码上。缺点也很明显,对于想自己进行自定义配置的时候,就会稍微麻烦些。

查看当前webpack的详细配置

使用 vue inspect 可以查看到详细的配置列表

扩展webpack配置

当我们想要修改或者扩展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 开发的标准工具 的介绍非常详细,而且还有中文版,非常易懂,

sass支持

  1. 组件中这么写 <style lang="scss"></style> 就可以支持scss语法

在组件中import别的scss文件,写法如下

@import "./assets/style/app";

在组件中使用自定义的 functions 和 mixin,我暂时没找到全局引用的办法,只能在需要使用的组件文件中手动引用,如下

@import "../assets/style/functions"; @import "../assets/style/mixin"; .rem { height: px2rem(187.5px); //自定义的函数 } .mimi { @include clearfix(); //自定义的mixin }

为了抹平各个浏览器间的差异,我们需要引入 normalize.css

// app.scss @import "./nodemodules/normalize.css/normalize"; //引用第三方normalize @import "customnormalize"; // 自定义的normalize

rem布局

在移动端下使用rem布局是个不错的选择,既然我们使用里的scss,那么可以使用函数来简化我们的重复计算的工作。设计给到的通常是2倍图,宽为750px,那么我们可以将基准设为 document.getElementsByTagName(&#39;html&#39;)[0].style.fontSize = window.innerWidth / 10 + &#39;px&#39;; 然后写个转换函数,如下:

// _functions.scss
@function px2rem($px) {
    $rem: 75px;
    @return ($px/$rem) + rem;
}

我们在使用的时候,就可以这么写

.rem {
    height: px2rem(300px); // 2倍图下的宽是300px,
}

转换成css就是

.rem {
    height: 4rem;
}

路由设计

主要包括路由懒加载、路由前置检查、合法性校验逻辑,以下是我写的一个简单路由

import Vue from &#39;vue&#39;;
import Router from &#39;vue-router&#39;;

// 路由懒加载
const getComponent = (name: string) => () => import(`./views/${name}.vue`);

Vue.use(Router);

const router = new Router({
    routes: [
        {
            path: &#39;/&#39;,
            name: &#39;home&#39;,
            component: getComponent(&#39;home&#39;)
        },
        {
            path: &#39;/about&#39;,
            name: &#39;about&#39;,
            component: getComponent(&#39;about&#39;),
            meta: {
                auth: true
            }
        },
        {
            path: &#39;*&#39;,
            name: &#39;not_fount&#39;,
            component: getComponent(&#39;notFount&#39;)
        }
    ]
});

/**
 * 路由前置检查
 */
router.beforeEach((to, from, next) => {
    // 合法性校验
    if (to.meta.auth) {
        console.log(&#39;into auth&#39;);
        next();
    }
    next();
});
export default router;

api 设计

新建service文件夹用于存放api脚本,根据业务模块来划分文件,如用户相关的api一个文件、购买相关的一个文件,api.ts是各模块api的集合,如下

// service/api.ts
export { userApi } from &#39;./user&#39;;
export { buyApi } from &#39;./buy&#39;;

// service/user.ts
export const userApi = {
    /**
     * 获取用户数据
     */
    userInfo: &#39;/node_api/read/userInfo&#39;
};
// service/buy.ts
export const buyApi = {
    /**
     * 购买
     */
    shoping: &#39;/node_api/shop/buy&#39;
};

这么划分,是为了项目结构和业务结构都足够清晰,同时可以避免单文件过长的问题。

HTTP请求二次封装

发送http我使用的是非常流行的axios,我在其基础上,稍微进行简单的封装,然后暴露 request对象供调用。二次封装主要是为了解决以下几个问题

简化参数,把一些常用参数都赋默认值,简化外部的使用,使得更加通用和利于排查问题。

返回报文统一处理,我们通常需要对些高频的场景做相同的处理,如错误码、未登录等场景,可以在它提供的返回响应拦截器中,统一处理。

防止重复提交,因为网络、后端处理的因素,有时接口响应会较慢,那么用户可能会在非常短的时间内,反复点击按钮,在第一次请求未返回的情况下,会再次发起新的请求,那么我们可以在axios提供的前置拦截器中搞点事情。关于防止重复请求这东东,我在以前的一篇文章有写过,前端防止用户重复提交-js 感兴趣的小伙伴可以看看。

根据以上几点,下面是我封装的request文件,思路都比较简单,就不多说啦

import axios from &#39;axios&#39;;
import qs from &#39;qs&#39;;

const Axios = axios.create({
    baseURL: &#39;/&#39;,
    timeout: 10000,
    responseType: &#39;json&#39;,
    withCredentials: true,
    headers: {
        &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded;charset=utf-8&#39;
    }
});
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(&#39;Please slow down a little&#39;);
            });
        }
        requestMap.set(keyString, true);
        Object.assign(config, { _keyString: keyString });

        if (config.method === &#39;post&#39; || config.method === &#39;put&#39; || config.method === &#39;delete&#39;) {
            // 序列化
            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 = &#39;post&#39;) => {
    return Axios({
        method,
        url,
        data,
        params: method.toUpperCase() === &#39;GET&#39; && data
    });

};

export { request };

vuex状态管理

这里我根据业务模块来划分文件结构,如下图

分为首页模块和用户模块,每个模块都有自己独立的 state mutations 等,在store.ts中,引入各模块的文件,如下

import Vue from &#39;vue&#39;;
import Vuex from &#39;vuex&#39;;
import index from &#39;./indexModule/index&#39;;
import user from &#39;./userModule/user&#39;;

Vue.use(Vuex);

export default new Vuex.Store({
    modules: {
        user,
        index
    }
});

大家注意到这里有个 store_types.ts 文件,这个文件主要是为了搭配ts使用的,文件内容如下

export enum UserType {
    /**
     * 模块名称
     */
    &#39;MODULE_NAME&#39; = &#39;user&#39;,
    /**
     * 增加次数
     */
    &#39;ADD_COUNT&#39; = &#39;addCount&#39;,
    /**
     * 计算属性-获取十倍的值
     */
    &#39;GET_TEM_COUNT&#39; = &#39;getTenCount&#39;
}

在看下组件中的使用方式:

<script lang="ts">
import { UserType } from &#39;@/store/store_types&#39;;
import { Component, Prop, Vue, Watch,Emit } from &#39;vue-property-decorator&#39;;
import {
    Action,
    Getter,
    Mutation,
    namespace,
    State
} from &#39;vuex-class&#39;;

@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>

虽然这么写的确有点绕,但有个好处,我们可以通过注释清晰知道方法和属性的说明

小结

以上是我根据自己工作中常见的场景来设计的,希望能对小伙伴能有帮助,其中设计不当的地方,欢迎小伙伴们在留言区一起探讨哈~

评论 抢沙发

表情
  1. #1

    来自天津南开的用户 7天前
    信息量好大

  2. #2

    来自北京昌平的用户 19天前
    太秀了老哥,顶一个

  3. #3

    来自河北沧州的用户 21天前
    学习了