认识组件
组件化开发:
将一个完成的页面,划分成一个个的组件,最终由一个个组件来完成整个页面你的开发,这个过程就是组件化开发
优势:复用!!
也就是说组件只需要写一次,然后,用到组件的地方,只需要拿过去用就可以了
组成
一个组件由三部分组成:HTML+CSS+JS
两种注册组件的方式:
1 全局组件
2 局部组件
注册全局组件:
第一个参数:表示组件名称
第二个参数:是一个配置对象,这个配置对象类似于 Vue 构造函数中使用的配置对象
也就是说:在 Vue 构造函数中能够使用的配置项,几乎都可以用在组件中
template 作用:指定组件模板
注意:只能出现一个根节点
通过 data 指定组件中用到的数据
注意:组件中的数据data,是一个方法,通过方法的返回对象来作为组件数据
我的第一个全局组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用组件: -->
<hello></hello> <hello></hello> <hello></hello> <hello></hello>
</div>
<script src="./vue.js"></script>
<script>
/*
两种注册组件的方式:
1 全局组件
2 局部组件
一个组件由三部分组成:HTML+CSS+JS
*/
// 注册全局组件:
// 第一个参数:表示组件名称
// 第二个参数:是一个配置对象,这个配置对象类似于 Vue 构造函数中使用的配置对象
// 也就是说:在 Vue 构造函数中能够使用的配置项,几乎都可以用在组件中
Vue.component('hello', {
// template 作用:指定组件模板
// 注意:只能出现一个根节点
template: `
<div>
<h1>这是我的第一个组件 - Hello 组件</h1>
<span @click="fn">{{ msg }}</span>
</div>
`,
// 通过 data 指定组件中用到的数据
// 注意:组件中的数据data,是一个方法,通过方法的返回对象来作为组件数据
data() {
return {
msg: 'Vue Component data'
}
},
// 钩子函数
created() {
console.log('created 钩子函数执行了')
},
// 方法
methods: {
fn() {
console.log('方法执行了')
}
}
})
const vm = new Vue({
el: '#app',
data: {}
})
</script>
</body>
</html>
注册局部组件
通过配置项 components: {}
//html
<div id="app">
<item></item>
<wang></wang>
</div>
//js
Vue.component('wang',{
template:`
<p>我是王婷,<ting></ting></p>
`,
components:{
//组件名称
ting:{
//组件模板
template:`
<span>我最美,{{wt}}</span>
`,
//组件使用的数据
data(){
return {
wt:'确认过眼神,这是真的'
}
}
}
}
});
const vm = new Vue({
el: '#app',
data: {
msg:''
},
components:{
item:{
template:`
<p>我是一个局部的组件:{{chang}}</p>
`,
data(){
return{
chang:'验证通过'
}
}
}
}
})
两中组件的区别
1.全局组件可以在任意的地方使用
2.局部组件只能在所属组件的 模板 中使用
使用组件
组件使用中的细节点:
表单/ul/ol等中指定的标签
产生的原因:在tbody中只能使用tr标签,使用其他的标签,浏览器解析的时候,会把其他标签作为table外部标签解析出去,结构就会发生分离,ul和ol也是如此
使用is解决问题,例如tr标签
//html
<table>
<tbody>
<tr is=chang></chang>
</tbody>
</table>
//js
Vue.component('chang', {
template: `
<tr>
<td>常杰</td>
<td>李想</td>
<td>老丁</td>
</tr>
`
});
使用ref属性获取dom对象
//html
<div id="app">
<table>
<tbody>
<tr is=chang>
</chang>
</tbody>
</table>
</div>
//js
Vue.component('chang', {
template: `
<tr>
<td ref='hello' @click="handler">{{current}}</td>
</tr>
`,
data(){
return{
current:'常杰'
}
},
methods:{
handler(){
// alert('你触发了点击事件');
console.log(this.$refs.hello)
}
}
});
组件通讯:
组件是一个独立且封闭的个体,也就是说:默认情况下,组件只能使用自身的数据,而不能使用外部的数据
但是,在组件化开发过程中,两个组件通讯( 也就是两个组件使用对方法的数据 ),是很常见的问题。
这种问题就需要通过 组件通讯 机制来解决
组件通讯的三种情况:
1 父组件 传递数据给 子组件
2 子组件 传递数据给 父组件
3 非父子组件(兄弟组件)
父 -->子通讯:
父组件: Vue实例
子组件: child组件
步骤:
1 在子组件标签中添加要传递的属性
<child msg="123"></child> msg就是要传递的属性
<child :msg="parentMsg"></child> 表示传递动态数据给子组件
2 在子组件中通过 props 显示指定要接受的属性
props: ['msg']
3 此时,就可以在子组件中直接使用 msg 这个数据了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
.parent {
background-color: hotpink;
padding: 50px;
}
.child {
background-color: #daa520;
height: 100px;
}
</style>
</head>
<body>
<div id="app" class="parent">
父组件:{{ parentMsg }}
<!-- 渲染子组件: -->
<!-- <child msg="123"></child> -->
<!-- <child msg="parentMsg"></child> -->
<!-- 表示将父组件中的数据 parentMsg 传递给子组件 -->
<child :msg="parentMsg"></child>
</div>
<script src="./vue.js"></script>
<script>
// 注册子组件
Vue.component('child', {
template: `
<div class="child">
<p>这是子组件 - {{ msg }}</p>
</div>
`,
// 组件中通过 props 配置项,来显示指定要接受的数据
props: ['msg']
})
const vm = new Vue({
el: '#app',
data: {
parentMsg: '这是父组件中的数据'
}
})
</script>
</body>
</html>
子 -->父通讯
·思路:父组件提供一个方法,让子组件调用,子组件调用方法的时候将数据作为参数传递,这样,父组件就拿到子组件中传递过来的数据了
function parent(data) {
console.log('子组件中传递过来的数据:', data)
}
子组件调用方法: parent( '要传递给父组件的数据' )
实现子组件给父组件传值的过程
1.首先给父组件注册一个方法,同时在data设置一个变量准备接收子组件传出的值
2.给子组件的标签上绑定自定义事件,并把方法作为事件函数传入
3.在子组件中设置模板。在模板中设置事件,触发子组件的方法,在子组件的方法中,获取到传入的自定义事件,并且触发这个事件,传递参数
4.子组件触发这个方法,并把数据作为方法的参数传出
5.在父组件中处理触发事件的操作
//html
<div id="app">
我儿子给我发的信息:{{msg}}
<!-- 2.给子组件的标签上绑定自定义事件,并把方法作为事件函数传入 -->
<son @pmsg="fn"></son>
</div>
//js
Vue.component('son',{
// 3.在子组件中设置模板。在模板中设置事件,触发子组件的方法,在子组件的方法中,获取到传入的自定义事件,并且触发这个事件,传递参数
template:`
<p>我要给老爸发信息
<button @click="send">发送信息</button>
</p>
`,
// 4.子组件触发这个方法,并把数据作为方法的参数传出
methods:{
send(){
this.$emit('pmsg',"老爸,我资金有点....")
}
}
});
const vm = new Vue({
el: '#app',
data: {
msg:'没啥用,就占个位'
},
// 1.首先给父组件注册一个方法,同时在data设置一个变量准备接收子组件传出的值
methods:{
// 5.在父组件中处理触发事件的操作
fn(data){
this.msg = data
}
}
})
小案例:
计数器实现的步骤
- 1.首先创建一个子组件count
- 2.在组件中创建模板,在模板中绑定事件handler,插入数据num
- 3.在组件中创建模板中调用的方法handler
- 4.在模板中设置数据num
- 5.在组件标签上注册事件change,并绑定handlerChange方法(非常重要)
- 6.在组件标签上添加属性ref为one/two(非常重要)
- 7.在Vue实例中,添加方法handlerChange,并通过this.$refs.绑定的名称获取数据
- 8.获取到子组件的数据进行操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 5.在组件标签上注册事件change,并绑定handlerChange方法(非常重要) -->
<!-- 6.在组件标签上添加属性ref为one/two(非常重要) -->
<count ref="one" @change="handlerChange"></count>
<count ref="two" @change="handlerChange"></count>
<div>{{total}}</div>
</div>
<script src="./vue.js"></script>
<script>
// 1.首先创建一个子组件count
Vue.component('count',{
// 2.在组件中创建模板,在模板中绑定事件handler,插入数据num
template:`
<div @click="handler">{{num}}</div>
`,
// 4.在模板中设置数据num
data(){
return {
num: 0
}
},
// 3.在组件中创建模板中调用的方法handler
methods:{
handler(){
this.num++;
this.$emit("change")
}
}
});
const vm = new Vue({
el: '#app',
data: {
total: 0
},
// 7.在Vue实例中,添加方法handlerChange,并通过this.$refs.绑定的名称获取数据
methods:{
handlerChange(){
// console.log("handlerChange执行了")
// console.log(this.$refs.one.num)
// console.log(this.$refs.two.num)
// 8.获取到子组件的数据进行操作
this.total = this.$refs.one.num + this.$refs.two.num
}
}
})
</script>
</body>
</html>
其他通讯(非父子)
思路:
1 创建一个事件总线(空的Vue实例 bus)
2 哪个组件要接收数据,就注册事件
bus.$on(事件名称, () => {})
3 哪个组件要传递数据,就触发事件
bus.$emit(事件名称, 要传递的数据)
注意: 注册和触发事件的 bus 是同一个,事件名称也要相同
实际上,不管两个组件是什么关系,都可以通过 bus 方式来实现通讯!
步骤:
1.首先创建一个 空的vue实例的对象 bus
2.然后创建两个组件,一个临时用于接收 (wang) ,一个临时用于发送数据 (chang)
3.给两个组件设置好模板内容
4. 组件chang设置方法,在方法中通过bus.$emit获取事件,并设置触发事件的参数
5.在组件wang的钩子函数created中声明创建事件listen,并设置获取到参数后的处理
注意点:虽然用不到vm但是,必须声明创建这个实例,只有指定了vue的边界,代码才能生效
//html
<div id="app">
<chang></chang>
<wang></wang>
</div>
//js
// 空Vue实例
// 事件总线
// 1.首先创建一个 空的vue实例的对象 即事件总线bus
let bus = new Vue();
// 2.然后创建两个组件,一个临时用于接收 (wang) ,一个临时用于发送数据 (chang)
Vue.component('chang',{
// 3.给两个组件设置好模板内容
template:`
<p>我是常杰,我想对王婷说:<button @click="fn">加密发送</button></p>
`,
// 4. 组件chang设置方法,在方法中通过bus.$emit获取事件,并设置触发事件的参数
methods:{
fn(){
bus.$emit('listen','那天我一直在等你')
}
}
});
Vue.component('wang',{
template:`
<p>我是王婷,常杰对我说:{{msg}}</p>
`,
data(){
return {
msg:'接收信息中,请等待...'
}
},
// 5.在组件wang的钩子函数created中声明创建事件listen,并设置获取到参数后的处理
created(){
bus.$on('listen',data=>{
this.msg = data
})
}
});
//没有用到数据,但是必须声明
const vm = new Vue({
el:"#app",
data:{
}
});
细节补充
props的属性
props:是只读的,在组件中使用的时候,只能读取,不能修改props的值( 赋值
)如果修改这个值,会报错!!!
如果props是一个引用类型的数据,不能直接赋值,但是,可以修改引用类型中某个属性的值。
单向数据流:
是说组件之间数据流动方向是从 父组件流动到 子组件
父组件可以通过 props 将数据传递给子组件,并且,当父组件中的这个数据改变
后,这个改变后的数据会自动的再流动到子组件中。也就是说:子组件会自动接收到最
新的props数据,并且自动更新
总结:
1.props是只读的属性,只能读取props的值,而不能直接修改props的值
如果是引用类型(对象、数组),可以直接修改对象中某个属性的值,但是,如果要求很严谨,也不应该直接修改对象中某个属性的值!!!
2.单项数据流: 两个组件之间的数据流动方向,方向是: 父 -> 子
命名
HTML 标签或属性都是不区分大小写的(大小写不敏感的)
不管你写的是大写字母的标签名或属性名,还是小写的,最终,都会被转化为小写字母
同样的,在 Vue 中,给组件传递属性的时候,如果属性名中包含大写字母,在解析的时候,也会被转化为小写字母,再作为属性传递给组件
如何给props命名:
1 使用纯小写字母
<hello :childmsg="parentMsg"></hello>
props: ['childmsg']
2 传递属性的时候,使用短横线连接多个单词,在子组件中通过 驼峰命名法 来接收这个数据(规范)
传递(短横线连接) <hello :child-msg="parentMsg"></hello>
接收(驼峰命名法) props: ['childMsg']