搜索
查看: 68|回复: 0

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

[复制链接]
发表于 2023-5-10 12:04:54 | 显示全部楼层 |阅读模式
这篇文章主要为大家介绍了前端可视化搭建组件值与联动实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
- I6 D, C6 ]0 m2 }% d6 y
! l! T6 ?& H4 R( w' d6 X

3 {( ?" w% I6 z8 @4 p! x7 b
+
目录- t- E. f# a& ]1 s3 j- |& h
; R+ a9 U- T1 Y" g4 a; }: ^; O
正文

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

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

  • 多对多关系:即一个组件可以同时被多个组件联动;多个组件可以同时联动一个组件。
  • 一次性与持续性:一次性事件可以被覆盖,持续性事件会同时生效,且要考虑叠加关系。
    , U4 x% r6 }/ w+ Y( K

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

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

! [2 d% ?) h" M) X
组件值

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

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

  1. const table = {, g3 h+ D  S1 T: W/ L4 I  z: @
  2.   componentName: "table",
    - f6 h$ d" M4 t9 A. U6 p* s! s
  3.   runtimeProps: ({ componentId, setValue }) => ({
    0 s! r% a- @" o. w* M" q& ~
  4.     // 给组件注入 onChange 函数,在其触发时更新当前组件实例的组件值
    , Q  ^0 z# _. z6 }
  5.     onChange: (value) => setValue(componentId, value),! b/ ?5 j6 Q5 b& Z
  6.   }),& a& [4 x9 I* q0 G1 \3 D5 z
  7. };
复制代码

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

  1. const table = {9 {. L! `4 l! r* W; w9 I
  2.   componentName: "table",
    ) i) P) V, \% ?  h- M
  3.   // 声明 value 的值为组件 props.value 的返回值,并随着组件 props.value 的更新而更新" l! c6 ]3 O1 s- b
  4.   value: ({ selector }) => selector(({ props }) => props.value),# I! w/ V6 i7 k) K+ f. v) T
  5. };
复制代码
& f3 o4 s8 |: l# T

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

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

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

  1. // 组件值结构% u3 Q: ]; U" x8 W/ b0 Z4 d
  2. {
    8 @3 n4 V0 I* Z1 I
  3.   // 组件内输入框的值
    ( q% \* F6 S9 l2 g! x
  4.   text: '123',5 F2 \. ]5 v1 Y; m+ m9 n
  5.   // 组件内按钮被按下的次数. Q1 W9 i5 k2 R6 Y0 Z% S
  6.   buttonClickTimes: false7 _8 j6 r7 b' C  Y
  7. }
复制代码

2 v5 S$ L8 R# D1 m

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

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

- }9 M4 z6 _+ J' v
值联动

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

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

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

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

  1. const table = {
    9 f" ~/ i2 H- y$ y* ^% x
  2.   componentName: "table",
    , ?# D' q& Y$ G& ?. I
  3.   valueRelates: ({ componentId, selector }) => {
    / r+ d9 V/ u3 F' [6 a9 M: Y; N
  4.     return [- A: L2 b/ f: o+ A/ P: m0 E* d
  5.       {/ l$ ]/ b* I. Y+ T& d- w+ p4 k" ]2 F6 c
  6.         sourceComponentId: componentId, // 自己为触发源
    5 W6 U, {; M! r) a9 N+ R; E
  7.         targetComponentId: selector(({ props }) => props.targetComponentId), // 目标组件 ID 为 props.targetComponentId) v3 x+ q/ _1 Z9 n" K. O( W) I. @
  8.       },2 a+ u5 h! [0 A* d& \# P0 ?
  9.     ];
    6 t$ D! }3 s' W  x+ k& Q
  10.   },
    0 k* o2 A9 _$ \0 a" K% B
  11. };
复制代码
* I8 ^8 A0 v$ ?; b3 Q

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

  • 可以在任意组件实例定义多个联动关系,自然可以实现多对多联动。
  • valueRelates 引入 selector 可以响应 state 或 props 的变化,可以由任意状态驱动联动关系更新。
  • 如果 source 与 target 都不指向自己,则自己不参与到联动关系链中。
  • 声明式定义方式,自然在组件实例被销毁时失效。6 e& H; L! U) \2 [9 F

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

  1. const table = {
    ! ^8 k4 Y! i2 ^; b
  2.   componentName: "table",
    1 J* S# [- b; D) @9 ]+ b
  3.   runtimeProps: ({ selector }) => {* ~$ f  Z1 q1 i$ ~2 D5 ]. r( S
  4.     // relates 结构如下,对于每一个作用于自己的组件实例 ID 与最新 value 值都可以拿到
    7 C( R1 z2 r6 F3 ~5 j
  5.     // [{) m& s! K) R) z% t$ I
  6.     //   sourceComponentId: 'abc'," u/ X. h8 i' X1 b$ x* o) [
  7.     //   value: '123'8 U0 J; S- `1 G1 h" y* c
  8.     // }]4 q! s7 K6 k, Z
  9.     const relates = selector(({ relates }) => relates);, C4 {" Y' ]" }7 T
  10.     return {
    7 x. M9 U( ~" X, S
  11.       status: relates.length > 0 ? "linked" : "free",) `/ b8 p. ?3 |0 ]" a2 p6 T/ _$ s' B
  12.     };
    % K7 g! ^8 [7 s: T, s
  13.   },
    $ y/ }0 ~% v8 C" {- J, x2 a
  14. };
复制代码
1 X% \; Y: v2 H6 w8 f

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

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


3 _3 F3 W' Z2 E

; W, `# r/ I  t: |+ }9 k! L/ G

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


* ?7 w4 S# |/ v( y描述联动行为

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

  • 让另一个组件查询条件增加 "where name=" 当前输入框的值。
  • 当组件的值为 "delete" 时,让画布另一个组件隐藏。+ h4 I% w! S$ M. ~4 o. ~

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

  1. const table = {
    2 T+ e5 Z2 c' i9 ?3 J3 m8 ?
  2.   componentName: "table",: Y2 `% W6 W- o  V8 h5 U
  3.   valueRelates: ({ componentId, selector }) => {+ y! G+ [" D( {5 s& x
  4.     return [
    ! J/ L, U! B9 v# a7 y; R, m
  5.       {7 `# U$ L! a9 A4 f; e8 E
  6.         sourceComponentId: componentId,, Q8 @/ d% d: [
  7.         targetComponentId: selector(({ props }) => props.targetComponentId),
    . W% l) ?" h# E; _
  8.         // 作用为目标组件的查询筛选条件' {4 m& b$ e7 z% A" O1 J1 l# t
  9.         payload: "filter",6 J/ W- p" f  Q3 h* |
  10.       },
    . P% E) ?: e. E+ i7 Y
  11.       {
    5 B; S. x7 k0 y% J/ B" y6 h
  12.         sourceComponentId: componentId,+ J3 [. |# Y& u. L& F: k
  13.         targetComponentId: selector(({ props }) => props.targetComponentId),, B& {6 f) b! P1 e# Q; R
  14.         // 作用为目标组件是否隐藏6 k0 S) ?6 ?/ D  G5 u4 E
  15.         payload: "hide"," l/ ^! f) g/ h+ p6 r- E" e- M* x
  16.       },
    , u. s& M! i* Z0 S4 r
  17.     ];
    9 P4 V, Q) ?# T$ _2 a4 X
  18.   }," K! O3 N8 v) O* C
  19. };  [& u! O2 b9 j+ v+ Q% ^( s
复制代码

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

# |; b3 s! x1 c* ^
用持续联动实现一次性联动

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

比如:

  1. const table = {( o4 i7 A# W# U5 u
  2.   componentName: "table",2 ^/ J+ }: A+ W3 [) @& N
  3.   runtimeProps: ({ selector }) => {) p( q& `; y) E) t7 F& p5 _9 y
  4.     // 找到最初生效的,payload 为 color 的联动,覆盖 props.color
    0 G1 V! a0 T0 A! d  Q
  5.     const relateColor = selector(({ relates }) =>2 u" v$ ]8 E# e: N
  6.       relates.find((each) => each.payload === "color")  k) s4 x+ O6 u8 Y
  7.     );- c: c3 r1 T" K' F6 E' W) f
  8.     return {4 |) m5 `3 K( e4 \; s  g2 G2 x) ?
  9.       color: relateColor," r) T% m9 N! b/ B- F! Q
  10.     };
    # \! n8 P' S- q7 _6 R
  11.   },
    9 `. C# ]" B% I' ]4 M/ W! N
  12. };
复制代码
8 V8 r; ]" Z, {+ M) X& r; \

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


4 g# t. p9 j! j; A- S5 {3 q总结

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

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

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


2 H. R  h; I0 a' a5 a( v' K2 |7 X. W4 w
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 马上注册

本版积分规则

热议作品
精华帖子排行
精彩推荐

虾皮社区,成立十年了!

站长自己也搞不懂想做个什么,反正就是一直在努力的做!

Copyright © 2007-2019 xp6.org Powered by Discuz

QQ|小黑屋|手机版|Archiver|虾皮社区 ( 鲁ICP备13006813号-1 ) 鲁公网安备 37021102000261号 |网站地图
返回顶部 返回列表