1. 什么是组件
- 传统方式开发
- 一个网页通常包括三部分:结构(HTML)、样式(CSS)、交互(JavaScript)
- 组件化开发
- 每一个组件都有独立的 js,独立的 css,这些独立的 js 和 css 只供当前组件使用,不存在纵横交错。 更加便于维护
- 代码复用性增强。组件不仅让 js css 复用了,HTML 代码片段也复用了(因为要使用组件直接引入 组件即可)
- 什么是组件?
- 组件:实现应用中局部功能的代码和资源的集合。凡是采用组件方式开发的应用都可以称为组件化应用
- 模块:一个大的 js 文件按照模块化拆分规则进行拆分,生成多个 js 文件,每一个 js 文件叫做模块。凡 是采用模块方式开发的应用都可以称为模块化应用。
- 任何一个组件中都可以包含这些资源:HTML CSS JS 图片 声音 视频等。从这个角度也可以说明组件 是可以包括模块的
2. 组件创建、注册和使用
<div id="app">
<h1>{{msg}}</h1>
<!-- 3. 使用组件 -->
<userlogin></userlogin>
<userlist></userlist>
<userlogin></userlogin>
</div>
const myComponent = Vue.extend({
template : `
<ul>
<li v-for="(user,index) of users" :key="user.id">
{{index}},{{user.name}}
</li>
</ul>
`,
data(){
return {
users : [
{id:'001',name:'jack'},
{id:'002',name:'lucy'},
{id:'003',name:'james'}
]
}
}
})
// 全局注册
Vue.component('userlogin', userLoginComponent)
// 2. 注册组件(局部注册)
components : {
// userlist是组件的名字。myComponent只是一个变量名。
userlist : myComponent,
}
})
</body>
</html>
1.组件使用三步走
- 第一步:创建组件
Vue.extend({该配置项和new Vue的配置项几乎相同,略有差别})- 区别:
- 1.创建Veu组件时,配置项中不能使用el配置项(但需要template配置项来配置模板语句)
- 2.配置项中daya不能使用对象形式,必须使用function
- 第二步:注册组件
- 局部注册:
- 在配置项中使用
components,语法格式:
components : { // key:value 组件的名字 : 组件对象 } - 在配置项中使用
- 全局注册:
Vue.compont("key",value) - 使用组件: 在页面中需要使用组建的位置:
- 局部注册:
2.使用组件小细节
- 在Vue当中是可以使用自闭合标签的,但是前提必须在脚手架环境中使用。
- 在创建组件的时候
Vue.extend()可以省略,直接写:{}但是底层实际上还是会调用的,在注册组件的时候会调用。 - 组件的名字:
- 第一种:全部小写
- 第二种:首字母大写,后面都是小写
- 第三种:kebab-case命名法(串式命名法。例如:user-login)
- 第四种:CamelCase命名法(驼峰式命名法。例如:UserLogin),但是这种方式只允许在脚手架环境中使用。
- 不要使用HTML内置的标签名作为组件的名字。
- 在创建组件的时候,通过配置项配置一个name,这个name不是组件的名字,是设置Vue开发者工具中显示的组件的名字。
3.组件嵌套
<div id="root"></div>
<script>
// 创建Y1组件
const y1 = {
template : `
<div>
<h3>Y1组件</h3>
</div>
`
}
// 创建X1组件
const x1 = {
template : `
<div>
<h3>X1组件</h3>
</div>
`
}
// 创建Y组件
const y = {
template : `
<div>
<h2>Y组件</h2>
<y1></y1>
</div>
`,
components : {y1}
}
// 创建X组件
const x = {
template : `
<div>
<h2>X组件</h2>
<x1></x1>
</div>
`,
components : {x1}
}
// 创建app组件
const app = {
template : `
<div>
<h1>App组件</h1>
<x></x>
<y></y>
</div>
`,
// 注册X组件
components : {x,y}
}
// vm
const vm = new Vue({
el : '#root',
template : `
<app></app>
`,
// 注册app组件
components : {app}
})
</script>
4.vm与vc区别与联系
1.this
- new Vue({})配置项this:
Vue实例(vm) - Vue.extend({})配置项this:
VueComponent实例(vc) - vm与vc有大量的相同属性. 比如:生命周期钩子、methods、watch
2. vm === vc?
- 差不多,但不完全相等
- 列如:
- vm上有el,vc上没有
- vc的data必须是一个函数;而vm的data可以是对象,也可以是函数 总结:vm上有的vc上不一定有,vc上有的vm上一定有
3, Vue.extend()方法做了什么?
- 每一次的extend调用返回的都是一个全新的VueComponent函数
- Vue.extend()部分核心源码:
- 注意:每一次都会返回用户全新的VueComponet构造函数 是全新的
- 什么时候会调用构造函数来实列化VueComponent对象呢?
- Vue 在解析时会创建一个 VueComponent 实例,也就是:new VueComponent()
4.通过vc可以访问Vue原型对象上的属性
- 通过 vc 可以访问 Vue 原型对象上的属性:
Vue.prototype.counter = 100
console.log(vc.counter) // 100
- 这样设计的好处?
- 可以提高代码复用效率.Vue原型对象上有很多方法.
- 例如:mount(),代码得到了复用
- 实现机制?
VueComponent.prototype.__proto__ = Vue.protot
5.原型对象复习
prototype:显示原型对象,用法:函数.prototype,例如:Vue.prototype_proto_:隐式原型对象,用法:实例.proto,例如:vm.proto- 无论是通过 prototype 还是__proto__,获取的对象都是同一个,它是一个共享的对象,称为:XX 的原型对象
- 如果通过 Vue.prototype 获取的对象就叫做:Vue 的原型对象。
- 如果通过 User.prototype 获取的对象就叫做:User
- 图解:
6.原理剖析
VueComponent.prototype.__proto__ = Vue.protot
- 这样设计的好处
- 最终结果:Vue、vm、VueComponent、vc 都共享了 Vue 的原型对象(并且这个 Vue 的原型 对象只有一个)
5.单文件组件
1.什么是单文件组件?
- 一个文件对应一个组件
- 单文件组件的名字通常是:x.vue,这是 Vue 框架规定的,只有 Vue 框架能够认识,浏览器无法直接打开运行。需要 Vue 框架进行编译,将 x.vue 最终编译为浏览器能识别的 html
- 单文件组件的文件命名规范和组件名的命名规范相同:
- 全部小写:userlist
- 首字母大写,后面全部小写:Userlist
- kebab-case 命名法:user-list
- CamelCase 命名法:UserList(我们使用这种方式,和 Vue 开发者工具呼应。)
2.x.vue文件内容包括三块:
- 结构:
<template>HTML代码</template> - 交互:
<script>Js代码</script> - 样式:
<style>css代码</style>
3.export和import,ES6模块化语法
- 使用
export导出(暴露)组件,在需要使用组件都x.vue中使用import导入组件- 默认导入和导出
- export default{}
- import 任意名称 from '模块标识符'
- 按需导入和导出
- export{a,b}
- import {a,b} from '模块标识符'
- 分别导出
- export var name = ‘zhangsan’
- export function sayHi(){}
- 默认导入和导出
4. vue代码提示:vetur 插件
6. props配置
- 使用props配置可以接受其他组件传过来都数据,让组件都数据变为动态数据,共有三种接收方式:
- 简单接受
props : ['name','age','sex']
- 接收时添加类型限制
props : { name : String age : Number sex : String }
- 接收时添加类型限制,必要性限制,默认值
props : {
name : {type : Number, required : true },
age : { type : Number, default : 10 },
sex : { type : String, default : ‘男’ }
}
- 其他组件怎么将数据传递过来?
<User name="jack" age="20" sex="男"></User>
- 注意:
- 不要乱接收,接收的一定是其它组件提供的
- props 接收到的数据不能修改。(修改之后会报错,但页面会刷新。)可以找个中间变量来解决
7.从父组件获取子组件
1.ref作用
ref被用来给元素或者子组件注册引用信息- 引用信息会被注册在父组件都
$refs对象上 - 如果DOM元素上使用,引向都就是DOM元素
- 如果使用在子组件上,引用就指向组件实例
2.ref的使用
- 在组件上使用ref属性进行标识:
<User ref="userJack"> </User>
- 在程序中使用
$refs来获取子组件:
this.$refs.userJack
- 访问子组件的属性
this.$refs.userJack.name
- 访问子组件都子组件属性:
this.$refs.userJack.$refs.name
- ref 也可以使用在普通的 HTML 标签上,这样获取的就是这个 DOM 元素:
<input type="text" ref="test">
this.$refs.test
8.mixins配置(混入)
- 运行结果
- 该 代码中有
Vip.vue和User.vue代码中都有相同都methods,怎么将代码进行复用? - 使用
mixins配置进行混入. - 第一步:提取
- 单独定义一个 mixin.js(一般和 main.js 在同级目录),代码如下:
- 第二步:引入并使用
- 以上演示的是
methdos混入。如果已经有了一个a方法,那么再混入一个a方法会怎么样?
- 通过该代码可知:如果冲突了,会执行组件自身的,不会执行混入的 。(这是原则:混入的意思就是不破坏)
- 对于生命周期钩子函数来说,混入时,会采用叠加方式:
- 执行结果:
- 对于生命周期钩子来说,都有的话,采用叠加,先执行混入,再执行本身的
- 以上混入属于局部混入,只能混入到指定组件中
- 全局混入:
- 执行结果
- 一共四个组件,所以输入四次
mixin mounted
9. plugins配置(插件)
1. 插件定义
- 给Vue做功能增强的
- 怎么定义插件?以下上定义插件并暴露插件。插件是一个对象,对象中必须有
install方法,这个方法会被自动调用
- 插件一般都放在一个plugins.js文件中
- 导入插件并使用插件:
2.插件对象的install方法两个参数
- 第一个参数:Vue构造函数
- 第二个参数:插件使用者传递的数据
10.局部样式scope
- 默认情况下,在vue组件中定义的样式最终会汇总到一块,如果样式名一致,会导致冲突,冲突发生后,以后来加载的组件样式为准。怎么解决这个问题?
- 另外 vue 组件的 style 样式支持多种样式语言,例如:css、less、sass 等。如何选择使用呢?
- 使用less注意安装less-loader:npm i less-loader
- App跟组件中的样式style不建议添加scoped
11. 组件自定义事件
1.关于内置事件都实现步骤
- 第一步:提供事件源(以下这个按钮就是一个事件源)
- 第二步:给事件绑定事件
- v-on:事件名 或者 @事件名
- 第三步:编写回调函数,将回调函数和事件进行绑定
- 第四步:等待事件的触发,只要事件触发,则执行回调函数
2.关于组件的自定义事件,实现步骤:
- 第一步:提供事件源(这个事件源上一个组件)
- 第二步:给组件绑定事件
- v-on:事件名 或者 @事件名
- 第三步:编写回调函数,将回调函数和事件进行绑定
- 第四步:等待事件的触发,只要事件触发,则执行回调函数
- 对于组件自定义事件来说,要想让事件发生,需要去执行一段代码
- 这段代码负责去触发这个事件,让这个事件发生
- 这段代码写在哪里?
- 事件绑定在A组件上,则触发这个事件都代码要在A组件中编写
- 这段代码写在哪里?
3.解绑事件
- 哪个组件绑定就找哪个组件解绑:
methods : {
// 这种方式只能解绑一个事件。
unbinding(){ this.$off(‘event1’)
// 这种方式解绑多个事件。
this.$off([‘event1’, ‘event2’])
this.$off() // 解绑所有事件。
}
}
- 注意;vm和vc销毁的时候,所有组件以及子组件当中的事件会全部解绑
4.总结:到目前为止,父子组件之间如何通信
- 父 --> 子:
props - 子 --> 父:
- 第一种:在父中定义一个方法,将方法传递给子,然后在子中调用父传过来的方法,这样给父传数据。(这种方式以后很少使用)
- 第二种:使用组件自定义事件的方式
- App组件是父组件
- User组件上子组件
- 子组件向父组件传递数据(User给App组件传数据)
- 在父组件绑定事件
- 在子组件触发事件
- 父绑定,子触发
4.对于事件都once修饰符来说,组件都自定义事件也是可以使用的
- App部分代码
<div>
<button @click.once="hello">内置事件的实现步骤</button>
<!-- 给User组件绑定一个自定义的事件 -->
<!-- <User v-on:event1.once="doSome"></User> -->
<User v-on:event1="doSome" @event2="doOther"></User>
<!-- 简写形式 -->
<!-- <User @event1.once="doSome"></User> -->
<User @event1="doSome" @event2="doOther"></User>
<!-- 准备一个组件 -->
<User ref="user"></User>
</div>
// 组件自定义事件在mounted()函数触发
mounted() {
// 给ref="user"的组件绑定event1事件,并且给event1事件绑定一个回调函数:doSome
this.$refs.user.$on('event1', this.doSome)
// 如果回调函数是普通函数:函数体当中的this是User组件实例。不是App组件实例。
/* this.$refs.user.$on('event1', function(){
console.log(this)
}) */
// 如果回调函数是箭头函数:那么函数体当中的this就是App组件实例。
/* this.$refs.user.$on('event1', () => {
console.log(this)
}) */
this.$refs.user.$on('event2', this.doOther)
// 保证事件只触发一次。
//this.$refs.user.$once('event1', this.doSome)
},
methods: {
hello(){
console.log('hello vue!')
},
/* doSome(name, age, gender){
console.log(name, age, gender)
} */
// ES6的语法,...params这个params可以看做是一个数组。以数组的形式接收多个参数。
doSome(name, ...params){
console.log(name, params)
},
doOther(){
console.log('do other!')
}
},
- User部分代码
<div>
<button @click="triggerEvent1">触发event1事件</button>
<button @click="triggerEvent2">触发event2事件</button>
<button @click="unbinding">解绑事件</button>
<button @click="goodbye">再见</button>
</div>
methods: {
triggerEvent1(){
// 编写触发event1事件的代码
// this是当前的组件实例:vc
// 触发事件的同时,可以给事件绑定的回调函数传数据
this.$emit('event1', this.name, this.age, this.gender)
},
triggerEvent2(){
this.$emit('event2')
},
goodbye(){
this.$destroy()
},
// 解绑事件
unbinding(){
// 仅仅解绑this指向的这个组件实例上的event1事件。
//this.$off('event1')
//this.$off(['event1', 'event2'])
this.$off()
}
},
}
12. 全局事件总线
1.原理
- 给项目中所有组件找一个共享的vc对象。把这个共享的对象vc叫全局事件总线。所有的事件都可以绑定到这个共享对象上。
- 所有组件都通过这个全局事件总线对象来传递数据。这种方式可以完美都完成兄弟组件传达数据.
- 这样的共享对象必须具备两个特征:
- 1.能够让所有的vc共享
- 2.共享对象上有
$on``$off``emit等方法
- 第一种解决方案:
在 main.js 文件中:
// 获取 VueComponent 构造函数 c
onst VueComponentConstructor = Vue.extend({})
// 创建 vc const vc = new VueComponentConstructor()
// 让所有的 vc 都能够使用这个
vc Vue.prototype.$bus = vc
- 第二种解决方案:
在 main.js 文件中:
new Vue({ el : '#app',
render : h => h(App),
beforeCreate(){
Vue.prototype.$bus = this
}
})
- 永远记住的是:A组件向B组件传数据,在B组件中绑定事件(接).应该在A组件中触发事件(传)
- 数据发送方:触发事件
methods : { triggerEvent(){ this.$bus.$emit(‘eventx’, 传数据) } }
- 数据接收方:绑定事件
mounted(){ this.$bus.$on(‘eventx’, this.doSome) }
- 养成好习惯:组件实例被销毁前,将绑定在$bus上事件解绑
beforeDestroy(){ this.$bus.off('eventx')}