问题:我用vue在写一个地图类的页面时,小组件比较多,包括,书签,图层列表,资源目录...,刚开始我就是用父子组件嵌套,但随着小组件越来越多,页面越来越长,很多子组件的方法,也在父组件中调用,虽然也都是组件化,但是有1200多行,看着有些乱,各种方法调用,其他人看越来越费劲,
想法:我发现有一些重复的部分,重复的部分包括,组件引入,组件的注册,组件的使用,属性的传递。。。想着看看重复的部分能不能优化下,引入vuex等等,让代码看着不那么乱。
解决方法:于是想着能不能用参数配置,动态引入组件,动态注册组件,动态使用组件。
先看下效果图,工具箱参数配置(一些对象配置,包括工具的位置,大小等等)
1.原先引入组件
import BookMarks from "./widgets/BookMarks";
....
优化后,动态引入组件:
//正则匹配查找widgets文件夹下,以.vue结尾的文件
const files = require.context("../widgets", true, /\.vue$/);
let widgets = files.keys().map((file) => {
return files(file).default;
});
2.原先注册组件
components: {
BookMarks,
//其他的组件...
},
优化后,动态注册组件:
widgets.forEach((widget) => {
// 动态注册子组件
this.$options.components[widget.name] = widget;
});
3.原先使用组件
<template>
<div style="width: 100%; height: 100%">
//其他省略的代码
<BookMarks></BookMarks>
</div>
</template>
优化后动态使用组件,使用h(),render优化后
render的官网中使用方法:渲染函数 & JSX | Vue.js
//widget为第二部widgets中,符合条件的widget
let vnode = h(widget, { 'id': 'id'}, )
let newElement = document.createElement('div');
document.body.appendChild(newElement)
newElement.id = 'newDiv';
//render渲染子组件
render(vnode ,document.getElementById("newDiv"))
我最先开始也是用的render,但是在使用中,需要父子组件传值,试了prop,vuex都没能实现传值,antdesign 组件的css样式也丢失了,所以我又用了内置组件component,使用component也有些其他的问题,如何渲染多个组件等等,如果v-for循环component,那component绑定不同is属性感觉也不太好,希望评论区踊跃探讨。
4.接下来,看看源代码吧。
我用的是arcgis js api,本来是想将mapview属性用vuex管理,但是报错,所以才用事件传值的方式
<template>
<div style="width: 100%; height: 100%">
<component :is="selectLayout.componentName">
<template #header>
<map-app-header />
</template>
<template #content>
<BaseMap @getMapview="getMapview" />
</template>
<slot>
<div :style="toolBox.position">
<MapAppTool @clickMapTool="clickTool" />
</div>
<div id="mapAppContainer" ref="mapAppContainer">
<KeepAlive :max="10">
<component :is="toolComponentName" :mapview="mapview" >
</component>
</KeepAlive>
</div>
</slot>
</component>
</div>
</template>
<script>
// import { h, render,reactive,ref } from "vue";
import BaseMap from "../map/BaseMap.vue";
import MapAppHeader from "../map/components/MapAppHeader.vue";
import MapAppTool from "../map/components/MapAppTool.vue";
import { mapGetters, mapMutations } from "vuex";
// 动态引入子组件
const files = require.context("../map/widgets", true, /\.vue$/);
let widgets = files.keys().map((file) => {
return files(file).default;
});
export default {
components: {
BaseMap,//自定义的地图组件
MapAppHeader,//自定义的头部组件
MapAppTool,//自定义的工具箱组件
},
computed: {
...mapGetters("map", ["selectLayout"]),
toolBox: function () {
return this.selectLayout.layout.find((item) => item.name == "tool");
},
},
data() {
return {
toolComponentName: null,
};
},
created() {
widgets.forEach((widget) => {
// 动态注册子组件
this.$options.components[widget.name] = widget;
});
},
mounted() {},
methods: {
...mapMutations("map", ["updateWidgetInfo"]),
//获取地图
getMapview(mapView) {
this.mapview = mapView;
},
clickTool(tool) {
this.updateWidgetInfo({
name: tool.name,
field: "display",
val: !tool.display,
})
if(tool.componentName){
document.getElementById("mapAppContainer").style = tool.position;
this.toolComponentName = tool.componentName
}
if(tool.componentName && tool.type == "button"){
if(tool.display){
document.getElementById("mapAppContainer").style.display = 'none'
}else{
document.getElementById("mapAppContainer").style.display = 'block'
}
}
//通过render渲染,也能渲染出来,但是antdesign的css样式丢失了
// let widget = widgets.find(item=>item.name==tool.componentName)
// const div2 = h(widget);
// div2.type.props.config = reactive(tool)//没效果
// div2.type.data().mapview = ref('mapview') //没效果
// const asyncComponent = defineAsyncComponent(div);
// render(div2,document.getElementById("mapAppContainer"))
},
},
};
</script>
<style scoped>
.map_app {
width: 100%;
height: 100%;
position: relative;
}
</style>
//布局,也可以自定义多个想要的样式,我是全局引入的自定义布局样式,所以在上边调用的时候,就没有显示引入
<!--自定义布局样式1头部和内容-->
<template>
<a-layout>
<a-layout-header :style="header.style"><slot name="header"></slot></a-layout-header>
<a-layout-content>
<slot name="content"></slot>
</a-layout-content>
<slot></slot>
</a-layout>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters('map',[
"selectLayout"
]),
header: function () {
return this.selectLayout.layout.find(item=>item.name.toLowerCase() == 'header')
},
},
};
</script>
<style scoped>
.ant-layout-content {
width: 100%;
height: calc(100vh - 64px);
position: relative;
}
</style>
/*书签组件配置*/
export default {
id: 'map-tool-bookmarks',
name: "bookmarks",
componentName: "BookMarks",
visible: true,
display: true,
alt: "书签",
type: "button",
icon: "",
iconClass:"icon-shuqian",
event: "handleToggleVisibleClick",
params: "bookmarks",
clearEvent: "closeWidgetCard",
position: "position: absolute;bottom: 10px;right: 50px; border: '1px red solid'"
};