@wy
2019-07-15T01:58:04.000000Z
字数 2536
阅读 1445
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 组件当然不需要重新渲染。
以上按照约定设置好目录结构,目录结构是这样的:
[图]