Vue2组件


Vue2 - 组件

1. 对组件的理解

以前我们做前端项目的时候,比如一个管理系统,有顶部,内容区,底部,我们都是将页面的结构放到一个HTML结构中,然后js文件一般会模块化分为多个,分别引入,然后CSS也是多个,引入。

但是这样就会存在一个问题,比如说我现在要做第二个管理系统了,同样有顶部、底部,只是内容区不一样,那么我一般会复制之前的代码,CSS文件,js相关文件,然后来做,显然是十分麻烦的,而且当多个项目都引入了相同的CSS、js之后,不仅管理混乱,而且一处CSS文件修改了,凡是引入过它的页面样式都会导致修改,那么有没有什么办法来解决这个问题呢?

这就要引入Vue组件的概念了,有了Vue组件之后,我们只需要对于重复的内容引入组件即可,注意,这里可不是复制代码实现的,而是引入组件实现的,每一个组件中都有自己的HTML、CSS、JS。极大提高了服用的效果,而且管理起来非常清晰。

组件的定义——实现应用中局部功能代码资源的集合

其中Vue的组件中,又分为单文件组件非单文件组件

补充:

在实际开发中,往往真个系统就只有一个vm实例,其中vm实例下管理着很多的组件,其中,组件又是可以嵌套组件的。

关系图:

2. 非单文件组件

定义:一个文件中包含了n个组件

使用组件的步骤:

  1. 创建组件
  2. 注册组件
  3. 使用组件

例如下面的代码:

<div id="root">
    <h2>学校名称:{{schoolName}}</h2>
    <h2>学校地址:{{address}}</h2>
    <!--<button @click="showName">点我提示学校名</button>-->
    <hr/>
    <h2>学生姓名:{{studentName}}</h2>
    <h2>学生年龄:{{age}}</h2>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false
    new Vue({
        el: '#root',
        data: {
            schoolName: 'B站大学',
            address: 'www.bilibili.com',
            studentName: '念心卓',
            age: 18

        }
    })
</script>

以前我们写代码就是这样,整个页面使用一个HTML来写。

现在我们引入非单文件组件:

<div id="root">
    <school></school> <!--4. 使用组件-->
    <!--<button @click="showName">点我提示学校名</button>-->
    <hr/>
    <student></student><!--4. 使用组件-->
</div>
<script type="text/javascript">
    Vue.config.productionTip = false
    //1. 定义school组件
    const school =  Vue.extend({
        template:`
          <div>
              <h2>学校名称:{{schoolName}}</h2>
              <h2>学校地址:{{address}}</h2>
          </div>
        `,
        data(){
            return {
                schoolName: 'B站大学',
                address: 'www.bilibili.com'
            }
        }
    });
    //2. 定义student组件
    const student = Vue.extend({
        template:`
          <div>
              <h2>学生姓名:{{studentName}}</h2>
              <h2>学生年龄:{{age}}</h2>
          </div>
        `,
        data(){
            return {
                studentName: '念心卓',
                age: 18

            }
        }
    });

    new Vue({
        el: '#root',
        //3. 使用components配置来注册组件
        components:{
            school,
            student
        }
    })
</script>

对应的页面也是一样的。

仔细观察我的上述代码:

  1. 首先要定义组件

    定义组件的时候,需要使用extend关键字来定义,其本身有拓展的意思。

    Vue.extend({
        //配置对象
    })
    
  2. 配置组件内的数据

    比如基本的页面的HTML结构,使用template配置项来配置。

    注意:

    • 这里template的内容最好使用反引号包起来。
    • 模板里面的内容,必须要有一个根标签,否则报错,禁止使用<template>作为根标签

    其次,对于每个组件的数据,必须使用data函数来配置,不允许使用以前的data对象,因为可能多个页面使用同一个组件的时候,如果数据是使用的对象的方式来定义的话,那么如果其中一个页面的数据发生了变化,会连带其他页面的数据也跟着变化的,因为对象是引用类型。所以我们一般把组件中的数据配置为data函数,然后返回值是一个对象形式即可:

    data(){
        return 对象
    }
    
  3. 注册组件

    注册组件使用components关键字:

    new Vue({
        //其他配置...
        components:{
            //定义组件的名称,可以是key-value形式,如果你的key和value想等,可以写简写形式,如上我的代码
        }
    })
    
  4. 使用组件

    因为你定义好了组件,并且组成到了vm上,比如上面我定义的student组件,那么你使用的时候,直接写student标签即可。

    注意:这里虽然标签你的student第一个字母虽然是小写,但是在Vue开发者工具中却是大写:

3. 组件的几个注意点

3.1 组件名

组件由一个单词组成:

  1. 第一种写法(首字母小写):school
  2. 第一种写法(首字母大写):School

组件由多个单词组成:

  1. 第一种写法(kebab-case命名):my-school
  2. 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)

3.2 组件标签

第一种写法 - 双标签:<school></school>

第二种写法 - 自闭合:<school/>

备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

3.3 简写

之前我们定义一个组件:

const xxx = Vue.extend({
    //配置对象
})

简写:

const xxx = {
    //配置对象
}

4. 组件嵌套

从字面意思上也不难理解,组件之间是可以嵌套的:

<!-- 准备好一个容器-->
<div id="root">
    <school></school>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    //定义student组件
    const student = Vue.extend({
        name:'student',
        template:`
                <div>
                    <h2>学生姓名:{{name}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                </div>
            `,
        data(){
            return {
                name:'念心卓',
                age:18
            }
        }
    })
    //定义school组件
    const school = Vue.extend({
        name:'school',
        template:`
                <div>
                    <h2>学校名称:{{name}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <student></student>
                </div>
            `,
        data(){
            return {
                name:'尚硅谷',
                address:'北京'
            }
        },
        //注册组件(局部)
        components:{
            student //注意,这里必须注册要嵌套的组件
        }
    })
    const vm =  new Vue({
        el:'#root',
        components: {
            school
        }
    })

不过,学到后面组件由很多的时候,我们一般不会直接用vm来接管众多的组件,而是由一个新的组件APP来接管,然后vm接管APP,所以,APP是一人之下万人之上的组件。

<!-- 准备好一个容器-->
<div id="root">
    <app></app>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    //定义student组件
    const student = Vue.extend({
        name:'student',
        template:`
                <div>
                    <h2>学生姓名:{{name}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                </div>
            `,
        data(){
            return {
                name:'念心卓',
                age:18
            }
        }
    })

    //定义school组件
    const school = Vue.extend({
        name:'school',
        template:`
                <div>
                    <h2>学校名称:{{name}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <student></student>
                </div>
            `,
        data(){
            return {
                name:'尚硅谷',
                address:'北京'
            }
        },
        //注册组件(局部)
        components:{
            student
        }
    })
    //定义hello组件
    const hello = Vue.extend({
        template:`<h1>{{msg}}</h1>`,
        data(){
            return {
                msg:'Vue学习中...'
            }
        }
    })
    //定义app组件
    const app = Vue.extend({
        template:`
                <div>
                    <hello></hello>
                    <school></school>
                </div>
            `,
        //在app组件中,在接管众多组件
        components:{
            school,
            hello
        }
    })
    const vm =  new Vue({
        el:'#root',
        components: {
            app //vm只需接管app组件即可
        }
    })

可见上面的代码和效果都是由App来接管众多组件,然后由vm来接管app。

5. VueComponent

首先VueComponent是一个构造函数

之前我们定义组件的时候写了这么一部分代码:

//定义student组件
const student = Vue.extend({
    name:'student',
    template:`
            <div>
                <h2>学生姓名:{{name}}</h2>
                <h2>学生年龄:{{age}}</h2>
            </div>
        `,
    data(){
        return {
            name:'念心卓',
            age:18
        }
    }
})

现在我们输出这个组件对象来看看:

console.log(student)

惊奇的发现,输出它的时候,并不是一个对象形式,而是一个构造函数,那这个构造函数在哪里呢,我们来看看源码:

可见,整个Vue的JavaScript的源码中,只有在Vue.extend函数中出现了这个构造函数。

所以,看到这我们明白:xxx组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。

当我们定义好组件,并且注册好之后,我们要去使用他的时候,往往是这样:<xxxx</xxxx>的方式来使用,所以,当你使用的时候,Vue又帮你做了一件事:Vue解析时会帮我们创建xxxx组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。

特别注意:每次调用Vue.extend,也就是来使用这个组件的时候(<xxxx</xxxx>),返回的都是一个全新的VueComponent!!!!

//定义student组件
    const student = Vue.extend({
        name:'student',
        template:`
                <div>
                    <h2>学生姓名:{{name}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                </div>
            `,
        data(){
            return {
                name:'念心卓',
                age:18
            }
        }
    })
    console.log(student)
    //定义hello组件
    const hello = Vue.extend({
        template:`<h1>{{msg}}</h1>`,
        data(){
            return {
                msg:'Vue学习中...'
            }
        }
    })
    console.log(hello) 
    console.log(hello === student) //false

所以,虽然输出看上去像同一个VueComponent,但是实际上却是两个完全不同的VueComponent。

现在我们还有一个问题,关于this指向

<!-- 准备好一个容器-->
<div id="root">
    <app></app>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    //定义student组件
    const student = Vue.extend({
        name:'student',
        template:`
                <div>
                    <h2>学生姓名:{{name}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                    <button @click="showThis">点我输出this</button>
                </div>
            `,
        data(){
            return {
                name:'念心卓',
                age:18
            }
        },
        methods:{
            showThis(){
                console.log(this)
            }
        }
    })
    //定义app组件
    const app = Vue.extend({
        template:`
                <div>
                    <student></student>
                </div>
            `,
        components:{
            student
        }
    })
    const vm =  new Vue({
        el:'#root',
        components: {
            app
        }
    })
</script>

可见,在组件中,this指的就是VueComponent,并且仔细查看其里面的内容,发现和Vue实例vm实际上内容基本上一致。

所以:

  1. 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】
  2. new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】

VueComponent的实例对象,以后简称为:组件实例对象。Vue的实例对象,以后简称vm。

6. 一个重要的内置关系

之前我说过,VueComponent其实和Vue实例vm差不多,只是vm配置属性中,指明了为哪个容器服务,而组件却不可以,因为组件本身就是需要复用的。但是其中却包含了一个重要的内置关系:

VueComponent.prototype.__proto__ === Vue.prototype

为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。让它做一个兜底的选择,目的就是如果在组件实例上找数据,如果组件实例上找不到,就再去Vue实例上找数据,如果还找不到,那就却是没办法了,只有在Object去找了。

看到这里,你是不是有点熟悉感觉,没错,就是原型链。

忘记原型的读者,可以去JavaScript笔记中查找看看。

简单解释:

  1. 每个构造函数身上都有一个prototype属性,并且这个属性指向的是原型对象
  2. 每一个实例对象身上都有一个__proto__属性,并且这个属性指向prototype的原型对象。
  3. __proto__ 是JavaScript非标准属性,所以打印的时候显示出来的都是[[prototype]]

7. 单文件组件

有了上面几小节的知识铺垫,单文件组件也就可以来聊聊了。

从这里开始,我们将使用一个全新的文件格式.vue来表示一个单文件,每一个.vue文件中,包含3个部分:

  1. template:里面写以前的html结构,最终解析过后的template标签是不显示到页面上的,不过还是要有根标签。
  2. script:里面写以前的JavaScript代码
  3. style:里面写以前的css样式
<template>
<!--  里面写以前的html结构-->
</template>
<script>
  /*里面写以前的JavaScript代码*/
</script>
<style>
  /*里面写以前的css样式*/
</style>

并且,对于单文件组件命名,如果是一个单词,我们一般首字母大写,如果是多个单词组成,我们一般是大驼峰。

下面改造之前出现过的代码:


Student.vue文件:

<template>
  <div>
    <h2>学生姓名:{{name}}</h2>
    <h2>学生年龄:{{age}}</h2>
  </div>
</template>
<script>
  export default {
    name:'Student',
    data(){
      return {
        name:'张三',
        age:18
      }
    }
  }
</script>
<style>
  /*里面写以前的css样式*/
</style>

对于上面script标签中的代码,export语法是ES6模块化中的语法,不懂的可以去补一下基础,并且上面组件也是用到简写形式,原本是这样的:

<template>
  <div>
    <h2>学生姓名:{{name}}</h2>
    <h2>学生年龄:{{age}}</h2>
  </div>
</template>
<script>
  const student = Vue.extend({
    name:'Student',
    data(){
      return {
        name:'张三',
        age:18
      }
    }
  }) 
</script>

然后你必须要把这个组件给暴露出去,让外面通过import方式引入,所以就有了export关键词:

export const student = Vue.extend({
    name:'Student',
    data(){
      return {
        name:'张三',
        age:18
      }
    }
  })

或者:

const student = Vue.extend({
    name:'Student',
    data(){
      return {
        name:'张三',
        age:18
      }
    }
  });
  export default student;

这里的暴露方式有多种,可以自行去了解模块化。

只不过我上面写了一个最简单的形式。


School.vue文件:

<template>
  <div class="demo">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="showName">点我提示学校名</button>
  </div>
</template>

<script>
export default {
  name: "School",
  data() {
    return {
      name: 'B站大学',
      address: www.bilibili.com
    }
  },
  methods: {
    showName() {
      alert(this.name)
    }
  }
}
</script>
<style>
  .demo {
    background-color: orange;
  }
</style>

组件有了之后,我们还要一个一人之下的组件App.vue:

<template>
  <student></student>
  <school></school>
</template>

<script>
 //要先引入组件才能够使用
import Student from "./Student.vue";
import School from "./School.vue";
    
export default {
  name: "App",
  components:{ //注册组件
    Student,
    School
  }
}
</script>

<style >

</style>

现在App组件也有了,现在我们需要一个大哥了Vue实例,申明Vue实例我们一般使用main.js文化:

import App from "./App.vue";

new Vue({
    el:'#root',
    template:`
        <App></App>
    `,
    components:{
        App
    }
})

现在Vue也有了,我们需要一个容器,让Vue实例来为我们服务,一般使用index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>练习一下单文件组件的语法</title>
    </head>
    <body>
        <!-- 准备一个容器 -->
        <div id="root"></div>
         <script type="text/javascript" src="../Vue_js/vue.js"></script> 
         <script type="text/javascript" src="./main.js"></script> 
    </body>
</html>

注意:我们必须先使用Vue的开发文件,之后才能够引入main.js。


目录结构:

这就是Vue开发的第一步。

但是你运行代码一看,发现报错:

表面浏览器不能直接支持ES6的模块化语法,这怎么办呢?

这就要说到下一章了 - Vue脚手架


文章作者: 念心卓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 念心卓 !
  目录