vue移动端项目架构设计(附demo)

引言

记录 vue 项目中所使用的技术细节,此文着重使用和封装层面,原理性的东西会附上参考文章链接。

建议 clone 下来代码看文章:vue-template-project

麻烦动动小手点个 star 哦。

项目初始化

技术选型

结合vue生态,此移动端项目模板使用如下技术:

  • 前端框架——vue
  • vue 状态管理——vuex
  • vue 路由管理——vue-router
  • 请求方式——axios
  • 样式管理——less
  • 包管理——npm/cnpm

vue-cli4搭建项目

vue-cli 工具更新很快,我们现在项目中仍使用的是 vue-cli2 ,项目中如需更新脚手架工具,按照以下步骤更新即可。

安装 Vue CLI

安装

npm install -g @vue/cli

如果存在旧版本的 vue-cli ,需先卸载再安装:

npm uninstall vue-cli -g

检测版本:

vue --version
#OR
vue -V

创建项目

运行以下命令创建一个项目:

vue create vue-webapp-template
// vue-webapp-template是你创建的项目名称

新的脚手架工具也给提供了可视化界面创建和管理项目,如需使用可视化工具搭建项目可参考——从零使用vue-cli+webpack4搭建项目

image
image

使用命令行创建项目时,会让你选择默认或手动配置,我选的第二个手动配置(Manually),因为在项目里有轻微的强迫症,没有用到的就没有选,具体每个部分、每个目录是做什么的,这个文章讲的比较清楚——从零使用vue-cli+webpack4搭建项目

移动端组件库选型

其实,组件库是可选可不选的,如果团队中都是大牛,而且有专门的 UI 团队设计复用组件,项目中所用之处皆已封装为组件或插件,我认为这样的团队完全不需使用外部的组件库,团队本身都代表着效率,为什么还要参考别人的效率工具

如果不是上面那种团队,我觉得还是谦虚些,选一个组件库支持基础开发更为稳妥。毕竟,业务繁忙时,效率至上

对比了好多个移动端组件库,对比结果如下:

image

在这里我们选用 vant

项目初始化后执行:

// 安装
npm i vant -S
// 安装插件
npm i babel-plugin-import -D
// 在.babelrc 中添加配置
// 注意:webpack 1 无需设置 libraryDirectory
{
  "plugins": [
    ["import", {
      "libraryName": "vant",
      "libraryDirectory": "es",
      "style": true
    }]
  ]
}

// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
module.exports = {
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
};
// 接着你可以在代码中直接引入 Vant 组件
// 插件会自动将代码转化为方式二中的按需引入形式
import { Button } from 'vant';

基本知识

vuex数据管理

在这里只介绍项目中如何使用 vuex 进行数据管理,具体知识点请查看 官网

store 目录的设计参考官网推荐购物车案例

vuex 执行的流程图如下:

image

接下来展示在组件中如何调用 state、getters、actions、mutations。

state&&getters

import { mapState, mapGetters } from 'vuex'
export default {
  computed: {
    // 使用对象展开运算符将此对象混入到外部对象中
    // home代表是store中的哪一个模块
    ...mapState('home', {
      // 箭头函数可使代码更简练
      home1: state => state.home1
    }),
    ...mapGetters('home', {
      home1Getter: 'home1'
    })
  }
}

actions&&mutations

import { mapMutations, mapActions } from 'vuex'
export default {
  methods: {
    ...mapActions('home', {
      handleActions: 'getExample'
    }),
    ...mapMutations('home', {
      handleMutations: 'handleMutations'
    }),
  }
}

vue-router路由管理

路由管理方面采用的是一个主文件和各个模块的路由文件的方式,这样维护起来会稍微舒心一些,不至于当你接到一个项目时,几千行代码在一起,看着也不是很舒服。基础目录如下:

--router
  --index.js
  --home.js
  --my.js

index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import home from './home'
import my from './my'

Vue.use(VueRouter)

const routes = [...home, ...my]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

在 index.js 里面可以加一些全局路由守卫的东西。

例如可以在路由的 meta 中加入每个页面的 title,然后当用户进入每个页面前,判断这个组件是否有 title 属性,如果有的话,就按照你定义的 title 进行展示。

// 设置页面title
router.beforeEach((to, from, next) => {
  if (to.meta.title) {
    document.title = to.meta.title
  }
  next()
})

最常用的应该是当用户未登陆时,如果想进入某个页面,需跳转至登陆页面。

实现思路:加一个全局的登陆态,并利用前端存储保存在本地,如果未登陆跳转到登陆的页面,登陆后进入本想进入的页面。

mixin

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

如果你有一些公用的数据和方法,不想在另一个组件里面再写一遍,就可以写一个 mixin 的 js 文件,类似:

const homeMixin = {
  // 在不止一个文件用到的数据
  data () {
    return {
      homeMixin: 'test-homeMixin'
    }
  },
  // 在不止一个文件用到的方法
  methods: {
    one () {

    },
    two () {

    }
  }
}

export default homeMixin

在组件中如何使用呢?

// 引入
import homeMixin from 'components/common/home.js'
// 使用
export default {
    mixins:[homeMixin]
}

工具封装

axios封装(请求拦截,响应拦截)

在我这个搭建的模板项目中只是简单的做了一点封装,之后我自己用到这个模板后,也可以有更多的操作性。

import Vue from 'vue'
import axios from 'axios'
import { Toast } from 'vant'
Vue.use(Toast)
axios.defaults.headers['content-Type'] = 'application/json;charset=UTF-8' // 'Content-Type': 'application/x-www-form-urlencoded'

// 请求拦截
axios.interceptors.request.use(function (config) {
  if (config.method === 'post') {

  } else if (config.method === 'get') {

  }
  return config
}, function (error) {
  return Promise.reject(error)
})

// 响应拦截
axios.interceptors.response.use(res => res, err => {
  if (err && (err.toString().indexOf('500') > -1 || err.toString().indexOf('502') > -1 || err.toString().indexOf('404') > -1)) {
    Toast('网络或接口异常')
    return Promise.reject('网络或接口异常')
  } else {
    return Promise.reject(err)
  }
})

api统一管控

api 是按照每个模块创建的文件。

--service
----api.js  // axios封装
----homeApi.js // home模块所有的请求
----myApi.js  // my模块所有的请求

homeApi.js

import './api.js'
import axios from 'axios'
/**
 * get 案例
 * @param options
 */
export const getExample = options => {
  return axios.get('mock/home1.json', { params: options })
}

/**
 * post 案例
 * @param options
 * @returns {*}
 */
export const postExample = options => {
  return axios.post('mock/home2.json', options)
}

更为具体的 axios 封装和 api 统一管控的内容可以参考我的另一篇文章详解vue中Axios的封装与API接口的管理

常用函数封装

utils.js

模板项目中封装了一个日期格式化的函数,项目中可以根据自己的需要封装几个常用的函数。

/**
 * 日期格式化 new Date(...).format('yyyy-MM-dd hh:mm:ss')
 * @param fmt
 * @returns {*}
 */
window.Date.prototype.format = function (fmt) {
  let o = {
    'M+': this.getMonth() + 1,
    'd+': this.getDate(),
    'h+': this.getHours(),
    'm+': this.getMinutes(),
    's+': this.getSeconds(),
    'q+': Math.floor((this.getMonth() + 3) / 3),
    'S': this.getMilliseconds()
  }
  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length))
  }
  for (var k in o) {
    if (new RegExp('(' + k + ')').test(fmt)) {
      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
    }
  }
  return fmt
}

组件化思想

什么是组件化

组件化并不是前端所特有的,一些其他的语言或者桌面程序等,都具有组件化的先例。确切的说,只要有UI层的展示,就必定有可以组件化的地方。简单来说,组件就是将一段UI样式和其对应的功能作为独立的整体去看待,无论这个整体放在哪里去使用,它都具有一样的功能和样式,从而实现复用,这种整体化的细想就是组件化。不难看出,组件化设计就是为了增加复用性,灵活性,提高系统设计,从而提高开发效率。

简而言之:一个 .vue 文件就是一个组件

slot扩展组件

Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot> 元素作为承载分发内容的出口。

它允许你像这样合成组件:

<home-header title="home标题">
    <p>这是slot</p>
</home-header>

然后你在 <home-header> 的模板中可能会写为:

<div id="header">
  <div class="left">{{title}}</div>
  <slot></slot>
</div>

install封装插件

如果重复业务很多的话,相较于组件化,插件化无疑是更能加快开发效率的方式。

开发插件

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

使用插件

通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:

// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)

new Vue({
  // ...组件选项
})

也可以传入一个可选的选项对象:

Vue.use(MyPlugin, { someOption: true })

Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。

Vue.js 官方提供的一些插件 (例如 vue-router) 在检测到 Vue 是可访问的全局变量时会自动调用 Vue.use()。然而在像 CommonJS 这样的模块环境中,你应该始终显式地调用 Vue.use()

// 用 Browserify 或 webpack 提供的 CommonJS 模块环境时
var Vue = require('vue')
var VueRouter = require('vue-router')

// 不要忘了调用此方法
Vue.use(VueRouter)

效率工具

rem布局——cssrem+flexble.js

移动端最常用的布局无非有这几种:响应式布局、vw + vh 布局、rem 布局、vm + rem 布局。

这里采用的是 rem 布局,使用的是淘宝出品的 Flexible.js 。

;(function(win, lib) {
    var doc = win.document;
    var docEl = doc.documentElement;
    var metaEl = doc.querySelector('meta[name="viewport"]');
    var flexibleEl = doc.querySelector('meta[name="flexible"]');
    var dpr = 0;
    var scale = 0;
    var tid;
    var flexible = lib.flexible || (lib.flexible = {});
    
    if (metaEl) {
        console.warn('将根据已有的meta标签来设置缩放比例');
        var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
        if (match) {
            scale = parseFloat(match[1]);
            dpr = parseInt(1 / scale);
        }
    } else if (flexibleEl) {
        var content = flexibleEl.getAttribute('content');
        if (content) {
            var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
            var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
            if (initialDpr) {
                dpr = parseFloat(initialDpr[1]);
                scale = parseFloat((1 / dpr).toFixed(2));    
            }
            if (maximumDpr) {
                dpr = parseFloat(maximumDpr[1]);
                scale = parseFloat((1 / dpr).toFixed(2));    
            }
        }
    }

    if (!dpr && !scale) {
        var isAndroid = win.navigator.appVersion.match(/android/gi);
        var isIPhone = win.navigator.appVersion.match(/iphone/gi);
        var devicePixelRatio = win.devicePixelRatio;
        if (isIPhone) {
            // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
            if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {                
                dpr = 3;
            } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
                dpr = 2;
            } else {
                dpr = 1;
            }
        } else {
            // 其他设备下,仍旧使用1倍的方案
            dpr = 1;
        }
        scale = 1 / dpr;
    }

    docEl.setAttribute('data-dpr', dpr);
    if (!metaEl) {
        metaEl = doc.createElement('meta');
        metaEl.setAttribute('name', 'viewport');
        metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
        if (docEl.firstElementChild) {
            docEl.firstElementChild.appendChild(metaEl);
        } else {
            var wrap = doc.createElement('div');
            wrap.appendChild(metaEl);
            doc.write(wrap.innerHTML);
        }
    }

    function refreshRem(){
        var width = docEl.getBoundingClientRect().width;
        if (width / dpr > 540) {
            width = 540 * dpr;
        }
        var rem = width / 10;
        docEl.style.fontSize = rem + 'px';
        flexible.rem = win.rem = rem;
    }

    win.addEventListener('resize', function() {
        clearTimeout(tid);
        tid = setTimeout(refreshRem, 300);
    }, false);
    win.addEventListener('pageshow', function(e) {
        if (e.persisted) {
            clearTimeout(tid);
            tid = setTimeout(refreshRem, 300);
        }
    }, false);

    if (doc.readyState === 'complete') {
        doc.body.style.fontSize = 12 * dpr + 'px';
    } else {
        doc.addEventListener('DOMContentLoaded', function(e) {
            doc.body.style.fontSize = 12 * dpr + 'px';
        }, false);
    }
    

    refreshRem();

    flexible.dpr = win.dpr = dpr;
    flexible.refreshRem = refreshRem;
    flexible.rem2px = function(d) {
        var val = parseFloat(d) * this.rem;
        if (typeof d === 'string' && d.match(/rem$/)) {
            val += 'px';
        }
        return val;
    }
    flexible.px2rem = function(d) {
        var val = parseFloat(d) / this.rem;
        if (typeof d === 'string' && d.match(/px$/)) {
            val += 'rem';
        }
        return val;
    }

})(window, window['lib'] || (window['lib'] = {}));

事实上 flexible.js 做了下面三件事:

  • 动态改写标签
  • <html>元素添加data-dpr属性,并且动态改写data-dpr的值
  • <html>元素添加font-size属性,并且动态改写font-size的值

因为我使用的是 vsCode 编辑器,在这里介绍一下使用 vsCode 时,如何快速的将 px-->rem。

  1. 下载 cssrem 插件


    image
  2. 打开 vsCode 编译器————文件————首选项————设置————搜索 cssrem ————进行设置

image

因为我们的设计稿尺寸是375px的,自动转换时除以37.5得到应有的 rem 值。

image

解决300ms延迟

移动设备上的浏览器默认会在用户点击屏幕大约延迟300毫秒后才会触发点击事件。

原因: 移动端的双击会缩放导致click判断延迟。

安装FastClick

npm i fastclick -S

调用

//jquery
<script type='application/javascript' src='/path/to/fastclick.js'></script>
$(function() {
    FastClick.attach(document.body);
});

//原生js
if ('addEventListener' in document) {
    document.addEventListener('DOMContentLoaded', function() {
        FastClick.attach(document.body);
    }, false);
}

//vue 
import FastClick from 'fastclick'
FastClick.attach(document.body);

移动端测试工具v-console

平时在 web 应用开发过程中,我们可以 console.log 去输出一些信息,但是在移动端,也就是在手机上, console.log 的信息我们是看不到的。

这种情况下,可以选择使用 alert 弹出一些信息,但是这种方法不怎么方便,也会阻断 JS 线程,导致后面的线程都不执行,影响调试体验。

那么,如果将console.log应用到移动端呢?
需要借助第三方插件:vConsole

安装

npm install vconsole

在main.js引入

import Vconsole from 'vconsole';
new Vconsole();

在需要的地方

console.log('内容')
image

项目优化

使用README.md记录每次更新的内容

在我目前的团队,人员流动还是比较大的,而且我们的项目耦合度很低,可能几个页面就是一个项目,所以造成项目很多。为了其他人更快的接手一个项目,所以在开发时制定了一个规范,即在 README.md 文件中列出每个文件中需要共享的部分,以减少沟通成本。

> 项目相关备注

- 相关人员 `有多人情况下全部列出`
  + 业务分析师:
  + 前端开发人员:
  + 后台开发人员:

- 环境地址 `有更多环境依次补全, 以下详情有则补充`
  * 测试环境
    + 测试环境页面访问地址:
    + 测试环境接口地址:
    + 测试环境部署方式:

  * 生产环境
    + 生产环境页面访问地址:
    + 生产环境接口地址:
    + 生产环境部署方式:

- 补充说明:

- 迭代说明:
    - v1.0
    ......

分环境打包

当我们在实际开发时,最少会分三个环境:开发环境(用于本地开发)、测试环境(模拟生产环境,上线前的测试)、生产环境。

  • package.json
  "scripts": {
    "serve": "vue-cli-service serve --open --mode development",
    "build": "vue-cli-service build",
    "test": "vue-cli-service build --mode test"
  },

具体请参看:详解vue-cli4环境变量与分环境打包方法

mock数据

在前端开发过程中,有后台配合是很必要的。但是如果自己测试开发,或者后台很忙,没时间,那么我们需要自己提供或修改接口。下面提供两种方式,第二种更简单,个人推荐第二种。

mock文件

  1. 安装
npm i mockjs -D
  1. 在 src 目录下新建 mock 目录
image
  1. index.js 内容如下
const Mock = require('mockjs')

Mock.mock('/test/get', 'get', require('./json/testGet'))
Mock.mock('/test/post', 'post', require('./json/testPost'))
  1. json 文件内容如下,以 testGet.json 为例:
{
  "result": "success",
  "data": {
    "sex": "man",
    "username": "前端林木--get",
    "age": 0,
    "imgUrl": ""
  },
  "msg": ""
}
  1. 在main.js入口文件中引入mock数据
if (env === 'DEV') {
  require('./mock') // 引入mock数据
}
  1. vue 中封装,然后调用即可
export const getExample = options => {
  return axios.get('/test/get', { params: options })
}

第三方接口 eolinker

  1. 官网接口地址:https://www.eolinker.com/#/home/project/api/

需登录,没注册过的小伙伴,注册一个账号吧。

  1. 注册好后有一个默认接口,当然我们要做自己的项目。

  2. 新建项目

image
  1. 添加接口
image
  1. 自定义接口
image
  1. 使用接口
image
image
  1. 前端项目中,后台 url 地址,有开发版,测试版,本地版等多个版本,建议大家把开发的的URL换成 mock 的地址。
image

webpack 优化项目

如何提高 webpack 的打包速度

  1. 优化 Loader

    对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。当然了,我们是有办法优化的。

    首先我们可以优化 Loader 的文件搜索范围

module.exports = {
  module: {
    rules: [
      {
        // js 文件才使用 babel
        test: /\.js$/,
        loader: 'babel-loader',
        // 只在 src 文件夹下查找
        include: [resolve('src')],
        // 不会去查找的路径
        exclude: /node_modules/
      }
    ]
  }
}

对于 Babel 来说,我们肯定是希望只作用在 JS 代码上的,然后 node_modules 中使用的代码都是编译过的,所以我们也完全没有必要再去处理一遍。

当然这样做还不够,我们还可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间

loader: 'babel-loader?cacheDirectory=true'

  1. HappyPack

    受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。

    HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了

module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 后面的内容对应下面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 开启 4 个线程
    threads: 4
  })
]

  1. DllPlugin

    DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。

    接下来我们就来学习如何使用 DllPlugin

// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想统一打包的类库
    vendor: ['react']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]-[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必须和 output.library 一致
      name: '[name]-[hash]',
      // 该属性需要与 DllReferencePlugin 中一致
      context: __dirname,
      path: path.join(__dirname, 'dist', '[name]-manifest.json')
    })
  ]
}

然后我们需要执行这个配置文件生成依赖文件,接下来我们需要使用 DllReferencePlugin 将依赖文件引入项目中

// webpack.conf.js
module.exports = {
  // ...省略其他配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是之前打包出来的 json 文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}

  1. 代码压缩

    在 Webpack3 中,我们一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,我们可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。

    在 Webpack4 中,我们就不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

  2. 一些小的优化点

    我们还可以通过一些小的优化点来加快打包速度

    • resolve.extensions:用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面
    • resolve.alias:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径
    • module.noParse:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助

如何用 webpack 来优化前端性能

⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速⾼效。

  • 压缩代码:删除多余的代码、注释、简化代码的写法等等方式。可以利⽤ webpack 的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩 JS ⽂件, 利⽤ cssnano (css-loader?minimize)来压缩 css。
  • 利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径。
  • Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现。
  • Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存。
  • 提取公共第三⽅库: SplitChunksPlugin 插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码。

骨架屏展示

项目 demo 中使用的是 vant 中的 Skeleton 骨架屏

// 引入
import Vue from 'vue';
import { Skeleton } from 'vant';

Vue.use(Skeleton);
// 展示子组件
// 将loading属性设置成false表示内容加载完成,此时会隐藏占位图,并显示Skeleton的子组件

<van-skeleton
  title
  avatar
  :row="3"
  :loading="loading"
>
  <div>实际内容</div>
</van-skeleton>
export default {
  data() {
    return {
      loading: true
    }
  },
  mounted() {
    this.loading = false;
  }
};

如果想自己搭建一个骨架屏,给大家几个参考链接:

  1. Vue页面骨架屏注入实践
  2. 前端骨架屏方案小结
  3. 为vue项目添加骨架屏

总结

以上就是 vue 移动端整体大的项目架构设计了(webpack 有一部分没做演示,有需要的童鞋要实践一下哦),总结一篇文章好辛苦。

2020-1-10 0:55

晚安~


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,030评论 5 464
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,198评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,995评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,973评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,869评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,766评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,967评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,599评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,886评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,901评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,728评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,504评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,967评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,128评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,445评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,018评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,224评论 2 339

推荐阅读更多精彩内容

  • 一、 组件component 1. 什么是组件? 组件(Component)是 Vue.js 最强大的功能之一。组...
    饥人谷_Leonardo阅读 1,933评论 0 18
  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,137评论 0 1
  • 前言 首先,当我们编写代码时,不通过模块化的思想想要引入一个js,通常是在html文件中创建一个script标签,...
    zouCode阅读 2,301评论 0 1
  • ## 框架和库的区别?> 框架(framework):一套完整的软件设计架构和**解决方案**。> > 库(lib...
    Rui_bdad阅读 2,884评论 1 4
  • MVVM Vue的指令 基本概念: Vue中指令都是以v-xx开头,指令的作用,最终都是拿着数据,渲染我们指令标签...
    getElementsByMK阅读 1,840评论 0 2