Vue组件之间的通讯是咱们在项目中经常碰到的,而选择合适的通讯方式尤其重要,这里总结下做者在实际项目中所运用到的通讯方案,若有遗漏,请你们见谅。文章代码具体见DEMO;文章首发于imondo.cnvue
Vue中常见的是父与子组件间的通讯,所要用到的关键字段是props
和$emit
。git
props
接受父组件传给子组件信息的字段,它的类型:Array<string> | Object
;详细解释能够参考文档github
$emit
由子组件触发事件向上传播给父级消息。vue-cli
示例:api
// Parent <template> <div class="parent"> 我是父组件 <p>来自子级的回答:{{ childMsg }}</p> <Child :msg="msg" @click="handleClick"/> </div> </template> <script> import Child from "./Child"; export default { name: "Parent", components: { Child }, data() { return { msg: "叫你吃饭了", childMsg: '' }; }, methods: { // 接收来自子级的事件消息 handleClick(val) { this.childMsg = val; } } }; </script> // Child <template> <div class="child"> <p>我是子组件</p> <p>父级来的信息: {{ msg }}</p> <button @click="handleClick">回答父级</button> </div> </template> <script> export default { name: "Child", // 接收父级传来的信息 props: { msg: String }, methods: { // 向父级传播事件消息 handleClick() { this.$emit('click', '我知道了'); } }, }; </script>
效果以下:app
有时候咱们可能会碰到组件间的无限嵌套,这是咱们使用props
时没法向下无限极传递数据的,这是咱们能够用到provide/inject
;provide
能够向其子孙组件传递数据,而不关子孙组件的层级有多深,使用inject
均可以拿到数据。详细解释能够参考文档ide
示例:函数
// Grand <template> <div class="grand"> <p>我是祖父</p> <Parent /> </div> </template> <script> export default { name: "Grand", provide: { grandMsg: '都来吃饭' }, components: { Parent } }; </script> // Parent <template> <div class="parent"> 我是父组件 <p>祖父的信息:{{ grandMsg }}</p> <Child /> </div> </template> <script> import Child from "./Child"; export default { name: "Parent", components: { Child }, inject: { grandMsg: { default: '' } } }; // Child <template> <div class="child"> <p>我是子组件</p> <p>爷爷的信息: {{ grandMsg }}</p> </div> </template> <script> export default { name: "Child", inject: { grandMsg: { default: '' } } }; </script>
效果以下:ui
provide
和inject
绑定并非可响应的。咱们能够经过传递祖父级的实例this
或着使用observable
来使传递的数据是响应的。
// Grand <template> <div class="grand"> <p>我是祖父</p> <input type="text" v-model="msg" placeholder="输入祖父的消息"/> <Parent /> </div> </template> <script> import Parent from "./Parent"; export default { name: "Grand", provide() { return { // 利用函数 provide 返回对象 grandVm: this // 传递实例 }; }, ... data() { return { msg: "" }; } }; </script> // Child <template> <div class="child"> <p>我是子组件</p> <p>爷爷的实例信息: {{ grandVmMsg }}</p> </div> </template> <script> export default { name: "Child", inject: { grandVm: { default: () => { ""; } } }, computed: { grandVmMsg() { return this.grandVm.msg; } } }; </script>
效果以下:this
使用observable
让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
示例:
// Grand provide() { this.read = Vue.observable({ msg: '' }) return { read: this.read }; }
效果以下:
同级别组件相互间的通讯,咱们可使用EventBus
或着Vuex
。
简单的EventBus
示例:
// Bus.js import Vue from "vue"; export default new Vue(); // Child <div class="child"> <p>我是子组件一</p> <button @click="handleClick">组件一事件</button> </div> <script> import Bus from "./Bus"; export default { name: "Child", methods: { handleClick() { Bus.$emit("click", "嘿,老铁"); } } }; </script> // ChildOne <div class="child"> <p>我是子组件二</p> <p>兄弟叫我:{{ msg }}</p> </div> <script> import Bus from "./Bus"; export default { name: "ChildOne", data() { return { msg: "" }; }, mounted() { Bus.$on("click", msg => { this.msg = msg; }); } }; </script>
效果以下:
v-model
与sync
v-model
是咱们用ElementUI
常见的表单绑定值方式;能够直接修改子组件修改父组件传入的值,简化了咱们组件通讯的逻辑。
示例:
// ModelCom <div class="child"> <input type="text" @input="handleInput"> </div> <script> export default { name: "ModelSync", methods: { // 经过绑定表单input中的input事件,向上触发input事件来修改值 handleInput(e) { const value = e.target.value; this.$emit('input', value); } } }; </script> // Home <ModelSync v-model="msg"/>
效果以下:
sync
修饰符也能够是咱们的prop
进行双向绑定。
它须要咱们在子组件内触发this.$emit('update:prop', val)
事件
// ModelCom <input type="text" @input="handleChange"> ... props: ['value'], methods: { handleChange(e) { const value = e.target.value; // 触发更新 this.$emit('update:value', value); } } // Home <ModelSync :value.sync="syncMsg"/>
效果以下:
$children
与$parent
咱们能够在组件中经过当前的实例对象访问到组件的$children
和$parent
来找到各自组件的父级组件或子级组件实例。
示例:
// Child <div class="child"> <p>我是子组件</p> <p>来自父组件的msg: {{ msg }}</p> </div> ... <script> export default { name: "ChildParent", data() { return { value: '' } }, computed: { msg() { return this.$parent.value; } }, created() { console.log(this.$parent); } } // Parent <input v-model="value" />
经过在父组件中输入值能够看到子组件数据也同时更新了
$attrs
与$listeners
$attrs
能够经过 v-bind="$attrs"
将组件上的特新都(class 和 style 除外)传入内部组件;传入的值与inheritAttrs
的设置有关,一般封装高级组件。
当咱们inheritAttrs
设置 true
;组件渲染DOM时写在组件的特性会渲染上去;
$listeners
包含了父做用域中的 (不含 .native 修饰器的) v-on 事件监听器。它能够经过 v-on="$listeners" 传入内部组件。
具体详细可见文档
示例:
// Attr <div class="child"> <p>Attr</p> <p>这是$attrs:{{ placeholder }}</p> <p>这是$listeners:{{ test }}</p> <button @click="$listeners.click">监听了$listeners</button> </div> ... <script> export default { name: "AttrListen", inheritAttrs: true, props: { test: { type: String, default: '' } }, data() { return { placeholder: this.$attrs.placeholder } } }; </script> // Home <AttrListen placeholder="这是个attr" :test="value" v-bind="$attrs" v-on="$listeners" @click="handleListen"/>
效果以下:
经过封装函数来向上或向下派发事件
参考见Vue.js组件精讲
// emitter.js function broadcast(componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } }); } export default { methods: { dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } } };
经过封装函数来查找指定任意组件
参考见Vue.js组件精讲
// 由一个组件,向上找到最近的指定组件 function findComponentUpward (context, componentName) { let parent = context.$parent; let name = parent.$options.name; while (parent && (!name || [componentName].indexOf(name) < 0)) { parent = parent.$parent; if (parent) name = parent.$options.name; } return parent; } export { findComponentUpward }; // 由一个组件,向上找到全部的指定组件 function findComponentsUpward (context, componentName) { let parents = []; const parent = context.$parent; if (parent) { if (parent.$options.name === componentName) parents.push(parent); return parents.concat(findComponentsUpward(parent, componentName)); } else { return []; } } export { findComponentsUpward }; // 由一个组件,向下找到全部指定的组件 function findComponentsDownward (context, componentName) { return context.$children.reduce((components, child) => { if (child.$options.name === componentName) components.push(child); const foundChilds = findComponentsDownward(child, componentName); return components.concat(foundChilds); }, []); } export { findComponentsDownward }; // 由一个组件,找到指定组件的兄弟组件 function findBrothersComponents (context, componentName, exceptMe = true) { let res = context.$parent.$children.filter(item => { return item.$options.name === componentName; }); let index = res.findIndex(item => item._uid === context._uid); if (exceptMe) res.splice(index, 1); return res; } export { findBrothersComponents };
项目中组件的通讯方式大概经常使用的是上面几种方案,咱们能够经过不一样的方式来实现组件通讯,可是选择合适组件通讯方式可使咱们事半功倍。写的不当之处,望指正~