从Vue3.0来看组件新写法

2,197 阅读4分钟

之前写过一篇文章漫谈 vue 组件设计 ,这里写这篇文章原因是不满一个相同的组件需要在各个页面重复写,而且这样搞维护成本实在太高,但是受限于 vue2.x 的响应机制没有想到更好的方法,所以在某些组件中使用的时候(例如在表单中使用需要添加校验规则、按需使用组件)需求很难实现。

而从 Vue3.0 的响应式的重写以及组合 api 的出现,让我觉得眼前一亮,下面以省、市、区级联选择和表单校验为例子,因为主要 讲解思路所以 UI 方面选择了ant-design-vue帮助快速构建视图

在线演示

准备实现功能

1

先上一张最终实现的效果图,下面内容会对照着讲解,代码放置到了github.com/bosens-Chin…

  • 省、市、区组件可以自由组合使用(但是这个例子选取的不太好关系相互之间依赖,所以实际上只能省+市+区省+市
  • 不需要手动管理状态,只需要提供一个v-model作为绑定值即可

实现

根据上面需求,我们一个个实现

  • 组件组合使用

要实现组件组合使用,这就要求组件本身耦合度不能太高,只展示数据并不进行任何的省、市、区具体功能的处理

<a-select value={props.modelValue} onChange={onChange}>
  {...modelValueList.value.map((item) => {
    return <a-select-option value={item.name}>{item.value}</a-select-option>;
  })}
</a-select>

组件的结构如上所示,其实省、市、区的组件结构是一样的不同的只是结构不同,所以实现的时候只需要使用工厂模式即可,这样可以做到少维护可扩展。

  • 管理更新状态

这一点使用了组合 api 来完成,在新发布的版本中,vue 把响应式功能抽离出来,响应式数据可以通过ref来定义,通过watch来做到监听,例如这一个例子

import { watch, ref } from "vue";
const test = ref(null);
watch(test, () => {
  console.log("变更");
});
test.value = 5;

而且这个 ref 可以共享给其他程序使用,例如通过import导入给其他组件使用,这样刷新的时候自然而然组件也会发生变化。

下面通过一个rely.js文件来进行组件状态管理,它可以帮助我们实现以下流程

123

在实际过程中我们切换省、市、区的时候需要我们还可以判断下绑定值存不存在 list 数据中,不存在就要清空。

下面通过注释的方式来讲解

import { ref, watch } from "vue";
import { getProvince, getCity, getArea } from "../../api/region";

const created = () => {
  // 下面字段分比为省、市、区的绑定字段以及list数据
  const province = ref(undefined);
  const provinceList = ref([]);
  const city = ref(undefined);
  const cityList = ref([]);
  const area = ref(undefined);
  const areaList = ref([]);

  // 初始化省级列表
  getProvince()
    .then((res) => {
      provinceList.value = res;
    })
    .catch(() => {
      provinceList.value = [];
    });

  // 监听省级变化,更新市级的变化
  watch(province, (value) => {
    if (!value) {
      city.value = undefined;
      cityList.value = [];
      return;
    }
    getCity(value)
      .then((res) => {
        cityList.value = res;
        // 如果绑定值不存在对应的list数据中清除掉
        city.value = res.find((f) => f.name === city.value)?.name;
      })
      .catch(() => {
        cityList.value = [];
        city.value = undefined;
      });
  });
  // 监听市级变化,更新区的数据
  watch(city, (value) => {
    if (!value) {
      areaList.value = [];
      area.value = undefined;
      return;
    }
    getArea(value)
      .then((res) => {
        areaList.value = res;
        // 如果绑定值不存在对应的list数据中清除掉
        area.value = res.find((f) => f.name === area.value)?.name;
      })
      .catch(() => {
        areaList.value = [];
        area.value = undefined;
      });
  });
  // 返回数据,提供给其他组件使用
  return {
    province,
    provinceList,
    city,
    cityList,
    area,
    areaList,
  };
};
export default created;

这里需要返回一个函数,因为组件状态不能被污染

  • 组合

assembly.js

import { watch } from "vue";
import created from "./rely";

const basicsComponents = () => {
  // 数据
  const { province, provinceList, area, areaList, city, cityList } = created();
  // 工厂模式返回视图
  const assemble = (modelValue, modelValueList) => {
    return {
      // 3.0中默认绑定的字段
      props: {
        modelValue: null,
      },
      setup(props, { emit }) {
        // 监听绑定值变化
        watch(modelValue, (value) => {
          emit("update:modelValue", value);
        });
        // 监听modelValue值,更新到v-model中
        watch(
          () => props.modelValue,
          (value) => {
            modelValue.value = value;
          },
          { immediate: true }
        );
        const onChange = (value) => {
          emit("update:modelValue", value);
        };
        return () => (
          <a-select value={props.modelValue} onChange={onChange}>
            {...modelValueList.value.map((item) => {
              return (
                <a-select-option value={item.name}>
                  {item.value}
                </a-select-option>
              );
            })}
          </a-select>
        );
      },
    };
  };

  const componentsProvince = assemble(province, provinceList);
  const componentsCity = assemble(city, cityList);
  const componentsArea = assemble(area, areaList);
  return {
    componentsProvince,
    componentsCity,
    componentsArea,
  };
};
export default basicsComponents;

最后

在使用过程中发现 Vue3.0 的热更新速度提升幅度不小使用起来很丝滑,但是还是有不少问题,比如我对 css 添加了scoped在开发环境下没有任何问题,在生产环境下data-***属性没有出现导致样式没作用起来,所以生产环境使用需要谨慎。

文章如果对你有帮助,可以点一下star支持一下作者。