组件
什么是组件
组件是可复用的 Vue 实例,且带有一个名字:我们可以把组件作为自定义元素来使用,
例如: <button-counter>。
组件是一个功能高内聚(组件的特性都应该有,比如按钮,可以有点击效果,用户可点击,可不让用户点击),业务低耦合(用户点击了按钮要做什么事情)的一个模块。高内聚的本质也就抽象和封装,目的是为了代码结构清晰,减少代码量。低耦合的目的是为了不同服务之间不同的业务代码不混用,降低了整体宕机的风险。
以不同的组件来划分不同的功能模块,需要什么功能就去调用对应的模块
1、每一个组件都应该使用.vue文件来命名
2、每一个组件都有一些模板
3、在这个文件中使用template来标记为一个html模板,在这里边编写html,和之前页面写法一致--template
4、通过之前的学习知道,createApp方法要接收一个对象,所以这个页面要导出一个对象--js --script
5、样式 -- style
vue文件在脚手架中运行的时候,会被编译为一个js代码,如果有template模板,那么它会被编译成一个render方法
scoped给样式添加作用域
给元素添加一个属性data-v-xxx,并且给样式添加属性选择.btn[data-v-782f4806]
<style scoped>
.btn {
display: inline-block;
vertical-align: middle;
line-height: 32px;
text-align: center;
font-size: 14px;
}
</style>
【面试】为什么在vue组件中data要写成函数而不是对象?
1. vue组件本质上是一个vue实例对象
2. 如果data是对象,所有vue组件实例对象都会共享data对象数据,
如果A组件改变了数据,B组件会受到影响.
3. 定义成函数,函数有自己的块作用域,每个vue实例都有自己的data块作用域相互不影响
组件化和模块化的区别
组件化:是从UI界面的角度进行划分的,根据封装的思想,把页面上可重用的 UI,结构封装为组件,从而方便项目的开发和维护。
模块化:是从代码的逻辑角度去划分的 方便代码分层开发保证每个功能模块的职能单一
使用组件
vue中组件名字不能和原生元素名字一致
<div id="app">
<h2>{{title}}</h2>
<!-- 使用组件 -->
<button-counter></button-counter> //3.将组件名用-分开使用组件
</div>
<script>
const { createApp } = Vue
//1.构造定义组件
// vue选项都能使用 data methods computed watch template模板
const ButtonCounter = { //驼峰命名
template: `<div>
<button @click="plus">{{count}} 加一</button>
</div>`,
data() {
return {
count: 0,
}
},
methods: {
plus() {
this.count++
},
},
}
const app = createApp({
data() {
return {
title: '组件学习',
}
},
})
// 2.注册组件 <button-counter></button-counter>
app.component('ButtonCounter', ButtonCounter)
app.component('ButtonMinus',ButtonMinus)
app.mount('#app') //挂载
</script>
组件和子组件的生命周期
组件和子组件的生命周期顺序,是在父组件的mounted之前会去对子组件进行初始化和挂载(这里的挂载是子组件把它自己挂载到父组件上),最后再把所有的组件全部挂载到dom上
组件通讯
一个组件(A)引用另外一个组件(B),那么B组件就是A组件的子组件,A组件是B组件的父组件
父传子 props
props是只读属性,不要修改它!!!
在子组件定义props选项,接收参数
在父组件里定义属性传参, 属性名与props选项接收参数属性名相同
<!--父-->
<div id="app">
<child username="jack" :user="users"></child>
</div>
//child子组件
props:['user']
props:{ user:Object }
props:{ user:{ type:Object, defaule:{} } }
<div id="app">
<h2>{{title}}</h2> //显示'父组件'
<child :msg="message" username="jack" :user="user"></child>
</div><!--父组件定义属性传参-->
<script src="./lib/vue.global.js"></script>
<script>
const { createApp } = Vue
//定义子组件child
const Child = {
props:['msg','username','user'],
// props:{
// msg:{
// type:String,
// default:'helloworld'
// },
// username:String,
// user:Object
// },
data() {
return {
title: '子组件',
};
},
template:`<div class="child">
<h3>{{title}}</h3>
<p>{{msg}}</p>
<p>{{username}}</p>
<p>{{user}}</p>
</div>`
}
createApp({
components:{
Child
},
data() {
return {
title: '父组件',
message:'这是来自父组件的内容',
user:{
name:'jack',
age:18
}
}
},
}).mount('#app')
</script>
注册组件时 在component里的注册组件是构造函数
在节点上的组件才是实例,所以父传子的参数要在节点的实例上传,即父组件中
子组件定义传递的属性的名字和类型、默认值、是否必传,父组件在子组件的标签上添加属性,如果没有使用绑定语法那么绑定是字符串(例如 user:"true" true是字符串而不是布尔),如果要传递具体的数据对象,那么必须使用绑定语法(:user:"true")
<!--父组件-->
<template>
<div>
<h3>父子组件传值</h3>
<button @click="parentCallback">我是一个按钮</button>
<!-- 父组件给子组件传递参数--唯一的办法就是绑定属性 -->
<!-- 绑定属性的时候,一定要注意,如果没有使用绑定语法,那么传递的都是普通属性 -->
<!-- <QfButton qflabel="保存" showPre="true"/> -->
<QfButton v-on:qfClick="parentCallback" qflabel="保存" :showPre="true"/>
<QfButton/> //组件实例化
<QfButton qflabel="返回" :showPend="true"/>
</div>
</template>
<script>
import QfButton from './QfButton.vue' //从子组件引入
export default {
// 注册的组件(QfButton)不是实例,是一个实例的构造函数--ComopentOptions--组件用于实例化的选项配置
// 在页面使用的时候才进行实例化
components: { QfButton},
methods: {
parentCallback() {
console.log('-------------------------- 父组件回调方法被触发')
console.log(arguments)
}
}
}
</script>
<!--子组件-->
<template>
<div @click="childCallback" class="qf-btn">
<i v-if="showPre" class="pre"></i>
{{qflabel}}
</div>
</template>
<script>
export default {
// 那么接收父组件传递的数据,应该使用props属性来接收数据
// 属性传递,是传递给实例的属性,那么页面上可以直接使用
props: {
// qflabel: String,
qflabel: {
type: String,
// 设置成必须传递 如果父组件没有值传来就会报错
required: true
},
showPre: {
// 数据类型
type: Boolean,
// 指定默认值
default: false
}
}
}
</script>
子传父
$emit使用
// $emit: (event: string, ...args: any[]) => void 定义$emit方法的使用规则
// 第一个参数是event: string -> 事件名字;
// ...args: any[] -> ...args 表示把参数展开,any[] 表示是数组,数组中的每一项可以是任意数据类型
// 从第二个参数开始,为这个事件传递的参数
this.$emit('qfClick', ...[1, 'string', true, {}, [], (function() {})])
法一 在外部定义: 在子组件上绑定一个自定义事件 <子组件的@click"$emit('someEvent') ">
template: `<div class="child">
<h3>{{title}}</h3>
<button @click="$emit('someEvent')">确定</button>
</div>`
法二 在内部定义: this.$emit('someEvent',参数) '事件名字',传的参数
template: `<div class="child">
<h3>{{title}}</h3>
<button @click="onEmitEvent">确定</button>
</div>`
//组件Child
methods:{
onEmitEvent(){
//触发自定义事件
this.$emit('someEvent',this.count) //count是data()里的 这里的自定义事件用驼峰命名
}
在使用子组件的地方用刚刚自定义的事件,就是body里用-把驼峰命名分开的div里
@some-event="callback"
<div id="app">
<h2>{{title}}</h2>
<p>显示子组件内容: {{num}}</p>
<child @some-event="callback"></child> //用-将事件的驼峰命名分开
</div>
methods:{ //父组件的方法里
callback(num){
// alert('触发自定义事件someEvent :'+num)
this.num = num
}
}
$parent
可以获取父组件实例 this.$parent===父组件的this
最大的坏处是,人家用你的组件还必须起你定义的变量和方法名字
this.$parent.pcb(3) //直接调用父组件中的pcb()方法并传3
<!--父组件-->
<template>
<div>
<h3>父组件</h3>
<h5>
当前组件的值:{{value}}
</h5>
<Child @qfcb="pcb"/>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: { Child },
data() {
return {
value: 10
}
},
methods:{
pcb(step) {
this.value += step //点击+3
},
}
}
</script>
<template>
<div>
<hr>
<p>
<button @click="myClickFunc">直接改变父组件的value值</button>
</p>
<h6>
子组件的值:{{count}}
</h6>
</div>
</template>
<script>
export default {
data() {
return {
count: 1
}
},
methods: {
myClickFunc() {
// 可以获取父组件实例 this.$parent===父组件的this
// this.$parent.value += 10
// 最大的坏处是,人家用你的组件还必须起你定义的变量和方法名字
this.$parent.pcb(3) //直接调用父组件中的pcb()方法并传3
}
}
}
</script>
refs属性
vue中不允许直接使用document.getElementById等方法来获取dom元素,应该使用关联属性(ref)来获取虚拟dom关联的真实dom
非必须情况下不要使用,因为组件内部的数据和方法是组件自己封装的,有可能调用了实例的方法,导致组件业务错误
一定是接口文档给出来的接口才可以调用,否则不能使用
定义
ref 普通元素上
组件上
ref="名称"
<div id="app">
<p ref="p1">元素一</p> //可在普通元素上
<son ref="sonref"></son> //在需要的子组件上
<button @click="getPEle">确定</button>
</div>
获取
this.$refs.名称
可获取整个节点内容,通过对象.方法调用里面的方法、元素等
<script>
const {createApp} = Vue
const Son = {
data() {
return {
title: '子组件',
count:100
};
},
template:`<div>
<h1>{{title}}</h1>
</div>`,
methods:{
onPlus(){
console.log('onplus 子组件方法');
}
}
}
createApp({
components:{
Son
},
methods: {
getPEle(){
let pEle = this.$refs.p1 //获取整个p1节点
console.log(pEle); //<p>元素一</p>
let sonEle = this.$refs.sonref
console.log(sonEle.title);
sonEle.onPlus() //同样能调用son的方法
console.log(sonEle.count);
}
},
}).mount('#app')