localhost地址 = 127.0.0.1
组件拆分与嵌套
插件: vetur:提供vue语句、显示vue语句高亮效果; Vue VSCode Snippets:帮我们写好了代码片段 Vue 3 Snippets:也是代码片段
代码片段:vue-base-css(vbase)
在App.vue引入组件(全局组件/局部组件) 在main.js引入 App.vue
App.vue引入
//3.使用
<template>
<Header/>
<Main/>
<Footer/>
</template>
//1.引入
import Header from './Header.vue'
import Main from './Main'
import Footer from './Footer'
export default{
//2.注册
components:{
Header,
Main,
Footer
}
}
这里不用写Header.vue,因为cli基于webpack,帮我们在extensions中配置过了 但是最好加后缀,这样点击ctrl可以进入页面,并且引入组件时会有代码提示
在第3步使用的时候,按照组件的命名规范,应该是用小写的header和footer,但是会和html中命名冲突,所以用大写的。MainBanner = main-banner
组件的css作用域:scoped
HelloWorld.vue
<h2>Hello,World</h2>
App.vue
<style scoped>
h2{
color:red;
}
</scoped>
实际上HelloWorld.vue和App.vue的标签都会变成red;明明我已经加上了scoped属性,为什么还会造成
实际上HelloWorld的组件就是h2标签
1.但是在HelloWorld.vue上,给h2再套一层div,就可以解决问题,也许是vue-loader的bug
<div>
<h2>HelloWorld</h2>
</div>
2.不要直接给一个标签样式,而是给他一个类
<h2 class="title"></h2>
组件的通信(父子间传递数据)
父子组件如何进行通信?
1.父-->子:通过props属性
父组件App.vue
3.使用组件
<template>
<div>
<show-message title="哈哈哈" content="嘿嘿嘿"/>
注:使用data中的数据,要用v-bind
<show-message :title="title" content=":content"/>
注:使用data中对象的数据
<show-message :title="message.title" :content="message.content"/>
注:直接使用对象
<show-message v-bind="message"/>
</div>
</template>
1.引入组件
import ShowMessage from './ShowMessage.vue';
2.注册组件
export default{
components:{
ShowMessage
},
data(){
return{
title:"嘻嘻嘻",
content:"我是嘻嘻嘻",
message:{
title:"嘿嘿",
content:"我是嘿嘿",
}
}
}
}
子组件ShowMessage.vue
<template>
<div>
2.使用父组件的attribute中传过来的值
<h2>{{title}}</h2>
<p>{{content}}</p>
</div>
</template>
<script>
export default{
1.接收父组件传来的数据
props:['title','content']
}
</script>
props:可以是数组,也可以是对象
props:用来接收父组件传来的数据,名字要和标签名一样。
因为props是在组件上自定义的attribute,父组件给attribute赋值,子组件通过attribute名称获取到对应的值。
props是对象的话,可以用来限定传来的值
export default{
props:{
//1.希望得到title属性中,传来的值是string类型的
title:String,
//2.这里也可以写成对象形式
content:{
//必传且类型为String,也可以设置默认值
type:String,
required:true,
//default:"123"
}
//3.也可以是多个数据类型
counter:[String,Number],
//4.当限制类型是Object时,default的写法
info:{
//对象或数组默认值必须从一个工厂函数获取
type:Object,
default(){
return {message:"hello"}
}
}
}
}
4.为什么类型是Object的时候会要这样写?
答:因为当我多个组件使用info数据时,我其中某一个组件相对info对象中的message属性进行修改:info.message="123"。这时,其实修改的不是这个组件中的message组件,而是把公共的组件给修改了,其他组件使用message属性的时候就会发现值被改变了。
所以需要使用default(),限定某个组件需要的返回的具体数值
props补充
props命令规则和组件的命名规则一样
非Prop的Attribute
在App.vue中
//就是我show-message组件自己使用了class的 不传递数据的属性时
<show-message class = "why"/>
分为3中情况来进行处理
(1):当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中。
App.vue
<div>
<show-message class = "why"/>
</div>
实质上在ShowMessage.vue中是这样显示的(被继承到根组件了)
<div class="why">
{title}
</div>
(2):禁用Attribute继承和多根节点
在ShowMessage.vue
export default{
inheritAttrs:false,
props:{...}
}
此时App.vue中书写的非prop,就不会继承到ShowMessage的根组件上
这种场景一般发生在:我写的非prop是想给组件的,不是给组件的根标签的,就会使用禁用,然后通过$attrs来访问非props的attribute
在ShowMessage.vue
<div>
//从attrs中,取到class的属性
<h2 :class="$attrs.class">{{title}}</h2>
//当atts中的非props属性过多时
<h2 v-bind="$attrs">{{title}}</h2>
</div>
(3):多个根节点的Attribute 在App.vue
//这是id就找不到到底让哪个根标签继承,需要手工指定
<muilt-root id = "abc" />
在MuiltRoot.vue中,有多个根标签
//手动指定绑定
<h2 :id="$attrs.id"></h2>
<h2></h2>
<h2></h2>
<h2></h2>
2.子-->父:通过$emit触发事件
父组件触发子组件的+1和-1,当触发的时候,我父组件也会执行一些相应的方法
在App.vue
<template>
<div>
<h2>当前计数: {{counter}}</h2>
//3.监听事件,得到触发过来的事件以及数据,使用v-on(@),才可以调用事件方法,因为这不是内置的click方法。将触发过来的事件和这里的进行绑定。
<counter-operation @add="addOne"
@sub="subOne"
@addN="addNNum">
</counter-operation>
</div>
</template>
<script>
import CounterOperation from './CounterOperation.vue';
export default {
components: {
CounterOperation
},
data() {
return {
counter: 0
}
},
methods: {
addOne() {
this.counter++
},
subOne() {
this.counter--
},
addNNum(num, name, age) {
console.log(name, age);
this.counter += num;
}
}
}
</script>
在CounterOperation.vue
<template>
<div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<input type="text" v-model.number="num">
<button @click="incrementN">+n</button>
</div>
</template>
<script>
export default {
// 1.告诉App.vue,我要触发add、sub、addN事件
// emits: ["add", "sub", "addN"],
// 对象写法的目的是为了进行参数的验证
// null表示不用参数验证
emits: {
add: null,
sub: null,
//payload:就是要传递的数据们,具体写就是num、name、age
addN: (num, name, age) => {
console.log(num, name, age);
if (num > 10) {
return true
}
return false;
}
},
data() {
return {
num: 0
}
},
methods: {
increment() {
console.log("+1");
//2. 触发事件
this.$emit("add");
},
decrement() {
console.log("-1");
//2. 触发事件
this.$emit("sub");
},
//2. 触发事件,并传递数据
incrementN() {
this.$emit('addN', this.num, "why", 18);
}
}
}
</script>
综合练习(商品页面切换)
App.vue
<template>
3.使用传过来的事件
<tab-control :titles="titles"
@titleClick="titleClick">
</tab-control>
<h2>{{contents[currentIndex]}}</h2>
</template>
<script>
import TabControl from './TabControl.vue'
export default{
components:{
TabControl,
},
data(){
titles:['衣服','鞋子','裤子'],
contents:['衣服界面','鞋子页面','裤子页面'],
currentIndex:0
},
methods:{
titleClick(index){
this.currentIndex = index
}
}
}
</script>
TabControl.vue
<template>
<div class="tab-control">
<div
class=tab-control-item
:class="{active: currentIndex === index}"
v-for="(title,index) in titles"
:key="title"
@click="itemClick(index)"
>
<span>{{title}}</span>
</div>
</div>
</template>
<script>
export default{
//1.我在点击title的时候,下面显示数据,告诉App我要触发此事件
emits:["titleClick"],
props:{
titles:{
type:Array,
default(){
return []
}
}
},
data(){
return{
currentIndex:0
}
},
methods:{
itemClick(index){
this.currentIndex = index;
//2.将事件数据传给App
this.$emit('titleClick',index)
}
}
}
</script>
<style scpoed>
.tab-control-item.active{
color:red
}
</style>
总结emits: 我希望在子组件中点击东西的时候,父组件也可以有相应的反应,所以诞生了emits,用来子组件传递父组件。
1.在子组件中书写要触发的事件是什么:emit['事件名']
2.在子组件中想要让父组件执行操作的方法里,传递事件以及相应数据。表示,当我子组件执行这个方法时,我想要让父组件也做出相应的操作,操作的一些相关数据来源于我子组件里
3.父组件收到相应的事件后,@emit的事件名="父组件要做的方法名",这个时候父组件要做的方法里,可以使用传过来的数据了,并且在子组件执行时,父组件也会相应的执行,并返回具体的操作结果。