theme: vuepress
highlight: vs2015
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第28期 | vue react 小程序 message 组件 点击了解本期详情一起参与。
今天阅读的是:组件库的message组件
element
- https://github.com/ElemeFE/element/tree/dev/packages/message
element plus
- https://github.com/element-plus/element-plus/tree/dev/packages/components/message
antd
- https://github.com/ant-design/ant-design/tree/master/components/message
antd vue
- https://github.com/vueComponent/ant-design-vue/tree/main/components/message
相信使用组件库的对于element ui
和antd
这两个库都很熟悉,那么,他们的message
组件是怎么实现的呢?
实际用例
源码分析
-
查看测试用例
-
我们以
element plus
作为源码分析,跳转到对应的package
,看下目录结构
测试用例
跳转至测试用例,查看需要实现的功能点
// 渲染
describe('render', () => {
// 基本渲染
test('basic render test', () => {
})
// 支持vnode渲染
test('should be able to render VNode', () => {
})
// 当 dangerouslyUseHTMLString = true 渲染html元素
test('should be able to render raw HTML with dangerouslyUseHTMLString prop', () => {
})
// 当 dangerouslyUseHTMLString = false | undefined 时,不渲染html元素
test('should not be able to render raw HTML without dangerouslyUseHTMLString prop', () => {
})
})
// 消息类型
describe('Message.type', () => {
// 检查几种类型的message 是否正常渲染
test('should be able to render typed messages', () => {
})
// 当输入不合法的icon,不渲染
test('should not be able to render invalid type icon', () => {
})
})
describe('event handlers', () => {
// 点击关闭按钮正常关闭
test('it should be able to close the message by clicking close button', async () => {
})
// 延迟关闭
test('it should close after duration', async () => {
})
// 当悬停时阻止关闭
test('it should prevent close when hovered', async () => {
})
// 当duration设置为 0 时,不会自动关闭
test('it should not close when duration is set to 0', () => {
})
// 当点击 esc 键,消息弹窗关闭
test('it should close when esc is pressed', async () => {
})
// 触发close事件
test('it should call close after transition ends', async () => {
})
})
根据测试用例,我们可以得到几个功能点
-
渲染
- 基本渲染
- 支持
vnode
- 渲染
html
元素
-
消息类型
- ‘success’ | ‘warning’ | ‘info’ | ‘error’
-
事件
- 关闭
- 延迟关闭
- 悬停不消失
源码实现
// method.ts
// 出口
export default message as Message
所以,我们需要查看的是message
的构造方法
const message: MessageFn &
Partial<Message> & { _context: AppContext | null } = (
options = {},
context
) => {
// 检查window是否存在
if (!isClient) return { close: () => undefined }
// 检查max数量的类型和当前的实例数是否超出配置项最大数量
if (isNumber(messageConfig.max) && instances.length >= messageConfig.max) {
return { close: () => undefined }
}
// 规范化配置选项
const normalized = normalizeOptions(options)
// 单例模式
if (normalized.grouping && instances.length) {
const instance = instances.find(
({ vnode: vm }) => vm.props?.message === normalized.message
)
if (instance) {
instance.props.repeatNum += 1
instance.props.type = normalized.type
return instance.handler
}
}
// 如果不存在则创建实例,并将当前的Message加入到数组中
const instance = createMessage(normalized, context)
instances.push(instance)
return instance.handler
}
const createMessage = (
{ appendTo, ...options }: MessageParamsNormalized,
context?: AppContext | null
): MessageContext => {
// 计算zindex
const { nextZIndex } = useZIndex()
// 设置唯一id
const id = `message_${seed++}`
// 初始化onClose
const userOnClose = options.onClose
// 创建容器
const container = document.createElement('div')
// 参数定义
const props = {
...options,
// 设置每个消息框的偏移量,保证不重叠
zIndex: nextZIndex() + options.zIndex,
id,
onClose: () => {
userOnClose?.()
closeMessage(instance)
},
// clean message element preventing mem leak
onDestroy: () => {
// 当元素销毁时,VNode应该会被收集释放内存,但是为了避免内存泄露,手动将当前的vm设置为null
render(null, container)
}
}
// 创建vnode节点
const vnode = createVNode(
MessageConstructor,
props,
isFunction(props.message) || isVNode(props.message)
? {
default: isFunction(props.message)
? props.message
: () => props.message
}
: null
)
// 绑定当前context
vnode.appContext = context || message._context
render(vnode, container)
// instances will remove this item when close function gets called. So we do not need to worry about it.
appendTo.appendChild(container.firstElementChild!)
const vm = vnode.component!
const handler: MessageHandler = {
// instead of calling the onClose function directly, setting this value so that we can have the full lifecycle
// for out component, so that all closing steps will not be skipped.
close: () => {
vm.exposed!.visible.value = false
}
}
const instance: MessageContext = {
id,
vnode,
vm,
handler,
props: (vnode.component as any).props
}
// 返回当前实例
return instance
}
那么,message中不同类型的消息框是怎么实现的呢
// message.ts
export const messageTypes = ['success', 'info', 'warning', 'error'] as const
export type messageType = typeof messageTypes[number]
// method.ts
// 创建不同类型的message
messageTypes.forEach((type) => {
message[type] = (options = {}, appContext) => {
const normalized = normalizeOptions(options)
return message({ ...normalized, type }, appContext)
}
})
总结
太久没更新文章了,赶紧练练手
通过上述分析,我们可以知道message是怎么创建出来的,并且对应测试用例的功能点是如何实现的。
- 同一时间应该只存在一个message,使用单例模式
vnode
本身支持html
元素和text
,所以可以实现html
元素渲染- 巧妙的是,使用了
forEach
来创建不同类型的message - 对元素销毁时,可能发生内存泄露的边界条件的考虑
- 测试用例的编写
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容