@wy
2019-07-15T09:58:04.000000Z
字数 2536
阅读 1321
vuejs
最近在做公司内部使用的建站工具,写完后又做了一版简化的,放在了github上,点击这里查看,欢迎star,目前搭建了个大框出来,后续会再进行完善,继续往里面添加组件和功能。
项目中使用的技术点:
- 使用vue-cli3搭建的项目
- 完全用 typescript 的语法来写
- vue-property-decorator 用装饰器来简化书写
- Vuex做状态管理,记录用户操作数据的过程
以下是成型的建站工具的样子,图片来自互联网。
[图片1]
简单的说一下使用方式,左侧聚集了各种各样的组件图标,拖拽到中间的操作区域,就是最终要呈现的样子,右侧是对选中操作区的某个组件进行设置,例如样式,数据、动画等,也有很多个组件。
在进行建站时,不会把左侧的所有组件都能用上,一个站点可能只会用3-4个组件,要进行设置的组件也只需要3-4个即可。也就是说,在初次加载的时候,不需要把所有的组件全部加载进来。
即便在创建站点后,返回来进行编辑,也只需要在初次加载已经用到的3-4个组件就好了。等到正式发布成为一个站点,只需要预览已经选中的组件,不用加载没用上的组件。
一开始写并不是太顺利,进行了各种尝试,才敲定了最终可行的方式。这里要分享的是如何使用 Vue 中 动态组件 以及 webpack代码分割 来完成功能的过程。
假如需要用到三个组件,分别是:图片、文本、搜索。
<template>
<div class="comment">
<div v-for="item in types">
// 显示图片组件
<com-img v-if="item.type === 'img'" :data="item.data />
// 显示文本组件
<com-text v-if="item.type === 'text'" :data="item.data />
// 显示搜索组件
<com-search v-if="item.type === 'search'" :data="item.data />
</div>
</div>
</template>
以上会根据拖拽不同的组件,区分 type 类型,来判断是否渲染此组件。但如果所支持的组件列表变得越来越长,这就会变得非常混乱和重复。要增加一个组件就要增加一个 v-if 进行类型判断,不够智能。
还会存在这样一个问题,建站工具中,理想状态下拖拽左侧同一个图标多次,会在操作区渲染同一个组件多次,而如果按照上面的代码进行判断,无论拖拽同一个图标多少次,操作区始终只会显示这个组件一次,而且只会按最后一次拖拽过来的数据为准。
在 Vue 中提供了动态组件来完成以上功能。
首先把需要的组件都放在一个集合中,根据约定好的类型,作为 key 值,方便查找。
import text from '@/text'
import search from '@/search'
import img from '@/img'
// 组件集合
let components = {
text,
search,
img
}
在模板中根据选中的数据,通过类型找到需要渲染的组件。
// 模板
<div class="item" v-for="item in types">
<component :is="addComponents(item)" :data="item.data"></component>
</div>
// 用到的方法
data(){
return {
types:[] // 存放所需要组件的数据
}
},
methods:{
addComponents(item){
return components[item.type]
}
}
这样就很好的解决了上面的问题,有多少条数据,就渲染几个组件,即便是同一个图标被拖拽了多次,也会在操作区把同一个组件渲染多次。
但这种方式存在两个问题:
第一个问题:会将所有的组件加载进来。不过使用异步组件可以解决这个问题,但不可避免的要手动加载,方式是:
const text = () => import('@/text');
const search = () => import('@/search');
const img = () => import('@/img');
但这会引发第二个问题。
第二个问题:每次添加或删除一个组件时,都会对 types 这个数据进行操作,从而重新会进行 v-for 循环,这导致动态加载的组会重新渲染。这会造成已经给组件设置的数据在重新渲染后会丢失。
我们可以定义一个包装组件,在包装组件中动态加载渲染所需要的组件。
<template>
<component :is="component" :data="data" v-if="component" />
</template>
<script>
export default {
name: 'dynamic-link',
props: ['data', 'type'],
data() {
return {
component: null,
}
},
computed: {
loader() {
if (!this.type) {
return null
}
return () => import(`templates/${this.type}`)
},
},
mounted() {
this.loader()
.then(() => {
this.component = () => this.loader()
})
.catch(() => {
this.component = () => import('templates/default')
})
},
}
</script>
使用方式和之前一模一样:
<template>
<div class="item" v-for="item in types">
<dynamic-link :type="item.type" :data="item.data"></dynamic-link>
</div>
</template>
这样就很好的解决了上述问题:
1. 不需要把每个组件都引入进来,但需要按照约定设置好目录结构。(当然,你也可以做一个组件集合,根据类型去取对应组件)。
2. 每次渲染不会重新渲染组件,只会渲染给 types 新增类型对应的组件。这是因为其他的数据并没有发生变化,dynamic-link 组件当然不需要重新渲染。
以上按照约定设置好目录结构,目录结构是这样的:
[图]