这是我参与「第四届青训营 」笔记创作活动的第7天
前言
记录目前开发lowcode项目遇到的一些问题和解决方法,由于自身对vue基础不是很好,其中还记录一些vue知识点。
如何实现一个简易Lowcode项目框架?
好问题,那我们先来想想要做一个lowcode项目需要什么。根据官方的要求和网上项目的实际体验,lowcode项目和别的项目不同在与有一个拖拽组件操作,并且这个拖拽效果需要可以实时反馈的。
大致实现思路
首先我们需要把整个页面划分为两部分,一边作为存放组件列表,另一边是为一个canvas。
可能有小伙伴不了解canvas是什么,这里简单解释一下,canvas是html一个标签,作用是把一个区域变成一块画布。我们可以用代码在上面画出我们想要的东西。如果想了解更多可以看这里
介绍完这两个想必可能大伙也知道大概逻辑了,其实就是我们把一些组件预处理在左边的列表中,然后为每个组件设定一个编号,当我们点击对应的组件,就获取到这个组件的编号。拖动到中间的画布把对应内容给组件画出来。
拖拽效果如何实现?
在上面介绍想必已经了解的大概思路了,但是如何实现拖拽呢?
实际上拖拽效果很容易实现,在这里我们要先了解一个叫draggable的属性,这个属性可以让我们的页面上的标签比如div等内容实现拖拽效果。
draggable可以有以下值:
true: 可以拖动元素。false: 不能拖动元素。
但是这样我们只能拖动而已,并不能完成我们项目所理想的效果。我们可是要求拖动后还可以获取对应的组件信息的。
在这里我们需要了解两个事件dragstart 和 drop事件,这两个事件一个是拖动后开始开始执行,一个是拖动结束后执行。(参考下面这个例子)
我来解释一下如何实现这个代码的,这里不仅用到了上面所说的两个事件,还用到他里面自带的一个参数属性。dataTransfer这个属性中有个setDataapi,借助这个api可以让我们在拖动开始后把拖动元素所需要的属性(比如他的id),加到dataTransfer中,在拖完后,我们就可以通过drop事件获取到它我们所需要的数据了。
那想必说到这里大家应该知道拖拽的实现方法了吧。
将我们上面的例子的实现方式带入我们的项目中,我们就可以为每一个组件加入一个dragable属性,来实现拖动效果,并且为每个拖动完成后设置dragstart事件来获取到我们拖动的是哪个组件,把我们的组件id通过setData加入dataTransfer中,在拖动完毕后,如果拖动在合法区域(也就是我们的画布中),触发drop事件,用getData获取到dataTransfer中的组件id,并加入我们canvas中。
如何渲染?
那我们是如何让画布渲染的呢?
要知道我们拖动组件加入时很多时候不止一个,因此我们就需要把拖拽完的合法组件加入一个数组中,并且没吃更新都需要去除之前画的内容,重新出新的组件。
如何处理好组件列表?
组件列表就是我们左侧的那些组件。在这里可以先将所有实际的组件加入到全局组件中。
通过Vue.componentapi来实现这个效果,像这样就可以把全部的组件加入到全局组件中。
components.forEach(key => {
Vue.component(key, () => import(`@/custom-component/${key}/Component`))
})
那我们如何调用的呢?
这里我们需要用到动态组件的知识点。(相信这个知识点在单页面开发中是非常常用的知识点吧)
具体使用方法就是用一个component的标签,并且搭配:is来动态更新组件。如果不了解的话可以看官网
那这样就很容易了是吧,因为我们设置组件都是全局组件,这样我们只要为组件绑定好一个名字,通过is来获取对应的组件。比如像下面这样这直接component弄一个不同名字。到时候在渲染页面中,用<component :is="item.component" />即可完成渲染效果
{
component: 'Text',
label: '文字',
propValue: '双击编辑文字',
icon: 'wenben',
style: {
width: 200,
height: 28,
fontSize: 14,
fontWeight: 400,
lineHeight: '',
letterSpacing: 0,
textAlign: '',
color: '',
},
},
其它vue知识点
mapState
用处/意义:mapState是来解决vuex获取公共数据,当一个组件需要获取多个状态的时候,并且将这些状态都声明为计算属性会有些重复和冗余。可以用mapState来解决。
例子:
<template>
<div id="example">
{{count}}
{{name}}
{{nameAlias}}
</div>
</template>
<script>
import { mapState } from 'vuex' // 引入mapState
export default {
data () {
return {
temp: 'hello',
tempcount: 1,
tempcount2: 2
}
},
computed: {
// tempCountPlusTempCount2 这个计算属性并没有涉及到vuex管理的状态
tempCountPlusTempCount2() {
return this.tempcount+this.tempcount2
}
// 由于 Vuex 的状态存储是响应式的,所以可以使用计算属性来获得某个状态
// 当状态改变时,都会重新求取计算属性,并且触发更新相关联的 DOM
// 通过下面的计算属性,就可以在当前组件中访问到count,name,nameAlias等了 在模板中我们通过大括号符号打印出来
// 下面的计算属性涉及到了vuex管理的状态
count () { // 这实际上是ES6中对象的简化写法 完整写法是 count: function { return this.$store.state.count }
return this.$store.state.age
},
name () { // 这实际上是ES6中对象的简化写法 完整写法是 name: function { return this.$store.state.age }
return this.$store.state.age
},
nameAlias () {
return this.$store.state.name
}
countplustempcount: function (state) {
return this.tempcount + this.$store.state.count
},
countplustempcount2 (state) {
return this.tempcount2 + this.$store.state.count
}
// 但有一个问题
// 当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。比如上面的name(),count(),nameAlias(),显得重复,代码冗长
// 为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:
}
}
</script>
用mapState来解决代码冗余
<template>
<div id="example">
{{count}}
{{name}}
{{nameAlias}}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
temp: 'hello',
tempcount: 1,
tempcount2: 2
}
},
computed: mapState({
count: 'count', // string 映射 this.count 为 store.state.count的值
// 箭头函数可使代码更简练
name: (state) => state.name, // function 映射 this.name 为 store.state.name的值
nameAlias: 'name', // string 映射 this.nameAlias 为 store.state.name的值
countplustempcount: function (state) { // 用普通函数this指向vue实例,但是在箭头函数中this就不是指向vue实例了,所以这里必须用普通哈数
return this.tempcount + state.count
},
countplustempcount2 (state) {
return this.tempcount2 + state.count
}
})
}
</script>
-
对象的第一个属性是string类型的,count: 'count', 这条语句映射出了this.count, 值等于store.state.count的值。
-
对象的第二个属性是一个箭头函数,name: (state) => state.name,,映射 this.name 为 store.state.name的值。
-
对象的第三个属性是一个string类型,nameAlias: 'name',映射 this.nameAlias 为 store.state.name的值, 和第一个属性的用法本质是一致的,不过这里映射出的计算属性的名称与 state 的子节点名称不同。
-
对象的第四个属性是一个普通函数,普通函数和箭头函数的不同之处在于,普通函数中的this指向了vue实例,因为可以访问到当前组件的局部状态,比如this.tempcount。
-
对象的第五个属性是一个普通函数,第五个和第四个的用法本质是一样的,只不过第五个用了ES6中对象的简化写法。
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
比如上面的count和name,可以直接用字符串来代替。(这也是大部分mapState的用法)
computed: mapState([
// 映射 this.count 为 store.state.count
'count',
'name'
])
上面的写法等于
computed: {
count () {
return this.$store.state.count
},
name () {
return this.$store.state.name
}
}
最优化的写法
computed: {
tempCountPlusTempCount2() {
return this.tempcount+this.tempcount2
},
...mapState(['count','name']),
...mapState({
nameAlias: 'name', // string 映射 this.nameAlias 为 store.state.name的值
countplustempcount: function (state) { // 用普通函数this指向vue实例,但是在箭头函数中this就不是指向vue实例了,所以这里必须用普通哈数
return this.tempcount + state.count
},
countplustempcount2 (state) {
return this.tempcount2 + state.count
}
})
}
Object.keys()
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)。
也就是说我们可以用此方法罗列出对象的所有属性名
var obj = {'a':'123','b':'345'};
console.log(Object.keys(obj)); //['a','b']
var obj1 = { 100: "a", 2: "b", 7: "c"};
console.log(Object.keys(obj1)); // console: ["2", "7", "100"]
var obj2 = Object.create({}, { getFoo : { value : function () { return this.foo } } });
obj2.foo = 1;
console.log(Object.keys(obj2)); // console: ["foo"]