文章内容输出来源:拉勾教育大前端高薪训练营
子组件通过props接受数据
<template>
<div>
<h2>{{title}}h2>
div>
template>
<script>
export default {
// props: ['title'],
props: {
title: String
}
}
script>
父组件中给子组件通过响应属性传值
<template>
<div>
<child title="My journey with Vue">child>
div>
template>
<script>
import child from './01-Child'
export default {
components: {
child
}
}
script>
通过自定义事件子组件给父组件传值
<template>
<div>
<h1 :style="{ fontSize: fontSize + 'em' }">Props Down Childh1>
<button @click="handler">文字增大button>
div>
template>
<script>
export default {
props: {
fontSize: Number
},
methods: {
handler () {
this.$emit('enlargeText', 01)
}
}
}
script>
父组件中注册子组件内部触发的事件
<template>
<div>
<h1 :style="{ fontSize: hFontSize + 'em' }">Event Up Parenth1>
这里的文字不需要变化
<child :fontSize="hFontSize" v-on:enlargeText="enlargeText">child>
<child :fontSize="hFontSize" v-on:enlargeText="enlargeText">child>
<child :fontSize="hFontSize" v-on: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>
通过事件中心eventbus触发和注册事件
import Vue from 'vue'
export default new Vue()
触发eventbus中的事件
<template>
<div>
<h1>Event Bus sibling01h1>
<div class="number" @click="sub">-div>
<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)
}
},
add () {
this.value++
bus.$emit('numchange', this.value)
}
}
}
script>
注册事件
<template>
<div>
<h1>Event Bus sibling02h1>
<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两个作用:
在普通HTML标签上使用ref,获取到的是DOM
在组件标签上使用ref,获取到的是组件实例
子组件中定义ref
<template>
<div>
<h1>
ref Child
h1>
<input ref="input" type="text" v-model="value">
div>
template>
<script>
export default {
data () {
return {
value: ''
}
},
methods: {
focus () {
this.$refs.input.focus()
}
}
}
script>
父组件获取子组件的ref
<template>
<div>
<h1>
ref Parent
h1>
<child ref="c">child>
div>
template>
<script>
import child from './04-child'
export default {
components: {
child
},
mounted () {
this.$refs.c.focus()
this.$refs.c.value = 'hello input'
}
}
script>
ref这种方式不到万不得已不要使用,会导致数据的混乱。
store.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
<template>
<div>
<h1> componentA h1>
user name : {{ sharedState.user.name }}
<button @click="change"> Change Info button>
div>
template>
<script>
import store from './store'
export default {
methods: {
change () {
store.setUserNameAction('componentA')
}
},
data () {
return {
privateState: {},
sharedState: store.state
}
}
}
script>
componentB.vue
<template>
<div>
<h1> componentB h1>
user name : {{ sharedState.user.name }}
<button @click="change"> Change Info button>
div>
template>
<script>
import store from './store'
export default {
methods: {
change () {
store.setUserNameAction('componentB')
}
},
data () {
return {
privateState: {},
sharedState: store.state
}
}
}
script>
定义store:store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {}
})
注入store:
import store from './store'
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
store/index.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
<template>
<div id="app">
<h1>Vuex - Demo h1>
<p>count: {{ count }}p>
<p>msg: {{ msg }}p>
div>
template>
<script>
import { mapState } from 'vuex'
export default {
name: 'App',
computed: {
// count: state => state.count,
...mapState(['count', 'msg'])
}
}
script>
$store.state.xxx
和mapState
展开两种写法都可以。不过使用mapState
展开成计算属性时,如果原本就有这个属性名,那么mapState
展开的属性就不起作用,可以通过给属性重命名的方式更改计算属性的名称:
<p>count: {{ num }}p>
<p>msg: {{ message }}p>
...mapState({ num: 'count', message: 'msg' })
Vuex中的getter相当于VUE中的计算属性。
store/index.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
<template>
<div id="app">
<h1>Vuex - Demo h1>
<p>count: {{ num }}p>
<p>msg: {{ message }}p>
<h2>Getterh2>
<p>reverseMsg: {{ reverseMsg }}p>
div>
template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
name: 'App',
computed: {
// count: state => state.count,
// ...mapState(['count', 'msg']),
...mapState({ num: 'count', message: 'msg' }),
...mapGetters(['reverseMsg'])
}
}
script>
使用mapGetters将VUE中的getters导入到Vue的计算属性中,用法和mapState类似。
Mutation中修改state,只能支持同步操作。
store/index.js
mutations: {
increate (state, payload) {
state.count += payload
}
},
在模板中通过$store.emit()
来提交mutation。
App.vue
<button @click="$store.commit('increate', 3)">Mutationbutton>
而mutation的本质是方法,所以可以通过mapMutations将mutation中的方法展开到Vue的methods中
App.vue
<template>
<div id="app">
<h1>Vuex - Demo h1>
<p>count: {{ num }}p>
<p>msg: {{ message }}p>
<h2>Getterh2>
<p>reverseMsg: {{ reverseMsg }}p>
<h2>Mutationh2>
<button @click="increate(3)">Mutationbutton>
div>
template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
name: 'App',
computed: {
// count: state => state.count,
// ...mapState(['count', 'msg']),
...mapState({ num: 'count', message: 'msg' }),
...mapGetters(['reverseMsg'])
},
methods: {
...mapMutations(['increate'])
}
}
script>
打开Vue调试工具,可以看到vuex中的mutation变化,每个mutation上面的三个按钮,分别是提交本次mutation、恢复到本次的mutation、时光旅行。
Action中可以进行异步操作,不过如果需要修改state,得提交Mutation。
Store/index.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
<template>
<div id="app">
<h1>Vuex - Demo h1>
<p>count: {{ num }}p>
<p>msg: {{ message }}p>
<h2>Getterh2>
<p>reverseMsg: {{ reverseMsg }}p>
<h2>Mutationh2>
<button @click="increate(3)">Mutationbutton>
<h2>Actionh2>
<button @click="increateAsync(6)">Mutationbutton>
div>
template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'App',
computed: {
// count: state => state.count,
// ...mapState(['count', 'msg']),
...mapState({ num: 'count', message: 'msg' }),
...mapGetters(['reverseMsg'])
},
methods: {
...mapMutations(['increate']),
...mapActions(['increateAsync'])
}
}
script>
Module可以让单一状态树拆分成多个模块,每个模块可以拥有state、mutation、action、getter,甚至嵌套子模块。
在使用模块里的数据时,可以通过$store.模块名.state状态属性名
的方式访问
在使用模块里的方法时,可以通过$store.commit('mutation方法名')
的方式提交mutation
当想要有更强的封装性时,可以开启命名空间,在导出的模块对象里增加一个namespaced属性为true,然后就可以在Vue中使用 mapState('模块名', ['state状态属性名'])
的方式获取到属性名称,使用mapMutations('模块名', ['mutation方法名'])
的方式获取到方法名。
./store/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
}
./store/modules/cart.js
const state = {}
const getters = {}
const mutations = {}
const actions = {}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
./store/index.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
<template>
<div id="app">
<h1>Vuex - Demo h1>
<p>count: {{ num }}p>
<p>msg: {{ message }}p>
<h2>Getterh2>
<p>reverseMsg: {{ reverseMsg }}p>
<h2>Mutationh2>
<button @click="increate(3)">Mutationbutton>
<h2>Actionh2>
<button @click="increateAsync(6)">Mutationbutton>
<h2>Modulesh2>
<p>products: {{ products }}p>
<button @click="setProducts([])">Mutationbutton>
div>
template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'App',
computed: {
// count: state => state.count,
// ...mapState(['count', 'msg']),
...mapState({ num: 'count', message: 'msg' }),
...mapGetters(['reverseMsg']),
...mapState('products', ['products'])
},
methods: {
...mapMutations(['increate']),
...mapActions(['increateAsync']),
...mapMutations('products', ['setProducts'])
}
}
script>
Vuex中的状态的更新要通过提交mutation来修改,但其实在组件中还可以通过$store.state.msg
进行修改,从语法从面来说这是没有问题的,但是这破坏了Vuex的约定,如果在组件中直接修改state,devtools无法跟踪到这次状态的修改。
开启严格模式之后,如果在组件中直接修改state会抛出错误,但数据仍被成功修改。
如何开启:在store中增加一个属性strict为true
store/index.js
export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
state: {
count: 0,
msg: 'Hello Vuex'
},
// ...
})
App.vue
<h2>stricth2>
<button @click="$store.state.msg = 'lagou'">strictbutton>
注意:不要在生产模式下开启严格模式,严格模式会深度检查状态树,检查不合规的状态改变,会影响性能。
我们可以在开发模式下开启严格模式,在生产模式中关闭严格模式:
strict: process.env.NODE_ENV !== 'production',
地址:https://github.com/goddlts/vuex-cart-demo-template.git
用到了ElementUI、Vuex、Vue-Router
项目根目录下的server.js文件是一个node服务,为了模拟项目接口。
页面组件和路由已经完成了,我们需要使用Vuex完成数据的交互。
三个组件:
这个参数可以订阅一个函数,让这个函数在所有的mutation结束之后执行。
const myPlugin = store => {
// 当store初始化后调用
store.subscribe((mutation, state) => {
// 每次mutation之后调用
// mutation的格式为{ type, payload }
})
}
Store/index.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]
})
Myvuex/index.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
import Vuex from '../myvuex'