发布时间:2022-12-08 21:30
props主要用于父组件向子组件通信。在父组件中通过用 :msg=“msg” 绑定需要传给子组件的属性值,然后再在子组件中用 props 接收该属性值。
方法一 普通方式:
// 父组件 传值
<child :msg1="msg1" :list="list"></child>
<script>
import child from "./child.vue";
import { ref, reactive } from "vue";
export default {
setup() {
//基础类型传值
const msg1 = ref("父组件传给子组件的msg1");
// 复杂类型(数组或对象)传值
const list = reactive(['苹果', '梨', '香蕉'])
return {
msg1,
list
}
}
}
</script>
// 子组件 接收
<template>
<ul >
<li v-for="i in list" :key="i">{{ i }}</li>
</ul>
</template>
<script>
export default {
// props接受父组件传过来的值
props: ["msg1", "list"],
setup(props) {
console.log(props);
// { msg1:"父组件传给子组件的msg1", list:['苹果', '梨', '香蕉'] }
},
}
</script>
方法二:使用 setup 语法糖
// 父组件 传值
<child :msg="msg" :list="list">
</child>
<script setup>
import child from "./child.vue";
const list = reactive(['苹果', '梨', '香蕉'])
const msg = ref("父组件传给子组件的值");
</script>
// 子组件 接收
<template>
<ul >
<li v-for="i in list" :key="i">{{ i }}</li>
</ul>
</template>
<script setup>
// 这里不需要在从vue中引入defineProps,直接用
const props = defineProps({
// 第一种写法
msg: String,
// 第二种写法
list: {
type: Array,
default: () => [],
}
})
console.log(props);
</script>
$emit 也就是通过自定义事件传值,主要用于子组件向父组件通信。
在子组件的点击事件中,通过触发父组件中的自定义事件,把想传给父组件的信息以参数的形式带过去,父组件便可以拿到子组件传过来的参数值。
// 子组件 派发
<template>
<button @click="handleClick">按钮</button>
</template>
<script setup>
let infos = ref('还好');
const handleClick = () => {
// 触发父组件中的方法,并把值以参数的形式传过去
emit('myClick', infos);
};
const emit = defineEmits(['myClick']);
</script>
// 父组件 接收
<template>
<child @myClick="onMyClick"></child>
</template>
<script setup>
import child from "./child.vue";
// 父组件接受到子组件传过来的值
const onMyClick = (msg) => {
console.log(msg);
}
</script>
expose与ref 主要用于父组件获取子组件的属性或方法。在子组件中,向外暴露出属性或方法,父组件便可以使用 ref 获取到子组件身上暴露的属性或方法。
<template>
<div>父组件:拿到子组件的message数据:{{ msg }}</div>
<button @click="callChildFn">调用子组件的方法</button>
<hr />
<Child ref="com" />
</template>
<script setup>
import Child from './child.vue';
const com = ref(null); // 通过 模板ref 绑定子组件
const msg = ref('');
onMounted(() => {
// 在加载完成后,将子组件的 message 赋值给 msg
msg.value = com.value.message;
});
function callChildFn() {
console.log(com.value, '====');
// 调用子组件的 changeMessage 方法
com.value.show();
// 重新将 子组件的message 赋值给 msg
msg.value = com.value.message;
}
</script>
子组件:
<template>
<div> 子组件:</div>
</template>
<script setup>
const message = ref('子组件传递得信息');
const show = () => {
console.log('子组件得方法');
};
defineExpose({
message,
show,
});
</script>
attrs 主要用于子组件获取父组件中没有通过 props 接收的属性。
<template>
<Child :msg1="msg1" :msg2="msg2" title="子组件" />
</template>
<script setup>
import Child from './child.vue';
const msg1 = ref('信息1');
const msg2 = ref('信息2');
</script>
子组件
<template>
<div> 子组件:{{ msg1 }}-{{ attrs.msg2 }}-{{ attrs.title }}</div>
</template>
<script setup>
// 子组件接收msg1
defineProps({
msg1: String,
});
const attrs = useAttrs();
// 因为子组件接收了msg1,所以打印的结果中不会包含msg1, { msg2:"信息1", title: "子组件" }
// 如果子组件没有接收msg1,打印的结果就是 { msg1: "信息1", msg2:"信息12", title: "子组件" }
console.log(attrs);
</script>
遇到多层传值时,使用 props 和 emit 的方式会显得比较笨拙。这时就可以用 provide 和 inject 了。
provide与inject 主要为父组件向子组件或多级嵌套的子组件通信。
provide:在父组件中可以通过 provide 提供需要向后代组件传送的信息。
inject:从父组件到该组件无论嵌套多少层都可以直接用 inject 拿到父组件传送的信息。
<template>
<div>------祖父组件---------</div>
<button @click="fn">改变location的值</button>
<br />
<div>双向数据绑定:</div>
姓名 {{ userInfos.username }}:
<input v-model="userInfos.username" />
<Child />
</template>
<script setup>
import Child from './child.vue';
let location = ref('传递祖父的参数');
var userInfos = reactive({
username: '张三',
age: 20,
});
let fn = () => {
location.value = '改变值';
};
provide('location', location);
provide('userInfos', readonly(userInfos));
</script>
子组件:
<template>
<div>
<Sun />
</div>
</template>
<script>
import Sun from "./sun.vue";
</script>
孙组件:
<template>
<div>
<h5>-------------孙组件接受参数-------------</h5>
<div>1.祖父组件定义provide,孙组件inject接受:{{ location }}</div>
<p>用户信息: {{ userInfos.username }}</p>
<br />
<br />
<div>2.provide inject实现父子组件传值的时候,子组件改变数据也会影响父组件</div>
<br />姓名:
<input v-model="userInfos.username" />
</div>
</template>
<script setup>
let location = inject('location');
let userInfos = inject('userInfos');
</script>
注意:增加readonly后,子组件修改后,不会影响到父组件
获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理,不能给属性重新赋值。只读代理是递归的:访问的任何嵌套 property 也是只读的。
简单得理解:要确保父组件传递得数据不会被子孙组件更改时,增加readonly
v-model 是 Vue 的一个语法糖。在 Vue3 中的玩法就更多了
<template>
<Child v-model="message" />
</template>
<script setup>
import Child from './child.vue';
const message = ref('父传给子');
</script>
子组件:
<template>
<div>
<button @click="handleClick">修改model</button>
{{ modelValue }}
</div>
</template>
<script setup>
// 接收
defineProps([
'modelValue', // 接收父组件使用 v-model 传进来的值,必须用 modelValue 这个名字来接收
]);
const emit = defineEmits(['update:modelValue']); // 必须用 update:modelValue 这个名字来通知父组件修改值
function handleClick() {
// 参数1:通知父组件修改值的方法名
// 参数2:要修改的值
emit('update:modelValue', '子改变值');
}
</script>
<template>
<Child v-model:msg1="message1" v-model:msg2="message2" />
</template>
<script setup>
import Child from './child.vue';
const message1 = ref('水果1');
const message2 = ref('水果2');
</script>
子组件:
<template>
<div>
<div><button @click="changeMsg1">修改msg1</button> {{ msg1 }}</div>
<div><button @click="changeMsg2">修改msg2</button> {{ msg2 }}</div>
</div>
</template>
<script setup>
// 接收
defineProps({
msg1: String,
msg2: String,
});
const emit = defineEmits(['update:msg1', 'update:msg2']);
function changeMsg1() {
emit('update:msg1', '蔬菜1');
}
function changeMsg2() {
emit('update:msg2', '蔬菜2');
}
</script>
v-model 还能通过 . 的方式传入修饰。v-model 有内置修饰符——.trim、.number 和 .lazy。但是,在某些情况下,你可能还需要添加自己的自定义修饰符。
<template>
<Child v-model.uppercasefn="message" />
</template>
<script setup>
import Child from './child.vue';
const message = ref('水果');
</script>
子组件:
<template>
<div>
<div>{{ modelValue }}</div>
</div>
</template>
<script setup>
const props = defineProps(['modelValue', 'modelModifiers']);
const emit = defineEmits(['update:modelValue']);
onMounted(() => {
console.log(props.modelModifiers, '自定义v-model 修饰符');
// 判断有没有uppercasefn修饰符,有的话就执行 下面得方法 方法
if (props.modelModifiers.uppercasefn) {
emit('update:modelValue', '蔬菜');
}
});
</script>
插槽可以理解为传一段 HTML 片段给子组件。子组件将 元素作为承载分发内容的出口。
插槽的基础用法非常简单,只需在 子组件 中使用 标签,就会将父组件传进来的 HTML 内容渲染出来。
<template>
<Child>
<div>渲染</div>
</Child>
</template>
子组件:
// Child.vue
<template>
<div>
<slot></slot>
</div>
</template>
具名插槽 就是在 默认插槽 的基础上进行分类,可以理解为对号入座。
<template>
<Child>
<template v-slot:monkey>
<div>渲染</div>
</template>
<button>按钮</button>
</Child>
</template>
子组件:
<template>
<div>
<!-- 默认插槽 -->
<slot></slot>
<!-- 具名插槽 -->
<slot name="monkey"></slot>
</div>
</template>
父组件需要使用 标签,并在标签上使用 v-solt: + 名称 。子组件需要在 标签里用 name= 名称 对应接收。
<template>
<!-- v-slot="{scope}" 获取子组件传上来的数据 -->
<!-- :list="list" 把list传给子组件 -->
<Child v-slot="{ scope }" :list="list">
<div>
<div>{{ scope.name }}--职业:{{ scope.occupation }}</div>
<hr />
</div>
</Child>
</template>
<script setup>
import Child from './child.vue';
const list = reactive([
{ name: '鲁班', occupation: '辅助' },
{ name: '貂蝉', occupation: '刺客和法师' },
{ name: '虞姬', occupation: '射手' },
]);
</script>
子组件:
<template>
<div>
<!-- 用 :scope="item" 返回每一项 -->
<slot v-for="item in list" :scope="item"></slot>
</div>
</template>
<script setup>
defineProps({
list: {
type: Array,
default: () => [],
},
});
</script>
Vue3中移除了事件总线,但是可以借助于第三方工具来完成,Vue官方推荐mitt或tiny-emitter;
在大多数情况下不推荐使用全局事件总线的方式来实现组件通信,虽然比较简单粗暴,但是长久来说维护事件总线是一个大难题。
mitt.js 不是专门给 Vue 服务的,但 Vue 可以利用 mitt.js 做跨组件通信。(vue3去掉了 o n 、 on、 on、off后,使用mitt第三方库替代eventBus的原理。)
npm install --save mitt
父组件:
<template>
<div>
<Home />
<Mitt/>
<div>
</template>
<script >
import Home from "@/views/home.vue";
import Mitt from "@/views/mitt.vue";
</script >
emiter.js
// mitt库默认导出的是一个函数,我们需要执行它从而得到事件总线的对象
import mitt from 'mitt'
const emiter = mitt()
export default emiter
子组件Home:
<template>
<div>
<p>这里是home组件</p>
<button @click="sendHomeContent">$mitt发送数据</button>
</div>
</template>
<script>
import { ref}from 'vue'
import emitter from "./../model/emitter.js";
export default {
setup(props,ctx) {
const money = ref(98);
var sendHomeContent=()=>{
// emit发送信息
emitter.emit("moneyEvent",money.value += 2);// 触发自定义总线moneyEvent,并传入一个对象 。
}
return{
sendHomeContent
}
}
};
</script>
子组件 Mitt:
<template>
<div>
<p>这里是Mitt组件</p>
<p>接收到的数据:{{ amount }}</p>
</div>
</template>
import { ref, onUnmounted, onMounted } from 'vue';
import emitter from "../model/event";
export default {
setup(props,ctx) {
const amount = ref(0);
const callback = (res) => {
if (res) {
amount.value = res;
}
}
onMounted(() => {
//DOM挂载完毕
emitter.on('moneyEvent', callback );
})
onUnmounted(() => {
//销毁完毕
emitter.off('moneyEvent',callback );
});
return {
amount
}
}
};
总结:
emit 发送信息
on 接收信息
off 取消监听
清除所有的事件写法
emitter.all.clear()
Vuex和Pinia是Vue3中的状态管理工具,使用这两个工具可以轻松实现组件通信。
Pinia 是最近比较火热的一个工具,也是用来处理 跨组件通信 的,极大可能成为 Vuex 5
获取节点:这是 ref 的基本功能之一,目的就是获取元素节点,在 Vue 中使用方式也很简单
<template>
<div id="app">
<div ref="test">你好!</div>
</div>
</template>
<script>
export default {
mounted() {
console.log(this.$refs.test); // <div>你好!</div>
},
};
</script>
Vue3 中通过 ref 访问元素节点与 Vue2 不太一样,在 Vue3 中我们是没有 this 的,也没有 this.$refs。想要获取 ref,我们只能通过声明变量的方式。
<template>
<div ref="test">你好!</div>
</template>
<script setup >
import { onMounted, ref } from "vue";
const test = ref(null);
onMounted(() => {
console.log(test.value); // <div>你好!</div>
});
</script>
注意点:
• 变量名称必须要与 ref 命名的属性名称一致。
• 通过 test.value 的形式获取 DOM 元素。
• 必须要在 DOM 渲染完成后才可以获取 test.value,否则就是 null。
使用 ref 的场景有多种,一种是单独绑定在某一个元素节点上,另一种便是绑定在 v-for 循环出来的元素上了。这是一种非常常见的需求,在 Vue2 中我们通常使用:ref="…"的形式,只要能够标识出每个 ref 不一样即可。
但是在 Vue3 中又不太一样,不过还是可以通过变量的形式接收。
<template>
<div>
<p ref="test">1. v-for 中的 Ref 数组</p>
<div v-for="item in 5" :key="item" :ref="setItemRef">
{{ item }} -水果
</div>
<button @click="printFN">点击2</button>
</div>
</template>
<script>
import { ref,onMounted}from 'vue'
export default {
setup(props,ctx) {
const test = ref(null);
let itemRefs = [];
const setItemRef = el => {
if (el) {
itemRefs.push(el)
}
}
onMounted(() => {
console.log(test.value,'在 vue3 中'); // <div>小猪课堂</div>
});
const printFN=()=>{
console.log(itemRefs,'在嵌套 vue3 中')
}
return {
test,
setItemRef,
printFN
}
}
};
</script>
这里我们需要注意一下:我们似乎没办法区分哪个 li 标签哪个 ref,初次之外,我们的 itemRefs 数组不能够保证与原数组顺序相同,即与 list 原数组中的元素一一对应。
前面我们在组件上定义 ref 时,都是以一个字符串的形式作为 ref 的名字,其实我们的 ref 属性还可以接收一个函数作为属性值,这个时候我们需要在 ref 前面加上:。
<template>
<div :ref="setHelloRef">水果</div>
</template>
<script>
import { ref}from 'vue'
export default {
setup(props,ctx) {
const setHelloRef = (el) => {
console.log(el); // <div>水果</div>
};
return {
setHelloRef
}
}
};
</script>
上段代码中 ref 属性接收的是一个 setHelloRef 函数,该函数会默认接收一个 el 参数,这个参数就是我们需要获取的 div 元素。假如需求中我们采用这种方式的话,那么完全可以把 el 保存到一个变量中去,供后面使用。
v-for 中使用ref 函数
<template>
<div v-for="item in 10" :key="item" :ref="(el) => setItemRefs(el, item)">
{{ item }} -水果
</div>
</template>
<script>
import { ref,onMounted}from 'vue'
export default {
setup(props,ctx) {
let itemRefs = [];
const setItemRefs = (el,item) => {
if(el) {
itemRefs.push({
id: item,
el,
});
}
}
onMounted(() => {
console.log(itemRefs,'在 vue3 中'); // <div>小猪课堂</div>
});
return {
setItemRefs
}
}
};
</script>
在 v-for 中使用函数的形式传入 ref 与不使用 v-for 时的形式差不多,不过这里我们做了一点变通,为了区别出哪个 ref 是哪一个 li 标签,我们决定将 item 传入函数,也就是(el) => setItemRefs(el, item)的写法。
这种形式的好处既让我们的操作性变得更大,还解决了 v-for 循环是 ref 数组与原数组顺序不对应的问题。
之前所使用 ref 时,都是在一个具体的 dom 元素上绑定,但是我们也可以将 ref 绑定在组件上,比如在 Vue2 中,我们将 ref 绑定在组件上时,便可以获取到该组件里面的所有数据和方法.在 Vue3 中,使用 ref 获取子组件时,如果想要获取子组件的数据或者方法,子组件可以通过defineExpose 方法暴露数据。
参考上面“expose / ref=》父获取子得属性或方法”