Vue中少见但实用的技巧

439 阅读5分钟

     摘自: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-for 隐形条件终止递归. 

三、自定义组件使用 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 组件的时候,经常会用到数据传递;将父组件的数据传递给子组件,有时候也需要通过子组件去事件去触发父组件的事件;总结一下比较常用的三种解决办法:

  1. 通过 props 的方式向子组件传递(父子组件)

  2. vuex 进行状态管理(父子组件和非父子组件) 

  3. 父组件通过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>
    

后来在查看iview和element源码的时候发现还有第四种传递方式,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}}。

假如我们需要传递的属性只有1,2个还行,但是如果我们要传递的有几个或者10来个的情况,这会是什么样的场景,我们会在每个组件不停的props,每个必须写很多遍。有没有其它方便的写法?有,通过vuex的父子组件通信,的确这个是一个方法,但是还有其它的方法,这个就是我们要说的。通过inheritAttrs选项,以及实例属性$attrs

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并没有注册和使用,那么下浏览器渲染出来是什么样呢?如下图:



我们看到:子组件内未被注册的属性将作为普通html元素属性被渲染,如果想让属性能够向下传递,即使prop组件没有被使用,你也需要在组件上注册。这样做会使组件预期功能变得模糊不清,同时也难以维护组件,尤其是多层嵌套传递的场景。

在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 )