还在烦恼满足不了坑爹产品的奇葩需求?来感受下Vue3组合式api的魅力!

写在前头

相信大家平时工作中没少被各种坑爹产品的奇葩需求气吐血,原本谈的好好的需求说改就改,也不知道是用户抽风还是产品抽风。被这么搞了几次以后,想了想还是写个组件封装思路出来保护自己所剩不多的头发。

后续会出一个plus版本、敬请期待`

准备

采用vue3的组合式api来编写,详情可以参考我的 common-template 专栏,有现成的代码可以使用。

一、目标

先明确我们要做什么:

  • 封装一个 useXXX 的函数,可以通过该函数传递参数、调用组件内部方法(修改传入参数、获取传入参数等)。
  • 主模块可以使用 @register 的方式进行复用。

对于一个组件来说,上面两点是最基本的需求。

二、文件结构

bash 复制代码
📦XXX
 ┣ 📂src                   
 ┃ ┣ 📂comps            # 子模块
 ┃ ┃ ┣ 📜XXXitem.vue
 ┃ ┣ 📂hooks 
 ┃ ┃ ┣ 📜useXXX.ts
 ┃ ┣ 📜BasicXXX.vue     # 主模块
 ┃ ┣ 📜props.ts
 ┃ ┣ 📜types.ts
 ┗ 📜index.ts

三、具体实现

在新建好的 useXXX 文件里编写 register 部分,这部分主要是为了让组件可以复用。

把组件要暴露出去的 method 也一起放在这里。

ts 复制代码
// src/hooks/useXXX.ts
import { nextTick, onUnmounted, ref, unref, watch } from 'vue';
import { getDynamicProps } from '/@/utils/props';
import { DemoActionType, DemoPropsType, UseDemoReturnType } from '../types';

export function useDemo(props: DemoPropsType): UseDemoReturnType {
  // 组件实例
  const listRef = ref<Nullable<DemoActionType>>(null);

  // 确保获取到组件实例
  async function getDemo() {
    const list = unref(listRef);
    if (!list) {
      console.log('demo示例尚未获取,请确保在执行操作时已呈现demo!');
    }
    await nextTick();
    return list as DemoActionType;
  }

  // 注册组件
  function register(instance: DemoActionType) {
    // 确保性能
    onUnmounted(() => {
      listRef.value = null;
    });

    // 组件实例赋值
    listRef.value = instance;

    watch(
      () => props,
      () => {
        // getDynamicProps函数可以把ref数据转为unref数据
        props && instance.setProps(getDynamicProps(props));
      },
      {
        immediate: true,
        deep: true,
      },
    );
  }

  // 组件暴露出去的方法
  const method: DemoActionType = {
    setProps: async (props: Partial<DemoPropsType>) => {
      const demo = await getDemo();
      demo.setProps(props);
    },
  };

  return [register, method];
}
ts 复制代码
// utils
import { unref } from 'vue';

// 动态使用hook参数
export function getDynamicProps<T, U>(props: T): Partial<U> {
  const ret: Recordable = {};

  Object.keys(props as Recordable).forEach((key) => {
    ret[key] = unref((props as Recordable)[key]);
  });

  return ret as Partial<U>;
}
ts 复制代码
// src/types.ts
export interface DemoPropsType {
  // 入参1
  field1: string;
}

export interface DemoActionType {
  setProps: (prop: Partial<DemoPropsType>) => void;
}

export type RegisterFn = (instance: DemoActionType) => void;

export type UseDemoReturnType = [RegisterFn, DemoActionType];

接下来我们到主模块里编写 useXXX函数里用到的 setProps 方法。

把主模块需要的 getProps 方法也放在这里。

vue 复制代码
// src/BasicDemo.vue
<script setup lang="ts">
  import { computed, onMounted, ref, unref } from 'vue';
  import { DemoActionType, DemoPropsType } from './types';
  import { demoProps } from './props';
  import { deepMerge } from '/@/utils';

  const props = defineProps(demoProps);

  const emit = defineEmits(['register']);

  const propsRef = ref<Partial<DemoPropsType>>();

  const getProps = computed(() => {
    return { ...props, ...unref(propsRef) };
  });

  const setProps = (props: Partial<DemoPropsType>) => {
    propsRef.value = deepMerge(unref(propsRef) || {}, props);
  };

  const demoAction: DemoActionType = {
    setProps,
  };

  onMounted(() => {
    emit('register', demoAction);
  });
</script>

<template>
  <div>{{ getProps.field1 }}</div>
</template>
ts 复制代码
// src/props.ts
export const demoProps = {
  filed1: {
    type: String,
    default: '',
  },
};

四、思路解析

当我们在页面上用 <BasicXXX @register="register"></BasicXXX> 的方式使用时,组件通过在 onMounted 生命周期里 emit 出方法来实现暴露组件内部函数的需求。

const [register, {setProps}] = useXXX() 的方式来调用useXXX 方法里的 register满足组件复用的需求。

当我们传参的时候会触发 watch 调用 setProps 方法,满足传参和修改参数的需求。

这实际上就是组合式api的一种运用。

vue 复制代码
<script setup lang="ts">
  import { BasicDemo, useDemo } from '/@/components/hp-demo';

  const [register, { setProps }] = useDemo({
    field1: '示例数据',
  });
</script>

<template>
  <div>
    <span @click="setProps({ field1: '修改后数据1' })">点击修改参数1</span>
    <span @click="setProps({ field1: '修改后数据2' })">点击修改参数2</span>
    <BasicDemo @register="register" />
  </div>
</template>

如果我们有多个组件,可以使用多个 useXXX 互相不会冲突。

如果我们有多个子模块,可以在 comps 文件夹里添加,在 BasicXXX 主模块里使用。

如果我们有多个 hook ,可以在 hooks 文件夹里编写,在 BasicXXX 主模块里通过 demoAction 向外暴露。

相关推荐
難釋懷28 分钟前
Vue解决开发环境 Ajax 跨域问题
前端·vue.js·ajax
特立独行的猫a33 分钟前
Nuxt.js 中的路由配置详解
开发语言·前端·javascript·路由·nuxt·nuxtjs
咸虾米35 分钟前
在uniCloud云对象中定义dbJQL的便捷方法
前端·javascript
梨子同志36 分钟前
JavaScript Proxy 和 Reflect
前端·javascript
汤圆炒橘子40 分钟前
状态策略模式的优势分析
前端
90后的晨仔1 小时前
解析鸿蒙 ArkTS 中的 Union 类型与 TypeAliases类型
前端·harmonyos
IT_陈寒1 小时前
Element Plus 2.10.0 重磅发布!新增Splitter组件
前端·人工智能·后端
挑战者6668881 小时前
vue入门环境搭建及demo运行
前端·javascript·vue.js
贩卖纯净水.1 小时前
Webpack的基本使用 - babel
前端·webpack·node.js
在人间耕耘2 小时前
鸿蒙应用开发:WebSocket 使用示例
typescript