去哪儿网项目学习总结

栏目: JavaScript · 发布时间: 5年前

内容简介:移动端浏览器那它是如何实现的呢?浏览器在捕捉到第一次点击事件后,会等待一段时间,如果在这段时间内,用户没有再次进行点击操作的话,就执行单击事件;如果用户进行了第二次点击操作的话,就会执行双击事件。这段等待的时间大约如何解决这个延迟呢?有很多方法,这里推荐两种比较简单的方法:
readme              //项目的说明文件
package.json         //第三方依赖包配置
package.lock.json    //帮助我们去确定安装的第三方依赖包的具体的版本,保持团队编程的统一
license             //开源协议的说明
index.html          //项目默认的首页模版文件
.postcssrc.js        //对 postcss 的配置项
.gitignore          //不需要上传到 git 上的文件管理 
.eslintrc.js        //对写的代码检测是否标准做一个检测
.eslintignore       //配置不需要 eslintrc 检测 工具 检测的文件
.editorconfig       //配置编辑器总风格统一的自动化格式的语法
.babelrc            //项目写的代码是 Vue 的大文件组件的代码的写法,所以需要通过 babel 这种语法解析器做一些语法上的转换,最终转换成浏览器能够编译执行的代码,babel 需要做额外配置时,就放在文件里面
static                  //static 目录放的是静态资源,要用到的静态图片啊或者后续需要模拟的 json 数据
node_modules                 //项目中需要用到的第三方 node 包
src                         //放的是项目的源代码
src/main.js                  //整个项目的入口文件
src/app.vue                 //整个项目最原始的根组件
src/router/index.js          //项目的路由放置位置
src/components               //项目中要用到的小组件
src/assets                  //项目中需要用到的图片
config                      //放置项目配置文件
config/index.js              //放基础配置
config/dev.ent.js            //开发环境配置信息
config/prod.ent.js           //线上环境配置信息
build                      //放置项目打包的 webpack 配置信息,vue-cli 会自动构建
build/webpack.base.conf.js   //基础的 webpack 配置信息
build/webpack.dev.conf.js    //开发环境的 webpack 配置信息
build/webpack.prod.conf.js   //线上环境的 webpack 配置信息
复制代码

移动端 300ms 延迟

移动端浏览器 click 事件为什么会有 300ms 的延迟呢?因为在手机上有个双击方案 —— 在手机上快速点击两下,实现页面放大;再次双击,恢复到原始比例。

那它是如何实现的呢?浏览器在捕捉到第一次点击事件后,会等待一段时间,如果在这段时间内,用户没有再次进行点击操作的话,就执行单击事件;如果用户进行了第二次点击操作的话,就会执行双击事件。这段等待的时间大约 300ms

如何解决这个延迟呢?有很多方法,这里推荐两种比较简单的方法:

  • <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1,minimum-scale=1, user-scalable=no">

    width=device-width 宽度为设备宽度 initial-scale=1 初始比例为 1 maximum-scale=1 最大比例为 1 minimum-scale=1 最小比例为 1 user-scalable=no 用户不能进行放大缩小

  • 引入第三方库 fastclick

    npm install fastclick --save
    import FastClick from 'fastclick'
    FastClick.attach(document.body)
    

1px 像素问题

红色边框是用 border-bottom: 1px solid red; 写的,在手机上明显可以看出它不是 1px

去哪儿网项目学习总结

怎么解决这个问题呢?

方法有好几种,这里推荐一种: 伪类 + transform 看代码:

.border1
    height: .5rem
    position: relative
.border1:before
    position: absolute
    top:-.5rem
    left:0
    content: ''
    width:100%
    height:1px
    border-top:1px solid rgba(0,0,0,.3)
    transform: scaleY(0.5)
复制代码

这种方式的原理很简单,就是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transformscale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。

样式重置

网上有很多 reset.css 找一份引入到项目之中。

轮播

去哪儿网项目学习总结

占位

图片是可替代资源,在页面显然时,会先将页面中静态的内容渲染上去,等数据返回后,在进行重新渲染,这样页面就会出现抖动,影响用户体验,同时性能也比较低。

可以用下面的 css 代码对这些可替换资源先进行占位,页面大体框架在第一次渲染后就能呈现给用户,数据获取到后,替换相应的内容就可,就不会出现抖动了。

.icon-img
    overflow: hidden
    width: 100%
    height: 0
    padding-bottom: 100%
复制代码

样式穿透

在子组件中实现在这样的布局,需要用到样式穿透,不然是无法滚动下半部分的。

.icons >>> .swiper-container
  height: 0
  padding-bottom:50%
复制代码

多页

现在上面的轮播,一页上有 8icon ,此时如果需求变成了 9 个,怎么样才能做到在不改动代码的前提下,能实现任意数量的 icon

可以用计算属性 computediconList 进行监听:

  1. Home.vue 首先定义了 iconList: [] 通过属性传递给了 Icons.vue 组件。
  2. Icons.vue 中通过 props 接收到了 iconList 数据。
  3. 使用 computed 之所以能对 iconList 监听,是因为刚开始传递进来的 iconList 是空数组,当获取到到数据之后,在传递过来 iconList 是有值的, iconList 一旦发生了变化, computed 就能捕捉到。
  4. computed 中还有一个计算属性 showIconList ,它的用途是:
    iconList
    v-if=showIconList
    swiper
    

代码:

<swiper :options="swiperOption">
  <swiper-slide v-for="(page,index) of pages" :key="index" v-if="showIconList">
    <div class="icon" v-for="item of page" :key="item.id">
      <div class="icon-img">
        <img class="icon-img-content" :src="item.iconUrl" alt="">
      </div>
      <p class="icon-desc">{{item.desc}}</p>
    </div>
  </swiper-slide>
</swiper>
computed: {
    pages () {
      const pages = []
      this.iconList.forEach((item, index) => {
        const page = Math.floor(index / 8)          //每页是 8 个,index / 8 能获取到页数
        if (!pages[page]) {     //初始化每一项
          pages[page] = []
        }
        pages[page].push(item)  //变成新数组
      })
      return pages
   },
   showIconList () {
      return this.iconList.length
   }
}
复制代码
去哪儿网项目学习总结

城市选择

兄弟组件通信

这块内容比较多,重新写了一遍文章,介绍了两种数据传递的方式,城市选择的这边用的是 vuexVue 中非父子组件间的传值

节流

手指在城市字母表中滑动时,会触发无数次 handleTouchMove 这个函数,这就对性能影响很大。

函数节流:通过设定一个时间周期,只要在这个周期内函数就不执行。

实现方法:

ihandleTouchMove (e) {
  if (this.touchStatus) {
    if (this.timer) {
      clearTimeout(this.timer)
    }
    this.timer = setTimeout(() => {
      const touchY = e.touches[0].clientY - 79
      const index = Math.floor((touchY - this.startY) / 20)
      if (index >= 0 && index < this.letters.length) {
        this.$emit('change', this.letters[index])
      }
    }, 10)
  }
}
复制代码

这里设置的周期是 10ms10ms 这个代码只会执行一次,大大优化了性能

keep-alive 优化请求

只发送一次请求

每次点击城市或者回到首页时,都会重新发送一个 ajax 请求,因为当路由切换的时候,这个组件就会被重新渲染,组件一被重新渲染, mounted 这个钩子函数就会被执行。这样就会对性能造成比较大的影响。

Vue 也考虑到了这一点,为我们提供了一个 keep-alive 的标签。

<keep-alive>
   <router-view/>
</keep-alive>
复制代码

路由的内容被加载过一次之后,就把路由的内容放到内存之中,下次在进这个路由的时候不需要再重新渲染组件了,只需你从内容里把以前的内容拿出来显示就可以了。

城市改变在发送请求

按照上面这样优化,当我改变城市时,它也不会发送请求,因为这一块用的是内存里的数据,那么这个选择曾是功能就变得有名无实,那该怎么改进呢?

当我们使用了 keep-alive 标签后,会自动执行钩子函数 activated ,而 mounted 钩子函数是不会被执行的。

activated () {
    if (this.lastCity !== this.city) {
      this.lastCity = this.city
      this.getHomeInfo()
    }
}
复制代码

详情页

全局事件

详情页绑定了一个全局事件,当我在详情页面中滚动,这个样写没有问题,但是当我去到其他页面,在滚动时,你就会发现,刚刚你绑定在详情页中的滚动事件,在这个页面也被执行了,这肯定是有问题的。

其实在我们使用了 keep-alive 标签后,会有两个生命周期函数分别是: activateddeactivated

activated :页面展示的时候被执行

deactivated :页面被隐藏或者页面即将被替换成新的页面时被执行

activated () {
    window.addEventListener('scroll', this.handleScroll)
},
deactivated () {
    window.removeEventListener('scroll', this.handleScroll)
}
复制代码

这段代码是页面被展示的执行 scroll ,页面被隐藏的时候移除 scroll 事件

递归组件

递归组件就是在我组件的自身去调用组件的自身

假如说现在有这样的数据结构,一级标题,二级标题,三级标题,如何实现呢?

data () {
    return {
        "categoryList": [{
            "title": "成人票",
            "children": [{
                "title": "成人三馆联票",
                "children": [{
                    "title": "成人三馆联票 - 某一销售店"
                }]
            }, {
                "title": "成人五馆联票"
            }]
        }, {
            "title": "儿童票"
        }, {
            "title": "学生票"
        }, {
            "title": "特惠票"
        }]
    }
}
复制代码

对一层标题用 v-for 来进行循环,二、三层标题该怎么显示出来呢?在写组件的时候,都会写一个 name 的属性,它其中一个用途就是—— 递归组件

<div
    class="item"
    v-for="(item, index) of categoryList"
    :key="index"
>
    <div class="item-title">
      <span class="item-title-icon"></span>
      {{item.title}}
    </div>
    <div class="item-title-children" v-if="item.children">  //判断是否有数据中是否有 children 这个属性,如果有就使用递归组件
      <detail-list :categoryList="item.children"></detail-list>     //把 children 传给递归组件
    </div>
 </div>
复制代码

如图所示:

去哪儿网项目学习总结

keep-alive 不缓存

Detail.vue 页面中,当我点击了其他景点后,它也是不会发送请求的,那么 Detail 页面就不会重新渲染了。

可以使用 keep-aliveexclude 属性,给它默认设置为 Detail ,用途是每次进入 Detail 页面都会发送请求

<keep-alive exclude="Detail">   //使用 exclude 属性,可以设置不需要缓存的页面
   <router-view/>
</keep-alive>
复制代码

组件中 name 名字的用途

组件中 name 这个值到底是干什么用的呢:

  • 递归组件可以用到
  • 当你相对某个页面想取消缓存的时候会用到
  • 在 Vue 的开发调试工具中会用到

webpack 别名设置

使用 alias

项目中有许多地方需要引入一些公用样式,此项目样式是用 stylus 写了,比如很多地方都需要用到主题色,统一写在一个文件中后期维护很方便。但是引入这个文件很麻烦:

../../../assets/styles/varible.css
复制代码

如果每个页面都这样引入文件,一方面写的不优雅,另一方面维护也不方便。

module.exports = {
  ...
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      'styles': resolve('src/assets/styles')
    }
  }
}
复制代码

build/webpack.base.config.js 文件中找到 resolvealias ,它可以对路径进行简化操作,项目中引入这个文件只需要写 styles/varible.css 即可。

路径分配

在自己开发中,经常需要自己 moke 数据`

axios.get('/api/detail.json', {
    params: {
      id: this.$route.params.id
    }
}).then(this.getDetailInfoSucc)
复制代码

这样写路径是访问不到自己 mock 的数据的,那应该怎么写呢?

axios.get('/static/mock/detail.json', {
    params: {
      id: this.$route.params.id
    }
}).then(this.getDetailInfoSucc)
复制代码

/api 改成 /static/mock/ 这样访问到我们本地的数据了,但是这样有风险的,上线前你需要改回 /api ,很容易出错,造成 bug

module.exports = {
  dev: {
    ...
    proxyTable: {
      '/api':{
        target: 'http://localhost:8080',
        pathRewrite: {
          '^/api':'/static/mock'
        }
      }
    }
 }
复制代码

config/index.js 文件中找到 dev 下的 proxyTable ,它可以代理路径,我们在项目中写 /api ,通过 proxyTable 可以自动找到 /static/mock 这个目录。

移动端可以访问

当手机上用本地的 ip 地址访问项目时被拒绝了,这是因为前端项目是通过 webpack-dev-server 启动的, webpack-dev-server 默认不支持 ip 的形式访问页面,这就需要把它默认的配置项做一个修改。

"scripts": {
    "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "lint": "eslint --ext .js,.vue src",
    "build": "node build/build.js"
}
复制代码

当每次运行 npm run start 或者 npm run dev 时,都是在运行 scriptsdev 指令,只需要在它上面加上 --host 0.0.0.0 就可以了


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

我的第一本算法书

我的第一本算法书

[日]石田保辉、[日]宮崎修一 / 张贝 / 人民邮电出版社 / 2018-10 / 69.00元

本书采用大量图片,通过详细的分步讲解,以直观、易懂的方式展现了7个数据结构和26个基础算法的基本原理。第1章介绍了链表、数组、栈等7个数据结构;从第2章到第7章,分别介绍了和排序、查找、图论、安全、聚类等相关的26个基础算法,内容涉及冒泡排序、二分查找、广度优先搜索、哈希函数、迪菲 - 赫尔曼密钥交换、k-means 算法等。 本书没有枯燥的理论和复杂的公式,而是通过大量的步骤图帮助读者加深......一起来看看 《我的第一本算法书》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换