# my-message **Repository Path**: pingjiajia/my-message ## Basic Information - **Project Name**: my-message - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-12-14 - **Last Updated**: 2025-12-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于Vue3 + Typescript 封装 Element-Plus 组件 ## 1. 课程简介 **背景**: 该课程是基于`Vue3 + Typescript + Vite`构建, 教会大家封装`Element-Plus`组件 **具备能力**: - 最新的 Vue3 及相关技术 - 组件的设计思想 - 大厂的开发模式/代码规范 **技术**: - `Vue3 ` - 首次渲染 / diff 算法 更快 - 内存占用 / 打包体积 更少 - `Composition API` 组合 API ![](image-20220713152104493.fa32fb2e.png) - Options API:基于对象的方式,将组件的各种选项,如data、methods、computed等,组织在一个对象中 - Composition API:允许我们将组件的逻辑拆分成更小的、可复用的部分,从而实现更高度的组件复用。 - `Typescript` - 介绍: 是一种带有 `类型语法` 的 JavaScript 语言,在任何使用 JavaScript 的开发场景中都可以使用。 - JavaScript 代码 ```JavaScript // 没有明确的类型 const age = 18 ``` - TypeScript 代码 ```Typescript // 有明确的类型,可以指定age是number类型(数字类型) const age: number = 18 ``` - 作用: 编译时进行类型检查提示错误 ```JavaScript const num = 18; num.toLowerCase() // Uncaught TypeError: num.toLowerCase is not a function ``` 这些错误导致在开发项目的时候,需要花挺多的时间去定位和处理 BUG 原因: - JS 是动态类型的编程语言,动态类型最大的特点就是它只能在 `代码执行` 期间做类型的相关检查,所以往往你发现问题的时候,已经晚了。 解决方案 - TS 是静态类型的编程语言,代码会先进行编译然后去执行,在 `代码编译` 期间做类型的相关检查,如果有问题编译是不通过的,也就暴露出了问题。 TS 优势 - 更早发现错误,提高开发效率 - 随时随地提示,增强开发体验 - 强大类型系统,代码可维护性更好 - `Vite` - 一种新型前端构建工具,能够显著提升前端开发体验 - 对比` webpack` - `webpack`构建原理 - 需要查找依赖,打包所有的模块,然后才能提供服务,更新速度会随着代码体积增加越来越慢 ![](image-20220711150331172.50be2d0e.png) - `vite `的原理 - 使用原生 ESModule 通过 script 标签动态导入,访问页面的时候加载到对应模块编译并响应 ![](image-20220711151009063.43b3198f.png) - `Vue3 + TS + Vite` 最新的开发技术栈,你还在等什么..... ## 2. 项目创建 掌握:使用 create-vue 脚手架创建项目 create-vue参考地址: 1. 执行创建命令: ```bash # pnpm pnpm create vue # npm npm init vue@latest # yarn yarn create vue ``` 2. 选择项目依赖内容 ![](vite-comp.jpg) ## 3. 组件需求分析 ### 3.1 Message 消息提示 - 组件分析 功能分析 - 常用于主动操作后的反馈提示 - 提示在一定时间后可以消失 - 可以手动关闭 - 有多种类型 ( success warning error message) 难点 - 使用函数式的方式来创建组件 ```js createMessage('hello Vue', props) // 如何将一个组件函数式的渲染到一个节点上? // 可以采用createApp?..... ``` 类型属性分析 ```typescript interface MessageProps { message?: stirng; duration?: number; showClose?: boolean; type?: 'primary' : 'success' : .... } ``` 事件以及实例 ```js 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 ```js rules: { // 关闭组件命名规则 'vue/multi-word-component-names': 'off' } ``` 2. 编写组件 Message.vue ```css ``` types.ts ```typescript interface MessageProps { message?: stirng; duration?: number; showClose?: boolean; type?: 'primary' : 'success' : .... } ``` App.vue ```JavaScript import Message from './components/Message/Message.vue' ``` ### 3.4 将组件Render到DOM节点上 使用createApp 的弊端 - 该方法太重了, 它返回的是一个应用的实例, 而我们这里只需要轻量级的解决方法 - 隆重介绍render 函数 ```javascript // 它负责将一个vnode渲染到dom节点上 // 它是一个轻量级的解决方案 import { render } from 'vue' render(vNode, DOM节点) ``` method.ts ```JavaScript 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 ```JavaScript // import Message from './components/Message/Message.vue' createMessage({ message: 'hello vue', duration: 0 }) ``` ### 3.5 移除节点 method.ts ```diff // 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 ```diff export interface MessageProps { // .... ++ onDestroy: () => void } ++ export type CreateMessageProps = Omit ``` Message.vue ```diff ++ div class="message-box" :style="cssStyle" ref="messageRef" v-show="visible"> ``` ### 3.8 获取bottomOffset types.ts ```diff ++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 ``` method.ts ```diff 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 ```diff ++ 3333 const lastOffset = computed(() => getLastBottomOffset(props.id)) ``` ### 3.9 解决Message重叠问题 method.ts ```diff ++ 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 ```diff onMounted(() => { ++ const instance = createMessage({ message: 'hello vue1', duration: 0 }) ++ setTimeout(() => { ++ instance.destroy() ++ }, 2000); }) ``` meesage.vue ```diff defineExpose({ bottomOffset, ++ visible }) ``` method.ts ```diff 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 ```diff export interface MessageContext { // xxx ++ destroy: () => void } ``` ### 3.11 添加样式 Message.vue ```JavaScript
``` styles/vars.css ```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 ```css @import './vars.css'; @import '../components/Message/style.css'; ``` Message/style.css ```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 ```diff import { createApp } from 'vue' import App from './App.vue' ++ import './styles/index.css' createApp(App).mount('#app') ``` ### 3.12 总结 - 了解最新的 Vue3 及相关技术 & 优势 - 具备经典组件的设计与开发,提升架构思维和代码设计能力