前端可视化搭建组件值与联动实现详解

目录
  • 正文
  • 组件值
  • 值联动
  • 描述联动行为
  • 用持续联动实现一次性联动
  • 总结

正文

组件联动是指几个组件相互关联。也就是当一个组件状态变化时,其他组件可以响应。

组件联动是多对多关系的,且目的分为一次性与持续性:

  • 多对多关系:即一个组件可以同时被多个组件联动;多个组件可以同时联动一个组件。
  • 一次性与持续性:一次性事件可以被覆盖,持续性事件会同时生效,且要考虑叠加关系。

一定程度上,持续性事件可以覆盖一次性事件的场景:组件永远响应最后一个过来的事件即可。

接下来我们引入 组件值值联动 两个概念,来实现持续性联动功能。

组件值

每个组件实例都有一个唯一的组件值。

我们可以通过 getValue(componentId)setValue(componentId, value) 访问或更新组件值:

const table = {
  componentName: "table",
  runtimeProps: ({ componentId, setValue }) => ({
    // 给组件注入 onChange 函数,在其触发时更新当前组件实例的组件值
    onChange: (value) => setValue(componentId, value),
  }),
};

也可以通过 componentMeta.value 声明组件值,比如下面的例子,让组件值与 props.value 同步:

const table = {
  componentName: "table",
  // 声明 value 的值为组件 props.value 的返回值,并随着组件 props.value 的更新而更新
  value: ({ selector }) => selector(({ props }) => props.value),
};

以上两种方式任选一种使用即可。

为什么一个组件实例只有一个组件值?

一个组件可能同时拥有多个状态,比如该组件内部有一个输入框,还有一个按钮,可能输入框的值,与按钮的点击状态都会对其他组件产生联动效果。但这并不意味着一个组件实例需要多个组件值,我们可以将组件值定义为对象,并合理规划不同的 key 描述不同维度的值:

// 组件值结构
{
  // 组件内输入框的值
  text: '123',
  // 组件内按钮被按下的次数
  buttonClickTimes: false
}

为什么不用 props.value 代替组件值?

理论上可以,但这样限定了组件对 props 的定义。也许有的组件用 props.value 描述输入框的值,但也有比如 Check 组件,用 props.checked 表示当前选中状态。只有抽象一个定义与组件元信息的规则,让业务自由对接,才可以让组件值适配任意类型的组件。

值联动

有了组件值这个概念,就可以以组件实例为粒度,设计组件的关联关系了。

为了让组件关联更加灵活,我们的设计需要满足以下几种能力:

  • 联动关系支持多对多。
  • 可以随着全局数据状态变化,或者组件自身 props 变化,随时改变组件关联关系。
  • 一个组件可以定义其他几个组件的关联关系,哪怕自己不参与到联动关系链中。
  • 当组件实例被删除时,由它定义的联动关系立刻失效。

估我们采用 componentMeta.valueRelates 声明式定义值联动关系:

const table = {
  componentName: "table",
  valueRelates: ({ componentId, selector }) => {
    return [
      {
        sourceComponentId: componentId, // 自己为触发源
        targetComponentId: selector(({ props }) => props.targetComponentId), // 目标组件 ID 为 props.targetComponentId
      },
    ];
  },
};

这样设计可以同时满足以上四个要求,解释如下:

  • 可以在任意组件实例定义多个联动关系,自然可以实现多对多联动。
  • valueRelates 引入 selector 可以响应 stateprops 的变化,可以由任意状态驱动联动关系更新。
  • 如果 sourcetarget 都不指向自己,则自己不参与到联动关系链中。
  • 声明式定义方式,自然在组件实例被销毁时失效。

那么组件如何响应联动呢?重点就在这里,组件可以通过 selector(({ relates }) =>)relates 拿到自己当前的联动状态,比如:

const table = {
  componentName: "table",
  runtimeProps: ({ selector }) => {
    // relates 结构如下,对于每一个作用于自己的组件实例 ID 与最新 value 值都可以拿到
    // [{
    //   sourceComponentId: 'abc',
    //   value: '123'
    // }]
    const relates = selector(({ relates }) => relates);
    return {
      status: relates.length > 0 ? "linked" : "free",
    };
  },
};

如果我们在 runtimeProps 里使用 selector 监听 relates,就可以在联动状态变化时,驱动组件渲染,并传入联动相关状态;如果在 fetcher 里使用 selector 监听 relates,就可以在联动状态变化时,驱动组件触发查询,等等。

以后我们拓展越来越多的组件元信息回调函数,支持了 selector 之后,都可以声明式的响应 relates 变化,也就是组件可以声明式灵活响应联动,真正意义上让联动可以用在任何场景。

框架没有对联动做太多的联动内置行为,实现的都是灵活规则,虽然业务需要补全不少声明,但胜在灵活与用法统一。

描述联动行为

不同的联动可能做不同的事,比如一个输入框组件,可能同时有以下两种作用:

  • 让另一个组件查询条件增加 "where name=" 当前输入框的值。
  • 当组件的值为 "delete" 时,让画布另一个组件隐藏。

为了区分联动的功能,可以在 valueRelates 增加 payload 参数,描述该联动的目的:

const table = {
  componentName: "table",
  valueRelates: ({ componentId, selector }) => {
    return [
      {
        sourceComponentId: componentId,
        targetComponentId: selector(({ props }) => props.targetComponentId),
        // 作用为目标组件的查询筛选条件
        payload: "filter",
      },
      {
        sourceComponentId: componentId,
        targetComponentId: selector(({ props }) => props.targetComponentId),
        // 作用为目标组件是否隐藏
        payload: "hide",
      },
    ];
  },
};

然后目标组件就可以根据实际情况,在 fetcher 过滤 relatespayload="filter" 的值,在 runtimeProps 过滤 relatespayload="hide" 的值。

用持续联动实现一次性联动

每一次组件更新 value 值后,都会刷新对目标组件 relates 的位置,具体来说,会将其置顶,所以目标组件可以根据 relates 先来后到顺序判断,比如在联动效果冲突时,让排在前面的优先生效。

比如:

const table = {
  componentName: "table",
  runtimeProps: ({ selector }) => {
    // 找到最初生效的,payload 为 color 的联动,覆盖 props.color
    const relateColor = selector(({ relates }) =>
      relates.find((each) => each.payload === "color")
    );
    return {
      color: relateColor,
    };
  },
};

当另一个组件触发 value 变化时,它会排在目标组件 relates 最前面,这样的话,如果目标组件按照如上方式编写响应代码,就总会响应最后一次生效的联动。

总结

这一节介绍了如何设置联动,并引出了组件值概念。

在框架层定义抽象的组件值概念,并通过声明式或调用式对接到 state 状态或组件 props,这种抽象理念会贯穿整个框架的设计过程。相似的 valueRelates 也具有声明式能力,并将联动作用通过 selectorrelates 对象传递给组件实例使用,让联动的消费灵活度大大增加。

可视化搭建框架设计思路可能都大同小异,但可惜的是,许多搭建框架都对比如联动、查询等场景做了定制化约束,使每个框架或多或少存在着私有协议,而我在这个系列想强调的是,可以进一步抽象,让框架提供业务自由定义协议的能力,而不是提供某个固定的协议。

讨论地址是:精读《组件值与联动》· Issue #469 · dt-fe/weekly

以上就是前端可视化搭建组件值与联动实现详解的详细内容,更多关于前端可视化搭建组件值联动的资料请关注我们其它相关文章!

(0)

相关推荐

  • 前端框架ECharts dataset对数据可视化的高级管理

    目录 dataset 管理数据 dataset 管理数据 提供一份数据. 声明一个 X 轴,类目轴(category).默认情况下,类目轴对应到声明多个 bar 系列,默认情况下,每个系列会自动对应到 dataset 的每一列. option = { legend: {}, tooltip: {}, dataset: { // source: [ ['product', '2015', '2016', '2017'], ['Matcha Latte', 43.3, 85.8, 93.7], ['

  • JS前端可视化GraphQL使用详解

    目录 正文 什么是 GraphQL? 什么是 GraphiQL? 开始 结尾 正文 了解事物幕后的运作方式往往有好处,但并非总是如此. 因为不必使事情过于复杂.而可视化图形界面在处理这么一个场景中,是首当其冲的. 在本文中,我将带你了解如何使用 GraphiQL 来辅助 GraphQL 的开发. 什么是 GraphQL? 在我们谈论 GraphiQL 之前,让我们先谈谈 GraphQL. GraphQL 是一种用于应用程序编程接口 (API) 的开源数据查询和操作语言,也是一种使用现有数据完成查

  • 前端音频可视化Web Audio实现示例详解

    目录 背景 实现思路 实现 一.频率图 二.实时频率图 关于请求音频跨域问题解决方案 总结 背景 最近听音乐的时候,看到各种动效,突然好奇这些音频数据是如何获取并展示出来的,于是花了几天功夫去研究相关的内容,这里只是给大家一些代码实例,具体要看懂.看明白,还是建议大家大家结合相关API文档来阅读这篇文章. 参考资料地址:Web Audio API - Web API 接口参考 | MDN (mozilla.org) 实现思路 首先画肯定是用canvas去画,关于音频的相关数据(如频率.波形)如何

  • JS前端可视化canvas动画原理及其推导实现

    目录 前言 动画的本质 动画的实现 动画的推导 小结 前言 到目前为止我们的 fabric.js 雏形已经有了,麻雀虽小五脏俱全,我们不仅能够在画布上自由的添加物体,同时还实现了点选和框选,并且能够对它们做一些变换,不过只有变换这个操作还不够灵活,要是能够让物体动起来就好了,于是就引入了这个章节的主题:动画,以及动画最核心的一个问题,如何保证在不同的电脑上达到同样的动画效果?然后说干就干,立马开撸. 虽然我写的是系列文章,但每个章节单独食用是木问题的,所以,请放心大胆的看

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

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

  • vue父组件触发事件改变子组件的值的方法实例详解

    父组件向子组件通信 业务场景:现在我要在父组件点击一个回退按钮,这个回退会传进子组件中(子组件中有两步进程),子组件来处理. 解决方案 起初我是父组件通过props传值,但是发现只有组件第一次加载时才能传值,通过事件改变的父组件值并不会再通过过props传递,也就是说props只有加载组件时才会工作,并不会根据值改变动态操作 后面,我是通过操作dom的方法,this.$refs.children这样直接操作子组件 <ProgressTwo ref="progressTwo" v-

  • vue组件间的参数传递实例详解

    场景分析 在前端开发中,我们常常会运用到"组件库".在main入口中引入组件库,就可以很轻松的在页面中引入,并做一些基本的配置,如样式,颜色等.只需要在引入的组件中写入特定的属性,就能够定义. 举例说明 例如:element-ui组件库中使用switch开关,有个属性active-color是设置"打开时"的背景色.change事件是触发状态的事件. <el-switch v-model="value" :active-color=&quo

  • vue组件三大核心概念图文详解

    前言 本文主要介绍属性.事件和插槽这三个vue基础概念.使用方法及其容易被忽略的一些重要细节.如果你阅读别人写的组件,也可以从这三个部分展开,它们可以帮助你快速了解一个组件的所有功能. 本文的代码请猛戳 github博客 ,纸上得来终觉浅,大家动手多敲敲代码! 一.属性 1.自定义属性props prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的.写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型.默认值或自定义校验属性的值,这点在组件开发中很重要,

  • 高级前端必会的package.json字段知识详解

    目录 概览 name name命名规范 不安全的URL字符 私源npm包怎么命名? version description keywords homepage repository license author contributors files main bin scripts dependencies.devDependencies.peerDependencies private publishConfig types module unpkg sideEffects 注意点 engin

  • 为Vue3 组件标注 TS 类型实例详解

    目录 为 props 标注类型 使用 <script setup> 非 <script setup> 为 emits 标注类型 使用 <script setup> 非 <script setup> 为 ref() 标注类型 默认推导类型 通过接口指定类型 通过泛型指定类型 为 reactive() 标注类型 默认推导类型 通过接口指定类型 为 computed() 标注类型 默认推导类型 通过泛型指定类型 为事件处理函数标注类型 为 provide / in

  • vue组件间通信子与父详解(二)

    接着vue组件父与子通信详解继续学习. 二.组件间通信(子组件传值给父组件) 通过事件的方式来完成数据的传输. ①在父组件中 定义一个方法,用来接收子组件所通过事件传来的值 methods:{ recvMsg:function(msg){ //参数msg就是子组件通过事件出来的数据 } } ②绑定事件处理函数 事件一般情况 都是自定义事件 <child-component @myEvent="recvMsg"></child-component> ③在子组件触发

  • jQuery EasyUI 组件加上“清除”功能实例详解

    1.背景 在使用 EasyUI 各表单组件时,尤其是使用 ComboBox(下拉列表框).DateBox(日期输入框).DateTimeBox(日期时间输入框)这三个组件时,经常有这样的需求,下拉框或日期只允许选择.不允许手动输入,这时只要在组件选项中加入 editable:false 就可以实现,但有一个问题,就是:一旦选择了,没办法清空.经过研究,可以用一个变通的解决方案:给组件加上一个"清除"按钮,当有值是,显示按钮,点击按钮可清空值,当无值是,隐藏按钮. 2.函数定义 定义JS

  • vue和better-scroll实现列表左右联动效果详解

    一.实现思路 (1)实现上是左右分别一个better-scroll列表 (2)利用计算右侧列表每一个大区块的高度来计算左侧的位置 二.实现 1.实现左右两个better-scroll (1)dom结构(better-scroll要求,会把最外层dom的第一个子元素作为要滚动的区域) 左边滚动列表dom <div class="menu-wrapper" v-el:menu-wrapper> <ul> <li v-for="item in good

  • Android入门教程之组件Activity的生命周期详解

    目录 返回栈 Activity 状态 1. 运行状态 2. 暂停状态 3. 停止状态 4. 销毁状态 Activity 的生存期 onCreate() onStart() onResume() onPause() onStop() onDestroy() onRestart() 完整生存期 可见生存期 前台生存期 Activity 回收处理 返回栈 Android 中的 Activity 是可以层叠的,我们每启动一个新的 Activity,就会覆盖在原有的 Activity 之上,然后点击 Ba

随机推荐