vue3实战-业务组件开发总结

概述

vue3提供了compostion api,一方面给JS开发提供了更多的灵活性,另一方面对开发出稳定性高,扩展性好,易阅读维护成本低的代码提出了新的挑战,在使用vue3的过程中,我们应该如何开发自己的业务组件,读完本篇文章你将会获得答案。

分析过程

如果把逻辑都放到vue模版中,setup函数将会非常臃肿,因此,首先要对自己的业务组件逻辑部分进行拆分(模板部分拆分不在本次讨论范围),以音频文件上传功能为例。
vue3实战-业务组件开发总结_第1张图片
文件上传分为:

1、选择文件

2、对文件进行试听、增加、删除

3、上传文件、上传进度、上传状态

4、上传成功、上传失败的处理

我们把1、2分为一类,挑选文件的处理;把3、4分为一类,文件上传的处理;

实战过程

一、首先编写两个逻辑文件

1、choise-audio.ts:挑选文件的处理

2、upload-audio.ts:文件上传的处理

二、编写vue文件中逻辑部分

实现文件上传的核心功能点,但具体功能细节不在这里

import uploadAudio from './upload-audio';
import choiseAudio from './choise-audio';
 setup() {
    const route = useRoute();
    // TODO:
    const fileListInit: FileListType[] = [];
    const fileList = ref(fileListInit);
    const teacherId = route.query.id;
    const uploadData = {
      teacherId
    };

 const {
      handleUnit,
      handleChange,
      handleAudioPlay,
      handleAudioPause,
      handleRemove
    } = choiseAudio(fileList);

    const {
      // handleTips,
      beforeUpload,
      handleUploadAgain,
      handleSuccess,
      handleProgress,
      handleError,
      handleSubmit
    } = uploadAudio(fileList);

    return {
      handleUnit,
      fileList,
      uploadData,
      handleError,
      handleRemove,
      handleAudioPlay,
      handleAudioPause,
      handleChange,
      handleProgress,
      handleSuccess,
      handleUploadAgain,
      beforeUpload,
      handleSubmit
    };
}

三、编写逻辑文件

1、编写choise-audio.ts
编写选择文件的主要逻辑:试听、删除、选择、文件类型判断等:

export default function choiseAudio(fileList: any): ChoiseType {
  const ctx = getCurrentInstance();
  const {
    proxy: { $confirm, $message }
  } = ctx;

  // 处理文件大小
  const handleUnit = (val: number) => {
    return caculateFileSize(val);
  };

  // 从上传队列中移除
  const handleDelItem = (uid: number) => {
    fileList.value = ChoiseAudio.deleteFile(fileList.value, uid);
  };

  // 文件类型判断
  const handleGetFileType = (file: FileListType) => {
    const type = ChoiseAudio.getFileType(file);
    if (type !== 'wav' && type !== 'aac') {
      handleDelItem(file.uid);
      // handleTips(file.uid, '不支持文件类型,请重新上传', false);
    }
  };

  // 文件过大的提示
  const handleOversize = (file: any) => {
    // 文件不得大于 100M
    if (file.size > 100 * 1024 * 1024) {
      handleDelItem(file.uid);
      // handleTips(file.uid, '文件大于100M,请重新选择', false);
    }
  };

  // 播放音频
  const handleAudioPlay = (file: any, index: number) => {
    if (!el) {
      el = document.createElement('audio');
    }
    ChoiseAudio.handleAudioPlay(currentIndex, fileList, el, file, index);
    currentIndex = index;
  };

  // 暂停音频
  const handleAudioPause = (file: FileListType, index: number) => {
    if (!el) return;
    ChoiseAudio.handleAudioPause(el, fileList, index);
  };
  // 处理变化
  const handleChange = (file: FileListType) => {
    if (file.status === 'ready') {
      ChoiseAudio.fileListDataInit(file, fileList);
      handleGetFileType(file); // 校验文件类型
      // handleAudioUrl(file); // 获取音频 url 和时长
      handleOversize(file); // 文件大小校验
    }
  };

  // 删除语音
  const handleRemove = (file: FileListType, index: number) => {
   
  };

  return {
    handleUnit,
    handleRemove,
    // handleOversize,
    // handleDelItem,
    // handleGetFileType,
    handleAudioPlay,
    handleChange,
    handleAudioPause
  };
}

2、编写upload-audio.ts
编写上传的逻辑部分:上传成功、上传失败、上传进度、上传状态等逻辑部分

export default function uploadAudio(fileList: any): UploadAudioType {
  const ctx = getCurrentInstance();

  const {
    proxy: { $message }
  } = ctx;
  // 设置提示
  const handleTips = (uid: any, tip: string, flag: boolean) => {
    fileList.value = UploadAudio.handleFileUploadTips(
      uid,
      tip,
      flag,
      fileList.value
    );
  };
  // 上传失败
  const handleError = (err: any, file: FileListType) => {
    handleTips(file.uid, '上传失败', false);
  };
  // 展示上传进度
  const handleProgress = (event: any, file: FileListType) => {
    fileList.value = UploadAudio.handleProgress(file, fileList.value, false);
  };
  // 处理上传成功
  const handleSuccess = (res: any, file: FileListType) => {
    
  };

  // 处理提交
  const handleSubmit = () => {
    (ctx as any).refs.upload.submit();
  };

  // 上传之前
  const beforeUpload = (file: FileListType) => {
   
  };
  // 重新上传
  const handleUploadAgain = (file: FileListType, index: number) => {
    
  };

  return {
    handleTips,
    beforeUpload,
    handleUploadAgain,
    handleSuccess,
    handleProgress,
    handleError,
    handleSubmit
  };
}

四、业务逻辑代码拆分

在复杂业务场景中,我们可以把业务组件逻辑代码进行进一步拆分:视图逻辑与非视图业务逻辑

1、我们把文件选择的非视图逻辑抽象为类ChoiseAudio
在选择文件过程中对数据的计算处理、文件的处理等

class ChoiseAudio {
  /**
   *
   * @param file
   * @param fileList
   * @description 初始化音视频数据
   */
  static fileListDataInit(file: FileListType, fileList: any): void {
    file.type = '1';
    file.playStat = 'pause';
    fileList.value.push(file);
  }

  /**
   *
   * @param file
   * @returns
   * @description 创建audioElement
   */
  static createAudio(file: FileListType): HTMLMediaElement {
    const url = URL.createObjectURL(file.raw);
    const audioElement = new Audio(url);
    audioElement.setAttribute('src', url);
    return audioElement;
  }

  /**
   *
   * @param fileList
   * @description 删除
   */
  static deleteFile(fileList: any, uid: number): FileListType[] {
    fileList.forEach((item: FileListType, index: number) => {
      if (item.uid === uid) {
        fileList.splice(index, 1);
      }
    });
    return fileList;
  }

  /**
   *
   * @param audioElement
   * @param fileList
   * @param file
   * @description audio 标签加载音频文件并获取音频文件时长
   *
   */
  static handleAudioUrl(
    audioElement: HTMLMediaElement,
    fileList: any,
    file: FileListType
  ): any {
    
  }

  /**
   *
   * @param file
   * @returns
   * @description 获取文件类型
   */
  static getFileType(file: FileListType): string {
    const startIndex = file.name.lastIndexOf('.');
    const type = file.name
      .substring(startIndex + 1, file.name.length)
      .toLowerCase();

    return type;
  }

  /**
   *
   * @param currentIndex
   * @param fileList
   * @param el
   * @param file
   * @param index
   * @description 播放
   */
  static handleAudioPlay(
    currentIndex: number,
    fileList: any,
    el: HTMLMediaElement,
    file: FileListType,
    index: number
  ): void {
    
  }

  /**
   *
   * @param el
   * @param fileList
   * @param index
   * @description 暂停
   */
  static handleAudioPause(
    el: HTMLMediaElement,
    fileList: any,
    index: number
  ): void {
    el.pause();
    (fileList.value as FileListType[])[index].playStat = 'pause';
  }
}

2、我们把文件上传的非视图逻辑抽象为类UploadAudio
在上传的过程中数据处理:


 class UploadAudio {
  /**
   *
   * @param uid
   * @param tip
   * @param flag
   * @param fileList
   * @returns
   * @description 处理fileList数据,进行提示操作
   */
  static handleFileUploadTips(
    uid: number,
    tip: string,
    flag: boolean,
    fileList: any
  ): FileListType[] {
    const newFileList = fileList.map((val: FileListType, index: number) => {
      if (val.uid === uid) {
        fileList[index].tip = tip;
        if (flag) {
          fileList[index].class = 'fail';
        } else {
          fileList[index].class = '';
        }
      }
      return val;
    });
    return newFileList;
  }

  /**
   *
   * @param file
   * @param fileList
   * @param flag
   * @returns
   * @description 处理进度条
   */
  static handleProgress(
    file: FileListType,
    fileList: any,
    flag: boolean
  ): FileListType[] {
    const fileItem = fileList.find(
      (item: FileListType) => item.uid === file.uid
    );
    // console.log(fileItem.percentage, file.percentage);

    if (fileItem) {
      fileItem.status = flag ? 'success' : 'uploading';
      fileItem.percentage = flag ? 100 : file.percentage;
    }

    return fileList;
  }
}

总结

业务开发中要遵循单一职责、逻辑关注点分离、逻辑视图分离原则,这样子开发出来的代码也不会那么乱,质量自然会高很多,而且如此设计对单元测试来说真的不能再友好了(测试视图复杂,测试逻辑还是简单多的),如果每一个函数都有返回值那就更好了。

以上是我的项目总结,如果你对vue3有更好的实践,欢迎留言。

参考

vue官网中关于逻辑关注点分离:https://vue3js.cn/docs/zh/guide/composition-api-introduction.html#%E4%BB%80%E4%B9%88%E6%98%AF%E7%BB%84%E5%90%88%E5%BC%8F-api
vue组件分离UI与业务逻辑:https://cloud.tencent.com/developer/article/1694043

你可能感兴趣的:(vue,vue)