vue项目实践@树洞

瘦欲@ 提交于 2019-11-30 03:42:55

  业务开发
  
  业务逻辑这块儿并就没有什么特别重要的东西要说,无非就是样式、兼容,再加上vue的语法。这些都是基本功,移动端没有IE,故而不会有太多让人窒息的问题。如果说有什么难点,那就是vuex,主要是它的几个核心比较难理解,理解了也不难。
  
  目前只是一些基础的业务逻辑,故只是提一下我在这个项目中遇到的一些问题。
  
  ## Tabbar多米诺骨牌 ##
  
  使用Vant的Tabbar完成底部菜单,五个菜单项,中间项是一个创建消息的功能。类似于手机QQ空间底部菜单栏,这个功能需要功能需要登录且是一个弹窗。那么问题来了:第一,不能使用<router-link>,须根据是否已登录执行相应的逻辑;第二,页面跳转到相应的链接,这个地址再回来对应的索引必须正确;第三,弹窗关闭,对应tabbar的索引也应该进行正确的指示;第四,这个创建图标不能有指示效果,也就是说它只能有一种状态;第五,不是所有页面都有Tabbar,它相对于底部的间距需要处理。
  
  先解决第五个问题,提取组件,所有样式、逻辑集中处理,在需要的地方引用即可。这很容易想到,那么底部菜单的间距怎么做?这也容易想得到,组件渲染完成,给body注入一个菜单高度的底部间距即可。别忘了,在组件结束要移除这个样式。那么,什么时候注入,在哪个生命周期函数内注入?我在百度这个问题的时候,发现有人说beforeMount,这样真的可以吗?看图说话:
  
  红线之上是第一次渲染,红线之下是切换Tabbar之后的渲染,beforeMount在beforeDestroy之前先执行了,所以逻辑应该写在mounted里面。这就OK了吗?vue这类框架设计出来的终极目的是不去操作DOM,让虚拟DOM来做这个工作,所以这样做是不科学的。这里可以绑定class属性或者绑定style属性来做这件事,body操作不了,只好对App.vue下手。绑定写好了,怎样触发它呢?
  
  这就需要用到vuex,mounted触发一次状态值改变,beforeDestroy再触发一次状态值改变。这样,只要使用这个组件都会触发样式更替,第五个问题完美解决。同样利用vuex来解决第二和第三个问题,这两个问题可以并案处理。最初我想用参数来处理,后来觉得不科学,假如底部菜单架构改变会有很大改动。于是我改为映射,将路由地址存在数组里面,解析地址得出索引。
  
  // 匹配路由是否存在于地址
  
  function match(path) {
  
  return path ? (new RegExp(www.sanguoyoux.cn`${path}$`, 'www.hongyaoyul.cn ')).test(document.location) : false
  
  }
  
  // 根据匹配得出路径索引
  
  function getPathIndex(www.digpgtai.cn) {
  
  return ['/', '/chat', '', '/mail', www.pingguoyul.cn'/centre'].findIndex(v => match(v)) || 0
  
  }
  
  假如一个页面有多个路由的情况,现在的方法是无法完成的,比如首页,可以是/,也可以是/home,也可以是/index。还有一种路由地址冲突的情况,也会导致这个方法失效。这个根据具体情况做好映射就可以了,我的逻辑比较简单。得到索引之后,触发store.js里面的值改变,得到正确的显示。Vant的Tabbar索引必须正确,否则Tabbar切换路由上就会报错。因为创建消息是弹窗,弹窗不会触发地址改变,那么获取到的索引不会改变。
  
  下面是地址携带索引参数的方式,则是通过路由参数来完成索引的识别,个人认为这会让地址变得很奇怪。
  
  // 解析地址参数
  
  function parse(url) {
  
  return (url || document.location.search |www.jinyazptdL.cn | document.location.hash).match(/\?(.*?)$/ig)
  
  }
  
  // 将参数转为对象
  
  function getParamsByReg(url) {
  
  let params = parse(url)
  
  if(params) {
  
  params = params[0].replace(/^\?/g, www.xinchenptgw.cn'').replace(/(^|&)(.*?)=(.*?)/g, ',"$2":$3')
  
  params = JSON.parse(`${params.replace(/^,/, '{')}}`)
  
  }
  
  return params || {www.jintianxuesha.com}
  
  }
  
  第一个问题和第四个问题很简单,Vant的Tabbar默认不是路由模式,这个item不配置to属性。在触发切换的事件上根据需要进行逻辑判断,第一个问题解决。第四个问题,Vant同样给了开发者自由嵌入内容的选择。这两个问题看似不是什么问题,其实不然,这儿用的是别人封装好的库,很简单。提出这两个问题的目的在于,这是思考问题和解决问题的过程,也是写出优秀组件的必然过程。
  
  ## 下拉加载 ##
  
  Vant封装了List组件,上滑加载更多,默认要预先加载一屏的内容,而且似乎只对异步有效。它的原理大概是监听滚动的距离,再根据视窗的高度和内容的高度计算距离底部的位置进行加载。这些逻辑不算复杂,需要注意的是加完事件监听,在组件销毁的时候应该对对应的引用进行销毁,否则很容易造成内存泄漏。
  
  除了使用系统的滚动监听外,还可以自己编写滚动效果,这样计算会更加方便,这方面的逻辑可以参考iScroll。如果自己懒得动手,可以借助别人写好的库,目前我还没在vue中使用过,暂无推荐。any-touch支持vue,better-scroll是改写的iScroll,具体效果怎样,只有使用之后才知道。
  
  ## 修改<title> ##
  
  很多人立马想到JS动态修改,先在路由里定义好title,然后在路由跳转的时候进行修改。
  
  export default new Router({
  
  mode: 'history',
  
  routes: http://jintianxuesha.com/?id=190[
  
  {
  
  path: '/',
  
  name: 'home',
  
  meta: {
  
  title: '森林'
  
  },
  
  component: () => import('./views/Home.vue')
  
  }
  
  ]
  
  })
  
  router.beforeEach((to, from, next) => {
  
  if (to.meta.title) {
  
  document.title = to.meta.title
  
  }
  
  next()
  
  })
  
  假如title需要读取文章的标题,每次都不一样,这种方法就有问题了。自己撸代码就不必了(想锻炼一下自己也未尝不可),有现成的车轱辘vue-wechat-title。
  
  # 安装
  
  npm i -D vue-wechat-title
  
  // 全局引入
  
  import Vue from 'vue'
  
  import Title from 'vue-wechat-title'
  
  Vue.use(Title)
  
  <template>
  
  <!-- 使用 -->
  
  <div id="app" v-wechat-title="$route.meta.title" img-set=" ">
  
  <router-view/>
  
  </div>
  
  </template>
  
  v-wechat-title属性换成变量,就能实现title动态改变了。img-set属性用来修改图标,默认值是一个base64图标会有警告,因为还没有图标,所以我用了一个空格占位。
  
  ## vue-lazyload ##
  
  vant是集成了vue-lazyload,我的引入方式是全部导入,在使用图片懒加载的时候却告诉我没有指令。因此,我只好另外安装vue-lazyload,然后全局引入,也就是现在代码库的样子。后来仔细看了官方文档,默认的是vant输出,集成的组件不在vant中,改成这样就可以了。
  
  import Vant, { Lazyload } from 'vant'
  
  import 'vant/lib/index.css'
  
  Vue.use(Lazyload)
  
  Vue.use(Vant)
  
  ## 不存在的路由 ##
  
  当用户请求了一个不存在的地址,因为匹配不到对应的路由,这时就是一片空白,对这样的路由要进行拦截。
  
  router.beforeEach((to, from, next) => {
  
  if (to.matched.length === 0) {
  
  // 跳转到404页面
  
  next({ path: '/404', replace: true })
  
  } else {
  
  next()
  
  }
  
  })
  
  打包测试
  
  当引入vant之后,再一次跑项目其实很快就会发现,编译速度特别慢。事实上vue-cli 3在编译上做了优化,速度应该比vue-cli 2快才对,怎么会感觉慢呢?!先打包一下再看看什么效果!
  
  两个警告,说的都是同一个问题——资源过大。别急,复制第一个警告去问下度娘怎么处理。度娘给了我们很多答案,答案都差不多,归结起来就两点:第一,关闭警告,我不听你的警告;第二,调节阈值,将警戒线调高。然后呢,然后可爱的小伙伴们就按着他的意思去做了。如果是一两篇文章这么说情有可原,一连串的都这么说,误人子弟啊。我前面的文章就提到过,很多人自己压根儿就没搞清楚就去复制别人的文章,搞得满世界都是差不多文章。他还不服,转载标识都舍不得给原创。
  
  为什么说误人子弟?脚疼就把脚给据了,手疼把手砍了,头疼头部以下截肢:这不是瞎胡闹吗?闭上眼,看不见,问题就解决了吗?为什么webpack给的上限建议是244KiB?这个值其实已经很大了,这么大的文件会影响到资源的加载!我们不能捂着眼睛就当做看不见了,而应该去优化资源。
  
  VUE CLI自身给了图片压缩,这个且不管了,进入vue.config.js做一些调整。生产环境不需要排错,先关闭map。
  
  productionSourceMap: false
  
  代码的压缩工具换成uglifyjs-webpack-plugin,可以利用环境变量来判断是否去除打印。将它按照到devDependencies里面,在.env.debug文件增加一个值NPM_CONFIG_REPORT,利用这个值判断是测试服务器还是正式服务器。
  
  NODE_ENV=production
  
  VUE_APP_HOST=https://www.debug.com
  
  NPM_CONFIG_REPORT=true
  
  如果是正式服务器就去除打印,如果是测试服务器就允许打印,这样就不必为了调试产生的代码烦恼。很多人认为console.log无所谓,毕竟在手机端是看不到的。这是错误的想法,console.log会影响性能。可以做个测试,用canvas绘制一个1920*1080的壁纸,然后console.log输出base64,感受效果。
  
  // 配置工具
  
  configureWebpack: config => {
  
  if(process.env.NODE_ENV === 'production') {
  
  if(!process.env.NPM_CONFIG_REPORT) {
  
  // 压缩代码
  
  config.plugins.push(new UglifyJsPlugin({
  
  uglifyOptions: {
  
  compress: {
  
  drop_debugger: true,
  
  drop_console: true,
  
  pure_funcs: ['console.log']
  
  }
  
  },
  
  sourceMap: false,
  
  parallel: true
  
  }))
  
  }
  
  }
  
  }
  
  稍微做了下优化,再次打包,没什么作用嘛,依旧包很大。为什么呢?安装webpack-bundle-analyzer到devDependencies,修改vue.config.js。
  
  // 引入工具
  
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  
  // 配置工具
  
  chainWebpack: config => {
  
  // 其他代码
  
  // ...
  
  if(process.env.NODE_ENV === 'production') {
  
  // 打包分析
  
  if(process.env.NPM_CONFIG_REPORT) {
  
  config.plugin('webpack-bundle-analyzer').use(BundleAnalyzerPlugin).end()
  
  config.plugins.delete('prefetch')
  
  }
  
  }
  
  }
  
  我将分析工具配置在debug环境下运行,用debug环境打包,看下究竟是什么占据这么大空间。
  
  vant最大,其次是vue。整个vant都在里面,必然会增加包的体积,所以在引入的时候我就表示这种引入方式不科学。前面我讲废话时也曾吐槽过将jQuery引入vue,就是这个原因。用document.getElementById解决的事,偏偏去引入一个jQuery,这是看不起这个时代呀?
  
  废话少说,有没有办法压缩它呢?答案是肯定的。看下打包时控制台的打印,三个字段File、Size、Gzipped。第三个字段就是压缩包的大小,这需要服务端配合。而且解压需要一个过程,这肯定不是优先的解决办法。既然它们合并在一起会干坏事,可不可以将它们分离?首先想到的就是cdn,通过人工去解决,很常见的一个办法。可是,这个操作其实是机械化的事情,如果每个项目都这么做,不就不符合工程化思想了吗?
  
  当然,cdn并不是不好的思路,为了分流,这还是必然的。我在想,有没有自动化的方式来分离代码,这些资源几乎是不变的,何不提取公共库呢?利用webpack-cli,将需要提取的库分离出来,然后将公共库插入到html中。具体操作参考《vue-cli3 DllPlugin 提取公用库》,我就不再多说。配置好了,先跑一下公共库,再次打包。
  
  什么?公共库的包又超标了,它让我去webpack官网看看代码分割!这个超标的库只有vant,还能继续分割吗?我也没有答案,于是我试着将vant的整体引入改为按需引入,很明显包的体积一下子就下来了。不过,在上传的代码中我并没有按需引入,主要是方便使用。在实际开发中,只会用到部分组件,按需引用是最佳解决方式。
  
  刚刚webpack提示已经提示了分割代码,在开发中还可以利用这个办法来减轻包的体积。VUE CLI是用的webpack打包,因此,学习webpack也是一个必修的功课,更多优化可以参考《Vue Cli3 项目打包优化》。
  
  最后
  
  因为这个项目缺少产品角色,一开始并没有架构好,想法不成熟。我一边搭建项目,一边思考业务的时候,发现这个项目有点大,牵扯的业务甚多。加上各种原因,最后并没有按着理想的进度完成,被迫中止。这个系列暂且会告一段落,计划中还有一个服务器渲染的章节。
  
  服务器渲染是建立在服务器基础之上,如果只是简单的业务逻辑就没必要拿出来说,看官方文档就好了,这一定得和服务器一起才有意义。通常,在实际工作中接触不到服务器,绝大部分人就是码农而已。我认为知道服务器部署对服务器渲染会理解更多一点,这对于我来说也是一个全新的课题。
  
  服务器渲染没有数据的话也是没有意义的,在中止的这段时间我会重新梳理@树洞的业务,从最核心的地方入手,精简项目,确保项目完整。也希望再更新的时候能给大家带来更多有用的东西。
  
  ## 代码仓库 ##
  
  https://gitee.com/IanLew/tree-hole.git
  
  ## @树洞系列 ##
  
  vue项目实践@树洞(一)
  
  vue项目实践@树洞(二)
  
  vue项目实践@树洞(三)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!