# vuex-car-demo **Repository Path**: cloveryuan/cartdemo ## Basic Information - **Project Name**: vuex-car-demo - **Description**: 初识vuexdemo - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-08-14 - **Last Updated**: 2020-12-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 先启动后端服务 node server ### 再启动个面板,启动前端服务 npm install npm run serve 项目就跑起来了,点击悬浮框去购物车,会跳转到car模块,购物车商品通过插件plugins做了缓存 ## Vuex状态管理 ### 一、组件内的状态管理流程 #### 1. 状态管理 + state:驱动应用的数据源 + view:以声明方式将state映射到视图 + actions:相应在view上的用户输入导致的状态变化 01.jpeg ### 二、组件间通信方式 #### 1. 父组件给子组件传值 + 子组件通过props接受数据 ```vue ``` + 父组件中给子组件通过响应属性传值 ```vue ``` #### 2. 子组件给父组件传值 + 通过自定义事件子组件给父组件传值 ```vue ``` + 父组件中注册子组件内部触发的事件 ```vue ``` #### 3. 不相关组件之间传值 + 通过事件中心eventbus触发和注册事件 ```js import Vue from 'vue' export default new Vue() ``` + 触发eventbus中的事件 ```vue ``` + 注册事件 ```vue ``` #### 4. 其他常见方式(不推荐使用) + $root + $parent + $children + $ref ref两个作用: 1. 在普通HTML标签上使用ref,获取到的是DOM 2. 在组件标签上使用ref,获取到的是组件实例 子组件中定义ref ```vue ``` 父组件获取子组件的ref ```vue ``` > ref这种方式不到万不得已不要使用,会导致数据的混乱。 ### 三、简易的状态管理方案 #### 1. 父子组件传值的问题 + 多个视图依赖同一状态 + 来自不同视图的行为需要变更同一状态 #### 2. 集中式的状态管理 store.js ```js export default { debug: true, state: { user: { name: 'xiaomao', age: 18, sex: '男' } }, setUserNameAction (name) { if (this.debug) { console.log('setUserNameAction triggered: ', name) } this.state.user.name = name } } ``` componentA.vue ```vue ``` componentB.vue ```vue ``` ### 四、Vuex回顾 #### 1. 什么是Vuex + Vuex是专门为Vue.js设计的状态管理库 + Vuex采用集中式的方式存储需要共享的状态 + Vuex的作用是进行状态管理,解决复杂组件通信,数据共享 + Vuex集成到了devtools中,提供了time-travel时光旅行历史回滚功能 #### 2. 什么情况下使用Vuex + 非必要的情况不要使用Vuex + 大型的单页应用程序 + 多个视图依赖于同一状态 + 来自不同视图的行为需要变更同一状态 #### 3. Vuex核心概念 + Store: 是一个容器,包含着应用中的大部分状态,不能直接改变store中的状态,要通过mutation的方式改变状态。 + State:是状态,保存在Store中,因为Store是唯一的,所以State也是唯一的,也称为单一状态树。这里的状态是响应式的。 + Getter:是Vuex中的计算属性,方便从一个属性派生出其他的值。它内部会对计算的属性进行缓存,只有当依赖改变的时候,才会重新进行计算。 + Mutation:状态的变换必须要通过提交Mutation来完成。 + Action:和MuTation类似,不同的是Action可以进行异步的操作,内部改变状态的时候,都需要提交Mutation。 + Module:当Store太过臃肿时,可以将Store分成多个模块,每个模块里有State、Mutation、Action、Getter,甚至是子模块。 ### 五、Vuex使用 #### 1. Vuex基本结构 定义store:store/index.js ```js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules: {} }) ``` 注入store: ```js import store from './store' new Vue({ router, store, render: h => h(App) }).$mount('#app') ``` #### 2. State 02.jpeg store/index.js ```js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, mutations: { }, actions: { }, modules: { } }) ``` App.vue ```vue ``` ​ `$store.state.xxx`和`mapState`展开两种写法都可以。不过使用`mapState`展开成计算属性时,如果原本就有这个属性名,那么`mapState`展开的属性就不起作用,可以通过给属性重命名的方式更改计算属性的名称: ```vue

count: {{ num }}

msg: {{ message }}

``` ```js ...mapState({ num: 'count', message: 'msg' }) ``` #### 3. Getter Vuex中的getter相当于VUE中的计算属性。 store/index.js ```js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, getters: { reverseMsg (state) { return state.msg.split('').reverse().join('') } }, mutations: { }, actions: { }, modules: { } }) ``` App.vue ```vue ``` 使用mapGetters将VUE中的getters导入到Vue的计算属性中,用法和mapState类似。 #### 4. Mutation Mutation中修改state,只能支持同步操作。 store/index.js ```js mutations: { increate (state, payload) { state.count += payload } }, ``` 在模板中通过`$store.emit()`来提交mutation。 App.vue ```vue ``` 而mutation的本质是方法,所以可以通过mapMutations将mutation中的方法展开到Vue的methods中 App.vue ```vue ``` 打开Vue调试工具,可以看到vuex中的mutation变化,每个mutation上面的三个按钮,分别是提交本次mutation、恢复到本次的mutation、时光旅行。 03.png #### 5. Action Action中可以进行异步操作,不过如果需要修改state,得提交Mutation。 Store/index.js ```js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, getters: { reverseMsg (state) { return state.msg.split('').reverse().join('') } }, mutations: { increate (state, payload) { state.count += payload } }, actions: { increateAsync (context, payload) { setTimeout(() => { context.commit('increate', payload) }, 2000) } }, modules: { } }) ``` App.vue mapActions用法同mapMutations ```vue ``` #### 6. Module Module可以让单一状态树拆分成多个模块,每个模块可以拥有state、mutation、action、getter,甚至嵌套子模块。 在使用模块里的数据时,可以通过`$store.模块名.state状态属性名`的方式访问 在使用模块里的方法时,可以通过`$store.commit('mutation方法名')`的方式提交mutation 当想要有更强的封装性时,可以开启命名空间,在导出的模块对象里增加一个namespaced属性为true,然后就可以在Vue中使用 `mapState('模块名', ['state状态属性名'])`的方式获取到属性名称,使用`mapMutations('模块名', ['mutation方法名'])`的方式获取到方法名。 ./store/modules/products.js ```js const state = { products: [ { id: 1, title: 'iPhone 11', price: 8000 }, { id: 2, title: 'iPhone 12', price: 10000 } ] } const getters = {} const mutations = { setProducts (state, payload) { state.products = payload } } const actions = {} export default { namespaced: true, state, getters, mutations, actions } ``` ./store/modules/cart.js ```js const state = {} const getters = {} const mutations = {} const actions = {} export default { namespaced: true, state, getters, mutations, actions } ``` ./store/index.js ```js import Vue from 'vue' import Vuex from 'vuex' import products from './modules/products' import cart from './modules/cart' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, getters: { reverseMsg (state) { return state.msg.split('').reverse().join('') } }, mutations: { increate (state, payload) { state.count += payload } }, actions: { increateAsync (context, payload) { setTimeout(() => { context.commit('increate', payload) }, 2000) } }, modules: { products, cart } }) ``` App.vue ```vue ``` ### 六、严格模式 Vuex中的状态的更新要通过提交mutation来修改,但其实在组件中还可以通过`$store.state.msg`进行修改,从语法从面来说这是没有问题的,但是这破坏了Vuex的约定,如果在组件中直接修改state,devtools无法跟踪到这次状态的修改。 开启严格模式之后,如果在组件中直接修改state会抛出错误,但数据仍被成功修改。 如何开启:在store中增加一个属性strict为true store/index.js ```js export default new Vuex.Store({ strict: process.env.NODE_ENV !== 'production', state: { count: 0, msg: 'Hello Vuex' }, // ... }) ``` App.vue ```vue

strict

``` 04.png > 注意:不要在生产模式下开启严格模式,严格模式会深度检查状态树,检查不合规的状态改变,会影响性能。 > > 我们可以在开发模式下开启严格模式,在生产模式中关闭严格模式: > > ` strict: process.env.NODE_ENV !== 'production',` ### 七、购物车案例 #### 1. 模板 地址:https://github.com/goddlts/vuex-cart-demo-template.git 用到了ElementUI、Vuex、Vue-Router 项目根目录下的server.js文件是一个node服务,为了模拟项目接口。 页面组件和路由已经完成了,我们需要使用Vuex完成数据的交互。 三个组件: + 商品列表组件 + 购物车列表组件 + 我的购物车组件(弹出窗口) #### 2. 商品列表组件 + 展示商品列表 + 添加购物车 #### 3. 我的购物车组件 + 购买商品列表 + 统计购物车中的商品数量和价格 + 购物车上的商品数量 + 删除按钮 #### 4. 购物车组件 + 展示购物车列表 + 全选功能 + 增减商品功能和统计当前商品的小计 + 删除商品 + 统计选中商品和价格 #### 5. Vuex插件介绍 + Vuex的插件就是一个函数 + 这个函数接受一个store参数 这个参数可以订阅一个函数,让这个函数在所有的mutation结束之后执行。 ```js const myPlugin = store => { // 当store初始化后调用 store.subscribe((mutation, state) => { // 每次mutation之后调用 // mutation的格式为{ type, payload } }) } ``` Store/index.js ```js import Vue from 'vue' import Vuex from 'vuex' import products from './modules/products' import cart from './modules/cart' Vue.use(Vuex) const myPlugin = store => { store.subscribe((mutation, state) => { if (mutation.type.startsWith('cart/')) { window.localStorage.setItem('cart-products', JSON.stringify(state.cart.cartProducts)) } }) } export default new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { products, cart }, plugins: [myPlugin] }) ``` ### 八、模拟实现Vuex Myvuex/index.js ```js let _Vue = null class Store { constructor (options) { const { state = {}, getters = {}, mutations = {}, actions = {} } = options this.state = _Vue.observable(state) this.getters = Object.create(null) Object.keys(getters).forEach(key => { Object.defineProperty(this.getters, key, { get: () => getters[key](state) }) }) this._mutaions = mutations this._actions = actions } commit (type, payload) { this._mutaions[type](this.state, payload) } dispatch (type, payload) { this._actions[type](this, payload) } } function install (Vue) { _Vue = Vue _Vue.mixin({ beforeCreate () { if (this.$options.store) { _Vue.prototype.$store = this.$options.store } } }) } export default { Store, install } ``` 将store/index.js中的vuex的导入替换成myvuex ```js import Vuex from '../myvuex' ``` ----