写在前面#
对于项目内需要经常复用的组件,并基于实际项目环境,封装一些公共的Plugin,
本篇文章为previewImage.js
基于个人项目环境进行封装的Plugin,仅以本文介绍封装Plugin思想心得,故相关代码可能不适用他人
项目环境#
Vue3.x + Ant Design Vue3.x + Vite3.x
背景介绍#
研发的管理后台中,涉及到需要预览的图片,结合使用的UI框架AntDesignVue3.x
,一般情况下的使用方式是这样:
<template>
<div>
<a-button type="primary" @click="() => setVisible(true)">show image preview</a-button>
<a-image
:width="200"
:style="{ display: 'none' }"
:preview="{
visible,
onVisibleChange: setVisible,
}"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const setVisible = (value): void => {
visible.value = value;
};
return {
visible,
setVisible,
};
},
});
</script>
当然还有另外的情况:
1.后台中需要预览图片的地方多
2.需要动态去修改图片src
出现上述两种情况,为避免写重复的代码,减少写控制变量和事件代码,针对代码层面进行优化时,则可以考虑进行二次封装
封装分解:核心代码#
import { createVNode, render as vueRender } from 'vue';
import { Image } from 'ant-design-vue';
const container = document.createDocumentFragment();
function render(props) {
const vm = createVNode(Image, { ...props });
vueRender(vm, container);
return vm;
}
基于上述代码,实现创建一个虚拟节点,用来实现单图片预览的基础,下面则顺便对于使用到的内容进行讲解,权当是又一次加固相关技术知识吧~
封装分解:render#
export declare const render: RootRenderFunction<Element | ShadowRoot>;
export declare type RootRenderFunction<HostElement = RendererElement> = (vnode: VNode | null, container: HostElement, isSVG?: boolean) => void;
render
函数是 Vue2.x 中新增的一个函数
在 Vue 中我们使用模板 HTML 语法组建页面的,使用 render
函数我们可以用 Js 语言来构建 DOM. 因为 Vue 是虚拟 DOM ,所以在拿到 Template 模板时也要转译成 VNode 的函数,而用 render
函数构建 DOM ,Vue 就免去了转译的过程,因此相对而言,提升了性能
核心代码内,vueRender
函数接受两个参数,下面我们看一下第一参数vm
封装分解:createVNode#
export declare const createVNode: typeof _createVNode;
declare function _createVNode(type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props?: (Data & VNodeProps) | null, children?: unknown, patchFlag?: number, dynamicProps?: string[] | null, isBlockNode?: boolean): VNode;
export declare interface VNode<HostNode = RendererNode, HostElement = RendererElement, ExtraProps = {
[key: string]: any;
}> {
/* Excluded from this release type: __v_isVNode */
/* Excluded from this release type: __v_skip */
type: VNodeTypes;
props: (VNodeProps & ExtraProps) | null;
key: string | number | symbol | null;
ref: VNodeNormalizedRef | null;
/**
* SFC only. This is assigned on vnode creation using currentScopeId
* which is set alongside currentRenderingInstance.
*/
scopeId: string | null;
/* Excluded from this release type: slotScopeIds */
children: VNodeNormalizedChildren;
component: ComponentInternalInstance | null;
dirs: DirectiveBinding[] | null;
transition: TransitionHooks<HostElement> | null;
el: HostNode | null;
anchor: HostNode | null;
target: HostElement | null;
targetAnchor: HostNode | null;
/* Excluded from this release type: staticCount */
suspense: SuspenseBoundary | null;
/* Excluded from this release type: ssContent */
/* Excluded from this release type: ssFallback */
shapeFlag: number;
patchFlag: number;
/* Excluded from this release type: dynamicProps */
/* Excluded from this release type: dynamicChildren */
appContext: AppContext | null;
/* Excluded from this release type: ctx */
/* Excluded from this release type: memo */
/* Excluded from this release type: isCompatRoot */
/* Excluded from this release type: ce */
}
createVNode(Image, {...props})
,接受的参数:“定义的元素”、“元素的扩展属性”
结合实际业务需求,第一个参数(定义的元素)为Image
组件,为保证全局使用,则我们可以将一般情况下需要写的变量控制,如组件显示visible
,组件图片源地址src
,以及组件上的触发事件events
都可以使用props
传入。如此,便实现了虚拟节点的创建.
由于render: RootRenderFunction
中所接受的参数,container: HostElement
为必传,因此下面看一下核心代码中第二个参数container
封装分解:createDocumentFragment#
const container = document.createDocumentFragment()
创建一个新的空白的文档片段即虚拟DOM.
DocumentFragments (en-US) 是 DOM 节点。它们不是主 DOM 树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到 DOM 树。在 DOM 树中,文档片段被其所有的子元素所代替。
因为文档片段存在于内存中,并不在 DOM 树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能
完整代码:previewImage.js#
import { createVNode, render as vueRender } from 'vue';
import { Image } from 'ant-design-vue';
import 'ant-design-vue/es/image/style/index.less';
let previewImageInstance;
const container = document.createDocumentFragment();
function render(props) {
const vm = createVNode(Image, { ...props });
vueRender(vm, container);
return vm;
}
// 关闭
function close() {
update('', false);
}
// 销毁
function destroy() {
if (previewImageInstance) {
vueRender(null, container);
previewImageInstance.component.update();
previewImageInstance = null;
}
}
// 更新
function update(src, visible) {
previewImageInstance.component.props.preview.visible = visible;
previewImageInstance.component.props.src = src;
previewImageInstance.component.update();
}
// 显示
function show(src) {
if (previewImageInstance) {
update(src, true);
} else {
previewImageInstance = render({
preview: { visible: true, onVisibleChange: close },
src,
});
}
}
const previewImage = {
show,
destroy,
};
export default previewImage;
实际使用#
在需要使用预览图片的页面文件
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
/* 实际使用,同样的,可以在任何需要使用的地方调用事件 */
proxy.$previewImage.show(src);
发散思考:如何实现多张图预览呢?#
其实,封装的Plugin目前仅支持单张图片预览,也就是当页面内同时出现多张图片时,并没有AntDesignVue中预览显示视图层可以前后切换的功能,那如何实现呢?
可以这样做,也就是传入一个数组,而render
里,组件树的vnodes必须是唯一的,则可以使用循环,将多个Image循环出来,具体可以参考下官网的写法
function render() {
return h(
'div',
Array.from({ length: 20 }).map(() => {
return h('p', 'hi')
})
)
}