在知识管理工具的竞争格局中,Notion 与 Miro 分别占据了文档编辑与可视化白板的领先地位。AFFiNE 作为新一代开源替代品,通过 TypeScript+Rust 的混合架构和创新的块级设计,实现了文档与白板的无缝融合,为知识管理领域带来了全新的工程范式。534 个 GitHub 星标和活跃的开源社区发展,充分证明了这一技术路线的市场认可度。
混合架构的战略选择:TypeScript 与 Rust 的协同设计
AFFiNE 选择 TypeScript+Rust 的技术组合,并非简单的技术堆叠,而是基于性能优化、跨平台兼容性和长期维护性的战略考量。TypeScript 提供了优秀的开发体验和丰富的生态,而 Rust 则承担了底层性能关键的数据处理任务。
前后端语言边界的设计哲学
// TypeScript端 - 用户交互层与业务逻辑
export interface BlockNode {
id: string;
type: string;
props: Record<string, any>;
children: BlockNode[];
}
export class BlockManager {
private blocks: Map<string, BlockNode> = new Map();
private listeners: Set<BlockChangeListener> = new Set();
// 高层业务逻辑处理
async createBlock(type: string, props: any): Promise<string> {
const id = generateId();
const block: BlockNode = {
id,
type,
props: this.sanitizeProps(props),
children: []
};
this.blocks.set(id, block);
this.notifyChange('create', block);
// 异步同步到Rust后端
await this.syncToRust(id, block);
return id;
}
// 与Rust后端通信的接口
private async syncToRust(id: string, block: BlockNode): Promise<void> {
const serializedData = JSON.stringify(block);
const result = await nativeModule.processBlockData(id, serializedData);
if (!result.success) {
throw new Error(`Rust同步失败: ${result.error}`);
}
}
}
// Rust端 - 高性能数据处理
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio::sync::mpsc;
#[derive(Debug, Serialize, Deserialize)]
pub struct BlockData {
pub id: String,
pub r#type: String,
pub props: HashMap<String, serde_json::Value>,
pub children: Vec<BlockData>,
}
pub struct RustProcessor {
storage: HashMap<String, BlockData>,
sync_sender: mpsc::UnboundedSender<SyncMessage>,
}
impl RustProcessor {
pub fn new() -> Self {
Self {
storage: HashMap::new(),
sync_sender: mpsc::unbounded_channel().0,
}
}
pub fn process_block_data(&mut self, id: String, data: String) -> Result<String, String> {
let block: BlockData = serde_json::from_str(&data)
.map_err(|e| format!("反序列化失败: {}", e))?;
// 高性能数据索引和查询
self.build_block_index(&block);
self.storage.insert(id.clone(), block);
// 异步触发同步事件
let _ = self.sync_sender.send(SyncMessage::BlockUpdated(id));
Ok(id)
}
fn build_block_index(&self, block: &BlockData) {
// 建立高效的搜索索引
self.index_block_content(block);
if !block.children.is_empty() {
for child in &block.children {
self.index_block_content(child);
}
}
}
}
这种混合架构实现了职责分离:TypeScript 处理复杂的用户交互逻辑,而 Rust 专注于高性能的数据处理和存储。这种设计既保持了开发效率,又确保了系统的整体性能。
块级架构的创新实践:从 Notion 理念到工程实现
AFFiNE 的块级架构是 "万物皆模块" 设计理念的深度工程化实践。每个内容单元都被抽象为独立的 Block,支持动态组合和跨视图迁移。
Block 系统的类型系统设计
// Block类型系统定义
export abstract class BaseBlock<TProps extends BaseProps> {
abstract readonly type: string;
protected props: TProps;
constructor(props: TProps) {
this.props = props;
}
// 块级协议定义
abstract render(): React.ReactNode;
abstract toJSON(): BlockNode;
abstract fromJSON(data: BlockNode): BaseBlock<TProps>;
// 块级操作接口
blockClone(): this {
return new (this.constructor as new (props: TProps) => this)({...this.props});
}
blockMerge(other: this): boolean {
// 实现特定块的合并逻辑
return this.canMergeWith(other);
}
protected canMergeWith(other: this): boolean {
return this.type === other.type && this.props.mergeCompatible;
}
}
// 文本块的具体实现
export class TextBlock extends BaseBlock<TextProps> {
readonly type = 'text';
render() {
return (
<div
contentEditable
suppressContentEditableWarning
onInput={this.handleTextChange}
className="text-block"
data-block-id={this.props.id}
>
{this.props.content}
</div>
);
}
toJSON(): BlockNode {
return {
id: this.props.id,
type: this.type,
props: this.props,
children: []
};
}
static fromJSON(data: BlockNode): TextBlock {
return new TextBlock(data.props as TextProps);
}
private handleTextChange = (event: React.FormEvent<HTMLDivElement>) => {
const content = event.currentTarget.textContent || '';
this.props.content = content;
this.onBlockChange();
};
private onBlockChange() {
// 触发块级更新事件
document.dispatchEvent(new CustomEvent('block:update', {
detail: { id: this.props.id, block: this }
}));
}
}
// 数据库块实现 - 支持多视图
export class DatabaseBlock extends BaseBlock<DatabaseProps> {
readonly type = 'database';
render() {
return (
<DatabaseView
data={this.props.data}
viewType={this.props.currentView}
onViewChange={this.handleViewChange}
onDataChange={this.handleDataChange}
/>
);
}
// 动态视图切换
private handleViewChange = (newView: ViewType) => {
this.props.currentView = newView;
this.onBlockChange();
};
// 数据变更处理
private handleDataChange = (newData: any[]) => {
this.props.data = newData;
this.onBlockChange();
// 与Rust后端同步数据库变更
this.syncDatabaseChanges();
};
private async syncDatabaseChanges(): Promise<void> {
const queryResult = await nativeModule.queryDatabase(
this.props.id,
JSON.stringify(this.props.data)
);
// 处理查询结果
if (queryResult.optimized) {
this.applyQueryOptimization(queryResult.suggestions);
}
}
}
跨视图 Block 渲染引擎
// Block渲染引擎 - 支持文档视图和白板视图
export class BlockRenderer {
private viewMode: 'page' | 'edgeless' = 'page';
private layoutEngine: LayoutEngine;
private reactEngine: ReactEngine;
constructor() {
this.layoutEngine = new LayoutEngine();
this.reactEngine = new ReactEngine();
}
// 根据视图模式渲染Block
renderBlock(block: BaseBlock<any>): React.ReactNode {
if (this.viewMode === 'page') {
return this.renderForPageView(block);
} else {
return this.renderForEdgelessView(block);
}
}
private renderForPageView(block: BaseBlock<any>): React.ReactNode {
const pageLayout = this.layoutEngine.calculatePageLayout(block);
return (
<div
className="page-view-item"
style={{
position: 'relative',
top: pageLayout.y,
left: pageLayout.x,
width: pageLayout.width,
height: pageLayout.height
}}
>
<BlockContainer>
{block.render()}
</BlockContainer>
</div>
);
}
private renderForEdgelessView(block: BaseBlock<any>): React.ReactNode {
const edgelessLayout = this.layoutEngine.calculateEdgelessLayout(block);
return (
<div
className="edgeless-view-item"
style={{
position: 'absolute',
transform: `translate(${edgelessLayout.x}px, ${edgelessLayout.y}px)`,
transformOrigin: '0 0'
}}
draggable
onDragStart={this.handleDragStart}
onDragEnd={this.handleDragEnd}
>
<DraggableContainer>
<ResizableContainer
width={edgelessLayout.width}
height={edgelessLayout.height}
onResize={this.handleResize}
>
<BlockContainer>
{this.renderForEdgeless(block)}
</BlockContainer>
</ResizableContainer>
</DraggableContainer>
</div>
);
}
private renderForEdgeless(block: BaseBlock<any>): React.ReactNode {
// 为白板视图优化的渲染逻辑
if (block instanceof TextBlock) {
return <TextBlockEdgeless {...block.props} />;
} else if (block instanceof DatabaseBlock) {
return <DatabaseBlockMini {...block.props} />;
} else if (block instanceof ImageBlock) {
return <ImageBlockEdgeless {...block.props} />;
}
// 默认渲染
return block.render();
}
}
实时协作的 CRDT 实现:Yjs 深度集成
AFFiNE 的实时协作能力基于 Conflict-free Replicated Data Type (CRDT),使用 Yjs 作为核心实现。这种设计确保了分布式环境下的数据一致性。
CRDT 数据结构设计
// 基于Yjs的协作数据结构
import * as Y from 'yjs';
import { Awareness } from 'y-protocols/awareness';
export class CollaborativeDoc {
private ydoc: Y.Doc;
private awareness: Awareness;
private blocks: Y.Map<Y.Map<any>>;
private metadata: Y.Map<any>;
constructor() {
this.ydoc = new Y.Doc();
this.awareness = new Awareness(this.ydoc);
// 初始化数据结构
this.blocks = this.ydoc.getMap('blocks');
this.metadata = this.ydoc.getMap('metadata');
this.setupEventListeners();
}
private setupEventListeners(): void {
// 监听块级变更
this.blocks.observe(event => {
event.changes.keys.forEach((change, key) => {
if (change.action === 'add') {
this.onBlockAdded(key, this.blocks.get(key)!);
} else if (change.action === 'delete') {
this.onBlockDeleted(key);
} else if (change.action === 'update') {
this.onBlockUpdated(key, this.blocks.get(key)!);
}
});
});
}
// 添加新Block
addBlock(blockId: string, blockData: any): void {
this.ydoc.transact(() => {
const blockMap = new Y.Map();
// 将Block数据转换为Yjs结构
Object.entries(blockData).forEach(([key, value]) => {
blockMap.set(key, this.serializeValue(value));
});
this.blocks.set(blockId, blockMap);
});
}
// 更新Block
updateBlock(blockId: string, updates: Record<string, any>): void {
this.ydoc.transact(() => {
const blockMap = this.blocks.get(blockId);
if (blockMap) {
Object.entries(updates).forEach(([key, value]) => {
blockMap.set(key, this.serializeValue(value));
});
}
});
}
// 序列化管理Yjs数据
private serializeValue(value: any): Y.Any {
if (value === null || value === undefined) {
return value;
}
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return value;
}
if (Array.isArray(value)) {
const yarr = new Y.Array();
value.forEach(item => {
yarr.push([this.serializeValue(item)]);
});
return yarr;
}
if (typeof value === 'object') {
const ymap = new Y.Map();
Object.entries(value).forEach(([key, val]) => {
ymap.set(key, this.serializeValue(val));
});
return ymap;
}
return value;
}
// 反序列化管理Yjs数据
private deserializeValue(yvalue: Y.Any): any {
if (yvalue === null || yvalue === undefined) {
return yvalue;
}
if (yvalue instanceof Y.Map) {
const obj: Record<string, any> = {};
yvalue.forEach((value, key) => {
obj[key] = this.deserializeValue(value);
});
return obj;
}
if (yvalue instanceof Y.Array) {
return yvalue.toArray().map(item => this.deserializeValue(item));
}
return yvalue;
}
}
冲突解决与状态同步
// 冲突解决策略
export class ConflictResolver {
// 基于时间戳的冲突解决
static resolveByTimestamp<T>(local: T, remote: T, localTimestamp: number, remoteTimestamp: number): T {
return remoteTimestamp > localTimestamp ? remote : local;
}
// 基于操作类型的冲突解决
static resolveByOperationType(
localOp: Operation,
remoteOp: Operation,
context: MergeContext
): MergeResult {
// 不同操作类型的合并规则
const mergeRules: Record<string, OperationMergeRule> = {
'create+create': this.resolveCreateConflict,
'update+update': this.resolveUpdateConflict,
'delete+update': this.resolveDeleteUpdateConflict,
'move+resize': this.resolveMoveResizeConflict
};
const ruleKey = `${localOp.type}+${remoteOp.type}`;
const rule = mergeRules[ruleKey];
if (rule) {
return rule(localOp, remoteOp, context);
}
// 默认使用时间戳策略
return {
merged: this.resolveByTimestamp(localOp.data, remoteOp.data, localOp.timestamp, remoteOp.timestamp),
source: 'timestamp'
};
}
private static resolveCreateConflict(
localOp: Operation,
remoteOp: Operation,
context: MergeContext
): MergeResult {
// 创建冲突:使用唯一ID策略或位置分离
if (localOp.targetId !== remoteOp.targetId) {
return {
merged: [localOp.data, remoteOp.data],
source: 'id-different'
};
}
// 相同ID的创建冲突:基于位置调整
const adjustedLocal = this.adjustBlockPosition(localOp.data, context.positionConflict);
const adjustedRemote = this.adjustBlockPosition(remoteOp.data, context.positionConflict);
return {
merged: [adjustedLocal, adjustedRemote],
source: 'position-adjusted'
};
}
private static resolveUpdateConflict(
localOp: Operation,
remoteOp: Operation,
context: MergeContext
): MergeResult {
// 更新冲突:字段级别合并
const merged = { ...localOp.data };
Object.keys(remoteOp.data).forEach(field => {
if (context.fieldTimestamps[field] < remoteOp.timestamp) {
merged[field] = remoteOp.data[field];
}
});
return {
merged,
source: 'field-merge'
};
}
// 位置调整算法
private static adjustBlockPosition(block: any, conflict: PositionConflict): any {
if (conflict.type === 'overlap') {
// 解决重叠:智能位移
return {
...block,
position: {
x: block.position.x + conflict.offsetX,
y: block.position.y + conflict.offsetY
}
};
}
return block;
}
}
本地优先架构的数据同步设计
AFFiNE 采用 "本地优先" 的架构理念,确保用户数据始终首先存储在本地,同时支持跨设备同步。这种设计平衡了隐私保护与用户体验。
本地存储与同步策略
// 本地优先存储管理
export class LocalFirstStorage {
private db: LocalDB;
private syncManager: SyncManager;
private conflictResolver: ConflictResolver;
constructor() {
this.db = new IndexedDBStorage('affine-local');
this.syncManager = new SyncManager();
this.conflictResolver = new ConflictResolver();
}
// 保存数据到本地
async saveToLocal<T>(key: string, data: T): Promise<void> {
try {
// 1. 保存到本地数据库
await this.db.put(key, {
data,
timestamp: Date.now(),
version: this.getCurrentVersion()
});
// 2. 触发本地事件
document.dispatchEvent(new CustomEvent('local:update', {
detail: { key, data, timestamp: Date.now() }
}));
// 3. 异步同步到云端
this.syncManager.queueSync(key, data);
} catch (error) {
console.error('本地保存失败:', error);
throw new Error('数据保存失败');
}
}
// 同步冲突解决
async resolveSyncConflict(localData: any, remoteData: any): Promise<any> {
const localVersion = localData.version || 0;
const remoteVersion = remoteData.version || 0;
if (localVersion === remoteVersion) {
// 版本相同,需要更深入的冲突分析
return this.conflictResolver.resolveByOperationType(
localData.operations,
remoteData.operations,
this.createMergeContext(localData, remoteData)
);
}
// 版本不同,使用时间戳策略
return this.conflictResolver.resolveByTimestamp(
localData.data,
remoteData.data,
localData.timestamp,
remoteData.timestamp
);
}
// 离线操作队列
async queueOfflineOperation(operation: OfflineOperation): Promise<void> {
const queue = await this.db.get('offline-queue') || [];
queue.push({
...operation,
id: generateId(),
queuedAt: Date.now()
});
await this.db.put('offline-queue', queue);
// 尝试立即同步
this.syncManager.attemptSync();
}
}
// 跨设备同步管理
export class SyncManager {
private syncQueue: Map<string, SyncTask> = new Map();
private syncInterval: NodeJS.Timeout;
private isOnline: boolean = navigator.onLine;
constructor() {
this.setupNetworkListeners();
this.syncInterval = setInterval(() => {
this.processSyncQueue();
}, 5000); // 每5秒尝试同步一次
}
// 网络状态监听
private setupNetworkListeners(): void {
window.addEventListener('online', () => {
this.isOnline = true;
this.processSyncQueue();
});
window.addEventListener('offline', () => {
this.isOnline = false;
});
}
// 队列同步任务
async queueSync(key: string, data: any): Promise<void> {
const task: SyncTask = {
key,
data,
attempts: 0,
priority: this.calculatePriority(data),
createdAt: Date.now()
};
this.syncQueue.set(key, task);
// 高优先级任务立即尝试同步
if (task.priority === 'high' && this.isOnline) {
this.processTask(task);
}
}
// 处理同步队列
private async processSyncQueue(): Promise<void> {
if (!this.isOnline || this.syncQueue.size === 0) {
return;
}
// 按优先级排序
const tasks = Array.from(this.syncQueue.values())
.sort((a, b) => b.priority - a.priority)
.slice(0, 5); // 每次最多处理5个任务
for (const task of tasks) {
await this.processTask(task);
}
}
// 处理单个同步任务
private async processTask(task: SyncTask): Promise<void> {
try {
task.attempts++;
const result = await this.performSync(task);
if (result.success) {
this.syncQueue.delete(task.key);
document.dispatchEvent(new CustomEvent('sync:success', {
detail: { key: task.key, result }
}));
} else {
// 同步失败,检查是否需要重试
if (task.attempts < this.maxAttempts && this.shouldRetry(task, result)) {
task.nextRetryAt = Date.now() + this.calculateRetryDelay(task);
} else {
this.syncQueue.delete(task.key);
document.dispatchEvent(new CustomEvent('sync:failure', {
detail: { key: task.key, error: result.error }
}));
}
}
} catch (error) {
console.error('同步任务处理失败:', error);
}
}
}
AI 集成的架构设计
AFFiNE 通过 Canvas AI 概念,将 AI 能力深度集成到文档编辑和白板创作中,实现了智能化的内容生成和优化。
AI 工作流引擎
// AI工作流引擎
export class AIWorkflowEngine {
private aiProviders: Map<string, AIProvider> = new Map();
private workflowQueue: PriorityQueue<AIWorkflow> = new Queue();
private contextManager: ContextManager;
constructor() {
this.contextManager = new ContextManager();
this.initializeAIProviders();
}
// 初始化AI提供商
private initializeAIProviders(): void {
// 支持多种AI模型
this.aiProviders.set('openai', new OpenAIProvider());
this.aiProviders.set('claude', new ClaudeProvider());
this.aiProviders.set('local', new LocalAIProvider());
}
// AI工作流执行
async executeWorkflow(workflow: AIWorkflow): Promise<AIResult> {
// 1. 构建上下文
const context = await this.contextManager.buildContext(workflow.context);
// 2. 选择AI模型
const provider = this.selectOptimalProvider(workflow, context);
// 3. 执行AI任务
const result = await provider.execute(workflow.prompt, context);
// 4. 结果处理与集成
return this.processAIResult(result, workflow);
}
// 上下文感知的内容生成
async generateContextualContent(
blockType: string,
currentContent: string,
workspace: WorkspaceContext
): Promise<string> {
const contextualPrompt = this.buildContextualPrompt(blockType, currentContent, workspace);
const workflow: AIWorkflow = {
type: 'generate',
prompt: contextualPrompt,
context: {
blockType,
currentContent,
workspace,
recentBlocks: await this.getRecentBlocks(workspace.currentPage),
userPreferences: workspace.user.preferences
},
options: {
maxTokens: 500,
temperature: 0.7,
blockType: blockType
}
};
return this.executeWorkflow(workflow);
}
// 智能布局优化
async optimizeEdgelessLayout(blocks: BlockNode[]): Promise<LayoutOptimization[]> {
const optimizationWorkflow: AIWorkflow = {
type: 'optimize-layout',
prompt: this.buildLayoutOptimizationPrompt(blocks),
context: {
blocks,
canvasSize: { width: 1920, height: 1080 },
viewportConstraints: { minMargin: 20, preferredSpacing: 40 },
userInteractionHistory: await this.getUserLayoutPreferences()
},
options: {
algorithm: 'genetic',
iterations: 100,
populationSize: 50
}
};
const result = await this.executeWorkflow(optimizationWorkflow);
return this.parseLayoutOptimization(result.content);
}
private buildContextualPrompt(blockType: string, content: string, workspace: WorkspaceContext): string {
const templates = {
text: `作为写作助手,请优化以下文本内容:\n\n${content}\n\n基于当前工作区的上下文:\n${JSON.stringify(workspace.recentActivity, null, 2)}`,
database: `作为数据分析助手,基于以下数据库上下文,生成相关的洞察和建议:\n\n数据内容:\n${content}\n\n工作区信息:\n${JSON.stringify(workspace.pageContext, null, 2)}`,
mindmap: `作为思维导图专家,基于以下内容创建思维导图的结构:\n\n核心内容:\n${content}\n\n用户角色:\n${workspace.user.role}\n项目类型:\n${workspace.pageContext.projectType}`
};
return templates[blockType] || templates.text;
}
}
性能优化与工程实践
虚拟化渲染
// 大数据集的虚拟化渲染
export class VirtualizedRenderer {
private containerRef: React.RefObject<HTMLDivElement>;
private viewportHeight: number;
private itemHeight: number;
private overscan: number = 5;
renderVisibleBlocks(blocks: BlockNode[], scrollTop: number): React.ReactNode {
const startIndex = Math.floor(scrollTop / this.itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(this.viewportHeight / this.itemHeight) + this.overscan,
blocks.length
);
const visibleBlocks = blocks.slice(startIndex, endIndex);
return (
<div
style={{
height: blocks.length * this.itemHeight,
position: 'relative'
}}
>
{visibleBlocks.map((block, index) => (
<div
key={block.id}
style={{
position: 'absolute',
top: (startIndex + index) * this.itemHeight,
left: 0,
right: 0,
height: this.itemHeight
}}
>
{this.renderBlock(block)}
</div>
))}
</div>
);
}
}
内存优化策略
// 内存优化的Block管理
export class OptimizedBlockManager {
private blockCache: LRUCache<string, BlockNode> = new LRUCache(1000);
private lazyLoader: LazyBlockLoader;
// 懒加载Block内容
async loadBlock(id: string): Promise<BlockNode | null> {
// 检查缓存
const cached = this.blockCache.get(id);
if (cached) {
return cached;
}
// 从存储中懒加载
const block = await this.lazyLoader.loadBlock(id);
if (block) {
this.blockCache.set(id, block);
}
return block;
}
// 智能缓存淘汰
private handleMemoryPressure(): void {
if (performance.memory) {
const memoryInfo = performance.memory;
const usedPercentage = memoryInfo.usedJSHeapSize / memoryInfo.jsHeapSizeLimit;
if (usedPercentage > 0.8) {
// 内存使用率超过80%,清理缓存
this.blockCache.clear();
this.triggerGarbageCollection();
}
}
}
}
结语
AFFiNE 通过 TypeScript+Rust 的混合架构,实现了文档编辑与可视化白板的无缝融合,为知识管理工具开辟了新的技术路径。其块级架构设计不仅继承了 Notion"万物皆模块" 的理念,更在工程实现上实现了跨视图的动态渲染和协同编辑。
项目的成功证明了开源技术在复杂应用场景下的可行性。从 Rust 后端的高性能数据处理,到 TypeScript 前端的复杂交互逻辑,再到 CRDT 实现的实时协作能力,每一个技术决策都体现了工程实践的深度思考。
随着 AI 技术的深度集成和本地优先理念的推广,AFFiNE 不仅是一个工具产品,更是下一代知识管理架构的技术实验场。它为开发者提供了宝贵的混合架构实践经验,也为知识管理领域的技术演进指明了方向。
参考资料:
- AFFiNE 官方仓库:https://github.com/toeverything/AFFiNE
- BlockSuite 项目:https://github.com/toeverything/BlockSuite
- OctoBase 数据库:https://github.com/toeverything/OctoBase