组件化开发

9 篇文章 0 订阅

1 认识组件化

1.1 什么是组件化

人面对复杂问题的处理方式:
任何一个人处理信息的逻辑能力都是有限的。所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。
但是,我们人有一种天生的能力,就是将问题进行拆解。如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。
在这里插入图片描述

组件化也是类似的思想︰
如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

我们将一个完整的页面分成很多个组件。每个组件都用于实现页面的一个功能块。而每一个组件又可以进行细分。
在这里插入图片描述

3.1.2 Vue组件化思想

  • 组件化是Vue.js中的重要思想
    • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
    • 任何的应用都会被抽象成一棵组件树。
      在这里插入图片描述
  • 组件化思想的应用:
    • 有了组件化的思想,我们在之后的开发中就要充分的利用它
    • 尽可能的将页面拆分成一个个小的、可复用的组件。
    • 这样让我们的代码更加方便组织和管理,并且扩展性也更强。

2 组件化基础

注册组件的基本步骤
组件的使用分成三个步骤:

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

在这里插入图片描述

2.1 注册组件

2.1.1 注册的基本步骤

  <div id="app">
    <!-- 3.使用组件 -->
    <my-cpn></my-cpn>
    <my-cpn></my-cpn>
    <my-cpn></my-cpn>
    <my-cpn></my-cpn>
  </div>
  <script src="../js/vue.js"></script>
  <script>
    // ES6中新加了 ``包裹字符串,可以换行
    // 1.创建组件构造器对象
    const cpnConstructor = Vue.extend({
      template: `
        <div>
          <h2>标题</h2>
          <p>内容1</p>
          <p>内容2</p>
        </div>`
    })
    // 2.注册组件
    // 参数1:组件的标签名
    // 参数2:组件构造器对象
    Vue.component('my-cpn', cpnConstructor);

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

注册组件步骤解析
这里的步骤都代表什么含义呢?

1.Vue.extend() :

  • 调用Vue.extend()创建的是一个组件构造器。
  • 通常在创建组件构造器时,传入template代表我们自定义组件的模板。
  • 该模板就是在使用到组件的地方,要显示的HTML代码。
  • 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。

2.vue.component() :

  • 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
  • 所以需要传递两个参数:1、注册组件的标签名2、组件构造器

3.组件必须挂载在某个Vue实例下否则它不会生效。(见下页)

  • 我们来看下面我使用了三次
  • 而第三次其实并没有生效:
    在这里插入图片描述

2.1.2 全局和局部组件

全局组件:在Vue的实例之外将构造器注册为组件。
可以在多个Vue的实例下面使用

局部组件:在Vue的实例中,使用templates选项将构造器注册为组件。
只可以在注册为组件的实例下面使用。

  <div id="app">
    <!-- 3.使用组件 -->
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
  </div>
  <script src="../js/vue.js"></script>
  <script>
    // ES6中新加了 ``包裹字符串,可以换行
    // 1.创建组件构造器对象
    const cpnConstructor = Vue.extend({
      template: `
        <div>
          <h2>标题</h2>
          <p>内容1</p>
          <p>内容2</p>
        </div>`
    })

    const app = new Vue({
      el:'#app',
      components: {
        // 2.注册组件
        // cpn:使用组件时的标签名;cpnConstructor:组件构造器对象
        cpn: cpnConstructor
      }
    })
  </script>

在实际开发中,用的最多的是局部组件,一般只有一个Vue实例。

2.1.2 父组件和子组件

在前面我们看到了组件树:组件和组件之间存在层级关系,而其中一种非常重要的关系就是父子组件的关系。

我们来看通过代码如何组成的这种层级关系:

 <div id="app">
    <!-- 3.使用组件 -->
    <cpn2></cpn2>
    <!-- 不能以子标签的形式在Vue实例中使用,会报错 -->
    <!-- <cpn1></cpn1> -->
  </div>
  <script src="../js/vue.js"></script>
  <script>
    // 1.创建cpnC1构造器--子组件构造器
    const cpnC1 = Vue.extend({
      template: `
        <div>
          <h2>标题</h2>
          <p>内容1</p>
        </div>`
    })
    // 2.创建cpnC2构造器--父组件构造器
    const cpnC2 = Vue.extend({
    template: `
      <div>
        <h2>标题</h2>
        <p>内容2</p>
        <cpn1></cpn1>
      </div>
      `,
    // 在父组件构造器中,注册子组件
    components: {
      cpn1: cpnC1
    }

    })
    const app = new Vue({
      el:'#app',
      // 在Vue实例app中注册父组件
      components: {
        // cpn:使用组件时的标签名;cpnConstructor:组件构造器对象
        cpn2: cpnC2
      }
    })
  </script>

父子组件错误用法:以子标签的形式在Vue实例中使用
因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
<child-cpn></child-cpn>是只能在父组件中被识别的。
类似这种用法(以子标签的形式在Vue实例中使用),<child-cpn></child-cpn>是会被浏览器忽略的。

2.1.2 注册组件语法糖

Vue提供注册的语法糖,简化过程。
主要是省去了调用Vue.extend()的步骤,而是直接使用一个对象来代替。(内部传给了extend方法)

  1. 全局组件注册的语法糖
  <div id="app">
    <!-- 使用组件 -->
    <cpn></cpn>
  </div>
  <script src="../js/vue.js"></script>
  <script>
    // 1.全局组件注册的语法糖
    Vue.component('cpn',{
      template: `
        <div>
          <h2>标题</h2>
          <p>内容1</p>
        </div>`
    });

    const app = new Vue({
      el:'#app'
    })
  </script>
  1. 注册局部组件的语法糖
 <div id="app">
    <!-- 使用组件 -->
    <cpn2></cpn2>
  </div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el:'#app',
      // 注册局部组件的语法糖
      components: {
        cpn2: {
          template: `
	        <div>
	          <h2>标题</h2>
	          <p>内容2</p>
	        </div>`
        }
      }
    })
  </script>

2.1.2 模板的分离写法

在JS代码里,template模板写法中,有很多HTML模板,看起来很乱。
使用分离写法,将模板分离出来写,挂载到对应的组件上,结构会更加清晰。
Vue 提供了两种方案定义HTML模板内容:

  1. 使用<script>标签
 <div id="app">
    <!-- 使用组件 -->
    <cpn></cpn>
  </div>
  <!-- 1.使用script标签定义模板内容 -->
  <!-- type 设置为"text/x-template"-->
  <!-- id 设置为标签名-->
  <script type="text/x-template" id="myCpn">
    <div>
      <h2>标题</h2>
      <p>内容2</p>
    </div>
  </script>
  
  <script src="../js/vue.js"></script>
  
  <script>
    // cpn是标签名
    Vue.component('cpn',{
      // 因为是id,所以用id选择器,id名前加上“#“
      template: '#myCpn'
    });
    const app = new Vue({
      el:'#app'
    })
</script>

局部组件的模板分离写法:

 <div id="app">
    <!-- 使用组件 -->
    <cpn></cpn>
  </div>
  <!-- 1.使用script标签定义模板内容 -->
  <!-- type 设置为"text/x-template"-->
  <!-- id 设置为标签名-->
  <script type="text/x-template" id="myCpn">
    <div>
      <h2>标题</h2>
      <p>内容2</p>
    </div>
  </script>
  
  <script src="../js/vue.js"></script>
  
  <script>
    const app = new Vue({
      el:'#app',
      // 注册局部组件的语法糖
      components: {
        cpn: {
          template: myCpn
        }
      }
    })
  </script>
  1. 使用<template>标签
  <div id="app">
    <!-- 使用组件 -->
    <cpn></cpn>
    
  </div>
  <!-- 2.使用template标签 -->
  <template id="myCpn">
    <div>
      <h2>标题</h2>
      <p>内容2</p>
    </div>
  </template>
  
  <script src="../js/vue.js"></script>
  
  <script>
    // 1.全局组件注册的语法糖
    Vue.component('cpn',{
      template: '#myCpn'
    });

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

2.1.2 组件的其他属性

data属性

组件是一个单独功能模块的封装,有属于自己的HTML模板,也应该有属于自己的数据data。
组件中不能访问Vue实例中的data,通过组件自己的data选项,保存数据。

组件数据的存放
组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
只是这个data属性必须是一个函数
而且这个函数返回一个对象,对象内部保存着数据

  <script>
    // 1.全局组件注册的语法糖
    Vue.component('cpn',{
      template: '#myCpn',
      data() {
      	return {
      		title: 'hello'
      	}
      }
    });

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

为什么组件data必须是函数

2.2 数据传递

  • 子组件是不能引用父组件或者Vue实例的数据的。

  • 但是,在开发中,往往一些数据确实需要从上层传递到下层︰

    • 比如在一个页面中,我们从服务器请求到了很多的数据。
    • 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
    • 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组伟)将数据传递给小组件(子组件)。
  • 如何进行父子组件间的通信呢?Vue官方提到

    • 通过props向子组件传递数据
    • 通过事件向父组件发送消息
      Patent(父组件)
      Child([水组件)
      -----$emit Events------
      在下面的代码中,我直接将Vue实例当做父组件,并且其中包含子组件来简化代码。
      真实的开发中,Vue实例和子组件的通信父组件和子组件的通信过程是一样的。

父子组件的访问方式:$children

  • 有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
    • 父组件访问子组件:使用$children或$refs
    • 子组件访问父组件:使用$parent
  • 我们先来看下$children的访问
    • this.$children是一个数组类型,它包含所有子组件对象。
    • 我们这里通过一个遍历,取出所有子组件的message状态。
  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn></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: 'hello'
      },
      methods: {
        btnClick() {
           console.log(this.$children);
           for (let item of this.$children) {
             console.log(item.name);
             item.showMessage();
           }
        }
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              name: '我是子组件的name'
            }
          },
          methods: {
            showMessage() {
              console.log('showMessage');
            }
          }
        }
      }
    })
  </script>

$children需要通过下标值去拿子组件,在实际开发中下标值会变化,用的非常少(比如拿到所有的子组件)。

父子组件的访问方式:$refs

  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    // 在该子组件上加上ref属性
    <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: 'hello'
      },
      methods: {
        btnClick() {
          // $refs 默认是空白的
          // 在某个组件上加一个ref属性,可以通过this.$refs.aaa(属性值)拿到该组件
          console.log(this.$refs.aaa);
        }
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              name: '我是子组件的name'
            }
          },
          methods: {
            showMessage() {
              console.log('showMessage');
            }
          }
        }
      }
    })
  </script>

子访问父:$parent$root

 <div id="app">
    <cpn></cpn>
  </div>

  <template id="cpn">
    <div>
      <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: {
        name: '我是根组件的name'
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              name: '我是父组件的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.name);
                }
          }
            }
          }
        }
      }
    })
  </script>

3.组件化高级

3.1 插槽slot

3.1.1 编译作用域

3.1.1 为什么使用slot

  • slot(发音为[slɒt])翻译为插槽:

    • 在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
    • 插槽的目的是让我们原来的设备具备更多的扩展性。
    • 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
  • 组件的插槽:

    • 组件的插槽也是为了让我们封装的组件更加具有扩展性。
    • 让使用者可以决定组件内部的一些内容到底展示什么。
  • 例子︰移动网站中的导航栏。

    • 移动开发中,几乎每个页面都有导航栏。
    • 导航栏我们必然会封装成一个插件,比如nav-bar组件。
    • 一旦有了这个组件,我们就可以在多个页面中复用了。
  • 但是,每个页面的导航是一样的吗?No,我以京东M站为例
    在这里插入图片描述

  • 如何去封装这类的组件呢?

    • 它们也很多区别,但是也有很多共性。
    • 如果,我们每一个单独去封装一个组件,显然不合适︰比如每个页面都返回,这部分内容我们就要重复去封装。
    • 但是,如果我们封装成一个,好像也不合理︰有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。
  • 如何封装合适呢?抽取共性,保留不同。

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

这就是为什么我们要学习组件中的插槽slot的原因。

3.1.1 slot的基本使用

  1. 插槽的基本使用:在组件里定义一个<slot></slot>,在使用该组件时,在中间插入要替换的元素。
  2. 插槽的默认值:<slot><button>按钮</button></slot>。如果在该组件中没有插入任何其他内容,就默认显示默认值。
  3. 如果有多个值,同时放入到组件进行替换时,一起作为替换元素。
  <div id="app">
    <cpn><button>按钮</button></cpn>
    <cpn><span>span</span></cpn>
    <cpn>
      <!-- 3.如果有多个值,同时放入到组件进行替换时,一起作为替换元素。 -->
      <i>i</i>
      <div>div</div>
      <p>p</p>
  </cpn>
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
  </div>
  <template id="cpn">
    <div>
      <h2>我是组件h2</h2>
      <p>我是组件p</p>
      <!-- 1.插槽的基本使用:在组件里定义一个`<slot></slot>` -->
      <!-- 2.插槽的默认值:`<slot><button>按钮</button></slot>` -->
      <slot><button>按钮</button></slot>
    </div>
  </template>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      components: {
        cpn: {
          template: '#cpn'
        }
      }
    })
  </script>

3.1.1 slot的具名插槽

  • 当子组件的功能复杂时,子组件的插槽可能并非是一个。
    • 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
    • 那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
    • 这个时候,我们就需要给插槽起一个名字
  • 如何使用具名插槽呢?
    • 非常简单,只要给slot元素一个name属性即可
    • <slot name='myslot'></slot>

示例代码如下:

  <div id="app">
    <cpn>
      <!-- 只会替换没有名字的slot -->
      <span>标题</span>
      <!-- 只会替换名字为left的slot -->
      <button slot="left">按钮</button>
      <span slot="center">替换</span>
      <p slot="right">p</p>
    </cpn>
  </div>

  <template id="cpn">
    <div>
      <slot name="left"><span>左边</span></slot>
      <slot name="center"><span>中间</span></slot>
      <slot name="right"><span>右边</span></slot>
    </div>
  </template>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      components: {
        cpn: {
          template: '#cpn'
        }
      }
    })
  </script>

3.1.1 slot作用域插槽

3.1.1.1 编译作用域

在真正学习插槽之前,我们需要先理解一个概念∶编译作用域。
官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念︰我们来考虑下面的代码是否最终是可以渲染出来的:
<my-cpn v-show="isShow"></my-cpn>中,我们使用了isShow属性。isShow属性包含在组件中,也包含在Vue实例中。

  <div id="app">
    <!-- isShow使用的是实例中的属性,而不是子组件的属性-->
    <!-- 查找变量时,看是在哪个模板里(Vue实例的模板) -->
    <!-- 可以当做普通的div来看 -->
    <cpn v-show="isShow"></cpn>
  </div>

  <template id="cpn">
    <!-- 给div设置 v-show="isShow" ,仍然是显示的,不知道为什么 -->
    <div>
      <!-- 会使用子组件里的isShow -->
      <h2 v-show="isShow">我是子组件</h2>
      <p>我是内容</p>
    </div>
  </template>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: 'hello',
        isShow: true
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              isShow: false
            }
          }
        }
      }
    })
  </script>

官方给出了—条准则∶父组件模板的所有东西都会在父级作虑域内编译;子组件模板的所有东西都会在子级作用域内编译

3.1.1.2 作用域插槽:准备
  • 父组件替换插槽的标签,但是内容由子组件来提供。
  • 我们先提一个需求∶
    • 子组件中包括一组数据,比如: pLanguages: [‘JavaScript’ , ‘Python’, ‘Swift’ , ‘Go’,‘C++’]
    • 需要在多个界面进行展示︰
      • 某些界面是以水平方向——展示的,
      • 某些界面是以列表形式展示的,
      • 某些界面直接展示一个数组
    • 内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
      • 利用slot作用域插槽就可以了

3.2 动态组件

3.3 异步组件

3.4 组件声明周期

4.Vue CLI详解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值