这一篇是这个系列里的第二篇文章,如果没有看过第一章节,请移步到第一章节。
从零实现一套低代码(保姆级教程) — 【1】初始化项目,实现左侧组件列表
毕竟要看一下实现出来什么东西,再决定是不是要学习呢。
那整体来说,本系列主要就是实现出一套低代码的项目,而如果你已经看完了第一节。希望在后面的章节里,你依旧能一边参考线上例子,一边自己实现。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
XinBuilder 点击跳转
在上一节课里,我们实现了左侧组件列表的展开。注意一点,在文章后面,我做了一点样式的优化,在github的提交里可以看到。
目前的效果是这样:
而这一篇文章,主要是为了实现画布区(当然是简单先实现出来效果),并且能够往里面拖入组件。
如果你对上一节内容已经完成了的话,那么就开始吧。
首先我们回顾一下第一节的内容,我们左侧组件列表里的组件是哪里来的?
是在components文件夹下的index.js中,通过export导出的。
那我们导出的是什么,就是组件本身。所以在renderComponent方法中,我们可以拿到组件的类型,然后在画布区通过<Com / >的方式展现。
OK,如果这里没有问题的,每次我拖拽组件的时候,我要保留当前拖拽组件的name。
现在我们补充renderComponent方法:
const renderComponent = () => {
return <div className="componetGroup">
{
Object.keys(components).map(name => {
const Icon = componentIconMap[name]
const text = componentTextMap[name]
return <div key={name} className='componentItem'>
// 新加的onDragStart方法,传入组件方法和组件类型
<div onDragStart={onDragStart(name)} draggable style={{display: 'inline-block'}}><Icon style={{marginRight:'10px'}} /><span>{text}</span></div>
</div>
})
}
</div>
}
那在onDragStart方法里我们应该做什么呢,要把这个数据传递给画布区。然后在画布区里插入Com就行了,是吧。
这里我们先不考虑怎么传递,直接先挂载window上。后面会有其他的方法,先实现逻辑就行。
const onDragStart = (name: string) => {
return () => {
window.nowCom = name
}
}
经过这部分修改,我们每次拖拽组件的时候,在window上就会挂载一个组件类型。
读者也可自己打个log来看一下,是不是正确的数据。
现在我们拖拽左侧组件,不管拖拽到哪里,鼠标上肯定都有一个禁用符号。
那是因为浏览器的默认行为是,所有的组件都不允许防止拖拽元素。
为了解决这个问题,我们可以禁止onDragEnter和onDragOver的默认行为,同时给画布区一个onDrop事件。
onDrop事件,是当我把组件,拖入到画布区后,触发的事件,所以画布区的渲染逻辑。我们要写在这个方法里。
import './index.css'
export default function MainCom() {
const onDrop = () => {
}
const onDragEnter = (e: any) => {
e.preventDefault()
}
const onDragOver = (e: any) => {
e.preventDefault()
}
return (
<div onDrop={onDrop} onDragOver={onDragOver} onDragEnter={onDragEnter} className='mainCom'>
</div>
)
}
我之前说过,对于画布区,我的设计思路是,整个屏幕都是画布区。只不过左侧栏,右侧栏和顶部栏会遮挡画布区,不过我可以通过折叠他们来扩大画布区。
所以对于画布区来说,我设置的样式是整个屏幕,当然如果你不希望这么做,也可以自己调整对应的样式。
.mainCom {
width: 100%;
height: 100vh;
}
OK。实现完上面的步骤,我们就可以实现画布区的渲染了。
触发Drop事件的时候,window上已经有一个nowCom了,我们就可以通过这个组件类型,获取到对应的组件。
但是我们会拖入很多组件,所以我们先用一个List(后面会采用redux来进行存储),来存储所有的组件类型。
const [comList, setComList] = useState<string []>([])
const onDrop = () => {
comList.push(window.nowCom)
setComList([...comList])
}
这样每次拖拽到画布区后,comList中就会存储一个组件类型。
在render函数中,我们再遍历comList,返回对应的组件。
return (
<div onDrop={onDrop} onDragOver={onDragOver} onDragEnter={onDragEnter} className='mainCom'>
{
comList.map(name => {
const Com = components[name as keyof typeof components];
return <Com />
})
}
</div>
)
OK。现在我们就可以看一下页面效果,是不是发现拖拽完之后并没有效果。。。。
那是因为被顶部栏和左侧栏挡住了,因为我们没有设置组件的位置,默认就会在左上角。
但是看页面结构,你会发现组件已经添加到画布区了。
目前我们已经把组件添加到画布区了,但是我们肯定希望的是,我鼠标在哪,我拖拽的组件的位置就在哪里。
那我们实现一下这部分功能,那我们可以通过e.clientX和e.clientY来确定鼠标的位置。最终怎么映射到组件上呢?组件需要接受一个style属性。
所以,我们的comList里,就不能单纯的是一个stirng类型的name了。我们还需要样式,所以我们修改为成一个对象类型的结构。
interface ComJson {
comType: string,
style?: any
}
// 修改存储类型
const [comList, setComList] = useState<ComJson []>([])
const onDrop = () => {
comList.push({
comType: window.nowCom
})
setComList([...comList])
}
return (
<div onDrop={onDrop} onDragOver={onDragOver} onDragEnter={onDragEnter} className='mainCom'>
{
comList.map(com => {
const Com = components[com.comType as keyof typeof components];
return <Com />
})
}
</div>
)
现在我们再onDrop中,给组件加上style。
const onDrop = (e: any) => {
// 新加的代码。给对应的组件加上style
const endLeft = e.clientX;
const endTop = e.clientY;
const style = {
position: 'absolute',
left: endLeft + 'px',
top: endTop + 'px',
zIndex:100
}
comList.push({
comType: window.nowCom,
style
})
setComList([...comList])
}
再把这个style再传递给组件:
return (
<div onDrop={onDrop} onDragOver={onDragOver} onDragEnter={onDragEnter} className='mainCom'>
{
comList.map(com => {
const Com = components[com.comType as keyof typeof components];
// 新加的代码,将style传递给组件
return <Com style={com.style} />
})
}
</div>
)
OK,现在我们离成功只有一步了,这个style传给哪个组件了?
就是componets文件夹下的组件,现在我们给button组件的props里,接收一个style!!!!
export default function Button(props: any) {
const {style} = props
return (
<div style={style}>Button</div>
)
}
现在再来看一下效果吧!
因为我们的Button里面只有一个文本,所以展示的也是文本。
OK,到现在画布区的渲染也算是简单实现了。
可能有细心的读者会发现,最后循环渲染的组件,没有key。是因为在后面的章节,我们要给组件生成一个唯一的ID,然后当做key。这里就先空着了。
对于其他的组件,如果在你的代码里有,可以把style补充进去。当然这里博主给一个思路,既然style可以传过去,那其他属性是不是也可以传。如果你的button组件,就是antD的button组件,那你可以传的属性是不是很多呢?
当然这些内容后面都会实现的。
本章内容会提交在github上:
https://github.com/TeacherXin/XinBuilder2
commit: 第二节:实现画布区并展示组件