Vuex状态管理及手写 Vuex

114 阅读1分钟

这是我参与8月更文挑战的第9天,活动详情查看: 8月更文挑战

组件内状态管理流程

new Vue({
    // state
    data () {
        return {
            count: 0
        }
    },
    // view
    template: `<div>{{ count }}</div>`,
    // actions
    methods: {
        increment () {
            this.count++
        }
    }
})

状态管理

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

vue单向数据流示意图

问题背景

三种组件间通信方式

  • 父组件给子组件传值
  • 子组件给父组件传值
  • 不相关组件之间传值

2RLo8K.png

1. 父组件给子组件传值

  • 子组件中通过 props 接收数据
  • 父组件中给子组件通过相应属性传值
// Child.vue
export default {
    // props: ['title'],
    props: {
        title: String
    }
}

// Parent.vue
<template>
    <div>
        <child title="My first Child Title"></child>
    </div>
</template>
import Child from './Child.vue'
export default {
    components: {
        Child
    }
}

2. 子组件给父组件传值

  • 通过 $emit 触发自定义事件

3. 不相关组件之间传值

  • 通过 event-bus
// eventbus.js
import Vue from 'vue'
export default new Vue()

// 01.vue
import bus from './eventbus.js'

add () {
    this.value++
    bus.$emit('numchange', this.value)
}

// 02.vue
import bus from './eventbus'
bus.$on('numchange', value => {
    console.log('num == ' + value)
})

通过 ref 获取子组件

  • $root
  • $parent
  • $children
  • $refs

ref 两个作用

  • 在普通 HTML 标签上使用 ref, 获取到的是 DOM
  • 在组件标签上使用 ref, 获取到的是组件实例

简易的状态管理方案

所遇问题

  • 多个视图依赖同一状态
  • 来自不同视图的行为需要变更同一状态
// 简易的状态管理
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
    }
}

什么是 Vuex

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

什么情况下使用 Vuex

  • 非必要的情况不要使用 Vuex
  • 大型的单页面应用程序
    • 多个视图依赖于同一状态
    • 来自不同视图的行为需要变更同一状态

Vuex 核心概念

Vuex核心概念

  • Store
  • State
  • Getter
  • Mutation
  • Action
  • Module

Vuex 的基本结构

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

Vue.use(Vuex)

export default new Vuex.Store({
    state: {},
    mutations:{},
    actions: {},
    modules: {}
})

// 在 main.js 中
import store from './store'
new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')

1. State

// store.js
export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations:{},
    actions: {},
    modules: {}
})

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

<script>
import { mapState } from 'vuex'

//...
computed: {
    ...mapState(['count'])
    // 或者
    ...mapState({
        count: 'count'
    })
}
</script>

2. Getter

// store.js
export default new Vuex.Store({
    state: {
        count: 0,
        msg: 'Hello World'
    },
    getters: {
        reverseMsg (state) {
            return state.msg.split('').reverse().join('')
        }
    },
    mutations:{},
    actions: {},
    modules: {}
})

// App.vue

<h2>Getter: reverseMsg: {{ $store.getter.reverseMsg }}</h2>
<h2>Getter: reverseMsg: {{ reverseMsg }}</h2>

<script>
import { mapGetters } from 'vuex'

export default {
    computed: {
        ...mapGetters(['reverseMsg'])
    }
}
</script>

3. Mutation

// store.js
mutations: {
    add (state, payload) {
        state.count += payload
    }
}

// App.vue
<button @click="$store.commit('add', 2)">Mutation</button>

<script>
import { mapMutations } from 'vuex'
methods: {
    ...mapMutations(['add'])
}
</script>

4. Action

// store.js
actions: {
    increateAsync (content, payload) {
        setTimeout(() => {
            content.commit('add', payload)
        }, 2000)
    }
}

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

<script>
import { mapAction } from 'vuex'
methods: {
    ...mapActions(['increateAsync'])
}
</script>

5. Module

为了模块更好的封装性,建议打开命名空间 namespaced: true

// store/modules/cart.js
// store/modules/products.js
// store/index.js

// store/index.js
modules: {
    products,
    cart
}

// App.vue
<script>
import { mapState } from 'vuex'
export default {
    computed: {
        ...mapState('products', ['products'])
    }
}
</script>

Vuex 严格模式

记住不要在生产模式下开启严格模式,严格格式会深度检查状态树,来检查不合归的状态表现,会影响性能

// store.js

export default new Vuex.Store({
    strict: true
})

手写 myVuex.js

// 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._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
}