vue3笔记(3-1)通信 父子组件通信

一个功能在多个页面中被使用到,可以抽成了组件,以实现复用。
vue使用单项数据流保持数据的统一,业务中难免遇到父子组件通信问题,这篇做一个使用记录。

执行顺序

执行顺序:父组件先创建,然后子组件创建;子组件先挂载,然后父组件挂载,即“父beforeCreate-> 父create -> 子beforeCreate-> 子created -> 子mounted -> 父mounted”。

单向数据流

所有的prop都使得其父子之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致应用的数据流向难以理解。额外的,每次父级组件发生更新时,子组件中所有的prop都将会刷新为最新的值。

父子组件通信实现—父组件修改子组件数据

应用场景:父组件改变子组件的值。
应用方法:子组件通过defineExpose暴露自己的属性,父组件通过ref获取子组件的属性并进行修改。
子组件 child.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
  <div>我是demo组件</div>
</template>
<script setup>
  import { ref, reactive } from 'vue'
  const data = reactive({
    name: 'ginny',
  })

  defineExpose({
    data,
  })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
  <Child ref="refChild"></Child>
</template>

<script setup>
import { ref ,reactive,onMounted} from 'vue'
import Child from './view/child.vue'

let refChild = ref(null)

onMounted(() => {
  const { data } = refChild.value //子组件属性
})
</script>

注意:子组件只能是vue形式写的组件,之前复用了一个tsx组件,defineExpose方法失效。

父子组件通信—自定义事件

应用场景:子组件的状态改变时,父组件对应状态也发生改变。
应用方法:子组件通过$emit派发一个自定义事件,父组件接收到后,由父组件修改自身状态。

业务描述:公司的项目需要实现针对组织架构选择部门—即树状展示部门,同时多选。部门展示被抽成子组件,调用时,需要获取子组件已经勾选的内容。
实现方案:父级向子级dept-tree通过props传入状态sum(这里为父级sumbit事件中的触发的状态改变)。子组件观察状态的变化,通过 $emit 事件告诉父组件。父组件使用自定义的getDeptNodes方法获取payload(即子组件已选内容)。

父组件调用代码如下:

1
2
3
4
5
<dept-tree
@getDeptNodes="getDeptNodes"
:sum="deptChosen"
>
</dept-tree>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import DeptTree from "@/components/Tree/dept.vue";
components: {
DeptTree
},
setup: {
const state = reactive({
deptChosen: [], //dept checkbox选中状态数组
});
const getDeptNodes = (params) => {
console.log('deptNodes=',params);
}
return {
getDeptNodes
}
}

子组件模板渲染部门数据。
通过观察状态sum的变化,通过emit触发自定义的getDeptNodes方法,传入已选内容。

1
2
3
4
5
6
7
8
9
10
11
<template>
<el-tree
ref="treeRef"
:data="deptTree"
show-checkbox
default-expand-all
check-strictly=true
node-key="id"
:props="defaultProps"
/>
</template>

Vue对定义了typeprop执行运行时验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<script lang="ts">
import { defineComponent, reactive, toRefs, onMounted, watch } from 'vue';
import { getDept} from "@/api/users";
import { ElMessage, ElTree } from "element-plus";
import { arrToTree } from "@/utils";

export default defineComponent({
name: "deptTree",
props: {
sum: { // 作为ref对象传入,可能有其他问题
type: Object,
default: null
}
},
setup(props, { emit }) {
const state = reactive({
deptTree:[]
});

onMounted(() => {
getDataDept();
});

// 部门数据
const getDataDept = () => {
getDept({}).then(res => {
const { data } = res.data;
let deptTreeArr = [];
deptTreeArr.push(arrToTree(data));
state.deptTree = deptTreeArr
}).catch((error) => {
ElMessage.error('获取部门数据错误!');
})
};
const treeRef = ref<InstanceType<typeof ElTree>>();
const defaultProps = {
children: 'children',
label: 'label',
}

const sum = props.sum;
watch(sum,() => {
console.log('props.sum=',sum)
emit('getDeptNodes',{"deptNodes":getCheckedNodes()})
});

const getCheckedNodes = () => { // 选中的部门
return treeRef.value!.getCheckedNodes(false, false);
}

return {
treeRef,
defaultProps,
...toRefs(state),
}
}
});
</script>