上节说到组件http://www.javashuo.com/article/p-thaozczc-dc.html,这一节继续来学习组件:javascript
原文博客地址,欢迎学习交流: 点击预览
从github上获取本文代码: 示例代码
封装的组件要具有复用性和通用性。css
先来讲复用,复用主要是复用 HTML 结构,外加这块结构中的交互 js,和针对这一块设置的 css。 这三者是构成一个组件最基本的要素,这三者相互隔离有相互做用,将三者聚合起来,在须要使用的地方,相似一个变量(标签对)同样,会引用这一块的全部功能,能够屡次使用。html
在 vue 中提供了单文件组件,一个文件就是一个组件,这样把组件模块化的方式,让开发者更方便的利用组件堆积页面。将三者聚合在一个文件中,孤立的存在,减小了改动组件而影响外界的风险,极大的提升了代码可维护性。vue
再说通用性,在讨论通用性这点上,要向两个方面思考:java
组件达到复用后,能够在多个地方使用,而使用的位置不一样,须要展现的数据也不一样,此时封装的组件要具备通用性,组件内部则由外界使用组件时来决定将要显示的数据,须要将数据传递给组件。git
组件的内部除了须要数据外,不可避免的还有交互,当完成一个交互后,须要对外界产生影响,这不能在组件内部作具体的事情,由于使用的位置不一样,所产生的效果也不同,而完成这一系列事情则交给外界来决定,须要组件内部通讯给外界,告诉外界,内部完成了一次交互。github
从封装一个自定义的下拉框 custom-select 组件开始。segmentfault
要达到封装性好,而且能够写多种功能的代码块,那么组件自己就是一个函数或者类,须要使用 Vue.extend( options ) 来建立构造器,这个构造器能够由开发者本身手动初始化挂载,也能够注册成组件在其余组件的模板中使用。数组
在 body 中放置挂载点:浏览器
<div id="app"></div>
定义组件的构造器,并手动初始化,手动挂载:
let customeSelect = Vue.extend({ template: ` <div class="select"> <h2>这是一个定义的下拉框</h2> <p>请选择:北京</p> <ul> <li>北京</li> <li>上海</li> <li>杭州</li> </ul> </div> ` }) // 手动初始化,挂载到页面的挂载点上 new customeSelect().$mount('#app');
选择手动初始化的方式,调用内置方法 $mount 方法进行挂载,随后组件的模板进行编译,替换掉挂载点,渲染在页面中。
每每定义组件的构造器后,不须要手动的进行初始化,而是在其余组件的模板中当成标签来使用,这时候须要调用 Vue.component( id, [definition] ) 注册成组件。
// 注册组件,传入一个扩展过的构造器 Vue.component('my-component', Vue.extend({ /* ... */ })) // 注册组件,传入一个选项对象 (自动调用 Vue.extend) Vue.component('my-component', { /* ... */ }) // 获取注册的组件 (始终返回构造器) var MyComponent = Vue.component('my-component')
根据注册组件的语法,实际上是能够省略调用 Vue.extend 这一步,只须要传入 选项对象便可,内部会自定调用 Vue.extend ,因此定义组件变成了这样的简写方式:
Vue.component('custome-select',{ template: ` <div class="select"> <h2>这是一个定义的下拉框</h2> <p>请选择:北京</p> <ul> <li>北京</li> <li>上海</li> <li>杭州</li> </ul> </div> ` })
未来 custome-select 就当成了标签使用在其余组件的模板中 < custome-select>< /custome-select>,Vue在编译模板时,就回去找这种自定义标签是不是一个组件,若是已经注册的话,就会把注册的构造器进行初始化,编译组件模板,最终将编译后的模板替换掉自定义标签的位置。若是没有注册直接使用,则会抛出错误。
关于组件名称的命名:
注意:注册时随便使用两种命名方式的任何一种,在模板中一概采用烤串命名才有效。
定义挂载点,并使用组件:
<div id="app"> <custome-select></custome-select> </div>
启动应用:
new Vue({ el: '#app' })
最终渲染后的结构为:
<div id="app"> <!--如下替换了原来 <custome-select></custome-select> 标签位置--> <div class="select"> <h2>这是一个定义的下拉框</h2> <p>请选择:北京</p> <ul> <li>北京</li> <li>上海</li> <li>杭州</li> </ul> </div> </div>
目前 HTML 达到了复用的目的,但使用屡次依然显示的是写死的数据。做为显示数据的 HTML 结构,在不一样地方使用,所要展现的数据由外界来决定,这就须要给组件传递数据。
<div id="app"> <!-- 城市的下拉 --> <custome-select></custome-select> <!-- 用户的下拉 --> <custome-select></custome-select> <!-- 日期的下拉 --> <custome-select></custome-select> </div>
而传递参数实际上就是给组件的构造器传递参数,本质上就是给函数传参。函数的参数分为实参和形参两个部分:
如今组件写在模板中以标签对的形式呈现,须要传递实际的参数,惟一的地方就是写在行间做为自定义属性,而传递的参数会有不少个,最好代表具体的含义,须要和组件约定好属性名,传递参数:
<div id="app"> <!-- 城市的下拉 --> <custome-select title="请选择城市" :list="['北京','上海','杭州']"></custome-select> <!-- 用户的下拉 --> <custome-select title="请选择用户" :list="['张三','李四','王五']"></custome-select> </div>
在组件中须要显示的用 props 接收传递的数据,这样的好处就是一旦看到组件,就会很清晰快速的了解到组件所须要的数据。
注意: 在行间写上自定义属性,要解析为数组,在属性名前加上 v-bind,解析为 javascript 表达式,不然只能当成是字符串。
具体以下:
Vue.component('custome-select',{ // 关于props具体参考: // https://cn.vuejs.org/v2/guide/components-props.html#Prop-%E7%B1%BB%E5%9E%8B // https://cn.vuejs.org/v2/guide/components-props.html#Prop-%E9%AA%8C%E8%AF%81 props:{ title: { type: String, default: '这是一个定义的下拉框' }, list:{ type: Array, default(){return []} }, selectIndex:{ type: Number, default:0 } }, template: ` <div class="select"> <h2>{{title}}</h2> <p>请选择:{{list[selectIndex]}}</p> <ul> <li v-for="item,index in list" :key="index" > {{item}} </li> </ul> </div> ` })
在组件中约定了三个须要接收的参数,分别写出了接受的类型和默认值,props参数文档以下:
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
title | 定制组件的标题 | String | '这是一个定义的下拉框'' |
list | 定制组件的下拉列表 | Array | [] |
selectIndex | 选择要展现的一项 | String | 0 |
有了文档,很清晰的知道每个属性表明的意思,传入响应的参数后,就会达到预期的效果。
以上渲染后直接把下拉框显示了出来,下拉框应该是在点击 p 标签时候才能显示,再次点击就隐藏掉,要实现这样的一个显示隐藏切换功能。
在 Vue 中不提倡直接操做 DOM,须要设置一个状态来肯定 DOM 的状态,当须要改变 DOM 时,只须要改变设置好的状态便可,把咱们的关注点放在状态的维护上,而无需手动操做 DOM 改变。
这个状态不受外界的影响,属因而组件自身的状态变化,定义在组件内部,而且改变时只能由组件自身更改。
具体以下:
Vue.component('custome-select',{ ... 省略了props设置 data(){ return { show: false // 一开始状态为false,也就是不显示下拉列表 } }, template: ` <div class="select"> <h2>{{title}}</h2> <p @click="toggleShow">请选择:{{list[selectIndex]}}</p> <ul v-show="show"> <li v-for="item,index in list" :key="index" > {{item}} </li> </ul> </div> `, methods:{ toggleShow(){ this.show = !this.show; } } })
以上作了三件事情:
Vuejs 这个框架要作的就是状态和UI保持同步。
单向数据流顾名思义就是单方向的数据流向,也就是数据只能从一边流向另外一边,反过来则不行,如黄河之水从天上来,却不能再流回到天上去。具体到组件中,就是:父子 prop 之间造成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,子组件改变不能改变父组件。这样设计的目的是防止从子组件意外改变父级组件的状态,从而致使应用的数据流向难以理解。
与之对应的就是双向数据流,父组件子组件均可以任意修改,互相产生影响,这样的话使用这套数据的其余组件也会跟着变化,变得很是的诡异。
在复杂的应用中,控制数据有规则的改变和传递很是重要,若是不是单向数据流的限制,任何组件都能修改数据,就跟定义全局的数据在任何程序都能修改同样,最终通过多个函数的调用修改后,出现了问题,不能准确的定位到具体的函数中,排查问题会变的很是的困难。
每次父级组件发生更新时,子组件中全部的 prop 都将会刷新为最新的值。由父组件传递给子组件的数据,子组件内部不能改变 prop。若是你这样作了,Vue 会在浏览器的控制台中发出警告。
来个例子说明一下。上面的例子中,须要在下拉框中选择具体的的一项,显示在 p标签中,要显示的数据是经过外界传递的 selectIndex 来决定从 list 中选取哪一项。那咱们能够这样来作,在点击下拉框的某一项时,改变 selectIndex 为点击的一项的下标便可,具体以下:
HTML 代码:
<div id="app"> <!-- 城市的下拉 --> <custome-select title="请选择城市" :list="['北京','上海','杭州']" :select-index="0" ></custome-select> </div>
JavaScript代码:
Vue.component('custome-select',{ props:{ // 省略了title和list.... selectIndex:{ type: Number, default:0 } }, data(){ return { show: false // 一开始状态为false,也就是不显示下拉列表 } }, template: ` <div class="select"> <h2>{{title}}</h2> <p @click="toggleShow">请选择:{{list[selectIndex]}}</p> <ul v-show="show"> <li v-for="item,index in list" :key="index" @click="changeIndex(index)" > {{item}} </li> </ul> </div> `, methods:{ toggleShow(){ this.show = !this.show; }, changeIndex(index){ // 改变为选中的下标,此时会报错 this.selectIndex = index; } } }) new Vue({ el: '#app' })
以上代码作的事情:
以上的报错已经警告了,不能直接修改props的值,可是组件内部是能够修改组件内部数据 data ,因此修改以下:
// 其余代码省略 Vue.component('custome-select',{ data(){ return { currentIndex: this.selectIndex // 把selectIndex做为currentIndex的初始值 } }, template: ` <div class="select"> <h2>{{title}}</h2> <p @click="toggleShow">请选择:{{list[currentIndex]}}</p> <ul v-show="show"> <li v-for="item,index in list" :key="index" @click="changeIndex(index)" > {{item}} </li> </ul> </div> `, methods:{ changeIndex(index){ // 改变本身内部状态currentIndex this.currentIndex = index; } } })
以上代码分析:
从以上的例子中能够看出来,data 中定义的就是组件内部状态,只在组件内部更改,而传递的 props 不能在组件内修改,能够经过赋值给data,修改data的值来更新组件自身的状态。
上面说的是父组件向子组件定制数据传递 props,在子组件内部会产生一些交互。
子组件内部交互一旦发生后,父组件是须要根据子组件的交互会产生一些影响,好比改变颜色,显示文字等。父组件这些变换又不能写在子组件的交互中,由于子组件是通用的组件。一旦写了某个父组件的业务代码,只能和这个父组件绑定在一块儿,不能使用在别的地方了了,此时组件不能达到通用的目的。
举个例子:
如下使用了两次 custome-select 组件,当点击第一个组件的下拉框某一项时候,就须要改变 class 为 test1 的div 样式为 red 色。当点击第二个组件的下拉框某一项时候,就须要改变 class 为 test2 的div 样式为 blue 色。
<div id="app"> <!-- 城市的下拉 --> <div class="test1" :style="{color: color1}">第一个需求</div> <custome-select title="请选择城市" :list="['北京','上海','杭州']" :select-index="0"> </custome-select> <!-- 用户的下拉 --> <div class="test2" :style="{color: color2}">第二个需求</div> <custome-select title="请选择用户" :list="['张三','李四','王五']" :select-index="1"> </custome-select> </div>
使用了两次组件,组件内部点击下拉框时不能写具体的处理第一个需求仍是第二个需求。而是交到外部的父组件来决定,这时候父组件就须要知道子组件内部是否点击了下拉框。而点击下拉框这个动做是由用户触发的,不知什么时候会触发一次,那怎么办呢?
跟原生的元素处理思路同样,假定之后用户点击了这个元素后,须要改变页面中样式,那么就须要监控这个元素的点击事件,只要用户点击了,触发事件处理函数,在函数中写具体改变样式这个动做。
HTML代码:
<button onclick="handle()">按钮</button>
JavaScript代码:
<script> // 全局设置函数 function handle(){ document.body.style.background = 'red' } </script>
以上代码是 DOM0 级时代的写法,直接在行间写监听事件,这样写更直观。目的就是当有用户点击了按钮一下,浏览器内部就会发布一个 click 事件,而正好咱们在元素上监听了 click 事件,就会把对应的事件处理函数触发,从而达到开发者的目的,对页面作出一些变化。
组件标签使用在模板中,此时外界须要知道组件内部发生了的交互,那么思路一致,也须要在行间监听事件,不过此事件名字不限因而 w3c 规定的事件名,能够自定义事件名,结合 Vue 中绑定事件的方式,代码以下:
<div id="app"> <!-- 城市的下拉 --> <div class="test1" :style="{color: color1}">第一个需求</div> <custome-select title="请选择城市" :list="['北京','上海','杭州']" :select-index="0" @click-option="changeTest1Handle" > </custome-select> <!-- 用户的下拉 --> <div class="test2" :style="{color: color2}">第二个需求</div> <custome-select title="请选择用户" :list="['张三','李四','王五']" :select-index="1" @click-option="changeTest2Handle" > </custome-select> </div>
把事件处理函数写在选项对象中:
new Vue({ el: '#app', data: { color1: '', color2: '' }, methods: { // 第一个需求 changeTest1Handle(){ this.color1 = 'red'; }, // 第二个需求 changeTest2Handle(){ this.color2 = 'blue'; } } })
以上代码准备完毕,去点击下拉选项,并无触发父组件的函数,并无完成需求,为何呢?
在原生元素上在行间监控事件,用户点击元素后,浏览器会发布 click 事件。而如今换作是使用自定义事件来监控子组件内部产生的交互,这就须要在子组件内部本身发布这个自定义的事件,不然监控的自定义事件是无效的。
那何时发布事件呢?就是在用户点击了下拉框的选项时候发布这个自定义事件便可。
你能够这样来理解,监听原生事件 click ,只须要监听,开发者无需手动的在浏览器内部写发布事件,click 事件名是浏览器给开发者约定的名字。而如今咱们须要本身设计子组件发布事件,父组件监听这样的机制。因此须要开发者本身约定事件的名字和手动的在组件中发布事件。在 Vue 中这样的订阅/发布模式已经写好,开发者只须要调用便可。
在子组件中发布事件:
// 其余代码省略 methods:{ changeIndex(index){ this.currentIndex = index; // 在点击选项时候产生交互,手动发布事件,通知父组件 this.$emit('click-option'); } }
当点击选项时候,父组件中会完成不一样的需求,改变不一样元素的颜色。
以上代码父子组件之间彻底的解耦,父组件中不使用这个组件,依然能够工做,子组件不使用在这个组件中,可使用在任意其余的组件中。若是父组件关系子组件内部选中下拉框一项这个交互,只须要监听 click-option这个自定义事件,不关心则不监听。
以上能够看出一个组件数据的来源有两个:
父子组件之间通讯:
以上属于我的理解,若有误差欢迎指正学习,谢谢。