跟学一个网课老师作的仿原生音乐APP跟学的笔记,记录点滴,也但愿对学习vue初学小伙伴有点帮助
import fastClick from 'fastclick' fastClick.attach(document.body)
对es6的高级语法进行转义当运行环境中并无实现的一些方法,babel-polyfill 会给其作兼容
须要在main.js中引入javascript
babel-runtime 是供编译模块复用工具函数。是锦上添花
babel-polyfil是雪中送炭,是转译没有的api.
创建基本的页面骨架,基本的组件引入
header rank recommend search singer tab 这几个组件组成页面骨架css
qq音乐html
Jsonp发送的不是一个ajax请求,他动态建立一个script标签,script没有同源策略限制,因此能跨域 有一个返回参数 callback , 后端解析url,返回一个方法。
scr/common/jsonp.js
中import originJSONP from 'jsonp' export default function jsonp(url, data, option) { // jsonp的三个参数 // - url-->一个纯净的url地址 // - data --> url中的 query 经过 data 拼到url上 // - option url += (url.indexOf('?') < 0 ? '?' : '&') + param(data) return new Promise((resolve, reject) => { originJSONP(url, option, (err, data) => { if (!err) { resolve(data) } else { reject(err) } }) }) } // 拼接data到url function param (data) { let url = '' for (var k in data) { let value = data[k] !== undefined ? data[k] : '' url += `&${k}=${encodeURIComponent(value)}` } // encodeURIComponent() 函数可把字符串做为 URI 组件进行编码。 return url ? url.substring(1) : '' }
注意:当路径报错的时候,咱们要想到webpack.base.conf.js配置文件中的 alias 选项 确保路径是否匹配
recommend.vue
中的 created
生命周期钩子中调用_getRecommend
方法_getRecommend
方法调用recommend.js
中暴露出来的getRecommend
方法getRecommend
方法调用了 Jsonp
方法, Jsonp方法抓取接口,从而得到数据
- 有的jsonp接口url很长,可是真正的url知识前面的部分
- 大公司通常用0来表明一切正常
轮播图数据获取完成后,就下来作的就是搭建轮播页面 ,接下来编写一个轮播组件 slider.vue
java
base
文件夹,储存如同slider.vue的基础组件在silder.vue中,咱们使用了slot插槽,外部引用slider的时候slider标签里面包裹的dom会被插入到slot插槽部分。webpack
import Slider from 'base/slider/slider'
,并在components中注册Slider,以后就可使用Slider标签了这个时候咱们打开项目,会发现已有数据,可是样式还不行,在props中添加loop,autoplay,interval(滚动间隔),
使用了第三方轮播 better-scroll 来进一步实现 slidergit
新版的BS中snap属性集合成了一个对象选项 而旧版的是单独的属性名,这点要注意
咱们要保证渲染的时机是正确的,一般在mounted
生命周期钩子中初始化,保证BS正常渲染的话咱们一般在mounted里面加一个延迟es6
mounted () { setTimeout(() => { // 浏览器17ms刷新一次, 这里延迟20ms 确保组件已经渲染完成 this._setSliderWidth() // 设置slider宽度 this._initDots() // 初始话dots this._initSlider() // 初始化slider }, 20)
_setSliderWidth
方法 -- 轮播图组件的宽度计算这里要注意,这时候执行玩宽度方法以后,可能无效,这是由于在宽度计算的时候,slot插槽里面的东西还未加载,为了解决这个问题,咱们能够在recommend.vue中 给slider 的父元素 加上v-if="recommends.length"
,确保渲染时机正确github
_initSlider()
方法 -- 使用new BScroll
建立轮播实例,设置无限滚动及其余的相关初始化配置,至此,咱们的轮播页面已经能够无缝滚动了添加dots导航web
五个数据,dom有七个,由于loop为ture的时候,bs会自动在先后各拷贝一份。咱们想要添加dots,必须保证和数据数同样,因此咱们应该在bs初始化以前完成dots的初始化
初始化dots为一个长度为childern.length的数组this.dots = new Array(this.children.length)
在slider.vue中循环 v-for="(item,index) of dots"
添加选中样式:class="{active:currentPageIndex === index}"
在bs滚动的时候 会派发一个事件 在初始化slider 绑定一个事件
this.slider.on('scrollEnd', () => { let pageIndex = this.slider.getCurrentPage().pageX if (this.loop) { pageIndex -= 1 this.currentPageIndex = pageIndex if (this.autoplay) { clearTimeout(this.timer) this._play() } } })
使用了 bs中的 getCurrentPage
方法来获取滚动的当前页面
在autoplay中使用了bs 的 goToPage
方法来实现轮播
以前的slider基本完成,可是此时若是改变窗口大小,页面就会乱掉
使用resize窗口监听事件,配合bs的refresh刷新方法 实现每一次改变窗口大小都能重置宽度
window.addEventListener('resize', () => { if (!this.slider) { // slider尚未初始化的时候 return } this._setSliderWidth(true) this.slider.refresh() })
在app.vue 中使用keepalive标签,来避免重复请求
咱们在跳转到其余页面的时候,要记得清理定时器,优化效率
destroyed() { clearTimeout(this.timer) // 性能优化小习惯 }
在pc版的qq音乐中获取请求接口
因为QQ音乐的歌单数据时,请求接口host和refer规定了必须是qq音乐的地址,咱们本地就会请求失败。为了解决这个问题,咱们可使用 手动代理 假装成qq音乐地址请求接口 欺骗接口
在项目开发的时候,接口联调的时候通常都是同域名下,且不存在跨域的状况下进行接口联调,可是当咱们如今使用vue-cli进行项目打包的时候,咱们在本地启动服务器后,好比本地开发服务下是 http://localhost:8080 这样的访问页面,可是咱们的接口地址是 http://xxxx.com/save/index 这样的接口地址,咱们这样直接使用会存在跨域的请求,致使接口请求不成功,所以咱们须要在打包的时候配置一下,咱们进入 config/index.js 代码下以下配置便可:
dev: { // 静态资源文件夹 assetsSubDirectory: 'static', // 发布路径 assetsPublicPath: '/', // 代理配置表,在这里能够配置特定的请求代理到对应的API接口 // 例如将'localhost:8080/api/xxx'代理到'www.example.com/api/xxx' // 使用方法:https://vuejs-templates.github.io/webpack/proxy.html proxyTable: { '/': { target: 'https://c.y.qq.com', // 接口的域名 secure: false, // 若是是https接口,须要配置这个参数 changeOrigin: true, // 若是接口跨域,须要进行这个参数配置 pathRewrite: { '^/api': '/' }, headers: { referer: 'https://c.y.qq.com' } } }
注意: '/api' 为匹配项,target 为被请求的地址,由于在 ajax 的 url 中加了前缀 '/api',而本来的接口是没有这个前缀的,因此须要经过 pathRewrite 来重写地址,将前缀 '/api' 转为 '/'。若是自己的接口地址就有 '/api' 这种通用前缀,就能够把 pathRewrite 删掉。
咱们经过代理得到ajax数据后,将其赋值给 discListthis.discList = res.data.list
以后将disclist渲染到组件中v-for="item of discList"
因为 滚动 是一个很基础的组件 因此在common里建立scroll.vue组件,使代码结构化
<template> <div ref="wrapper"> <slot></slot> </div> </template>
在Recommend.vue中 必定要绑定data数据,由于scroll.vue中 watch 监听data数据的变化来刷新better-scroll 这里的能够绑定recommend.vue中的 discList 数组来座位 data这里的 recommends 和 discList 数据获取是有前后顺序的,通常都是先recommends再discList,若是先获取到的是discList的话 歌单列表就会出现滚动不到底部的问题
为了确保recommend数据后加载的状况下咱们的表单还能正常滚动发,咱们能够给slider中的img添加一个loadImage方法@load="loadImage"
,方法调用一个 refresh方法便可 this.$refs.scroll.refresh()
为了不请求的每一张图片都执行一次,咱们能够设置一个bool标志位来控制 ,只要有一张图片加载完成便可,以下:
loadImage() { if (!this.checkLoaded) { this.$refs.scroll.refresh() this.checkLoaded = true } }
节省流量,提高加载速度
npm 安装
npm install vue-lazyload
在main.js中添加代码
import VueLazyLoad from 'vue-lazyload' Vue.use(VueLazyLoad, { loading: require('common/images/touxiang.png') })
在Recommend.vue中使用<img v-lazy="item.imgurl" alt="">
有些状况下点击事件之间互相冲突,咱们在使用fastclick的时候,能够给点击的dom添加一个fastclick里的一个css needsclick
的类名,来确保点击事件能够正常执行
为了增长交互体验,在表单还未渲染以前,咱们可使用一个loading来占位。
在base中新建loading组件
<template> <div class="loading"> <img src="./loading.gif" alt=""> <p class="desc">{{title}}</p> </div> </template> <script> export default { props: { title: { type: String, default: '许文瑞正在吃屎。。。。' } } } </script>
在recommend.vue中添加以下代码:
<div class="loading-content" v-show="!discList.length"> <loading></loading> </div>
咱们和之前同样,利用咱们封装的jsonp等发放,来请求咱们的接口,返回给singer.vue。
成功获取数据之后,咱们发现,官网的数据的数据结构和咱们想要的不同,因此咱们下一步进行数据结构的聚合处理
咱们但愿的数据结构是数据按照字母排序的数组再加上一个热门的数组的集合,显然咱们在官网的到的数据不是这样的,咱们构造一个_normalizeSinger方法来完成:
_normalizeSinger(list) { // 处理数据结构 形参为list let map = { // 把数据都存在map对象中 hot: { // 热门城市 title: HOT_NAME, items: [] // 初始化空数组 } } list.forEach((item, index) => { // 循环数组中的每一项 if (index < HOT_SINGER_LENGTH) { // 由于原始数据是按照热度排列的,因此获取前十的热门 map.hot.items.push(new Singer({ // push到咱们的hot数组中 // new Singer: 为了模块化和减小代码的复用,咱们在common > js 建立了一个singer.js // 来建立一个类构造器 里面包括歌手头像的拼接 id: item.Fsinger_mid, name: item.Fsinger_name })) } const key = item.Findex // 歌手姓氏字首字母 if (!map[key]) { // 若是不存在 map[key] = { // 建立 title: key, items: [] } } map[key].items.push(new Singer({ // 追加到map.items中 id: item.Fsinger_mid, name: item.Fsinger_name })) }) // 为了获得有序列表 咱们须要处理map let hot = [] // 热门城市 let ret = [] // 字母表城市 for (let key in map) { // 循环 let val = map[key] if (val.title.match(/[a-zA-Z]/)) { // 正则匹配字母 ret.push(val) } else if (val.title === HOT_NAME) { hot.push(val) // 热门城市 } } ret.sort((a, b) => { return a.title.charCodeAt(0) - b.title.charCodeAt(0) // 把字母城市按charcode字母排序 }) return hot.concat(ret) // 将字母城市追加到hot城市 返回给外部 }
细节点注意
关于歌手图片的获取,经过官网观察,咱们发现图片是有一个网址拼接item.Fsinger_mid
来完成的,因此咱们在common >js >singer.js中 使用了${}
来拼接,获取歌手图片地址,拼接url语法是使用的是 `` 而不是' '
数据咱们获取到了,咱们接下来开发listview.vue组件,由于这个列表组件咱们后面有不少页面也要用到,因此咱们在base下建立基础组件 listview.vue
在listview.vue中引入 咱们以前封装好的scroll组件import Scroll from 'base/scroll/scroll'
经过获取的数据,进行两次遍历渲染,就能获得咱们想要的dom页面了
html代码以下
<template> <scroll class="listview" :data="data"> <ul> <li v-for="(group, index) in data" :key="index" class="list-group"> <h2 class="list-group-title">{{group.title}}</h2> <ul> <li v-for="(item, index) in group.items" :key="index" class="list-group-item"> <img v-lazy="item.avatar" class="avatar"> <span class="name">{{item.name}}</span> </li> </ul> </li> </ul> <div class="list-shortcut"> <ul> <li class="item" v-for="(item, index) in shortcutList" :key="index"> {{item}} </li> </ul> </div> </scroll> </template>
至此 歌手页面就能正常滚动了
接下来,开始咱们的字母导航器的样式制做
咱们能够在listview.vue中建立一个计算属性shortcutList
computed: { shortcutList() { return this.data.map((group) => { return group.title.substr(0, 1) }) } },
以后在页面中v-for渲染shortcutList便可 配合css样式 实现边栏的字母导航dom的制做
<div class="list-shortcut" @touchstart="onShortcutTouchStart" @touchmove.stop.prevent="onShortcutTouchMove" > <ul> <li class="item" v-for="(item, index) in shortcutList" :key="index" :data-index="index" :class="{'current': currentIndex === index}" > {{item}} </li> </ul> </div>
静态的字母导航在页面中已经展示出来了
接下来 来给导航器添加滑动点击等事件,使其动态化
滑动右边字母导航 listview实时滚动
在字母html标签中加入touch事件**
@touchstart="onShortcutTouchStart" @touchmove.stop.prevent="onShortcutTouchMove"
在循环中遍历index值,在后面的touch中获取索引,因为蕾相似此类获取数据的方法是不少地方都能用到的,咱们在dom.js中添加getData方法
export function getData(el, name, val) { const perfix = 'data-' name = perfix + name if (val) { return el.setAttribute(name, val) } else { return el.getAttribute(name) } }
接下来 为scroll组件添加 跳转方法
scrollTo() { this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments) }, scrollToElement() { this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
完整的touch方法代码以下:
onShortcutTouchStart(e) { let anchorIndex = getData(e.target, 'index') // 获取data let firstTouch = e.touches[0] // 刚开始触碰的位置坐标 this.touch.y1 = firstTouch.pageY this.touch.anchorIndex = anchorIndex this._scrollTo(anchorIndex) // 经过使用_scrollTo方法来跳转到咱们的字母所在位置 }, onShortcutTouchMove(e) { // 屏幕滑动方法 要明确开始滚动和结束滚动的两个位置,而后计算出滚动到哪个字母 let firstTouch = e.touches[0] // 中止滚动时的位置坐标 this.touch.y2 = firstTouch.pageY // 保存到touch对象中 let delta = (this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT | 0 // 计算滚动了多少个字母 let anchorIndex = parseInt(this.touch.anchorIndex) + delta // this.touch.anchorIndex 字符串转化为整型 this._scrollTo(anchorIndex) // 跳转到字母位置 }
注意:经过getData方法的到的anchorIndex是一个字符串,记得要用parseInt转化为数字
至此 滑动字母导航器 左边的list已经能够实现滚动了
滚动左边list 右边字母导航高亮
解决这个问题 ,就要知道左边listview滚动到的相对位置
在scroll标签组件绑定@scroll='scroll' 来将滚动的实时位置赋值给this.scrollY
scroll(pos) { this.scrollY = pos.y console.log(pos) // 测试 }
在listview中添加监视属性data
watch: { data() { setTimeout(() => { // 数据变化到dom变化有一个延迟,因此这个加一个定时器 this._calculateHeight() // 计算每个group的高度 }, 20) }
每次data变化,都会从新计算group的高度
_calculateHeight方法
_calculateHeight() { this.listHeight = [] const list = this.$refs.listGroup let height = 0 this.listHeight.push(height) for (let i = 0; i < list.length; i++) { let item = list[i] height += item.clientHeight this.listHeight.push(height) // 获得一个包含每个group高度的数组 } }
这样 就能获得一个包含全部grroup高度的一个数据
在watch里监听scrollY
拿到了每组的位置,咱们能够监听scrollY 联合二者判断字母导航器应该滚动到的位置
scrollY(newY) { const listHeight = this.listHeight // 当滚动到顶部 newY > 0 if (newY > 0) { this.currentIndex = 0 return } // 在中间部分滚动 for (let i = 0; i < listHeight.length; i++) { let height1 = listHeight[i] let height2 = listHeight[i + 1] if (-newY >= height1 && -newY < height2) { this.currentIndex = i this.diff = height2 + newY // 注意 newY为负值 return } } // 当滚动到底部,且-newY 大于最后一个元素的上线 this.currentIndex = listHeight.length - 2 }
:class="{'current': currentIndex === index}"
细节优化
完善_scrollTo方法
_scrollTo(index) { if (!index && index !== 0) { // 点击之外的部分 无反应 return } if (index < 0) { // 滑动到顶部时 index为负 index = 0 } else if (index > this.listHeight.length - 2) { // 滑动到尾部 index = this.listHeight.length - 2 } this.scrollY = -this.listHeight[index] // 每次点击都更改scrollY以实现同步 this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 300) }
fixedTitle
计算属性
fixedTitle() { if (this.scrollY > 0) { return '' } return this.data[this.currentIndex] ? this.data[this.currentIndex].title : '' }
页面html
<div class="list-fixed" v-show="fixedTitle" ref="fixed"> <h1 class="fixed-title">{{this.fixedTitle}}</h1> </div>
至此 顶部的fixedtitle标题就作好了 可是咱们发现两个title在重合的时候 并非很完美,下面咱们就来添加一个顶上去的动画来优化
在scrollY函数中 咱们能够轻松获取一个 diff 值
this.diff = height2 + newY // 注意 newY为负值
经过监听diff 咱们能够来实现咱们的要求
diff(newVal) { let fixedTop = (newVal > 0 && newVal < TITLE_HEIGHT) ? newVal - TITLE_HEIGHT : 0 if (this.fixedTop === fixedTop) { return } this.fixedTop = fixedTop this.$refs.fixed.style.transform = `translate3d(0,${fixedTop}px,0)` }
歌手详情使用二级子路由来开发
路由是由组件承载的
在router -- index.js中 写入代码
添加字路由
{ path: '/singer', name: 'Singer', component: Singer, children: [ { path: ':id', component: SingerDetail } ] }
如代码所示,在Singer component组件路由选项中,添加children
实现二级路由,而后须要在页面上加上router-view
标签来挂在这个二级路由显示页面
编写跳转逻辑
在次页面中,二级路由的跳转是在listview.vue中经过点击事件向外派发事件来实现的
selectItem(item) { this.$emit('select', item) // 向外派发事件 }
由于listview.vue是一个基础组件,不会编写业务逻辑,因此把点击事件派发出去,让外部实现业务逻辑的编写
在singer.vue 中,咱们监听到这个派发出来的select
<list-view :data="singers" @select="selectSinger"></list-view>
而后在selectSinger方法里面使用vue-router的 编程式跳转接口
selectSinger(singer) { this.$router.push({ path: `/singer/${singer.id}` // 跳转页面 }) }
将singer-detail.vue 组件用transition标签包裹
并在css中添加动画
.slide-enter-active, .slide-leave-active transition: all 0.3s .slide-enter, .slide-leave-to transform: translate3d( 0, 100%, 0)
就下来,开始正式开发singer-detail组件,在这以前,咱们先了解一下Vuex 跳转到vuex笔记
export function getSingerDetail(singerId) { const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg' const data = Object.assign({}, commonParams, { hostUin: 0, needNewCode: 0, platform: 'h5page', order: 'listen', begin: 0, num: 50, songstatus: 1, g_tk: 649509476, singermid: singerId // 注意是mid而不是id 不要出错 }) return jsonp(url, data, options) }
当在singer-detail页面上刷新的时候,会获取不到数据,由于咱们的数据是经过跳转获得的,若是咱们在singer-detail数据上刷新,将返回上一级signer
this.$router.push('/singer')
common>js>song.js
export default class Song { constructor({id, mid, singer, name, album, duration, image, url}) { this.id = id this.mid = mid this.singer = singer this.name = name this.album = album this.duration = duration this.image = image this.url = url } } export function createSong(musicData) { return new Song({ id: musicData.songid, mid: musicData.songmid, singer: filterSonger(musicData.singer), name: musicData.songname, album: musicData.albumname, duration: musicData.interval, image: `https://y.gtimg.cn/music/photo_new/T002R300x300M000${musicData.albummid}.jpg?max_age=2592000`, url: `http://ws.stream.qqmusic.qq.com/C100${musicData.songmid}.m4a?fromtag=0&guid=126548448&crazycache=1` }) } function filterSonger(singer) { let ret = [] if (!singer) { return '' } singer.forEach((s) => { ret.push(s.name) }) return ret.join('/') }
经过方法调用类构造器,咱们就能经过createSong(musicData)
来整理得到咱们须要的结构数据
singer-detail
methods: { _getDetail() { if (!this.singer.id) { this.$router.push('/singer') } getSingerDetail(this.singer.id).then((res) => { if (res.code === ERR_OK) { console.log(res.data.list) this.songs = this._normalizeSongs(res.data.list) } }) }, _normalizeSongs(list) { let ret = [] list.forEach((item) => { let {musicData} = item if (musicData.songid && musicData.albummid) { ret.push(createSong(musicData)) } }) return ret } }
这样 经过调用_normalizeSongs方法 --> createSong 来获得songs数据
在props中接受变量 bgImgae songs title
在singer-detail
经过计算属性拿到title 和 bgImage ,
<music-list :songs="songs" :title="title" :bg-image="bgImage"></music-list>
这样就完成了父组件的singer-detail向子组件的music-list的传值
由于歌曲列表是滚动的 咱们在music-list中复用了scroll组件咱们还须要编写一个song-lsit组件,为接下来所用 跳转到song-list组件开发
在music-list编写代码:
<scroll class="list" ref="list" :data="songs" :probe-type="probeType" :listen-scroll="listenScroll" @scroll="scroll" > <div class="song-list-wrapper"> <song-list :songs="songs"></song-list> </div> <div class="loading-container" v-show="!songs.length"> <loading></loading> </div> </scroll>
至此,打开页面,咱们能够看到歌单列表已经能够正常滚动
这是咱们发现咱们的页面上所有被歌单列表所占用, 要计算图片的位置把歌手背景图展示出来
在mounted生命周期钩子里添加
this.$refs.list.$el.style.top = `${this.$refs.bgImage.clientHeight}px`
这样就能实现歌手海报图的展现了
咱们在music-list.vue中加入一个layer层,用于跟着跟单一块儿滚动,来覆盖咱们的bg-image,这样就能视觉上达到咱们想要的效果了
<div class="bg-layer" ref="layer"></div>
监听滚动距离
为scroll组件传入probeType值和listenScroll值
created() { this.probeType = 3 this.listenScroll = true }
为scroll添加scroll方法来监听滚动距离
scroll(pos) { this.scrollY = pos.y }
并监听scrollY数据
watch: { scrollY(newY) { let translateY = Math.max(this.minTranslateY, newY) let zIndex = 0 let scale = 1 let blur = 0 this.$refs.layer.style[transform] = `translate3d(0, ${translateY}px, 0)` const percent = Math.abs(newY / this.imageHeight) if (newY > 0) { scale = 1 + percent zIndex = 10 } else { blur = Math.min(20 * percent, 20) } this.$refs.filter.style[backdrop] = `blur(${blur}px)` if (newY < this.minTranslateY) { zIndex = 10 this.$refs.bgImage.style.paddingTop = 0 this.$refs.bgImage.style.height = `${RESERVED_HEIGHT}px` this.$refs.pbtn.style.display = 'none' } else { this.$refs.bgImage.style.paddingTop = '70%' this.$refs.bgImage.style.height = 0 this.$refs.pbtn.style.display = '' } this.$refs.bgImage.style.zIndex = zIndex this.$refs.bgImage.style[transform] = `scale(${scale})` } }
处理方法见上面代码zIndex相关操做
处理见上代码 bgImage scale相关的操做
在scroll结尾复用loading 便可
<template> <div class="song-list"> <ul v-for="(song, index) in songs" :key="index" class="item"> <div class="content"> <h2 class="name">{{song.name}}</h2> <p class="desc">{{getDesc(song)}}</p> </div> </ul> </div> </template> <script type="text/ecmascript-6"> export default { props: { songs: { type: Array, default: () => [] } }, methods: { getDesc(song) { return `${song.singer} - ${song.album}` } } } </script>
在music-list中传入song值
<song-list :songs="songs"></song-list>
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
简单的说,当咱们的vue项目比较复杂的时候,有的时候两个兄弟组件,或者相关度联系很低的组件相互之间须要同时获取或监听同一个数据或状态,这个时候咱们就要使用vuex
vuex 就像是一个大的机房,里面存着共享数据。这个房间咱们可让任何一个组件进来获取数据或者更新数据
安装vuex
npm install vuex --save
在项目的根目录下,咱们通常会新建一个store文件夹,里面添加新建文件:
数据映射 getters.js
getters
和 vue 中的computed
相似 , 都是用来计算 state 而后生成新的数据 ( 状态 ) 的。
以此项目为例子,须要各个组件之间共享一个singer数据
state.js
const state = { singer: {} } export default state
mutation-types.js
export const SET_SINGER = 'SET_SINGER'
使用常量替代 mutation 事件类型在各类 Flux 实现中是很常见的模式。这样可使 linter 之类的工具发挥做用,同时把这些常量放在单独的文件中可让你的代码合做者对整个 app 包含的 mutation 一目了然
mutations.js
import * as types from './mutation-types' // import * as obj from "xxx" 会将 "xxx" 中全部 export 导出的内容组合成一个对象返回。 const mutations = { [types.SET_SINGER](state, singer) { state.singer = singer } } export default mutations
mutations.js 能够理解为是一个修改数据的方法的集合
getter.js
有时候咱们须要从 store 中的 state 中派生出一些状态,若是有多个组件须要用到此属性,咱们要么复制这个函数,或者抽取到一个共享函数而后在多处导入它——不管哪一种方式都不是很理想。
Vuex 容许咱们在 store 中定义“getter”(能够认为是 store 的计算属性)。就像计算属性同样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被从新计算。
export const singer = state => state.singer
index.js
import Vue from 'vue' import Vuex from 'vuex' import * as actions from './actions' import * as getters from './getters' import state from './state' import mutations from './mutations' import createLogger from 'vuex/dist/logger' Vue.use(Vuex) // 注册插件 const debug = process.env.NODE_ENV !== 'production' // 线下调试的时候 debug 为 ture export default new Vuex.Store({ // new一个实例 actions, getters, state, mutations, strict: debug, // 开启严格模式,用于下面来控制是否开启插件 plugins: debug ? [createLogger()] : [] // 开启插件 })
main.js
在vue的main.js 中 注册 vuex
import store from './store' .... new Vue({ el: '#app', render: h => h(App), router, store })
以上,vuex的初始化就完成了
singer.vue 写入 state
在组件中提交 Mutation
你能够在组件中使用 this.$store.commit('xxx')
提交 mutation,或者使用 mapMutations
辅助函数将组件中的 methods 映射为 store.commit
调用(须要在根节点注入 store
)。
import {mapMutations} from 'vuex'
在methods结尾添加
...mapMutations({ setSinger: 'SET_SINGER' // 将 `this.setSinger()` 映射为 `this.$store.commit('SET_SINGER')` })
经过this.setSinger(singer)
实现了对Mutations的提交
singer-detail.vue 取出state数据
引入
import {mapGetters} from 'vuex'
在computed中
computed: { ...... ...mapGetters([ 'singer' // 把 `this.signer` 映射为 `this.$store.getters.singer` ]) }
至此,singer-detail 和 singer 之间就实现 singer 的共享了