Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vuex数据流管理 #44

Open
janeLLLL opened this issue Dec 22, 2020 · 0 comments
Open

vuex数据流管理 #44

janeLLLL opened this issue Dec 22, 2020 · 0 comments
Labels

Comments

@janeLLLL
Copy link
Owner

该笔记参考拉勾教育前端课程:https://kaiwu.lagou.com/

vuex

  • Vue组件间通信方式回顾

  • Vuex核心概念和基本使用回顾

  • 购物车案例

  • 模拟实现Vuex

组件内的状态管理流程

Vue核心功能:数据驱动和组件化

  • state:驱动应用的数据源
  • view:以声明方式将state映射到视图
  • actions:响应在view上的用户输入导致的状态变化

Vue组件间通信方式回顾

父组件给子组件传值

  • 子组件中通过props接收数据
  • 父组件中给子组件通过相应属性传值
<!--父:注册子组件-->
<child title="My journey with Vue"></child>
//子
  props: {
    title: String
  }

子组件给父组件传值

  1. 父:注册事件
<child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
<child :fontSize="hFontSize" v-on:enlargeText="hFontSize += $event"></child>
enlargeText (size) {
	this.hFontSize += size
}
  1. 子:通过$emit传递参数
<button @click="handler">文字增大</button>
handler () {
      //this:当前子组件对象
      this.$emit('enlargeText', 0.1)
    }

不相关组件之间传值

使用Event Bus作为事件中心

  1. eventbus.js
//调用它的$emit和$on
import Vue from 'vue'
export default new Vue()
  1. 组件一:调用$emit发布
bus.$emit('numchange', this.value)
  1. 组件二:调用$on订阅
bus.$on('numchange', (value) => {
      this.msg = `您选择了${value}件商品`
    })

通过ref获取子组件

  • $root
  • $parent
  • $children
  • $refs
    • 在普通HTML标签上使用ref,获取到的是DOM
    • 在组件标签上使用ref,获取到的是组件实例
<template>
	<input ref="input">
</template>
<script>
	export default {
        methods: {
        // 用来从父级组件聚焦输入框
        focus: function () {
        	this.$refs.input.focus()
    		}
    	}
    }
</script>

在使用子组件的时候,添加 ref 属性:

<base-input ref="usernameInput"></base-input>

然后在父组件等渲染完毕后使用$refs访问:

mounted(){
    this.$refs.usernameInput.focus()
}

简易的状态管理方案

  • 如果多个组件之间要共享状态(数据),多个组件之
    间互相传值很难跟踪数据的变化,如果出现问题很难定位问题
  • 多个视图依赖于同一状态
  • 来自不同视图的行为需要变更同一状态
  • 把组件的共享状态抽取出来,以一个全局单例模式管理
  1. 创建一个共享的仓库 store 对象
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
}
}
  1. 把共享的仓库 store 对象,存储到需要共享状态的组件的 data 中
import store from './store'
export default {
methods: {
	// 点击按钮的时候通过 action 修改状态
    change () {
    store.setUserNameAction('componentB')
    }
},
data () {
    return {
        privateState: {},
        sharedState: store.state
    }
	}
}

接着我们继续延伸约定,组件不允许直接变更属于 store 对象的 state,而应执行 action 来分发(dispatch) 事件通知 store 去改变,这样最终的样子跟 Vuex 的结构就类似了。这样约定的好处是,我们能够记录所有 store 中发生的 state 变更,同时实现能做到记录变更、保存状态快照、历史回滚/时光旅行的先进的调试工具

Vuex概念

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新

把组件的共享状态抽取出来,以一个全局单例模式管理(不管在树的哪个位置,任何组件都能获取状态或者触发行为)

  • Vuex 是专门为 Vue.js 设计的状态管理库
  • 它采用集中式的方式存储需要共享的数据
  • 从使用角度,它就是一个 JavaScript 库
  • 它的作用是进行状态管理,解决复杂组件通信,数据共享

使用场景

大型单页应用程序:

  • 多个视图依赖于同一状态
  • 来自不同视图的行为需要变更同一状态

eg:购物车

核心概念

  • Store:容器,包含应用中大部分状态,要通过提交Mutation方式改变状态
  • State:状态,响应式,保存在Store中,状态是唯一的,称为单一状态树。后续+模块化。
  • Getter:计算属性,方便从一个属性派生出其它的值,内部可以对计算的结果进行缓存
  • Mutation:状态变化需要过提交Mutation
  • Action:类似Mutation,还可以进行异步操作。内部改变状态时都要提交Mutation
  • Module:模块。使用单一状态树,所有对象会集中在一个对象树上,应用十分复杂时,对象会变得臃肿;可以将Store划分为模块,每个模块有自己的State、Mutation、Action

结构分析

  • 导入 Vuex
import Vuex from 'vuex'
  • 注册 Vuex
//store\index.js
Vue.use(Vuex)
export default new Vuex.State({
    state:{
        
    },
    mutations:{
        
    },
    modules:{
        
    }
})
  • 注入 $store 到 Vue 实例
//app.js
import store from './store'
new Vue({
    store
}).$mount('#app')

State

  • 在组件中绑定状态
  1. store\index.js
//store\index.js
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    msg: 'Hello Vuex'
  },
})
  1. app.vue
<!--app.vue-->
<div>
    <!--count:{{ $store.count }}-->
    msg:{{ message }}
</div>

<script>
    //简化模板内容
	import { mapState } from 'vuex'
    export default{
          computed: {
    // count: state => state.count
              //映射状态属性
            ...mapState({ num: 'count', message: 'msg' }),
		},
    }
</script>
  • Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态
  • 使用 mapState 简化 State 在视图中的使用,mapState 返回计算属性

Getter

  • Getter 就是 store 中的计算属性,使用 mapGetter 简化视图中的使用
  • 意思就是在状态内部使用它去处理属性
  1. store\index.js
////store\index.js
getters: {
    reverseMsg (state) {
      return state.msg.split('').reverse().join('')
    }
  },

join() 方法會將陣列(或一個類陣列(array-like)物件)中所有的元素連接、合併成一個字串,並回傳此字串

  1. app.vue
<!--app.vue-->
<div>
    reverseMsg: {{ reverseMsg }}
</div>

<script>
    //简化模板内容:可以传数组和对象
	import { mapGetters } from 'vuex'
    export default{
          computed: {
              ...mapGetters(['reverseMsg']),
		},
    }
</script>

Mutaion

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数

使用 Mutation 改变状态的好处是:集中的一个位置对状态修改,不管在什么地方修改,都可以追踪到状态的修改。可以实现高级的 time-travel 调试功能

  1. store\index.js
mutations: {
    increate (state, payload) {
      state.count += payload
    }
  },
  1. app.vue
<!--app.vue-->
<div>
    <!--count的值会加2
	 <button @click="$store.commit('increate', 2)">Mutation</button>
</div>
	-->
    <button @click="increate(3)">Mutation</button>
</div>

<script>
    //简化模板内容
	import { mapMutations } from 'vuex'
    export default{
          methods: {
            ...mapMutations(['increate']),
          }
    }
</script>

Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态
  • Action 可以包含任意异步操作
  1. store\index.js
actions: {
    increateAsync (context, payload) {
      setTimeout(() => {
        context.commit('increate', payload)
      }, 2000)
    }
  },
  1. app.vue

    Action 通过 store.dispatch 方法触发:

<!--app.vue-->
<div>
    <!--count的值会加2
	 <button @click="$store.dispatch('increateAsync', 6)">Mutation</button>
</div>
	-->
	<button @click="increateAsync(6)">Action</button>
</div>

<script>
    //简化模板内容
	import { mapActions } from 'vuex'
    export default{
          methods: {
            ...mapActions(['increateAsync']),
          }
    }
</script>

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。

  1. products.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
}
  1. store\index.js:导入两个模块
import products from './modules/products'
import cart from './modules/cart'

modules: {
    products,
    cart
  }

通过store.state.xxx访问模块成员

  1. app.vue
<!--app.vue-->
<div>
    <!-- products: {{ $store.state.products.products }} <br>
    <button @click="$store.commit('setProducts', [])">Mutation</button> -->
    products: {{ products }} <br>
	<button @click="setProducts([])">Mutation</button>
</div>

<script>
    //简化模板内容
	import { mapMutations } from 'vuex'
    export default{
          methods: {
              //1.命名空间 2.映射属性
            ...mapMutations('products', ['setProducts'])
          }
    }
</script>

严格模式

const store = new Vuex.Store({
  // ...
  strict: true
})

在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到

e.g:浏览器会报错,但数据还是会被修改

<button @click="$store.state.msg = 'Lagou'">strict</button>

  • 不要在生产环境下开启严格模式,它会:

严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失

状态树:用一个对象就包含了全部的应用层级状态

  • 构建工具来处理,webpack打包时通过环境变量去处理
const store = new Vuex.Store({
  // ...
  strict: process.env.NODE_ENV !== 'production'
})

购物车案例

功能列表

  • 商品列表组件
  • 商品列表中弹出框组件
  • 购物车列表组件

商品列表

  • Vuex 中创建两个模块,分别用来记录商品列表购物车的状态,store 的结构:

商品列表功能-Products.js

  • products 模块,store/modules/products.js

  • store/index.js 中注册 products.js 模块

    export default new Vuex.Store({
      modules: {
        products,
        cart
      },
    })
  • views/products.vue 中实现商品列表的功能

添加购物车-cart.js

  • cart 模块实现添加购物车功能,store/modules/cart.js
  • store/index.js 中注册 cart 模块
  • view/products.vue 中实现添加购物车功能

弹出购物车窗口

  • src\components\pop-cart.vue
  1. 展示购物车列表

    ...mapState('cart', ['cartProducts']),
  2. 删除

    src\store\modules\cart.js

    ...mapMutations('cart', ['deleteFromCart'])
  3. 小计

    src\store\modules\cart.js

    ...mapGetters('cart', ['totalCount', 'totalPrice'])

购物车

购物车列表

……

全选功能

  • cart 模块实现更新商品的选中状态,store/modules/cart.js

Vuex插件介绍

  • Vuex的插件就是一个函数
  • 这个函数接收一个store的参数
const myPlugin = store => {
    //当store初始化后调用
	store.subscribe((mutain, state) => {
        //每次mutation之后调用
        //mutation的格式为{type, payload}
    })
}
const store = new Vuex.Store({
	plugins: [myPlugin]
})

以该项目为例:src\store\index.js

const myPlugin = store => {
    store.subscribe((mutation, state) => {
        if(mutation.type.startWith('cart/')){
            window.localStorage.setItem('cart-products', JSON.stringify(state.cart.cartProducts))
        }
    })
}

简单模拟Vuex

初始化Vuex架构

  • src\myvuex\index.js
let _Vue = null
class Store { }

//vue-router类似
//vue构造函数
function install(Vue) {
    _Vue = Vue
}

export default {
    Store,
    install
}
  • install
//vue-router类似
function install(Vue) {
    _Vue = Vue
    //通过混入beforeCreate获取到vue实例
    _Vue.mixin({
        beforeCreate(){
            if(this.$options.store) {
                _Vue.prototype.$store = this.$options.store
            }
        }
    })
}
  • store
class Store {
    constructor(options) {
        const {
            state = {},
            getter = {},
            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._mutations = mutations
        this._actions = actions
    }
    commit(type, payload) {
        this._mutations[type](this.state, payload)
    }
}
@janeLLLL janeLLLL added the vuex label Dec 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant