手把手,仿element-plus实现message

1,150 阅读3分钟

这是手把手仿element-plus组件的第三篇,今天来封装message组件。

还是分三部分,先讲解思路,然后看element-plus实现,最后再自己实现一版。

思路讲解

message组件,大家肯定都用过。

1.基本功能就是在视图窗口,动态创建一个组件,组件可以接收内容,图标等信息。

伪代码大概长这样。

<!-- 1.模板 -->
<div>
  <icon name="success">
  <slot>
    {{message}}
  </slot>
  <icon name="close">
</div>


<!-- 2.动态创建 -->
h(compoennt, props, {
  default: () => {}
})

2.考虑到可能有多个message,所以message的位置需要动态计算。

实现的思路就是我们把每个message组件存一下,根据个数计算一下展示的位置。如果有message组件销毁了,则需要通知其他message组件,重新计算位置。

伪代码大概长这样。

<!-- 1.存message组件 -->
let arr = []
arr.push(instance)

<!-- 2.计算位置 -->
computed(() => {
    // 根据arr计算
})

3.销毁组件

既然有创建,那肯定就有销毁,这个肯定是成对出现的。

动态创建的组件如何销毁呢?

大概率还是使用render函数。

// dom为容器
render(null, dom)

2.elemment-plus源码阅读

先clone代码。

git clone https://github.com/element-plus/element-plus
cd element-plus
pnpm i
pnpm run docs:dev

1.参数处理

从截图可以看出,代码的入口就是method下的message方法。

image.png

image.png

message方法支持传入字符串,也支持传入对象,所以normalizeOptions方法的作用就是处理参数的。(这个暂时不管)

核心代码就是下面三行,跟我们分析的差不多,先创建组件,然后添加到数组中。

 const instance = createMessage(normalized, context)
  instances.push(instance)
  return instance.handler

2.创建组件

createMessage函数。

image.png

在创建组件这,有一个自增的id,用来标记,跟查找组件。

container是容器,用于render函数挂载。

  const props = {
    ...options,
    // now the zIndex will be used inside the message.vue component instead of here.
    // zIndex: nextIndex() + options.zIndex
    id,
    onClose: () => {
      userOnClose?.()
      closeMessage(instance)
    },

    // clean message element preventing mem leak
    onDestroy: () => {
      // since the element is destroy, then the VNode should be collected by GC as well
      // we do not want cause any mem leak because we have returned vm as a reference to users
      // so that we manually set it to false.
      render(null, container)
    },
  }

props参数,加了id,onClose跟onDestroy方法。

这里的代码,跟分析的也差别不大。onDestroy里面的render是销毁组件,closeMessage是删除数组中对应的元素,重新计算位置。

然后就是渲染挂载组件的逻辑了。(如果message的值是函数,或者虚拟节点,则会当成默认插槽传入)

image.png

3.组件模板

看一下MessageConstructor这个组件。

先看一下template结构。

image.png

vue内置组件,transition添加动画,根据leave事件销毁组件。

有插槽则展示插槽,没有的话,则显示p标签。

组件位置的计算,也跟预想差不多,根据组件列表进行计算。

image.png

image.png

3.手写组件

手写逻辑跟源码几乎一样,去掉了一些非核心的判断。

代码也比较多,文章中就不贴了,可自行下载查看。(已实现核心功能,后续会完善功能以及样式)

git clone https://github.com/bubuui/bubu-ui
cd bubu-ui
npm i 
npm run docs:dev

说几个知识点。

在message函数中,是先创建了组件,然后再push到全局instances中。

组件第一次计算值,是获取不到的,所以要给instances设置响应式-shallowReactive。

我们只关心组件个数,所以使用shallowReactive而不是reactive。

组件中的变量可以通过defineExpose暴露出来,以便在动态创建时修改。

  const vm = vnode.component!;

  const handler = {
    close: () => {
      vm.exposed!.visible.value = false;
    },
  };

3.

组件中用到了很多hook,均来自于vueuse三方库。

感兴趣的可自行查阅,后续也会出相关hook源码阅读文章。

3.总结一下

message组件看起来简单,但是想要实现一个完善的组件,还是有很多细节的。

包含ts内容,组件功能实现,代码组织,样式架构...。

如果看完有收获,欢迎点赞、评论、分享支持。你的支持和肯定,是我写作的动力