还在烦恼满足不了坑爹产品的奇葩需求?来感受下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 向外暴露。

相关推荐
真滴book理喻19 分钟前
Vue(四)
前端·javascript·vue.js
蜜獾云22 分钟前
npm淘宝镜像
前端·npm·node.js
dz88i822 分钟前
修改npm镜像源
前端·npm·node.js
Jiaberrr26 分钟前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
顾平安2 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网2 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工2 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染
不是鱼2 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
沈剑心2 小时前
如何在鸿蒙系统上实现「沉浸式」页面?
前端·harmonyos
一棵开花的树,枝芽无限靠近你2 小时前
【PPTist】组件结构设计、主题切换
前端·笔记·学习·编辑器