Vuex
组件之间传值
目标:熟悉组件之间传值的各种情况(关注非父子之间传值)
- 父组件向子组件传值 props
 - 子组件向父组件传值 $emit
 - 非父子组件之间传值 : 爷孙;兄弟
- 发布订阅模式(微博:大V——粉丝)
 
 

- 基于Vue实现发布订阅模式
 
// 相当于中介const eventBus = new Vue()// 订阅事件eventBus.$on('event-b', (param) => {console.log(param) // 123})// 发布事件eventBus.$emit('event-b', 123)
- 通过兄弟组件之间的传值进行验证
 
总结:
- 少量的组件之间数据传递可以用这种模式
 - 但是不建议大量的组件通信采用这种机制(代码比较乱,后期维护比较麻烦)
 
状态管理必要性分析
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 如果使用了Vuex,就可以非常方便的进行复杂的组件之间数据传递(非父子关系)

总结:
- 所有组件的数据进行统一管理(存储和变更),每个组件内部就不再需要维护这些数据了
 - 数据变更时,统一修改Store中数据即可,组件中用到这个数据的组件会自动更新(数据是响应式的)
 
Vuex介绍
目标:熟悉Vuex是如何实现上述集中管理组件数据这种思想(模式)的

- state 管理组件数据,管理的数据是响应式的,当数据改变时驱动视图更新。
 - mutations 更新数据,state中的数据只能使用mutations去改变数据(只能处理同步的场景)
 - actions 处理异步场景,处理成功后把数据提交给mutations,进而更新state
 - Devtools指的是浏览器的Vue插件调试工具,它可以监控到数据的所有变更操作。
 

- getters相当于在State和组件之间添加一个环节(对state中的数据进行加工处理后再提供给组件)
 - getters不要修改state中的数据
 
初始化项目
目标:基于脚手架初始化项目
- 第一步:
npm i vuex --save/-S - 第二步: 创建store.js  
import vuex from 'vuex'import vue from 'vue' - 第三步:
Vue.use(vuex) - 第四步:
const store = new Vuex.Store({...配置项}) - 第五步:导出 
export default store - 第六步:导入main.js 在根实例配置 store 选项指向 store 实例对象
 
// 初始化一个vuex的实例(数据仓库) 导出即可import Vuex from 'vuex'import Vue from 'vue'// 使用安装Vue.use(Vuex)// 初始化const store = new Vuex.Store({// 配置(state|mutations|actions)})export default store
import store from '@/store'new Vue({// 把store对象挂载到vue实例对象中,这样就可以在所有的组件中获取store中的数据了store,render: h => h(App),}).$mount('#app')
总结:
- 初始化store对象
 - 把store对象挂载到Vue实例中
 
状态state
初始化状态
状态state用于存储所有组件的数据。
- 管理数据
 
// 初始化vuex对象const store = new vuex.Store({state: {// 管理数据count: 0}})
- 在组件获取state的数据:原始用法插值表达式
 
<div>A组件 state的数据:{{$store.state.count}}</div><div>A组件 state的数据:{{count}}</div>
- 使用计算属性:
 
// 把state中数据,定义在组件内的计算属性中computed: {// 1. 最完整的写法// count: function () {// return this.$store.state.count// },// 2. 缩写count () {return this.$store.state.count}}// 不能使用剪头函数 this指向的不是vue实例
总结:
- state中的数据是自定义的,但是state属性名是固定的
 - 获取数据可以通过 $store.state
 - 可以使用计算属性优化模板中获取数据的方式
 - 计算属性不可以使用箭头函数(箭头函数本身是没有this的,实际上用的是父级函数中的this)
 
mapState
目标:简化获取store数据的代码
- 把vuex中的state数据映射到组件的计算属性中。
 
import { mapState } from 'vuex'
- 使用:mapState(对象)
 
// 使用mapState来生成计算属性 mapState函数返回值是对象// 使用mapState使用对象传参// computed: mapState({// // 1. 基础写法 (state) 代表就是vuex申明的state// // count: function(state) {// // return state.count// // }// // 2. 使用箭头函数// // count: state => state.count// // 3. vuex提供写法 (count是state中的字段名称)// count: 'count',// // 4. 当你的计算属性 需要依赖vuex中的数据 同时 依赖组件中data的数据// count (state) {// return state.count + this.num// }// })
- 使用:mapState(数组)
 
// 2、mapState参数是一个数组// computed: mapState(['count', 'total'])
- 如果组件自己有计算属性,state的字段映射成计算属性
 
// 3、即在内部保留原有的计算属性,又要把store中的数据映射为计算属性computed: {// 组件自己的计算属性calcNum () {return this.num + 1},// 把mapState返回值那个对象进行展开操作(把对象的属性添加到该位置)...mapState(['count'])}
总结: 1、是否组件的所有数据都应该放到Store中?不一定(数据仅仅需要在本组件使用,那么没有必要放到Store),放到Store中的数据一般需要多个组件共享。 2、mapState
- 基本使用
 - 简化用法
 - 自定义和映射计算属性结合。
 
状态修改mutations
状态修改基本操作
目标:Vuex规定必须通过mutation修改数据,不可以直接通过store修改状态数据。 为什么要用mutation方式修改数据?Vuex的规定 为什么要有这样的规定?统一管理数据,便于监控数据变化
- 定义状态修改函数
 
// mutations是固定的,用于定义修改数据的动作(函数)mutations: {// 定义一个mutation,用于累加count值// increment这个名字是自定义的increment (state, payload) {// state表示Store中所有数据// payload表示组件中传递过来的数据state.count = state.count + payload},decrement (state, payload) {state.count = state.count - payload}}
- 组件中调用
 
methods: {handleClick1 () {// 通过触发mutation修改state中的count的值this.$store.commit('increment', 2)},handleClick2 () {this.$store.commit('decrement', 1)}},
总结:
- 先定义(mutations),再出发 this.$store.commit(‘mutation的名称,参数’)
 - mutation的本质就是方法,方法名称自定义,mutation函数内部负责处理的变更操作。
 - 一种操作就是一个mutation,不同的mutation处理不同的场景。
 
mapMutations
- 把vuex中的mutations的函数映射到组件的methods中
 - 通俗:通过mapMutations函数可以生成methods中函数
 
methods: {// 1、对象参数的写法// ...mapMutations({// // 冒号右侧的increment是mutation的名称// // 冒号左侧的increment是事件函数的名称,可以自定义// increment: 'increment'// })// 2、数组参数的写法(事件函数名称和mutation名称一致)...mapMutations(['increment'])// 3、这种写法和第2种等效// increment (param) {// // 点击触发该函数后要再次触发mutation的// this.$store.commit('increment', param)// }}
总结:
- mapMutations函数的作用:简化methods的定义
 - 原始方式:通过$store.commit方法触发mutation
 - 简写方式一:对象写法
 - 简写方式二:数组写法
 
异步操作action
异步获取数据
目标:主要用于处理异步的任务
- 定义获取数据方法
 
// actions是固定的,用于定义异步操作的动作(函数)actions: {// 定义了一个action,用于查询接口数据async queryData (context, payload) {console.log(payload)// 调用接口获取数据const ret = await axios.get('http://test.zjie.wang/tab')// 必须触发mutation修改list的值// context类似于this.$storecontext.commit('updateList', ret.data.list)}},mutations: {updateList (state, payload) {state.list = payload}}
- 组件使用:
 
methods: {handleQuery () {// 触发action(必须调用dispatch方法)this.$store.dispatch('queryData', 111)}}
总结:
- action的作用:处理异步任务,获取异步结果后,把数据交给mutation更新数据
 - 触发action需要使用 this.$store.dispatch
 
mapActions
- mapActions辅助函数,把actions中的函数映射组件methods中
 - 通俗:通过mapActions函数可以生成methods中函数
 
// 相当于 methods申明了一个函数fn(num){ this.$store.dispatch('queryData', num)}// ...mapActions({// fn: 'queryData'// })// 相当于 methods申明了一个函数getData(num){ this.$store.dispatch('getData', num)}...mapActions(['queryData'])
总结:
- 原始方式:this.$store.dispatch(‘queryData’, num)
 - 简化方式一:对象
 - 简化方式二:数组
 
getters用法
目标:熟悉getters的应用场景和具体使用步骤
- 先定义getters
 
// 相当于state的计算属性(基于State处理成另外一份数据)// getters的主要应用场景:模板中需要的数据和State中的数据不完全一样// 需要基于state中的数据进行加工处理,形成一份新的的数据,给模板使用getters: {getPartList (state) {return state.list.filter(item => {return item.id > 1})}}
- 再使用getters
 
- 基本使用
 
caleList () {// 注意:获取getters的值,不需要加括号(当属性使用)return this.$store.getters.getPartList},
- 简化用法
 
import { mapGetters } from 'vuex'// mapGetters的作用:把getters映射为计算属性computed: {...mapGetters(['getPartList']),// ...mapGetters({// calcList: 'getPartList'// }),// calcList () {// // 注意:获取getters的值,不需要加括号(当属性使用)// return this.$store.getters.getPartList// },}

总结:
- getters相当于在State和组件之间添加一个环节(对state中的数据进行加工处理后再提供给组件)
 - getters不要修改state中的数据
 - getters类似之前的计算属性(基于state中的数据进行计算)
 
总结
- 基于发布订阅模式处理非父子组件之间的数据传递
- const eventBus = new Vue()
 - 订阅 eventBus.$on(‘事件名称’,事件处理函数)
 - 发布 eventBus.$emit(‘事件名称’, 传递的数据)
 
 - vuex
- vuex的核心思想:组件数据统一管理(统一存储,统一修改)
 - vuex具体实现的核心概念
- state 用来保存组件的数据
- 基本使用
 - 简化用法 mapState
 
 - mutations 用来修改state中的数据(数据是响应式的)
- 基本使用
 - 简化用法 mapMutations
 
 - actions 用来处理异步任务,获取异步的结果,但是不可以修改数据
- 基本使用
 - 简化用法 mapActions
 
 - getters 用来处理state中的数据,方便给组件提供符合需求的数据格式
- 基本使用
 - 简化用法 mapGetters
 
 
 - state 用来保存组件的数据
 
 
Vuex案例实战
豆瓣接口介绍
豆瓣接口地址支持jsonp但是不支持cors。
- http://api.douban.com/v2/movie/subject/:id 详情
 - http://api.douban.com/v2/movie/in_theaters 正在热映
 - http://api.douban.com/v2/movie/coming_soon 即将上映
 - http://api.douban.com/v2/movie/top250 top250
 
注意:
- 豆瓣的接口请求限制,每个外网IP有请求次数限制。
 - 豆瓣的图片访问显示,非豆瓣域名下发起的图片请求不给予响应。
 - 近期:官方停用搜索相关接口,必须要注册豆瓣api平台获取认证apikey才行。
 - 网友提供 
apikey=0df993c66c0c636e29ecbb5344252a4a 
总结:
- 实现案例的基本路由配置
 - 基于vuex管理相关的数据
 - 电影列表功能
 - 电影详情功能
 
初始化项目
目标:基于脚手架初始化项目
- main.js
 
import Vue from 'vue'import App from './App.vue'import store from './store'import router from './router'Vue.config.productionTip = falsenew Vue({render: h => h(App),store,router}).$mount('#app')
- store.js
 
// 管理数据import Vuex from 'vuex'import Vue from 'vue'Vue.use(Vuex)const store = new Vuex.Store({state: {list: []}})export default store
- router.js
 
// 路由相关功能import Vue from 'vue'import VueRouter from 'vue-router'import Home from '../views/movie/index.vue'Vue.use(VueRouter)const router = new VueRouter({// TODO 路由规则routes: [{path: '/',name: 'Home',component: Home}]})export default router
总结:
- 实现项目的基本结构:路由和vuex的配置
 - 实现主页的基本布局(导入默认提供的样式和字体图标)
 
配置路由及组件
- 封装组件
- 头部组件 NavBar.vue
 - 底部组件 BottomMenu.vue
 - 路由组件
- 页面根组件 index.vue
 - 正在热映 HotMovie.vue
 - 即将上映 WillMovie.vue
 - top250 TopMovie.vue
 - 电影详情 DetailMovie.vue
 
 
 - 路由规则
- /
 - /hot
 - /movie
 - /top
 - /detail
// 配置路由映射const router = new VueRouter({routes: [{path: '/',name: 'Home',component: Home,redirect: '/hot',children: [{ path: 'hot', component: HotMovie },{ path: 'will', component: WillMovie },{ path: 'top', component: TopMovie },{ path: 'detail', component: DetailMovie }]}]})
<template><div id="app"><!-- 顶部导航栏 --><nav-bar></nav-bar><!-- 中间路由填充位 --><div class="wrapper"><router-view></router-view></div><!-- 底部菜单 --><bottom-menu></bottom-menu></div></template>
 
 
总结:根据功能模块,把页面进行组件化拆分并且配置整体的路由效果
电影列表功能
第一步:申明数据,根据页面需要的数据进行申明。
state: {mlist: []},
第二步:定义修改数据的方法
// mutationsmutations: {// 初始化热映列表initMlist (state, payload) {state.mlist = payload}},
第三步:获取数据的方法
// actionsactions: {// 获取电影热映列表async movieList (context) {// 调用接口获取热映列表数据const ret = await axios.get('http://test.zjie.wang/api/hot')// 把异步获取的数据交给mutation,从而修改state中的数据context.commit('initMlist', ret.data.hot)}}
第四步:调用获取数据的方法
methods: {...mapActions(['movieList'])},created () {// 触发加载热映数据的action// this.$store.dispatch('movieList')this.movieList()}
第五步:获取vuex的数据
computed: {// ...mapState(['mlist']),...mapState({list: 'mlist'})// list () {// // 获取state中的热映数据// return this.$store.state.mlist// }},
第六步:渲染页面
<ul class="list"><li v-for='item in list' :key='item.id'><a href="./item.html"><img :src="item.img"><div class="info"><h3>{{item.title}}</h3><p>豆瓣评分:8</p><p><span class="tag" :key='index' v-for='(tag, index) in item.tags.split(",")'>{{tag}}</span></p></div></a></li></ul>
总结:
- 封装action,调用接口获取数据
 - 封装mutation,用于修改列表数据
 - 导入状态数据(原始、映射写法)
 - 导入action方法(原始、映射写法)
 - 触发action (原始的方式触发dispatch方法)
 - 模板填充
 
电影详情功能
- 电影列表  电影的详情地址   都不一样   都会来到电影详情组件
- 使用动态路由功能 /detail/:id ; /detail?id=123
 - 电影详情组件获取id获取详情数据 $route.params.id ; $route.query.id
 
 
第一步:路由规则
{ path: '/detail', component: DetailMovie }
<router-link :to='{path: "/detail", query: {id: item.id}}'>
第二步:准备数据
state: {mlist: [],movieInfo: null},
第三步:修改数据函数
mutations: {// 更新电影详情数据updateMovie (state, payload) {state.movieInfo = payload}},
第四步:获取数据去修改数据的函数
actions: {// 获取电影详情数据async movieDetail (context, id) {// 调用接口获取详情数据const ret = await axios.get('http://test.zjie.wang/api/hot/' + id)// 触发mutation更新详情数据context.commit('updateMovie', ret.data)}}
第五步:在组件使用数据
第六步:在组件初始化获取数据
import { mapState, mapActions } from 'vuex'export default {name: 'DetailMovie',computed: {// 这里获取的默认值是null// 接口调用成功后会给他进行初始化...mapState(['movieInfo'])},methods: {...mapActions(['movieDetail'])},created () {// 调用接口(根据电影的id获取详情数据)this.movieDetail(this.$route.query.id)}}
第七步:渲染页面
<div class="item" v-if='movieInfo'><img :src="movieInfo.img" alt=""><div><p>豆瓣评分:{{movieInfo.score}}</p><p>产地:{{movieInfo.country}}</p><p><span class="tag" :key='index' v-for='(item,index) in movieInfo.tags.split(",")'>{{item}}</span></p><p>{{movieInfo.summary}}</p></div></div>
总结:
- 熟悉路由的参数传递用法:动态路由;查询字符串传参
 - 封装action
 - 封装mutation
 - 导入状态
 - 映射action方法
 - 触发action方法的调用
 - 渲染模板(需要判断存在性)
 
关于路由的参数传递
目标:熟悉采用props方式向路由组件传递参数的方式
- 路由的链接写法
 
<router-link :to='"detail/" + item.id + "?title=hot"' >
- 路由的映射写法
 
{path: 'detail/:id',component: Detail,// 箭头后面的小括号不可以省略(默认会return这个对象)// <router-link :to='"/detail/" + item.id + "?title=hot"' >// to = /detail/99?title=hot// 此时路由向组件注入了几个属性?2个(title和id)props: (route) => ({ title: route.query.title, id: route.params.id })}
- 组件中获取路由参数
 
export default {name: 'Detail',// 通过props获取路由参数props: ['id', 'title'],
总结:路由映射中,props取值集中情况? 1、布尔值,route.params的参数会变成组件的props 2、对象,对象中的属性直接变成组件的props 3、函数,函数返回值是一个对象,对象中的属性会变成组件的props
Store模块化拆分
Store中代码越来越多,不方便后续的维护和扩展
拆分Store模块
- 入口文件 store/index.js
 
import Vue from 'vue'import Vuex from 'vuex'import listModule from '@/store/module-list.js'import detailModule from '@/store/module-detail.js'Vue.use(Vuex)const store = new Vuex.Store({modules: {list: listModule,detail: detailModule}})export default store
- 模块内部代码 store/module-list.js
 
export default {state: () => ({}),mutations: { },actions: { },getters: { }}
总结
- 不同的功能可以拆分为不同的js模块
 - 在入口文件中可以导入所有的模块并进行统一配置
 - 拆分模块后,后续的维护会变得方便。
 
模块成员的访问规则
- namespaced属性的作用:把模块变成隔离的区域,那么访问模块成员时,需要添加前缀(模块名称)
 - 如果不加这个属性,那么模块中定义的成员都是全局的,如果重名就会冲突
 - 全局和局部模块的访问规则的区别
 
- 全局模式下(不加namespaced)
 
// 在不添加命名空间的前提下(模块中不添加namespaced),安装如下的方式获取模块的状态数据...mapState({msg: state => state.detail.msg})
- 局部模块下(添加namespaced)
 
// 如果模块添加的namespaced,那么可以按照如下方式简化// 如下的参数一表示模块名称,参数二表示state中属性名称...mapState('detail', ['msg'])
访问模块成员的方法
- 原始写法(state之后多了一个模块名称)
 
msg () {return this.$store.state.detail.msg}
- 简化为对象写法(通过形参进行优化,不再需要this.$store)
 
...mapState({msg: state => state.detail.msg})
- 简化为数组写法
 
...mapState('detail', ['msg', 'info'])
- 通过辅助函数进行简化
 
import { createNamespacedHelpers } from 'vuex'// 参数是模块的名称const { mapState, mapActions } = createNamespacedHelpers('detail')computed: {...mapState(['msg', 'info'])}methods: {// ...mapActions('detail', ['getInfo'])...mapActions(['getInfo'])},
组件同时访问多个模块的成员
- 映射多次实现多模块导入
 
methods: {...mapActions('list', {getInfo1: 'getInfo'}),...mapActions('detail', {getInfo2: 'getInfo'})}created () {this.getInfo1()this.getInfo2()}
- 通过数组方式映射一次
 
methods: {...mapActions(['list/getInfo','detail/getInfo'])}
created () {this['list/getInfo']()this['detail/getInfo']()}
总结
- 熟悉整体案例需求:电影列表;电影详情
 - 核心知识模块:vue-router;vuex
 - 项目路由配置:一级路由和二级路由
 - 基于vuex实现功能:电影列表和电影详情
- 创建action
 - 创建mutation
 - 组件中导入action
 - 组件中触发action
 - 组件中使用state中的数据
 
 - 状态数据的模块化管理
- 好处:方便后期的维护和扩展
 - 基本的拆分流程
- 通过modules属性配置模块
 - 每一个模块内部包含独立的状态信息:state/mutations/actions/getters
 
 - 组件中如何使用模块的成员
- 全局模块 (不包含namespaced属性):不同模块的相同成员名称会冲突
 - 局部模块(包含namespaced属性):不同模块的相同成员名称不会冲突,但是访问时需要添加前缀(模块名称)
 
 - 访问模块成员方法的简化写法
- 原始写法
 - 简化为对象方式
 - 简化为数组方式
 - 利用辅助函数
 
 - 同一组件访问多个模块的用法
- 多次导入(推荐)
 - 单词导入
 
 - 基于模块化方式重构案例功能:电影列表和电影详情
 
 
