vue实战 您所在的位置:网站首页 移动端架构 vue实战

vue实战

2023-12-19 18:53| 来源: 网络整理| 查看: 265

引言

记录 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搭建项目。

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

移动端组件库选型

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

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

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

在这里我们选用 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 执行的流程图如下:

接下来展示在组件中如何调用 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;' // '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

然后你在 的模板中可能会写为:

{{title}} 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 做了下面三件事:

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

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

下载 cssrem 插件

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

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

解决300ms延迟

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

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

安装FastClick npm i fastclick -S 调用 //jquery $(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('内容')

项目优化 使用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文件 安装 npm i mockjs -D 在 src 目录下新建 mock 目录

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

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

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

新建项目

添加接口

自定义接口

使用接口

前端项目中,后台 url 地址,有开发版,测试版,本地版等多个版本,建议大家把开发的的URL换成 mock 的地址。

webpack 优化项目 如何提高 webpack 的打包速度

优化 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'

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 }) ]

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'), }) ] }

代码压缩

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

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

一些小的优化点

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

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的子组件 实际内容 export default { data() { return { loading: true } }, mounted() { this.loading = false; } };

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

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

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

2020-1-10 0:55

晚安~



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有