@fluid-experimental/tree

中文文档
3.12.0发布于 2026/11/11MITTypeScriptES Module近 30 天下载 2273

分布的树

中文使用文档
文档目录30 节
  1. 使用 Fluid 框架库
  2. 安装
  3. API 文档
  4. 树抽象
  5. 定义
  6. 标识符
  7. 特质
  8. 有效载荷
  9. 示例树
  10. 创建共享 Tree
  11. 阅读
  12. 读取树的当前状态
  13. 阅读任意修改树
  14. 听树上的变化
  15. 编辑
  16. 改变原子
  17. 冲突
  18. 制约因素
  19. 更改拒绝
  20. 摘要
  21. 历史
  22. 历史虚拟化
  23. 客户最低要求
  24. 支持的运行时间
  25. 支持的工具
  26. 模块分辨率
  27. 模块格式
  28. 贡献准则
  29. 帮助
  30. 商标

@fluid-experental/tree (英语)

重要:这个DDS不再活跃地发展. 截至"流体框架"2.0发布时,可通过"活化"(Fluid Framework's 2.0 release)提供一棵已成品的树 DDS @ fluid框架/ 树。 。 。 . 请使用图书馆代替.

这个包裹是实验性的. API可以不经通知而更改.

在生产情景中不要将其作为依赖. 我们不保证这一一揽子计划或其API的稳定.

使用 Fluid 框架库

对于依赖流体框架库的公共API,我们建议 ^ (caret)版本范围. 例如,使用 ^1.3.4。 。 。 .

依赖不稳定的API,例如: beta API,我们推荐一个更具限制性的版本范围. 例如,使用一个 ~ 版本范围.

安装

运行此命令以安装软件包 :

npm i @fluid-experimental/tree

API 文档

读取 @fluid-experental/tree (英语) API 文档 https://fluidframework.com/docs/apis/tree (中文(简体) )。 。 。 .

A 类 流线 共享对象 树有:

  • 交易编辑和快照隔离
  • 强大的节点身份
  • 高质量自动合并分辨率
  • 包含 Move 的灵活操作集
  • 历史检查、操纵和元数据

对树的修订(见 编辑日志 财务报告和审定财务报表 树视图)是从 Edits 的序列创建的.

从语义上来说,树的目前状态被定义为:

这个 初始树,由所有编辑按顺序修改.

编辑顺序为:

  1. 所有经过确认的编辑,按照Fluid协商一致商定的顺序进行.
  2. 所有本地编辑( Fluid 尚未承认) 按应用顺序进行 .

开始

树抽象

用于 SharedTree 由四个主要属性的节点组成: a 定义,一个 身份零或以上 特征,可选 有效载数 包含任意序列数据.

定义

节点的定义传达出节点的语义意义. 它通常用来将节点与元数据连接起来,例如用于此树代表的图案.

标识符

一个节点的标识符是一个与该节点相关的独特的密钥. 标识符可以用来取出当前视图"共享Tree"中的节点,并提供了在进行编辑到树上时参考已存在的节点的方法.

特质

特质是父节点下面的儿童节点的序列. 每个特征都由标签确定,可能包含一个或多个儿童。 组织一个节点的孩子在特征下(而不是像许多树所做的那样在单一的自由形式列表中),允许更多的直观地构建文件.

// A parent node with three traits, labelled "name", "employees" and "products"
{
    definition: 'Company',
    identifier: 42,
    traits: {
        name: [{...}] // Traits may contain just one child node...
        employees: [{...}, {...}, {...}], // ...or many children
        products: [{...}, {...}]
    }
}

有效载荷

一个节点的有效载荷是一个任意状态的袋子,只有一个要求:它必须是JSON-序列可分. 有效载荷允许树存储无法由节点本身有效编码的数据(例如字符串或数字)

示例树

考虑由二维空间中的点(4,9)组成的文档。 一种方式是,将文档编码成树状格式,由 SharedTree 也许是这样的:

// These definitions conceptually refer to *all* points/numbers in existence.
const pointDefinition = '3781c3b1-41e6-43c5-9ffd-13916071d4dc';
const numberDefinition = '3e5a1652-983f-4533-bb59-130ac8f3714e';

// These identifiers refer to *the particular* point/number nodes in the tree below.
const pointIdentifier = 100;
const xIdentifier = 101;
const yIdentifier = 102;

const pointDocument: Node = {
    definition: pointDefinition
    identifier: pointIdentifier,
    traits: {
        x: {
            definition: numberDefinition,
            identifier: xIdentifier,
            payload: 4
        },
        y: {
            definition: numberDefinition,
            identifier: yIdentifier,
            payload: 9
        }
    }
};

请注意,这个例子并不是要被逐字地当作有效的代码——它为了简单起见,用有效载荷的表示来欺骗一点,并且不能有效地构建节点的识别符. 然而,对树木结构来说,这是真实的.

创建共享 Tree

共享 Tree 遵循典型 D. 流畅的DDS公约 并可以用流体运行时实例构建 :

const tree = SharedTree.create(runtime);

创建后,树将包含一个单一的节点: 首选项。所有 共享Trees 开始于此初始节点作为他们的根节点,这些节点不能被删除或移动。 它为在它下面插入新的节点提供了一个锚.

阅读

共享 Tree 提供 树视图s是树在给定修订时不可改变的快照。 这些视图可以针对树的当前状态(即应用了所有已知的编辑后的最新状态)生成,也可以在树的编辑史进行具体修订后为树的状态生成.

读取树的当前状态

这个 currentView 属性在 PowerTree 上是最容易获取最新修订版视图的方法.

function getChildrenUnderTrait(sharedTree: SharedTree, parentId: NodeId, traitLabel: TraitLabel): TreeViewNode[] {
    // Get the most up-to-date view of the tree at this moment.
    const view = sharedTree.currentView;
    // Get the IDs of children in some trait with `getTrait`:
    const childIds = view.getTrait({ label: traitLabel, parent: parentId });
    // Get the node for a given ID with `getViewNode`:
    return childIds.map((id) => view.getViewNode(id));
}

请注意: view 永远不会改变,即使共享树应用或收到更多的编辑。 如果您想要在追加编辑后更新视图, 您必须拨打 sharedTree.currentView 再来一次.

阅读任意修改树

如果您想要在当前视图之前检查某个状态的树, 共享 Tree 提供了 日志编辑器 在具体修订时征求意见.

function getViewAfterEdit(sharedTree: SharedTree, editId: EditId): TreeView {
    // First, find which revision corresponds to a given edit
    const revision = sharedTree.edits.getIndexOfId(editId);
    // Then, ask the logViewer to create a view at that specific revision
    return sharedTree.logViewer.getRevisionViewInSession(revision);
}

听树上的变化

共享 Tree 曝光一个 EditCommitted 当本地或远程编辑被应用到树上时,会点火的事件。 当心! 这个API有一个严重的陷阱。 无法保证编辑由 EditCommitted 是树上最近编辑的日志。 这是因为本地编辑总是被认为是日志中最新的,但也可能有来自其他客户端的远程编辑 先后顺序 不过 发现于 本地编辑。 因此,正在使用 EditId 编辑 EditCommitted 查询 LogViewer 需要非常小心的注意 并且容易发生错误.

没错 强烈的 鼓励使用 Checkout 相反,它提供了一个更清洁、更安全的API,用于倾听树的变化。 欲了解更多信息,请参见下方"使用取出".

编辑

对于简单的编辑(交易性不重要的一个), SharedTree 提供如下内容的便利和必要的API:

const view = sharedTree.currentView;
sharedTree.applyEdit(Change.insertTree(fooNode, StablePlace.atStartOf({ parent: view.root, label: 'foo' }));
sharedTree.applyEdit(Change.move(barNode, StablePlace.after(fooNode.identifier)));

这将插入 fooNode 在根节点下方的"foo"特征开始移动 barNode 从树上到树后 foodNode 在"foo"的特质. 每项行动将自行进行 Edit,这是 SharedTree'事务原子 (一个修订版对应一个编辑).

一个 Edit 属于 SharedTree它规定了如何通过修改顺序修改文档(见 更改类型。每次编辑,在应用到文档的版本(一个 TreeView)时,都会产生新版本的文档.

一旦编辑得到Fluid服务的认可(因此它有一个序列号,并将被收录在摘要中),它所应用的文档版本是固定的:它不会被应用到它之前编辑的版本以外的任何版本. 可能有一些操作会根据已有的编辑创建出新的编辑并应用到不同的上下文中(例如撤销),但这些在逻辑上被认为是新的编辑.

“移动”和“删除”操作具有额外的复杂性,即需要指定地点(StableRange(s) 在 SharedTree 应当移动(或分别删除)。 A 类 StableRange 包含开始 StablePlace 和结束 StablePlace。 。 。 . StablePlaces不是节点,而是可以插入节点的地方。 每个地方由"锚"组成,它要么是特质,要么是另一个节点.

说我们要删除 fooNode 我们在上面插入。 有四种方法我们可以说明 StableRange 删除在不同时编辑的情况下等同的:

const trait = { parent: initialTree, label: 'foo' };
const stableRange1 = StableRange.from(StablePlace.atStartOf(trait)).to(StablePlace.atEndOf(trait));
const stableRange2 = StableRange.from(StablePlace.atStartOf(trait)).to(StablePlace.after(fooNode));
const stableRange3 = StableRange.from(StablePlace.before(fooNode)).to(StablePlace.atEndOf(trait));
const stableRange4 = StableRange.from(StablePlace.before(fooNode)).to(StablePlace.after(fooNode));

一旦考虑同时编辑,则不同的方式锁定此选项 StableRange 可能会影响此编辑是否与其他编辑冲突.

也注意到其中一些规格有一些更方便的简称。 见 StableRange 需要更多信息的文档.

改变原子

以上一项或两项呼吁 tree.applyEdit 可能无法适用。 将更改组合到树上,以便所有更改同时成功和适用,或者如果其中任何更改失败,则无一适用。 实现这一点有三种办法.

将所有更改一起应用

上面的两个编辑可以简单地将它们的修改压缩成一个单一的编辑.

sharedTree.applyEdit([
    ...Change.insertTree(fooNode, StablePlace.atStartOf({ parent: view.root, label: 'foo' }),
    ...Change.move(barNode, StablePlace.after(fooNode.identifier))
]);

使用交易

只有知道所有的变化,上述方法才有可能。 客户端则可能希望逐渐建立一系列变化,并在视图上观察其影响,但等待日后在编辑中提交. A 类 交易 是一个轻而易举的工具.

const transaction = new Transaction(sharedTree);
transaction.apply(Change.insertTree(fooNode, StablePlace.atStartOf({ parent: view.root, label: 'foo' }));
const viewAfterFirstEdit = transaction.currentView; // This is the view after applying the above change. It is not the current view of the SharedTree (which has not had the above change applied).
transaction.apply(Change.move(barNode, StablePlace.after(fooNode.identifier)));
transaction.closeAndCommit(); // If all changes were successful, this will apply them together as a single edit to the SharedTree. The transaction is now "closed" and any future changes will be ignored.

如果对交易应用的任何更改失败,交易将自动关闭.

使用检出

A 类 离职 类似于一个 Transaction 因为它应用了随时间而变化,但具有一些额外的特征:

  • 在一个单一的寿命期内可以提交多个编辑 Checkout。 。 。 .

    const checkout = new EagerCheckout(sharedTree);
    checkout.openEdit();
    checkout.applyChanges(Change.insertTree(fooNode, StablePlace.atStartOf({ parent: initialTree, label: 'foo' })));
    checkout.applyChanges(Change.move(barNode, StablePlace.after(fooNode.identifier)));
    checkout.closeEdit(); // This submits the changes to the tree in an edit
    checkout.openEdit();
    checkout.applyChanges(Change.delete(barNode.identifier));
    checkout.closeEdit(); // This submits another edit to the tree
    

    如果更改未能适用, closeEdit 将投出一个错误。 侦破这个案子 getEditStatus 和电话 abortEdit 换句话说.

  • 取出可以重置一个正在编辑中的基准 .

    const checkout = new EagerCheckout(sharedTree);
    checkout.openEdit();
    checkout.applyChanges(Change.insertTree(fooNode, StablePlace.atStartOf({ parent: initialTree, label: 'foo' })));
    // ... Edits are applied to the tree (e.g. by other clients)
    checkout.rebaseCurrentEdit(); // Rebases the current changes in this edit to the SharedTree's current view.
    checkout.applyChanges(Change.move(barNode, StablePlace.after(fooNode.identifier)));
    checkout.closeEdit(); // This submits the changes to the tree in an edit
    
  • 检查执行可以选择它们多久一次将其视图与基础同步 SharedTree 当不是编辑时(即快照隔离)。 如果您想要尽可能频繁同步( 可能这样) , 请使用 EagerCheckout. 如果你喜欢控制同步的节奏, LazyCheckout 可以通过 Checkout.waitForPendingUpdates如果需要更复杂的政策,客户可以自行离职.

  • 结账提供 viewChange 事件: 一个方便的 API 来观察对“ 检查” 的修改 currentView预计客户会订阅 viewChange 活动,并相应更新其应用 viewChange 发生。 这几乎总是比低层更可取的 SharedTree.EditCommitted 事件,因为它让应用程序作者直接对树的内容的更改作出反应,而不是需要意识到导致这些更改的基础编辑。 对大多数应用来说 viewChange 因为它遵循了关于何时开火的特别规则. viewChange 在下列情况下被解雇:

    • 如果有的话 没有 此取出( 即不介于两者之间) 的当前编辑 openEdit() 财务报告和审定财务报表 closeEdit(), (中文(简体) ), viewChange 被开除...
      • 当编辑直接应用到 离职 本地客户端
      • 以 EagerCheckout 当编辑被应用到 共享主题 本地客户端或远程客户端
      • 以 LazyCheckout 何时 waitForPendingUpdates() 被调用, 本地客户端或远程客户端中有未完成的编辑到树
    • 如果有的话 实值 正在编辑此检出, viewChange 被开除...
      • 当更改被应用到正在检查的编辑中时
      • 当正在进行的编辑通过 rebaseCurrentEdit()

    这项政策乍一看可能显得很复杂,但实际上它提供了自然流动。 取出时总是将直接应用于取出本身的更改通知听众,但取出外部的更改(如从远程客户端的更改)则根据取出的政策被缓冲. EagerCheckout 完全不会缓冲它们 并立即启动一个变化事件,而 LazyCheckout 在被请求之前避免启动更改事件(通过) waitForPendingUpdates()) (中文(简体) ). 请注意,每一种离职都提供 快速隔离,意思是,当退出处于编辑(即介于 openEdit() 财务报告和审定财务报表 closeEdit())它不会启动一个事件,以进行来自出票场外的修改。 这是可取的,因为它阻止了视图更改正在构建的当前编辑的"从下".

    这个 viewChange 事件还提供了之前的视图以及新视图,如果客户想要,可以生成两种视图的三角形:

    const checkout = new EagerCheckout(sharedTree);
    checkout.on('viewChange', (before: TreeView, after: TreeView) => {
        // Use the delta object as a convenient way to see which nodes were added, deleted, or changed between views
        const delta = before.delta(after);
    }));
    
    

冲突

由于HuideTree的合作和分布性质, 编辑可以基于一个版本的树来构建,该版本不同于最终被应用的版本. 更改 API 的设计允许捕捉"更改"中编辑的许多实际意图. 例如,A和B之间的插入可以在A之后或B之前被锁定. 如果它真的打算介于A和B之间,可以包含一个需要A和B仍然相邻的约束,否则编辑会相冲突. 也可以用所插入的内容来取而代之. 这种灵活性使大多数编辑都能够以在重新排序时正确应用其意图的方式编码, 在无法做到这一点的罕见情况下,它们将发生冲突,而不是以非故意保留的方式适用: 共享Tree一般遵循这一政策,即不实施更改比违反用户期望或意图的方式实施更好.

当某项更改未能适用,或某项制约表明其适用,但可能并不理想时,则称为相冲突. 目前,如果因冲突而未能适用更改,则取消.

制约因素

目前制约因素大多得不到支持。 它们将在即将展开的 " 共享Tree v2 " 实施过程中得到支持.

A 类 Constraint 可以添加到 Edit 的更改列表中,这些更改可以用来检测一个 Edit 仍然可以应用(而非冲突),但可能由于重排而失去其原始语义.

例如,可以同时进行两个编辑:一个是按字母顺序排序的列表,另一个是将一个项目添加到列表中. 视其变化的排序方式和插入的确切发生地点而定,如果该插入首先被确认,则该类型可能或不会发生冲突. 在一些领域,希望这种冲突能够发生. 在此类领域,可以添加一个约束,要求列表包含与创建排序编辑时相同的一组项目,以便正确应用. 约束可以指定如果被违反将会发生什么:见 ConstraintEffect 内 持续类型 详细情况.

请注意,这些限制不仅仅适用于同时进行的编辑: 编辑到历史中也使用冲突(并因此被限制)来阻止历史编辑以打破其语义的方式被重新编译. 在上述例子中,当一个用户在排序后将一个项目从列表中删除时,可能会发生这种情况. 如果该类有列表包含预期项目的限制,则撤销将违反该限制 (即使清单已经大部分或全部分类). 这使得应用程序有机会通过在完成倒数时重新应用更新的列表中的类型来解决制约性违法行为, ,从而保持新项目(其删除被取消)将正确排序的预期行为. 详见下文"编年史"一栏.

更改拒绝

在同时发生变化的情景中,流体服务确认这些变化的顺序可能导致变化无效. 编辑是交易性的,所以编辑中的任何无效的更改都会使整个编辑无效.

然而,任何变化组合都不会导致客户端崩溃. 更改在应用前经过验证,目前无效的更改被删除.

更改拒绝示例

假设树上有一个单一的节点,A,客户端创建了编辑,1,在节点A后插入节点. 同时另一个客户端创建了编辑器,2,删除了节点A. 如果Fluid服务序列在编辑 1 之前编辑 2,那么编辑将会因为其主播被删除而变得无效. 在这种情况下,删除了编辑 1 .

可能的更改/编辑结果

应用编辑或更改被应用.
无效的更改鉴于当前树和脱落状态,无法应用的成型(不是畸形)变化.
无效编辑无法应用到当前树上的形状良好的编辑 .
变化无法在任何树上应用的更改没有出错 。 例如,一个没有兄弟关系,也没有特征的StablePlace.
编辑格式错误包含一个或多个错误变化的编辑,或带有一系列变化的编辑,这些变化不可能在不出错的情况下相继应用。 (例如,父节点两次).

撤消/ 重写

协作背景下的撤销是复杂的,因为取消的改变可能不是最近的改变. 这意味着撤销和重做确实需要被当作任意历史编辑,添加和去除过去作为特定点的修改,并将它的影响与之后的编辑相协调. 两者 SharedTree 财务报告和审定财务报表 Checkout 曝光 revert 作为返回任意编辑的方法.

const editId = sharedTree.applyEdit(Change.insertTree(fooNode, StablePlace.atStartOf({ parent: view.root, label: 'foo' }));
const undoEditId = sharedTree.revert(editId); // Undoes the insert
const redoEditId = sharedTree.revert(undoEditId); // Redoes the insert

摘要

历史

A 类 SharedTree 可以选择保留其"历史",即所有经过时间排序的编辑. 这有存储和性能管理费,默认是禁用。 一个"共享Tree"对象的例子将永远包含在其存在期间创建/接收的所有编辑,从而能够对这些编辑进行撤销,重做和历史翻转.

目前, SharedTree 使用历史创建的文档永远不会被删除.

历史虚拟化

由"共享Tree"生成的摘要包括了当前视图和编辑历史. 然而,装入摘要的新客户端可以单独使用当前视图. 这使得历史可以被虚拟化来减少大型编辑历史客户端的负载时间.

编辑通过Aync API实现虚拟化并进行点播下载. 编辑历史的使用在历史相关操作之外是罕见的,因此预计客户端不会经常下载编辑历史. 特化编辑也被缓存并定期被从内存中移除,然而,在本届会话中添加到历史中的编辑从未被删除.

客户最低要求

Fluid框架客户端库支持此文档中的平台. 这些要求有意具有限制性. 在一个主要的版本系列中,我们可以放松这些要求,但我们不能使其更严格. 对于长期支持(LTS)版本,我们可能需要支持这些平台几年.

其他配置可以奏效,但Fluid框架不支持. 如果一个不支持的配置停止工作, 我们不会将此归类为错误 . 要请求支持未列出的配置, 请存档 . 产品组会评估你的要求. 在此问题上,请具体说明配置的现状:

  • 配置有效但需要官方支持.
  • 配置不起作用,需要更改.

支持的运行时间

  • Fluid框架支持节点.js版本22和24同时收到 上游支助。 。 。 .
  • Fluid框架支持支持ES2022标准库的现代浏览器.

支持的工具

  • 类型脚本 6.0数字 :
    • 流体框架支持全部 strict 选项.
    • 设定建设目标(lib, (中文(简体) ), target改为: ES2022 或后会.
    • 启用 strictNullChecks。 。 。 .
    • 流体框架不支持 在 TypeScript 6.0 中贬值的配置选项。 。 。 .
    • 流体框架不完全支持 exactOptionalPropertyTypes。 。 。 . 如果您启用此选项, 请不要使用 in, (中文(简体) ), Reflect.has, (中文(简体) ), Object.hasOwn,或 Object.prototype.hasOwnProperty 改为Fluid框架类型的窄成员. 这些方法可以错误地排除 undefined 从可能的值.
  • 网络软件包 页:1
    • 我们不需要一个特定的捆绑者. 其他处理ES模块的捆绑机可以工作,但我们只积极测试网页包.

模块分辨率

在类型脚本中 compilerOptions,使用时 Node16, (中文(简体) ), Node20, (中文(简体) ), NodeNext,或 Bundler 模块分辨率. 这些设置遵循 节点.js v12+无害环境管理 分辨率和加载算法。 。 。 .

不使用 Node10 模块分辨率.

模块格式

  • ES模块: 使用ES模块来消耗Fluid框架客户端软件包,包括在Node.js中.
  • 常见JS : Fluid Framework在3.0版本或后期不正式支持CommonJS.

贡献准则

你们可以 贡献 通过这些方式实现流体框架:

详细指示改为: repo 文档。 。 。 .

本项目遵循 Microsoft 开源行为守则。 。 。 . 更多信息,改为: 《行为守则》经常被问到的问题。 。 。 . 问题或评论,联系 打开码@ microsoft.com。 。 。 .

这个项目可能包含微软项目的商标或标志,产品,或服务. 这些商标或标志的使用必须遵循微软 商标和品牌准则。 。 。 . 在这个项目的修改后版本中使用微软商标或标志不得造成混淆或暗示微软赞助.

帮助

读取 流体框架文件 用于了解流体框架概念和API.

要求提供文件未载列的资料, 创建问题。 。 。 .

商标

这个项目可能包含微软项目的商标或标志,产品,或服务.

这些商标或标志的使用必须遵循微软的 商标和品牌准则。 。 。 .

在这个项目的修改后版本中使用微软商标或标志不得造成混淆或暗示微软赞助.

版本与发布记录

@fluid-experimental/tree 共发布 638 个版本,首次发布于 2021/01/23;过去 12 个月发布了 48 个版本。

版本发布日期距今
3.12.0latest2026/11/114 天前
3.11.02026/11/0411 天前
3.10.02026/11/0411 天前
3.7.02026/10/2719 天前
3.6.02026/10/2026 天前
3.5.02026/10/131 个月前

其他发布通道:next → 2.0.0-dev-rc.1.0.0.225277,canary → 2.0.0-internal.8.0.8,dev → 2.116.0-416006,rc → 2.118.1。

关于 @fluid-experimental/tree 的常见问题

@fluid-experimental/tree 是什么?

分布的树

如何安装 @fluid-experimental/tree?

在项目目录运行 npm install @fluid-experimental/tree,或使用 pnpm add / yarn add / bun add。

@fluid-experimental/tree 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fluid-experimental/tree 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@fluid-experimental/tree 可以商用吗?

@fluid-experimental/tree 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fluid-experimental/tree 的下载量有多少?

根据 npm 官方下载统计,@fluid-experimental/tree 在 2026/10/15 – 2026/11/13 这 30 天内被下载 2,273 次,日均约 76 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fluid-experimental/tree 还在维护吗?

最新版本 3.12.0 发布于 2026/11/11(4 天前);至今共发布 638 个版本,其中过去 12 个月发布了 48 个;首次发布于 2021/01/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。