饮冰三年-人工智能-Vue-66 Vue组件化

上一篇:饮冰三年-人工智能-Vue-47 初识Vue

二、Vue组件化

1、Vue组件

组件(Component)是 Vue.js 最强大的功能之一。

组件可以扩展 HTML 元素,封装可重用的代码。

组件化思想

  • 组件化的思想让我们能够开发出一个个独立且可复用的小组件来构造我们的应用。
  • 每一个应用(功能)都可以抽象成一个组件树

2、组件的使用

2.1 原理

组件的使用分三步:

  • 创建组件构造器  调用const mycomponent = Vue.extend({template : ``})
  • 注册组件                  调用Vue.component(‘自己起的组件名’,mycomponent)
  • 使用组件                  在上方的template里使用

2.2 初始组件

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 
 4 <head>
 5   <meta charset="UTF-8">
 6   <title>Document</title>
 7   <link rel="stylesheet" href="style.css">
 8   <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
 9 
10 </head>
11 
12 <body>
13   <div id="app">
14     <!--3.使用组件-->
15     <my-cpn></my-cpn>
16     </br>
17     </br>
18     </br>
19     <my-cpn></my-cpn>
20     </br>
21     </br>
22     </br>
23     <my-cpn></my-cpn>
24   </div>
25   <script>
26     // 1.创建组件构造器对象
27     const cpnC = Vue.extend({
28       template: `
29       <div>
30         <h2>1:创建组件构造器  调用const mycomponent = Vue.extend({template : ""}) </h2>
31         <p>注意:这里可以使用特殊符号,支持字符串换行</p>
32         <h2>2:注册组件         调用Vue.component(‘自己起的组件名’,mycomponent)</h2>
33         <p>第一个参数是自己起的组件名,第二个参数是你在创建组件构造器的时候的const变量。</p>
34         <h2>3:使用组件 在上方的template里使用</div></h2>
35       </div>
36       `
37     }
38     )
39     // 2.注册组件
40     Vue.component('my-cpn', cpnC)
41 
42     const app = new Vue({
43       el: '#app'
44     })
45   </script>
46 
47 </body>
48 
49 </html>
demo1 

2.3 全局组件与局部组件 

当我们通过调用Vue.component()注册组件时,组件的注册是全局的 这意味着该组件可以在任意Vue示例下使用。

如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>

  <div id="app1">
    <!--3.使用组件-->
    <my-cpn></my-cpn>
    <my-cpn2></my-cpn2>
  </div>
  <div id="app2">
    <my-cpn></my-cpn>
    <my-cpn2></my-cpn2>
  </div>
  <script>
    // 1.创建组件构造器对象
    const cpnC = Vue.extend({
      template: `
      <div>
        <h2>当我们通过调用Vue.component()注册组件时,组件的注册是全局的 这意味着该组件可以在任意Vue示例下使用</h2>
      </div>
      `
    }
    )
    const cpnC2 = Vue.extend({
      template: `
      <div>
        <h2>如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件</h2>
      </div>
      `
    }
    )
    // 2.注册组件
    Vue.component('my-cpn', cpnC) //2-1 注册到了Vue下
    const app = new Vue({
      el: '#app1',
      //2-2 注册到了app下,那就只能在app中使用
      components: {
        'my-cpn2': cpnC2
      }
    })

    const app2 = new Vue({
      el: '#app2'
    })
  </script>

</body>

</html>
全局组件与局部组件

2.4 父组件与子组件 

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 
 4 <head>
 5   <meta charset="UTF-8">
 6   <title>Document</title>
 7   <link rel="stylesheet" href="style.css">
 8   <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
 9 </head>
10 
11 <body>
12 
13   <div id="app">
14     <!--3.使用组件-->
15     <my-cpn></my-cpn>
16     <child-cpn></child-cpn>
17   </div>
18   <script>
19      const childComponent = Vue.extend({
20       template: `
21       <div>
22         <h2>我是子组件</h2>
23         <p>子组件只能在父组件中被识别的,不能单独使用</p>
24         <p>由于是依序加载,所以子组件需要放在父组件之前声明</p>
25       </div>
26       `
27     }
28     )
29     // 1.创建组件构造器对象
30     const parentComponent = Vue.extend({
31       template: `
32       <div>
33         <h2>我是父组件</h2>
34         <child-cpn></child-cpn>
35       </div>
36       `,
37       components: {
38         'child-cpn': childComponent
39       }
40     }
41     )
42    
43     // 2.注册组件
44     Vue.component('my-cpn', parentComponent) //2-1 注册到了Vue下
45     const app = new Vue({
46       el: '#app'
47     })
48 
49   </script>
50 
51 </body>
52 
53 </html>
父组件与子组件

注意:

  • 子组件只能在父组件中被识别的,不能单独使用
  • 由于是依序加载,所以子组件需要放在父组件之前声明

2.5 注册组件语法糖

在上面注册组件的方式,可能会有些繁琐。 Vue为了简化这个过程,提供了注册的语法糖。

主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。

 

2.6 模板的分离写法

刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。

如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。

Vue提供了两种方案来定义HTML模块内容:

  1. 使用<script>标签
  2. 使用<template>标签

 

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>

  <div id="app1">
    <!--3.使用组件-->
    <my-cpn></my-cpn>
    <my-cpn2></my-cpn2>
  </div>
  <div id="app2">
    <my-cpn></my-cpn>
    <my-cpn2></my-cpn2>
  </div>
  <script type="text/x-template" id="myCpn">
    <div>
      <h2>当我们通过调用Vue.component()注册组件时,组件的注册是全局的 这意味着该组件可以在任意Vue示例下使用</h2>
    </div>
  </script>
  <script type="text/x-template" id="myCpn2">
    <div>
      <h2>如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件</h2>
    </div>
  </script>
  <script>
    // 2.注册组件
    Vue.component('my-cpn', {
      template: '#myCpn'
    }) //2-1 注册到了Vue下
    const app = new Vue({
      el: '#app1',
      //2-2 注册到了app下,那就只能在app中使用
      components: {
        'my-cpn2': {
          template: '#myCpn2'
        }
      }
    })
    const app2 = new Vue({
      el: '#app2'
    })
  </script>

</body>

</html>
1.使用script标签

 

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>

  <div id="app">
    <!--3.使用组件-->
    <my-cpn></my-cpn>
    <child-cpn></child-cpn>
  </div>
  <template id="Ftemplate">
    <div>
      <h2>我是父组件</h2>
      <child-cpn></child-cpn>
    </div>
  </template>
  <template id="Stemplate">
    <div>
      <h2>我是子组件</h2>
      <p>子组件只能在父组件中被识别的,不能单独使用</p>
      <p>由于是依序加载,所以子组件需要放在父组件之前声明</p>
    </div>
  </template>
  <script>
    // 2.注册组件
    Vue.component('my-cpn', {
      template: '#Ftemplate',
      components: {
        'child-cpn': {
          template: '#Stemplate'
        }
      }
    }) //2-1 注册到了Vue下
    const app = new Vue({
      el: '#app'
    })

  </script>

</body>

</html>
使用template标签

3、组件的数据交互

3.1 组件数据存放

组件是一个单独功能模块的封装: 这个模块有属于自己的HTML模板,也有属性自己的数据data。

组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)

只是这个data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>

  <div id="app1">
    <!--3.使用组件-->
    <my-cpn></my-cpn>
    <my-cpn2></my-cpn2>
  </div> 
  <template id="myCpn">
    <div>
      <h2>消息:{{message}}</h2>
    </div>
  </template>
  <script>
    // 2.注册组件
    Vue.component('my-cpn', {
      template: '#myCpn',
      data(){
        return{
            message:'我是组件中data方法返回的数据'
        }
      }
    }) 
    const app = new Vue({
      el: '#app1' ,
      data:{
        message:"我是Vue中的数据内容"
      }
    }) 
  </script>

</body>

</html>
组件中的数据源
  1. 组件中能不能直接访问Vue实例中的data,假设如果可以,数据都放在Vue示例中,Vue会变得非常臃肿
  2. 为什么data()必须是函数?原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。

3.2 组件数据传递

如何进行父子组件间的通信呢?

  • 通过props向子组件传递数据
  • 通过事件向父组件发送消息

  3.2.1 父级向子级传递

    在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的。

    在组件中,使用选项props来声明需要从父级接收到的数据。

    props的值有两种方式:

      • 方式一:字符串数组,数组中的字符串就是传递时的名称。
      • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值、是否必填等。

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>
  <div id="app1">
    <!--3.使用组件-->
    <myccpn :cmovies="movies"></myccpn>
  </div>
  <template id="myCpn">
    <div>
      <ul v-for="item in cmovies">
        <li>{{item}}</li>
      </ul>
    </div>
  </template>
  <script>
    const myccpn = {
      template: '#myCpn',
      props: ['cmovies']
    }
    const app = new Vue({
      el: '#app1',
      data: {
        movies: ["教父", "阿甘正传", "辛德勒名单", "这个杀手不太冷", "海上钢琴师"]
      },
      // 字面量 myccpn : {}
      components: {
        myccpn
      }
    })
  </script>

</body>

</html>
父组件向子组件传递数组
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>
  <div id="app1">
    <!--3.使用组件-->
    <myccpn :c-movies="movies"></myccpn>
  </div>
  <template id="myCpn">
    <div>
      <ul v-for="item in cMovies">
        <li>{{item}}</li>
      </ul>
    </div>
  </template>
  <script>
    const myccpn = {
      template: '#myCpn',
      props: ['cMovies']
    }
    const app = new Vue({
      el: '#app1',
      data: {
        movies: ["教父", "阿甘正传", "辛德勒名单", "这个杀手不太冷", "海上钢琴师"]
      },
      // 字面量 myccpn : {}
      components: {
        myccpn
      }
    })
  </script>

</body>

</html>
数据绑定时,驼峰命名的改写

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>
  <div id="app1">
    <myccpn :c-movies="movies" v-bind:title="title"></myccpn>
  </div>
  <template id="myCpn">
    <div>
      <h2>{{title}}</h2>
      <ul v-for="item in cMovies">
        <li>{{item}}</li>
      </ul>
    </div>
  </template>
  <script>
    const myccpn = {
      template: '#myCpn',
      props: ['title', 'cMovies']
    }
    const app = new Vue({
      el: '#app1',
      data: {
        title: "电影列表",
        movies: ["教父", "阿甘正传", "辛德勒名单", "这个杀手不太冷", "海上钢琴师"]
      },
      // 字面量 myccpn : {}
      components: {
        myccpn
      }
    })
  </script>

</body>

</html>
只能有一个正确的根节点

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>
  <div id="app1">
    <!--3.使用组件-->
    <myccpn v-bind:info="info"></myccpn>
  </div>
  <template id="myCpn">
    <div>
      <h2>{{info.name}}</h2>
      <ul v-for="item in cMovies">
        <li>{{item}}</li>
      </ul>
    </div>
  </template>
  <script>
    function Info(id, name) {
      this.id = id
      this.name = name
    }
    const myccpn = {
      template: '#myCpn',
      props: {
        info: {
          type: Info,
          required: true
        },
        cMovies: {
          type: Array,
          default:  ["暂无影片"]
        }
      }
    }
    const app = new Vue({
      el: '#app1',
      data: {
        info: new Info(1, "电影列表"),
        movies: ["教父", "阿甘正传", "辛德勒名单", "这个杀手不太冷", "海上钢琴师"]
      },
      // 字面量 myccpn : {}
      components: {
        myccpn
      }
    })
  </script>

</body>

</html>
注意数组和对象的默认值

 

注意

  1. props中如果数据名称用驼峰命名的话(props: ['cMovies']),绑定数据的时候,需要将大写字母改成-小写字母(<myccpn :c-movies="movies"></myccpn>)
  2. 父组件和子组件之间可以重名
  3. 组件模板应该包含一个根元素
  4. props中可以设置其他拓展功能,但是数组和对象的默认值设置需要注意

 3.2.2 子级向父级传递

    子组件传递数据或事件到父组件中,需要使用自定义事件来完成。    

    什么时候需要自定义事件呢?

      • 当子组件需要向父组件传递数据时,就要用到自定义事件了。
      • 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。

    自定义事件的流程:

      • 在子组件中,通过$emit()来触发事件。
      • 在父组件中,通过v-on来监听子组件事件。

 

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>
  <div id="app1">
    <myccpn @item-click="cpnClick"></myccpn>
    <p>当前选择的是:{{curSelect}}</p>
  </div>
  
  <template id="myCpn">
    <div>
      <button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
    </div>
  </template>
  <script>
    function Info(id, name) {
      this.id = id
      this.name = name
    }
    const myccpn = {
      template: '#myCpn',
      data() {
        return {
          categories: [
            { id: 'aaa', name: '热门推荐' },
            { id: 'bbb', name: '手机数码' },
            { id: 'ccc', name: '家用家电' },
            { id: 'ddd', name: '电脑办公' }
          ]
        }
      },
      methods: {
        btnClick(item) {
          // 发射事件:自定义事件
          this.$emit('item-click', item)
        }
      }
    }
    const app = new Vue({
      el: '#app1',
      data:{curSelect:""},
      // 字面量 myccpn : {}
      components: {
        myccpn
      },
      methods: {
        cpnClick(item) {
          this.curSelect = item.name
        }
      }
    })
  </script>

</body>

</html>
子组件通过注册的方式向父组件传值

3.3 组件之间访问 

  有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。

  1. 父组件访问子组件:使用$children或$refs
  2. 子组件访问父组件:使用$parent
  3. 子组件访问根组件:使用$root
  4. 非父子组件:中央事件总线

  3.3.1 父组件访问子组件

  我们先来看下$children的访问

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>
  <div id="app1" style="border: 1px solid red;">
    <h1> Vue 中包含父组件 </h1>
    <parent_cpn></parent_cpn>
  </div>

  <template id="parent_cpn">
    <div style="border: 1px solid yellow;">
      <h2> 父组件 中包含两个子组件 和 一个按钮 </h2>
      <children1_cpn></children1_cpn>
      <children2_cpn></children2_cpn>
      <button @click="parent_cpn_click">按钮</button>
    </div>
  </template>

  <template id="children1_cpn">
    <div style="border: 1px solid blue;">
      <h3> 子组件1 中包含 各自的方法 </h3>
    </div>
  </template>

  <template id="children2_cpn" style="border: 1px solid blue;">
    <div style="border: 1px solid blue;">
      <h3> 子组件2 中包含 各自的方法 </h3>
    </div>
  </template>

  <script>
    // 子组件1
    const children1_cpn = {
      template: '#children1_cpn',
      data() {
        return { name: "子组件1" }
      },
      methods: {
        children_cpn_click(item) {
          return console.log("子组件1的cpnClick方法!")
        }
      }
    }
    // 子组件2
    const children2_cpn = {
      template: '#children2_cpn',
      data() {
        return { name: "子组件2" }
      },
      methods: {
        children_cpn_click(item) {
          return console.log("子组件2的cpnClick方法!")
        }
      }
    }
    // 父组件
    const parent_cpn = {
      template: '#parent_cpn',
      components: {
        children1_cpn,
        children2_cpn,
      },
      methods: {
        parent_cpn_click(item) {
          for (item of this.$children) {
            console.log(item)
            console.log(item.children_cpn_click()) //调用子组件的方法 
          }
        }
      }
    }
    // Vue 中包含父组件
    const app = new Vue({
      el: '#app1',
      data: { curSelect: "" },
      components: {
        parent_cpn
      }
    })
  </script>

</body>

</html>
通过children关键字父组件访问子组件

$children的缺陷:

    1. 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
    2. 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
    3. 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs

  我们接下来看下$refs的访问

    • $refs和ref指令通常是一起使用的。
    • 首先,我们通过ref给某一个子组件绑定一个特定的ID。
    • 其次,通过this.$refs.ID就可以访问到该组件了。

       

  

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>
  <div id="app1" style="border: 1px solid red;">
    <h1> Vue 中包含父组件 </h1>
    <parent_cpn></parent_cpn>
  </div>

  <template id="parent_cpn">
    <div style="border: 1px solid yellow;">
      <h2> 父组件 中包含两个子组件 和 一个按钮 </h2>
      <children1_cpn ref="child1"></children1_cpn>
      <children2_cpn ref="child2"></children2_cpn>
      <button @click="parent_cpn_click">按钮</button>
    </div>
  </template>

  <template id="children1_cpn">
    <div style="border: 1px solid blue;">
      <h3> 子组件1 中包含 各自的方法 </h3>
    </div>
  </template>

  <template id="children2_cpn" style="border: 1px solid blue;">
    <div style="border: 1px solid blue;">
      <h3> 子组件2 中包含 各自的方法 </h3>
    </div>
  </template>

  <script>
    // 子组件1
    const children1_cpn = {
      template: '#children1_cpn',
      data() {
        return { name: "子组件1" }
      },
      methods: {
        children_cpn_click(item) {
          return console.log("子组件1的cpnClick方法!")
        }
      }
    }
    // 子组件2
    const children2_cpn = {
      template: '#children2_cpn',
      data() {
        return { name: "子组件2" }
      },
      methods: {
        children_cpn_click(item) {
          return console.log("子组件2的cpnClick方法!")
        }
      }
    }
    // 父组件
    const parent_cpn = {
      template: '#parent_cpn',
      components: {
        children1_cpn,
        children2_cpn,
      },
      methods: {
        parent_cpn_click(item) {
          // for (item of this.$children) {
          //   console.log(item)
          //   console.log(item.children_cpn_click()) //调用子组件的方法 
          // }
          console.log(this.$refs.child1.name) 
          console.log(this.$refs.child2.children_cpn_click()) 
        }
      }
    }
    // Vue 中包含父组件
    const app = new Vue({
      el: '#app1',
      data: { curSelect: "" },
      components: {
        parent_cpn
      }
    })
  </script>

</body>

</html>
父组件通过refs关键字访问子组件

  3.3.2 子组件访问父组件与根组件

  如果我们想在子组件中直接访问父组件,可以通过$parent

  注意事项:

    • 尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做。
    • 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
    • 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
    • 另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>
  <div id="app1" style="border: 1px solid red;">
    <h1> Vue 中包含父组件 </h1>
    <parent_cpn></parent_cpn>
  </div>

  <template id="parent_cpn">
    <div style="border: 1px solid yellow;">
      <h2> 父组件 中包含两个子组件 </h2>
      <children1_cpn ref="child1"></children1_cpn>
      <children2_cpn ref="child2"></children2_cpn>
    </div>
  </template>

  <template id="children1_cpn">
    <div style="border: 1px solid blue;">
      <h3> 子组件1 中包含  一个按钮 </h3>
      <button @click="children_cpn_click">按钮</button>
    </div>
  </template>

  <template id="children2_cpn" style="border: 1px solid blue;">
    <div style="border: 1px solid blue;">
      <h3> 子组件2 中包含  一个按钮 </h3>
      <button @click="children_cpn_click">按钮</button>
    </div>
  </template>

  <script>
    // 子组件1
    const children1_cpn = {
      template: '#children1_cpn',
      data() {
        return { name: "子组件1" }
      },
      methods: {
        children_cpn_click(item) {
          console.log(this.$root.name) //访问根组件data
        }
      }
    }
    // 子组件2
    const children2_cpn = {
      template: '#children2_cpn',
      data() {
        return { name: "子组件2" }
      },
      methods: {
        children_cpn_click(item) {
          console.log(this.$parent.name) //访问父组件data
          console.log(this.$parent.parent_cpn_click()) //访问父组件data
        }
      }
    }
    // 父组件
    const parent_cpn = {
      template: '#parent_cpn',
      components: {
        children1_cpn,
        children2_cpn,
      },
      data() {
        return { name: "我是父组件" }
      },
      methods: {
        parent_cpn_click(item) {
          console.log("父组件的cpnClick方法!")
        }
      }
    }
    // Vue 中包含父组件
    const app = new Vue({
      el: '#app1',
      data: { name: "我是根组件名称" },
      components: {
        parent_cpn
      }
    })
  </script>

</body>

</html>
子组件访问根组件

  3.3.3 非父子组件访问

  刚才我们讨论的都是父子组件间的通信,那如果是非父子关系呢?

  非父子组件关系包括多个层级的组件,也包括兄弟组件的关系。

   在Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。 但是这种方案和直接使用Vuex的状态管理方案还是逊色很多。 并且Vuex提供了更多好用的功能,所以这里我们暂且不讨论这种方案,后续我们专门学习Vuex的状态管理。

3.4 插槽

  组件的插槽:

  • 组件的插槽也是为了让我们封装的组件更加具有扩展性。
  • 让使用者可以决定组件内部的一些内容到底展示什么。
  • 其作用类似于 占位符

  如何封装合适呢?

  • 抽取共性,保留不同。 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
  • 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。 是搜索框,还是文字,还是菜单。由调用者自己来决定。
  • 其核心思想是:求同存异

3.4.1 初使用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>

  <div id="app">
    <mycpn><p>我可以是P标签</p></mycpn>
    <mycpn><button>我可以是按钮</button></mycpn>
  </div>
  <template id="myCpn">
    <div>
      <h2>插槽-开始</h2>
      <slot></slot>
      <h2>插槽-结束</h2>
    </div>
  </template>
  <script>
    const mycpn = {
      template: '#myCpn',
      data() {
        return {
          message: 'Hello,'
        }
      }
    }
    const app = new Vue({
      el: '#app',
      data: {
        message: "Hello,Vue"
      },
      components: {
        mycpn,
      }
    })
  </script>

</body>

</html>
slot初使用

3.4.2 具名插槽

当子组件的功能复杂时,子组件的插槽可能并非是一个。

  • 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
  • 那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
  • 这个时候,我们就需要给插槽起一个名字

如何使用具名插槽呢?

  • 非常简单,只要给slot元素一个name属性即可 <slot name='myslot'></slot>

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>

  <div id="app">
    <mycpn></mycpn>
    <mycpn>
      <button slot="left">我是左边按钮</button>
      <p slot="center">我是中间P标签</p>
      <input slot="right" value="我是右边文本框"></input>
    </mycpn>
  </div>
  <template id="myCpn">
    <div>
      <h2>插槽-开始</h2>
      <slot name="left">默认值(左)</slot>
      <slot name="center">默认值(中)</slot>
      <slot name="right">默认值(右)</slot>
      <h2>插槽-结束</h2>
    </div>
  </template>
  <script>
    const mycpn = {
      template: '#myCpn',
      data() {
        return {
          message: 'Hello,'
        }
      }
    }
    const app = new Vue({
      el: '#app',
      data: {
        message: "Hello,Vue"
      },
      components: {
        mycpn,
      }
    })
  </script>

</body>

</html>
具名插槽

3.4.3 作用域插槽

我们需要先理解一个概念:编译作用域

我们来考虑下面的代码是否最终是可以渲染出来的:

  <my-cpn v-show="isShow"></my-cpn>中,我们使用了isShow属性。 isShow属性包含在组件中,也包含在Vue实例中。

答案:最终可以渲染出来,也就是使用的是Vue实例的属性。

原因分析:

  • 官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
  • 而我们在使用<my-cpn v-show="isShow"></my-cpn>的时候,整个组件的使用过程是相当于在父组件中出现的。
  • 那么他的作用域就是父组件,使用的属性也是属于父组件的属性。
  • 因此,isShow使用的是Vue实例中的属性,而不是子组件的属性。

父组件替换插槽的标签,但是数据或内容由子组件来提供。

我们先提一个需求:

  • 子组件中包括一组数据,比如:pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']
  • 需要在多个界面进行展示: 某些界面是以水平方向一一展示的, 某些界面是以列表形式展示的, 某些界面直接展示一个数组
  • 内容在子组件,希望父组件告诉我们如何展示,怎么办呢? 利用slot作用域插槽就可以了

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.13/dist/vue.js"></script>
</head>

<body>

  <div id="app">
    <mycpn>
      <template v-slot="pLanguage">
        <ul>
          <li v-for="(item, index) in pLanguage.data"> {{ item }} </li>
        </ul>
      </template>
    </mycpn>
    <mycpn>
      <template v-slot="pLanguage">
        <span >{{pLanguage.data.join('----')}}</span>
      </template>
    </mycpn>
  </div>
  <template id="myCpn">
    <div>
      <slot :data="pLanguage"></slot>
    </div>
  </template>
  <script>
    const mycpn = {
      template: '#myCpn',
      data() {
        return {
          pLanguage: ['Java', 'C#', 'Python', 'Go', 'JavaScript']
        }
      }
    }
    const app = new Vue({
      el: '#app',
      data: {
        isShow: true
      },
      components: {
        mycpn,
      }
    })
  </script>

</body>

</html>
作用域插槽

 下一篇: 饮冰三年-人工智能-Vue-67 Webpack

原文地址:https://www.cnblogs.com/YK2012/p/14878949.html