Bootstrap

04Vue3-组件化开发

组件基础

组件基本实例

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>

效果
在这里插入图片描述

;