草庐IT

低代码平台前端的设计与实现(四)组件大纲树的构建设计

w4ngzhen 2023-03-28 原文

在上篇文章,我们已经设计了一个简单的设计态的Canvas,能够显示经过BuildEngine生成的ReactNode进行渲染。本文,我们将继续上一篇文章的成果,设计并实现一个能够显示组件节点大纲树的组件。

什么是组件大纲树?

我们希望用户能通过一个地方比较明显的看到当前整个ComponentNode的树状结构;当用户点击某个ComponentNode的时候,既能够在DesignCanvas上高亮当前选中的UI元素,同时对于组件大纲树上也能高亮对应的树状节点。

PS:我们所设计的低开前端平台定位是轻量级。所以,我们在构建整个平台核心库的时候,并不会设计的非常复杂,本次我们将不会设计直接将元素进行拖拉拽到画布的内容,而是会围绕整个节点大纲树,来优化我们的低开体验。

如何设计实现大纲树与设计态UI界面的统一?

在本次设计与开发之前,我们需要回顾一下上篇文章中(低代码平台前端的设计与实现(三)设计态画布DesignCanvas的设计与实现 - 知乎 (zhihu.com))关于DesignCanvas的设计。DesignCanvas的过程设计如下:

正如上图所示,DesignCanvas的执行过程中,step4 -> data5 -> step6 -> data7 是在一个函数处理过程中的。

为了实现本次的需求,我们可能需要对上述的过程进行一定的优化,达到UI的渲染与元素节点大纲树组件在同一个DesignCanvas中的渲染的目的。在讨论如何修改前,我们先采用一个流程图来展示这个过程:

从上图,我们可以很容易的知道,为了让ComponentNode树到UI界面的生成与ComponentNode树到节点大纲树的生成是一致且同步的。我们需要将ComponentNode object和selectedNodePath再交给组件大纲树进行渲染。

在这样一套设计下,无论点击大纲树任意树节点,还是点击设计态UI界面的任意UI组件。我们都能够通过相关的事件(对于大纲树来说是树节点的点击事件;对于设计态UI界面上的UI组件来说是前面设计的wrapper的点击事件)拿到当前点击的元素的唯一path标识;然后,我们将拿到的path标识设置给selectedtNodePath这个state,最后再由该state来同时控制大纲树的节点高亮和设计态UI界面上的UI组件的边框高亮。这个过程由下面的流程图来简单描述:

大纲树组件实现

首先,我们选择了antd5的Tree树形组件。对于该组件我们会以受控的方式来使用,具体来讲,Tree树形组件的节点选中通过属性selectedKeys控制;树形组件的节点展开通过属性expandedKeys来控制。当然,一旦我们选择该组件以受控方式使用,那么不可避免的需要用对应的onSelect事件onExpand事件来获取当前状态值,再交给上述的selectedKeysexpandedKeys

Tree组件的基本用法

本节内容主要讲antd5的Tree树形组件的基本用法,目的是为了后面我们具体的大纲树组件做基础准备,可以完全当作独立的一节内容来看。

Tree的selectedKeys接收的是一个数组,用以表现被选中的节点。但需要特别注意:

Tree在默认的使用场景下是单个选中。也就是说,用户点击任意一个节点时,就选中该节点;点击其他节点,则选中其他节点。同一时间只会有一个被选中的节点。selectedKeys尽管是一个数组,但在单选场景下,要不是一个空数组来表示没有节点选中,要不是一个只有一个元素的数组,表示某一个节点选中。下面用一个Demo来演示:

 /**
 * 首先准备一段测试数据:
 * 1
 * ├ 1-1
 * └ 1-2
 *   └ 1-2-1
 * 注意:TREE_DATA是一个数组!
 **/
const TREE_DATA = [
    {
        key: '1',
        title: 'title 1',
        children: [{
            key: '1-1',
            title: 'title 1-1'
        }, {
            key: '1-2',
            title: 'title 1-2',
            children: [{
                key: '1-2-1',
                title: 'title 1-2-1'
            }]
        }]
    }
]

然后,编写一段代码,将selectedKeys设置为1-2-1,也就是说,我们选中了上面的1-2-1节点:

export const TreeDemo = () => {
    return <Tree selectedKeys={['1-2-1']} treeData={TREE_DATA}/>
}
// 再次强调,selectedKeys是一个数组,但是在默认情况下,该数组只有一个元素或者空。

这个例子的效果如下:

从上面的gif可以看到界面渲染后,选中的节点就是1-2-1。同时,其他的节点无论我们如何点击,都不会有任何的效果(受控)。为了能够点击后,让Tree组件选中对应的节点,我们需要将selectedKeys至少作为一个state来存放,然后通过onSelect来设置该state:

export const TreeDemo = () => {
    // 用一个state来表明当前选择的Keys
    const [currSelectedKeys, setCurrSelectedKeys] = useState<string[]>([]);
    return <Tree
        treeData={TREE_DATA}
        selectedKeys={currSelectedKeys}
        onSelect={selectedKeys => {
            // 当我们点击任何一个节点的时候,都会触发该onSelect,第一个参数则是即将选中的Keys
            // 当然,根据文档,我们重复点击同一节点,也会触发该onSelect事件,但参数 selectedKeys 会是一个空数组
            console.log('onSelect, selectedKeys: ', selectedKeys);
            setCurrSelectedKeys(selectedKeys as string[])
        }}
    />
}

上述的过程,可以用如下的数据流来描述:

上述过程中,currSelectedKeys表明当前选中的Keys(默认的单选模式下,是一个长度为1或0的数组),传给Tree的属性selectedKeys,Tree组件的UI展示的过程中使用根据selectedKeys来高亮对应节点;当然,我们点击任意节点的时候,会触发onSelect事件,该事件第一个参数就是点击选中的节点的Keys,我们可以直接将这个值再次设置给currSelectedKeys这个state。在上述的代码下,我们可以看到效果如下:

现在,我们分析了selectedKeys后,再来分析一下Tree树形组件的expandedKeys。这个属性是一个数组,控制整个Tree节点展开的Keys。我们首先将该值设置为:['1']

    ... ...
    return <Tree
        treeData={TREE_DATA}
        selectedKeys={currSelectedKeys}
        onSelect={selectedKeys => {
           ... ...
        }}
+       expandedKeys={['1']}
    />

然后查看Demo效果:

可以看到,无论怎样点击节点左侧的三角,都无法展开或收起对应的子节点。类似的,我们使用一个state来存储展开的节点,然后使用onExpand事件来设置,即可达到效果:

组件大纲树面板

有了上面关于antd5的Tree树形组件的受控方式的使用基础,我们开始设计我们自己的组件大纲树组件,这里我们为它取名为:ComponentNodeTreeDesignPanel。该组件的props如下:

interface ComponentNodeTreeDesignPanelProps {
    /**
     * 根 ComponentNode
     */
    componentNode: ComponentNode;
    /**
     * 选中的元素节点
     */
    selectedComponentNodePath: string;
    /**
     * 点击选中Tree中的某个节点的事件回调
     * @param selectedNodePath 选中指定的节点的Path,
     * 譬如:"/page/panel@0/button@0"
     */
    onComponentNodeSelected: (selectedNodePath: string) => void;
}

我们再来讨论下这些属性如何关联内部的antd Tree树形组件的渲染与行为的。这里,我直接用一个流程图来描述:

上述过程具体为:

  1. 首先,为了呈现组件节点树状UI,很容易知道至少需要将ComponentNode对象传入,因为该对象本身就是树形的,只需要进行简单的数据转换即可完成Tree的树形数据格式,并渲染;

  2. 其次,为了达到高亮对应的节点效果,则需要传入当前选中的节点的唯一标识path,在内部转换为selectedKeys和expandedKeys;

  3. 最后,当我们点击Tree的节点时候,需要把对应的节点信息传到上层,让外部再次控制传入当前选中的ComponentNode的path,形成一个闭环的数据流。

当然,这里面还涉及一些转换,还有path的构成规则。这里不再赘述,感兴趣的读者可以阅读有关ComponentNodeTreeDesignPanel的组件代码。

最终效果:

附录

本次的内容已经提交至Github,并在打上了相应的Git tag标识:

https://github.com/w4ngzhen/lite-lc/tree/chapter_04

commit信息(倒序):

2. 修改DesignCanvas相关逻辑,实现ComponentNodeTreeDesignPanel组件与BuildEngine生成的组件对于选中的节点path,同步分别高亮树形节点和UI组件。

1. 新增工具方法,支持根据 ComponentNode 的path,得到该节点的整个链路path形成的数组;新增 ComponentNodeTreeDesignPanel 组件,内部使用antd5的Tree树形控件呈现 ComponentNode 的树状结构,且通过外部传入的"选中节点path"属性,以受控方式控制高亮节点。

有关低代码平台前端的设计与实现(四)组件大纲树的构建设计的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  6. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  7. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  8. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  9. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  10. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

随机推荐