美文网首页vue
vue3 笔记一:在子组件中 props 的参数无法直接被改变

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

作者: 暴躁程序员 | 来源:发表于2024-09-27 23:06 被阅读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>
    

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/cbdlrjtx.html