Vue.js开发去哪儿网WebApp

  1. 理解整个vue项目的开发流程,上手中型vue项目的开发

Vue Router 来做多页面的路由
Vuex 多个组件的数据共享
插件swiper实现页面轮播效果
Axios 来进行 Ajax 数据的获取

  1. 移动端页面布局技巧
  2. stylus 编写前端的样式
  3. 公用组件的拆分
  4. 规范的代码编写
    1.4 项目目录
    附上项目目录和仓库地址vue仿去哪儿网webapp
    F:.
    │ .babelrc
    │ .editorconfig
    │ .eslintignore
    │ .eslintrc.js
    │ .gitignore
    │ .postcssrc.js
    │ index.html
    │ package-lock.json
    │ package.json
    │ README.en.md
    │ README.md

    ├─build
    │ build.js
    │ check-versions.js
    │ logo.png
    │ utils.js
    │ vue-loader.conf.js
    │ webpack.base.conf.js
    │ webpack.dev.conf.js
    │ webpack.prod.conf.js

    ├─config
    │ dev.env.js
    │ index.js
    │ prod.env.js

    ├─src
    │ │ App.vue
    │ │ main.js
    │ │
    │ ├─assets
    │ │ └─styles
    │ │ │ border.css
    │ │ │ iconfont.css
    │ │ │ mixins.styl
    │ │ │ reset.css
    │ │ │ varibles.styl
    │ │ │
    │ │ └─iconfont
    │ │ iconfont.eot
    │ │ iconfont.svg
    │ │ iconfont.ttf
    │ │ iconfont.woff
    │ │
    │ ├─common
    │ │ ├─fade
    │ │ │ FadeAnimation.vue
    │ │ │
    │ │ └─gallary
    │ │ Gallary.vue
    │ │
    │ ├─pages
    │ │ │ testGit.js
    │ │ │
    │ │ ├─city
    │ │ │ │ City.vue
    │ │ │ │
    │ │ │ └─components
    │ │ │ Alphabet.vue
    │ │ │ Header.vue
    │ │ │ List.vue
    │ │ │ Search.vue
    │ │ │
    │ │ ├─detail
    │ │ │ │ Detail.vue
    │ │ │ │
    │ │ │ └─components
    │ │ │ Banner.vue
    │ │ │ Header.vue
    │ │ │ List.vue
    │ │ │
    │ │ └─home
    │ │ │ Home.vue
    │ │ │
    │ │ └─components
    │ │ Header.vue
    │ │ Icons.vue
    │ │ Recommend.vue
    │ │ Swiper.vue
    │ │ Weekend.vue
    │ │
    │ ├─router
    │ │ index.js
    │ │
    │ └─store
    │ index.js
    │ mutations.js
    │ state.js

    └─static
    .gitkeep

复制代码1.5 项目代码初始化
由于做的是webapp,所以需要针对移动端,做相应的准备。

  1. meta标签相关设置
    index.html

复制代码效果:页面比例始终是1:1,用户通过手指操作缩放是无效的
2. 引入reset.css
目的:重置页面样式

因为在不同移动端、不同浏览器上页面的初始样式是不一样的,引入reset.css为了保证在每个浏览器上展示出的初始效果是一样的

  1. 引入border.css
    目的:解决移动端1像素边框问题
  2. 项目中安装fastclick
    npm install fastclick --save
    目的:解决移动端300ms延迟问题
    1.6 页面组件化
    路由
    router-index.js
    import Vue from ‘vue’
    import Router from ‘vue-router’
    import Home from ‘@/pages/home/Home’
    import City from ‘@/pages/city/City’
    import Detail from ‘@/pages/detail/Detail’

Vue.use(Router)

export default new Router({
routes: [{
path: ‘/’,
name: ‘Home’,
component: Home
}, {
path: ‘/city’,
name: ‘City’,
component: City
}, {
path: ‘/detail/:id’,
name: ‘Detail’,
component: Detail
}],
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
})

复制代码页面相关目录
pages
├─city
│ │ City.vue
│ │
│ └─components
│ Alphabet.vue
│ Header.vue
│ List.vue
│ Search.vue

├─detail
│ │ Detail.vue
│ │
│ └─components
│ Banner.vue
│ Header.vue
│ List.vue

└─home
│ Home.vue

└─components
Header.vue
Icons.vue
Recommend.vue
Swiper.vue
Weekend.vue
common
├─fade
│ FadeAnimation.vue

└─gallary
Gallary.vue
复制代码
比如,对于景点门票页面,可以将其拆分成若干个小组件,放到 components 目录下,通过在 Home.vue 容器组件中引用组件,整合出页面

Home.vue部分代码

复制代码二、项目插件的使用
2.1 Ajax 获取 首页数据
vue推荐使用axios,实现跨平台的数据请求
安装 axios
npm install axios --save

在 Home.vue 发送 Ajax 请求是最好的选择,这个组件获取 Ajax 数据之后,可以把数据传给每个子组件

把一些静态的文件放置在static目录下,通过 http://localhost:8080/static/mock/index.json 可以访问到

static
│ .gitkeep

└─mock
city.json
detail.json
index.json
复制代码Home.vue 部分代码

复制代码父子组件之间进行通讯

父组件通过 props 传递数据给子组件,子组件通过 emit 发送事件传递数据给父组件

以 List 组件 为例(List.vue 部分代码)

热销推荐
  • {{item.title}}

    {{item.desc}}

    查看详情

复制代码2.2 轮播图
安装 vue-awesome-swiper 插件
npm install vue-awesome-swiper@2.6.7 --save
轮播在多个组件中使用

以 home-components-Swiper.vue 为例

复制代码2.3 Better-scroll
安装
npm install better-scroll --save
使用

  • ...
  • ...
  • ...

export default {
city: defaultCity
}

复制代码mutations.js
export default {
changeCity (state, city) {
state.city = city
try {
localStorage.city = city
} catch (e) {}
}
}
复制代码index.js
import Vue from ‘vue’
import Vuex from ‘vuex’
import state from ‘./state’
import mutations from ‘./mutations’

Vue.use(Vuex)

export default new Vuex.Store({
state,
mutations
})

复制代码Home.vue 组件,在计算属性中,this. s t o r e . s t a t e . x x x , 在 这 个 项 目 中 是 t h i s . store.state.xxx,在这个项目中是 this. store.state.xxxthis.store.state.city可以获取到 state 数据。当然,为了使代码更加简洁,用 mapState 将 this.xxx 映射为 this. s t o r e . s t a t e . x x x 。 在 L i s t . v u e 中 , 通 过 c o m m i t 来 触 发 m u t a t i o n s 里 面 的 方 法 进 行 数 据 的 修 改 。 同 样 , 为 了 使 代 码 更 加 简 洁 , 引 入 m a p M u t a t i o n s 将 t h i s . c h a n g e C i t y ( c i t y ) 映 射 为 t h i s . store.state.xxx。 在 List.vue 中,通过 commit 来触发 mutations 里面的方法进行数据的修改。同样,为了使代码更加简洁,引入 mapMutations 将 this.changeCity(city) 映射为 this. store.state.xxxList.vuecommitmutations使mapMutationsthis.changeCity(city)this.store.commit(‘changeCity’, city)。
【city/List.vue 具体是】
import { mapState, mapMutations } from ‘vuex’

computed: {
…mapState({
currentCity: ‘city’
})
},
methods: {
handleCityClick (city) {
// this. s t o r e . c o m m i t ( ′ c h a n g e C i t y ′ , c i t y ) t h i s . c h a n g e C i t y ( c i t y ) t h i s . store.commit('changeCity', city) this.changeCity(city) this. store.commit(changeCity,city)this.changeCity(city)this.router.push(’/’)
},
…mapMutations([‘changeCity’])
}
复制代码这样就实现了这几个组件的数据共享。
三、项目难点
3.1 兄弟组件间联动
实现功能:点击城市列表页面右侧的字母,列表选项会滚动到对应的字母区域。【gif展示】

兄弟组件的传值,可以通过 bus 总线的形式来传值。但是因为我们现在这个非父子组件比较简单,可以让 Alphabet.vue 组件将值传递给父组件 City.vue 组件,然后 City.vue 组件再将值转发给 List.vue 组件,这样就实现了兄弟组件的传值。【子组件给父组件,父组件再转给另一个子组件】。这样,在 Alphabet.vue 中点击右侧字母,会获取到对应的字母。

Alphabet.vue
在循环的元素上加一个点击事件,例如 handleLetterClick,然后在 methods 中写这个事件方法:

    复制代码接下来,将父组件接收到的这个数据转发给子组件 List.vue,父组件是通过属性向子组件传值的。
    首先在父组件 City.vue 里的 data 中定义一个 letter,默认值是空,在 handleLetterClick 方法中,当接受到外部传来的 letter 的时候,让 this.letter = letter。
    City.vue

    复制代码最后只需要把 letter 传递给子组件 List.vue 就可以了,在 City.vue 组件的模板 city-list 中通过 :letter=“letter” 向子组件 List 传值,在 props 中接收这个 letter,并且验证类型为 String 类型。
    List.vue
    props: {
    hot: Array,
    cities: Object,
    letter: String
    }
    复制代码这样就实现了兄弟组件的传值。
    【项目难点】
    接下来要做的是,当 List.vue 发现 letter 有改变的时候,就需要让组件显示的列表项跟 letter 相同的首字母的列表项要显示出来,怎么做呢?
    这个时候就要借助一个侦听器,监听letter的变化;
    better-scroll 给提供了这样一个接口,scroll.scorllToElement,如果 letter 不为空的时候,就调用 this.scroll.scrollToElement() 这个方法,可以让滚动区自动滚到某一个元素上,那么怎么传这个元素呢?在循环城市这一块中,给循环项加一个 ref 引用来获取当前 Dom 元素,等于 key,然后回到侦听器的 letter 中,定义一个 element,它就等于通过 ref 获取到的元素:

    List.vue
    watch: {
    letter() {
    if (this.letter) {
    const element = this.$refs[this.letter][0];
    this.scroll.scrollToElement(element);
    }
    }
    },
    复制代码这个时候就可以通过字母获取到对应的区域,然后把 element 传入 scrollToElement 里,注意,上边代码最后加了一个 [0],这是因为如果不加,通过 ref 或的内容就是一个数组,这个数组里的第一个元素才是真正的 DOM 元素,这个时候,点击右侧字母表,就可以跳到对应的字母下的城市列表了。
    点击跳转的功能实现啦
    接下来再实现一下滑动右侧字母表,左侧城市列表切换的效果。

      复制代码3.2 search组件
      功能:进入到城市选择页面的时候,当 focus 到搜索框,输入城市名或拼音能够把搜索的结果显示出来。

        复制代码【性能优化—防抖】

        写一个侦听器 watch,在里边监听 keyword 的改变,考虑到性能优化,使用防抖的方式来实现,先在 data 中定义一个 timer 定时器,默认值为 null,然后在监听 keyword 的方法中,判断,当 timer 为 null 时,清除这个定时器。下面写这个定时器的方法,当延时 100ms 的时候,箭头函数会被执行。

        3.3 递归组件
        递归组件的意思就是在组件自身调用组件自身。
        数据 detail.json
        {
        “ret”: true,
        “data”: {
        “sightName”: “大连圣亚海洋世界(AAAA景区)”,
        “bannerImg”: “http://img1.qunarzz.com/sight/p0/201404/23/04b92c99462687fa1ba45c1b5ba4ad77.jpg_600x330_bf9c4904.jpg”,
        “gallaryImgs”: [“http://img1.qunarzz.com/sight/p0/201404/23/04b92c99462687fa1ba45c1b5ba4ad77.jpg_800x800_70debc93.jpg”, “http://img1.qunarzz.com/sight/p0/1709/76/7691528bc7d7ad3ca3.img.png_800x800_9ef05ee7.png”],
        “categoryList”: [{
        “title”: “成人票”,
        “children”: [{
        “title”: “成人三馆联票”,
        “children”: [{
        “title”: “成人三馆联票 - 某一连锁店销售”
        }]
        },{
        “title”: “成人五馆联票”
        }]
        }, {
        “title”: “学生票”
        }, {
        “title”: “儿童票”
        }, {
        “title”: “特惠票”
        }]
        }
        }

        复制代码list.vue

        {{item.title}}

        复制代码
        上面代码中,在 list-children 这个元素下,先做了一个判断,当 item.children 下有值的时候,调用一下自身,也就是 detail-list 这个组件,这个组件也是通过属性的形式,传一个 list,因为在 list.vue 中已经通过 props 接收到 list 了,而且外层已经循环过 list 了,现在要获取 list 下的 children 中的数据,所以直接让这个 list 属性等于 item.children 就可以了。因为数据存在层级关系,可以通过添加样式呈现出来,效果如下图:

        四、项目中遇到的问题及解决方案
        这部分内容并不是所有在项目中遇到的问题和解决方法,因为上文中也有相应的描述,这部分内容是对上文的补充。
        4.1 localStorage
        刚开始在实现首页右上角城市定位显示的时候,src 目录下新建了一个 store 目录,存储了 Vuex 中的默认数据,city 直接设置成了“北京”,但是其实这样去写,是有问题的,点击城市,会改变这个 city,但是当页面刷新了,就又变回了北京。
        考虑到在真实的项目中,如果你这次选中了一个城市,下次再打开这个网页的时候,上次选的城市还应该在的,怎么解决这个问题呢?
        这时可以借助 HTML5 中提供了一个新的 api,叫做 localStorage localStorage,它可以实现本地存储,在这里也就是实现保存城市的功能。
        store/index.js中,这样去写代码,当用户尝试去改变城市的时候,我不但把 state 中的 city 改了,同时还去存一个 localStorage,直接写 localStorage.city = city 就可以了。然后让 stare 中 city 的默认值是 localStorage.city || “北京”,就可以了。也就是 city 的值我默认先去 localStorage 中取,如果取不到,才用默认的 “北京”。
        store/index.js
        import Vue from “vue”;
        import Vuex from “vuex”;
        Vue.use(Vuex);
        export default new Vuex.Store({
        state: {
        city: localStorage.city || “北京”
        },
        mutations: {
        changeCity(state, city) {
        state.city = city;
        localStorage.city = city;
        }
        }
        })
        复制代码这个时候打开页面,当用户选择一个城市,然后刷新页面,可以看到上次选择的城市还在。但是当使用 localStorage 的时候,建议在外层包裹一个 try{ }catch(e){ },因为在某些浏览器,如果用户关闭了本地存储这样的功能,或者使用隐身模式,使用 localStorage 可能导致浏览器直接抛出异常,代码就运行不了了,为了避免这种问题,建议在外层加一个 try{ }catch(e){ },怎么加呢?
        先定义一个默认的 defaultCity 等于“北京”,然后写一个 try{ }catch(e){ },这样写:如果有 localStorage.city,default.city 就等于 localStorage.city,下边 state 中的 city 就可以等于 defaultCity 了,同样在 mutations 的 changeCity 中也要写一个 try{ }catch(e):
        store/index.js
        import Vue from “vue”;
        import Vuex from “vuex”;
        Vue.use(Vuex);

        let defaultCity = “北京”
        try {
        if (localStorage.city) {
        defaultCity = localStorage.city;
        }
        } catch (e) { }

        export default new Vuex.Store({
        state: {
        city: defaultCity
        },
        mutations: {
        changeCity(state, city) {
        state.city = city;
        try {
        localStorage.city = city;
        } catch (e) { }
        }
        }
        })
        复制代码现在我们看到 store/index.js 这个文件慢慢的变得复杂起来了,实际上,在真正的项目开发和之中,会做进一步的拆分,也就是把这个文件拆分为 State、Actions、Mutations,在 store 中创建一个文件叫 state.js(只存储公用数据),然后把设置默认数据的这块代码放进去,并通过 export 导出,内容就是在 index.js 中定义的 state 对象里的内容:
        let defaultCity = “北京”
        try {
        if (localStorage.city) {
        defaultCity = localStorage.city;
        }
        } catch (e) { }
        export default {
        city: defaultCity
        }
        复制代码接下来,只需要在 index.js 中 import state 就可以了:
        import Vue from “vue”;
        import Vuex from “vuex”;
        import state from “./state”;
        Vue.use(Vuex);

        export default new Vuex.Store({
        state: state,
        mutations: {
        changeCity(state, city) {
        state.city = city;
        try {
        localStorage.city = city;
        } catch (e) { }
        }
        }
        })
        复制代码接着,在 store 目录下创建一个文件,叫做 mutations.js,然后把 index.js 中的 mutations 对象里的代码剪切进去:
        export default {
        changeCity(state, city) {
        state.city = city;
        try {
        localStorage.city = city;
        } catch (e) { }
        }
        }
        最终 index.js 就变成了这样:

        import Vue from “vue”;
        import Vuex from “vuex”;
        import state from “./state”;
        import mutations from “./mutations”;
        Vue.use(Vuex);

        export default new Vuex.Store({
        state: state,
        mutations: mutations
        })
        复制代码这样,我们就将 vuex 的代码拆分成了 State、Actions、Mutations 这几个部分,未来它的维护性也会得到比较大的提高。
        4.2 keep-alive
        使用 keep-alive 优化网页性能
        当写完城市列表响应代码,启动服务,打开页面,这样看不存在什么问题,基本的一些业务逻辑都已经实现了,但是在控制台中打开 Network 网络这个选项,选择 XHR,当初次进入首页的时候,请求了一个 index.json 的文件,然后切换到列表页,又请求了一个 city.json,然后再回到首页,index.json 又请求了一次,再次去列表页,city.json 又请求了一次,也就是,每一次路由发生变化的时候,Ajax 都会重新的被发送。

        思考是什么原因导致这样的问题呢,打开 Home.vue 首页这个组件,每一次打开这个首页的时候,都会被重新的渲染,所以 mounted 这个钩子就会被重新的执行,那么这个 Ajax 数据就会被重新获取,那么这么能让它只获取一次呢?
        打开 main.js,可以看到入口组件是 App 这个组件,再打开 App.vue,router-view 显示的是当前地址所对应的内容,我们可以在外层包裹一个 keep-alive 的一个标签,他是 Vue 自带的一个标签,他的意思就是我的路由的内容被加载一次后,我就把路由中的内容放到内存之中,下一次再进入这个路由的时候,不需要重新渲染这个组件,去重新执行钩子函数,只要去内存里把以前的内容拿出来就可以。

        评论
        添加红包

        请填写红包祝福语或标题

        红包个数最小为10个

        红包金额最低5元

        当前余额3.43前往充值 >
        需支付:10.00
        成就一亿技术人!
        领取后你会自动成为博主和红包主的粉丝 规则
        hope_wisdom
        发出的红包
        实付
        使用余额支付
        点击重新获取
        扫码支付
        钱包余额 0

        抵扣说明:

        1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
        2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

        余额充值