大前端- vue - vuex状态管理

一:组件间通信方式

state:驱动应用的数据源。

view:以声明方式将state映射到视图。

actions:响应在view上的用户输入导致的状态变化。

状态管理:
大前端- vue - vuex状态管理

组件间的通信方式:
  1. 父组件给子之间的传值(父子组件)
    1. 子组件中通过props接收数据。2. 父组件中给子组件通过相应属性传值

子组件

<template>
  <div>
    <h1>Props Down Child</h1>
    <h2>{{ title }}</h2>
  </div>
</template>

<script>
export default {
 // props接收父组件传递的参数
  // props: ['title'],
  props: {
    title: String
  }
}
</script>

父组件

<template>
  <div>
    <h1>Props Down Parent</h1>
    // 传递title给子组件
    <child title="My journey with Vue"></child>
  </div>
</template>

<script>
import child from './01-Child'
export default {
  components: {
    child
  }
}
</script>

  1. 子组件给父组件传值
    子组件:
    this.$emit('enlargeText', 0.1)
<template>
  <div>
    <h1 :style="{ fontSize: fontSize + 'em' }">Props Down Child</h1>
    <button @click="handler">文字增大</button>
  </div>
</template>

<script>
export default {
  props: {
    fontSize: Number
  },
  methods: {
    handler () {
     // 在子组件中使用父组件传递过来的事件,并传递值
      this.$emit('enlargeText', 0.1)
    }
  }
}
</script>

父组件:

<template>
  <div>
    <h1 :style="{ fontSize: hFontSize + 'em'}">Event Up Parent</h1>

    这里的文字不需要变化
	// @enlargeText="enlargeText" 传递事件给子组件
    <child :fontSize="hFontSize" @enlargeText="enlargeText"></child>
    <child :fontSize="hFontSize" @enlargeText="enlargeText"></child>
    <child :fontSize="hFontSize" @enlargeText="hFontSize += $event"></child>
  </div>
</template>

<script>
import child from './02-Child'
export default {
  components: {
    child
  },
  data () {
    return {
      hFontSize: 1
    }
  },
  methods: {
    enlargeText (size) {
      this.hFontSize += size
    }
  }
}
</script>

  1. 不相关组件之间的传值
    也是使用事件的方式:bus:事件中心

eventBus.js

import Vue from 'vue'
export default new Vue()

子组件1:

bus.$emit('numchange', this.value) bus.$emit触发事件, numchange:自定义事件名,this.value传递的参数

<template>
  <div>
    <h1>Event Bus Sibling01</h1>
    <div class="number" @click="sub">-</div>
    <input type="text" style="width: 30px; text-align: center" :value="value">
    <div class="number" @click="add">+</div>
  </div>
</template>

<script>
import bus from './eventbus'

export default {
  props: {
    num: Number
  },
  created () {
    this.value = this.num
  },
  data () {
    return {
      value: -1
    }
  },
  methods: {
    sub () {
      if (this.value > 1) {
        this.value--
        //  bus.$emit('numchange', this.value)触发事件
        // numchange:自定义事件名,this.value传递的参数
        bus.$emit('numchange', this.value)
      }
    },
    add () {
      this.value++
      bus.$emit('numchange', this.value)
    }
  }
}
</script>

<style>
.number {
  display: inline-block;
  cursor: pointer;
  width: 20px;
  text-align: center;
}
</style>

子组件2
bus.$on:注册:自定义事件numchange,() => {}:回调函数

<template>
  <div>
    <h1>Event Bus Sibling02</h1>

    <div>{{ msg }}</div>
  </div>
</template>

<script>
import bus from './eventbus'
export default {
  data () {
    return {
      msg: ''
    }
  },
  created () {
  
    bus.$on('numchange', (value) => {
      this.msg = `您选择了${value}件商品`
    })
  }
}
</script>
  1. 组件间通信方式回顾-通过 ref 获取子组件
    ref的2个作用:
    1. 在普通的html标签上使用ref,获取到的是DOM
    2. 在组件标签上使用ref,获取到的是组件实例
    child.vue
<template>
  <div>
    <h1>ref Child</h1>
    <input ref="input" type="text" v-model="value">
  </div>
</template>

<script>
export default {
  data () {
    return {
      value: ''
    }
  },
  methods: {
    focus () {
     // 在普通的html 上使用ref
      this.$refs.input.focus()
    }
  }
}
</script>

parent.vue

<template>
  <div>
    <h1>ref Parent</h1>

    <child ref="c"></child>
  </div>
</template>

<script>
import child from './04-Child'
export default {
  components: {
    child
  },
  mounted () {
  	// 在组件上使用ref,获取到的是组件的实例
    this.$refs.c.focus()
    this.$refs.c.value = 'hello input'
  }
}
</script>
  1. 状态管理方案 - Vuex
    Vuex:集中式的状态管理
    在组件中不能直接更改状态的属性,如果想要更该需要在store中actions中的定义函数修改,这样做的好处是:能记录store中的数据的所有的变更。

解决的问题:多个视图依赖同一状态
来自不同视图的行为需要变更同一状态

store.js

// state: 存储的状态
// setUserNameAction:通过视图和用户交互的时候更改状态用的。
// debug:方便调试,如果为true,在通过action修改数据的时候会打印日志。
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
  2. 使用store.state.user
<template>
  <div>
    <h1>componentA</h1>
    user name: {{ sharedState.user.name }}
    <button @click="change">Change Info</button>
  </div>
</template>

<script>
// 1. 引入store
import store from './store'
export default {
  methods: {
    change () {
    // 使用store中的action更改数据
      store.setUserNameAction('componentA')
    }
  },
  data () {
    return {
      privateState: {},
      // 使用:store.state
      sharedState: store.state
    }
  }
}
</script>

vuex核心概念和基本使用

什么是vuex?
vuex是专门为vuejs设计的状态管理库。
vuex采用集中式的方式存储需要共享的状态。
vuex的作用是进行状态管理,解决复杂组件通信,数据共享。
vuex集成到了devtools中,提供了time-travel时光旅行历史回滚功能。

什么情况下使用vuex?
非必要的情况不需要使用vuex。
大型的单页应用程序:1.多个视图依赖同一状态。2.来自不同视图的行为需要变更同一状态。

vuex的核心概念:

大前端- vue - vuex状态管理
上述图的描述:
state:state中存储数据
vue components:把状态绑定到组件中渲染到用户界面展示给用户。用户可以和视图交互,例如点击按钮(加入购物车):通过dispatch分发actions,此处不直接提交mutations,因为actions中可以做异步操作,(购买的时候需要发送异步请求,异步请求结束之后,再通过提交mutation记录状态的更改,)
mutations: mutations必须是同步的,所有状态的更改都要通过mutations,这样做的目的是:通过mutations可以追踪到所有状态的改变,阅读代码的时候更容易分析应用内部的状态改变,还可以记录每一次状态的改变,实现高级的调试功能。

核心概念:
Store:
state:响应式的状态
Getter: 有点类似于vue中的计算属性,方便从一个属性派生出其他的值。内部可以对计算的结果进行缓存,只有当依赖的状态发生改变的时候才会重新计算。
Mutation:状态的变化必须通过提交mutation来完成。
Actions: action和mutation类似,不同的是action可以进行异步的操作。内部改变状态的时候都需要提交mutation
Module:模块,当数据变得非常大的时候可以采用模块的方式。

如果使用?整体的代码结构
store.js

import Vue from 'vue'
import Vuex from 'vuex'


Vue.use(Vuex)

export default new Vuex.Store({

  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
})

main.js

import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

console.log(store)

使用state:

import Vue from 'vue'
import Vuex from 'vuex'


Vue.use(Vuex)

export default new Vuex.Store({

  state: {
  	count:0,
  	msg: 'hello vuex'
  },
  getters: {
  	// 把msg倒序输出
  	reverseMsg(state) {
		return state.msg.split('').reverse().join()
	}
  },
  mutations: {
  },
  actions: {
  },
})

在组件中使用:
方法1: $store.state.count

<template>
  <div id="app">
     count:{{ $store.state.count }}
    msg: {{ $store.state.msg }}
{{count}}
{{msg}}
   </div>
<template>

方法2:mapState

import { mapState } from 'vuex'
computed: {
	// 方法1:写成数组的方式
	//...mapState(['count', 'msg'])
	// 方法2:函数方式的写法
	count: state => state.count,
	msg: state => state.msg,
	// 方法3:写成对象的形式,可以解决命名冲突的问题
	...mapState({num: 'count', message: 'msg'}),
}

Gutter(在视图中显示数据): 类似于计算属性,想把store中的数据处理一下再显示到页面上。

 getters: {
  	// 把msg倒序输出
  	reverseMsg(state) {
		return state.msg.split('').reverse().join()
	}
  },

在组件中使用:
方式1: 直接在模版中使用:$store.getters.reverseMsg方式2:使用mapGetters`


<div>
	{{$store.getters.reverseMsg}}
	{{reverseMsg}}
</div>
<script>
	import { mapGetters} from 'vuex'
	computed: {
		...mapGetters([reverseMsg])
	}
</script>

Mutation(修改状态):如何修改状态?状态的修改必须经过提交mutation,mutation必须是同步执行,这样可以保证能够在mutation中收集到所有的状态修改。不要在mutation中使用异步方式,如果需要使用异步则使用action
用法:

// 点击【增加】按钮
mutations: {
	increate(state, paylod) {
		state.count + = paylod
	}
}

在组件中使用:
方式1:$store.commit('increate', 2)

<button @click="$store.commit('increate', 2)">增加</button>

方式2: mapMutations

<button @click="increate(3)">增加</button>


import {mapMutations} from 'Vuex'
method:{
	...mapMutations(['increate']),
}

Action: 如果需要使用异步操作,则使用action,当异步结束后如果需要更改状态,需要提mutation来修改state。因为所有的状态更改都是使用mutation。例如:如果需要获取商品数据,则需要在action 中发送请求,异步执行完毕,获取到商品数据之后,需要再提交mutation,把数据记录到state中。

Action使用:

actions: {
	increateAsync(context, payload) {
	// setTimeout是异步任务
      setTimeout(() => {
        context.commit('increate', payload)
      }, 2000)		
	}
}

在组件中使用:
方法1:$store.dispatch('increateAsync', 5)
方法2: mapActions

<button @click="$store.dispatch('increateAsync', 5)">Action</button> 

<button @click="increateAsync(6)">Action</button>

import { mapActions } from 'vuex'
methods: {
    ...mapActions(['increateAsync']),
  }

Module:模块化处理state

目录结构:
大前端- vue - vuex状态管理
index.js

import Vue from 'vue'
import Vuex from 'vuex'
// 模块化处理state
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
  }
})

modules/cart.js

const state = {}
const getters = {}
const mutations = {}
const actions = {}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

modules/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
}

在组件中使用模块的方法和state属性
方法1:$store.state.products.products. 第一个products指的是模块的名字,第2个products指的是state中的属性
方法2:mapMutations

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

方法2:

 products: {{ products }} <br>
<button @click="setProducts([])">Mutation</button>

import {mapMutations} from 'vuex'
computed: {
	// 需要注意:mapState的第一个参数:products指的是模块的名字
	// 第2个参数:['products']指的是state中的属性
    ...mapState('products', ['products'])
  },
methods: {
	// 需要注意:mapMutations的第一个参数:products指的是模块的名字
	// 第2个参数:['setProducts']指的是mutation中的方法名字
    ...mapMutations('products', ['setProducts'])
  }

vuex严格模式:
所有的状态更改必须经过mutation,但是这只是一个口头约定,如果在组件中$store.state.count = 1也是可以修改的的,不会抛出错误,但是这样状态state的修改就变得不可追踪,devtools页没有办法追踪,开启严格模式之后,如果这样修改的话,就会抛出错误。

开启严格模式:strict: process.env.NODE_ENV !== 'production',

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({
 // 开启严格模式
  strict: process.env.NODE_ENV !== 'production',
  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
  }
})

开启之后的报错:
大前端- vue - vuex状态管理
不要在生产模式开启,因为会深度监听影响性能。

购物车案例

购物车功能:
1.用到了node写的接口:node server.js
2.
页面:
大前端- vue - vuex状态管理

大前端- vue - vuex状态管理
大前端- vue - vuex状态管理

当刷新购物车页面之后当,前购物车中的数据是存储在本地的。

项目代码地址:
三个组件:商品列表组件(products.vue),
购物车组件(cart.vue),
鼠标悬浮【我的购物车】展示购物车中的商品组件(pop-cart.vue)

商品列表组件:

模拟实现vuex

myVuex/index.js

let _Vue = null
class Store {
  constructor (options) {
    const {
      state = {},
      getters = {},
      mutations = {},
      actions = {}
    } = options
    // _Vue.observable(state):把数据变成响应式的数据
    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)
  }

  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
}

上一篇:vuex的取值与操作


下一篇:引入vuex的store状态后报警告