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

相关推荐
秋月华星1 小时前
【flutter】TextField输入框工具栏文本为英文解决(不用安装插件版本
前端·javascript·flutter
千里码aicood2 小时前
[含文档+PPT+源码等]精品基于Python实现的校园小助手小程序的设计与实现
开发语言·前端·python
青红光硫化黑2 小时前
React基础之React.memo
前端·javascript·react.js
大麦大麦2 小时前
深入剖析 Sass:从基础到进阶的 CSS 预处理器应用指南
开发语言·前端·css·面试·rust·uni-app·sass
m0_616188494 小时前
Vue3 中 Computed 用法
前端·javascript·vue.js
六个点4 小时前
图片懒加载与预加载的实现
前端·javascript·面试
Patrick_Wilson4 小时前
🔥【全网首篇】30分钟带你从0到1搭建基于Lynx的跨端开发环境
前端·react.js·前端框架
逍遥客.4 小时前
uniapp对接打印机和电子秤
javascript·vue.js·uni-app
Moment4 小时前
前端 社招 面筋分享:前端两年都问些啥 ❓️❓️❓️
前端·javascript·面试
Moment4 小时前
一坤时学习 TS 中的装饰器,让你写 NestJS 不再手软 😏😏😏
前端·javascript·面试