zoukankan      html  css  js  c++  java
  • vue.js学习之组件(下篇)

     本文的Demo和源代码已放到GitHub,如果您觉得本篇内容不错,请点个赞,或在GitHub上加个星星!

    https://github.com/zwl-jasmine95/Vue_test

     以下所有知识都是基于vue.js 2.0版本


    一、组件编译作用域

    <child-component> {{ message }}</child-component>

    message 应该绑定到父组件的数据,组件作用域简单地说是:

    父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。

    组件的模板是在其作用域内编译的,那么组件选项对象中的数据也应该是在组件模板中使用的。

     1     <div id="component-demo">
     2         <!--  #component-demo是Vue实例挂载的元素,应该在挂载元素范围内使用组件-->
     3         <hello-component></hello-component>
     4     </div>
     5 
     6 <script type="text/javascript">
     7     Vue.component('hello-component',{
     8         template:'<h1>hello component!</h1>'
     9     });
    10     var vm = new Vue({
    11         el:'#component-demo'
    12     });
    13 
    14 </script>

    在创建一个Vue实例时,除了将它挂载到某个HTML元素下,还要编译组件,将组件转换为HTML片段。
    除此之外,Vue实例还会识别其所挂载的元素下的<hello-component>标签,然后将<hello-component>标签替换为HTML片段。

    实际上浏览器仍然是不理解<hello-component>标签的,

    组件在使用前,经过编译已经被转换为HTML片段了,组件是有一个作用域的,那么组件的作用域可以将它理解为组件模板包含的HTML片段,组件模板内容之外就不是组件的作用域了。

    例如,hello-component组件的作用域只是下面这个小片段:

    通俗地讲,在子组件中定义的数据,只能用在子组件的模板。在父组件中定义的数据,只能用在父组件的模板。如果父组件的数据要在子组件中使用,则需要子组件定义props。


    二、使用slot分发内容

     1、什么是“内容分发”?

    在使用组件时,往往会这样:

    <app>
      <app-header></app-header>
      <app-footer></app-footer>
    </app>

    注意两点:

    1. <app> 组件不知道它会收到什么内容。这是由使用 <app> 的父组件决定的。

    2. <app> 组件很可能有它自己的模版。

    为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发。Vue.js 实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的 <slot> 元素作为原始内容的插槽。

    2、单个slot

     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>单个slot</title>
     6     <script type="text/javascript" src="../lib/js/vue.js"></script>
     7 </head>
     8 <body>
     9     <div id="demo">
    10         <h1>我是父组件</h1>
    11         <my-component>
    12             <p>这是初始内容1</p>
    13             <p>这是初始内容2</p>
    14         </my-component>
    15     </div>
    16 
    17     <template id="myComponent">
    18         <div>
    19             <h1>我是子组件的标题</h1>
    20             <slot>没有分发内容的时候才会显示</slot>
    21         </div>
    22     </template>
    23 
    24     <script type="text/javascript">
    25 
    26         Vue.component('my-component',{
    27             template:'#myComponent'
    28         });
    29 
    30         var vm = new Vue({
    31             el:'#demo'
    32         });
    33     </script>
    34 
    35 </body>
    36 </html>
    View Code

    结果:

     除非子组件模板包含至少一个 <slot> 插口,否则父组件的内容将会被丢弃(其他情况2)当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。

    最初在 <slot> 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。

    •  其他情况1:删除父组件模板中的内容

         

    •  其他情况2:删除子组件模板里的<slot>

    • 其他情况3:子组件里有多个<slot>

     

    (当然,这里有两个匿名<slot>会有警告,应该用特殊的属性 name 来配置如何分发内容。详见第三节 具名slot)

     3、具名slot

    <slot> 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。

    仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot,这些找不到匹配的内容片段将被抛弃。

     demo :

     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>具名slot</title>
     6     <script type="text/javascript" src="../lib/js/vue.js"></script>
     7 </head>
     8 <body>
     9     <div id="demo">
    10         <h1>我是父组件</h1>
    11         <my-component>
    12             <h1 slot="header">这里可能是一个页面标题</h1>
    13             <p>主要内容的一个段落。</p>
    14             <p>另一个主要段落。</p>
    15             <p slot="footer">这里有一些联系信息</p>
    16         </my-component>
    17     </div>
    18 
    19     <template id="myComponent">
    20         <div class="container">
    21             <header>
    22                 <slot name="header"></slot>
    23             </header>
    24             <main>
    25                 <slot>这里是匿名slot</slot>
    26             </main>
    27             <footer>
    28                 <slot name="footer"></slot>
    29             </footer>
    30         </div>
    31     </template>
    32 
    33     <script type="text/javascript">
    34 
    35         Vue.component('my-component',{
    36             template:'#myComponent'
    37         });
    38 
    39         var vm = new Vue({
    40             el:'#demo'
    41         });
    42     </script>
    43 </body>
    44 </html>
    View Code

     

     4、作用域插槽

    2.1.0新增

     作用域插槽是一种特殊类型的插槽,用作使用一个 (能够传递数据到) 可重用模板替换已渲染元素。

     1   <div class="parent">
     2         <child>
     3             <template scope="props">
     4                 <span>hello from parent</span>
     5                 <span>{{ props.text }}</span>
     6             </template>
     7         </child>
     8     </div>
     9 
    10     <template id="myComponent">
    11         <div class="child">
    12             <slot text="hello from child">没有分发内容的时候才会显示</slot>
    13         </div>
    14     </template>
    15 
    16     <script type="text/javascript">
    17         Vue.component('child',{
    18             template:'#myComponent'
    19         });
    20         
    21         var vm = new Vue({
    22             el:'.parent'
    23         });
    24     </script>

     在子组件中,只需将数据传递到插槽,就像你将 props 传递给组件一样

    在父级中,具有特殊属性 scope 的 <template> 元素必须存在,表示它是作用域插槽的模板。scope 的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象

     作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项。作用域插槽也可以是具名的。(线上demo

     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>作用域插槽-列表</title>
     6     <script type="text/javascript" src="../lib/js/vue.js"></script>
     7 </head>
     8 <body>
     9     <div class="parent">
    10         <my-list :items="items">
    11             <template slot="item" scope="props">
    12                 <li class="my-fancy-item">{{ props.text }}</li>
    13             </template>
    14         </my-list>
    15     </div>
    16 
    17     <template id="myComponent">
    18         <ul>
    19             <slot name="item" v-for="item in items" :text="item.text"></slot>
    20         </ul>
    21     </template>
    22 
    23     <script type="text/javascript">
    24         Vue.component('my-list',{
    25             template:'#myComponent',
    26             data:function () {
    27                 return {
    28                     items:[
    29                         {id:1,text:'列表1'},
    30                         {id:2,text:'列表2'},
    31                         {id:3,text:'列表3'},
    32                         {id:4,text:'列表4'}
    33                     ]
    34                 }
    35             }
    36         });
    37 
    38         var vm = new Vue({
    39             el:'.parent',
    40             data:{
    41                 items:[]
    42             }
    43         });
    44     </script>
    45 
    46 </body>
    47 </html>
    View Code

    (这里代码中删除两处对效果并没有什么影响)


    三、动态组件

     通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,我们让多个组件可以使用同一个挂载点,并动态切换:

    var vm = new Vue({
      el: '#example',
      data: {
        currentView: 'component1'   //默认选中的组件
      },
      components: {
        component1: { /* ... */ },
        component2: { /* ... */ },
        component13: { /* ... */ }
      }
    })
    <component v-bind:is="currentView">
      <!-- 组件在 vm.currentview 变化时改变! -->
    </component>

    也可以直接绑定到组件对象上:

    var Home = {
      template: '<p>Welcome home!</p>'
    }
    var vm = new Vue({
      el: '#example',
      data: {
        currentView: Home
      }
    })

    通过具体实例来说明:demo

     1   <div class="container">
     2         <!--导航栏-->
     3         <ul class="nav nav-pills">
     4             <li><a href="javascript:void(0)" @click="toggleTab(0)">{{tabText1}}</a></li>
     5             <li><a href="javascript:void(0)" @click="toggleTab(1)">{{tabText2}}</a></li>
     6             <li><a href="javascript:void(0)" @click="toggleTab(2)">{{tabText3}}</a></li>
     7         </ul>
     8         <!-- 点击导航后要切换的内容容器 -->
     9         <div class="content">
    10             <!-- 如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数 -->
    11             <keep-alive><component :is="currentView"></component></keep-alive>
    12         </div>
    13     </div>
    14 
    15     <!-- 点击导航后要切换的内容 -->
    16     <template id="tab-content1">
    17         <div>这是第一个选项卡的内容!</div>
    18     </template>
    19 
    20     <template id="tab-content2">
    21         <div>这是第二个选项卡的内容!</div>
    22     </template>
    23 
    24     <template id="tab-content3">
    25         <div>这是第三个选项卡的内容!</div>
    26     </template>
    27 
    28     <script type="text/javascript">
    29         //局部注册组件(选项卡内容)
    30         var tab1 = {
    31             template:'#tab-content1'
    32         };
    33         var tab2 = {
    34             template:'#tab-content2'
    35         };
    36         var tab3 = {
    37             template:'#tab-content3'
    38         };
    39         
    40         var vm = new Vue({
    41             el:'.container',
    42             data:{
    43                 tabText1:'选项卡1',
    44                 tabText2:'选项卡2',
    45                 tabText3:'选项卡3',
    46                 currentView:tab1
    47             },
    48             //注册局部组件
    49             components:{
    50                 tabComponent1:tab1,
    51                 tabComponent2:tab2,
    52                 tabComponent3:tab3
    53             },
    54             methods:{
    55                 toggleTab:function (i) {
    56                     var arr = ['tabComponent1','tabComponent2','tabComponent3'];
    57                     this.currentView = arr[i];
    58                 }
    59             }
    60         
    61         })
    62     </script>

    如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:

    <keep-alive>
      <component :is="currentView">
        <!-- 非活动组件将被缓存! -->
      </component>
    </keep-alive>

    四、子组件索引

     尽管有 props 和 events,但是有时仍然需要在 JavaScript 中直接访问子组件。为此可以使用 ref 为子组件指定一个索引 ID。例如:

     1 <div id="parent">
     2         <user-profile ref="profile"></user-profile>
     3     </div>
     4 
     5     <script type="text/javascript">
     6         Vue.component('user-profile',{
     7             template:'<p>{{message}}</p>',
     8             data:function () {
     9                 return {
    10                     message:'这里是子组件索引!'
    11                 }
    12             }
    13         });
    14 
    15         var parent = new Vue({
    16             el: '#parent'
    17         });
    18         // 访问子组件
    19         var child = parent.$refs.profile;
    20         console.log(child.$data);   //打印子组件的数据

    当 ref 和 v-for 一起使用时,ref 是一个数组,包含相应的子组件。

    $refs 只在组件渲染完成后才填充,并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案——应当避免在模版或计算属性中使用 $refs


    五、递归组件

    组件在它的模板内可以递归地调用自己,不过,只有当它有 name 选项时才可以

    name: 'unique-name-of-my-component'

    当你利用Vue.component全局注册了一个组件, 全局的ID作为组件的 name 选项,被自动设置.

    Vue.component('unique-name-of-my-component', {
      // ...
    })

    如果你不谨慎, 递归组件可能导致死循环。要确保递归调用有终止条件 (比如递归调用时使用 v-if 并让他最终返回 false)

    实例:依次输出123456789

    (1)定义一个组件模板,基本标签为<span>0</span>,然后调用该组件。并且将数值加1(如果加1之后不超过10)。注意这些操作一定要放在一个标签内,如下代码中的div,否则会报错。

    (2)定义父组件,并且传入初始count值

    (3)注册组件,并且定义v-if的成立条件

    效果图:

     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>递归组件</title>
     6     <script type="text/javascript" src="../lib/js/vue.js"></script>
     7 </head>
     8 <body>
     9 <div id="parent">
    10     <counter-component :count="0"></counter-component>
    11 </div>
    12 
    13 <template id="counterComponent">
    14     <div>
    15         <span>{{count}}</span>
    16         <counter-component :count="count+1" v-if="countNum">{{count}}</counter-component>
    17     </div>
    18 </template>
    19 
    20 <script type="text/javascript">
    21     Vue.component('counter-component',{
    22         name:'counter-component',
    23         template:'#counterComponent',
    24         props:['count'],
    25         computed:{
    26             countNum:function () {
    27                 return this.count < 10;
    28             }
    29         }
    30     });
    31 
    32     var parent = new Vue({
    33         el: '#parent'
    34     });
    35 
    36 </script>
    37 </body>
    38 </html>
    View Code

    六、组件间的循环使用

     假设有两个组件称为 A 和 B,模块系统看到它需要 A,但是首先 A 需要 B,但是 B 需要 A,而 A 需要 B,陷入了一个无限循环,因此不知道到底应该先解决哪个。如下:

    当使用Vue.component将这两个组件注册为全局组件的时候,框架会自动为你解决这个矛盾。
    然而,如果使用诸如Webpack或者Browserify之类的模块化管理工具来requiring/importing组件的话,就会报错了。

    要解决这个问题,我们需要在其中一个组件中 (比如 A) 告诉模块化管理系统,“A 虽然需要 B,但是不需要优先导入 B”

    在我们的例子中,我们选择在tree-folder 组件中来告诉模块化管理系统循环引用的组件间的处理优先级,我们知道引起矛盾的子组件是tree-folder-contents,所以我们在beforeCreate 生命周期钩子中去注册它:

    beforeCreate: function () {
      this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
    }

    七、组件综合案例-树形组件

     demo完成效果:

    步骤:

    1、首先在创建根实例时定义一组数据,在父组件上利用v-for指令循环数据,将每一项数据通过props传递给子组件(列表项li),并将数据的name值在li显示。

     HTML:

     1 <div class="container">
     2     <ul class="list-group">
     3         <tree-node v-for="item in items" :parent_node="item" :key="item.name"></tree-node>
     4     </ul>
     5 </div>
     6 <template id="treeNode">
     7     <li class="list-group-item">
     8         <div>
     9             {{parent_node.name}}
    10         </div>
    11     </li>
    12 </template>

    JS:

     1 Vue.component('tree-node',{
     2         name:'tree-node',
     3         template:'#treeNode',
     4         props:['parent_node']
     5     });
     6 
     7     var vm = new Vue({
     8         el:'.container',
     9         data:{
    10             items:[
    11                 {
    12                     name:'列表1'
    13                 },
    14                 {
    15                     name:'列表2'
    16                 },
    17                 {
    18                     name:'列表3'
    19                 }
    20             ]
    21         }
    22     })

    注意:

    key 的特殊属性主要用在 Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用key,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。

    有相同父元素的子元素必须有独特的key。重复的key会造成渲染错误。最常见的用例是结合 v-for。

    <ul>
      <li v-for="item in items" :key="item.id">...</li>
    </ul>

    2、给数据加一项data,用来存放二级列表数据;循环使用组件tree-node,并且将传递给v-for的列表项改为对应二级列表的列表项。

     1 var vm = new Vue({
     2         el:'.container',
     3         data:{
     4             items:[
     5                 {
     6                     name:'列表1',
     7                     data:[
     8                         {name:'列表1-1'},
     9                         {name:'列表1-2'},
    10                         {name:'列表1-3'}
    11                     ]
    12                 },
    13                 {
    14                     name:'列表2',
    15                     data:[
    16                         {name:'列表2-1'},
    17                         {name:'列表2-2'},
    18                         {name:'列表2-3'}
    19                     ]
    20                 },
    21                 {
    22                     name:'列表3',
    23                     data:[]
    24                 }
    25             ]
    26         }
    27     })
     1 <template id="treeNode">
     2     <li class="list-group-item">
     3         <div>
     4             {{parent_node.name}}
     5         </div>
     6         <ul>
     7             <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
     8         </ul>
     9     </li>
    10 </template>

    此时效果如下:

    3、给一级列表项数据添加open字段,用来显示列表是否展开;

    (我默认列表1展开,列表2和列表3不展开)

    4、给一级列表添加两个图标(open和close图标),用v-if来判断哪一个图标显示。两个图标的显示条件都是列表项存在第二级列表数据-data。

     1 <template id="treeNode">
     2     <li class="list-group-item">
     3         <div>
     4             <span v-if="!parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-close"></span>
     5             <span v-if="parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-open"></span>
     6             {{parent_node.name}}
     7         </div>
     8         <ul>
     9             <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
    10         </ul>
    11     </li>
    12 </template>

    此时效果为:

    5、用v-show和v-if给二级列表加上显示条件。当没有data数据的时候,二级列表不存在;当open字段为false的时候,二级列表不显示;

     1 <template id="treeNode">
     2         <li class="list-group-item">
     3             <div>
     4                 <span v-if="!parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-close"></span>
     5                 <span v-if="parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-open"></span>
     6                 {{parent_node.name}}
     7             </div>
     8 
     9             <ul v-if="parent_node.data" v-show="parent_node.open">
    10                 <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
    11             </ul>
    12 
    13         </li>
    14     </template>

    此时显示效果为:

    至此demo算是已经完成了,最后一步则是给一级列表加上点击事件。

    6、给列表项中的div加上点击事件


     本文的Demo和源代码已放到GitHub  https://github.com/zwl-jasmine95/Vue_test

    如果您觉得本篇内容不错,请点个赞,或在GitHub上加个星星!

  • 相关阅读:
    025-Cinder服务-->安装并配置一个本地存储节点(ISCSI)
    023-OpenStack 创建实例类型临时磁盘的讲解
    addClass+siblings+removeClass用意:
    SublimeText 改变 tab的距离
    正则表达式选取数值
    正则表达式用来根据某种匹配模式寻找字符串中的某些单词。
    hasOwnProperty()函数
    翻转字符串算法
    输入框禁用和启用
    什么是thinkphp
  • 原文地址:https://www.cnblogs.com/jasmine-95/p/7383707.html
Copyright © 2011-2022 走看看