5.组件化开发

1. 组件化跟模块化的区别


  • 组件的出现就是为了拆分 vue 实例的庞大内容的,我们可以以不同的组件来划分不同的功能模块
  • 之后我们需要什么样的功能就直接搬运过来什么样的组件就可以了
  • 模块化是从程序逻辑代码结构来划分的(比如我们会将所有 api 封装起来,单独放到一个模块中这就是按照逻辑来划分的),后端的模块化是为了代码的分层开发,保证每个功能模块的职能单一
  • 组件化是按照 ui 视图界面来划分的(比如我们将表单作为一个组件来使用,这个就是按照视图来划分),前端的组件化是为了代码的重用

2. Vue 中定义组件的三种方式


(1) 使用 Vue.extend 来创建全局组件

  • 这种方式是通过对 Vue 实例的继承来完成的,因此创建的组件将会是全局组件,也就是所有 Vue 实例都可以访问的组件
  • 在定义的时候我们将会用一个变量来接受这个新的实例
  • 其定义参数为一个对象,其中 template 属性指向了我们定义的 html 结构
  • 之后再使用 Vue.component('组件名称',{创建出来的模板对象,也就是我们用来接受新的组件实例的那个变量的名字})
  • 这样一操作我们就可以将组件注册到我们的组件堆里面了
  • 在 vm 实例里面用驼峰形式定义的东西在 html 页面中使用的化必须要转成中间用杠链接的形式,如:'myNewComp'->'my-new-comp'
  • 我们也可以把 Vue.extend 作为 Vue.component 的第二个参数直接进行注册,这样可以省一个步骤

html

<div id="app">
  <div>{{message}}</div>
  <!-- 直接使用组件的名称即可 -->
  <my-comp-one></my-comp-one>
</div>

javascript

// 创建一个Vue.extend实例
let bigFont = Vue.extend({
  template: "<h1>This text is big font text"
});
// 将创建的实例注册到组件堆上面
Vue.component("myCompOne", bigFont);

let vm = new Vue({
  el: "#app",
  data: {
    message: "yerts"
  }
});

(2) 直接使用 Vue.component 中的字面量形式

  • 在第一种定义方式中我们将 Vue.extend 作为第二个参数传入到了 Vue.component 中,其实我们可以做的更绝
  • 也就是将 Vue.extend 直接作为一个对象字面量的形式,直接在里面定义 template 属性,然后将其传入到 Vue.component 的第二个参数
  • 记住,所有的组件必须只能有一个根节点,不然都会报错

html

<!-- 直接使用组件的名称即可 -->
<my-comp-one></my-comp-one>
<my-comp-tow></my-comp-tow>

javascript

//直接将定义过程作为字面量的形式传入注册函数的方式创建组件
Vue.component("myCompTow", {
  template: "<h2 style='color: red;'>This is my second component.</h2>"
});
let vm = new Vue({
  el: "#app",
  data: {
    message: "yerts"
  }
});

(3) 通过让 template 指向 html 文件中的 template 的方式来构建组件

  • 前两种方式在定义一个组建的时候都定义在了一个 js 文件中,但是这样就带来了不会有智能提示的功能
  • 因此第三种定义组建的方式就是解决这一问题的
  • 我们可以在 html 页面中使用 template 标签作为模板的容器,并设置其 id 属性作为我们的选择器,之后的使用过程中我们只需要在获取 template 的地方使用template:"#选择器"这种方式来让其指向我们自己定义的模板即可,这种方式定义的时候也需要我们的Vue.component('组件名称',{template:'#id'})来注册全局组件

html

<div id="app">
  <div>{{message}}</div>
  <!-- 直接使用组件的名称即可 -->
  <my-comp-one></my-comp-one>
  <my-comp-tow></my-comp-tow>
  <my-comp-three></my-comp-three>
</div>
<!-- 定义组件 -->
<template id="myTempOne">
  <div>
    <div>This is a template build by template tag.</div>
    <div style="color: cornflowerblue;">easy to use !</div>
  </div>
</template>

javascript

// 通过在html页面定义好组件的结构,在js文件里面注册其行为的方式构建组件
Vue.component("myCompThree", {
  template: "#myTempOne"
});
let vm = new Vue({
  el: "#app",
  data: {
    message: "yerts"
  }
});
  • 这种情况下我们的 template 标签不用包括在 app 标签里面了

3. Vue 中定义私有组件


  • 我们可以使用 Vue 实例中的 components 属性来定义 Vue 实例的私有组件
  • 其中的 template 属性什么的依然可以使用上面提到的三种方式,只是不用再使用 Vue.extend 来定义了,因为 templates 已经在 Vue 实例内部了
  • 私有组件的 data 属性是用来存放组件身上的私有数据的,但是与 Vue 实例的不同之处在于组件中的 data 是函数,并且会返回私有组件的数据
  • 其返回的数据是对象形式返回的,因此我们在 return 的时候需要 return 一个对象才能正常使用,定义完之后我们就可以像 Vue 实例一样引用组件自身定义的数据

JavaScript

let vm = new Vue({
  el: "#app",
  data: {
    message: "yerts"
  },
  components: {
    // 自定义私有组件的方式来创建并注册一个组件
    myCompFour: {
      template: "#myTempTow",
      data() {
        return {
          message: "this is message from myCompFour"
        };
      }
    }
  }
});

html

<div id="app">
  <div>{{message}}</div>
  <!-- 直接使用组件的名称即可 -->
  <my-comp-one></my-comp-one>
  <my-comp-tow></my-comp-tow>
  <my-comp-three></my-comp-three>
  <my-comp-four></my-comp-four>
</div>

<template id="myTempTow">
  <div>
    <div>This is a template build by template tag and it's a privte comp</div>
    <div style="color:coral;">{{message}}</div>
  </div>
</template>
  • 为什么组件身上的 data 必须要是函数
  • 这样设计的好处在于每次定义的组件都可以有自己的数据,而不是共用一个 data,而且每个组件身上的 data 发生变化也不会影响到其他组件身上的 data 数据
  • 这样的话每当我们使用一次组件的时候都会返回一个 data 数据对象,因此不会造成所有组件共同使用同一个数据的尴尬情景
  • 我们可以在我们组件身上使用一些指令来实现我们的一些想法,比如我们可以通过 v-if 来判断要展示哪个组件,其实现方法是在要展示的组件身上加上 v-if,v-else 指令,用其他变量去控制他们的显隐属性来达到我们的目的
  • 这样就可以达到组件切换的目的,其实我们也可以使用 Vue 本身提供的 component 标签来实现组件的切换
  • 下面将给出两种组件切换的方式,分别是使用 v-if 或者 component 标签来实现的

v-if 来实现切换

<input type="button" value="进行切换" @click="flag=!flag" />
<my-comp-one v-if="flag"></my-comp-one>
<my-comp-tow v-else="flag"></my-comp-tow>
  • 我们也可以使用 component 元素来实现我们的切换效果,其中如果我们想要哪个组件出现在 component 这个元素的位置的话
  • 我们就将 component 元素的 is 属性绑定为我们想要出现的组件名称就可以了
  • 所以说 component 元素更像是一种占位符,专门用来给组件占位置的

component 组件来实现切换

<a href="" @click.prevent="showThree">展示三号组件</a>
<a href="" @click.prevent="showFour">展示四号组件</a>
<component :is="componentId"></component>
let vm = new Vue({
  el: "#app",
  data: {
    message: "yerts",
    flag: true,
    componentId: "myCompFour"
  },
  methods: {
    showFour: function() {
      this.componentId = "myCompFour";
    },
    showThree: function() {
      this.componentId = "myCompThree";
    }
  },
  components: {
    // 自定义私有组件的方式来创建并注册一个组件
    myCompFour: {
      template: "#myTempTow",
      data() {
        return {
          message: "this is message from myCompFour"
        };
      }
    }
  }
});

实现组件之间的切换动画

  • 我们想实现切换动画只需要在 component 标签的外面包上 transition 标签即可,不过要设置一些属性
  • 比如要设置 v-enter,v-leave-to,v-enter-active,v-leave-active 这些样式过度类需要设置
  • 但是我们发现两个组件在切换的时候会有占位的情况,也就是第二个组件若想要进来则必须要等第一个组件先出去这样
  • 这个问题我们可以直接设置 transition 标签的 mode 属性,这个属性可以帮我们设置动画在运行的时候两个组件之间怎么完成交接
  • 比较常用的有 mode="out-in"也就是先出去,后进来,这样就保证了不会有占位等待的烦恼了

4. 父组件向子组件传值(通过属性绑定)


  • 实际上我们也可以将 Vue 的实例理解成一个组件,因为他有自己的 data,自己的 methods,自己的其他相关属性,它里面的 template 是 el,这样一理解的话我们就会发现 Vue 实例实际上是一个大的组件,并且其他在他身上定义的组件为其子组件
  • 在默认情况下子组件不能直接去访问父组件的 data 属性,同样父组件也不能直接去访问子组件的 data 属性
  • 因此我们需要一些方法来打通父子组件之间的通信问题
  • 我们可以这样理解,在组件的使用的时候我们有一个 app 组件实例,在默认情况下我们是不能直接向它内部的组件传递值的,因此我们就想到了一种曲线救国的方法,那就是我通过属性绑定的方式,把父组件的数据绑定到子组件内部的变量中, 因此我只要去找被绑定的数据就可以访问到我父组件的数据,也就是 v-bind:去绑定我的数据到子组件的变量中
  • 我们就可以在子组件身上使用属性绑定机制绑定一个自定义属性
  • 比如我们在子组件身上定义一个自定义属性,我们将其命名为 parentMessage
  • 之后我们把父组件身上的一个数据通过属性绑定的方式传到 parentMessage 身上,也就是::parentMessage="dataFromParent",也就是我们需要在父组件身上的 dataFromParent 这个变量身上绑定我们要传递的数据
  • 我们想要在子组件身上直接使用这个数据的话实际上是不可能用上的,因为我们的数据还没有在子组件身上定义
  • 所以我们就要在子组件身上定义一个变量去接收父组件传过来的数据,这样我们就完成了一次传递
  • Vue 规定父组件传过来的数据接收变量必须要放在一个组件内部叫做 props 的一个数组身上,所以我们需要在子组件内部定义这个 props 属性,也就是把我们在组件调用的地方定义的变量作为一个元素放进 props 属性内部就可以了
  • 因此我们可以将 data 属性跟 props 属性区别开来了,data 属性中的数据是组件私有的数据,这些数据一般在初始化的时候需要定义,而 props 中的数据一般是由父组件传递进来的,可能在不同的地方组件的数据会是不同的内容
  • 比如我们子组件 Ajax 过来的数据就可以放到 data 身上,而且每个组件调用的地方都有自己的 data,因此不用担心数据的混淆问题
  • data 上面的数据是可读可写的,但 props 里面的数据是只读不可写的

html

<div id="app">
  <!-- 在调用的时候需要去传值 -->
  <my-comp-four :data-from-parent="messageToChild"></my-comp-four>
</div>
<!-- 定义组件 -->
<template id="myTempFour">
  <div>
    <div>This is a template build by template tag and it's a privte comp</div>
    <div style="color:coral;">
      <!-- 使用父组件传进来的数据 -->
      {{dataFromParent.message}}-----{{dataFromParent.number}}
    </div>
  </div>
</template>

JavaScript

// 通过在html页面定义好组件的结构,在js文件里面注册其行为的方式构建组件
Vue.component("myCompThree", {
  template: "<h1>this is comp three.</h1>"
});
let vm = new Vue({
  el: "#app",
  data: {
    message: "yerts",
    flag: true,
    componentId: "myCompFour",
    messageToChild: {
      message: "this message is from parent.",
      number: 0
    }
  },
  methods: {
    showFour: function() {
      this.componentId = "myCompFour";
    },
    showThree: function() {
      this.componentId = "myCompThree";
    }
  },
  components: {
    // 自定义私有组件的方式来创建并注册一个组件
    myCompFour: {
      template: "#myTempFour",
      data() {
        return {
          message: "this is message from myCompFour"
        };
      },
      //需要在props里面定义被传进来的数据
      props: ["dataFromParent"]
    }
  }
});
  • 这里记住一个问题我们没有解决,如果使用 component 这个标签来切换的话,我们怎么去实现父组件向子组件传值,后期我们将会有相应的解答

5. 子组件向父组件传值(通过事件调用)


  • 如果父组件想要给子组件传递方法的话需要使用事件绑定机制,也就是 v-on:或者@来实现,只需要将把 v-bind 换成 v-on 就可以了,其他内容都一样,当我们在触发一个从父组件身上传过来的函数的时候会先触发子组件的方法,之后再去通过子组件触发父组件的方法,这就相当于子组件触发了父组件的方法,也就是传递了一个方法
  • 但是在传递方法的时候不用在 props 选项里面写自己用于接收的方法名,不过要在组件内部创建一个方法,用来执行父组件传过来的方法,执行的方式就是在这个方法内部写一个this.$emit('我们自己定义的用于接收方法的方法名')其含义就是执行的意思,也就是说我们在一个方法内部说明执行一个方法就可以了,之后再通过事件触发这个方法就可以做到我们调用里面定义执行的方法了
  • 下面将给一个例子

html

<div id="app">
  <!-- 这里我们用属性绑定机制把数据传到datafromparent上面了,我们用事件绑定机制把方法绑定到childactive身上了 -->
  <my-comp-four
    :data-from-parent="messageToChild"
    @child-active="parentMethod"
  ></my-comp-four>
</div>
<!-- 定义组件 -->
<template id="myTempFour">
  <div>
    <!-- 当我们触发了这个函数之后,这个函数内部的this.$emit方法将会执行父组件传过来的方法 -->
    <div @click="myClick">
      This is a template build by template tag and it's a privte comp
    </div>
    <div style="color:coral;">
      {{dataFromParent.message}}-----{{dataFromParent.number}}
    </div>
  </div>
</template>

JavaScript

let vm = new Vue({
  el: "#app",
  data: {
    message: "yerts",
    flag: true,
    componentId: "myCompFour",
    messageToChild: {
      message: "this message is from parent.",
      number: 0
    }
  },
  methods: {
    showFour: function() {
      this.componentId = "myCompFour";
    },
    showThree: function() {
      this.componentId = "myCompThree";
    },
    // 用于传递给子组件的方法
    parentMethod: function() {
      console.log("this is parent method.");
    }
  },
  components: {
    // 自定义私有组件的方式来创建并注册一个组件
    myCompFour: {
      template: "#myTempFour",
      data() {
        return {
          message: "this is message from myCompFour"
        };
      },
      props: ["dataFromParent"],
      methods: {
        //用于执行接收到父组件的内容的方法的方法
        myClick: function() {
          //emit代表触发的意思,其参数直接输入我们自己定义的接收父组件传递的方法就可以了
          this.$emit("child-active");
        }
      }
    }
  }
});
  • 既然我们可以调用父组件的方法了,那么也就是说我们也可以在调用这个方法的同时也传入我们参数,这样一来我们就可以把子组件的数据作为方法的参数传给父组件,就完成了子组件向父组件传递数据的功能,虽然两种方式都是父组件在向子组件绑定内容,但是在第二种情形下子组件就可以通过这个绑定的内容来传递自己的参数
  • 下面我们就来实践一下 ,同时在父组件内部定义几个数据用于接收子组件传过来的数据
  • 在使用 this.$emit 的时候第一个参数输入函数名,之后的参数就作为输入父组件需要的实参的位置了

html

<div id="app">
  <!-- 我们使用childactive来接收了父组件传过来的方法,之后我们在emit的时候可以加上参数去执行这个方法,这样子组件的数据就可以传递到父组件身上了 -->
  <my-comp-four
    :data-from-parent="messageToChild"
    @child-active="parentMethod"
  ></my-comp-four>
</div>
<!-- 定义组件 -->
<template id="myTempFour">
  <div>
    <div @click="myClick">
      This is a template build by template tag and it's a privte comp
    </div>
    <div style="color:coral;">
      {{dataFromParent.message}}-----{{dataFromParent.number}}
    </div>
  </div>
</template>

JavaScript

let vm = new Vue({
  el: "#app",
  data: {
    message: "yerts",
    flag: true,
    componentId: "myCompFour",
    messageToChild: {
      message: "this message is from parent.",
      number: 0
    },
    // 子组件传过来的数据就可以绑定在这个对象身上了
    childData: {}
  },
  methods: {
    showFour: function() {
      this.componentId = "myCompFour";
    },
    showThree: function() {
      this.componentId = "myCompThree";
    },
    parentMethod: function(childData) {
      //子组件传递进来的数据
      this.childData = childData;
      console.log("this is parent method.");
      //把子组件传递的数据绑定到这个对象身上
      console.log(this.childData);
    }
  },
  components: {
    // 自定义私有组件的方式来创建并注册一个组件
    myCompFour: {
      template: "#myTempFour",
      data() {
        return {
          message: "this is message from myCompFour"
        };
      },
      props: ["dataFromParent"],
      methods: {
        myClick: function() {
          //emit代表触发的意思,其参数直接输入我们自己定义的接收父组件传递的方法就可以了
          //第一个参数为方法名,之后的内容都可以作为传递的参数
          this.$emit("child-active", this.message);
        }
      }
    }
  }
});
  • 一般需要用户去查看的数据最好放在本地存储也就是 localStorage 中,因为这样操作将会对用户体验带来很大的提升
  • 有些需要上传完数据就要更新的功能可以添加到父组件内部,之后用子组件去调用即可

6. 用ref获取DOM元素身上的东西跟子组件的引用


  • 在我们的业务开发中有些时候会有需要获取当前元素的dom内容的情况,比如我们用getElementById('xx')来获取dom之后再访问其innerHTML内容的情况,这种情况下Vue给我们提供了this.$refs的选项,这就类似于getElementById('xx'),之后的内容直接在其身上获取就可以了,
  • 我们在实现这个功能的时候需要在被操作的元素身上加上ref="xxx"这个属性,这样才能通过this.$refs.xxx去获取它身上的内容
  • xxx.innerText就是表示这个元素身上在标签里面包裹的内容
  • 这样我们就操作了dom身上的内容
  • 类似的我们也可以在组件身上使用这一功能,我们也可以为组件绑定一个ref属性,并且通过ref属性来获取组件内部的东西
  • 通过这种方式我们可以直接获取组件身上的数据,方法等一系列内容
<div id="app">
        <h1 ref="refH1">这是一个大大的标签</h1>
        <my-comp-four ref="compFour" :data-from-parent="messageToChild" @child-active="parentMethod"></my-comp-four>
    </div>
    <!-- 定义组件 -->
    <template id="myTempFour">
        <div>
            <div @click="myClick">This is a template build by template tag and it's a privte comp</div>
            <div style="color:coral;">{{dataFromParent.message}}-----{{dataFromParent.number}}</div>
        </div>
    </template>
methods: {

        showFour: function () {
            this.componentId = 'myCompFour'

        },
        showThree: function () {
            this.componentId = 'myCompThree'
        },
        parentMethod: function (childData) {
            this.childData = childData
            console.log('this is parent method.')
            console.log(this.childData)
            console.log(`${this.$refs.compFour.message}-----refMessage`)
            console.log(`${this.$refs.refH1.innerText}-----refMessage`)
        }
    },
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容