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

本篇是对3-1的补充,使用setup语法糖,以及eslint之后的bug修复。

单向数据流

可以使用eslint来主动识别是否编码过程违背了单向数据流的思想,若有问题,编译时可能出现Unexpected mutation of "props" prop的报错。

业务描述

Navbar中有一个avatar,鼠标悬浮出现下拉框,点击个人中心后使用抽屉形式的表单来做个人信息的修改。
具体设计如下:
vue3
vue3

父子组件通信实现

我的想法是Navbar作为一个父组件(总控制器),有Avatar和Drawer两个子组件,子组件管理自己的状态,当有对其他组件有影响的状态的改变要通知父组件。
Navbar.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="navbar-wrapper">
<Avatar @handleDrawer="handleDrawer" />
</div>
<Drawer :show="showDrawer" @handleDrawer="handleDrawer" />
</template>

<script setup lang="ts">
import Avatar from './avatar.vue'
import Drawer from './drawer.vue'
const showDrawer = ref(false)

// drawer总控制器
const handleDrawer = (flag: boolean) => {
console.log('flag=', flag)
showDrawer.value = flag
}
</script>

Avatar.vue中图标的hover状态只与自身有关,点击“个人中心”-openMyInfo方法会触发Drawer状态改变,使用emit告知父组件状态改变。

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
<template>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
<div class="avatar-wrapper">
<img src="user-avatar" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="openMyInfo"
@mouseenter="handleHover(1, true)"
@mouseleave="handleHover(1, false)"
>
<div class="left-img">
<img v-if="!state.hoverInfo" src="@/assets/img/my-info.png" alt="" />
<img v-else src="@/assets/img/my-info-active.png" alt="" />
</div>
<span>个人中心</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>

<script setup lang="ts">
import FormInstance from 'element-plus'
import { ElMessage, ElMessageBox, ElNotification, ElDrawer } from 'element-plus'
const state = reactive({
hoverInfo: false,
})

const handleHover = (flag: Number, action: any) => {
state.hoverInfo = action
}
const myEmit = defineEmits(['handleDrawer'])
const openMyInfo = () => {
myEmit('handleDrawer', true)
}
</script>

Drawer.vue
这里需要注意的是,我一开始直接将 props.show 赋予 v-model,结果出现了vue/no-mutating-props问题,查阅资料后发现, 是直接对 prop 的内容进行了修改,违反了单向数据流原则。
原因:实质上,在子组件内,我们并不可以直接将 prop 的变量应用于子组件深层次组件的 v-model 上(因为 v-model 会隐含的对 prop 值更新。
解决方式:将 props.show 通过中间变量进行 v-model 化,再把中间变量传入子组件深层次组件的 v-model 。下面的计算属性showDrawer就是这里的中介。

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
<template>
<el-drawer
ref="drawerRef"
v-model="showDrawer"
:before-close="handleClose"
>
<el-tabs v-model="drawActive" type="card">
<el-tab-pane label="修改密码" name="first">
<el-form :model="pwdForm" :rules="pwdRules" ref="pwdFormRef">
<el-form-item label="新密码">
<el-input v-model="pwdForm.password" type="password"/>
</el-form-item>
<div class="drawer__footer">
<el-button @click="cancelForm">取消</el-button>
<el-button type="primary" @click="Sumbit(pwdFormRef)"></el-button>
</div>
</el-form>
</el-tab-pane>
</el-tabs>
</el-drawer>
</template>

<script setup lang="ts">
import FormInstance from 'element-plus'
import { ElMessage, ElNotification, ElDrawer } from 'element-plus'
import { updateUserPwd } from '@/api/users'

const props = defineProps({
show: {
type: Boolean,
default: false
}
})
const myEmit = defineEmits(['handleDrawer'])
const showDrawer = computed({
get: () => props.show,
set: () => {}
})

const handleClose = () => {
myEmit('handleDrawer', false)
}

const cancelForm = () => {
myEmit('handleDrawer', false)
}
</script>

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!