从零实现一套低代码(保姆级教程) --- 【2】实现画布区并展示组件

摘要

这一篇是这个系列里的第二篇文章,如果没有看过第一章节,请移步到第一章节。
从零实现一套低代码(保姆级教程) — 【1】初始化项目,实现左侧组件列表

毕竟要看一下实现出来什么东西,再决定是不是要学习呢。
那整体来说,本系列主要就是实现出一套低代码的项目,而如果你已经看完了第一节。希望在后面的章节里,你依旧能一边参考线上例子,一边自己实现。

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
XinBuilder 点击跳转

在上一节课里,我们实现了左侧组件列表的展开。注意一点,在文章后面,我做了一点样式的优化,在github的提交里可以看到。
目前的效果是这样:

在这里插入图片描述
而这一篇文章,主要是为了实现画布区(当然是简单先实现出来效果),并且能够往里面拖入组件。

如果你对上一节内容已经完成了的话,那么就开始吧。
在这里插入图片描述

1.组件该如何渲染

首先我们回顾一下第一节的内容,我们左侧组件列表里的组件是哪里来的?
是在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来看一下,是不是正确的数据。

2.实现画布区的可拖拽

现在我们拖拽左侧组件,不管拖拽到哪里,鼠标上肯定都有一个禁用符号。
那是因为浏览器的默认行为是,所有的组件都不允许防止拖拽元素。

为了解决这个问题,我们可以禁止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。实现完上面的步骤,我们就可以实现画布区的渲染了。

3. 实现画布区添加组件

触发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。现在我们就可以看一下页面效果,是不是发现拖拽完之后并没有效果。。。。
那是因为被顶部栏和左侧栏挡住了,因为我们没有设置组件的位置,默认就会在左上角。

但是看页面结构,你会发现组件已经添加到画布区了。

在这里插入图片描述

4.实现组件的位置定位

目前我们已经把组件添加到画布区了,但是我们肯定希望的是,我鼠标在哪,我拖拽的组件的位置就在哪里。

那我们实现一下这部分功能,那我们可以通过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) => {// 新加的代码。给对应的组件加上styleconst 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} = propsreturn (<div style={style}>Button</div>)
}

现在再来看一下效果吧!
在这里插入图片描述
因为我们的Button里面只有一个文本,所以展示的也是文本。

OK,到现在画布区的渲染也算是简单实现了。

博主补充

可能有细心的读者会发现,最后循环渲染的组件,没有key。是因为在后面的章节,我们要给组件生成一个唯一的ID,然后当做key。这里就先空着了。

对于其他的组件,如果在你的代码里有,可以把style补充进去。当然这里博主给一个思路,既然style可以传过去,那其他属性是不是也可以传。如果你的button组件,就是antD的button组件,那你可以传的属性是不是很多呢?
当然这些内容后面都会实现的。

本章内容会提交在github上:
https://github.com/TeacherXin/XinBuilder2
commit: 第二节:实现画布区并展示组件