侧边栏壁纸
博主头像
soulballad博主等级

技术文章记录及总结

  • 累计撰写 169 篇文章
  • 累计创建 26 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录
Vue

3.组件化开发

soulballad
2022-01-18 / 0 评论 / 0 点赞 / 35 阅读 / 4,764 字
温馨提示:
本文最后更新于 2022-05-30,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. 认识组件化

1.1 什么事组件化

  • 人面对复杂问题的处理方式:
    • 任何一个人处理信息的逻辑能力都是有限的
    • 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。
    • 但是,我们人有一种天生的能力,就是将问题进行拆解。
    • 如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。
  • 组件化也是类似的思想:
    • 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
    • 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

1.2 Vue组件化思想

  • 组件化是Vue.js中的重要思想

    • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
    • 任何的应用都会被抽象成一颗组件树。 -
    • 20220117220456
  • 组件化思想的应用:

    • 有了组件化的思想,我们在之后的开发中就要充分的利用它。
    • 尽可能的将页面拆分成一个个小的、可复用的组件。
    • 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
  • 所以,组件是Vue开发中,非常重要的一个篇章,要认真学习。

2. 注册组件

2.1 注册组件的基本步骤

  • 组件的使用分成三个步骤:
    • 创建组件构造器
    • 注册组件
    • 使用组件。
  • 我们来看看通过代码如何注册组件
  • 查看运行结果:
    • 和直接使用一个div看起来并没有什么区别。
    • 但是我们可以设想,如果很多地方都要显示这样的信息,我们是不是就可以直接使用来完成呢?
      20220117221149

2.2 组件注册步骤解析

  • 这里的步骤都代表什么含义呢?
  1. Vue.extend():
    • 调用Vue.extend()创建的是一个组件构造器。
    • 通常在创建组件构造器时,传入template代表我们自定义组件的模板。
    • 该模板就是在使用到组件的地方,要显示的HTML代码。
    • 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
  2. Vue.component():
    • 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
    • 所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
  3. 组件必须挂载在某个Vue实例下,否则它不会生效。(见下页)
    • 我们来看下面我使用了三次
    • 而第三次其实并没有生效:

3. 组件其他补充

3.1 全局组件和局部组件

  • 当我们通过调用Vue.component()注册组件时,组件的注册是全局的
    • 这意味着该组件可以在任意Vue示例下使用
  • 如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

20220117221903

3.2 父组件和子组件

  • 在前面我们看到了组件树:

    • 组件和组件之间存在层级关系
    • 而其中一种非常重要的关系就是父子组件的关系
  • 我们来看通过代码如何组成的这种层级关系:

    <div id="app">
      <cpn2></cpn2>
      <!--<cpn1></cpn1>-->
    </div>
    
    <script src="../js/vue.js"></script>
    <script>
      // 1.创建第一个组件构造器(子组件)
      const cpnC1 = Vue.extend({
        template: `
          <div>
            <h2>我是标题1</h2>
            <p>我是内容, 哈哈哈哈</p>
          </div>
        `
      })
    
      // 2.创建第二个组件构造器(父组件)
      const cpnC2 = Vue.extend({
        template: `
          <div>
            <h2>我是标题2</h2>
            <p>我是内容, 呵呵呵呵</p>
            <cpn1></cpn1>
          </div>
        `,
        components: {
          cpn1: cpnC1
        }
      })
    
      // root组件
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        },
        components: {
          cpn2: cpnC2
        }
      })
    </script>
    
  • 父子组件错误用法:以子标签的形式在Vue实例中使用

    • 因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
    • 该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
    • 是只能在父组件中被识别的。
    • 类似这种用法,是会被浏览器忽略的。

3.3 注册组件语法糖

  • 在上面注册组件的方式,可能会有些繁琐。
    • Vue为了简化这个过程,提供了注册的语法糖。
    • 主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
  • 语法糖注册全局组件和局部组件
<div id="app">
  <cpn1></cpn1>
  <cpn2></cpn2>
</div>

<script src="../js/vue.js"></script>
<script>
  // 1.全局组件注册的语法糖
  // 1.创建组件构造器
  // const cpn1 = Vue.extend()

  // 2.注册组件
  Vue.component('cpn1', {
    template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容, 哈哈哈哈</p>
      </div>
    `
  })

  // 2.注册局部组件的语法糖
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      'cpn2': {
        template: `
          <div>
            <h2>我是标题2</h2>
            <p>我是内容, 呵呵呵</p>
          </div>
    `
      }
    }
  })
</script>

3.3 模板的分离写法

  • 刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。
  • 如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
  • Vue提供了两种方案来定义HTML模块内容:
    • 使用<script>标签
    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
    </div>
    
    <!--1.script标签, 注意:类型必须是text/x-template-->
    <script type="text/x-template" id="cpn">
    <div>
      <h2>我是标题</h2>
      <p>我是内容,哈哈哈</p>
    </div>
    </script>
    
    <script src="../js/vue.js"></script>
    <script>
    
      // 1.注册一个全局组件
      Vue.component('cpn', {
        template: '#cpn'
      })
    
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        }
      })
    </script>
    
    • 使用<template>标签
    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
    </div>
    
    <!--2.template标签-->
    <template id="cpn">
      <div>
        <h2>我是标题</h2>
        <p>我是内容,呵呵呵</p>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
    
      // 1.注册一个全局组件
      Vue.component('cpn', {
        template: '#cpn'
      })
    
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        }
      })
    </script>
    

4. 组件数据存放

4.1 组件不能访问Vue实例数据

  • 我们发现组件不能访问Vue实例数据,而且即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变的非常臃肿。
  • 结论:Vue组件应该有自己保存数据的地方

4.2 组件数据的存放

  • 组件自己的数据存放在哪里呢?
    • 组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
    • 只是这个data属性必须是一个函数
    • 而且这个函数返回一个对象,对象内部保存着数据
<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>

<!--2.template标签-->
<template id="cpn">
  <div>
    <h2>{{title}}</h2>
    <p>我是内容,呵呵呵</p>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  // 1.注册一个全局组件
  Vue.component('cpn', {
    template: '#cpn',
    data() {
      return {
        title: 'abc'
      }
    }
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      // title: '我是标题'
    }
  })
</script>

4.3 为什么是一个函数

  • 首先,如果不是一个函数,Vue直接就会报错。
  • 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。

5. 父子组件通信

  • 在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的。
  • 但是,在开发中,往往一些数据确实需要从上层传递到下层:
    • 比如在一个页面中,我们从服务器请求到了很多的数据。
    • 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
    • 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
  • 如何进行父子组件间的通信呢?Vue官方提到
    • 通过props向子组件传递数据
    • 通过事件向父组件发送消息

20220117223534

真实的开发中,Vue实例与子组件的通信和父组件与子组件的通信过程是一样的

6. 父级向子级传递

6.1 props基本用法

  • 在组件中,使用选项props来声明需要从父级接收到的数据。
  • props的值有两种方式:
    • 方式一:字符串数组,数组中的字符串就是传递时的名称。
    • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
  • 我们先来看一个最简单的props传递:
<div id="app">
  <cpn :c-info="info" :child-my-message="message" v-bind:class></cpn>
</div>

<template id="cpn">
  <div>
    <h2>{{cInfo}}</h2>
    <h2>{{childMyMessage}}</h2>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const cpn = {
    template: '#cpn',
    props: {
      cInfo: {
        type: Object,
        default() {
          return {}
        }
      },
      childMyMessage: {
        type: String,
        default: ''
      }
    }
  }

  const app = new Vue({
    el: '#app',
    data: {
      info: {
        name: 'why',
        age: 18,
        height: 1.88
      },
      message: 'aaaaaa'
    },
    components: {
      cpn
    }
  })
</script>

6.2 props数据验证

  • 在前面,我们的props选项是使用一个数组。
  • 我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。
  • 验证都支持哪些数据类型呢?
    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol
  • 当我们有自定义构造函数时,验证也支持自定义的类型

20220117223415

7. 子级向父级传递

7.1 子级向父级传递

  • props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
  • 我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
  • 什么时候需要自定义事件呢?
    • 当子组件需要向父组件传递数据时,就要用到自定义事件了。
    • 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
  • 自定义事件的流程:
    • 在子组件中,通过$emit()来触发事件。
    • 在父组件中,通过v-on来监听子组件事件。
  • 我们来看一个简单的例子:
    • 我们之前做过一个两个按钮+1和-1,点击后修改counter。
    • 我们整个操作的过程还是在子组件中完成,但是之后的展示交给父组件。
    • 这样,我们就需要将子组件中的counter,传给父组件的某个属性,比如total。
<!--父组件模板-->
<div id="app">
  <cpn @item-click="cpnClick"></cpn>
</div>

<!--子组件模板-->
<template id="cpn">
  <div>
    <button v-for="item in categories"
            @click="btnClick(item)">
      {{item.name}}
    </button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  // 1.子组件
  const cpn = {
    template: '#cpn',
    data() {
      return {
        categories: [
          {id: 'aaa', name: '热门推荐'},
          {id: 'bbb', name: '手机数码'},
          {id: 'ccc', name: '家用家电'},
          {id: 'ddd', name: '电脑办公'},
        ]
      }
    },
    methods: {
      btnClick(item) {
        // 发射事件: 自定义事件
        this.$emit('item-click', item)
      }
    }
  }

  // 2.父组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn
    },
    methods: {
      cpnClick(item) {
        console.log('cpnClick', item);
      }
    }
  })
</script>

7.2 自定义事件

20220117223806

8. 父子组件的访问

8.1 访问方式 $children

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

    • 父组件访问子组件:使用$children或$refs
    • 子组件访问父组件:使用$parent
  • 我们先来看下$children的访问

    • this.$children是一个数组类型,它包含所有子组件对象。
    • 我们这里通过一个遍历,取出所有子组件的message状态。
<div id="app">
  <cpn></cpn>
  <cpn></cpn>

  <my-cpn></my-cpn>
  <y-cpn></y-cpn>

  <cpn ref="aaa"></cpn>
  <button @click="btnClick">按钮</button>
</div>

<template id="cpn">
  <div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    methods: {
      btnClick() {
        // 1.$children
        console.log(this.$children);
        for (let c of this.$children) {
          console.log(c.name);
          c.showMessage();
        }
        console.log(this.$children[3].name);
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: '我是子组件的name'
          }
        },
        methods: {
          showMessage() {
            console.log('showMessage');
          }
        }
      },
    }
  })
</script>

8.2访问方式 $refs

  • $children的缺陷:
    • 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
    • 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
    • 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
  • $refs的使用:
    • $refs和ref指令通常是一起使用的。
    • 首先,我们通过ref给某一个子组件绑定一个特定的ID。
    • 其次,通过this.$refs.ID就可以访问到该组件了。
<div id="app">
  <cpn></cpn>
  <cpn></cpn>

  <my-cpn></my-cpn>
  <y-cpn></y-cpn>

  <cpn ref="aaa"></cpn>
  <button @click="btnClick">按钮</button>
</div>

<template id="cpn">
  <div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    methods: {
      btnClick() {
        // 2.$refs => 对象类型, 默认是一个空的对象 ref='bbb'
        console.log(this.$refs.aaa.name);
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: '我是子组件的name'
          }
        },
        methods: {
          showMessage() {
            console.log('showMessage');
          }
        }
      },
    }
  })
</script>

8.3 访问方式 $parent

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

<template id="cpn">
  <div>
    <h2>我是cpn组件</h2>
    <ccpn></ccpn>
  </div>
</template>

<template id="ccpn">
  <div>
    <h2>我是子组件</h2>
    <button @click="btnClick">按钮</button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: '我是cpn组件的name'
          }
        },
        components: {
          ccpn: {
            template: '#ccpn',
            methods: {
              btnClick() {
                // 1.访问父组件$parent
                // console.log(this.$parent);
                // console.log(this.$parent.name);

                // 2.访问根组件$root
                console.log(this.$root);
                console.log(this.$root.message);
              }
            }
          }
        }
      }
    }
  })
</script>

8.4 非父子组件通信

  • 刚才我们讨论的都是父子组件间的通信,那如果是非父子关系呢?
    • 非父子组件关系包括多个层级的组件,也包括兄弟组件的关系。
  • 在Vue1.x的时候,可以通过$dispatch和$broadcast完成
    • $dispatch用于向上级派发事件
    • $broadcast用于向下级广播事件
    • 但是在Vue2.x都被取消了
  • 在Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。
    • 但是这种方案和直接使用Vuex的状态管理方案还是逊色很多。
    • 并且Vuex提供了更多好用的功能,所以这里我们暂且不讨论这种方案,后续我们专门学习Vuex的状态管理。

9. 插槽slot

0

评论区