组件v-model
vue3
v-model 可以在组件上使用以实现双向绑定,实际是子组件更改父组件数据的语法糖 核心语法
HTML
<!-- 父组件 -->
<ChildA v-model:msg="msg"/>
<!-- 子组件: 特别注意自定义事件必须以update开头,冒号后是要绑定的数据 -->
<button @click="$emit('update:msg','msg的新值')">change</button>
父组件
HTML
<script>
import ChildA from './components/ChildA.vue';
export default {
data() {
return {
msg: 'old'
};
},
components: { ChildA }
}
</script>
<template>
<div class="app">
父组件
<hr>
<!-- v-model等价于下面的代码 -->
<!-- <ChildA :msg="msg" @update:msg="val=>msg=val"/> -->
<!-- v-model 组件双向数据绑定 -->
<ChildA v-model:msg="msg"/>
</div>
</template>
子组件
HTML
<script>
export default {
//父组件的传值
props: ['msg']
}
</script>
<template>
<div class="com-a">
<p>msg:{{ msg}}</p>
<!-- 发送自定义事件,更改父组件的值 -->
<p><button @click="$emit('update:msg','newvalue')">change</button></p>
</div>
</template>
vue2
vue2不支持组件v-model指令, 用.sync修饰符实现,但vue3废弃了sync修饰符 实现相同效果的方法
父组件
HTML
// 正常父传子:
<com1 :msg="num"></com1>
// 加上sync之后父传子:
<com1 :msg.sync="num"></com1>
// 它等价于
<com1 :msg="num" @update:msg="val=>num=val"></com1>
// 相当于多了一个事件监听,事件名是update:a,回调函数中,会把接收到的值赋值给属性绑定的数据项中。
子组件
HTML
<button @click="$emit('update:msg','newvalue')">change</button>
透传 Attributes
- vue3和vue2相同
透传 attribute"指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id
父组件
HTML
<script>
import Child from './components/Child.vue';
export default {
data() {
return {
msg: 'old'
};
},
components: { Child },
methods: {
onClick() {
console.log('click');
}
}
}
</script>
<template>
<div class="app">
父组件
<hr>
<!--
子组件没有用props或emit声明的属性会成为透传attribute
可通过查看元素查看透传属性
-->
<Child num1="1" :num2="2" msg1="hello" :msg2="'world'" @click="onClick" />
</div>
</template>
子组件
HTML
<script>
export default {
props: ['msg1'],
created(){
// 访问透传attribute
console.log(this.$attrs);
console.log(this.$attrs.msg2);
}
}
</script>
<template>
<div class="com-a">
<p>msg:{{ msg1}}</p>
<!-- 直接访问透传attribute -->
<p>msg2:{{ $attrs.msg2}}</p>
<!-- 父组件和子组件的click事件处理程序都会触发 -->
<p><button @click="console.log(2)">change</button></p>
<!-- 阻止冒泡,只触发子组件事件 -->
<p><button @click.stop="console.log(2)">change</button></p>
</div>
</template>
插槽 Slots (重要)
Vue3
- 插槽的作用
在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段
- 默认插槽
<slot> 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染
父组件
HTML
<script>
import Child from './Child.vue';
export default {
data() {
return {};
},
components: { Child }
}
</script>
<template>
<div class="parent">
<h3>父组件</h3>
<hr>
<h3>子组件</h3>
<Child>
<!-- 插槽的内容 -->
<em>子组件插槽的内容</em>
</Child>
</div>
</template>
<style lang="scss" scoped></style>
子组件
HTML
<template>
<div class="child">
<p>child-title</p>
<!-- 插槽的出口 -->
<slot></slot>
</div>
</template>
- 具名插槽
v-slot指令 定义具名插槽的内容 v-slot 有对应的简写 # ,因此 <template v-slot:header> 可以简写为 <template #header>。
其意思就是"将这部分模板片段传入子组件的 header 插槽中"
父组件
HTML
<script>
import Child from './Child.vue';
export default {
data() {
return {};
},
components: { Child }
}
</script>
<template>
<div class="parent">
<h3>父组件</h3>
<hr>
<h3>子组件</h3>
<Child>
<template v-slot:header>
header 插槽的内容
</template>
<template #footer>
footer 插槽的内容
</template>
<!-- 没有放在template的为默认内容 -->
<!-- 默认插槽的内容 -->
<template v-slot:default>
默认插槽的内容
</template>
<!-- 简写 -->
<!-- <template #default>
默认插槽的内容
</template> -->
</Child>
</div>
</template>
子组件
HTML
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<style lang="scss" scoped>
header {
border: 1px solid rgb(222, 67, 67);
}
footer{
border: 1px solid rgb(30, 186, 118);
}
main {
border: 1px solid rgb(53, 20, 184);
}
</style>
- 作用域插槽
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。
作用域插槽: 可以把子组件内部数据传递给对应模板的插槽
作用域插槽的作用: 在插槽对应的模板中可访问子组件内部的数据
示例:
HTML
<slot name="header" :text="text" :count="count"></slot>
默认插槽接收数据
HTML
<template>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
</template>
或者
<template>
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
</template>
具名插槽接收数据
HTML
<template>
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
</template>
- 模态框模板
![[1280X1280.PNGzzzz.PNG]]
![[e0e1ba5d-3596-4531-aad5-5f5e748e1ee7.png]]
![[1280X1280 (42).PNG]]
![[49559786-91ab-4173-ab73-adcf61fa588d.png]]
HTML
<script>
export default {
}
</script>
<template>
<div class="card">
<div class="title">
<div class="left">标题</div>
<div class="right">right</div>
</div>
<div class="content">内容</div>
<div class="footer">
<button>保 存</button>
<button>取 消</button>
</div>
</div>
</template>
<style lang="scss" scoped>
.card {
position: fixed;
width: 500px;
height: 300px;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
background-color: #fff;
// border: 1px solid #000;
.title {
display: flex;
justify-content: space-between;
width: 100%;
height: 50px;
padding: 10px;
background-color: rgb(244, 242, 242);
border-radius: 5px 5px 0 0;
border: 1px solid rgb(105, 103, 103);
box-sizing: border-box;
}
.content {
height: 200px;
border-left: 1px solid rgb(105, 103, 103);
border-right: 1px solid rgb(105, 103, 103);
box-sizing: border-box;
padding: 10px;
}
.footer {
display: flex;
justify-content: space-around;
padding: 10px 80px;
border: 1px solid rgb(105, 103, 103);
border-top: none;
// background-color: rgb(113, 31, 31);
button{
width: 100px;
height: 30px;
border-radius: 5px;
border: none;
background-color: rgb(24, 215, 245);
}
}
}
</style>
- 封装模态框组件
父组件
HTML
<script>
import ComA from './components/ComA.vue';
export default {
data() {
return {
msg: 'old',
list: ['a','b','c']
};
},
components: { ComA },
methods: {
add(){
console.log('fu-add');
}
}
}
</script>
<template>
<div class="app">
父组件
<hr>
<ComA title="分账角色管理">
<!-- <ComA title="分账角色管理" width="300"> -->
<!-- <template v-slot:extra="data"> -->
<template #extra="data">
<div class="right">
<button @click="add">添加---{{ msg }}---{{ data.type }}</button>
</div>
</template>
<div class="list">
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</ComA>
</div>
</template>
子组件
HTML
<script>
export default {
// extra可以自行根据需要定义更多的属性
props: ['title', 'extra','width'],
data(){
return {
type: 'custom'
}
},
methods: {
add(){
console.log('add');
}
},
computed: {
styleObj(){
let style = {}
this.width && (style.width = this.width + 'px')
return style
}
}
}
</script>
<template>
<div class="card" :style="styleObj">
<div class="title">
<div class="left">{{ title }}</div>
<slot name="extra" :type="type"></slot>
</div>
<div class="content">
<slot></slot>
</div>
<div class="footer">
<button>保 存</button>
<button>取 消</button>
</div>
</div>
</template>
<style lang="scss" scoped>
.card {
position: fixed;
width: 500px;
height: 300px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
// border: 1px solid #000;
.title {
display: flex;
justify-content: space-between;
width: 100%;
height: 50px;
padding: 10px;
background-color: rgb(244, 242, 242);
border-radius: 5px 5px 0 0;
border: 1px solid rgb(105, 103, 103);
box-sizing: border-box;
}
.content {
height: 200px;
border-left: 1px solid rgb(105, 103, 103);
border-right: 1px solid rgb(105, 103, 103);
box-sizing: border-box;
padding: 10px;
}
.footer {
display: flex;
justify-content: space-around;
padding: 10px 80px;
border: 1px solid rgb(105, 103, 103);
border-top: none;
// background-color: rgb(113, 31, 31);
button {
width: 100px;
height: 30px;
border-radius: 5px;
border: none;
background-color: rgb(24, 215, 245);
}
}
}
</style>
Vue2
- 插槽的作用
在组件模板中定义slot插槽,允许在使用组件时,在组件标签内部定义要渲染在插槽位置的内容
-
插槽的分类
-
匿名插槽
-
具名插槽
-
作用域插槽
-
-
匿名插槽
父组件template
HTML
<div id="app">
<!-- 子组件panel: 正在进行 -->
<panel :list="pending">
<h1>正在进行</h1>
</panel>
<!-- 子组件panel: 已经完成 -->
<panel :list="resolve">
<template>
<h4>已经完成</h4>
</template>
</panel>
</div>
子组件panel的template
HTML
<div class="box">
<!-- 匿名插槽-->
<slot></slot>
<ul>
<li v-for="(item,index) in list">{{item}}</li>
</ul>
</div>
- 具名插槽
父组件template
HTML
<div id="app">
<!-- 正在进行 -->
<panel :list="pending">
<template v-slot:header>
header的内容
</template>
<template v-slot:footer>
footer的内容
</template>
<!-- 任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。-->
<h1>我是默认的</h1>
</panel>
</div>
子组件template
HTML
<div class="box">
<!-- 有名字的插槽-->
<slot name="header"></slot>
<hr>
<!-- 默认插槽-->
<slot></slot>
<ul>
<li v-for="(item,index) in list">{{item}}</li>
</ul>
<!-- 有名字的插槽-->
<slot name="footer"></slot>
</div>
- 作用域插槽
作用域插槽: 可以把组件内部数据传递给对应模板的插槽 作用域插槽的作用: 在插槽对应的模板中可访问组件内部的数据 示例:
HTML
<slot name="header" :msg="msg" :num="num"></slot>
模板接收数据:
HTML
<template v-slot:header="data">
*** data是所有数据组成的集合
-
案例
-
父组件的template
HTML
<div id="app">
<!-- 正在进行 -->
<panel :list="pending">
<template v-slot:header="data">
header的内容---{{data.msg}}-----{{data.num}}
</template>
<template v-slot:footer>
footer的内容
</template>
<h1>我是默认的</h1>
</panel>
</div>
子组件
JavaScript
export default {
props: {
list: Array
},
data(){
return {
msg: '我是子组件的数据',
num: 9
}
}
}
HTML
<template>
<div class="box">
<slot name="header" :msg="msg" :num="num"></slot>
<hr>
<slot></slot>
<ul>
<li v-for="(item,index) in list">{{item}}</li>
</ul>
<slot name="footer"></slot>
</div>
</template>
4.依赖注入
![[1280X1280 (43).PNG]]
vue2和vue3相同
有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。
在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦 任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
-
在父组件中用provide提供数据
-
在后代组件中用inject获取依赖注入
main.js 全局注入
JavaScript
import { createApp } from 'vue'
// import './style.css'
import App from './App.vue'
const app = createApp(App)
//全局注入
app.provide("theme",'dark')
app.mount('#app')
Parent.vue
JavaScript
<script>
import {computed} from 'vue'
import ComA from './components/ComA.vue';
export default {
data() {
return {
msg: 'old',
type: 'top-level'
};
},
// 方式一
// provide: {
// greet: 'hello'
// },
// 方式二: 函数的形式可以访问到this,数据没有响应式
// provide(){
// return {
// msg: this.msg,
// greet: 'hello'
// }
// },
// 方式三
provide(){
return {
msg: computed(()=>this.msg),
greet: 'hello'
}
},
components: { ComA },
methods: {
add(){
console.log('fu-add');
}
}
}
</script>
<template>
<div class="app">
父组件---{{ msg }}
<hr>
<ComA></ComA>
<p><button @click="msg = 'new'">change-top</button></p>
</div>
</template>
<style lang="scss" scoped>
</style>
子组件
JavaScript
<script>
import DeepChild from './DeepChild.vue';
export default {
data() {
return {};
},
components: { DeepChild }
}
</script>
<template>
<div class="com-a">
<h2>com-a</h2>
<DeepChild></DeepChild>
</div>
</template>
<style lang="scss" scoped>
</style>
深层子组件
JavaScript
<script>
export default {
// theme为main.js的全局注入
inject: ['greet', 'msg','theme'],
data() {
return {
}
}
}
</script>
<template>
<div class="deep-child">
<h2>deep-child-----{{ greet }}----{{ msg }}</h2>
<p>{{ theme }}</p>
</div>
</template>
<style lang="scss" scoped></style>
5.异步组件
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现此功能
不使用异步组件
npm run build打包后,只有一个js, 会在应用初始化时即全部加载
HTML
<script>
import Child from './Child.vue'
export default {
data() {
return {
show: false
};
},
components: {
Child
}
}
</script>
<template>
<div class="parent">
<h3>父组件</h3>
<hr>
<h3>子组件</h3>
<p><button @click="show = true">显示子组件</button></p>
<Child v-if="show">
</Child>
</div>
</template>
<style lang="scss" scoped></style>
使用异步组件
child.vue会单独打包为一个js文件,会在需要时才加载该js
HTML
<script>
import { defineAsyncComponent } from 'vue'
export default {
data() {
return {
show: false
};
},
components: {
Child: defineAsyncComponent(() =>
import('./Child.vue')
)
}
}
</script>
<template>
<div class="parent">
<h3>父组件</h3>
<hr>
<h3>子组件</h3>
<p><button @click="show = true">显示子组件</button></p>
<Child v-if="show">
</Child>
</div>
</template>
<style lang="scss" scoped></style>
vue2
JavaScript
components: {
'my-component': () => import('./my-async-component')
}