封装组件引发的问题——vue给对象添加响应式属性

在开发中发现发现有好多关于搜索的表单的业务,所以想封装一个搜索的组件,只用通过声明式的配置就可以自动生成我想要的搜索表单。样子就是下图这个样子:
基于ant-vue组件库封装
以上组件封装基于Ant Design of Vue组件库,原理都一样,可根据自己需求更改。因为在给form对象动态添加属性值不是响应式的,导致表单无法双向绑定form对象的值,form对象的值能改变,但是视图不会改变。这个bug尤其体现在select选择器上,所以我给select选择器绑定了change事件,通过监听change事件去修改绑定的form的值。 最初的bug代码

<template>
  <div :class="advanced ? 'search' : null">
    <a-form-model ref="queryForm" :model="form" layout="horizontal">
      <div :class="advanced ? null : 'fold'">
        <template v-for="(item, index) in config">
          <template v-if="index == 0">
            <a-row :key="index">
              <template v-for="itemChild in item">
                <a-col :md="8" :sm="24" :key="itemChild.name">
                  <a-form-model-item
                    :label="itemChild.label"
                    :labelCol="{ span: 5 }"
                    :wrapperCol="{ span: 18, offset: 1 }"
                  >
                    <a-input
                      :placeholder="itemChild.placeholder"
                      v-model="form[itemChild.bindVal]"
                      v-if="itemChild.type == 'input'"
                    />
                    <a-select
                      allowClear
                      v-if="itemChild.type == 'select'"
                      @change="selectChange($event, itemChild.bindVal)"
                      :placeholder="itemChild.placeholder"
                    >
                      <a-select-option
                        v-for="option in itemChild.option"
                        :key="option.value"
                        :value="option.value"
                      >
                        {{ option.label }}
                      </a-select-option>
                    </a-select>
                  </a-form-model-item>
                </a-col>
              </template>
            </a-row>
          </template>
          <template v-else>
            <a-row :key="index" v-if="advanced">
              <template v-for="itemChild in item">
                <a-col :md="8" :sm="24" :key="itemChild.name">
                  <a-form-model-item
                    :label="itemChild.label"
                    :labelCol="{ span: 5 }"
                    :wrapperCol="{ span: 18, offset: 1 }"
                  >
                    <a-input
                      :placeholder="itemChild.placeholder"
                      v-model="form[itemChild.bindVal]"
                      v-if="itemChild.type == 'input'"
                    />
                    <a-select
                      allowClear
                      v-if="itemChild.type == 'select'"
                      @change="selectChange($event, itemChild.bindVal)"
                      :placeholder="itemChild.placeholder"
                    >
                      <a-select-option
                        v-for="option in itemChild.option"
                        :key="option.value"
                        :value="option.value"
                      >
                        {{ option.label }}
                      </a-select-option>
                    </a-select>
                  </a-form-model-item>
                </a-col>
              </template>
            </a-row>
          </template>
        </template>
      </div>
      <span style="float: right; margin-top: 3px">
        <a-button type="primary" @click="query">{{ queryBtnText }}</a-button>
        <a-button v-if="hasResetBtn" style="margin-left: 8px" @click="reset"
          >重置</a-button
        >
        <a @click="toggleAdvanced" style="margin-left: 8px">
          {{ advanced ? "收起" : "展开" }}
          <a-icon :type="advanced ? 'up' : 'down'" />
        </a>
      </span>
    </a-form-model>
  </div>
</template>

<script>
export default {
  nmae: "queryForm",
  props: {
    queryBtnText: {
      type: String,
      default: "查询",
    },
    hasResetBtn: {
      type: Boolean,
      default: true,
    },
    config: {
      type: Array,
    },
  },
  data() {
    return {
      // 控制展开收起
      advanced: false,
      form: {},
    };
  },
  beforeCreate() {
    this.$nextTick(() => {});
  },
  created() {
    this.config.flat(Infinity).forEach((item) => {
      this.form[item.bindVal] = '';
    });
  },
  mounted() {},
  methods: {
    query() {
      console.log(this.form);
    },
    reset() {
      for (let key in this.form) {
        this.form[key] = "";
      }
      // 强制刷新组件,因为form对象不是响应式的
      this.$forceUpdate();
    },
    // 展开收缩
    toggleAdvanced() {
      this.advanced = !this.advanced;
    },
    // 监听选择器change事件
    selectChange(val, key) {
      this.form[key] = val;
    },
  },
};
</script>

<style lang="less" scoped>
.search {
  margin-bottom: 54px;
}
.fold {
  width: calc(100% - 216px);
  display: inline-block;
}
@media screen and (max-width: 900px) {
  .fold {
    width: 100%;
  }
}
</style>

找到原因后,修改正确的代码:

<template>
  <div :class="advanced ? 'search' : null">
    <a-form-model ref="queryForm" :model="form" layout="horizontal">
      <div :class="advanced ? null : 'fold'">
        <template v-for="(item, index) in config">
          <template v-if="index == 0">
            <a-row :key="index">
              <template v-for="itemChild in item">
                <a-col :md="8" :sm="24" :key="itemChild.name">
                  <a-form-model-item
                    :label="itemChild.label"
                    :labelCol="{ span: 5 }"
                    :wrapperCol="{ span: 18, offset: 1 }"
                  >
                    <a-input
                      :placeholder="itemChild.placeholder"
                      v-model="form[itemChild.bindVal]"
                      v-if="itemChild.type == 'input'"
                    />
                    <a-select
                      allowClear
                      v-if="itemChild.type == 'select'"
                      v-model="form[itemChild.bindVal]"
                      :placeholder="itemChild.placeholder"
                    >
                      <a-select-option
                        v-for="option in itemChild.option"
                        :key="option.value"
                        :value="option.value"
                      >
                        {{ option.label }}
                      </a-select-option>
                    </a-select>
                  </a-form-model-item>
                </a-col>
              </template>
            </a-row>
          </template>
          <template v-else>
            <a-row :key="index" v-if="advanced">
              <template v-for="itemChild in item">
                <a-col :md="8" :sm="24" :key="itemChild.name">
                  <a-form-model-item
                    :label="itemChild.label"
                    :labelCol="{ span: 5 }"
                    :wrapperCol="{ span: 18, offset: 1 }"
                  >
                    <a-input
                      :placeholder="itemChild.placeholder"
                      v-model="form[itemChild.bindVal]"
                      v-if="itemChild.type == 'input'"
                    />
                    <a-select
                      allowClear
                      v-if="itemChild.type == 'select'"
                      v-model="form[itemChild.bindVal]"
                      :placeholder="itemChild.placeholder"
                    >
                      <a-select-option
                        v-for="option in itemChild.option"
                        :key="option.value"
                        :value="option.value"
                      >
                        {{ option.label }}
                      </a-select-option>
                    </a-select>
                  </a-form-model-item>
                </a-col>
              </template>
            </a-row>
          </template>
        </template>
      </div>
      <span style="float: right; margin-top: 3px">
        <a-button type="primary" @click="query">{{ queryBtnText }}</a-button>
        <a-button v-if="hasResetBtn" style="margin-left: 8px" @click="reset"
          >重置</a-button
        >
        <a @click="toggleAdvanced" style="margin-left: 8px">
          {{ advanced ? "收起" : "展开" }}
          <a-icon :type="advanced ? 'up' : 'down'" />
        </a>
      </span>
    </a-form-model>
  </div>
</template>

<script>
export default {
  nmae: "queryForm",
  props: {
    queryBtnText: {
      type: String,
      default: "查询",
    },
    hasResetBtn: {
      type: Boolean,
      default: true,
    },
    config: {
      type: Array,
    },
  },
  data() {
    return {
      // 控制展开收起
      advanced: false,
      form: {},
    };
  },
  beforeCreate() {
    this.$nextTick(() => {});
  },
  created() {
    this.config.flat(Infinity).forEach((item) => {
      // 给对象新增响应式属性
      this.$set(this.form, item.bindVal, undefined);
    });
  },
  mounted() {},
  methods: {
    query() {
      console.log(this.form);
    },
    reset() {
      for (let key in this.form) {
        this.form[key] = undefined;
      }
      // 强制刷新组件,因为form对象不是响应式的
      // this.$forceUpdate();
    },
    // 展开收缩
    toggleAdvanced() {
      this.advanced = !this.advanced;
    },
    // 监听选择器change事件
    selectChange(val, key) {
      this.form[key] = val;
    },
  },
};
</script>

<style lang="less" scoped>
.search {
  margin-bottom: 54px;
}
.fold {
  width: calc(100% - 216px);
  display: inline-block;
}
@media screen and (max-width: 900px) {
  .fold {
    width: 100%;
  }
}
</style>

在父组件中通过声明式的去使用封装好的组件:

<template>
  <div class="userPage">
    <a-row :gutter="24">
      <!-- 左侧树 -->
      <a-col :span="4">
        <a-card>
          <a-directory-tree
            multiple
            :show-line="false"
            defaultExpandAll
            @select="onSelect"
            @expand="onExpand"
          >
            <a-tree-node
              v-for="item in treeData"
              :key="item.id"
              :title="item.name"
            >
              <a-tree-node
                v-for="child in item.subs"
                :key="child.id"
                :title="child.name"
                is-leaf
              />
            </a-tree-node>
          </a-directory-tree>
        </a-card>
      </a-col>
      <!-- 右侧表格 -->
      <a-col :span="20">
        <a-card>
          <!-- 搜索部分 -->
          <queryForm :config="queryFormConfig" />
          <!-- 表格部分 -->
          <queryTable />
        </a-card>
      </a-col>
    </a-row>
  </div>
</template>

<script>
import { getList } from "@/services/jurisdiction";
import queryTable from "@/components/myComponents/queryTable";
import queryForm from "@/components/myComponents/queryForm";

export default {
  components: { queryTable, queryForm },
  data() {
    return {
      treeData: [],
      queryFormConfig: [
        [
          {
            label: "部门",
            type: "select",
            placeholder: "请选择部门",
            bindVal: "department",
            option: [
              { value: "0", label: "经济部" },
              { value: "1", label: "市场部" },
              { value: "2", label: "销售部" },
            ],
          },
          {
            label: "姓名",
            type: "input",
            placeholder: "请输入姓名",
            bindVal: "name",
          },
          {
            label: "电话",
            type: "input",
            placeholder: "请输入电话",
            bindVal: "tel",
          },
        ],
        [
          {
            label: "性别",
            type: "input",
            placeholder: "请输入姓名",
            bindVal: "sex",
          },
          {
            label: "年龄",
            type: "input",
            placeholder: "请输入姓名",
            bindVal: "age",
          },
          {
            label: "爱好",
            type: "input",
            placeholder: "请输入姓名",
            bindVal: "hobby",
          },
        ],
      ],
    };
  },
  created() {
    this.getTreeList();
  },
  methods: {
    async getTreeList() {
      const { data } = await getList();
      this.treeData = data;
    },
    onSelect(keys, event) {
      console.log("Trigger Select", keys, event);
    },
    onExpand() {
      console.log("Trigger Expand");
    },
  },
};
</script>

<style lang="less" scoped>
</style>

文章最后:更多精彩文章,欢迎参观我的公众号:小笑残虹。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值