JS前端画布与组件元信息数据流示例详解

目录
  • 正文
  • 拓展应用状态与静态方法
  • 总结

正文

接下来需要解决两个问题:

  • 可视化搭建的其他业务元素如何与画布交互。比如拓展属性配置面板、图层列表、拖拽添加组件、定位锚点、主题等等。
  • runtimeProps 如何访问到当前组件实例的 props

这两个问题非常重要,而恰好又可以通过良好的数据流设计一次性解决,接下来让我们分别分析讨论一下。

问题一:可视化搭建的其他业务元素如何与画布交互。比如拓展属性配置面板、图层列表、拖拽添加组件、定位锚点、主题等等

需要设计一个 Hooks API,可以访问到画布提供的方法、数据。在 React 设计中,访问 Hooks API 需要在一定上下文内,所以可以将 <Designer> 拆为 <Designer><Canvas>,其中 <Designer> 提供 Hooks 上下文,<Canvas> 负责渲染画布。这样开发者的使用方式就变成了这样:

import { createDesigner } from 'designer'
const { Designer, Canvas, useDesigner } = createDesigner()
const EditPanel = {
  const { addComponent } = useDesigner()
  return <button onClick={() => addComponent(/** ... */)}>创建组件</button>
}
const App = () => {
  <Designer>
    <Canvas />
    <EditPanel />
  </Designer>
}

为了支持多个 Designer 实例间隔离,通过 createDesigner 创建一套上下文独立的 API,这样就可以让画布、配置面板同时用 Designer 实现,用一套技术方案同时实现画布与配置表单,这样学习上下文、组件规范都可以统一为一套,表单、画布能力也可以共享。

<Designer> 内的组件可以通过 useDesigner 直接访问数据与方法,比如上面例子在直接访问内置方法 addComponent 时,不需要附加任何参加,而 addComponent 方法也永远保持引用不变,此时 useDesigner 不会导致 EditPanel 重渲染。

如果需要访问当前组件树,并在组件树变化时重渲染,可以通过如下方式访问:

const EditPanel = {
  const { componentTree } = useDesigner(state => ({
    componentTree: state.componentTree
  }))
}

该写法的效果是,当 state.componentTree 变化了,会触发 EditPanel 重新渲染,并拿到最新值。

同时也可以传入第二个参数 compare 自定义对比方法,默认为 shallowEqual

useDesigner(
  (state) => ({
    componentTree: state.componentTree,
  }),
  isEqual
);

如此一来,无论给画布拓展多少 UI 元素都没有问题,而且 UI 元素可以自由的访问画布方法与数据。

问题二:runtimeProps 如何访问到当前组件实例的 props

componentMeta.runtimeProps 中,我们构造一个 selector 函数用于访问当前组件 props:

const divMeta = {
  componentName: "div",
  runtimeProps: ({ selector }) => {
    const name = selector(({ props }) => props.name)
    return {
      fullName: `full-${name}`
    }
  }
  element: /** ... */
};

首先支持从 runtimeProps 回调里拿到 selector,并且该 selector 支持传入一个回调函数,该回调函数的参数中 props 指向当前组件实例的 props,通过该方法就可以访问组件 props 了。

该 selector 仅在 props.name 改变时重新执行,并且也遵循 compare 对比规则,即当 props.name 变化时,selector 回调函数的返回值通过 compare 与上一次值进行对比,如果没有变化就返回上一次的旧值,变化了则返回新值。默认对比函数为 shallowEqual,与 useDesigner 类似,也可以在第二个参数位置覆写 compare 方法。

那组件元信息如何访问内置静态方法呢?由于静态方法引用不变,因此可以在 selector 同级直接传入:

const divMeta = {
  componentName: "div",
  runtimeProps: ({ addComponent }) => {
    return {
      add: () => {
        /** addComponent(...) */
      }
    }
  }
  element: /** ... */
};

如此一来,我们就将数据流与组件元信息打通了,即 UI 可以通过 useDesigner 访问与操作数据流,组件元信息也可以直接拿到方法,或通过 selector 拿到数据,相应的也可以访问与操作数据流。这样的设计在以后拓展更多组件元信息函数时,都可以继承下来,开发者只要学习一次语法,就可以获得非常强力的拓展性。

拓展应用状态与静态方法

刚才介绍了一些内置的状态(componentTree)与方法(addComponent),在下一接会系统介绍笔者梳理了哪些内置状态与方法。首先抛开内置状态与方法不谈,应用肯定需要定义自己的状态与方法,我们可以提供两种模式给用户。

第一种是应用的状态与方法定义在外部,对应受控模式。

假设你的应用在对接 Designer 之前就已经用 Redux、Dva、Zustand 等状态管理库,那么就可以使用受控模式直接接入:

const App = () => {
  // 伪代码,不管是 useState 还是其他数据流管理状态,假这里拿到了数据与方法
  const { getAppInfo } = useSomeLib();
  const { userName } = useSomeLib("userName");
  return <Designer actions={{ getAppInfo }} state={{ userName }} />;
};

将方法传给 actions,状态传给 state

第二种是应用的状态与方法通过 <Designer> 定义,对用非受控模式。

假设你的应用之前没有使用任何数据流,那么也可以直接将 Designer 的数据流作为项目数据流使用:

import { createMiddleware, createDesigner } from "designer";
const middleware1 = createMiddleware({
  state: { userName: "bob " },
  actions: { getAppInfo: () => {} },
});
const { Designer } = createDesigner(middleware1);
const App = () => {
  return <Designer />;
};

通过 createMiddleware 创建一个中间件定义状态与函数,传入 createDesigner 即可生效。

也可以在 createMiddleware 里通过第二个参数定义自定义 hooks,或者拿到方法更改 State:

const middleware1 = createMiddleware(
  {
    state: { userName: "bob " },
  },
  ({ setState }) => {
    const setUserName = React.useCallback((newName: string) => {
      setState((state) => ({
        ...state,
        userName: newName,
      }));
    });
    return { setUserName };
  }
);

Designer 内部采用最朴素的 Redux 管理状态,提供了最基础的 getStatesetState 获取与修改状态,基于它们封装业务函数即可。

无论是受控模式,还是非受控模式(亦或两种模式同时使用),定义的状态与方法都可以在以下两个位置访问,第一个位置是 useDesigner

const {
  /** 自定义函数 */,
  setUserName,
  /** 自定义函数 */
  getAppInfo,
  /** 内置函数 */
  addComponent,
  // 内置变量
  componentTree,
  // 自定义变量
  userNamee
} = useDesigner(state => ({
  componentTree: state.componentTree,
  userName: state.userName
}))

第二个位置是组件元信息上的回调函数,比如 runtimeProps

const divMeta = {
  componentName: "div",
  runtimeProps: ({
    selector,
    /** 自定义函数 */,
    setUserName,
    /** 自定义函数 */
    getAppInfo,
    /** 内置函数 */
    addComponent
   }) => {
    const {
      /** 内置变量 */
      componentTree,
      /** 自定义变量 */
      userName
    } = selector(({ state }) => ({
      componentTree: state.componentTree,
      userName: state.userName
    }))
    return { componentTree, userName }
  }
  element: /** ... */
};

至此,我们实现了一套完整的数据流定义,包括:

  • 不同 Designer 之间上下文隔离。
  • 可无缝对接项目数据流,也可作为独立数据流方案提供。
  • 内置变量与函数与自定义变量、函数混合。
  • 无论在 UI 通过 useDesigner,还是在组件元信息通过 selector 都可访问这些变量与函数。

总结

一个基本可用的可视化搭建框架在本章就算设计完了。但这只是可视化搭建问题的冰山一角,未来的章节,笔者会逐渐为大家介绍更多可视化搭建的设计。

但无论框架未来怎么发展,也永远会基于这前三章的基本设定,总结一下,这三章的基本设定就是:设计一个逻辑与 UI 分离的可视化搭建协议,数据流、组件元信息、组件实例是永远的铁三角,数据流可以对接任意已存在的实现,或基于 Designer 规范实现,组件元信息与组件实例仅存储最基本信息,得益于数据流的自定义能力,以及无论何处都有完全的数据流访问能力,使业务框架既遵循规则,又可以千变万化。

抛开具体 API 设计或者命名不谈,一个有简洁、抽象,又提供极少量 API 却能满足所有业务定制诉求,是可视化搭建永远追求的目标。只要熟悉了这套规范,就可以几乎仅根据业务表现,一眼猜出是基于哪些 API 封装实现的,那么维护成本与理解成本将大大降低,规范的意义就体现在这里。

也许有同学会觉得,现在各个大厂都有无数可视化搭建的实现,可视化搭建概念都已经烂大街了,为什么还要重新设计一个呢?

因为也许数量不代表质量,维护的时间越久,参与的同学越多,越容易使设计变得冗余,概念变得复杂,要对抗这些递增的熵,唯有不断重新设计,从零开始反思方案。

下一讲理论思考会少一些,介绍可视化搭建框架会考虑内置哪些变量与方法,更多关于JS画布与组件元信息数据流的资料请关注我们其它相关文章!

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证

(0)

相关推荐

  • JS实现一个文件选择组件详解

    目录 前言 插件安装 插件使用 参数说明 前言 花了点时间利用广度与深度优先搜索算法实现了一个文件选择插件,支持无限层次的文件夹嵌套,已开源并打包上传到了npm. 本文将跟大家分享一下这个插件,欢迎各位感兴趣的开发者阅读本文. 插件安装 yarn add file-folder-selector # or npm install file-folder-selector --save 插件使用 在你需要使用此插件的业务代码中导入插件. <script setup lang="ts"

  • 关于vue.js组件数据流的问题

    一.组件 组件,可以说是现代前端框架中必不可少的组成部分.使用组件,不仅能极大地提高代码的复用率和开发者的开发效率,对于代码后期的维护也有着非常重要的意义.前端开发,由于历史遗留原因,WebComponent 虽然好用,但其发展情况却受到极大地限制,和很多新兴的前端技术一样,可望而不可即.基于这样的情况,聪明的开发者们尝试通过框架内部集成相应的功能来完成组件化,各种现代前端框架基本上都有各自的实现.这里我们来分析一下 vue 的组件,重点关注数据的流向. 二.vue 组件 vue 的组件,创建模

  • jsoneditor二次封装实时预览json编辑器组件react版

    目录 前言 设计思路 正文 jsoneditor的使用 结合react进行二次封装 前言 做为一名前端开发人员,掌握vue/react/angular等框架已经是必不可少的技能了,我们都知道,vue或react等MVVM框架提倡组件化开发,这样一方面可以提高组件复用性和可扩展性,另一方面也带来了项目开发的灵活性和可维护,方便多人开发协作.接下来文章将介绍如何使用react,开发一个自定义json编辑器组件.我们这里使用了jsoneditor这个第三方库,官方地址: jsoneditor 通过实现

  • JS前端画布与组件元信息数据流示例详解

    目录 正文 拓展应用状态与静态方法 总结 正文 接下来需要解决两个问题: 可视化搭建的其他业务元素如何与画布交互.比如拓展属性配置面板.图层列表.拖拽添加组件.定位锚点.主题等等. runtimeProps 如何访问到当前组件实例的 props. 这两个问题非常重要,而恰好又可以通过良好的数据流设计一次性解决,接下来让我们分别分析讨论一下. 问题一:可视化搭建的其他业务元素如何与画布交互.比如拓展属性配置面板.图层列表.拖拽添加组件.定位锚点.主题等等 需要设计一个 Hooks API,可以访问

  • JS前端中的设计模式和使用场景示例详解

    目录 引言 策略模式 1.绩效考核 2.表单验证 策略模式的优缺点: 代理模式 1.图片懒加载: 2.缓存代理 总结 引言 相信大家在日常学习和工作中都多多少少听说/了解/使用过 设计模式,我们都知道,使用恰当的设计模式可以优化我们的代码,那你是否知道对于前端开发哪些 设计模式 是日常工作经常用到或者必须掌握的呢?本文我将带大家一起学习下前端常见的设计模式以及它们的 使用场景!!! 本文主讲: 策略模式 代理模式 适合人群: 前端人员 设计模式小白/想知道如何在项目中使用设计模式 策略模式 策略

  • JS前端二维数组生成树形结构示例详解

    目录 问题描述 实现步骤 完整代码 问题描述 前端在构建国家的省市区结构时,接口返回的不是树形结构,这个时候就需要我们进行转化.以下数据为例 [ [ { "districtId": 1586533852834, "parentCode": "000", "nodeCode": "000001", "name": "浙江省", "districtType&qu

  • JS前端使用canvas动态绘制函数曲线示例详解

    目录 前言 第一步:绘制坐标系 1.如何确定 x 轴和 y 轴的边界值 2.不是传入多少网格数就是多少网格 3.如何让坐标原点位于画布中心 4.刻度总是会有浮点数 第二步:画函数曲线 第三步:绘制辅助线和交点坐标 第四步:平移 第五步:缩放 第六步:动态绘制曲线 第七步:模糊到高清 前言 不说废话,我们直入主题.先来看看读了这篇文章你将得到什么,就是下面这个东西啦(是不是很清晰很顺滑): 那具体要做什么呢,我们来简单拆解一下步骤: 绘制坐标系 绘制多条函数曲线 绘制辅助线和坐标点 支持平移.缩放

  • javascrip高级前端开发常用的几个API示例详解

    目录 MutationObserver API 特点 IntersectionObserver API 举个例子 图片懒加载 无限滚动 getComputedStyle() API 与style的异同 getBoundingClientRect API 应用场景 1.获取 dom 元素相对于网页左上角定位的距离 2.判断元素是否在可视区域内 MutationObserver MutationObserver 是一个可以监听 DOM 结构变化的接口. 当 DOM 对象树发生任何变动时,Mutati

  • vue+three.js实现炫酷的3D登陆页面示例详解

    目录 前言: Three.js的基础知识 关于场景 关于光源 关于相机(重要) 关于渲染器 完善效果 创建一个左上角的地球 使地球自转 创建星星 使星星运动 创建云以及运动轨迹 使云运动 完成three.js有关效果 结语 前言: 大家好,我是xx传媒严导(xx这两个字请自行脑补) . 该篇文章用到的主要技术:vue3.three.js 我们先看看成品效果: 高清大图预览(会有些慢): 座机小图预览: 废话不多说,直接进入正题 Three.js的基础知识 想象一下,在一个虚拟的3D世界中都需要什

  • React特征Form 单向数据流示例详解

    目录 引言 集中状态管理 双向数据流 那为何不选择双向数据流 小结 引言 今天将之前的内容做个系统整理,结合 React Form 案例, 来了解下为何React推荐单向数据流,如果采用双向管理,可能的问题 (关于React Form案例,可参考相关文章 - 学习React的特征(二) - React Form 集中状态管理 首先来看之前的React Form, 若采用单向数据流 import * as React from 'react'; const Useremail = props =>

  • vue3.2自定义弹窗组件结合函数式调用示例详解

    目录 前言 手写弹窗组件 组件调用 函数式调用 如何使用 含样式完整源码 效果图 前言 涉及的vue3知识点/API,createApp defineProps defineEmits <script setup> v-model <script setup> 就是 setup 语法糖 defineProps 和 props 用法差不多 defineEmits 声明可向其父组件触发的事件 手写弹窗组件 很简单的弹窗组件,支持设置标题 <script setup> def

  • vue实现前端展示后端实时日志带颜色示例详解

    目录 vue实现前端展示后端带颜色的日志 需求 操作 采用innerHTML例子 需求: 解决 效果 vue实现前端展示后端带颜色的日志 需求 通过loki获取项目产生的日志,并且在前端显示出来,一开始在没有经过处理的数据会显示一些乱码,并没有将字符转换 经过一番查询后,发现可以使用ansi_up来对日志进行操作颜色代码进行转化. 操作 ansi_up 能够装换颜色代码 GitHub地址 https://github.com/drudru/ansi_up 安装 npm install ansi_

  • C#实现给图片添加日期信息的示例详解

    实践过程 效果 代码 public partial class Form1 : Form { public Form1() { InitializeComponent(); } public string flag = null; PropertyItem[] pi; string TakePicDateTime; int SpaceLocation; string pdt; string ptm; Bitmap Pic; Graphics g; Thread td; private void

随机推荐