新的一天又开始啦,你们也应该看到个人标题了,是滴,Vue基础基本就到这里了,我们回头看看这一路,若是你都看了,而且都会写了,那么如今你就能够本身写一个Demo了,若是再了解一点路由,ajax请求(这里是axios),那么你就能够准备面试前端了,哈哈,固然没有这么夸张,日后的路还很长,至少我们基础都会了。html
这里我们再温习下以前讲了哪些基础知识:前端
《十五 ║Vue前篇:了解JS面向对象原理 & 学会嵌套字面量等4种函数定义 & this指向》vue
《十六 ║Vue前篇:ES6知识详细说明 & 如何在JS中进行模块化编程》ios
《十七 ║Vue基础:第一次页面引入Vue.js,了解 Vue实例是如何经过数据驱动来操做DOM的》git
《十八 ║Vue基础: 学习了经常使用的十大指令并一一举例,而后了解到了Vue的计算属性,侦听器,固然还有过滤器》github
《十九 ║Vue基础: 经过样式的动态绑定,进一步学习Vue是如何操做DOM的,而后深刻了解生命周期的八个阶段,以及其中的职能》面试
一共是五篇,基本已经涵盖了Vue的基础知识,今天呢,再说完组件之后,明天就正式开始搭建本地脚手架,终于开始安装软件了[ 哭笑 ],我这几天考虑了一下,在以后的讲解中,可能须要两个小项目的讲解,第一个就是我如今本身用到的一个,你们其实也能够看看 http://vue.blog.azlinli.com(买的服务器很差,首次加载慢),也不是啥隐私,这个是我以前练习的时候本身瞎玩的,只有首页和详情页,用的数据就是我们在以前系列里讲到的.net core api,这个可能在本周,或者本周末说到,主要的就是把以前的讲解给穿起来,而后再说下如何使用路由 v-router 和 ajax请求——axios,这样我这个项目就说到这里,而后第二个也是一个博客系统,用的是一套数据,只不过是用到了 Nuxt 框架了,基本结构又发生了变化,项目总体被封装了,更趋于工程化,至于为何要用到这个,就是由于它能够解决 MVVM 先后端分离的 SEO 的老大难的问题,你们能够先问问度娘,到时候都会说到滴。好啦,开始今天的基础篇最后一章节 —— 深刻了解下组件。ajax
上篇文件咱们也说到了,注册组件的其中一个办法就是 Vue.component()
方法,先传入一个自定义组件的名字,而后传入这个组件的配置。编程
Vue.component('mycomponent',{ template: `<div>个人组件</div>`, data () { return { message: '老张的哲学' } } })
定义好后,咱们就能够在Vue实例所定义的DOM元素内使用它(就是咱们在new vue的时候的 el 节点),这里咱们的页脚组件,全局组件,能够在其余地方使用axios
<div id="app"> <mycomponent></mycomponent> <my-component></my-component> </div> <script> //注意要在vue实例以前去定义,否则渲染页面的时候,会报错 // 定义一个名为 footer-vue 的新组件 Vue.component('footer-vue', { template: ` <div id="footer-vue"> <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p> <p> <a href="#">京ICP备00000000号</a> </p> </div> `, data () { return { message: 'hello world' } } }) var app = new Vue({ el: '#app',//没错,vue实例所定义的DOM元素就是这个,超过了这个区域,定义的组件会无效 data: { }, }) </script>
上边,咱们定义的组件是一个全局的组件,也就是说若是咱们定义了多个 vue实例,咱们均可以使用这一个组件,这就是全局的,
固然,既然有全局的,咱们也有局部的(咱们对联系方式定义局部组件,表示只有在当前页面的app元素内使用):
注意:全局的组件是 component,而 局部的是 components
var app = new Vue({ el: '#app', data: { }, components: { 'my-component': {//这个就是咱们局部组件的名字 在页面内使用 <my-component></my-component> template: ` <ul class ="contact-list non-style-list"> <li> <b class ="twitter">TWITTER</b>: <a href="#">@laozhang</a> </li> <li> <b class ="weibo">微博</b>: <a href="#">@laozhang</a> </li> <li> <b class ="zhihu">知乎</b>: <a href="#" ></a> </li> <li> <b class ="github">GITHUB</b>: <a href="https://github.com/anjoy8">anjoy8</a> </li> <li> <b class ="email">EMAIL</b>: <a href="mailto:laozhang@azlinli.com">randypriv at azlinli</a> </li> </ul> `,
data () {
return {
message: 'hello world two'
}
},
directives:{//自定义局部指令,使用的时候,直接能够 <my-component v-focus><my-component>
focus;{
inserted(el){
el.focus();
}
}
}
}
}
})
观察一下上边两种写法与的特色,你们应该也能说出来:
相同点:组件的模板只能有一个根节点,或者说根标签只能有一个(第一个的根元素是 <div>,第二个根元素是 <ul>),若是定义成这样就是不容许的,这里是两个根元素 <div> 和 <a>:
template: `<div>个人地盘听个人,哈哈,只能在当前个人Vue实例中使用</div> <a>个人一个标签</a>
`,
咱们看到在定义组件的时候和平时定义的 data 不同,这里的定义必定要是一个函数,由于若是像Vue实例那样,传入一个对象,因为JS中对象类型的变量实际上保存的是对象的引用
,因此当存在多个这样的组件时,会共享数据,致使一个组件中数据的改变会引发其余组件数据的改变。而使用一个返回对象的函数,每次使用组件都会建立一个新的对象,这样就不会出现共享数据的问题来了。
Vue.component('footer-vue', { template: ` <div id="footer-vue"> <p> <a href="#">京ICP备00000000号</a> </p> </div> `, data :{//这是错误栗子 message: 'hello world'//咱们用普通属性的方法 } })
若是咱们按照一个属性的写法的话,页面会成功的报了一个这样的错误,并且你们注意,这个错误是出如今哪里的,没错就是挂载结束前,也就是说,和实例化数据没影响,可是在挂载到页面,页面渲染的时候,出现了这个错误,因此你们在初学的时候,仍是要多了解下生命周期的概念。
注意:由于组件是可复用的 Vue 实例,因此它们与
new Vue
接收相同的选项,例如data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像el
这样根实例特有的选项。
Vue.extend()建立,而后由component来注册,两步
// extend 建立组件 var MyComponent = Vue.extend({ template: '<div>A custom component!</div>' }); // component注册 组件 Vue.component('my-component', MyComponent);//使用到了 extend 建立的组件 var vm = new Vue({ el: '#example', data: { } })
两种写法没有什么太多的区别,基原本说
extend 是构造建立一个组件的语法器,你给它参数 他给你建立一个组件, 而后这个组件,你能够做用到Vue.component 这个全局注册方法里, 也能够在任意vue模板里使用apple组件
var apple = Vue.extend({
….
})
Vue.component(‘apple’,apple)你能够做用到vue实例或者某个组件中的components属性中并在内部使用apple组件
new Vue({
components:{
apple:apple
}
})
可见上边的定义过程比较繁琐,也能够不用每次都调用两个,能够直接用 Vue.component 建立 ,也能够取组件 例以下
var apple = Vue.component(‘apple’)
<template id="temApp">//这里是定义一个id, <div> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">Form</router-link> | <router-link to="/Vuex">Vuex</router-link> </div> <router-view/> </div> </template> //在使用的时候,直接引用 #temApp Vue.component('footer-vue', { template:'#temApp', data () { return { message: 'hello world' } } })
你必定在开发中会遇到这样的需求,就是一个banner的切换:
咱们这时候可使用动态组件,很容易实现这个需求,经过 Vue 的 <component>
元素加一个特殊的 is
特性来实现:
<!-- 组件会在 `currentTabComponent` 改变时改变 --> <component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent
能够包括
<div id="dynamic-component-demo" class="demo"> <button v-for="tab in tabs"//for 循环展现 banner v-bind:key="tab" v-bind:class="['tab-button', { active: currentTab === tab }]" //这里语法错误,删除本行注释!绑定样式,当前组件增长 active 样式 v-on:click="currentTab = tab" >{{ tab }}</button> <!-- 组件的使用 经过currentTabComponent 来动态展现是哪个组件 --> <component v-bind:is="currentTabComponent" //这里语法错误,删除本行注释!经过 is 特性,来动态实现组件,核心 class="tab" ></component> </div> //定义三个组件,能够好比是咱们的三个页面, Vue.component('tab-home', { template: '<div>Home component</div>' //组件1,也就是页面1 }) Vue.component('tab-posts', { template: '<div>Posts component</div>' //组件2,页面2 }) Vue.component('tab-archive', { template: '<div>Archive component</div>' //组件3,页面3 }) new Vue({ el: '#dynamic-component-demo', data: { currentTab: 'Home',//当前banner名称 tabs: ['Home', 'Posts', 'Archive']//定义三个banner }, computed: {//计算属性,实时监控获取固然banner的值,并返回到页面 currentTabComponent: function () { return 'tab-' + this.currentTab.toLowerCase()//组件名称拼串,必定要和上边的三个组件名对应 } } })
注意:这里可使用 <keep-alive> 来缓存固然组件的内容
<keep-alive>
<component :is="currentTabComponent"></component>
<keep-alive>
在 Vue 中,父子组件的关系能够总结为 prop
向下传递,事件
向上传递。父组件经过 prop
给子组件下发数据,子组件经过事件
给父组件发送消息,这里我们先说下向下传递,经过属性Props属性来完成。
还记得以前我们说的,Vue 其实就是由许许多多个组件拼接而成,高效复用,相互之间能够传值,可是又不受影响,最多见的应用就是:组件 A 在它的模板中使用了组件 B。它们之间必然须要相互通讯:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。你们第一次使用的时候可能会有点儿不舒服,可是使用熟练之后,就会发现真的驾轻就熟,因此我们就先看看组件是如何通信的。
首先你们还记得我们定义的页脚组件么,就是刚刚说到的。我们看到最下边是备案号,如今想在备案号旁边加上咱的昵称”老张的哲学“,想一想很简单嘛,想一想确定不能直接写死数据吧,正好看到页面内定义vue实例的时候,有这个属性嘛,直接放到我们的页脚组件里,嗯就是这样:
// 定义一个名为 footer-vue 的新组件 Vue.component('footer-vue', { template: ` <div id="footer-vue"> <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p> <p> <a href="#">京ICP备00000000号{{authorHtml}}</a> </p> </div> `, data () { return { message: 'hello world' } } })
而后满怀开心的刷新页面一看,额报错了:
而后很熟练的必应翻译了一下(这是一个反面教材,你们要好好学习英文,多看国外教程 [苦笑] ),获得这样的:属性或方法 "authorHtml " 不该该是在实例上定义的, 而是在呈现过程当中引用的。经过初始化属性, 确保此属性在数据选项或基于类的组件中是被动的。说人话就是,这里不能使用实例上定义的值,好吧,查阅资料发现,组件只能被动的接受父组件的传参数,嗯因而乎咱们这样写:
<footer-vue :foo="author"></footer-vue>//在自定义的组件上,新增一个动态属性,而后属性的值 author 是父组件的,这个时候父组件就是已经把值给发过去了
这个时候,咱们就须要在子组件里接收一下
// 定义一个名为 footer-vue 的新组件 Vue.component('footer-vue', { template: ` <div id="footer-vue"> <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p> <p> <a href="#">京ICP备00000000号{{foo}}</a>//这里根据自身的props的参数来赋值 </p> </div> `, props: ['foo'],//这里根据组件的props属性,来被动接受组件传递来的参数 data () { return { message: 'hello world' } } })
刷新页面,这时候就真正的成功了。
这里咱们获得的结果和上边的是同样的,直接是一个字符串结果,而不是一个变量属性。
Vue.component('child', { // 声明 props props: ['message'], // 就像 data 同样,prop 也能够在模板中使用 // 一样也能够在 vm 实例中经过 this.message 来使用 template: '<span>{{ message }}</span>' }) <child message="老张的哲学"></child>
上边我们能够看到,经过父子传值,咱们把数据传递了过去,可是这个时候你须要思考,咱们只能传递数据 data 么,答案是否认的,咱们还能够传递方法事件 Function !很简单,直接看代码:
// 实例化 Vue var V = new Vue({ el: '#app', data: { now:'', lists:[ {id:1,name:'孙悟空'}, {id:2,name:'猪八戒'}, {id:3,name:'沙和尚'}, {id:4,name:'唐僧'}, {id:5,name:'小白龙'}, ], answer: 'Time is:' }, watch: { }, methods: { run(){ console.log(77) } }, mounted() { //this.now=this.dateFtt("yyyy-MM-dd hh:mm:ss",new Date());; } }) // 调用子组件,传递数据 lists 和事件 run <child :lists="lists" :run="run"> <template slot-scope="a"> {{a}} </template> </child> // 定义子组件 Vue.component('child',{ props:['lists','run'], methods:{ testRun(){ this.run() } }, template:` <div @click='testRun'> <ul> <li v-for="list in lists"> <slot :bbbbb="list"></slot> </li> </ul> </div> ` });
而后咱们点击 div,就会触发父组件事件:
是否是很简单,父子传值,方法事件也是特别经常使用的,你们要多学习学习。
注意:HTML 特性是不区分大小写的。因此,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 须要转换为相对应的 kebab-case (短横线分隔式命名),好比 如何上边的foo 写成了 fooPro ,那咱们定义属性的时候,就应该写 foo-pro
<footer-vue :foo-pro="author"></footer-vue>
若是咱们写了 fooPro 这样的写法,挂载页面的时候,就会警告,而且不会成功渲染
若是你使用字符串模板,则没有这些限制。(字符串模板:指的是在组件选项里用 template:"" 指定的模板,换句话说,写在 js 中的 template:"" 中的就是字符串模板。)
这个其实很简单,使用的也通常,不是不少,须要安装一个库 pubsub-js ,而后咱们就能够在任何组件内来实现订阅发布,从而来实现通信了:
父组件 —— 订阅消息(绑定事件监听)
子/孙组件 —— 发布消息(触发事件)
咱们知道,父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通讯呢?这个时候 Vue 的自定义事件系统就派得上用场了。
每一个 Vue 实例都实现了事件接口,即:
Vue 的事件系统与浏览器的 EventTarget API
有所不一样。尽管它们的运行起来相似,可是 $on
和 $emit
并非addEventListener
和 dispatchEvent
的别名。
另外,父组件能够在使用子组件的地方直接用 v-on
来监听子组件触发的事件。
//一、子组件内,有一个click,当点击的时候 触发 incrementCounter 方法
//二、方法被触发之后,向父组件 发送一个信号广播,并传递参数 counter,名字就是 increment。
//三、父组件经过监听,来获取到这个广播信号 increment ,而后触发 incrementTotal 方法
//四、incrementTotal 被触发,获取到参数 counter 值,执行相应的操做
<div id="counter-event-example"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter>//三、父组件经过监听,来获取到这个广播信号 increment ,而后触发 incrementTotal 方法 </div> Vue.component('button-counter', { template: '<button v-on:click="incrementCounter">{{ counter }}</button>',//一、子组件内,有一个click,当点击的时候 触发 incrementCounter 方法 data: function () { return { counter: 0 } }, methods: { incrementCounter: function () { this.counter += 1 this.$emit('increment',this.counter)//二、方法被触发之后,向父组件 发送一个信号广播,并传递参数 counter,名字就是 increment。 } },
mounted(){//经常使用,挂载完成后执行
this.incrementCounter();
} }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal(counter) {//四、incrementTotal 被触发,获取到参数 counter 值,执行相应的操做 this.total = counter } } })
上边注释的已经很清楚,就好像冒泡同样的,往上走,和 父传子,正好相反。
若是你这个看懂了,能够把上一节中的,经过属性传递方法的那种操做给改一下,改为咱们的自定义事件的方式:
<custom-input @run="run"></custom-input> <br/> {{something}} Vue.component('custom-input', { template: '<input type="text" @input="updateValue($event.target.value)"/>', methods:{ updateValue:function(value){ this.$emit('run', value) } } }) var vm = new Vue({ el: '#app', data: { something:'' }, methods:{ run(value){ this.something=value } } })
自定义事件能够用来建立自定义的表单输入组件,使用 v-model 来进行数据双向绑定。要牢记:
<input v-model="something">
这不过是如下示例的语法糖:
<input v-bind:value="something" v-on:input="something = $event.target.value">
因此在组件中使用时,它至关于下面的简写:
<custom-input v-bind:value="something" v-on:input="something = arguments[0]"> </custom-input>
<div id="app"> <custom-input v-model="something"></custom-input> <br/> {{something}} </div> // 注册 Vue.component('custom-input', { props:['something'], template: '<input type="text" v-bind:value="something" v-on:input="updateValue($event.target.value)"/>', methods:{ updateValue:function(value){ this.$emit('input', value) } } }) var vm = new Vue({ el: '#app', data: { something:'' } })
2019-06-27 更新:
<div id="app"> <child-component></child-component> </div> <script> Vue.component('child-component',{ template:` <div>Hello,World!</div> ` }) let vm = new Vue({ el:'#app', data:{ } }) </script>
Vue.component('child-component',{ template:` <div> <h4>Header.vue</h4> <slot name="girl"></slot> <div style="height:1px;background-color:red;"></div> <slot name="boy"></slot> <div style="height:1px;background-color:red;"></div> <slot></slot> </div> ` })
<child-component>
<template slot="girl">
<p style="color:red;">漂亮、美丽、购物、逛街</p>
</template>
<template slot="boy">
<strong>帅气、才实</strong>
</template>
<div>
<a href='#' >
我是一个没有表情的 a 标签
</a>
</div>
</child-component>
<child-component>
<template slot="boy">
<strong>帅气、才实</strong>
</template>
<div>
<div >
我这里须要一个div就行
</div>
</div>
</child-component>
Vue.component('child',{ props:['lists'], template:` <div> <ul> <li v-for="list in lists"> <slot :scope="list"></slot> </li> </ul> </div> ` });
var V = new Vue({ el: '#app', data: { lists:[ {id:1,name:'孙悟空'}, {id:2,name:'猪八戒'}, {id:3,name:'沙和尚'}, {id:4,name:'唐僧'}, {id:5,name:'小白龙'}, ] }, })
<div id="app"> <child :lists="lists"> <template slot-scope="a"> <div v-if='a.scope.id==1'>你好:<span>{{a.scope.name}}</span></div> <div v-else>{{a.scope.name}}</div> </template> </child> </div>
2018-09-14 更新:
若是咱们定义了一个组件 O,须要在 A、B、C三个页面使用,在O中有一部分是三个子组件相同的,有一部分是各自的不一样的,这个时候咱们就可使用 slot 分发;
内容分发的做用,就是为了组件的灵活性,在一个模板中,能够供调用者自定义部分
//声明模板 <template id="mysolt"> <div> <h3>个人公共的相同的内容</h3> <slot name="s1"></slot> </div> </template> //定义组件 Vue.component('my-solt', { template:'#mysolt', data () { return { message: 'hello world' } } }) //在a页面 <body> <div id="app"> <my-solt> <ul slot="s1"> <li>a</li> <li>a</li> <li>a</li> </ul> </my-solt> </div> </body> //在b页面 <body> <div id="app"> <my-solt> <ul slot="s1"> <p>b</p> <p>b</p> <p>b</p> </ul> </my-solt> </div> </body> //在c页面 <body> <div id="app"> <my-solt> <ul slot="s1"> <div>ccc</div> </ul> </my-solt> </div> </body>
在使用组件时,咱们经常要像这样组合它们:
<app> <app-header></app-header> <app-footer></app-footer> </app>
注意两点:
<app>
组件不知道它会收到什么内容。这是由使用 <app>
的父组件决定的。<app>
组件极可能有它本身的模板。为了让组件能够组合,咱们须要一种方式来混合父组件的内容与子组件本身的模板。使用特殊的 <slot>
元素做为原始内容的插槽。
一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:
<!-- 无效 --> <child-component v-show="someChildProperty"></child-component>
正确作法:
Vue.component('child-component', { // 有效,由于是在正确的做用域内 template: '<div v-show="someChildProperty">Child</div>', data: function () { return { someChildProperty: true } } })
假定 my-component 组件有以下模板:
<div>
<h2>我是子组件的标题</h2>
<slot>
只有在没有要分发的内容时才会显示。
</slot>
</div>
父组件模板:
<div> <h1>我是父组件的标题</h1> <my-component> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </my-component> </div>
渲染结果:
今天简单的说了下关于组件的一些问题,由于事件的问题,尚未说完,还在进一步整理当中,你们能够之后进一步的浏览本博文,经过组件的学习,你们在Vue开发的道路上又进了一步,好啦,关于Vue的基础知识就是这么多了,明天开始进入代码练习啦~~