likes
comments
collection
share

Vue组件通信小结

作者站长头像
站长
· 阅读数 40

概述

Vue中的组件通信无外乎父子通信、子父通信、兄弟通信、祖孙通信、自由通信;

其通信手段,有歌诀如下:

  • 父传子:props-down
  • 子传父:events-up
  • 兄弟传:借助父
  • 祖传孙:provide-inject
  • 自由传:中央库

本例我们以经典的TodoList(土豆丝儿)为例,与诸位就组件通信问题做一个了断~

组件结构图如下:

|- App.vue 根组件
    |- TodoList.vue 页面级组件
        |- DataPicker.vue 采集用户输入并提交
        |- DataLister.vue 待办事项展示列表,点击切换待办事项状态
|- store.js 中央数据仓库

父子通信

  • 父组件将自己的数据状态以props的形式传递给子组件;
  • 当父组件的数据状态发生变化时,子组件自动获得响应式更新;

本例实现父组件TodoList给子组件DataLister传入待办事项列表,由子组件进行渲染;

  • 父组件定义并维护自己的数据状态 TodoList.vue
data() {
    return {
        /* 李逵的待办事项列表 */
        todos: [
            {
                name: "杀到东京",
                done: false
            },
            {
                name: "夺了鸟位",
                done: false
            },
            {
                name: "让俺哥哥当大宋皇帝",
                done: false
            },
        ]
    }
},
  • 父组件以props的形式将状态传入子组件
<DataLister :list="todos"></DataLister>
  • 子组件声明接收来自父组件的props DataLister.vue
props: {
    list: Array
},
  • 子组件使用父组件传入的props,本例中渲染待办事项列表
<ul>
    <li v-for="item in list" :key="item.name">
        {{ item }}
    </li>
</ul>

子父通信

  • 子组件想要修改父组件中定义的数据,需要给父组件发送自定义事件,并携带业务参数;
  • 父组件接收并处理该自定义事件,并根据业务参数修改自己的数据;
  • 当父组件的数据状态发生变化时,子组件的视图同时得到更新;

本例实现在子组件DataLister中点击待办事项,切换该项目的完成状态为完成/未完成;

  • 子组件声明自定义事件

DataLister.vue

emits: ["itemClick"],
  • 在用户点击待办事项条目时,向父组件发送自定义事件,携带参数即为当前点击的条目;
<li 
    v-for="item in list"
    :key="item.name" 
    @click="onItemClick(item)">
        {{ item }}
</li>
methods: {
    onItemClick(item) {
        console.log("onItemClick", item);

        // 向父组件发送自定义事件 并携带业务参数
        this.$emit("itemClick", item)
    }
}
  • 父组件接收和处理子组件发送上来的自定义事件

TodoList.vue

<DataLister :list="todos" @item-click="toggleItemStatus"></DataLister>
methods: {
    toggleItemStatus(item) {
        console.log("toggleItemStatus", item);

        // 根据自定义事件携带的item参数 找出被点击的条目
        const todo = this.todos.find(todo => todo.name === item.name)

        // 切换该条目的状态
        todo.done = !todo.done
    },
}

样式配合

<ul>
    <li :class="{ done: item.done }" ...>
        {{ item }}
    </li>
</ul>
<style lang="css" scoped>
li.done {
    text-decoration: line-through;
}
</style>

兄弟通信

  • 两个兄弟节点间的通信需要借助于共同的父组件;
  • 即:Son1组件通过自定义事件通知父组件修改状态,而该状态恰好以props的形式传给了Son2组件,这样一来Son2组件也获得同步更新!

本例我们通过子组件DataPicker采集到用户输入的待办事项名称,点击按钮通知父组件TodoList添加待办事项到todos列表,而该数据会自动同步给DataLister子组件,事实上就实现了DataPickerDataLister两个兄弟组件间的通信;

  • 当用户在DataPicker中完成输入并点击按钮时,以自定义事件的方式通知父组件追加待办事项;

DataPicker.vue

<template>
    <div>
        <!-- {{ userInput }} -->
        <input v-model="userInput">
        <button @click="onActionBtnClick">+</button>
    </div>
</template>

<script>
export default {
    // 自定义事件:用户确认输入
    emits: ["userInputOk"],

    /* DataPicker自己维护用户的输入 */
    data() {
        return {
            // 该状态双向绑定到input框
            userInput: ""
        }
    },

    methods: {
        /* 响应用户点击确认按钮 */
        onActionBtnClick() {
            console.log("onActionBtnClick", this.userInput);

            // 向父组件发送自定义事件 携带用户的输入
            this.$emit("userInputOk", this.userInput)

            // 清空用户输入
            this.userInput = ""
        }
    }
}
</script>
  • 父组件接收来自DataPicker的自定义事件,读取事件参数,并追加待办事项到todos列表;

TodoList.vue

<DataPicker @user-input-ok="addTodo"></DataPicker>
/* 读取事件参数 + 构造一个todo对象 + 追加到待办事项列表 */
addTodo(userInput) {
    console.log("addTodo", userInput);

    this.todos.push({
        name: userInput,
        done: false
    })
}
  • 当父组件TodoList中的todos数据发生变化时,DataLister组件所接收到的props也随之变化,DataLister的视图将得到更新,事实上实现了DataPickerDataLister两个兄弟组件间的通信;

祖孙通信

  • 祖代组件以project的形式将特定数据状态透传整棵组件树
  • 该组件树上的任意后代组件通过inject的方式注入/订阅祖代透传下来的数据
  • 当祖代组件主动修改该数据状态时,所有订阅了该数据的后代组件都会被同步更新

本例实现在根组件App上定义全局主题样式(白天模式/黑夜模式),DataLister组件订阅和注入该数据,当用户在根组件App上点击按钮切换主题时,DataLister的显示样式将同步更新;

  • 全局配置inject数据的自动解包

Vue组件通信小结

main.js

const app = createApp(App)
app.config.unwrapInjectedRef = true
app.mount('#app')
  • 祖代组件定义主题数据状态,并透传全局;

App.vue

  data() {
    return {
      // 全局主题
      theme: "light" //或dark
    }
  },

  provide() {
    // 使用函数的形式,可以访问到 `this`
    return {
      theme: computed(() => this.theme)
    }
  },
  • 在App.vue中点击按钮切换主题
<button @click="switchTheme">切换主题</button>
  methods: {
    switchTheme() {
      console.log("switchTheme", this.theme);
      this.theme = (this.theme === "light" ? "dark" : "light")
    }
  }
  • 后代组件主动注入祖代透传下来的数据项
inject: ['theme'],

样式配合

<ul>
    <li 
        :class="{ light: theme === 'light', dark: theme === 'dark' }" ...>
            {{ item }}
    </li>
</ul>
<style lang="css" scoped>
...

/* 白天主题:白底黑子 */
li.light {
    background-color: white;
    color: black;
}

/* 夜间主题:黑底白字 */
li.dark {
    background-color: black;
    color: white;
}
</style>

自由通信

  • 组件间的自由无障碍通信需要借助中央数据仓库
  • 任何组件可以通过发送action的方式修改中央仓库的数据状态
  • 所有订阅了该状态的组件都会被同步更新

本例将前面的全局主题切换功能使用Vuex重构

  • 定义中央数据仓库

store.js

import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
    state() {
        return {
            theme: "light",
        }
    },

    actions: {
        switchTheme({ commit, state }) {
            state.theme = state.theme === "light" ? "dark" : "light"
        }
    },
})

export default store

main.js

import store from './store.js'

const app = createApp(App)

// 将 store 实例作为插件安装
app.use(store)
...
app.mount('#app')
  • 任意组件订阅Vuex中的theme状态

DataListerX.vue

computed: {
    ...mapState({
        theme: "theme",
        // list: "todos"
    }),
},
<ul>
    <li :class="{ done: item.done, light: theme === 'light', dark: theme === 'dark' }" v-for="item in list"
        :key="item.name" @click="toggleItemStatus(item)">
        {{ item }}
    </li>
</ul>
  • 任意组件通过派发action修改Vuex中的theme状态

App.vue

  methods: {
    ...mapActions(["switchTheme"]),
    // ...
  }
<button @click="switchTheme">切换主题</button>

获取源码

猛戳此处获取老师源码! 更高处见!

Vue组件通信小结

转载自:https://juejin.cn/post/7219490209715519547
评论
请登录