乐趣区

关于javascript:可视化搭建内置-API

在设计好画布与组件数据流体系后,实践上主体性能曾经实现,但不足不便易用的 API,所以还须要内置一些状态与办法。

然而内置状态与办法必须寻求业务的最大公约数,极具抽象性,增加需谨慎。

接下来咱们从必须有与倡议有的角度,看看一个可视化搭建须要内置哪些 API。

状态

状态是可变的,援用形式有如下两种。

第一种在任意 React 组件内通过 useDesigner 拜访,当状态变动时会触发所在组件重渲染:

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

第二种在任意组件元信息内通过 selector 拜访,当状态变动时会触发不同行为,比方在 runtimeProps 会触发组件重渲染,在 fetcher 会触发从新查问:

const tableMeta = {
  /** ... */
  runtimeProps: ({selector}) => {const { componentTree} = selector(({state}) => ({componentTree: state.componentTree,}));
    return {componentTree};
  },
};

componentTree

  • 评估:必须有
  • 类型:ComponentInstance

形容残缺组件树 JSON 构造。在非受控模式下,组件树就存储在 <Designer /> 实例外部,而受控模式下,组件树存储在内部状态。

但咱们容许这两种模式都能够拜访此状态,这样在开发可视化搭建利用的过程中,就不必关怀受控或非受控模式了,即一套代码同时兼容受控与非受控模式。

selectedComponentIds

  • 评估:倡议有
  • 类型:string[]

定义以后选中组件实例 id 列表。

尽管这个状态业务也能够定义,但选中组件在可视化搭建是一种常见行为,当前定义插件、自定义组件兴许都会读取以后选中的组件,如果框架定义了此通用 key,那么插件和自定义组件就可无缝联合到任意业务代码里。反之如果在业务层定义该状态,插件或者自定义组件也不晓得如何规范的读取到以后选中的组件。

canUndo, canRedo

  • 评估:倡议有
  • 类型:boolean

形容以后状态是否能撤销或重做。

该状态须要联合内置办法 undo() redo() 一起提供,属于“有了更好”的状态。但有时候也会产生困扰,比方你的利用分了多个 sheet,每个 sheet 内是一个画布实例,而你心愿撤销重做能够跨 sheet,那就不适宜用单实例提供的办法了。

办法

状态援用不可变,援用形式有如下两种。

第一种在任意 React 组件内通过 useDesigner 拜访,它不会变动,因而不会导致组件重渲染:

const {addComponent} = useDesigner();

第二种在任意组件元信息内通过回调拜访:

const tableMeta = {
  /** ... */
  runtimeProps: ({addComponent}) => {},};

getState()

  • 评估:必须有
  • 类型:() => State

获取利用全副状态,包含内置与业务自定义。

setState()

  • 评估:必须有
  • 类型:(state: State) => void

更新利用全副状态,包含内置与业务自定义。

getComponentTree()

  • 评估:必须有
  • 类型:() => ComponentInstance

返回以后组件树。

并不是有了 componentTree 状态就高枕无忧了,很多回调函数并不依赖组件树重渲染,而仅仅在触发时获取其瞬时值必须调用此办法。

尽管该办法肯定水平上能够用 getState().componentTree 代替,但组件树概念太重要了,以至于独自定义一个办法不会减少了解老本。另外在受控模式下,getState().componentTree 不肯定等价于 getComponentTree(),因为前者是从 <Designer /> 拿组件树,而后者间接申请内部状态最新的组件树,当组件树受控模式没有及时触发渲染同步时,后者值会比前者更新。

setComponentTree()

  • 评估:必须有
  • 类型:(callback: (now: ComponentInstance) => ComponentInstance) => boolean

更新以后组件树。

在非受控模式下等价于 setState() 批改 componentTree,但在非受控模式下,会间接透传到内部状态,间接批改一手组件树,因而极其状况下体现更稳固。

addComponent()

  • 评估:必须有
  • 类型 (componentInstance, parentIdPath?, index?, position?) => void

增加组件实例。

基于 setComponentTree() 实现,但因为其太常见且用意较为简单,抽成一个独立函数还是很有必要的。

  • componentInstance 必选,默认把组件实例增加到根节点的 children 地位。
  • parentIdPath 可选,形容要增加到的父节点 ID,当父节点没定义组件 ID 时,也能够用例如 children.0 这种组件树门路代替,所以名称不叫 parentId,而是 parentIdPath
  • index 可选,形容要增加到父节点子元素下标,比方增加到 children 的第几项。
  • position 可选,形容要增加到父节点 children 还是 props.header 等地位,毕竟组件实例并不只有 children 一个中央。

deleteComponent()

  • 评估:必须有
  • 类型:(componentIdPath: string) => boolean

删除组件实例。

基于 setComponentTree() 实现,但同理太罕用,所以独自提供。

这里还有个细节,就是 componentIdPath 指可传组件 ID,也可传组件树门路,而真正删除必定要从树上删,框架外部为了疾速从组件 ID 定位到 treePath,保护了一个映射表,因而应用该函数无论何时都是 O(1) 的工夫复杂度。

getComponent()

  • 评估:必须有
  • 类型:(componentIdPath: string) => ComponentInstance

查问组件实例。

基于 getComponentTree() 实现。“增删”都有了,“查”还能没有吗?

setComponent()

  • 评估:必须有
  • 类型:(componentIdPath, callback) => boolean

批改组件实例。

基于 setComponentTree() 实现,“增改查”都有了,就差一个“改”了。

setProps()

  • 评估:倡议有
  • 类型:(componentIdPath, callback) => boolean

批改组件实例的 props。

基于 setComponent() 实现,因为批改组件 props 属性比批改整个组件实例常见,倡议实现。

getProps()

  • 评估:倡议有
  • 类型:(componentIdPath) => any

获取组件实例的 props。

基于 getComponent() 实现,同理,调用可能比 getComponent() 更常见,因而倡议实现。

getComponents()

  • 评估:倡议有
  • 类型:() => ComponentInstance[]

获取全量组件实例数组。

因为组件树是树状构造,业务除了用递归形式遍历外,还能够提供这种获取打平模式的组件树以备不时之需。

getParentId()

  • 评估:必须有
  • 类型:(componentIdPath: string) => string

获取组件的父组件 ID。

认为 componentTree 为树状构造,所以间接从组件实例上找不到父节点,因而提供一个疾速找父节点的函数是十分必要的。

当然框架外部实现寻找父节点必定不会用遍历,而是提前解析组件树时就建设好关联映射表,所有内置办法工夫复杂度都是 O(1) 的。

getParentBy()

  • 评估:倡议有
  • 类型:(componentIdPath: string, finder: (parent: ComponentInstance) => boolean) => string

始终向上寻找父节点,直到找到为止。

基于 getParentId() 实现,不便业务向上寻找符合条件的父节点。

setParent()

  • 评估:必须有
  • 类型:(componentIdPath, parentIdPath, index, position) => boolean

调整某个组件的父节点。参数和 addComponent() 很像,只是把第一个从组件实例改为了组件 ID,参数含意雷同。

当画布波及组件跨父节点挪动时,这个办法就显得很要害了,尽管底层也是基于 setComponentTree 实现的。一个比较复杂的场景是,当组件跨节点挪动时,在组件树上操作还是比较复杂的,因为移除 + 增加无论先做哪个,都会导致组件树变动,从而导致后一个操作地位可能谬误。如果每次都从新寻址性能会较差,如果想用聪慧的办法绕过,逻辑还是比较复杂的,因而有必要内置该办法。

setComponentMeta()

  • 评估:必须有
  • 类型:(componentName: string, componentMeta: ComponentMeta) => void

更新组件元信息。

提供这个办法其实对框架的挑战比拟大,在提供很多生命周期的状况下,随时可能产生组件实例的更新,要保障整体逻辑合乎预期,须要认真设计一下。

getComponentMeta()

  • 评估:必须有
  • 类型:(componentName: string) => ComponentMeta

获取组件元信息。

既然能够注册组件元信息,就能够获取它。留神通过 <Designer /> 受控或者非受控模式注册,或者间接调用 setComponentMeta 注册的组件元信息都应该能够失常获取到。

getComponentMetas()

  • 评估:倡议有
  • 类型:() => ComponentMeta[]

批量获取所有已注册的组件元信息。

说不定业务会有什么特地的用处,倡议提供。

clearComponentMetas()

  • 评估:倡议有
  • 类型:() => void

清空所有组件元信息。

说不定业务会有什么特地的用处,倡议提供。

setSelectedComponentIds()

  • 评估:倡议有
  • 类型:(ids: string[]) => void

批改内置状态 selectedComponentIds

如果你提供了 selectedComponentIds 这个内置状态,那提供对应的批改办法就是强烈建议了。尽管也可通过 setState() 更新 selectedComponentIds Key 来实现。

getTreePath()

  • 评估:倡议有
  • 类型:(componentIdPath: string) => string

依据组件 ID 查找在组件树上的门路。

兴许业务想要本人操作组件树,那么框架提供依据组件 ID 找到组件树门路的办法就挺适合。

undo(), redo()

  • 评估:倡议有
  • 类型:() => void

撤销,重做。

如果提供了 canUndocanRedo 内置状态,那么肯定要提供 undo()redo() 内置函数。

getMergedProps()

  • 评估:倡议有
  • 类型:(componentIdPath: string) => any

返回组件最终混合后的 props。

因为组件 props 可能来自组件树,也可能来自 runtimeProps,为了避免傻傻分不清,因而规定 getProps() 仅获取组件树上序列化的 props,而 getMergedProps() 获取了蕴含 runtimeProps 解决后的最终 props。

getComponentDom()

  • 评估:倡议有
  • 类型:(componentIdPath: string) => HTMLElement

依据组件 ID 获取 DOM 实例。

框架最好通过一些技巧,让组件即使不必 forwardRef 也能拿到 DOM,那么组件只有存在 DOM,就能够通过该办法拿到,十分不便。

afterDomRender()

  • 评估:倡议有
  • 类型:(componentIdPath: string, callback: () => void) => Promise

当组件 ID 的 DOM 实例挂载后,执行 callback

因为组件 DOM 依赖渲染,所以不能保障 getComponentDom 时 DOM 真的实现了渲染,因而能够将机会放在 afterDomRender() 后,保障肯定能够拿到 DOM。

总结

这一章咱们设计了内置 API,设计思路总结如下:

  1. 从组件树这个外围概念散开,设置了必要的 API,以及一些逻辑简单,或者应用很不便的举荐 API。
  2. 尽管组件树是树状构造,但内置 API 须要思考易用性,所有操作都以组件 ID 作为参数,在外部实现时转化为操作组件树,并内置好 O(1) 工夫复杂度的优化措施。
  3. 外围 API 只有寥寥几个,其余 API 都以便利性为目标提供,且都以外围 API 为根底实现,这样框架外围会更稳固,框架大部分 API 只是一种实现规定,业务利用外围 API 领有更大的实现自在。

探讨地址是:精读《可视化搭建内置 API》· Issue #467 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

退出移动版