组件基础
目录
组件基本实例
App.vue
<template>
<div>
<span style="color: blueviolet">App.Vue</span>
</div>
<HelloWorld></HelloWorld>
</template>
<script>
import HW from './components/newHelloWorld'
export default {
name: 'App',
data() {
return {
}
},
components: {
//若键值名称相同,则写一个即可
HelloWorld: HW
}
}
</script>
<style scoped>
</style>
newHelloWorld.vue
<template>
<div>
<h1>{{msg}}</h1>
</div>
</template>
<script>
export default {
name: "newHelloWorld",
data() {
return {
msg: 'Hello World!!!!'
}
}
}
</script>
<style scoped lang='scss'>
/*scoped 可设置样式只在当前组件使用,不往下传递给子组件*/
</style>
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
index.html
<div id="app"></div>
组件间的通信
例子组件层次
父组件 App.vue
<template>
<section class="conn">
<header class="header">
<my-header></my-header>
</header>
<div class="main">
<div class="content">
<my-main></my-main>
</div>
<div class="siderbar">
<my-sider-bar></my-sider-bar>
</div>
</div>
<footer class="footer"></footer>
</section>
</template>
<script>
import MyHeader from "@/components/MyHeader";
import MySiderBar from "@/components/MySiderBar";
import MyMain from "@/components/MyMain";
export default {
name: 'App',
//子组件声明
components: {
MyHeader,
MyMain,
MySiderBar
}
}
</script>
<style scoped lang="scss">
$w:600px;
$color1:#ccc;
$color2:#888;
html,body {
margin: 0;
padding: 0;
}
.conn {
width: $w;
background-color: $color1;
height: 500px;
margin: 0 auto;
}
.header {
width: $w;
height: 80px;
background-color: $color2;
}
.main {
width: 100%;
height: 300px;
background-color: yellow;
}
.footer {
width: 100%;
height: 100px;
background-color: green;
}
.content {
width: 70%;
height: 300px;
float: left;
background-color: rebeccapurple;
}
.siderbar {
width: 30%;
height: 300px;
float: left;
background-color: aqua;
}
</style>
MyHeader.vue
<template>
<div>
<h1>{{msg}}</h1>
</div>
<my-conn></my-conn>
<my-bar></my-bar>
</template>
<script>
import MyConn from "@/components/childComp/MyConn";
import MyBar from "@/components/childComp/MyBar";
export default {
name: "MyHeader",
data() {
return {
msg: 'Hello World!!!!'
}
},
//子组件
components: {
MyBar,
MyConn
}
}
</script>
<style scoped></style>
MyMain.vue
<template>
<my-conn></my-conn>
</template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
components: {
MyConn
}
}
</script>
<style scoped></style>
MySiderBar.vue
<template>
<my-bar></my-bar>
<my-bar></my-bar>
<my-bar></my-bar>
</template>
<script>
import MyBar from "@/components/childComp/MyBar";
export default {
name: "MySiderBar",
components: {
MyBar
}
}
</script>
<style scoped></style>
MyConn.vue
<template>
<div class="myconn">
{{mess}}
</div>
</template>
<script>
export default {
name: "MyConn",
data() {
return {
mess: 'this is main test'
}
}
}
</script>
<style scoped>
.myconn {
width: 90%;
height: 150px;
background-color: brown;
margin: 10px;
}
</style>
MyBar.vue
<template>
<div class="mybar">
bar
</div>
</template>
<script>
export default {
name: "MyBar"
}
</script>
<style scoped>
.mybar {
width: 50px;
height: 50px;
margin: 10px;
background-color: cornflowerblue;
}
</style>
效果
1. 父组件传递子组件
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
传递的属性值时,属性名父组件和子组件最好一样,传递MyTitle,子组件就使用MyTitle
App.vue传递给MyMain.vue一个msg值和title值
v-bind
来动态传递 prop
<my-main msg="hello" :title="msg"></my-main>
......
data() {
return {
msg:'this is app data msg'
}
}
MyMain.vue从props接收,会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样。
<template>
<my-conn></my-conn>
{{msg}} {{title}}
</template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
//从上一层组件传过来的一个值,接收一个值
props: ['msg','title'],
components: {
MyConn
}
}
</script>
<style scoped></style>
效果
如果传递一个数组
App.vue
<my-main msg="hello" :title="msg" :article="article"></my-main>
......
data() {
return {
msg:'this is app data msg',
article: ['one','two','three']
}
}
MyMain.vue
<template>
<my-conn></my-conn>
{{msg}} {{title}}
<br>
<span v-for="(item,index) in article" :key="index">{{item}}<br></span>
</template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
//就要采用对象的方法,写法与数组方式不同
props: {
msg: {
type: String,
default:'#####' //设置缺省值,若无传值,等同于在data处声明一个msg:'####'一样
},
title: {
type: String,
required: true //表明该属性值必传,否则报错
},
article: {
type: Array,
default () { //Vue2的写法
return ['aaa','bbb','ccc']
},
// default: ['aaa','bbb','ccc'] Vue3支持的写法
}
},
components: {
MyConn
}
}
</script>
<style scoped></style>
效果
props可以多层传递,MyMain可以传递给MyConn,写法一样
MyConn.vue
<template>
<div class="myconn">
<p>conn content</p>
<span v-for="(item,index) in article" :key="index">{{item}}<br></span>
</div>
</template>
<script>
export default {
name: "MyConn",
props: {
article: {
type: Array
}
}
}
</script>
<style scoped>
.myconn {
width: 90%;
height: 150px;
background-color: brown;
margin: 10px;
}
</style>
MyMain.vue
<template>
<my-conn :article="article"></my-conn>
{{msg}} {{title}}
<br>
<span v-for="(item,index) in article" :key="index">{{item}}<br></span>
</template>
而App.vue里声明的article属性,一旦有变化,MyMain和MyConn就会随之变化
效果
2. 子组件传递父组件
子组件MyConn.vue
当点击这个按钮时,会触发changenum()方法,子组件可以通过调用内建的$emit
方法并传入事件名称来触发一个事件,同时传递值
<template>
<div class="myconn">
<button @click="changenum(2)">让父组件的+2</button>
<br>
</div>
</template>
<script>
export default {
name: "MyConn",
methods: {
changenum(num) {
this.$emit('mycountevent', num);
}
}
}
</script>
<style scoped>
.....
</style>
父组件MyMain.vue
组件实例提供了一个自定义事件的系统来解决这个问题。父级组件可以像处理 native DOM 事件一样通过 v-on
或 @
监听子组件实例的任意事件:
<template>
<div style="width: 200px;height: 50px;background-color: yellow">父组件的count:{{count}}</div>
<my-conn @mycountevent="mydemo"></my-conn>
<!--若不用方法,还可以是
$event可以访问子组件抛给父组件的值
<my-conn @mycountevent="count+=$event"></my-conn>
-->
</template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
components: {
MyConn
},
data() {
return {
count:0
}
},
methods: {
mydemo(data) {
this.count += data;
}
}
}
</script>
<style scoped></style>
效果
父子组件之间的访问方法
1. 子组件调用父组件的方法
子组件MyConn.vue
子组件调用父组件可以采用$parent
<template>
<div class="myconn">
<button @click="changenum">++</button>
<br>
</div>
</template>
<script>
export default {
name: "MyConn",
methods: {
changenum() {
this.$parent.add();
}
}
}
</script>
<style scoped>
......
</style>
父组件MyMain.vue
父组件声明了一个add()
<template>
<div style="width: 200px;height: 50px;background-color: yellow">父组件的count:{{count}}</div>
</template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
components: {
MyConn
},
data() {
return {
count:0
}
},
methods: {
add() {
this.count ++;
}
}
}
</script>
<style scoped></style>
效果
若是孙子组件想要访问爷爷组件的方法,也用$parent
但是要打两个$parent
,也可直接直接使用$root
孙子组件MyConn.vue
methods: {
changenum() {
this.$parent.add();
console.log(this.$parent.count) //访问MyMain.vue的count
console.log(this.$parent.$parent.msg) //访问App.vue的msg
this.$parent.$parent.appmet() //访问App.vue的appmet方法
//等价于
this.$root.appmet()
}
}
2. 父组件调用子组件的方法
$children
或 $refs
父组件MyMain.vue
<template>
<button @click="subson">让子组件-1 </button>
<my-conn ref="child"></my-conn>
</template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
components: {
MyConn
},
data() {
return {
count:0
}
},
methods: {
subson() {
console.log('父组件的subson()');
this.$refs.child.sub()
}
}
}
</script>
<style scoped></style>
子组件MyConn.vue
<template>
<div class="myconn">
子组件的num:{{num}}
</div>
</template>
<script>
export default {
name: "MyConn",
data() {
return {
num: 0
}
},
methods: {
sub() {
this.num--;
}
}
}
</script>
<style scoped>
....
</style>
效果
插槽slot
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 元素作为承载分发内容的出口。
插槽可以实现组件的扩展性 , 抽取共性, 保留不同
1. 基本使用
插槽slot是把父组件把数据渲染完了,再插到子组件里
子组件 MyBar.vue
<template>
<div class="mybar">
<h6>{{title}}</h6>
<slot></slot> <!--渲染插槽-->
</div>
</template>
<script>
export default {
name: "MyBar",
data() {
return {
title: 'title'
}
}
}
</script>
<style scoped>
.mybar {
width: 80px;
height: 80px;
margin-bottom:10px;
background-color: cornflowerblue;
}
</style>
父组件MySiderBar.vue
<template>
<my-bar>
<button>提交</button>
</my-bar>
<my-bar>
<a href="#">提交</a>
</my-bar>
<my-bar>
<p>提交文本</p>
</my-bar>
</template>
<script>
import MyBar from "@/components/childComp/MyBar";
export default {
name: "MySiderBar",
components: {
MyBar
}
}
</script>
<style scoped></style>
效果
2. 后备内容
插槽slot还可以包含任何模板代码,包括 HTML,即设置默认值,若父组件中的不提供任何插槽内容时:
<my-bar></my-bar>
<my-bar></my-bar>
<my-bar></my-bar>
而子组件为一个插槽设置具体的后备 (也就是默认的) 内容,它会在没有提供内容的时候被渲染
<div class="mybar">
<h6>{{title}}</h6>
<slot><button>提交</button></slot>
</div>
效果
3. 具名插槽
多个插槽,若没有设置插槽名称,会把所有的slot都替换掉,我们需要把slot插槽设置名称(具名插槽),按名字指定替换哪个slot,v-slot
简写为 #
v-slot:header
可以被简写为 #header
一个不带 name 的 出口会带有隐含的名字“default”。
子组件 MyBar.vue
<template>
<div class="mybar">
<h6>{{title}}</h6>
<!--第一个slot设置了后备内容-->
<slot name="one"><button>提交</button></slot><br>
<slot name="two">first</slot><br>
<slot>second</slot>
</div>
</template>
父组件MySiderBar.vue
注意:v-slot 只能添加在 上
<template>
<my-bar></my-bar>
<my-bar>
<!-- 指定替换哪个插槽 -->
<!-- v-slot 简写为 # -->
<template v-slot:one>
<a href="#" >替换文本</a>
</template>
<!-- v-slot:default 缺省名字 -->
<template v-slot:default>
<a href="#" >更新文本</a>
</template>
</my-bar>
<my-bar> </my-bar>
</template>
效果
4. 作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。当一个组件被用来渲染一个项目数组时,这是一个常见的情况,我们希望能够自定义每个项目的渲染方式。
在使用插槽时,父组件若想使用子组件在插槽里的属性值,可以像下面这样:
要想使 user和sex可用于父级提供的 slot 内容,我们可以添加一个 元素并将其绑定为属性
绑定在 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
子组件MyBar.vue
<template>
<div class="mybar">
<slot name="one"><button>提交</button></slot><br>
<slot name="two" :sex="sex">性别:男</slot><br>
<slot :user="user">名字:{{ user.name }}</slot>
</div>
</template>
<script>
export default {
name: "MyBar",
data() {
return {
title: 'title',
user: {name:'zhangsan'},
sex: '男'
}
}
}
</script>
父组件MySiderBar.vue
<my-bar>
<template v-slot:one>
<a href="#" >替换文本</a>
</template>
<!-- 插槽prop:setSex-->
<template v-slot:two="setSex">
{{ setSex.sex}}
</template>
<!-- 插槽 prop:newuser-->
<template v-slot:default="newuser">
<a href="#" >{{ newuser.user.name }}</a>
</template>
</my-bar>
效果