基于Vue3 + Typescript 封装 Element-Plus 组件

news2024/12/24 13:19:57

1. 课程简介

项目地址 git clone https://gitee.com/childe-jia/my-message.git

背景: 该课程是基于Vue3 + Typescript + Vite构建, 教会大家封装Element-Plus组件

具备能力:

  • 最新的 Vue3 及相关技术
  • 组件的设计思想
  • 大厂的开发模式/代码规范

技术:

  • Vue3

    • 首次渲染 / diff 算法 更快

    • 内存占用 / 打包体积 更少

    • Composition API 组合 API
      在这里插入图片描述

      • Options API:基于对象的方式,将组件的各种选项,如data、methods、computed等,组织在一个对象中
      • Composition API:允许我们将组件的逻辑拆分成更小的、可复用的部分,从而实现更高度的组件复用。
  • Typescript

    • 介绍: 是一种带有 类型语法 的 JavaScript 语言,在任何使用 JavaScript 的开发场景中都可以使用。

      • JavaScript 代码
      // 没有明确的类型
      const age = 18
      
      • TypeScript 代码
      // 有明确的类型,可以指定age是number类型(数字类型)
      const age: number = 18
      
    • 作用: 编译时进行类型检查提示错误

      const num = 18;
      num.toLowerCase() 
      // Uncaught TypeError: num.toLowerCase is not a function
      

      这些错误导致在开发项目的时候,需要花挺多的时间去定位和处理 BUG

      原因:

      • JS 是动态类型的编程语言,动态类型最大的特点就是它只能在 代码执行 期间做类型的相关检查,所以往往你发现问题的时候,已经晚了。

      解决方案

      • TS 是静态类型的编程语言,代码会先进行编译然后去执行,在 代码编译 期间做类型的相关检查,如果有问题编译是不通过的,也就暴露出了问题。

      TS 优势

      • 更早发现错误,提高开发效率

      • 随时随地提示,增强开发体验

      • 强大类型系统,代码可维护性更好

  • Vite

    • 一种新型前端构建工具,能够显著提升前端开发体验

    • 对比 webpack

      • webpack构建原理

        • 需要查找依赖,打包所有的模块,然后才能提供服务,更新速度会随着代码体积增加越来越慢
          在这里插入图片描述
    • vite 的原理

      • 使用原生 ESModule 通过 script 标签动态导入,访问页面的时候加载到对应模块编译并响应

在这里插入图片描述

  • Vue3 + TS + Vite 最新的开发技术栈,你还在等什么…

2. 项目创建

掌握:使用 create-vue 脚手架创建项目

create-vue参考地址:https://github.com/vuejs/create-vue

  1. 执行创建命令:

    # pnpm
    pnpm create vue
    # npm
    npm init vue@latest
    # yarn
    yarn create vue
    
  2. 选择项目依赖内容

在这里插入图片描述

3. 组件需求分析

3.1 Message 消息提示 - 组件分析

功能分析

  • 常用于主动操作后的反馈提示
  • 提示在一定时间后可以消失
  • 可以手动关闭
  • 有多种类型 ( success warning error message)

难点

  • 使用函数式的方式来创建组件

    createMessage('hello Vue', props)
    // 如何将一个组件函数式的渲染到一个节点上?
    // 可以采用createApp?.....
    
    
    

类型属性分析

```typescript 
interface MessageProps {
    message?: stirng;
    duration?: number;
    showClose?: boolean;
    type?: 'primary' : 'success' : .... 
}

事件以及实例

const instance = createMessage('hello world', props)
instance.close()

事件以及实例

const instance = createMessage('hello world', props)
instance.close()

3.2 Message组件创建

  1. 创建目录

    • Message
      • style.css - 样式
      • Message.vue - 组件
      • method.ts - api方法
      • types.ts - 辅助的 typescript 类型

    报错:

    Component name “Message” should always be multi-word

    原因:

    要求组件名称以驼峰格式命名, 自定义组件名称应该由多个单词组成, 防止和html标签冲突, 所以会报错

    解决:

    .eslintrc.js

      rules: {
        // 关闭组件命名规则
        'vue/multi-word-component-names': 'off'
      }
    
  2. 编写组件

Message.vue

<script setup lang="ts">
import { onMounted, ref } from 'vue';
import type { MessageProps } from './types'
const visible = ref(false)

const props = withDefaults(defineProps<MessageProps>(), {
  duration: 3000,
  type: 'message'
})
function startTimer() {
  if (props.duration === 0) return
  setTimeout(() => {
    visible.value = false
  }, props.duration);
}

onMounted(() => {
  visible.value = true
  startTimer()
})
</script>

<template>
  <div class="message-box" v-show="visible">
    <div class="message_content">
      <span>{{ message }}</span>
    </div>
      <img v-if="showClose" @click="visible = false" src="./close1.png" alt="">
  </div>
</template>

<style scoped>
.message-box {
  width: max-content;
  position: fixed;
  left: 50%;
  top: 20px;
  transform: translateX(-50%);
  border: 1px solid skyblue;
}
</style>

types.ts

interface MessageProps {
    message?: stirng;
    duration?: number;
    showClose?: boolean;
    type?: 'primary' : 'success' : .... 
}

App.vue

import Message from './components/Message/Message.vue'

<Message message="hello vue" :duration="0"></Message>

3.4 将组件Render到DOM节点上

使用createApp 的弊端

  • 该方法太重了, 它返回的是一个应用的实例, 而我们这里只需要轻量级的解决方法

  • 隆重介绍render 函数

    // 它负责将一个vnode渲染到dom节点上
    // 它是一个轻量级的解决方案
    import { render } from 'vue'
    render(vNode, DOM节点)
    

method.ts

import MessageConstructor from "./Message.vue";
import type { MessageProps } from "./types";
import { render, h } from 'vue'
export  const createMessage = (props: MessageProps) => {
  const container = document.createElement('div')
  const vnode = h(MessageConstructor, props)
  render(vnode, container)
  document.body.appendChild(container.firstElementChild!)
}

App.vue

// import Message from './components/Message/Message.vue'

createMessage({ message: 'hello vue', duration: 0 })

3.5 移除节点

method.ts

// import MessageConstructor from "./Message.vue";
-- import type { MessageProps } from "./types";
++ import type { CreateMessageProps } from "./types";
// import { render, h } from 'vue'
++ export const createMessage = (props: CreateMessageProps) => {
//   const container = document.createElement('div')
++ const destroy = () => {
++    render(null, container)
++  }
++  const newProps = {
++    ...props,
++    onDestroy: destroy
++  }
++  const vnode = h(MessageConstructor, newProps)
  render(vnode, container)
  document.body.appendChild(container.firstElementChild!)
}

types.ts

export interface MessageProps {
 // ....
++ onDestroy: () => void
}

++ export type CreateMessageProps = Omit<MessageProps, 'onDestroy'>

Message.vue

<script setup lang="ts">
// ....
++ watch(visible, (newValue) => {
++  if (!newValue) {
++    props.onDestroy()
++  }
++ })
<script/>

3.6 获取上一个组件实例

types.ts

import type { VNode } from "vue";
export interface MessageContext {
  id: string;
  vnode: VNode;
  props: MessageProps
}

method.ts

++ import type { CreateMessageProps, MessageContext } from "./types";
++ const instances: MessageContext[] = []
++ let seed = 1
export  const createMessage = (props: CreateMessageProps) => {
++  const id = `message_${seed++}`
  const destroy = () => {
++    const idx = instances.findIndex(instance => instance.id === id)
++    if (idx === -1) return 
++    instances.splice(idx, 1)
    render(null, container)
  }
// ....
  document.body.appendChild(container.firstElementChild!)
++  const instance = {
++    id,
++    vnode,
++    props: newProps
++  }
++  instances.push(instance)
++  return instance
}

++ export const getLastInstance = () => {
++  return instances[instances.length - 1]
++ }

Message.vue

import { getLastInstance } from './method'

const prevInstance = getLastInstance()
console.log('prevInstance', prevInstance);

3.7 动态计算组件位置

在这里插入图片描述
method.ts

// 伪方法: 获取上一个实例的最下面的坐标数字
export const getLastBottomOffset = () => {
  return 0
}

types.ts

export interface MessageProps {
  // ***	
++  offset?: number
}

Message.vue

<script setup lang="ts">
++ import {  nextTick } from 'vue';
++ import {  getLastBottomOffset } from './method'
const visible = ref(false)

const props = withDefaults(defineProps<MessageProps>(), {
  duration: 3000,
  type: 'message',
++  offset: 20
})
// const prevInstance = getLastInstance()
// console.log('prevInstance', prevInstance);
++ const messageRef = ref<HTMLDivElement>()
// 计算偏移高度
// div的高度
++ const height = ref(0)
// 上一个实例的最下面的坐标数字, 是0
++ const lastOffset = computed(() => getLastBottomOffset())
// 该元素的top计算值
++ const topOffset = computed(() => lastOffset.value + props.offset)
// 这个元素为下一个元素预留的offset, 也就是它最低端的bottom的值
++ const bottomOffset = computed(() => height.value + topOffset.value)
++ const cssStyle = computed(() => ({
++   top: topOffset.value + 'px'
++ }))
++  onMounted(async () => {
  visible.value = true
  startTimer()
++   await nextTick()
++   height.value = messageRef.value!.getBoundingClientRect().height
})
watch(visible, (newValue) => {
  if (!newValue) {
    props.onDestroy()
  }
})
++ 当然bottomOffset要给下一个组件使用, 所以说需要暴露出去
++ defineExpose({
++   bottomOffset
++ })
</script>
++ div class="message-box" :style="cssStyle" ref="messageRef" v-show="visible">

3.8 获取bottomOffset

types.ts

++1111 import type { ComponentInternalInstance } from 'vue'
export interface MessageProps {
  // xxx
++33333  id: string
}
export interface MessageContext {
 // xxx
++11111  vm: ComponentInternalInstance
}
++ 3333 export type CreateMessageProps = Omit<MessageProps, 'onDestroy'| 'id'>

method.ts

export  const createMessage = (props: CreateMessageProps) => {
 //  ...
  const newProps = {
     // xx
++  id
  }
  const vnode = h(MessageConstructor, newProps)
+++1111 console.log('vnode', vnode);
  
  render(vnode, container)
  document.body.appendChild(container.firstElementChild!)
++ 2222  const vm = vnode.component!
  const instance = {
    // xxx
++ 2222   vm
  }
}
// 获取上一个实例的最下面的坐标数字
++ 33333 export const getLastBottomOffset = (id: string) => {
++      const idx = instances.findIndex(instance => instance.id === id)
++     if (idx <= 0) {
++      return 0
++     } else {
++      const prev = instances[idx -1]
++      return prev.vm.exposed!.bottomOffset.value
  }
}

Message.vue

++ 3333 const lastOffset = computed(() => getLastBottomOffset(props.id))

<template>
++ 3333 <div class="message_content">
++      {{ offset }} {{ topOffset }} {{ height }} {{ bottomOffset }} <br>
++      <span>{{ message }}</span>
++ </div>	
</template>

3.9 解决Message重叠问题

method.ts

++ const instances: MessageContext[] = reactive([])

export const getLastBottomOffset = (id: string) => {
  const idx = instances.findIndex(instance => instance.id === id)
++  console.log('idx', id, idx, instances.length);
}

3.10 给组件实例添加删除方法

App.vue

onMounted(() => {
++  const instance = createMessage({ message: 'hello vue1', duration: 0 })
++  setTimeout(() => {
++    instance.destroy()
++  }, 2000);
})

meesage.vue

defineExpose({
  bottomOffset,
++  visible
})

method.ts

export  const createMessage = (props: CreateMessageProps) => {
++  const manuallyDestroy = () => {
++    const instance = instances.find(instance => instance.id === id)
++    if (instance) {
++      instance.vm.exposed!.visible.value = false
++    }
++  }

  const instance = {
    vm,
++  destroy: manuallyDestroy
  }
  instances.push(instance)
  return instance
}

types.ts

export interface MessageContext {
 // xxx 
++  destroy: () => void
}

3.11 添加样式

Message.vue

<div class="message-box" :class="{ [`el-message--${type}`]: type }" :style="cssStyle">

styles/vars.css

:root {
  /* message */
  --el-color-info-light-8: #e9e9eb;
  --el-color-info-light-9: #f4f4f5;
  /* success */
  --el-color-success: #67c23a;
  --el-color-success-light-8: #e1f3d8;
  --el-color-success-light-9: #f0f9eb;
  /* warning */
  --el-color-warning: #e6a23c;
  --el-color-warning-light-8: #faecd8;
  --el-color-warning-light-9: #fdf6ec;
  /* error */
  --el-color-error: #f56c6c;
  --el-color-error-light-8: #fde2e2;
  --el-color-error-light-9: #fef0f0;
}

styles/index.css

@import './vars.css';
@import '../components/Message/style.css';

Message/style.css

.message-box {
  width: max-content;
  position: fixed;
  left: 50%;
  top: 20px;
  transform: translateX(-50%);
  box-sizing: border-box;
  padding: 10px;
  display: flex;
  align-items: center;
  border-radius: 3px;

  border-color: var(--el-message-border-color);
  background-color: var(--el-message-bg-color);
  color: var(--el-message-text-color);
}
/* success */
.el-message--success {
  --el-message-bg-color: var(--el-color-success-light-9);
  --el-message-border-color: var(--el-color-success-light-8);
  --el-message-text-color: var(--el-color-success);
}
/* error */
.el-message--error {
  --el-message-bg-color: var(--el-color-error-light-9);
  --el-message-border-color: var(--el-color-error-light-8);
  --el-message-text-color: var(--el-color-error);
}
/* warning */
.el-message--warning {
  --el-message-bg-color: var(--el-color-warning-light-9);
  --el-message-border-color: var(--el-color-warning-light-8);
  --el-message-text-color: var(--el-color-warning);
}
/* message */
.el-message--message {
  --el-message-bg-color: var(--el-color-info-light-9);
  --el-message-border-color: var(--el-color-info-light-8);
}

main.ts

import { createApp } from 'vue'
import App from './App.vue'
++ import './styles/index.css'
createApp(App).mount('#app')

3.12 总结

  • 了解最新的 Vue3 及相关技术 & 优势

  • 具备经典组件的设计与开发,提升架构思维和代码设计能力

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1872844.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Transformer基础及视觉应用

文章目录 Transformer基础及视觉应用注意力机制基础(主要介绍Transformer用到的类型)Transformer的编解码器结构(Encoder and Decoder)Non-local Neural NetworksTransformer与大规模图像识别(Image Recognition at Scale)DETR-2020分割应用 Transformer基础及视觉应用 注意力…

小区物业服务差,现在催缴物业费,暂时不想交如何应对?

面对催缴物业费的情况&#xff0c;采取合理、合法的方式进行沟通和处理是非常重要的。如果您认为物业服务存在不足或者问题&#xff0c;可以按照以下步骤尝试解决问题&#xff0c;而不是直接拒绝缴费&#xff0c;避免后续可能产生的法律纠纷&#xff1a; 收集证据&#xff1a;首…

电脑怎么去除视频水印?电脑视频水印怎么去掉?

电脑怎么去除视频水印&#xff1f;有是我们见到喜欢的视频会保存下来&#xff0c;但是有时候保存的视频上面会带有水印&#xff0c;那么视频水印该如何去除呢&#xff1f;今天小编给大家推荐一个好用的视频去水印软件&#xff0c;操作简单&#xff0c;去水印效果好。 使用&…

tauri使用github action实现跨平台编译并解决编译错误等问题

正常编译为跨平台结果就像上面的&#xff0c;有mac/windows/linux的安装程序&#xff0c;直接下载就可以安装使用&#xff0c;我的这个livebox桌面端仓库地址&#xff1a;GitHub - Sjj1024/LiveBox: livebox&#xff0c;里面有编译文件可以参考。今天主要讲一下遇到的问题。 官…

碳课堂|ISO 14064标准新版变化

ISO 14064标准是针对组织碳排放方面的管理标准&#xff0c;包括温室气体排放和清除的量化、报告与验证的国际标准。其最新版本于 2018年发布&#xff0c;标志着对温室气体管理的全球认知和实践的进一步演进。ISO 14064 作为 ISO 14060 标准系列的重要组成部分&#xff0c;将继续…

bodypaint如何恢复布局设置

1.老师我手贱&#xff0c;布局改了&#xff0c;怎么恢复 2.左边咋没有纹理这个窗口了用来放参考图的 窗口&#xff0c;新建纹理视图&#xff0c;点那九点&#xff0c;拖拽&#xff0c;改变悬浮窗的状态

CatBoost原理介绍

文章最前&#xff1a; 我是Octopus&#xff0c;这个名字来源于我的中文名–章鱼&#xff1b;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github &#xff1b;这博客是记录我学习的点点滴滴&#xff0c;如果您对 Python、Java、AI、算法有兴趣&#xff0c;可以关注我的…

专业好用的数据恢复软件(iTop Data Recovery Pro v4.4.0.687 激活版 )

前言 由于 iTop 独特的恢复算法&#xff0c;这个强大的数据恢复工具加快了扫描和数据恢复过程。您无需等待很长时间即可取回数据。只需几分钟&#xff0c;您就可以按预期完美找回已删除的文件。此外&#xff0c;扫描选项多种多样&#xff0c;可满足不同的需求。您可以通过位置…

《昇思25天学习打卡营第3天 | 昇思MindSpore数据集 Dataset》

第三天 今天学习了不同的数据集加载方式、数据集常见操作和自定义数据集方法。 1.数据集加载。 以Mnist数据集为例。mindspore.dataset提供的接口仅支持解压后的数据文件&#xff0c;因此我们使用download库下载数据集并解压。 2.数据集迭代。 用create_tuple_iterator或create…

Android (已解决)Gradle 编译失败 Unsupported class file major version 61

文章目录 一、报错原因二、解决方法 一、报错原因 新版本的 Android Studio 默认使用的是 Java 17 LTS&#xff0c;而这个历史项目的 Gradle 版本很低&#xff0c;不支持高版本的 Java。 具体原因&#xff1a;Java 17 (major version 61) 编译的 class 文件&#xff0c;如果在…

重磅更新-UniApp自定义字体可视化设计

重磅更新-UniApp自定义字体可视化设计。 DIY可视化为了适配不同APP需要&#xff0c;支持用户自定义字体&#xff0c;自定义字体后&#xff0c;设计出来的界面更多样化&#xff0c;不再是单一字体效果。用户可以使用第三方字体加入设计&#xff0c;在设计的时候选择上自己的字体…

[数据集][目标检测]水面垃圾水面漂浮物检测数据集VOC+YOLO格式3749张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3749 标注数量(xml文件个数)&#xff1a;3749 标注数量(txt文件个数)&#xff1a;3749 标注…

开启网络监控新纪元:免费可视化工具助力网络信息链路拓扑监控大屏

在数字化浪潮汹涌的今天&#xff0c;网络已成为我们生活、工作的不可或缺的一部分。然而&#xff0c;你是否曾经想过&#xff0c;在这个庞大的网络世界中&#xff0c;是谁在默默守护着每一条信息的传输&#xff0c;确保我们的数据安全、稳定地抵达目的地&#xff1f; 网络信息链…

C# 在WPF .net8.0框架中使用FontAwesome 6和IconFont图标字体

文章目录 一、在WPF中使用FontAwesome 6图标字体1.1 下载FontAwesome1.2 在WPF中配置引用1.2.1 引用FontAwesome字体文件1.2.2 将字体文件已资源的形式生成 1.3 在项目中应用1.3.1 使用方式一&#xff1a;局部引用1.3.2 使用方式二&#xff1a;单个文件中全局引用1.3.3 使用方式…

Transformer教程之Encoder-Decoder架构

在当今的自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;Transformer已经成为不可或缺的模型。它以其高效的并行计算和卓越的性能在多个任务中占据了主导地位。在这篇文章中&#xff0c;我们将深入探讨Transformer的核心——Encoder-Decoder架构&#xff0c;帮助大家…

可燃气体报警器:广东深圳五金行业的安全守护者

在广东深圳这一五金制造与集散的重地&#xff0c;安全问题一直受到业界和社会的高度重视。在五金行业的日常运营中&#xff0c;可燃气体作为能源的重要来源&#xff0c;其安全使用与监控显得尤为重要。 可燃气体报警器作为保障生产安全的重要设备&#xff0c;在五金行业中扮演…

入门机器视觉的正确打开方式——徒手撸一个python+opencv实现的机器视觉简易调试工具(下)

目录 1.引言2.框架思路3.图像处理流程化的实现3.1如何解析图像流程数据结构3.2 使用networkx网络图库3.3 python实现 4.结论5.python源码PS.扩展阅读ps1.六自由度机器人相关文章资源ps2.四轴机器相关文章资源ps3.移动小车相关文章资源 1.引言 在当今AI时代&#xff0c;关于视觉…

【JPCS独立出版】第四届机电一体化技术与航空航天工程国际学术会议(ICMTAE 2024,8月2-4)

第四届机电一体化技术与航空航天工程国际学术会议&#xff08;ICMTAE 2024&#xff09;将围绕“机电一体化”、“电工与电子技术”、“航天工程”与“航空工程”等相关最新研究领域&#xff0c; 为来自国内外高等院校、科学研究所、企事业单位的专家、教授、学者、工程师等提供…

【语言模型】Xinference的部署过程

一、引言 Xinference&#xff0c;也称为Xorbits Inference&#xff0c;是一个性能强大且功能全面的分布式推理框架&#xff0c;专为各种模型的推理而设计。无论是研究者、开发者还是数据科学家&#xff0c;都可以通过Xinference轻松部署自己的模型或内置的前沿开源模型。Xinfe…

【MTK平台】如何学习Bluedroid A2DP Code

一 Bluedroid A2DP架构图 备注: vendor/mediatek/proprietary/packages/modules/Bluetooth/system/audio_a2dp_hw/src 目录下编译生成audio.a2dp.default.so,主要实现a2dp做为设备的功能 二 A2DP File Hierarchy ModuleFileDescriptionAudio HAL (hardware/libhardware/…