vue 单向数据流特点
一、根据使用场景分为以下解决方案:
场景一:父子状态初始一致,之后不一致,把父组件状态作为子组件初始值,比如:表单提交的数据反显
方案一:将 props 参数作为子组件新状态的初始值
注意:当 props 参数为对象类型时。需要借助深拷贝,否则会影响父组件状态
- 在父组件中
<script setup>
import { ref } from "vue";
import DialogForm from "@/components/DialogForm.vue";
const show = ref(false);
const toggle = () => {
show.value = !show.value;
};
const formData = ref({
username: "alias",
password: "123456",
});
const test = 100;
const submit = (v) => {
console.log("submit:", v);
};
</script>
<template>
<h1>{{ show }}</h1>
<el-button type="primary" @click="toggle">{{ show ? 'hide' : 'show' }} dialog</el-button>
<dialog-form v-model:show="show" :formData="formData" :test="test" @submit="submit" />
</template>
- 在子组件中
<script setup>
import { ref } from "vue";
const props = defineProps({
show: {
type: Boolean,
default: false,
},
formData: {
type: Object,
default: {},
},
test: {
type: Number,
default: 100,
},
});
const emit = defineEmits(["update:show", "submit"]);
// 子组件把父组件状态作为新状态的初始值,此处必须深拷贝,否则会同步修改父组件状态
const form = ref(JSON.parse(JSON.stringify(props.formData)));
// 不改变的父组件状态,作为重置数据
const resetForm = () => {
form.value = JSON.parse(JSON.stringify(props.formData));
};
const confirm = () => {
emit("submit", form.value);
resetForm();
emit("update:show", false);
};
const cancel = () => {
resetForm();
emit("update:show", false);
};
// 基础类型直接作为初始值即可
const newTest = ref(props.test);
const changeBase = () => {
newTest.value += 1;
};
</script>
<template>
<hr />
<div v-if="show">
<div>
<div style="margin: 20px 0">{{ formData }}</div>
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-form-item label="用户名">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" />
</el-form-item>
</el-form>
</div>
<div style="margin: 20px 0">
<el-button type="primary" @click="changeBase">改变基础类型props</el-button>
<div>{{ test }}</div>
<div>{{ newTest }}</div>
</div>
<div>
<el-button type="primary" @click="confirm">confirm</el-button>
<el-button @click="cancel">cancel</el-button>
</div>
</div>
</template>
场景二:父子状态始终一致,比如:dialog 的展示和隐藏
方案一:将 props 属性定义为对象类型,在子组件中改变 props 参数内部属性会改变父组件状态
- 在父组件中
<script setup>
import { ref } from "vue";
import DialogForm from "@/components/DialogForm.vue";
const showConfig = ref({
show: false,
});
const toggle = () => {
showConfig.value.show = !showConfig.value.show;
};
</script>
<template>
<h1>{{ showConfig.show }}</h1>
<el-button type="primary" @click="toggle">{{ showConfig.show ? 'hide' : 'show' }} dialog</el-button>
<dialog-form :showConfig="showConfig" />
</template>
- 在子组件中
<script setup>
const props = defineProps({
showConfig: {
type: Object,
default() {
return {};
},
},
});
const confirm = () => {
props.showConfig.show = false;
};
const cancel = () => {
props.showConfig.show = false;
};
</script>
<template>
<hr />
<div v-if="showConfig.show">
<div>
<el-button type="primary" @click="confirm">confirm</el-button>
<el-button @click="cancel">cancel</el-button>
</div>
</div>
</template>
方案二:在子组件中通过 emit 事件改变父组件状态
- 在父组件中
<script setup>
import { ref } from "vue";
import DialogForm from "@/components/DialogForm.vue";
const show = ref(false);
const toggle = () => {
show.value = !show.value;
};
</script>
<template>
<h1>{{ show }}</h1>
<el-button type="primary" @click="toggle">{{ show ? 'hide' : 'show' }} dialog</el-button>
<dialog-form :show="show" @closeDialog="toggle" />
</template>
- 在子组件中
<script setup>
const props = defineProps({
show: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["closeDialog"]);
const confirm = () => {
emit("closeDialog", false);
};
const cancel = () => {
emit("closeDialog", false);
};
</script>
<template>
<hr />
<div v-if="show">
<div>
<el-button type="primary" @click="confirm">confirm</el-button>
<el-button @click="cancel">cancel</el-button>
</div>
</div>
</template>
方案三:在子组件中通过 emit 事件改变父组件状态(借助 v-model 语法糖简写)
- 在父组件中
<script setup>
import { ref } from "vue";
import DialogForm from "@/components/DialogForm.vue";
const show = ref(false);
const toggle = () => {
show.value = !show.value;
};
</script>
<template>
<h1>{{ show }}</h1>
<el-button type="primary" @click="toggle">{{ show ? 'hide' : 'show' }} dialog</el-button>
<dialog-form v-model:show="show" />
</template>
- 在子组件中
<script setup>
const props = defineProps({
show: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:show"]);
const confirm = () => {
emit("update:show", false);
};
const cancel = () => {
emit("update:show", false);
};
</script>
<template>
<hr />
<div v-if="show">
<div>
<el-button type="primary" @click="confirm">confirm</el-button>
<el-button @click="cancel">cancel</el-button>
</div>
</div>
</template>
二、案例: 实现 dialog form 组件
- 在 父组件 中
<script setup>
import MyDialog from "@/components/MyDialog";
import { ref } from "vue";
// 1、dialog 是否展示状态
const isShow = ref(false);
const confirm = (v) => {
console.log(v);
};
const cancel = (v) => {
console.log(v);
};
// 2、定义form表单数据和配置
const formConfig = ref([
{
field: "name",
span: 20,
label: "名字",
type: "input",
},
{
field: "gender",
span: 20,
label: "性别",
type: "radio",
options: [
{
label: "男",
value: 1,
},
{
label: "女",
value: 2,
},
],
},
]);
const formData = ref({
name: "123",
gender: 1,
});
</script>
<template>
<h1>修改子组件 props</h1>
<div>{{ formData }}</div>
<el-button type="primary" @click="isShow = !isShow"> dialog {{ isShow }}</el-button>
<MyDialog v-model:isShow="isShow" :formConfig="formConfig" :formData="formData" @emitConfirm="confirm" @emitCancel="cancel" />
</template>
- 在 子组件 中
<template>
<div class="dialog-wrapper">
<el-dialog v-model="isDialog" title="提示" width="500" :before-close="handleClose">
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-col v-for="item in formConfig" :key="item.field" :span="item.span">
<el-form-item v-if="item.type === 'input'" :label="item.label">
<el-input v-model="form[item.field]" />
</el-form-item>
<el-form-item v-if="item.type === 'radio'" :label="item.label">
<el-radio-group v-model="form[item.field]">
<el-radio v-for="ra in item.options" :key="ra.label" :value="ra.value">{{ ra.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel"> 取消 </el-button>
<el-button type="primary" @click="confirm"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, watch, toRaw } from "vue";
const props = defineProps({
isShow: {
type: Boolean,
default: false,
},
formConfig: {
type: Array,
default() {
return [];
},
},
formData: {
type: Object,
default() {
return {};
},
},
});
// 1、定义 form 表单数据,深拷贝不直接改变 props 属性
const form = ref(JSON.parse(JSON.stringify(props.formData)));
// 2、定义控制 dialog 状态,此处需要借助watch、emit,不直接改变 props 属性
const isDialog = ref(false);
watch(
() => props.isShow,
(newValue) => {
isDialog.value = newValue;
}
);
const emit = defineEmits(["emitConfirm", "emitCancel", "update:isShow"]);
// 3、表单提交:提交表单数据、从父组件改变控制展示 dialog 状态、重置表单数据
const handleClose = () => {
emit("update:isShow", false);
form.value = JSON.parse(JSON.stringify(props.formData));
};
const confirm = () => {
emit("emitConfirm", form);
handleClose();
};
const cancel = () => {
emit("emitCancel", "");
handleClose();
};
</script>
网友评论