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

相关推荐
爱米的前端小笔记32 分钟前
前端面试:项目细节重难点问题分享(18)
前端·经验分享·面试·职场和发展·求职招聘
GoppViper1 小时前
uniapp view怎么按长度排列一行最多四个元素,并且换行后,每一行之间都有间隔
前端·uni-app·uniapp·样式·样式控制
吴楷鹏2 小时前
高一全栈开发;国产 Arc 浏览器;Tauri 2.0 发布 | 生活周刊 #3
前端·后端·程序员
曹天骄2 小时前
React 组件命名规范
前端·javascript
二手的程序员2 小时前
网络抓包06 - Socket抓包
开发语言·前端·网络·安全·网络安全
aherhuo3 小时前
shell脚本宝藏仓库(基础命令、正则表达式、shell基础、变量、逻辑判断、函数、数组)
linux·运维·前端·正则表达式
小白学习日记3 小时前
html复习
前端·html
风清云淡_A3 小时前
vue3.x系列之v-model的使用技巧及面试高频问题
前端·vue.js
我不会画饼鸭3 小时前
VueRouter前端路由
前端
Jiaberrr3 小时前
微信小程序实战教程:如何使用map组件实现地图功能
前端·javascript·微信小程序·小程序·map