# Vue 组件之间通信
# 1、props / $emit
父组件通过 props 的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信。
# 父组件向子组件传递
// 父组件
<template>
<div>
<Child :text="text" :pObj="objToChild" />
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: { Child },
data() {
return {
text: "传递给子组件的数据",
// 传给子组件的对象
objToChild: {
title: "国庆当即",
text: `1949年10月1日,中华人民共和国中央人民政府成立典礼,
即开国大典,在北京天安门广场隆重举行。“最早提出‘国庆日’
的马叙伦先生。`,
},
};
},
};
</script>
<template>
<div>
<!--使用父组件传递过来的数据-->
<h2>{{ text }}</h2>
<h3>{{ pObj.title }}</h3>
<span>{{ pObj.text }}</span>
</div>
</template>
<script>
export default {
// 接受父组件传递的数据 键与父组件中 : 后面绑定的属性名一致
props: {
text: {
type: String,
},
pObj: {
type: Object,
},
},
};
</script>
# 子组件向父组件传递
<template>
<div>
<Child @cData="this.getChidData" />
<!--使用子组件传递的数据-->
<h1>{{ dataFromChild && dataFromChild.title }}</h1>
{{ dataFromChild && dataFromChild.num }}
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: { Child },
data() {
return {
// 存放子组件传递的数据
dataFromChild: {},
};
},
methods: {
getChidData(val) {
this.dataFromChild = val;
},
},
};
</script>
<template>
<div>
<button @click="sendDataToParent">传递数据给父组件</button>
</div>
</template>
<script>
export default {
data() {
return {
// 传递给父组件的数据
cData: {
title: "子组件给父组件的数据",
num: 100,
},
};
},
methods: {
sendDataToParent() {
// 第一个参数是父组件中 `@`后的方法名,第二个参数是传递给父组件的数据
this.$emit("cData", this.cData);
},
},
};
</script>
# 2、$children / $parent
- 指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中。
💣
- 节制地使用 $parent 和 $children
- 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信
- 非响应式
- 使用 this.$children[index] index 取决于在 template 模板中使用的顺序
<!--父组件-->
<template>
<div>
父组件:
<button @click="getChidData">获取子组件的text数据</button>
{{ pData }}
<hr />
<Child />
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: { Child },
data() {
return {
pData: "", // 存放获取到的子组件数据
text: "父组件数据",
};
},
methods: {
getChidData() {
this.pData = this.$children[0].text;
console.log(this.pData); // 子组件数据
},
},
};
</script>
<!--子组件-->
<template>
<div>
子组件:
<button @click="getParentData">获取父组件的text数据</button>
{{ cData }}
<hr />
子组件:
<button @click="changeChildText">改变子组件数据</button>
{{ text }}
</div>
</template>
<script>
export default {
data() {
return {
cData: "", // 存放从父组件获取到的数据
text: "子组件数据",
};
},
methods: {
getParentData() {
this.cData = this.$parent.text;
console.log(this.cData);
},
changeChildText() {
this.text = "改变后的子组件数据";
},
},
};
</script>
# 3、provide/ inject
简单来说就是父组件中通过 provide 来提供变量, 然后再子组件中通过 inject 来注入变量。
WARNING
注意: 这里不论子组件嵌套有多深, 只要调用了 inject 那么就可以注入 provide 中的数据,而不局限于只能从当前父组件的 props 属性中回取数据
<!--Parent组件-->
<template>
<div>
<h1>Parent</h1>
{{ riches }}
<Child />
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: { Child },
provide() {
return {
riches: this.riches, // 将父组件的数据分发下级
};
},
data() {
return {
riches: {
house: "两套",
godbar: "10根",
cash: "100万",
},
};
},
methods: {},
};
</script>
<!--Child子组件-->
<template>
<div>
<h1>son</h1>
{{ riches }}
<hr />
<GrandSon />
</div>
</template>
<script>
import GrandSon from "./grandson.vue";
export default {
components: { GrandSon },
inject: ["riches"],
data() {
return {};
},
methods: {},
};
</script>
<!--孙子组件grandSon-->
<template>
<div>
<h1>grandSon</h1>
{{ cash }}
</div>
</template>
<script>
export default {
inject: ["riches"],
computed: {
cash() {
return this.riches.cash;
},
},
};
</script>
# 4、ref / refs
👳
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据
// 父组件Parent.vue
<template>
<div>
<h1>Parent</h1>
<Child ref="childRef" />
<button @click="getRefChildData">使用ref获取子组件数据</button>
<button @click="$refs.childRef.sayHello()">使用ref调用子组件方法</button>
{{ title }}
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: { Child },
data() {
return { title: "" };
},
methods: {
getRefChildData() {
this.title = this.$refs.childRef.title; //获取子组件数据
// this.$refs.childRef.sayHello() // 调用子组件方法
},
},
};
</script>
<!-- 子组件Child.vue -->
<template>
<div>
<h1>son</h1>
</div>
</template>
<script>
export default {
data() {
return {
title: "子组件数据",
};
},
methods: {
sayHello() {
alert("hello world");
},
},
};
</script>
# 5、eventBus
TIP
EventBus 是消息传递的一种方式,基于一个消息中心,订阅和发布消息的模式,称为发布订阅者模式。
- on('name', fn)订阅消息,name: 订阅的消息名称, fn: 订阅的消息
- emit('name', args)发布消息, name: 发布的消息名称, args: 发布的消息
# 初始化
// event-bus.js
import Vue from "vue";
export const EventBus = new Vue();
// 示例Parent组件
<template>
<div>
<Child />
<Child1 />
</div>
</template>
<script>
import Child from "./Child.vue";
import Child1 from "./Child1.vue";
export default {
components: { Child, Child1 },
};
</script>
# 发送事件
// child
<template>
<div>
<h1>子组件1</h1>
<button @click="sendData">sendData</button>
</div>
</template>
<script>
import { EventBus } from "./event-bus.js";
export default {
data() {
return {
fruits: ["苹果", "香蕉", "西瓜"],
};
},
methods: {
sendData() {
EventBus.$emit("watchFunc", { fruits: this.fruits });
},
},
};
</script>
# 接收事件
// chil1组件
<template>
<div>
<h1>子组件2</h1>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
import { EventBus } from "./event-bus.js";
export default {
data() {
return {
list: [],
};
},
mounted() {
EventBus.$on("watchFunc", (params) => {
this.list = params.fruits;
});
},
};
</script>
# 移除事件监听者
import { eventBus } from "event-bus.js";
EventBus.$off("addition", {});
# 6、Vuex
🐴
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
# 安装 veux
npm install vuex --save
或者
yarn add vuex
# 使用
- src 目录下新建一个 store 文件夹
- 新建 index.js / getters.js
- 新建 modules 文件夹里面存放 store 模块
// store/index.js
import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
Vue.use(Vuex);
// 导入所有在modules文件的模块 也可以单独导入
const modulesFiles = require.context("./modules", true, /\.js$/);
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './user.js' => 'user'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, "$1");
const value = modulesFiles(modulePath);
modules[moduleName] = value.default;
return modules;
}, {});
const store = new Vuex.Store({
modules,
getters,
});
export default store;
# 项目入口文件 main.js 引入
import store from "./store";
new Vue({
el: "#app",
router,
store, // 注入根实例
render: (h) => h(App),
});
# State
唯一数据源,Vue 实例中的 data 遵循相同的规则
// user.js模块示例
const state = {
token: getToken(),
name: "",
avatar: "",
introduction: "",
roles: [],
};
const mutations = {};
const actions = {};
export default {
state,
mutations,
actions,
};
1、在 Vue 组件中获得 Vuex 状态
// 创建一个 user 组件
const User = {
template: `<div>{{ userName }}</div>`,
computed: {
userName() {
return this.$store.state.user.name; // 因为分了模块所以.user.
},
},
};
2、mapState 辅助函数
import { mapState } from "vuex";
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: (state) => state.user.name,
}),
};
# Getters
TIP
从 store
中的 state
中派生出一些状态
可以认为是 store 的计算属性,就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,
且只有当它的依赖值发生了改变才会被重新计算。
// getters.js
const getters = {
token: (state) => state.user.token,
avatar: (state) => state.user.avatar,
name: (state) => state.user.name,
introduction: (state) => state.user.introduction,
};
export default getters;
// 组件中使用1
computed: {
userName () {
return this.$store.getters.name
}
}
// 组件中使用2
import { mapGetters } from 'vuex'
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'name'
])
// // 取别名
// ...mapGetters({
// // 把 `this.userName` 映射为 `this.$store.getters.name`
// userName: 'name'
// })
}
# Mutation
mutation
更改 Vuex
的 store
中的状态的唯一方法是提交 mutation
,非常类似于事件,通过store.commit 方法触发
// user.js模块
const mutations = {
SET_NAME: (state, name) => {
state.name = name;
},
};
this.$store.commit("SET_NAME", "jc-sir"); // 将user模块中的state.name变更为jc-sir
# Action
TIP
Action 类似于 mutation,不同在于 Action 提交的是 mutation,而不是直接变更状态,Action 可以包含任意异步操作
// user.js
import { getInfo } from "@/api/user";
const state = {
token: getToken(),
};
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token;
},
};
const actions = {
// user login
loginIn({ commit }, userInfo) {
const { username, password } = userInfo;
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password })
.then((response) => {
const { data } = response;
commit("SET_TOKEN", data.token);
resolve();
})
.catch((error) => {
reject(error);
});
});
},
};
// 调用登录 login.vue
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm)
.then(() => {
this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
this.loading = false
})
.catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
},
# Module
TIP
module 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)
const user = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const setting = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
user,
setting
}
})
store.state.user // -> user 的状态
store.state.setting // -> setting 的状态
# 7、localStorage / sessionStorage
- 该通信方式比较简单,缺点是数据和状态比较混乱,不太容易维护。
- 通过 window.localStorage.setItem(key,value)存储数据
- 通过 window.localStorage.getItem(key)获取数据
TIP
注意用
JSON.parse() / JSON.stringify()
做数据格式转换localStorage / sessionStorage
可以结合vuex
, 实现数据的持久保存,同时使用 vuex 解决数据和状态混乱问题.
# 8、$attrs与 $listeners
# 隔代通信的方式
- 1、使用 props/$emit 逐级传递
- 2、EventBUs 缺点:多人开发代码维护性较低,可读性差
- 3、Vuex 如果仅仅是传递数据, 而不做中间处理,使用 Vuex 处理大材小用
- 4、LocalStorage/SessionStorage
- 5、$attrs 和$listeners
// 父组件Parent
<template>
<div>
<Child :name="name" :height="height" :weight="weight" @testFun="testFun" />
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: { Child },
data() {
return {
name: "jc-sir",
height: 170,
weight: "62kg",
};
},
methods: {
testFun() {
console.log("hello");
},
},
};
</script>
// 子组件Child
<template>
<div>
<GrandSon v-bind="$attrs" v-on="$listeners" />
</div>
</template>
<script>
import GrandSon from "./grandSon";
export default {
components: { GrandSon },
};
</script>
// 孙组件grandSon
<template>
<div>
{{ $attrs }}
<button @click="fun()"></button>
</div>
</template>
<script>
export default {
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
methods: {
fun() {
// this.$listeners.testFun()
this.$emit("testFun");
},
},
};
</script>