Vue全家桶之Vue组件化开发

Posted 生命是有光的

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue全家桶之Vue组件化开发相关的知识,希望对你有一定的参考价值。

✍、目录脑图

🔥Vue🔥

🔥Vue全家桶地址
🔥Vue全家桶之Vue基础指令(一)https://blog.csdn.net/Augenstern_QXL/article/details/120117044
🔥Vue全家桶之Vue组件化开发(二)https://blog.csdn.net/Augenstern_QXL/article/details/120117322
🔥Vue全家桶之VueCLI 脚手架V2→V4版本(三)https://blog.csdn.net/Augenstern_QXL/article/details/120117453

1、组件化开发

我们将一个完整的页面分成很多个组件,每个组件都用于实现页面的一个功能块,而每一个组件又可以进行细分

1.1、组件的使用

步骤:

  1. 创建组件构造器: 调用 Vue.extend() 方法创建组件构造器
  2. 注册组件:调用 Vue.component() 方法注册组件
  3. 使用组件:在 Vue 实例的作用范围内使用组件

<div id="app">
  <!--3.使用组件-->
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>

  <div>
    <div>
      <my-cpn></my-cpn>
    </div>
  </div>
</div>

<my-cpn></my-cpn>

<script src="../js/vue.js"></script>
<script>
  // 1.创建组件构造器对象
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>我是标题</h2>
        <p>我是内容, 哈哈哈哈</p>
        <p>我是内容, 呵呵呵呵</p>
      </div> `
  })

  // 2.注册组件
  // 参数1:字符串格式,表示组件的注册名称
  // 参数2:需要被全局注册的那个组件
  Vue.component('my-cpn', cpnC)

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

1.2、步骤解析

  1. Vue.extend():

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

    • 调用 Vue.component() 是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称
    • 所以需要传递两个参数:1、注册组件的标签名。 2、组件构造器
  3. 组件必须挂载在某个Vue实例下,否则它不会生效

    我们来看下面我使用了三次 <my-cpn></my-cpn> ,而第三次其实并没有生效

1.3、全局组件和局部组件

当我们调用Vue.component()注册组件时,组件的注册是全局的

  • 这意味着该组件可以在任意Vue示例下使用

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

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

<script src="../js/vue.js"></script>
<script>
  // 1.创建组件构造器
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>我是标题</h2>
        <p>我是内容,哈哈哈哈啊</p>
      </div>
    `
  })

  // 2.注册组件(全局组件, 意味着可以在多个Vue的实例下面使用)
  // Vue.component('cpn', cpnC)

  // 疑问: 怎么注册的组件才是局部组件了?
  // 答:将组件构造器放在实例当中

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      // 将组件构造器放在实例当中  
      // cpn使用组件时的标签名
      cpn: cpnC
    }
  })

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

通过Vue.component()方法注册的组件是全局组件,通过 components 注册的是私有子组件

1.4、父组件和子组件

组件与组件之间存在层级关系,其中一种最重要的关系就是父子组件

<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></cpn1>  
      cpn1: cpnC1
    }
  })

  // root组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      // cpn1子组件在cpn2父组件中注册,父组件cpn2在Vue实例里面注册
      cpn2: cpnC2
    }
  })
</script>

1.4.1、父子组件的错误用法

<div id="app">
  <cpn2></cpn2>
  <!--父子组件的错误用法:以子组件的形式在 Vue 实例中使用-->  
  <!--<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></cpn1>  
      cpn1: cpnC1
    }
  })

  // root组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      // cpn1子组件在cpn2父组件中注册,父组件cpn2在Vue实例里面注册
      cpn2: cpnC2
    }
  })
</script>

父子组件的错误用法: 以子组件的形式在 Vue 实例中使用

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

1.5、注册组件语法糖🔥

  • 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>

1.6、模板的分离写法

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

  • 如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰
  • Vue 提供了两种方案来定义HTML模板内容
    • 使用 < script > 标签
    • 使用 < template > 标签

1.6.1、使用 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>

1.6.2、使用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>

1.7、组件数据存放

问题:组件可以访问Vue实例数据吗?我们来测试一下

结论:组件不能直接访问Vue实例中的 data

组件是一个单独功能模块的封装:

  • 这个模块有属于自己的 HTML 模板,也应该有属于自己的数据 data

组件自己的数据存放在哪呢?

  • 组件对象也有一个 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>

为什么 data 在组件中必须是一个函数呢?

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

1.8、父子组件通信🔥

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

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

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

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

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

1.9、props 基本用法

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

props 的值有两种方式:

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

1.9.1、传数组

<div id="app">
  <!-- 4.使用组件(v-bind动态绑定) -->  
  <cpn :cmessage="message" :cmovies="movies"></cpn>
</div>

<!-- 2.组件构造器的模板分离写法 -->
<template id="cpn">
  <div>
    <h1>{{cmovies}}}</h1>
    <h1>{{cmessage}}</h1>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  // 1.创建组件构造器(子组件)
  const cpn = {
    template: '#cpn',
    props: ['cmovies', 'cmessage']	//父传子,props
  }
  // 3.注册组件(将子组件在父组件里面注册)
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      movies: ['海王', '海贼王', '海尔兄弟']
    },
    components: {
      //对象字面量增强写法的属性增强写法
      cpn
    }
  })
</script>

注意:我们在使用组件时,需要用v-bind 动态绑定数据。

1.9.2、传对象

  • 在前面,我们的 props 选项是使用一个数组
  • 除了数组之外,我们也可以使用对象,当需要对props 进行类型等验证时,就需要对象写法了

验证支持的数据类型有:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

①类型限制

我们可以在 props 里面限制父组件给子组件传递的数据类型

<!--父组件模板-->
<div id="app">
  <cpn :cmessage="message" :cmovies="movies"></cpn>
</div>

<!--子组件模板-->
<template id="cpn">
  <div>
    <h1>{{cmovies}}}</h1>
    <h1>{{cmessage}}</h1>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  // 父传子: props
  const cpn = {
    template: '#cpn',
    props: {
      // 1.类型限制
      cmovies: Array,		// 限制父组件传的是数组类型
      cmessage: String,		// 限制父组件传的是字符串类型
    }

  }
  // root组件,我们当作父组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      movies: ['海王', '海贼王', '海尔兄弟']
    },
    components: {
      //对象字面量增强写法的属性增强写法
      cpn
    }
  })
</script>

②默认值和必传值

  • type : 限制的类型
  • default : 如果没有传值,给一个默认值
    • 注意:类型是对象或者数组时, 默认值必须是一个函数
  • required : 必须的,即意味着这个值是必须要传递的,不传就报错
<div id="app">
   <!--在这里传值--> 
  <cpn :cmessage="message" :cmovies="movies"></cpn>
</div>

<template id="cpn">
  <div>
    <h1>{{cmovies}}}</h1>
    <h1>{{cmessage}}</h1>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  // 父传子: props
  const cpn = {
    template: '#cpn',
    // props: ['cmovies', 'cmessage'],
    props: {

      // 2.提供一些默认值, 以及必传值
      cmessage: {
        type: String,         // 类型限制为 String
        default: 'aaaaaaaa',  // 如果没有传值,则给一个默认值
        required: true        // required 必须的,即意味着这个值是必须要传递的,不传就报错
      },
      // 类型是对象或者数组时, 默认值必须是一个函数
      cmovies: {
        type: Array,
        default() {
          return []
        }
      }
    },


  // root组件,我们当作父组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      movies: ['海王', '海贼王', '海尔兄弟']
    },
    components: {
      //对象字面量增强写法的属性增强写法
      cpn
    }
  })
</script>

③自定义类型

以上是关于Vue全家桶之Vue组件化开发的主要内容,如果未能解决你的问题,请参考以下文章

Vue全家桶之组件化开发

Vue全家桶之组件化开发

Vue全家桶之Vue基础指令

Vue全家桶之VueX

Vue全家桶之webpack详解

Vue全家桶之Vue-router路由