# 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 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

vuex

# 安装 veux

npm install vuex --save

或者

yarn add vuex

# 使用

  • src 目录下新建一个 store 文件夹
  • 新建 index.js / getters.js
  • 新建 modules 文件夹里面存放 store 模块
├── store
│   ├── modules
│   │   ├── user.js (存放user模块数据)
│   │   ├── setting.js (存放全局设置模块数据)
│   ├──getters.js
│   ├──index.js

 
 
 

 

 











 
 
 
 

 

// 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 更改 Vuexstore 中的状态的唯一方法是提交 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>
Last Updated: 3/3/2022, 11:41:43 PM