组件通过 <Canvas />
渲染在画布上,内容完全由组件树 componentTree
驱动,但也有一些情况我们需要把某个组件实例渲染到组件树之外,比如全屏、置顶等场景,甚至有些时候我们要渲染一个不在组件树中的临时组件,却要拥有一系列画布能力。
为了让组件渲染更灵活,我们暴露出 <ComponentLoader>
API:
import { createDesigner } from 'designer'
const { Designer, Canvas, ComponentLoader } = createDesigner()
const App = () => {
return (
<Designer componentTree={/** ... */}>
<Canvas />
{/** 任意位置,甚至 Canvas 的组件实例内使用 ComponentLoader 加载任意组件 */}
<ComponentLoader />
</Designer>
)
}
组件加载器有三种用法:按组件 ID 加载、按组件树路径加载、动态组件,下面分别介绍。
按组件 ID 加载
将组件树上的某个组件渲染到任何地方,即一个组件实例渲染到 N 个地方,实例级别信息共享,渲染为 N 份:
代码语言:javascript复制<ComponentLoader componentId="input1" />
如上例子,将组件 ID 为 input1
的组件渲染到目标位置。
甚至可以在组件内套组件,比如我们定义一个容器组件,内置渲染 ID 为 input1
的子组件:
const container: ComponentMeta = {
componentName: 'container',
// 组件 props 会自动注入 ComponentLoader
element: ({ ComponentLoader, children }) => {
return (
<div>
<ComponentLoader componentId="input1" />
{children}
</div>
)
}
}
当该组件 ID 在组件树中被移除时,<ComponentLoader componentId="input1" />
返回 null
。
按组件树路径加载
如果组件在组件树上没有 ID,或者你希望固定渲染某个位置的组件,而无论组件树如何变化,那么就可以采用按组件树路径的加载模式,将 componentId
替换为 treePath
即可:
<ComponentLoader treePath="children.0" />
如上例子,渲染的是 componentTree
根节点 children.0
位置的子组件,同样,但组件不存在时返回 null
。
动态组件
如果要渲染一个不存在于组件树的组件实例,还可以这么用 <ComponentLoader />
:
<ComponentLoader standalone componentName="card" />
即添加 standalone
表示它为一个 “孤立” 组件,即不存在于组件树的组件,以及 componentName
指定组件名。
之所以不需要指定 componentId
,是因为每个 ComponentLoader
此时都是一个唯一的实例,在 designer
内部会自动分配一个固定的组件 ID。
这么设计非常灵活,但实现起来难度是有一些,主要注意两点:
- 动态组件不存在于组件树,但我们之前设计在组件元信息的所有功能都要可以响应,这就要求框架代码不能依赖组件树产生作用,而是将所有组件独立存储计算,包括组件树上的,以及动态组件。
- 性能,独立组件加载器之间的执行并无关联,因为框架本身为响应式,为了防止频繁刷新或频繁计算需要设计一套自动批处理机制,类似 React 自动 batch 的实现。
对于动态组件,我们还可以传递更多参数:
代码语言:javascript复制<ComponentLoader standalone componentName="chart" props={{ color: 'red' }}>
<button>click</button>
</ComponentLoader>
如上例子,我们传了额外 props
属性,以及一个子元素给 chart
组件实例。
特别的,如果传递了 componentId
,可以将该动态组件的 ID 固定下来,方便进行联动:
<ComponentLoader standalone componentName="chart" componentId="abc" />
但动态组件也有一些限制,如下:
- 该方式渲染的组件元信息定义的
defaultProps
、props
不会生效,因为不存在于组件树中。 - 该组件无法通过
deleteComponent
删除,也无法通过setProps
、setComponent
等修改,因为渲染完全由父组件控制,而不由组件树控制。 - 不能用
setParent
改变这种组件的位置,因为其位置在代码中被固定了。
总结
其实 <Canvas />
根节点本质上等价于 <ComponentLoader treePath="" />
,即从根节点开始渲染一个组件实例。
所以提供 ComponentLoader
势必会让业务能力更灵活,在任意位置渲染组件,甚至渲染一个不存在于组件树的动态组件。
讨论地址是:精读《ComponentLoader 与动态组件》· Issue #482 · dt-fe/weekly