摘自:https://div.io/topic/1880,并加入一点自己实际练习的demo,记录下学习历程,并方便后续翻阅。
一、vue动态组件:is
关于is的妙用,参考另一篇自己写的demo文章:https://juejin.im/editor/drafts/5c863924e51d4561a0778dd5
二、递归组件
对于一些有规律的 dom 结构,我们可以通过递归方式来生成这个结构,在 vue 的模板中递归生成dom。
官方介绍:https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8
demo详见:https://github.com/elainema/elaine/tree/master/VUE/vue-components/src/views/component-communication和https://github.com/elainema/elaine/tree/master/VUE/vue-components/src/views/recursive-components
准备数据
首先为了使用递归组件需要准备一份数据,因为这次是生成一个菜单,所以准备一个菜单书数据,新建一个testdata.js 文件代码如下:
var demoData = [{
'id': '1',
'menuName': '基础管理',
'menuCode': '10',
'children': [{
'menuName': '用户管理',
'menuCode': '11'
},
{
'menuName': '角色管理',
'menuCode': '12',
'children': [{
'menuName': '管理员',
'menuCode': '121'
},
{
'menuName': 'CEO',
'menuCode': '122'
}]
},
{
'menuName': '权限管理',
'menuCode': '13'
}]
},
{
'id': '2',
'menuName': '商品管理',
'menuCode': ''
}];
export
default demoData;建立树形组件
现在建立树形组件,首先新建一个文件treeMenu,代码如下
<template>
<div>
<li>
<span @click="toggle">
<i v-if="hasChild"
class="icon"
v-bind:class="[open ? 'folder-open': 'folder' ]">
</i>
<i v-if="!hasChild" class="icon file-text"></i>
{{model.menuName}}
</span>
<ul v-show="open" v-if="hasChild">
<tree-menu
v-for="(item,index) in model.children"
v-bind:model="item" v-bind:key="index">
</tree-menu>
</ul>
</li>
</div>
</template>
<script>
export default {
name: "TreeMenu",
inheritAttrs:false,
props: ['model'],
data(){
return {
open:false
}
},
computed:{
hasChild(){
return this.model.children && this.model.children.length
}
},
methods:{
toggle(){
if(this.hasChild){
this.open = !this.open
}
}
}
</script>
<style>
ul { list-style: none; margin: 10px 0; }
li { padding: 3px 0; }
li > span { cursor: pointer; font-size: 14px; line-height: 20px; }
i.icon { display: inline-block; width: 20px; height: 20px; margin-right: 5px; background-repeat: no-repeat; vertical-align: middle; }
.icon.folder { background-image: url(/src/assets/folder.png); }
.icon.folder-open { background-image: url(/src/assets/folder-open.png); }
.icon.file-text { background-image: url(/src/assets/file-text.png); }
.tree-menu li { line-height: 1.5; }</style>上述代码中我们需要注意,这个组件必须含有 name 这个属性,因为没有 name 这个属性会造成控件自身不能调用自身,自身调用的时候最好有绑定 key ,因为这个 key 是唯一的标识,对于 vue 更新控件比较好.除非控件非常简单就不用 key.
三、自定义组件使用 v-model
我们知道,v-model是在表单类元素上进行双向绑定时使用的,比如:
<template>
<input type="text" v-model="data">
{{ data }}
</template>
<script>
export default {
data () {
return {
data: ''
}
}
}
</script>这时data就是双向绑定的,输入的内容会实时显示在页面上。在 Vue 1.x 中,自定义组件可以使用 props 的.sync双向绑定,比如:<my-component :data.sync="data"></my-component>在 Vue 2.x 中,可以直接在自定义组件上使用 v-model了,比如:
<my-component v-model="data"></my-component>在组件my-component中,通过this.$emit('input')就可以改变data的值了。
四、Vue 父子组件数据传递( inheritAttrs + $attrs + $listeners)
当我们在书写 vue 组件的时候,经常会用到数据传递;将父组件的数据传递给子组件,有时候也需要通过子组件去事件去触发父组件的事件;总结一下比较常用的三种解决办法:
-
通过
props的方式向子组件传递(父子组件) -
vuex进行状态管理(父子组件和非父子组件) -
父组件通过this.$refs[子组件ref] 可直接调用子组件的方法
// 父组件 <template> <child ref="child"></child> </template> <script> export default { name:'parent', mounted() { this.refs.child.childSubmit() } }</script> // 子组件 <template> <div> 这是一个子组件 </div> </template> <script> export default { name:'parent', methods:{ childSubmit() { alert("trigger") } } }</script>
inheritAttrs + $attrs + $listeners基本是大部分的公司或者项目都是用前面两种,我也不例外......初次看到第四种写法时甚至有些惊讶,原来还有这种写法,然后去API看才发现其实很早就有,只是没有仔细看文档......故整理一下,如果有需求可以尝试用一用,官方api地址(英文看到很懵,转中文文档先看。。。):https://cn.vuejs.org/v2/api/index.html#inheritAttrs
1、场景介绍
vue中一个比较令人烦恼的事情是属性只能从父组件传递给子组件。这也就意味着当你想向嵌套层级比较深组件数据传递,只能由父组件传递给子组件,子组件再传递给孙子组件...像下面这样:
<parent-component :passdown="passdown">
<child-component :passdown="passdown">
<grand-child-component :passdown="passdown">
....
就这样一层一层的往下传递passdown这个变量,最后才会用{{passdown}}。inheritAttrs + $attrs + $listeners
说实话,官方的解释开始看了几遍也是云里雾里的,忽略我的理解力。。。
2、实例:
父组件ComponentCommunication.vue
<template>
<div class="">
<MyTest :title="title" :massgae="massgae"></MyTest>
</div>
</template>
<script>
import MyTest from './MyTest.vue'
export default {
name:'componentCommunication',
data () {
return {
title:'定义在父组件的title',
massgae:'message111'
}
},
components:{ MyTest },
created:function(){ }}
</script>子组件MyTest.vue<template>
<section>
<div>这里是标题,父组件通过prop传递给子组件的:{{title}}
</div>
<div> 注意这里:this.$attrs{{$attrs}}
</div>
</section>
</template>
<script>
export default {
props:['title'],
data(){
return{ }
},
created:function(){
console.log(this.$attrs)//注意这里
}
}
</script>上边的代码,父组件传递了两个参数给子组件title和message,在子组件里只注册并使用了title,massgae并没有注册和使用,那么下浏览器渲染出来是什么样呢?如下图:

在Vue2.4.0,可以在组件定义中添加inheritAttrs:false,组件将不会把未被注册的props呈现为普通的HTML属性。
$attrs
关于Props 的一个令人讨厌的事情是,他们只能从父母传给孩子。 这意味着如果您有深入的嵌套组件,您需要传递数据,则必须将数据作为Props 绑定到每个中间组件中:
Props 来说还好,但是在一个真正的项目中,你可能会有许多更多的东西要传下去。您可以使用事件总线或Vuex来解决此问题,但Vue 2.4.0提供了另一种解决方案。 实际上,它是两个独立但相关的新功能的一部分:首先,一个称为
inheritAttrs的组件的标志,其次是一个实例属性$attrs。 在组件里我们可以通过其$attrs可以获取到父组件传递给子组件,但子组件没有使用和注册的数据。inheritAttrs
我们在子组件里设置 inheritAttrs: false // 默认是true, 渲染效果如下,可以看到父组件传递给组件的参数,但子组件未注册和使用的,不会作为普通html元素被渲染

$listeners
我的理解就是:子组件可以触发父组件的事件(不需要用什么那些麻烦的vuex或者一个空的 Vue实例作为事件总线,或者又是什么vm.$on )