@ fluid框架/ 树
要开始与 SharedTree 请在申请表上读读 快速启动引导。 。 。 .
该套件的内容也作为以下报告的一部分: fluid-framework 软件包 它提供了从此软件包中消耗功能的替代方法.
共享的哲学 涵盖共享旅游项目的目标, 以及这些目标对从事此软件包工作的开发者的一些影响.
使用 Fluid 框架库
对于依赖流体框架库的公共API,我们建议 ^ (caret)版本范围.
例如,使用 ^1.3.4。 。 。 .
依赖不稳定的API,例如: beta API,我们推荐一个更具限制性的版本范围.
例如,使用一个 ~ 版本范围.
安装
运行此命令以安装软件包 :
npm i @fluidframework/tree
从此软件包导入
此软件包使用 packet.json 导出 通过支持级别将API分离. 有关支助保障的信息改为: API 支持级别。 。 。 .
导入 public API 从 @fluidframework/tree。 。 。 .
导入 beta API 从 @fluidframework/tree/beta。 。 。 .
导入 alpha API 从 @fluidframework/tree/alpha。 。 。 .
导入 legacy API 从 @fluidframework/tree/legacy。 。 。 .
API 文档
读取 @ fluid框架/ 树 API 文档 https://fluidframework.com/docs/apis/tree (中文(简体) )。 。 。 .
状态
用户应注意的显著考虑:
- 持续格式是稳定的:使用本套件已发行的 2.0.0 或以上的版本所创建的文档得到长期充分支持.
- 目前所有范围的变化都被原子化. 这意味着,在插入/删除/移动多个相邻节点时,会将编辑分成单独的单一节点编辑. 这可以影响这些编辑的合并行为,以及大型数组编辑的性能.
- 某些文件(例如: 路线图)已过时. 这个 API 文档 来源于源代码中的文件评论,应当更加更新.
有关各种特征发展状况的更详细情况,请参见: 路线图。 。 。 .
动机
有许多不同的因素促使这个树DDS的创建. 各种可能的消费者(在若干公司之间)有重叠的特点要求 这似乎可以最好地通过合作 在一个由流体驱动的 丰富的树木执行. 目前的工作重点是:
- 语义:
- 高品质的语义合并,包括部分序列的移动(被称作"slice move").
- 交易性.
- 以敏锐有力的方式支持计划.
- 可缩放性 :
- 支持部分取景:允许高效地查看和编辑较大数据集的部分内容而无需下载整个内容.
- 容易(与客户共享代码)旋转可选服务的能力,以进一步提高可扩展性(例如:服务器侧面摘要,索引,权限等)
- 高效的数据编码.
- 表达性 :
- 对移动的高效支持,包括大段大序列和大子树的移动.
- 支持历史操作( ex: 撤销和重做) .
- 具有设计模式的灵活计划系统,可以随时间推移做出计划变化.
- 工作流程 :
- 良好支持下线.
- 可选支持分支和历史.
- 广泛性:满足未来用户的需求必须实际可行,超出我们在初始版本中所能负担的支助。 这包括下列需要:
- 新的字段类型, 允许以更具体的合并语义进行数据建模( 如: 添加对像集这样的特殊集合的支持, 或排序序列)
- 新服务( ex: 支持权限, 服务器侧索引等)
现有DDS中缺少什么?
directory 财务报告和审定财务报表 map 无法提供合并解析度,保证树的造型良好,同时支持想要的编辑 API(类似于子序列移动),
缺少(而且实际上无法扩展)处理大数据或计划的有效方法.
sequence 不捕捉等级或计划,也不处理部分视图.
此外,它实际的合并解决在一些情况中留下一些需要改进的地方 tree 旨在改进.
experimental/tree 在计划系统中没有构建一个减少可用数据以进行质量高的合并.
因为它完全基于节点标识符 所以需要将整棵树装入内存
(包括交易中不读出树上大部份就无法核实的限制因素).
experimental/PropertyDDS 目前没有理想的高质量合并逻辑,目前甚至没有支持高效移动.
理论上说,大部分期望是可能的,作为关于 PropertyDDS, (中文(简体) ),
但人们决定,从头开始建立这个更具特色的DDS更有意义 PropertyDDS 财务报告和审定财务报表 experimental/tree。 。 。 .
为什么不用现有的数据交换系统实施方法来构建一棵树,以及这与流体框架本身有何关系?
目前现有的DDS执行无法支持跨DDS交易. 例如,将一个序列的一部分从一个序列的DDS移动到另一个序列无法进行交易,指如果移动源相冲突,如果在不同的DDS中,则目的地的一半无法被更新或中止. Cross DDS移动目前也不可能像在单一的DDS内移动那样高效,而且如果没有重大框架变化,也没有一个好的方法可以跨越DDS历史或分支. 每一DDS的性能和存储成本也相当高,这使得这种方法比使用单一DDS要昂贵得多.
考虑这个新树DDS的一种方法是尝试将一些流体-框架特性(如查看数据子集的能力)与DDS的特性相混合(例如:每件下调间接费用,子序列的有效移动,交易). 如果这项努力取得成功,它可能会揭示一些改进的抽象,用于模块化的分级协作数据结构(可能为"域类型"), 它可以回到框架,使这个树特有的一些特征(例如:历史、分支、交易动作、减少间接费用)成为框架特征.
从这个角度看,这棵树是抽象概念和特征的证明,可以使框架受益,但最初更容易在DS中实施. 这棵树使这些功能在用户手中比在框架一级能够做的要快得多.
建议开发者工作流程
这个软件包可以使用其中的任何一种 关于流体框架的常规工作流程 和/或它的客户端发布包组,但对于只接触树包的工作,有一个可选的工作流程,可能更能进行人机学研究:
- 跟着 设置和建筑 说明.
- 打开 .vscode/Tree.code-工作空间 在VS代码中. 这将建议安装试验跑道扩展.
- 将树包建为正常( 例如 :
pnpm i && pnpm run build输入tree目录). - 在编辑树项目后运行
pnpm run build(还在tree)目录. - 使用 VS 代码中的“ 测试” 侧面板运行并调试测试, 或使用内线
Run | Debug在源代码中显示在测试之上的按钮: 两者均由工作空间推荐的摩卡测试扩展提供. 请注意,这不能建立测试,所以始终必须首先建立.
经常问的问题
为什么我不能指定可插入的内容到一个字段?
import { SchemaFactory } from "@fluidframework/tree";
const factory = new SchemaFactory("com.fluidframework.faq");
class Empty extends factory.object("Empty", {}) {}
class Test extends factory.object("Test", { data: Empty }) {}
function set(node: Test) {
node.data = {}; // Why does this not compile?
}
这是因为 类型脚本语言的限制 这使得树不可能允许它同时在获取器上保持强烈的打字功能来读取数据.
要解决这个问题,请创建一个无水化节点:
node.data = new Empty({}); // The unhydrated node's type matches the type returned by the getter, and thus is compatible with the setter
可插入的内容仍然可以在其他地方被使用,比如当嵌入到其他可插入的内容中,在ArrayNode编辑方法中,以及在初始化视图时.
// The empty node can be implicitly constructed from `{}` here, since this context allows insertable content, not just nodes.
const node = new Test({ data: {} });
见文档 树节点 对于一些关于明确和隐含的节点构造的注释.
为什么TypeScript唯一的类型会让我为我的字段提供内容 undefined? 吗?
可选字段允许 undefined 或类型 AllowedTypes 现场许可.
如果允许的类型减少为 never 仅限 undefined 将留下.
请参看下文.
为什么我的可插入内容被打成 never? 吗?
这是常见的问题 当与 投入 类型(详见链接文档).
这通常是一种症状,说明这种计划对于这种计划来说不够具体. 这最常见的情况是,为部分计划宣布一个常数,然后在后期建立完整的计划时使用. 例如, 如果您这样做, 它会失败 :
const itemTypes = [ItemA, ItemB]; // BAD: use `as const` to fix.
class Holder extends schemaFactory.object("Holder", { item: itemTypes }) {}
const holder = new Holder({ item: new ItemA({ a: 42 }) }); // Type 'ItemA' is not assignable to type 'never'.ts(2322)
这可以通过捕获一个更具体的类型来固定:
const itemTypes = [ItemA, ItemB] as const; // Fixed
class Holder extends schemaFactory.object("Holder", { item: itemTypes }) {}
const holder = new Holder({ item: new ItemA({ a: 42 }) });
如果您无法保留 Tuple 类型 as const,阿尔法API unsafeArrayToTuple 将一个数组视为包含其元素类型结合的每个成员的tuple.
它用其类型转换的原数组返回 UnionToTuple数字 :
import { unsafeArrayToTuple } from "@fluidframework/tree/alpha";
const itemTypes = [ItemA, ItemB];
// Adding `as const` above would be a better fix in this simple example.
const allowedTypes = unsafeArrayToTuple(itemTypes);
class Holder extends schemaFactory.object("Holder", { item: allowedTypes }) {}
const holder = new Holder({ item: new ItemA({ a: 42 }) });
偏爱 as const 可能的话.
unsafeArrayToTuple 不在运行时验证或更改数组.
一个阵列的元素类型可以包括实际上不在阵列中的计划.
帮助器使 TypeScript 接受这些缺失的策略作为允许的类型,但在运行时使用失败 :
function makeHolder(itemType: typeof ItemA | typeof ItemB) {
const allowedTypes = unsafeArrayToTuple([itemType]);
return class extends schemaFactory.object("Holder", { item: allowedTypes }) {};
}
class HolderA extends makeHolder(ItemA) {} // Only ItemA is allowed at runtime.
const holder = new HolderA({ item: new ItemB({ b: "hello" }) }); // Compiles, but throws at runtime.
参数类型表示 ItemA 或者说 ItemB不是说两个计划都在场.
只有当您能够确保元素类型结合中的每个计划都存在于数组中时,才会为允许的类型使用此帮助器 .
在使用 TypeScript 时, 我如何有效地使用 shareTree isolatedDeclarations? 吗?
SharedTree schema 大量使用表达类型和类表达式作为基础类,两者都是 isolatedDeclarations 不允许.
见 单独宣布(md) 解释问题和建议在需要使用 " 共享的Tree " 计划的项目中使用的方法 isolatedDeclarations。 。 。 .
建筑
本节涵盖"树"DDS的内部结构. 在本节中,此软件包的用户称为"应用程序". "应用程序"充满了"应用程序代码",意思是代码可以针对特定的计划和使用案例而特有. 这通常是指客户端"业务逻辑"或一些图形网络应用中的"视图"部分,但也可能指像服务一样无头的东西.
所有权和寿命
此图显示了有固态箭头的交易中的所有权等级, 有一些重要的有虚线箭头的引用:
graph TD;
subgraph "Persisted Data"
store["Data Store"]-->doc["Persisted Summaries"]
end
container["Fluid Container"]-->shared-tree
subgraph "@fluidframework/tree"
shared-tree--"extends"-->shared-tree-core
shared-tree-core-."reads".->doc
shared-tree-core-->EditManager-->X["collab window & branches"]
shared-tree-core-->Indexes-->ForestIndex
shared-tree-->view["SharedTreeView"]
transaction-."updates".->view
transaction-->EditBuilder
view-."reads".->ForestIndex
view-->transaction
end
tree 它是一个DDS,因此,它将其持续的数据储存在一个流体容器中,并且也属于同一容器.
如果那个容器里再没有提及DDS, 它可能会得到由流体GC收集的垃圾.
树的DDS本身,或更具体 shared-tree-core 由一系列指数(与数据库一样)组成,这些指数提供了数据,作为容器摘要的一部分得到坚持.
shared-tree-core 拥有这些数据库,负责从摘要中充实这些数据库,并在摘要中更新这些数据库.
见 指数和分支 详细介绍如何与分支合作.
当应用程序想要访问时 tree数据,他们通过 TreeView 将索引集成 API 根据 view-schema。 。 。 .
《意见》还可在申请书中说明:
view-schema- 常规数据适配器
- 请求或提示树上要保存的子集( TODO)
- 待处理交易
- 申请回调/事项的登记
由于各种观点认同来自 shared-tree,明确处置任何已创建的,必须避免泄漏.
交易由 Tree.runTransaction ,目前为同步.
支持同步交易,由应用程序管理寿命并确保其不超过视图寿命,
将来可以增加.
数据流动
查看
flowchart LR;
doc["Persisted Summaries"]--"Summary+Trailing ops"-->shared-tree-core
subgraph "@fluidframework/tree"
shared-tree--"configures"-->shared-tree-core
shared-tree-core--"Summary"-->Indexes--"Summary"-->ForestIndex;
ForestIndex--"Exposed by"-->ISharedTreeView
end
ISharedTreeView--"viewed by"-->app
shared-tree 配置 shared-tree-core 带有一套索引.
shared-tree-core 从流体容器下载汇总数据,将汇总数据(以及今后的任何编辑)输入索引.
shared-tree 然后构建默认视图.
使用 shared-tree 可以得到它能够读取数据的视图(其内部视图取自索引).
对于应用程序的任何特定部分,这通常遵循两种模式之一:
- 根据创建视图的需要读取树数据. 注册无效在树的被观测部分改变时召回 . 无效时,通过重读树来重建已无效的视图部分.
- 根据创建视图的需要读取树数据. 注册三角洲召回树的被观测部分改变时. 收到三角洲后,根据三角洲更新所处地貌.
TODO:最终这两种方法应该能够被混合并按所希望的应用的不同部分相匹配,得到范围三角洲. 目前三角洲是全球性的.
请注意,第一种模式是使用第二种模式实施的.
它通过将树数据存储在 forest 它使用三角洲更新自己.
当一个应用程序选择使用第二种模式时,
它可以被认为是选择进入一个专门的应用(或域)特定的树代表.
从这个角度看,第一种模式相当于使用平台提供的通用树的代表:
这通常应比较容易,但在特定情况下可能会造成一些执行间接费用.
当视图想要抓住树的一部分时(对于第一个模式), 他们这样做的"锁定者" 有明确的行为 通过编辑.
TODO:请注意,由于某种原因,应用程序需要 view-schema 从视图中应用到树上.
这样做的系统称为"计划",目前尚未实施.
当它设计得更好时,一些关于它如何工作的详情属于本节(以及下节).
编辑
用固态箭头编辑相干的数据流. 关键视图相关的更新是针对点箭头所做的.
这里显示交易期间的编辑 :
flowchart RL
subgraph "@fluidframework/tree"
transaction--"collects edits in"-->EditBuilder
EditBuilder--"updates anchors"-->AnchorSet
EditBuilder--"deltas for edits"-->transaction
transaction--"applies deltas to"-->forest["ISharedTreeView's forest"]
end
command["App's command callback"]
command--"Edits"-->transaction
forest-."invalidation".->command
应用程序可以使用他们的视图来定位他们想要编辑的地方. 应用程序将“ 命令” 传递到创建运行命令的交易的视图 . 此“ 命令” 可以交互编辑树 . 交易内部通过创建更改执行这些编辑 . 每项更改都以两种方式处理:
- 该变化被转换为三角洲,该三角洲适用于森林和任何现有锚地,允许应用程序事后读取更新的树.
- 适用于
EditBuilder用于创建/编码发送到 Fluid 的实际编辑.
一旦命令结束,交易就会卷回离开森林的状态.
如果命令没有错误, a changeset 是从应用到 EditBuilder,它被编码成流体操作.
然后,如果交易待决时有任何操作出现,则该视图重新对操作进行基础分析(只可能用于Aync交易,或者如果由于某种原因其为Aync而使视图落后).
最后,观点将行动发送给 shared-tree-core 向流体呈报.
这一意见导致行动成为当地行动 shared-tree-core 创建三角洲.
这个三角洲进入指数, 导致"森林Index" 和由此而来的观点更新,
以及加入三角洲的任何东西.
这表明交易已经完成. 森林的倒转或改变(以及由此产生的失效)和AnchorSet, 然后用编辑的最后版本更新它们. 在常见情况下,可以跳出(因为取消). 另外也没有显示在存储并发送到服务之前重新定位变换器的(通常也不需要)步骤.
flowchart LR
command["App's command callback"]--"commit"-->transaction
subgraph "@fluidframework/tree"
transaction--"build"-->EditBuilder
EditBuilder--"changeset"-->transaction
transaction--"changeset (from builder)"-->core["shared-tree-core"]
core--"changeset"-->EditManager--"changeset"-->local["Local Branch"]
end
core--"Op"-->service["Fluid ordering service (Kafka)"]
service--"Sequenced Op"-->clients["All clients"]
service--"Sequenced Op"-->log["Op Log"]
当行动得到排序, shared-tree-core 从订货处收到,
根据需要重新建立基础,并向指数发送另一个三角洲.
graph LR;
service["Fluid Service"]--"Sequenced Op"-->core["shared-tree-core"]
subgraph "@fluidframework/tree"
core--"changeset"-->EditManager
EditManager--"add changeset"-->remote["remote branch"]
remote--"rebase into"-->main[main branch]
main--"rebase over new changeset"-->local["Local Branch"]
main--"sequenced changeset"-->Indexes
local--"delta"-->Indexes
Indexes--"delta"-->ForestIndex
end
ForestIndex--"invalidates"-->app
Indexes--"delta (for apps that want deltas)"-->app
Schema 演化性
随着时间推移,应用程序作者可能想改变其文件的计划. 例如,他们可能希望增加新应用程序特性的支持,或以某种新方式代表现有内容.
在此之前,应用程序作者必须考虑其生态系统内的兼容性限制. 大多数生态系统没有办法确保应用程序可能打开的所有文件都使用新的计划,甚至协作会话中的所有用户都使用同一代码版本. 当两个客户端使用不同文档的代码版本试图合作时,这可能会出问题.
因此,当其代码支持与某个特定文件合作时,应用程序必须围绕政策进行前瞻性思考.
见 Schema 进化 以全面解决这一问题.
依赖关系
@fluidframework/tree 取决于流体运行时间(各种软件包在 @fluidframework/*(中文(简体) )
并且将直接依靠应用程序使用它(虽然届时将将其移出) @fluid-experimental) (中文(简体) ).
@fluidframework/tree 也很复杂,
因此,它的实施被分成几个部分,这些部分认真控制了依赖性,以帮助确保可维护的代码库.
这种内部结构的目标是使进化和维护变得容易.
指导这项工作的一些原则是:
-
避免循环依赖性 :
循环依赖性可以使逐渐学习一个代码库变得困难,也使部分代码库逐渐更新或替换变得困难. 此外,它们还会引起初始化后的运行时间问题.
-
最小化接合 :
减少依赖图中边缘的数量和复杂性. 这往往涉及一些办法,例如使一个组成部分通用,而不是直接取决于具体类型, 或结合了相关的组件,这些组件有很多接合.
-
减少过渡性依赖性:
尽可能保持给定组件的依赖性总数. 这既适用于模块层面,也适用于这些模块所定义的实际对象. 我们特别努力避免的一种特殊依赖是依赖于具有复杂条件逻辑的状态系统. 这方面的一个例子是: 重新基数 在那里,我们确保 状态系统,
Rebaser具体改革政策并不取决于实际变化. 相反,变革的实际替代政策逻辑背后是:ChangeRebaser接口,不依赖于Rebaser并且将政策暴露为纯粹的功能(因此是无国籍的). 这对于可检验性很重要,因为复杂的条件逻辑(如ChangeRebaser执行)需要广泛的单位测试, 这对于许多依赖性的状态系统和系统来说非常困难(而且往往很慢). 如果我们采取将改革政策重新定位的模式Rebaser子类, 这将违反这一指导原则并导致更难孤立和检验政策逻辑.减少过渡性依赖的另一个方面是减少特定情况所需的依赖性. 这意味着考虑出并不总是需要的代码(例如支持额外的特性和优化),这样在不需要时就可以省去.
shared-tree-core这是一个很好的例子:它可以在没有指数的情况下运行,而一个微不足道的改变家庭则允许它拥有极少的必要的依赖. 其形式往往取决于接口(接口可以将其执行互换或被嘲笑),例如:ChangeFamily,或者像我们所做的那样在登记册中收集功能FieldKinds财务报告和审定财务报表shared-tree-core指数. 依赖性注射是减少过渡性依赖性的有用模式的一个实例. 除了简化对系统的推理(为特定情景考虑的总数减少)并简化测试, 这种方法还使新特性的生命周期更容易管理,因为这些特性可以完全实施和测试,而不必在自身之外修改代码. 这使得这些特征的出厂前,稳定和最终被去皮化变得容易得多,而且如果最终需要更分化的生命周期,甚至可以从单独的包中发布.此外,这种建筑方法可以不牵扯不必要的功能,导致应用规模较小.
这些方法导致了一种大致类似于下图的依赖结构. 在本图中,略去一些已经暂时包含的依赖性箭头.
flowchart
direction TB
subgraph package ["@fluidframework/tree"]
direction TB
subgraph core ["core libraries"]
direction TB
schema-view
forest-->schema-stored
rebase-->tree
schema-stored-->dependency-tracking
schema-view-->schema-stored
dependency-tracking
forest-->tree
revertible-->rebase
end
core-->events-->util
core-->id-compressor-->util
core-->codec-->util
feature-->shared-tree-core
shared-tree-core-->core
shared-tree-->simple-tree
simple-tree-->feature
external-utilities-->feature
subgraph feature ["feature-libraries"]
direction TB
flex-tree-->contextuallyTyped
flex-tree-->node-key
defaultRebaser
contextuallyTyped-->defaultFieldKinds
defaultSchema-->defaultFieldKinds-->modular-schema
forestIndex-->treeTextCursor
modular-schema
node-key-->modular-schema
node-key-->defaultFieldKinds
object-forest-->mapTreeCursor-->treeCursorUtils
chunked-forest-->treeCursorUtils
schemaIndex
sequence-change-family-->treeTextCursor
end
subgraph domains
JSON
end
domains-->simple-tree
end
package-->runtime["Fluid runtime"]
开放设计问题
这里的设计问题都影响到本包中顶层模块的建筑作用,在固定时可能需要修改上述建筑细节. 范围较小但不影响总体结构的问题应在更本地化的地点记录在案.
专业的树下处理应如何构成?
应用程序应该有一个域模型,可以根据需要将树节点与自定义执行相混合. 自定义的实施也许应该能够预测有弹性的树木、森林含量(通过光标),并通过输入的再生或三角洲更新加以更新. 这对性能/可伸缩性很重要.
客户最低要求
Fluid框架客户端库支持此文档中的平台. 这些要求有意具有限制性. 在一个主要的版本系列中,我们可以放松这些要求,但我们不能使其更严格. 对于长期支持(LTS)版本,我们可能需要支持这些平台几年.
其他配置可以奏效,但Fluid框架不支持. 如果一个不支持的配置停止工作, 我们不会将此归类为错误 . 要请求支持未列出的配置, 请存档 . 产品组会评估你的要求. 在此问题上,请具体说明配置的现状:
- 配置有效但需要官方支持.
- 配置不起作用,需要更改.
支持的运行时间
- Fluid框架支持节点.js版本22和24同时收到 上游支助。 。 。 .
- 流体框架将停止支持第22版 当上游支持于2027-04-30结束时。 。 。 .
- 流体框架不支持节点.js与
--no-experimental-fetch旗帜.
- 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.
要求提供文件未载列的资料, 创建问题。 。 。 .
商标
这个项目可能包含微软项目的商标或标志,产品,或服务.
这些商标或标志的使用必须遵循微软的 商标和品牌准则。 。 。 .
在这个项目的修改后版本中使用微软商标或标志不得造成混淆或暗示微软赞助.