Skip to content

4 消息传递

约 478 个字 179 行代码 1 张图片 预计阅读时间 4 分钟

1 Vuex 实现数据共享

用于实现“集中式数据管理”的 Vue 插件

  • 对多个组件间的 读 / 写 进行集中管理
  • 适用于任意组件间通信

Vuex 不属于任何一个组件(包括 App)

Vuex 官网

Intro

工作原理

  • ajax 请求由 Actions 进行发送
  • Actions, Mutations, State 均受 Store 管理

搭建环境

  1. 安装 npm install vuex@3 (Vue2 - vuex3;Vue3 - vuex4)
  2. 使用插件 import Vuex from 'vuex' Vue.use(Vuex)

    并在 vm 中定义 store —— 定义 src/store/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex) // 不然会报错,淦内
    
    // actions - 响应组件中的动作
    const actions = {}
    // mutations - 用于操作数据(state)
    const mutations = {}
    // state - 存储数据
    const state = {}
    
    // 创建并向外暴露 store
    export default new Vuex.Store({
        actions,
        mutations,
        state
    })
    
    3. 在 App 中引入 Store
    // vue-cli 会把 import 按编写顺序汇总到最上方,再执行其他
    import store from './store'
    new Vue({
        store: store // 或者简写成 store
    })
    

Demo

  1. 把数据扔给 state
    const state = {
        sum: 0
    }
    
  2. 操作属性 - dispatch

    如果 action 中没有需要判断的逻辑,可以直接在此处调用 commit

    this.$store.commit('ADD', this.n) (也不用配置 action.add 了)

    methods: {
        increase() {
            // store 存储在 vc 上
            this.$store.dispatch('add', this.n) // 操作 - +n
        }
    }
    

  3. 配置对应的 action

    在 action 中可以继续把参数 dispatch 给其他的 action 进行处理(类似于把很多个 pipe 串起来);

    也可以用 ajax 向后端请求数据

    import axios from 'axios'
    const actions = {
        add: function(context, val) { // 可简写为 add(){}
            context.commit('ADD', val) // 字母大写区分一下
    
            // 业务逻辑一般写在 action 里:timer / 条件判断 ...
            // 此处可以使用 context.state.name 访问对应的值
        },
        addFromServer(context) { // axios 请求示例
            axios.get('url').then({
                res => {
                    context.commit('ADD_FROM_SERVER', res.data) // move on
                },
                err => {
                    alert(err.message)
                }
            })
        }
    }
    

  4. 配置对应的 mutation

    const mutations = {
        ADD: function(state, val) {
            state.sum += val // 直接操作 state 中的数据
        }
    }
    

  5. 读取 state 中的数据

    <h2>当前总和为:{{$store.state.sum}}</h2>
    

getters 配置项「可选」

需要读取加工过的 state 属性

// @ ./store/index.js
const getters = { // 用于加工 state 中的数据
    mul_10(state) {
            return state.sum*10
    }
}
// 记得塞到 vuex 的 options 中
// 可以通过 $state.getters.bigsum 进行访问

1.1 代码生成

可以采用计算属性简化插值语法中的内容:

computed: {
    sum() {
        return this.$store.state.sum // -> 直接用 {{sum}} 就行
    },
    bigsum() {
        return this.$store.getters.bigsum
    }
}
但还是有点冗余 emmm

MapState & MapGetters

「代码生成」批量生成类似于上述结构的计算属性

import {mapState, mapGetters} from 'vue'
// mapState(Obj) 的生成结果是一个对象
new Vue({
    computed: {
        // 对象式写法
        ...mapState({ // 计算属性名:state分量名
            he: 'sum',
            xuexiao: 'school',
            xueke: 'subject'
        }),
        // 数组式写法 - 生成和 state 中分量同名的计算属性
        ...mapState(['sum', 'school', 'subject']),
        // getters 的生成规则同理
        ...mapGetters(['bigsum'])
    }
})

MapActions & MapMutations

methods 中存在一堆重复调用 dispatch / commit 的函数

-> 所以我们生成一下

import {mapActions, mapMutations} from 'vue'
new Vue({
    methods: {
        ...mapActions(['addAwait', 'addOdd']), 
        // dispatch -> 调用时需要传参
        ...mapMutations({increase:'add', decrease:'sub'}) 
        // commit 生成格式如下:
        // increase(val) {this.$store.commit('add', val)}
        // 在绑定事件时就需要穿参 - @click="increase(n)"
    }
})

1.2 模块化 & namespace

把 action / mutation / state / getter 按照功能模块进行打包

在 store 中分装模块

// @ ./store/index.js
// 计算相关的配置
const countOptions = {
    namespace: true, // 否则无法在 map 中指定模块名称
    actions: {},
    mutations: {},
    state: {},
    getters: {}
}
// 人员相关的配置
const peopleOptions = {}

// 创建并暴露 store
export default new Vuex.Store({
    modules: { // 同样可以使用简写形式
        count: countOptions,
        people: peopleOptions
    }
})

修改 Map 生成代码

new Vue({
    computed: {
        ...mapState('count',['sum', 'school', 'subject'])
        ...mapState('person',['personList']) // 每个模块单独一行  
        // 也可以通过 this.$store.state.count.sum 来拿到数据
    }
})

修改 commit

new Vue({
    methods: {
        ...mapMutations('count',{increase:'add', decrease:'sub'}),
        // 同样是每个模块单独一行
        neo_person() {
            const p_obj = {id:nanoid(), name:this.name}
            this.$store.commit('person/ADD_PERSON', p_obj)
            // 手动调用 - namespace/function
        }
    }
})

修改 getter

// 对于 mapGetters 的修改和上面类似
// 手动从 getters 中读取值
computed: {
    firstName() {
        return this.$store.getters['person/firstPersonName'] 
        // 因为 . / 冲突,此处使用下标访问
    }
}

2 全局事件总线

任意组件间通信 其实就是所有人都通过第三方进行转接

// App.js - 安装全局事件 $bus = vm
new Vue({
    beforeCreate() { 
        Vue.prototype.$bus = this
    }
})

// 发信方(记得给标签绑定事件)
methods: {
    sendStuName() {
        this.$bus.$emit('hello_event', 666)
        // 触发三方的 hello 事件,携带数据 666
    }
}

// 收信方 - 回调函数定义在收信放的 methods 中
mounted() {
    this.$bus.$on('hello_event', (data) => { console.log(data) })
}

记得销毁组件时关掉 $bus 上绑定的相关事件

beforeDestroy() {
    this.$bus.$off('hello_event')
}

3 消息订阅 & 发布

实现任意组件间通信 - 此处试用 pubsub-js 实现

  1. 安装 pubsub-js - npm install pubsub-js
  2. 在涉及收发的组件中引入 - import pubsub from 'pubsub-js'
  3. 接收方订阅消息
    mounted() {
        // 每次订阅都会生成不同的订阅 id
        this.pubID = pubsub.subscribe('name',(msgName, data)=>{})
        // 回调是 method / 箭头函数(否则 this 指针为 undefined)
    }
    
  4. 发布消息
    methods: {
        sendInfo() {
            pubsub.publish('name', data)
        }
    }
    
  5. 销毁时解绑
    beforeDestroy() {
        pubsub.unsubscribe(this.pubID)
    }