文章目录
1. Props
父组件传值给子组件(简称:父传子)
Props 文档
父组件
// Parent.vue
<template>
<!-- 使用子组件 -->
<Child :msg="message" />
</template>
<script setup>
import Child from './components/Child.vue' // 引入子组件
let message = 'hello'
</script>
子组件
// Child.vue
<template>
<div>
{{ msg }}
</div>
</template>
<script setup>
const props = defineProps({
msg: {
type: String,
default: ''
}
})
// 在 js 里需要使用 props.xxx 的方式使用。在 html 中使用不需要 props
console.log(props.msg)
</script>
- 在
<script setup>
中必须使用defineProps API
来声明props
,它具备完整的推断并>* 且在<script setup>
中是直接可用的。- 在
<script setup> 中,
defineProps `不需要另外引入。- props 其实还能做很多事情,比如:设置默认值
default
,类型验证type
,要求必传required
,自定义验证函数validator
等等。
2. emits
子组件通知父组件触发一个事件,并且可以传值给父组件。(简称:子传父)
emits 文档
父组件
// Parent.vue
<template>
<div>父组件:{{ message }}</div>
<!-- 自定义 changeMsg 事件 -->
<Child @changeMsg="changeMessage" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
let message = ref('hello')
// 更改 message 的值,data是从子组件传过来的
function changeMessage(data) {
message.value = data
}
</script>
子组件
// Child.vue
<template>
<div>
子组件:<button @click="handleClick">子组件的按钮</button>
</div>
</template>
<script setup>
// 注册一个自定义事件名,向上传递时告诉父组件要触发的事件。
const emit = defineEmits(['changeMsg'])
function handleClick() {
// 参数1:事件名
// 参数2:传给父组件的值
emit('changeMsg', '扶苏')
}
</script>
- 和
props
一样,在<script setup>
中必须使用defineEmits API
来声明emits
,它具备完整的推断并且在<script setup>
中是直接可用的。更多细节请看 文档。
- 在
<script setup>
中,defineEmits
不需要另外引入。
3. ref 与 $parent
ref
可以获取元素的DOM
或者获取子组件实例的VC
。既然可以在父组件内部通过ref
获取子组件实例VC
,那么子组件内部的方法与响应式数据父组件也是可以使用的。
父组件
<template>
<div class="box">
<h1>我是父组件:ref parent</h1>
<h2>父组件拥有财产:{{ money }}</h2>
<button @click="handler">向子组件1拿100元</button>
<div class="container">
<Children1 ref="children1"></Children1>
<Children2 ref="children2"></Children2>
</div>
</div>
</template>
<script lang="ts" setup>
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
import Children1 from "./Children1.vue";
import Children2 from "./Children2.vue";
import { ref } from "vue";
let money = ref(10000);
// 获取子组件的实例 变量名就是ref绑定的名字,切记!
let children1 = ref();
let children2 = ref();
console.log(children1, children2);
//父组件内部按钮点击回调
const handler = () => {
money.value += 100;
children1.value.money -= 100;
};
defineExpose({
money,
});
</script>
<style scoped>
.box {
width: 1000px;
height: 500px;
background: skyblue;
}
.container {
display: flex;
justify-content: space-between;
}
</style>
但是需要注意,如果想让父组件获取子组件的数据或者方法需要通过
defineExpose
对外暴露,因为Vue3
中组件内部的数据对外“关闭的”,外部不能访问。
子组件
<template>
<div class="children1">
<h2>我是子组件1</h2>
<h3>子组件1拥有财产:{{ money }}</h3>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
let money = ref(9999);
defineExpose({
money,
});
</script>
<style scoped>
.children1 {
width: 300px;
height: 250px;
background-color: yellow;
}
</style>
$parent
可以获取某一个组件的父组件实例VC
,因此可以使用父组件内部的数据与方法。必须子组件内部拥有一个按钮点击时候获取父组件实例,当然父组件的数据与方法需要通过defineExpose
方法对外暴露。
<template>
<div class="children2">
<h2>我是子组件2</h2>
<h3>子组件2拥有财产:{{ money }}</h3>
<button @click="handler($parent)">向父组件拿300元</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
let money = ref(1000);
const handler = ($parent) => {
money.value += 300;
console.log($parent);
$parent.money -= 300;
};
</script>
<style scoped>
.children2 {
width: 300px;
height: 250px;
background-color: yellowgreen;
}
</style>
4. useAttrs
在
Vue3
中可以利用useAttrs
方法获取组件的属性与事件(包含:原生DOM
事件或者自定义事件),该函数功能类似于Vue2
框架中attrs
属性与$listeners
方法。
比如:在父组件内部使用一个子组件
HintButton
<template>
<div class="box">
<h1>我是父组件:attrs</h1>
<el-button type="primary" size="large" :icon="Edit"></el-button>
<!-- 自定义组件 -->
<!-- 实现将光标放在按钮上,会有文字提示 -->
<HintButton
type="primary"
size="large"
:icon="Edit"
@click="handler"
@xxx="handler"
:title="title"
></HintButton>
</div>
</template>
<script lang="ts" setup>
import { Edit } from "@element-plus/icons-vue";
import { ref } from "vue";
import HintButton from "./HintButton.vue";
const handler = () => {
alert(12306);
};
let title = ref("编辑");
</script>
<style scoped>
.box {
width: 1000px;
height: 500px;
background: skyblue;
}
</style>
子组件内部可以通过
useAttrs
方法获取组件属性与事件。它类似于props
,可以接受父组件传递过来的属性与属性值。需要注意如果defineProps
接受了某一个属性,useAttrs
方法返回的对象身上就没有相应属性与属性值。
<template>
<div :title="title">
<el-button v-bind:="$attrs"></el-button>
</div>
</template>
<script lang="ts" setup>
//引入useAttrs方法:获取组件标签身上属性与事件
import { useAttrs } from "vue";
//此方法执行会返回一个对象
let $attrs = useAttrs();
// 万一用 props 接受 title
let props = defineProps(["title"]);
// props 与 useAttrs 方法都可以获取父组件传递过来的属性与属性值
//但是 props 接收了 useAttrs 方法就获取不到了
console.log($attrs);
</script>
<style scoped></style>
5. v-model
5.1. 单值的情况
组件上的
v-model
使用modelValue
作为prop
和update:modelValue
作为事件。
父组件
// Parent.vue
<template>
<Child v-model="message" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const message = ref('雷猴')
</script>
子组件
// Child.vue
<template>
<div @click="handleClick">{{modelValue}}</div>
</template>
<script setup>
import { ref } from 'vue'
// 接收
const props = defineProps([
'modelValue' // 接收父组件使用 v-model 传进来的值,必须用 modelValue 这个名字来接收
])
const emit = defineEmits(['update:modelValue']) // 必须用 update:modelValue 这个名字来通知父组件修改值
function handleClick() {
// 参数1:通知父组件修改值的方法名
// 参数2:要修改的值
emit('update:modelValue', '喷射河马')
}
</script>
5.2. 多个 v-model 绑定
父组件
// Parent.vue
<template>
<Child v-model:msg1="message1" v-model:msg2="message2" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const message1 = ref('雷猴')
const message2 = ref('蟑螂恶霸')
</script>
子组件
// Child.vue
<template>
<div><button @click="changeMsg1">修改msg1</button> {{msg1}}</div>
<div><button @click="changeMsg2">修改msg2</button> {{msg2}}</div>
</template>
<script setup>
import { ref } from 'vue'
// 接收
const props = defineProps({
msg1: String,
msg2: String
})
const emit = defineEmits(['update:msg1', 'update:msg2'])
function changeMsg1() {
emit('update:msg1', '鲨鱼辣椒')
}
function changeMsg2() {
emit('update:msg2', '蝎子莱莱')
}
</script>
5.3. v-model 修饰符
父组件
// Parent.vue
<template>
<Child v-model.uppercase="message" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const message = ref('hello')
</script>
子组件
// Child.vue
<template>
<div>{{modelValue}}</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const props = defineProps([
'modelValue',
'modelModifiers'
])
const emit = defineEmits(['update:modelValue'])
onMounted(() => {
// 判断有没有 uppercase 修饰符,有的话就执行 toUpperCase() 方法
if (props.modelModifiers.uppercase) {
emit('update:modelValue', props.modelValue.toUpperCase())
}
})
</script>
6. 插槽 slot
插槽可以理解为传一段 HTML 片段给子组件。子组件将 元素作为承载分发内容的出口。
本文打算讲讲日常用得比较多的3种插槽:默认插槽、具名插槽、作用域插槽。
6.1. 默认插槽
父组件
// Parent.vue
<template>
<Child>
<div>雷猴啊</div>
</Child>
</template>
子组件
// Child.vue
<template>
<div>
<slot></slot>
</div>
</template>
6.2. 具名插槽
具名插槽 就是在 默认插槽 的基础上进行分类,可以理解为对号入座。
父组件
// Parent.vue
<template>
<Child>
<template v-slot:monkey>
<div>雷猴啊</div>
</template>
<!-- 简写 -->
<template #monkey>
<div>雷猴啊</div>
</template>
<button>鲨鱼辣椒</button>
</Child>
</template>
子组件
// Child.vue
<template>
<div>
<!-- 默认插槽 -->
<slot></slot>
<!-- 具名插槽 -->
<slot name="monkey"></slot>
</div>
</template>
- 父组件需要使用
<template>
标签,并在标签上使用v-solt: + 名称
简写:#名称
。- 子组件需要在
标签里用 name= 名称
对应接收。 - 这就是 对号入座。
- 最后需要注意的是,插槽内容的排版顺序,是 以子组件里的排版为准。
- 上面这个例子就是这样,你可以仔细观察子组件传入顺序和子组件的排版顺序。
6.3. 作用域插槽
父组件
// Parent.vue
<template>
<!-- v-slot="{scope}" 获取子组件传上来的数据 -->
<!-- :list="list" 把list传给子组件 -->
<Child v-slot="{scope}" :list="list">
<div>
<div>名字:{{ scope.name }}</div>
<div>职业:{{ scope.occupation }}</div>
<hr>
</div>
</Child>
</template>
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const list = ref([
{ name: '雷猴', occupation: '打雷'},
{ name: '鲨鱼辣椒', occupation: '游泳'},
{ name: '蟑螂恶霸', occupation: '扫地'},
])
</script>
子组件
// Child.vue
<template>
<div>
<!-- 用 :scope="item" 返回每一项 -->
<slot v-for="item in list" :scope="item" />
</div>
</template>
<script setup>
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
</script>
7. provide / inject
-
遇到多层传值时,使用
props
和emit
的方式会显得比较笨拙。这时就可以用provide
和inject
了。 -
provide
是在父组件里使用的,可以往下传值。 -
inject
是在子(后代)组件里使用的,可以网上取值。 -
无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。
父组件
// Parent.vue
<template>
<Child></Child>
</template>
<script setup>
import { ref, provide, readonly } from 'vue'
import Child from './components/Child.vue'
const name = ref('猛虎下山')
const msg = ref('雷猴')
// 使用readonly可以让子组件无法直接修改,需要调用provide往下传的方法来修改
provide('name', readonly(name))
provide('msg', msg)
provide('changeName', (value) => {
name.value = value
})
</script>
子组件
// Child.vue
<template>
<div>
<div>msg: {{ msg }}</div>
<div>name: {{name}}</div>
<button @click="handleClick">修改</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
const name = inject('name', 'hello') // 看看有没有值,没值的话就适用默认值(这里默认值是hello)
const msg = inject('msg')
const changeName = inject('changeName')
function handleClick() {
// 这样写不合适,因为vue里推荐使用单向数据流,当父级使用readonly后,这行代码是不会生效的。没使用之前才会生效。
// name.value = '雷猴'
// 正确的方式
changeName('虎躯一震')
// 因为 msg 没被 readonly 过,所以可以直接修改值
msg.value = '世界'
}
</script>
-
provide 可以配合 readonly 一起使用,详情可以看上面例子和注释。
-
provide 和 inject 其实主要是用在深层关系中传值,上面的例子只有父子2层,只是为了举例说明 我懒。
8. getCurrentInstance
getcurrentinstance 是 vue 提供的一个方法,支持访问内部组件实例。
getCurrentInstance 只暴露给高阶使用场景,典型的比如在库中。强烈反对在应用的代码中使用 getCurrentInstance。请不要把它当作在组合式 API 中获取 this 的替代方案来使用。
说白了,这个方法 适合在开发组件库的情况下使用,不适合日常业务开发中使用。
getCurrentInstance 只能在 setup 或生命周期钩子中调用。
<script setup>
import { getCurrentInstance } from 'vue'
// proxy 相当于
const { proxy } = getCurrentInstance()
</script>
- proxy 是一个被包装过的对象,它提供了对当前组件内属性和方法的访问。
- 你可以使用 proxy 来访问当前组件的属性和方法,而无需显式引用当前组件的实例对象。这使代码更加简洁和容易理解。
9. mitt.js
mitt.js 提供了更多的方法。
比如:
- on:添加事件
- emit:执行事件
- off:移除事件
- clear:清除所有事件
mitt.js 不是专门给 Vue 服务的,但 Vue 可以利用 mitt.js 做跨组件通信。
9.1. 安装
npm i mitt
9.2. 使用
Bus.js
// Bus.js
import mitt from 'mitt'
export default mitt()
Parent.vue
// Parent.vue
<template>
<div>
Mitt
<Child />
</div>
</template>
<script setup>
import Child from './Child.vue'
import Bus from './Bus.js'
Bus.on('sayHello', () => console.log('雷猴啊'))
</script>
Child.vue
// Child.vue
<template>
<div>
Child:<button @click="handleClick">打声招呼</button>
</div>
</template>
<script setup>
import Bus from './Bus.js'
function handleClick() {
Bus.emit('sayHello')
}
</script>