1.父传子
// 父组件
<button-counter
title="My journey with Vue"
isShow="false"
></button-counter>
// 子组件
// 使用了 <script setup> 的组件
<script setup>
import { ref } from 'vue'
const props = defineProps(['title','isShow'])
console.log(props)
console.log(props.title)
// 没有使用 <script setup> 的组件
<script>
export default {
props: ['title'],
setup(props,{emit}){
const title = props.title
return {
title,
}
}
}
</script>
2.子传父
// 子组件
// 没有使用 <script setup> 的组件
// 第一种:<button @click="$emit('enlarge-text')">Enlarge text</button>
<script>
export default {
props: ['title'],
setup(props,{emit}){
function enlargeClick() {
emit('enlarge-text')
}
return {
enlargeClick
}
}
}
</script>
// 第二种:<button @click="enlargeClick">变大按钮</button>
// 使用 <script setup> 的组件
<button @click="enlargeClick">变大按钮</button>
// 通过defineEmits宏来声明需要抛出的事件, 仅可用于<script setup>中
const emit = defineEmits(['enlarge-text'])
function enlargeClick() {
emit('enlarge-text')
}
// 父组件
<button-counter
title="My journey with Vue"
isShow="false"
@enlarge-text="postFontSize+=0.2"
></button-counter>
3.通过插槽来分配内容
// 父组件
<div>
<button-counter>这是通过插槽传递的内容: {{postFontSize}}</button-counter>
</div>
// 子组件
<div>
count: {{title}}
<button @click="enlargeClick">变大按钮</button>
我们使用 <slot> 作为一个占位符,父组件传递进来的内容就会渲染在这里。
<slot />
</div>
4.动态组件
<script setup>
import Home from './Home.vue'
import Posts from './Posts.vue'
import Archive from './Archive.vue'
import { ref } from 'vue'
const currentTab = ref('Home')
const tabs = {
Home,
Posts,
Archive
}
</script>
<template>
<div class="demo">
tab是每一个组件
<button
v-for="(_, tab) in tabs"
:key="tab"
:class="['tab-button', { active: currentTab === tab }]"
@click="currentTab = tab"
>
{{ tab }}
</button>
<component :is="tabs[currentTab]" class="tab"></component>
</div>
</template>
5.组件注册
// 全局注册
import MyComponent from './App.vue'
app.component('MyComponent', MyComponent)
// 局部注册 在<script setup>中引入后直接使用
<script setup>
import ComponentA from './ComponentA.vue'
</script>
<template>
<ComponentA />
</template>
// 不是在<script setup>中,需要使用 components 选项来显式注册:
import ComponentA from './ComponentA.js'
export default {
components: {
ComponentA
},
}
/*
全局注册VS局部注册
1.无法在生产打包时被自动移除 (也叫“tree-shaking”). 注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
2.全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
*/
6.props和单向数据流, props校验
<script setup>
defineProps({
greetingMessage: String
})
{{ greetingMessage }}
</script>
// 将一个对象的所有属性都当作 props 传入
const post = {
id: 1,
title: 'My Journey with Vue'
}
<BlogPost v-bind="post" />
// 等价于
<BlogPost :id="post.id" :title="post.title" />
/*
单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。
这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
*/
// 1.prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性
const props = defineProps(['initialCounter'])
// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)
// 2.需要对传入的 prop 值做进一步的转换 定义一个计算属性
const props = defineProps(['size'])
// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
<script setup>
defineProps({
propB: [String, Number],//多种可能的类型
propC: {
type: String,
required: true, //必传
default: '666' //默认值return {};
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' } // 或者 return {};//或 return []
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// 该值必须匹配这些字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
},
})
/*
1.所有 prop 默认都是可选的,除非声明了 required: true。
2.除 Boolean 外的未传递的可选 prop 将会有一个默认值 undefined。
3.Boolean 类型的未传递 prop 将被转换为 false。你应该为它设置一个 default 值来确保行为符合预期。
4.如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。
*/
7.v-model绑定在组件上
// 子组件
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
// 父组件
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const message = ref('hello')
</script>
<template>
<CustomInput v-model="message" /> {{ message }}
</template>
/*
1.将内部原生 input 元素的 value attribute 绑定到 modelValue prop
2.输入新的值时在 input 元素上触发 update:modelValue 事件
*/
// 另一种实现,利用计算属性
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
<template>
<input v-model="value" />
</template>
7.v-model 的参数,多个v-model绑定和单个一样
/*
默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue
作为对应的事件。我们可以通过给 v-model 指定一个参数来更改这些名字:
*/
// 父组件
<MyComponent v-model:title="bookTitle" />
// 子组件
// <!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
9.自定义 v-model 修饰符。
//父组件 自定义修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写:
const myText = ref('')
<MyComponent v-model.capitalize="myText" />
//子组件
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) } //接收一下,看有没有修饰符
})
const emit = defineEmits(['update:modelValue'])
console.log(props.modelModifiers) // { capitalize: true }
function emitValue(e){ // input事件,判断有没有这个修饰符,有就做一下操作,没有就不做
let value = e.target.value
if(props.modelModifiers.capitalize){
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue',value)
}
</script>
<template>
<input
type="text"
:value="modelValue"
@input="emitValue"
/>
</template>
10.透传 attribute
1.透传的 attribute 不会包含 <MyButton> 上声明过的 props 或是针对 emits
声明事件的 v-on 侦听函数,换句话说,声明过的 props 和侦听函数被 <MyButton>“消费”了。
2.透传的 attribute 若符合声明,也可以作为 props 传入 <BaseButton>。
3.如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false。
11.插槽
<slot> 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。
简单插槽
// 父组件
<FancyButton>
Click me! <!-- 插槽内容 -->
</FancyButton>
// 子组件
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
插槽内容可以是任意合法的模板内容,不局限于文本。例如我们可以传入多个元素,甚至是组件:
<Icon/>
<slot>
Submit <!-- 默认内容 --> slot的默认值,如果父组件传内容就用父组件的内容, 如果没有传内容就用默认内容.
</slot>
</button>
具名插槽
<slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容:
//子组件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main> //没有提供 name 的 <slot> 出口会隐式地命名为“default”。
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
//父组件
<BaseLayout>
<template v-slot:header> 简写: #header, 意思就是“将这部分模板片段传入子组件的 header 插槽中”
<!-- header 插槽的内容放这里 -->
</template>
</BaseLayout>
动态插槽
// 动态指令参数在 v-slot 上也是有效的,即可以定义下面这样的动态插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
作用域插槽
//子组件通过slot向父组件传递数据
<!-- <MySlot> 的模板 -->
const greeting = ref('英美俄日')
<div>
<slot :text="greeting" :count="1"></slot>
</div>
//默认插槽如何接收props⭐️????
<mySlot v-slot="slotProps"> 在 v-slot 中使用解构 v-slot="{ text, count }"
按钮
{{ slotProps.text }} // {{ slotProps.count }}
</mySlot>
12.provide(提供)和inject(注入)
一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
要为组件后代提供数据,需要使用到 provide() 函数:
provide() 函数接收两个参数。
第一个参数被称为注入名,可以是一个字符串或是一个 Symbol。后代组件会用注入名来查找期望注入的值。一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。
第二个参数是提供的值,值可以是任意类型,包括响应式的状态,
<script setup>
import { provide } from 'vue'
/* 注入名 */, /* 值 */
provide('message', 'hello!')
</script>
除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖:
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
要接收上层组件提供的数据,需使用 inject() 函数:
inject() 函数接收两个参数。第一个参数是接收的注入名, 第二个参数是默认值. 最好设置默认值
<script setup>中
import { inject } from 'vue'
const message = inject('message')
// 如果没有祖先组件提供 "message", `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')
// 默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值:
const value = inject('key', () => new ExpensiveClass())
</script>
// 不使用<script setup>,inject() 需要在 setup() 内同步调用:
import { inject } from 'vue'
export default {
setup() {
const message = inject('message')
return { message }
}
}
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。 可以给子组件传递函数
<!-- 在供给方组件内 --> 供给方提供一个数据和方法, 注入方可以通过调用函数传递参数的形式更改数据
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
13.异步组件
在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现此功能
<script setup>
import { defineAsyncComponent } from 'vue'
const AdminPage = defineAsyncComponent(() => import('./components/AdminPageComponent.vue'))
</script>
<template>
<AdminPage />
</template>
14.Transition和TransitionGroup
<Transition> 仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。
v-enter-from:进入动画的起始状态。
v-enter-active:进入动画的生效状态。
v-enter-to:进入动画的结束状态。
v-leave-from:离开动画的起始状态。
v-leave-active:离开动画的生效状态。
v-leave-to:离开动画的结束状态。
为过渡效果命名#
我们可以给 <Transition> 组件传一个 name prop 来声明一个过渡效果名:
<Transition name="fade"> ... </Transition>
.fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease; }
15.KeepAlive
<KeepAlive> 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
<KeepAlive> 默认会缓存内部的所有组件实例,但我们可以通过 include 和 exclude prop 来定制该行为。