一:组件间通信方式
state:驱动应用的数据源。
view:以声明方式将state映射到视图。
actions:响应在view上的用户输入导致的状态变化。
状态管理:
组件间的通信方式:
- 父组件给子之间的传值(父子组件)
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>
- 子组件给父组件传值
子组件: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>
- 不相关组件之间的传值
也是使用事件的方式: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>
子组件2bus.$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>
- 组件间通信方式回顾-通过 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>
- 状态管理方案 - 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
}
}
在组件中使用:
- 引入store
- 使用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的核心概念:
上述图的描述:
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
目录结构:
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
}
})
开启之后的报错:
不要在生产模式开启,因为会深度监听影响性能。
购物车案例
购物车功能:
1.用到了node写的接口:node server.js
2.
页面:
当刷新购物车页面之后当,前购物车中的数据是存储在本地的。
项目代码地址:
三个组件:商品列表组件(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
}