Vue组件通信小结
概述
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
子组件,事实上就实现了DataPicker
与DataLister
两个兄弟组件间的通信;
- 当用户在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的视图将得到更新,事实上实现了
DataPicker
与DataLister
两个兄弟组件间的通信;
祖孙通信
- 祖代组件以project的形式将特定数据状态透传整棵组件树
- 该组件树上的任意后代组件通过inject的方式注入/订阅祖代透传下来的数据
- 当祖代组件主动修改该数据状态时,所有订阅了该数据的后代组件都会被同步更新
本例实现在根组件App
上定义全局主题样式(白天模式/黑夜模式),DataLister
组件订阅和注入该数据,当用户在根组件App上点击按钮切换主题时,DataLister的显示样式将同步更新;
- 全局配置inject数据的自动解包
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>
获取源码
猛戳此处获取老师源码! 更高处见!
转载自:https://juejin.cn/post/7219490209715519547