vue

vue3 笔记一:在子组件中 props 的参数无法直接被改变

2024-09-27  本文已影响0人  暴躁程序员

vue 单向数据流特点

一、根据使用场景分为以下解决方案:

场景一:父子状态初始一致,之后不一致,把父组件状态作为子组件初始值,比如:表单提交的数据反显

方案一:将 props 参数作为子组件新状态的初始值

注意:当 props 参数为对象类型时。需要借助深拷贝,否则会影响父组件状态

  1. 在父组件中
<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>
  1. 在子组件中
<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 参数内部属性会改变父组件状态
  1. 在父组件中
<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>
  1. 在子组件中
<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 事件改变父组件状态
  1. 在父组件中
<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>
  1. 在子组件中
<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 语法糖简写)
  1. 在父组件中
<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>
  1. 在子组件中
<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 组件

  1. 在 父组件 中
<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>
  1. 在 子组件 中
<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>
上一篇 下一篇

猜你喜欢

热点阅读