[{"data":1,"prerenderedAt":3733},["ShallowReactive",2],{"article-/topics/frontend/high-performance-editor-guide":3,"related-frontend":1014,"content-query-3mCKOtIaHI":2927},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":12,"image":18,"featured":19,"readingTime":20,"body":21,"_type":1008,"_id":1009,"_source":1010,"_file":1011,"_stem":1012,"_extension":1013},"/topics/frontend/high-performance-editor-guide","frontend",false,"","构建高性能富文本编辑器：从架构设计到性能优化完全指南","深入剖析富文本编辑器的核心架构，探讨虚拟滚动、协同编辑、撤销重做等关键技术的实现原理与优化策略，帮助开发者构建流畅的编辑体验。","2024-12-24","HTMLPAGE 团队",[13,14,15,16,17],"富文本编辑器","性能优化","前端架构","ContentEditable","虚拟DOM","/images/topics/frontend-editor-guide.jpg",true,18,{"type":22,"children":23,"toc":973},"root",[24,32,38,43,48,55,69,96,106,116,122,127,152,157,163,168,201,206,211,220,225,230,241,249,301,307,312,321,331,364,369,375,380,389,394,399,408,417,475,480,485,508,513,522,527,535,544,552,561,566,571,576,585,590,595,604,610,615,624,629,634,639,700,705,758,763,803,808,848,853,858,910,915,920],{"type":25,"tag":26,"props":27,"children":29},"element","h2",{"id":28},"构建高性能富文本编辑器从架构设计到性能优化完全指南",[30],{"type":31,"value":8},"text",{"type":25,"tag":33,"props":34,"children":35},"p",{},[36],{"type":31,"value":37},"富文本编辑器是现代 Web 应用中最复杂的组件之一。从简单的博客编辑到专业的文档协作平台，编辑器的性能直接影响用户体验。本文将深入探讨构建高性能编辑器的核心技术与最佳实践。",{"type":25,"tag":26,"props":39,"children":41},{"id":40},"为什么编辑器开发如此困难",[42],{"type":31,"value":40},{"type":25,"tag":33,"props":44,"children":45},{},[46],{"type":31,"value":47},"在开始技术实现之前，我们需要理解富文本编辑器面临的独特挑战：",{"type":25,"tag":49,"props":50,"children":52},"h3",{"id":51},"_1-contenteditable-的历史包袱",[53],{"type":31,"value":54},"1. ContentEditable 的历史包袱",{"type":25,"tag":33,"props":56,"children":57},{},[58,60,67],{"type":31,"value":59},"浏览器原生的 ",{"type":25,"tag":61,"props":62,"children":64},"code",{"className":63},[],[65],{"type":31,"value":66},"contenteditable",{"type":31,"value":68}," 属性看似提供了开箱即用的编辑能力，但实际上充满陷阱：",{"type":25,"tag":33,"props":70,"children":71},{},[72,78,80,86,88,94],{"type":25,"tag":73,"props":74,"children":75},"strong",{},[76],{"type":31,"value":77},"跨浏览器不一致性",{"type":31,"value":79},"：同样的操作在不同浏览器中产生不同的 HTML 结构。例如，按下 Enter 键，Chrome 可能生成 ",{"type":25,"tag":61,"props":81,"children":83},{"className":82},[],[84],{"type":31,"value":85},"\u003Cdiv>",{"type":31,"value":87},"，而 Firefox 生成 ",{"type":25,"tag":61,"props":89,"children":91},{"className":90},[],[92],{"type":31,"value":93},"\u003Cbr>",{"type":31,"value":95},"。",{"type":25,"tag":33,"props":97,"children":98},{},[99,104],{"type":25,"tag":73,"props":100,"children":101},{},[102],{"type":31,"value":103},"Selection API 复杂性",{"type":31,"value":105},"：光标位置和选区的处理在不同浏览器中行为各异，需要大量兼容代码。",{"type":25,"tag":33,"props":107,"children":108},{},[109,114],{"type":25,"tag":73,"props":110,"children":111},{},[112],{"type":31,"value":113},"DOM 操作不可预测",{"type":31,"value":115},"：浏览器会自动\"优化\" HTML 结构，导致内容模型难以维护。",{"type":25,"tag":49,"props":117,"children":119},{"id":118},"_2-性能与功能的平衡",[120],{"type":31,"value":121},"2. 性能与功能的平衡",{"type":25,"tag":33,"props":123,"children":124},{},[125],{"type":31,"value":126},"编辑器需要实时响应用户输入，同时处理：",{"type":25,"tag":128,"props":129,"children":130},"ul",{},[131,137,142,147],{"type":25,"tag":132,"props":133,"children":134},"li",{},[135],{"type":31,"value":136},"复杂的格式化操作",{"type":25,"tag":132,"props":138,"children":139},{},[140],{"type":31,"value":141},"撤销/重做历史管理",{"type":25,"tag":132,"props":143,"children":144},{},[145],{"type":31,"value":146},"实时协同编辑",{"type":25,"tag":132,"props":148,"children":149},{},[150],{"type":31,"value":151},"大文档的渲染和滚动",{"type":25,"tag":33,"props":153,"children":154},{},[155],{"type":31,"value":156},"任何一个环节的性能问题都会导致输入延迟，严重影响用户体验。",{"type":25,"tag":49,"props":158,"children":160},{"id":159},"_3-内容模型的设计抉择",[161],{"type":31,"value":162},"3. 内容模型的设计抉择",{"type":25,"tag":33,"props":164,"children":165},{},[166],{"type":31,"value":167},"编辑器的内容模型决定了整个架构：",{"type":25,"tag":128,"props":169,"children":170},{},[171,181,191],{"type":25,"tag":132,"props":172,"children":173},{},[174,179],{"type":25,"tag":73,"props":175,"children":176},{},[177],{"type":31,"value":178},"基于 HTML",{"type":31,"value":180},"：直接操作 DOM，实现简单但难以维护",{"type":25,"tag":132,"props":182,"children":183},{},[184,189],{"type":25,"tag":73,"props":185,"children":186},{},[187],{"type":31,"value":188},"自定义数据结构",{"type":31,"value":190},"：如 Slate、ProseMirror 的文档模型，灵活但学习成本高",{"type":25,"tag":132,"props":192,"children":193},{},[194,199],{"type":25,"tag":73,"props":195,"children":196},{},[197],{"type":31,"value":198},"CRDT/OT",{"type":31,"value":200},"：支持协同编辑，但实现复杂度极高",{"type":25,"tag":26,"props":202,"children":204},{"id":203},"核心架构设计",[205],{"type":31,"value":203},{"type":25,"tag":33,"props":207,"children":208},{},[209],{"type":31,"value":210},"一个高性能编辑器通常采用分层架构：",{"type":25,"tag":212,"props":213,"children":215},"pre",{"code":214},"┌─────────────────────────────────────┐\n│         用户界面层 (UI Layer)        │\n│  工具栏、菜单、快捷键、拖拽处理        │\n├─────────────────────────────────────┤\n│        视图层 (View Layer)           │\n│  虚拟 DOM、渲染引擎、选区管理         │\n├─────────────────────────────────────┤\n│        模型层 (Model Layer)          │\n│  文档模型、Schema 验证、Transform     │\n├─────────────────────────────────────┤\n│        历史层 (History Layer)        │\n│  撤销/重做、操作合并、状态快照         │\n├─────────────────────────────────────┤\n│        协同层 (Collaboration Layer)  │\n│  OT/CRDT、冲突解决、实时同步          │\n└─────────────────────────────────────┘\n",[216],{"type":25,"tag":61,"props":217,"children":218},{"__ignoreMap":7},[219],{"type":31,"value":214},{"type":25,"tag":49,"props":221,"children":223},{"id":222},"文档模型设计",[224],{"type":31,"value":222},{"type":25,"tag":33,"props":226,"children":227},{},[228],{"type":31,"value":229},"文档模型是编辑器的基础。以下是一个类似 Slate.js 的设计：",{"type":25,"tag":212,"props":231,"children":236},{"code":232,"language":233,"meta":7,"className":234},"// 基础节点类型定义\ninterface BaseNode {\n  type: string;\n  children?: Node[];\n}\n\n// 文本节点 - 编辑器的叶子节点\ninterface TextNode {\n  text: string;\n  // 格式化标记\n  bold?: boolean;\n  italic?: boolean;\n  underline?: boolean;\n  code?: boolean;\n  link?: string;\n}\n\n// 元素节点 - 可包含子节点\ninterface ElementNode extends BaseNode {\n  type: 'paragraph' | 'heading' | 'blockquote' | 'code-block' | 'list-item';\n  level?: number; // 用于标题级别\n  language?: string; // 用于代码块\n  children: (ElementNode | TextNode)[];\n}\n\n// 编辑器状态\ninterface EditorState {\n  document: ElementNode[];\n  selection: Selection | null;\n  history: HistoryState;\n}\n\n// 选区表示\ninterface Selection {\n  anchor: Point; // 选区起点\n  focus: Point;  // 选区终点（可能在 anchor 之前）\n}\n\ninterface Point {\n  path: number[]; // 节点路径，如 [0, 2, 1] 表示第一个块的第三个子节点的第二个子节点\n  offset: number; // 文本偏移量\n}\n","typescript",[235],"language-typescript",[237],{"type":25,"tag":61,"props":238,"children":239},{"__ignoreMap":7},[240],{"type":31,"value":232},{"type":25,"tag":33,"props":242,"children":243},{},[244],{"type":25,"tag":73,"props":245,"children":246},{},[247],{"type":31,"value":248},"为什么采用这种设计？",{"type":25,"tag":250,"props":251,"children":252},"ol",{},[253,263,281,291],{"type":25,"tag":132,"props":254,"children":255},{},[256,261],{"type":25,"tag":73,"props":257,"children":258},{},[259],{"type":31,"value":260},"树形结构",{"type":31,"value":262},"：自然映射到 DOM，便于渲染",{"type":25,"tag":132,"props":264,"children":265},{},[266,271,273,279],{"type":25,"tag":73,"props":267,"children":268},{},[269],{"type":31,"value":270},"路径寻址",{"type":31,"value":272},"：通过 ",{"type":25,"tag":61,"props":274,"children":276},{"className":275},[],[277],{"type":31,"value":278},"path",{"type":31,"value":280}," 数组精确定位任意节点",{"type":25,"tag":132,"props":282,"children":283},{},[284,289],{"type":25,"tag":73,"props":285,"children":286},{},[287],{"type":31,"value":288},"不可变数据",{"type":31,"value":290},"：便于实现撤销重做和性能优化",{"type":25,"tag":132,"props":292,"children":293},{},[294,299],{"type":25,"tag":73,"props":295,"children":296},{},[297],{"type":31,"value":298},"Schema 可验证",{"type":31,"value":300},"：确保文档结构始终有效",{"type":25,"tag":49,"props":302,"children":304},{"id":303},"操作operations系统",[305],{"type":31,"value":306},"操作（Operations）系统",{"type":25,"tag":33,"props":308,"children":309},{},[310],{"type":31,"value":311},"所有对文档的修改都通过标准化的操作来完成：",{"type":25,"tag":212,"props":313,"children":316},{"code":314,"language":233,"meta":7,"className":315},"// 操作类型定义\ntype Operation = \n  | InsertTextOperation\n  | DeleteTextOperation\n  | InsertNodeOperation\n  | RemoveNodeOperation\n  | SetNodeOperation\n  | MergeNodeOperation\n  | SplitNodeOperation\n  | MoveNodeOperation\n  | SetSelectionOperation;\n\n// 插入文本操作\ninterface InsertTextOperation {\n  type: 'insert_text';\n  path: number[];\n  offset: number;\n  text: string;\n}\n\n// 删除文本操作\ninterface DeleteTextOperation {\n  type: 'delete_text';\n  path: number[];\n  offset: number;\n  text: string; // 被删除的文本，用于撤销\n}\n\n// 设置节点属性\ninterface SetNodeOperation {\n  type: 'set_node';\n  path: number[];\n  properties: Partial\u003CElementNode>;\n  newProperties: Partial\u003CElementNode>;\n}\n\n// 操作应用函数\nfunction applyOperation(state: EditorState, op: Operation): EditorState {\n  switch (op.type) {\n    case 'insert_text':\n      return applyInsertText(state, op);\n    case 'delete_text':\n      return applyDeleteText(state, op);\n    case 'set_node':\n      return applySetNode(state, op);\n    // ... 其他操作类型\n  }\n}\n\n// 操作反转 - 用于撤销\nfunction inverseOperation(op: Operation): Operation {\n  switch (op.type) {\n    case 'insert_text':\n      return {\n        type: 'delete_text',\n        path: op.path,\n        offset: op.offset,\n        text: op.text\n      };\n    case 'delete_text':\n      return {\n        type: 'insert_text',\n        path: op.path,\n        offset: op.offset,\n        text: op.text\n      };\n    // ... 其他反转逻辑\n  }\n}\n",[235],[317],{"type":25,"tag":61,"props":318,"children":319},{"__ignoreMap":7},[320],{"type":31,"value":314},{"type":25,"tag":33,"props":322,"children":323},{},[324,329],{"type":25,"tag":73,"props":325,"children":326},{},[327],{"type":31,"value":328},"操作系统的优势",{"type":31,"value":330},"：",{"type":25,"tag":250,"props":332,"children":333},{},[334,344,354],{"type":25,"tag":132,"props":335,"children":336},{},[337,342],{"type":25,"tag":73,"props":338,"children":339},{},[340],{"type":31,"value":341},"原子性",{"type":31,"value":343},"：每个操作都是最小单位，便于组合和回滚",{"type":25,"tag":132,"props":345,"children":346},{},[347,352],{"type":25,"tag":73,"props":348,"children":349},{},[350],{"type":31,"value":351},"可序列化",{"type":31,"value":353},"：操作可以通过网络传输，支持协同编辑",{"type":25,"tag":132,"props":355,"children":356},{},[357,362],{"type":25,"tag":73,"props":358,"children":359},{},[360],{"type":31,"value":361},"可预测性",{"type":31,"value":363},"：相同的操作序列总是产生相同的结果",{"type":25,"tag":26,"props":365,"children":367},{"id":366},"高性能渲染策略",[368],{"type":31,"value":366},{"type":25,"tag":49,"props":370,"children":372},{"id":371},"虚拟-dom-与差量更新",[373],{"type":31,"value":374},"虚拟 DOM 与差量更新",{"type":25,"tag":33,"props":376,"children":377},{},[378],{"type":31,"value":379},"编辑器不应该在每次修改后重新渲染整个文档。我们需要精确计算变更并只更新受影响的部分：",{"type":25,"tag":212,"props":381,"children":384},{"code":382,"language":233,"meta":7,"className":383},"interface VNode {\n  type: string | Component;\n  props: Record\u003Cstring, any>;\n  children: (VNode | string)[];\n  key?: string;\n  el?: HTMLElement; // 对应的真实 DOM 元素\n}\n\nclass EditorRenderer {\n  private container: HTMLElement;\n  private vdom: VNode | null = null;\n\n  render(state: EditorState): void {\n    const newVdom = this.stateToVdom(state);\n    \n    if (this.vdom) {\n      // 差量更新\n      this.patch(this.container, this.vdom, newVdom);\n    } else {\n      // 首次渲染\n      this.container.appendChild(this.createDom(newVdom));\n    }\n    \n    this.vdom = newVdom;\n  }\n\n  private stateToVdom(state: EditorState): VNode {\n    return {\n      type: 'div',\n      props: { \n        class: 'editor-content',\n        contenteditable: 'true',\n        spellcheck: 'false' // 禁用浏览器拼写检查，提升性能\n      },\n      children: state.document.map((block, index) => \n        this.nodeToVdom(block, [index])\n      )\n    };\n  }\n\n  private nodeToVdom(node: ElementNode | TextNode, path: number[]): VNode | string {\n    if ('text' in node) {\n      // 文本节点\n      return this.renderText(node);\n    }\n\n    // 元素节点\n    return {\n      type: this.getTagName(node.type),\n      props: {\n        'data-path': path.join(','),\n        class: this.getNodeClass(node)\n      },\n      children: node.children.map((child, index) => \n        this.nodeToVdom(child as ElementNode | TextNode, [...path, index])\n      ),\n      key: path.join('-')\n    };\n  }\n\n  private renderText(node: TextNode): VNode | string {\n    let content: VNode | string = node.text;\n    \n    // 按格式包装文本\n    if (node.bold) {\n      content = { type: 'strong', props: {}, children: [content] };\n    }\n    if (node.italic) {\n      content = { type: 'em', props: {}, children: [content] };\n    }\n    if (node.code) {\n      content = { type: 'code', props: {}, children: [content] };\n    }\n    if (node.link) {\n      content = { \n        type: 'a', \n        props: { href: node.link, target: '_blank' }, \n        children: [content] \n      };\n    }\n    \n    return content;\n  }\n\n  private patch(parent: HTMLElement, oldVNode: VNode, newVNode: VNode): void {\n    // 类型不同，完全替换\n    if (oldVNode.type !== newVNode.type) {\n      const newEl = this.createDom(newVNode);\n      parent.replaceChild(newEl, oldVNode.el!);\n      return;\n    }\n\n    // 复用 DOM 元素\n    const el = oldVNode.el!;\n    newVNode.el = el;\n\n    // 更新属性\n    this.patchProps(el, oldVNode.props, newVNode.props);\n\n    // 更新子节点（使用 key 优化）\n    this.patchChildren(el, oldVNode.children, newVNode.children);\n  }\n\n  private patchChildren(\n    parent: HTMLElement, \n    oldChildren: (VNode | string)[], \n    newChildren: (VNode | string)[]\n  ): void {\n    // 使用 key 进行高效的子节点对比\n    // 这里简化处理，实际应实现完整的 diff 算法\n    const keyedOld = new Map\u003Cstring, VNode>();\n    \n    oldChildren.forEach(child => {\n      if (typeof child !== 'string' && child.key) {\n        keyedOld.set(child.key, child);\n      }\n    });\n\n    newChildren.forEach((newChild, index) => {\n      if (typeof newChild === 'string') {\n        // 文本节点直接更新\n        const textNode = parent.childNodes[index];\n        if (textNode?.textContent !== newChild) {\n          textNode.textContent = newChild;\n        }\n        return;\n      }\n\n      const oldChild = newChild.key ? keyedOld.get(newChild.key) : oldChildren[index];\n      \n      if (oldChild && typeof oldChild !== 'string') {\n        this.patch(parent, oldChild, newChild);\n      } else {\n        parent.insertBefore(this.createDom(newChild), parent.childNodes[index] || null);\n      }\n    });\n  }\n}\n",[235],[385],{"type":25,"tag":61,"props":386,"children":387},{"__ignoreMap":7},[388],{"type":31,"value":382},{"type":25,"tag":49,"props":390,"children":392},{"id":391},"长文档虚拟滚动",[393],{"type":31,"value":391},{"type":25,"tag":33,"props":395,"children":396},{},[397],{"type":31,"value":398},"当文档包含数千个段落时，渲染所有内容会导致严重的性能问题。虚拟滚动只渲染可见区域内的内容：",{"type":25,"tag":212,"props":400,"children":403},{"code":401,"language":233,"meta":7,"className":402},"interface VirtualScrollState {\n  scrollTop: number;\n  viewportHeight: number;\n  itemHeights: Map\u003Cstring, number>; // 缓存每个块的高度\n  estimatedItemHeight: number;\n}\n\nclass VirtualScrollEditor {\n  private state: VirtualScrollState;\n  private container: HTMLElement;\n  private content: HTMLElement;\n  private document: ElementNode[];\n\n  constructor(container: HTMLElement) {\n    this.container = container;\n    this.state = {\n      scrollTop: 0,\n      viewportHeight: container.clientHeight,\n      itemHeights: new Map(),\n      estimatedItemHeight: 24 // 默认行高估算\n    };\n\n    this.setupScrollListener();\n  }\n\n  private setupScrollListener(): void {\n    // 使用 passive 事件监听器提升滚动性能\n    this.container.addEventListener('scroll', () => {\n      // 使用 requestAnimationFrame 避免过度渲染\n      requestAnimationFrame(() => {\n        this.state.scrollTop = this.container.scrollTop;\n        this.updateVisibleRange();\n      });\n    }, { passive: true });\n  }\n\n  private getVisibleRange(): { start: number; end: number } {\n    const { scrollTop, viewportHeight, itemHeights, estimatedItemHeight } = this.state;\n    \n    // 计算可见区域的起始和结束索引\n    let accumulatedHeight = 0;\n    let startIndex = 0;\n    let endIndex = this.document.length;\n\n    for (let i = 0; i \u003C this.document.length; i++) {\n      const itemHeight = itemHeights.get(String(i)) || estimatedItemHeight;\n      \n      if (accumulatedHeight + itemHeight >= scrollTop && startIndex === 0) {\n        // 添加缓冲区，提前渲染上方内容\n        startIndex = Math.max(0, i - 5);\n      }\n      \n      if (accumulatedHeight >= scrollTop + viewportHeight) {\n        // 添加缓冲区，多渲染下方内容\n        endIndex = Math.min(this.document.length, i + 10);\n        break;\n      }\n      \n      accumulatedHeight += itemHeight;\n    }\n\n    return { start: startIndex, end: endIndex };\n  }\n\n  private updateVisibleRange(): void {\n    const { start, end } = this.getVisibleRange();\n    \n    // 计算占位高度\n    const topPadding = this.getHeightBeforeIndex(start);\n    const bottomPadding = this.getHeightAfterIndex(end);\n\n    // 更新内容区域的 padding 来保持滚动位置\n    this.content.style.paddingTop = `${topPadding}px`;\n    this.content.style.paddingBottom = `${bottomPadding}px`;\n\n    // 只渲染可见范围内的块\n    this.renderBlocks(start, end);\n  }\n\n  private renderBlocks(start: number, end: number): void {\n    const fragment = document.createDocumentFragment();\n    \n    for (let i = start; i \u003C end; i++) {\n      const block = this.document[i];\n      const blockEl = this.renderBlock(block, i);\n      fragment.appendChild(blockEl);\n    }\n\n    // 使用 replaceChildren 一次性更新，减少重排\n    this.content.replaceChildren(fragment);\n\n    // 渲染后测量实际高度并缓存\n    requestAnimationFrame(() => {\n      this.measureRenderedBlocks(start, end);\n    });\n  }\n\n  private measureRenderedBlocks(start: number, end: number): void {\n    const children = this.content.children;\n    \n    for (let i = 0; i \u003C children.length; i++) {\n      const actualIndex = start + i;\n      const height = children[i].getBoundingClientRect().height;\n      this.state.itemHeights.set(String(actualIndex), height);\n    }\n  }\n}\n",[235],[404],{"type":25,"tag":61,"props":405,"children":406},{"__ignoreMap":7},[407],{"type":31,"value":401},{"type":25,"tag":33,"props":409,"children":410},{},[411,416],{"type":25,"tag":73,"props":412,"children":413},{},[414],{"type":31,"value":415},"虚拟滚动的关键优化点",{"type":31,"value":330},{"type":25,"tag":250,"props":418,"children":419},{},[420,430,440,458],{"type":25,"tag":132,"props":421,"children":422},{},[423,428],{"type":25,"tag":73,"props":424,"children":425},{},[426],{"type":31,"value":427},"高度缓存",{"type":31,"value":429},"：避免重复测量 DOM",{"type":25,"tag":132,"props":431,"children":432},{},[433,438],{"type":25,"tag":73,"props":434,"children":435},{},[436],{"type":31,"value":437},"缓冲区渲染",{"type":31,"value":439},"：在可见区域外多渲染一些内容，避免快速滚动时出现空白",{"type":25,"tag":132,"props":441,"children":442},{},[443,448,450,456],{"type":25,"tag":73,"props":444,"children":445},{},[446],{"type":31,"value":447},"滚动节流",{"type":31,"value":449},"：使用 ",{"type":25,"tag":61,"props":451,"children":453},{"className":452},[],[454],{"type":31,"value":455},"requestAnimationFrame",{"type":31,"value":457}," 合并滚动事件",{"type":25,"tag":132,"props":459,"children":460},{},[461,466,467,473],{"type":25,"tag":73,"props":462,"children":463},{},[464],{"type":31,"value":465},"批量 DOM 操作",{"type":31,"value":449},{"type":25,"tag":61,"props":468,"children":470},{"className":469},[],[471],{"type":31,"value":472},"DocumentFragment",{"type":31,"value":474}," 减少重排次数",{"type":25,"tag":26,"props":476,"children":478},{"id":477},"输入处理与性能优化",[479],{"type":31,"value":477},{"type":25,"tag":33,"props":481,"children":482},{},[483],{"type":31,"value":484},"用户输入是编辑器性能的关键瓶颈。每次按键都需要：",{"type":25,"tag":250,"props":486,"children":487},{},[488,493,498,503],{"type":25,"tag":132,"props":489,"children":490},{},[491],{"type":31,"value":492},"捕获输入事件",{"type":25,"tag":132,"props":494,"children":495},{},[496],{"type":31,"value":497},"更新文档模型",{"type":25,"tag":132,"props":499,"children":500},{},[501],{"type":31,"value":502},"重新渲染",{"type":25,"tag":132,"props":504,"children":505},{},[506],{"type":31,"value":507},"恢复光标位置",{"type":25,"tag":49,"props":509,"children":511},{"id":510},"输入事件处理",[512],{"type":31,"value":510},{"type":25,"tag":212,"props":514,"children":517},{"code":515,"language":233,"meta":7,"className":516},"class InputHandler {\n  private editor: Editor;\n  private compositionActive = false; // 输入法组合状态\n  private pendingOperations: Operation[] = [];\n  private flushTimeout: number | null = null;\n\n  constructor(editor: Editor) {\n    this.editor = editor;\n    this.setupEventListeners();\n  }\n\n  private setupEventListeners(): void {\n    const el = this.editor.element;\n\n    // 处理输入法组合输入（中文、日文等）\n    el.addEventListener('compositionstart', () => {\n      this.compositionActive = true;\n    });\n\n    el.addEventListener('compositionend', (e: CompositionEvent) => {\n      this.compositionActive = false;\n      // 组合输入结束后处理完整文本\n      this.handleTextInput(e.data);\n    });\n\n    // beforeinput 事件提供了更好的输入控制\n    el.addEventListener('beforeinput', (e: InputEvent) => {\n      if (this.compositionActive) return; // 组合输入时跳过\n      \n      e.preventDefault(); // 阻止默认行为，自己处理\n      this.handleInputEvent(e);\n    });\n\n    // 处理粘贴\n    el.addEventListener('paste', (e: ClipboardEvent) => {\n      e.preventDefault();\n      this.handlePaste(e);\n    });\n  }\n\n  private handleInputEvent(e: InputEvent): void {\n    const { inputType, data } = e;\n\n    switch (inputType) {\n      case 'insertText':\n        this.handleTextInput(data || '');\n        break;\n      case 'insertParagraph':\n      case 'insertLineBreak':\n        this.handleEnterKey();\n        break;\n      case 'deleteContentBackward':\n        this.handleBackspace();\n        break;\n      case 'deleteContentForward':\n        this.handleDelete();\n        break;\n      case 'deleteByCut':\n        this.handleCut();\n        break;\n      case 'formatBold':\n        this.toggleFormat('bold');\n        break;\n      case 'formatItalic':\n        this.toggleFormat('italic');\n        break;\n      // ... 更多输入类型\n    }\n  }\n\n  private handleTextInput(text: string): void {\n    const { selection } = this.editor.state;\n    if (!selection) return;\n\n    // 如果有选区，先删除选中内容\n    if (!this.isCollapsedSelection(selection)) {\n      this.deleteSelection(selection);\n    }\n\n    // 插入文本\n    const op: InsertTextOperation = {\n      type: 'insert_text',\n      path: selection.anchor.path,\n      offset: selection.anchor.offset,\n      text\n    };\n\n    // 批量处理操作，避免频繁渲染\n    this.queueOperation(op);\n  }\n\n  // 操作批处理 - 关键性能优化\n  private queueOperation(op: Operation): void {\n    this.pendingOperations.push(op);\n\n    // 清除之前的定时器\n    if (this.flushTimeout) {\n      clearTimeout(this.flushTimeout);\n    }\n\n    // 使用 microtask 在当前事件循环结束时批量应用操作\n    this.flushTimeout = window.setTimeout(() => {\n      this.flushOperations();\n    }, 0);\n  }\n\n  private flushOperations(): void {\n    if (this.pendingOperations.length === 0) return;\n\n    const ops = this.pendingOperations;\n    this.pendingOperations = [];\n    this.flushTimeout = null;\n\n    // 批量应用操作\n    this.editor.applyOperations(ops);\n  }\n\n  private handlePaste(e: ClipboardEvent): void {\n    const clipboardData = e.clipboardData;\n    if (!clipboardData) return;\n\n    // 优先处理 HTML 内容\n    const html = clipboardData.getData('text/html');\n    if (html) {\n      const fragment = this.parseHtml(html);\n      this.insertFragment(fragment);\n      return;\n    }\n\n    // 纯文本回退\n    const text = clipboardData.getData('text/plain');\n    if (text) {\n      this.handleTextInput(text);\n    }\n  }\n\n  private parseHtml(html: string): ElementNode[] {\n    // 创建安全的解析环境\n    const parser = new DOMParser();\n    const doc = parser.parseFromString(html, 'text/html');\n    \n    // 清理危险内容\n    this.sanitizeHtml(doc.body);\n    \n    // 转换为编辑器的文档模型\n    return this.domToNodes(doc.body);\n  }\n\n  private sanitizeHtml(element: HTMLElement): void {\n    // 移除脚本标签\n    element.querySelectorAll('script').forEach(el => el.remove());\n    \n    // 移除事件属性\n    element.querySelectorAll('*').forEach(el => {\n      Array.from(el.attributes).forEach(attr => {\n        if (attr.name.startsWith('on')) {\n          el.removeAttribute(attr.name);\n        }\n      });\n    });\n    \n    // 移除 style 标签中的 expression()（IE 漏洞）\n    element.querySelectorAll('style').forEach(el => el.remove());\n  }\n}\n",[235],[518],{"type":25,"tag":61,"props":519,"children":520},{"__ignoreMap":7},[521],{"type":31,"value":515},{"type":25,"tag":49,"props":523,"children":525},{"id":524},"性能优化技巧",[526],{"type":31,"value":524},{"type":25,"tag":33,"props":528,"children":529},{},[530],{"type":25,"tag":73,"props":531,"children":532},{},[533],{"type":31,"value":534},"1. 输入延迟测量与监控",{"type":25,"tag":212,"props":536,"children":539},{"code":537,"language":233,"meta":7,"className":538},"class PerformanceMonitor {\n  private inputLatencies: number[] = [];\n  private renderTimes: number[] = [];\n\n  measureInputLatency(callback: () => void): void {\n    const start = performance.now();\n    \n    callback();\n    \n    // 等待渲染完成后测量\n    requestAnimationFrame(() => {\n      requestAnimationFrame(() => {\n        const latency = performance.now() - start;\n        this.inputLatencies.push(latency);\n        \n        // 如果延迟过高，发出警告\n        if (latency > 100) {\n          console.warn(`High input latency detected: ${latency.toFixed(2)}ms`);\n        }\n        \n        // 只保留最近 100 次测量\n        if (this.inputLatencies.length > 100) {\n          this.inputLatencies.shift();\n        }\n      });\n    });\n  }\n\n  getAverageLatency(): number {\n    if (this.inputLatencies.length === 0) return 0;\n    const sum = this.inputLatencies.reduce((a, b) => a + b, 0);\n    return sum / this.inputLatencies.length;\n  }\n\n  getP95Latency(): number {\n    if (this.inputLatencies.length === 0) return 0;\n    const sorted = [...this.inputLatencies].sort((a, b) => a - b);\n    const index = Math.floor(sorted.length * 0.95);\n    return sorted[index];\n  }\n}\n",[235],[540],{"type":25,"tag":61,"props":541,"children":542},{"__ignoreMap":7},[543],{"type":31,"value":537},{"type":25,"tag":33,"props":545,"children":546},{},[547],{"type":25,"tag":73,"props":548,"children":549},{},[550],{"type":31,"value":551},"2. 智能防抖与节流",{"type":25,"tag":212,"props":553,"children":556},{"code":554,"language":233,"meta":7,"className":555},"class SmartThrottle {\n  private lastCall = 0;\n  private timeout: number | null = null;\n  private pendingArgs: any[] | null = null;\n\n  constructor(\n    private fn: (...args: any[]) => void,\n    private options: {\n      minInterval: number;  // 最小调用间隔\n      maxWait: number;      // 最大等待时间\n      leading: boolean;     // 首次立即执行\n    }\n  ) {}\n\n  call(...args: any[]): void {\n    const now = Date.now();\n    const elapsed = now - this.lastCall;\n\n    // 首次调用或间隔足够长\n    if (this.options.leading && (this.lastCall === 0 || elapsed >= this.options.minInterval)) {\n      this.execute(args);\n      return;\n    }\n\n    // 保存最新参数\n    this.pendingArgs = args;\n\n    // 设置延迟执行\n    if (!this.timeout) {\n      const delay = Math.min(\n        this.options.minInterval - elapsed,\n        this.options.maxWait\n      );\n\n      this.timeout = window.setTimeout(() => {\n        if (this.pendingArgs) {\n          this.execute(this.pendingArgs);\n          this.pendingArgs = null;\n        }\n        this.timeout = null;\n      }, Math.max(0, delay));\n    }\n  }\n\n  private execute(args: any[]): void {\n    this.lastCall = Date.now();\n    this.fn(...args);\n  }\n\n  cancel(): void {\n    if (this.timeout) {\n      clearTimeout(this.timeout);\n      this.timeout = null;\n    }\n    this.pendingArgs = null;\n  }\n}\n\n// 使用示例：优化自动保存\nconst autoSave = new SmartThrottle(\n  (content) => saveToServer(content),\n  {\n    minInterval: 2000,  // 最少 2 秒保存一次\n    maxWait: 10000,     // 最多等待 10 秒\n    leading: false       // 不立即保存，等用户停止输入\n  }\n);\n",[235],[557],{"type":25,"tag":61,"props":558,"children":559},{"__ignoreMap":7},[560],{"type":31,"value":554},{"type":25,"tag":26,"props":562,"children":564},{"id":563},"撤销重做系统",[565],{"type":31,"value":563},{"type":25,"tag":33,"props":567,"children":568},{},[569],{"type":31,"value":570},"撤销重做是编辑器的核心功能之一，直接影响用户体验。",{"type":25,"tag":49,"props":572,"children":574},{"id":573},"基于操作的历史管理",[575],{"type":31,"value":573},{"type":25,"tag":212,"props":577,"children":580},{"code":578,"language":233,"meta":7,"className":579},"interface HistoryState {\n  undoStack: OperationBatch[];\n  redoStack: OperationBatch[];\n}\n\ninterface OperationBatch {\n  operations: Operation[];\n  timestamp: number;\n  selectionBefore: Selection | null;\n  selectionAfter: Selection | null;\n}\n\nclass HistoryManager {\n  private undoStack: OperationBatch[] = [];\n  private redoStack: OperationBatch[] = [];\n  private currentBatch: Operation[] = [];\n  private batchTimeout: number | null = null;\n  private lastOperationTime = 0;\n\n  // 批处理时间窗口（毫秒）\n  private readonly BATCH_WINDOW = 300;\n  // 最大历史记录数\n  private readonly MAX_HISTORY = 100;\n\n  addOperation(op: Operation, selection: Selection | null): void {\n    const now = Date.now();\n    \n    // 如果距离上次操作超过时间窗口，创建新批次\n    if (now - this.lastOperationTime > this.BATCH_WINDOW && this.currentBatch.length > 0) {\n      this.commitBatch();\n    }\n\n    this.currentBatch.push(op);\n    this.lastOperationTime = now;\n\n    // 清空重做栈（新操作后无法重做）\n    this.redoStack = [];\n\n    // 设置批次提交定时器\n    if (this.batchTimeout) {\n      clearTimeout(this.batchTimeout);\n    }\n    this.batchTimeout = window.setTimeout(() => {\n      this.commitBatch();\n    }, this.BATCH_WINDOW);\n  }\n\n  private commitBatch(): void {\n    if (this.currentBatch.length === 0) return;\n\n    const batch: OperationBatch = {\n      operations: [...this.currentBatch],\n      timestamp: Date.now(),\n      selectionBefore: null, // 实际实现中需要记录\n      selectionAfter: null\n    };\n\n    this.undoStack.push(batch);\n    this.currentBatch = [];\n\n    // 限制历史记录数量\n    if (this.undoStack.length > this.MAX_HISTORY) {\n      this.undoStack.shift();\n    }\n\n    if (this.batchTimeout) {\n      clearTimeout(this.batchTimeout);\n      this.batchTimeout = null;\n    }\n  }\n\n  undo(editor: Editor): boolean {\n    // 先提交当前未完成的批次\n    this.commitBatch();\n\n    if (this.undoStack.length === 0) return false;\n\n    const batch = this.undoStack.pop()!;\n    \n    // 反转操作顺序并应用\n    const inverseOps = batch.operations\n      .slice()\n      .reverse()\n      .map(op => inverseOperation(op));\n\n    editor.applyOperations(inverseOps);\n\n    // 移动到重做栈\n    this.redoStack.push(batch);\n\n    // 恢复选区\n    if (batch.selectionBefore) {\n      editor.setSelection(batch.selectionBefore);\n    }\n\n    return true;\n  }\n\n  redo(editor: Editor): boolean {\n    if (this.redoStack.length === 0) return false;\n\n    const batch = this.redoStack.pop()!;\n    \n    // 重新应用原始操作\n    editor.applyOperations(batch.operations);\n\n    // 移回撤销栈\n    this.undoStack.push(batch);\n\n    // 恢复选区\n    if (batch.selectionAfter) {\n      editor.setSelection(batch.selectionAfter);\n    }\n\n    return true;\n  }\n\n  // 合并相似操作（如连续输入）\n  mergeSimilarOperations(ops: Operation[]): Operation[] {\n    const merged: Operation[] = [];\n    \n    for (const op of ops) {\n      const last = merged[merged.length - 1];\n      \n      // 合并连续的文本插入\n      if (\n        last &&\n        last.type === 'insert_text' &&\n        op.type === 'insert_text' &&\n        this.arePathsEqual(last.path, op.path) &&\n        last.offset + last.text.length === op.offset\n      ) {\n        last.text += op.text;\n        continue;\n      }\n      \n      merged.push(op);\n    }\n    \n    return merged;\n  }\n\n  private arePathsEqual(a: number[], b: number[]): boolean {\n    return a.length === b.length && a.every((v, i) => v === b[i]);\n  }\n}\n",[235],[581],{"type":25,"tag":61,"props":582,"children":583},{"__ignoreMap":7},[584],{"type":31,"value":578},{"type":25,"tag":49,"props":586,"children":588},{"id":587},"智能操作合并",[589],{"type":31,"value":587},{"type":25,"tag":33,"props":591,"children":592},{},[593],{"type":31,"value":594},"用户通常希望撤销\"一次编辑\"，而不是每个字符。智能合并让撤销更符合用户预期：",{"type":25,"tag":212,"props":596,"children":599},{"code":597,"language":233,"meta":7,"className":598},"class SmartHistoryManager extends HistoryManager {\n  // 判断是否应该开始新的撤销批次\n  shouldBreakBatch(currentOp: Operation, lastOp: Operation | null): boolean {\n    if (!lastOp) return false;\n\n    // 操作类型不同\n    if (currentOp.type !== lastOp.type) return true;\n\n    // 不同位置的操作\n    if (!this.arePathsEqual(currentOp.path, lastOp.path)) return true;\n\n    // 输入了特殊字符（空格、标点、换行）\n    if (currentOp.type === 'insert_text') {\n      const specialChars = /[\\s.,!?;:'\"()\\[\\]{}]/;\n      if (specialChars.test(currentOp.text)) return true;\n    }\n\n    // 删除操作方向改变\n    if (currentOp.type === 'delete_text' && lastOp.type === 'delete_text') {\n      // 如果删除方向从后向前变成从前向后，开始新批次\n      // 这里需要更复杂的逻辑来判断\n    }\n\n    return false;\n  }\n\n  // 自定义合并策略\n  getMergeStrategy(opType: string): 'always' | 'smart' | 'never' {\n    switch (opType) {\n      case 'insert_text':\n        return 'smart'; // 智能合并连续输入\n      case 'delete_text':\n        return 'smart'; // 智能合并连续删除\n      case 'set_node':\n        return 'never'; // 格式变更不合并\n      default:\n        return 'smart';\n    }\n  }\n}\n",[235],[600],{"type":25,"tag":61,"props":601,"children":602},{"__ignoreMap":7},[603],{"type":31,"value":597},{"type":25,"tag":26,"props":605,"children":607},{"id":606},"实战完整的编辑器组件",[608],{"type":31,"value":609},"实战：完整的编辑器组件",{"type":25,"tag":33,"props":611,"children":612},{},[613],{"type":31,"value":614},"将以上概念整合成一个可用的编辑器组件：",{"type":25,"tag":212,"props":616,"children":619},{"code":617,"language":233,"meta":7,"className":618},"// editor-core.ts\nclass Editor {\n  private container: HTMLElement;\n  private state: EditorState;\n  private renderer: EditorRenderer;\n  private inputHandler: InputHandler;\n  private history: HistoryManager;\n  private plugins: Plugin[] = [];\n\n  constructor(container: HTMLElement, options: EditorOptions = {}) {\n    this.container = container;\n    this.state = this.createInitialState(options.initialContent);\n    this.renderer = new EditorRenderer(container);\n    this.inputHandler = new InputHandler(this);\n    this.history = new HistoryManager();\n\n    // 初始化插件\n    this.plugins = (options.plugins || []).map(Plugin => new Plugin(this));\n\n    // 首次渲染\n    this.render();\n  }\n\n  private createInitialState(content?: ElementNode[]): EditorState {\n    return {\n      document: content || [\n        {\n          type: 'paragraph',\n          children: [{ text: '' }]\n        }\n      ],\n      selection: null,\n      history: {\n        undoStack: [],\n        redoStack: []\n      }\n    };\n  }\n\n  // 应用操作\n  applyOperations(ops: Operation[]): void {\n    let newState = this.state;\n\n    for (const op of ops) {\n      newState = applyOperation(newState, op);\n      this.history.addOperation(op, newState.selection);\n    }\n\n    this.state = newState;\n    \n    // 通知插件\n    this.plugins.forEach(plugin => plugin.onStateChange?.(this.state));\n\n    // 重新渲染\n    this.render();\n  }\n\n  // 渲染\n  private render(): void {\n    this.renderer.render(this.state);\n    \n    // 渲染后恢复选区\n    if (this.state.selection) {\n      this.restoreSelection(this.state.selection);\n    }\n  }\n\n  // 撤销\n  undo(): void {\n    this.history.undo(this);\n  }\n\n  // 重做\n  redo(): void {\n    this.history.redo(this);\n  }\n\n  // 获取内容\n  getContent(): ElementNode[] {\n    return this.state.document;\n  }\n\n  // 获取 HTML\n  getHtml(): string {\n    return this.renderer.toHtml(this.state.document);\n  }\n\n  // 设置选区\n  setSelection(selection: Selection): void {\n    this.state = { ...this.state, selection };\n    this.restoreSelection(selection);\n  }\n\n  private restoreSelection(selection: Selection): void {\n    const domSelection = window.getSelection();\n    if (!domSelection) return;\n\n    try {\n      const anchorNode = this.pathToDomNode(selection.anchor.path);\n      const focusNode = this.pathToDomNode(selection.focus.path);\n\n      if (anchorNode && focusNode) {\n        const range = document.createRange();\n        range.setStart(anchorNode, selection.anchor.offset);\n        range.setEnd(focusNode, selection.focus.offset);\n\n        domSelection.removeAllRanges();\n        domSelection.addRange(range);\n      }\n    } catch (e) {\n      console.error('Failed to restore selection:', e);\n    }\n  }\n\n  private pathToDomNode(path: number[]): Node | null {\n    let node: Element | null = this.container.querySelector('[data-path=\"' + path.slice(0, -1).join(',') + '\"]');\n    \n    if (!node) return null;\n\n    // 找到对应的文本节点\n    const textIndex = path[path.length - 1];\n    let textNodeIndex = 0;\n    \n    for (const child of node.childNodes) {\n      if (child.nodeType === Node.TEXT_NODE) {\n        if (textNodeIndex === textIndex) {\n          return child;\n        }\n        textNodeIndex++;\n      }\n    }\n\n    return node.firstChild;\n  }\n\n  // 销毁\n  destroy(): void {\n    this.plugins.forEach(plugin => plugin.destroy?.());\n    this.inputHandler.destroy?.();\n  }\n}\n\n// 使用示例\nconst editor = new Editor(document.getElementById('editor')!, {\n  initialContent: [\n    {\n      type: 'heading',\n      level: 1,\n      children: [{ text: '欢迎使用高性能编辑器' }]\n    },\n    {\n      type: 'paragraph',\n      children: [{ text: '开始编写您的内容...' }]\n    }\n  ],\n  plugins: [\n    MarkdownShortcutsPlugin,\n    AutoSavePlugin,\n    CollaborationPlugin\n  ]\n});\n",[235],[620],{"type":25,"tag":61,"props":621,"children":622},{"__ignoreMap":7},[623],{"type":31,"value":617},{"type":25,"tag":26,"props":625,"children":627},{"id":626},"性能调优清单",[628],{"type":31,"value":626},{"type":25,"tag":33,"props":630,"children":631},{},[632],{"type":31,"value":633},"在发布编辑器之前，请确保完成以下优化：",{"type":25,"tag":49,"props":635,"children":637},{"id":636},"渲染性能",[638],{"type":31,"value":636},{"type":25,"tag":128,"props":640,"children":643},{"className":641},[642],"contains-task-list",[644,656,665,674,691],{"type":25,"tag":132,"props":645,"children":648},{"className":646},[647],"task-list-item",[649,654],{"type":25,"tag":650,"props":651,"children":653},"input",{"disabled":19,"type":652},"checkbox",[],{"type":31,"value":655}," 使用虚拟 DOM 进行差量更新",{"type":25,"tag":132,"props":657,"children":659},{"className":658},[647],[660,663],{"type":25,"tag":650,"props":661,"children":662},{"disabled":19,"type":652},[],{"type":31,"value":664}," 大文档启用虚拟滚动",{"type":25,"tag":132,"props":666,"children":668},{"className":667},[647],[669,672],{"type":25,"tag":650,"props":670,"children":671},{"disabled":19,"type":652},[],{"type":31,"value":673}," 避免强制同步布局（读写分离）",{"type":25,"tag":132,"props":675,"children":677},{"className":676},[647],[678,681,683,689],{"type":25,"tag":650,"props":679,"children":680},{"disabled":19,"type":652},[],{"type":31,"value":682}," 使用 ",{"type":25,"tag":61,"props":684,"children":686},{"className":685},[],[687],{"type":31,"value":688},"will-change: transform",{"type":31,"value":690}," 优化滚动层",{"type":25,"tag":132,"props":692,"children":694},{"className":693},[647],[695,698],{"type":25,"tag":650,"props":696,"children":697},{"disabled":19,"type":652},[],{"type":31,"value":699}," 禁用不必要的浏览器功能（拼写检查、自动填充）",{"type":25,"tag":49,"props":701,"children":703},{"id":702},"输入性能",[704],{"type":31,"value":702},{"type":25,"tag":128,"props":706,"children":708},{"className":707},[642],[709,718,740,749],{"type":25,"tag":132,"props":710,"children":712},{"className":711},[647],[713,716],{"type":25,"tag":650,"props":714,"children":715},{"disabled":19,"type":652},[],{"type":31,"value":717}," 操作批处理减少渲染次数",{"type":25,"tag":132,"props":719,"children":721},{"className":720},[647],[722,725,726,732,734],{"type":25,"tag":650,"props":723,"children":724},{"disabled":19,"type":652},[],{"type":31,"value":682},{"type":25,"tag":61,"props":727,"children":729},{"className":728},[],[730],{"type":31,"value":731},"beforeinput",{"type":31,"value":733}," 替代 ",{"type":25,"tag":61,"props":735,"children":737},{"className":736},[],[738],{"type":31,"value":739},"keydown/keypress",{"type":25,"tag":132,"props":741,"children":743},{"className":742},[647],[744,747],{"type":25,"tag":650,"props":745,"children":746},{"disabled":19,"type":652},[],{"type":31,"value":748}," 正确处理输入法组合输入",{"type":25,"tag":132,"props":750,"children":752},{"className":751},[647],[753,756],{"type":25,"tag":650,"props":754,"children":755},{"disabled":19,"type":652},[],{"type":31,"value":757}," 粘贴内容异步解析",{"type":25,"tag":49,"props":759,"children":761},{"id":760},"内存管理",[762],{"type":31,"value":760},{"type":25,"tag":128,"props":764,"children":766},{"className":765},[642],[767,776,785,794],{"type":25,"tag":132,"props":768,"children":770},{"className":769},[647],[771,774],{"type":25,"tag":650,"props":772,"children":773},{"disabled":19,"type":652},[],{"type":31,"value":775}," 限制历史记录数量",{"type":25,"tag":132,"props":777,"children":779},{"className":778},[647],[780,783],{"type":25,"tag":650,"props":781,"children":782},{"disabled":19,"type":652},[],{"type":31,"value":784}," 大文档使用惰性加载",{"type":25,"tag":132,"props":786,"children":788},{"className":787},[647],[789,792],{"type":25,"tag":650,"props":790,"children":791},{"disabled":19,"type":652},[],{"type":31,"value":793}," 及时清理事件监听器",{"type":25,"tag":132,"props":795,"children":797},{"className":796},[647],[798,801],{"type":25,"tag":650,"props":799,"children":800},{"disabled":19,"type":652},[],{"type":31,"value":802}," 避免闭包导致的内存泄漏",{"type":25,"tag":49,"props":804,"children":806},{"id":805},"用户体验",[807],{"type":31,"value":805},{"type":25,"tag":128,"props":809,"children":811},{"className":810},[642],[812,821,830,839],{"type":25,"tag":132,"props":813,"children":815},{"className":814},[647],[816,819],{"type":25,"tag":650,"props":817,"children":818},{"disabled":19,"type":652},[],{"type":31,"value":820}," 输入延迟 \u003C 100ms",{"type":25,"tag":132,"props":822,"children":824},{"className":823},[647],[825,828],{"type":25,"tag":650,"props":826,"children":827},{"disabled":19,"type":652},[],{"type":31,"value":829}," 滚动流畅（60fps）",{"type":25,"tag":132,"props":831,"children":833},{"className":832},[647],[834,837],{"type":25,"tag":650,"props":835,"children":836},{"disabled":19,"type":652},[],{"type":31,"value":838}," 撤销重做符合用户预期",{"type":25,"tag":132,"props":840,"children":842},{"className":841},[647],[843,846],{"type":25,"tag":650,"props":844,"children":845},{"disabled":19,"type":652},[],{"type":31,"value":847}," 提供加载状态反馈",{"type":25,"tag":26,"props":849,"children":851},{"id":850},"总结",[852],{"type":31,"value":850},{"type":25,"tag":33,"props":854,"children":855},{},[856],{"type":31,"value":857},"构建高性能富文本编辑器是前端开发中最具挑战性的任务之一。本文介绍了：",{"type":25,"tag":250,"props":859,"children":860},{},[861,870,880,890,900],{"type":25,"tag":132,"props":862,"children":863},{},[864,868],{"type":25,"tag":73,"props":865,"children":866},{},[867],{"type":31,"value":222},{"type":31,"value":869},"：采用树形结构和不可变数据，便于操作和维护",{"type":25,"tag":132,"props":871,"children":872},{},[873,878],{"type":25,"tag":73,"props":874,"children":875},{},[876],{"type":31,"value":877},"操作系统",{"type":31,"value":879},"：所有修改通过标准化操作完成，支持撤销重做和协同编辑",{"type":25,"tag":132,"props":881,"children":882},{},[883,888],{"type":25,"tag":73,"props":884,"children":885},{},[886],{"type":31,"value":887},"高效渲染",{"type":31,"value":889},"：虚拟 DOM 差量更新和虚拟滚动处理大文档",{"type":25,"tag":132,"props":891,"children":892},{},[893,898],{"type":25,"tag":73,"props":894,"children":895},{},[896],{"type":31,"value":897},"输入优化",{"type":31,"value":899},"：操作批处理、输入法支持、性能监控",{"type":25,"tag":132,"props":901,"children":902},{},[903,908],{"type":25,"tag":73,"props":904,"children":905},{},[906],{"type":31,"value":907},"历史管理",{"type":31,"value":909},"：智能操作合并，提供自然的撤销体验",{"type":25,"tag":33,"props":911,"children":912},{},[913],{"type":31,"value":914},"编辑器开发没有银弹，需要根据具体场景在功能、性能和复杂度之间取得平衡。建议从成熟的开源项目（如 Slate、ProseMirror、Lexical）入手，理解其设计思想后再进行定制开发。",{"type":25,"tag":26,"props":916,"children":918},{"id":917},"延伸阅读",[919],{"type":31,"value":917},{"type":25,"tag":128,"props":921,"children":922},{},[923,937,949,961],{"type":25,"tag":132,"props":924,"children":925},{},[926,935],{"type":25,"tag":927,"props":928,"children":932},"a",{"href":929,"rel":930},"https://docs.slatejs.org/",[931],"nofollow",[933],{"type":31,"value":934},"Slate.js 官方文档",{"type":31,"value":936}," - 高度可定制的富文本编辑框架",{"type":25,"tag":132,"props":938,"children":939},{},[940,947],{"type":25,"tag":927,"props":941,"children":944},{"href":942,"rel":943},"https://prosemirror.net/docs/guide/",[931],[945],{"type":31,"value":946},"ProseMirror 指南",{"type":31,"value":948}," - 学术级的编辑器框架",{"type":25,"tag":132,"props":950,"children":951},{},[952,959],{"type":25,"tag":927,"props":953,"children":956},{"href":954,"rel":955},"https://lexical.dev/",[931],[957],{"type":31,"value":958},"Lexical 介绍",{"type":31,"value":960}," - Facebook 开源的现代编辑器框架",{"type":25,"tag":132,"props":962,"children":963},{},[964,971],{"type":25,"tag":927,"props":965,"children":968},{"href":966,"rel":967},"https://ckeditor.com/docs/ckeditor5/latest/framework/architecture/intro.html",[931],[969],{"type":31,"value":970},"CKEditor 5 架构",{"type":31,"value":972}," - 企业级编辑器的设计思路",{"title":7,"searchDepth":974,"depth":974,"links":975},3,[976,978,983,987,991,995,999,1000,1006,1007],{"id":28,"depth":977,"text":8},2,{"id":40,"depth":977,"text":40,"children":979},[980,981,982],{"id":51,"depth":974,"text":54},{"id":118,"depth":974,"text":121},{"id":159,"depth":974,"text":162},{"id":203,"depth":977,"text":203,"children":984},[985,986],{"id":222,"depth":974,"text":222},{"id":303,"depth":974,"text":306},{"id":366,"depth":977,"text":366,"children":988},[989,990],{"id":371,"depth":974,"text":374},{"id":391,"depth":974,"text":391},{"id":477,"depth":977,"text":477,"children":992},[993,994],{"id":510,"depth":974,"text":510},{"id":524,"depth":974,"text":524},{"id":563,"depth":977,"text":563,"children":996},[997,998],{"id":573,"depth":974,"text":573},{"id":587,"depth":974,"text":587},{"id":606,"depth":977,"text":609},{"id":626,"depth":977,"text":626,"children":1001},[1002,1003,1004,1005],{"id":636,"depth":974,"text":636},{"id":702,"depth":974,"text":702},{"id":760,"depth":974,"text":760},{"id":805,"depth":974,"text":805},{"id":850,"depth":977,"text":850},{"id":917,"depth":977,"text":917},"markdown","content:topics:frontend:high-performance-editor-guide.md","content","topics/frontend/high-performance-editor-guide.md","topics/frontend/high-performance-editor-guide","md",[1015,1340,1652],{"_path":1016,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1017,"description":1018,"keywords":1019,"image":1025,"author":1026,"date":1027,"readingTime":1028,"topic":5,"body":1029,"_type":1008,"_id":1337,"_source":1010,"_file":1338,"_stem":1339,"_extension":1013},"/topics/frontend/react-hooks-guide","React Hooks 完全指南","全面讲解 React Hooks，包括内置钩子、自定义钩子和最佳实践",[1020,1021,1022,1023,1024],"React","Hooks","自定义钩子","状态管理","函数组件","/images/topics/react-hooks-guide.jpg","AI Content Team","2025-12-08",23,{"type":22,"children":1030,"toc":1318},[1031,1036,1041,1047,1053,1064,1070,1079,1085,1094,1100,1106,1115,1121,1130,1136,1145,1151,1160,1165,1171,1180,1185,1197,1225,1236,1264,1269],{"type":25,"tag":26,"props":1032,"children":1034},{"id":1033},"react-hooks-完全指南",[1035],{"type":31,"value":1017},{"type":25,"tag":33,"props":1037,"children":1038},{},[1039],{"type":31,"value":1040},"Hooks 改变了 React 的开发方式。本文全面讲解如何使用和创建 Hooks。",{"type":25,"tag":26,"props":1042,"children":1044},{"id":1043},"内置-hooks",[1045],{"type":31,"value":1046},"内置 Hooks",{"type":25,"tag":49,"props":1048,"children":1050},{"id":1049},"usestate-状态管理",[1051],{"type":31,"value":1052},"useState - 状态管理",{"type":25,"tag":212,"props":1054,"children":1059},{"className":1055,"code":1057,"language":1058,"meta":7},[1056],"language-javascript","import { useState } from 'react'\n\nfunction Counter() {\n  const [count, setCount] = useState(0)\n  const [name, setName] = useState('John')\n  const [user, setUser] = useState({\n    age: 30,\n    email: 'john@example.com',\n  })\n  \n  // 使用函数初始化状态（对于复杂初始值）\n  const [data, setData] = useState(() => {\n    console.log('初始化数据...')\n    return fetchInitialData() // 仅在首次渲染时调用\n  })\n  \n  return (\n    \u003Cdiv>\n      \u003Cp>计数: {count}\u003C/p>\n      \u003Cbutton onClick={() => setCount(count + 1)}>增加\u003C/button>\n      \n      {/* 函数式更新 */}\n      \u003Cbutton onClick={() => setCount(prev => prev + 1)}>\n        函数式增加\n      \u003C/button>\n      \n      {/* 更新对象 */}\n      \u003Cbutton onClick={() => setUser({ ...user, age: user.age + 1 })}>\n        增加年龄\n      \u003C/button>\n    \u003C/div>\n  )\n}\n","javascript",[1060],{"type":25,"tag":61,"props":1061,"children":1062},{"__ignoreMap":7},[1063],{"type":31,"value":1057},{"type":25,"tag":49,"props":1065,"children":1067},{"id":1066},"useeffect-副作用处理",[1068],{"type":31,"value":1069},"useEffect - 副作用处理",{"type":25,"tag":212,"props":1071,"children":1074},{"className":1072,"code":1073,"language":1058,"meta":7},[1056],"import { useState, useEffect } from 'react'\n\nfunction DataFetcher() {\n  const [data, setData] = useState(null)\n  const [loading, setLoading] = useState(true)\n  const [error, setError] = useState(null)\n  const [userId, setUserId] = useState(1)\n  \n  // 副作用 - 每次渲染后执行\n  useEffect(() => {\n    console.log('组件已挂载或已更新')\n  })\n  \n  // 挂载时执行一次\n  useEffect(() => {\n    console.log('组件已挂载')\n    \n    return () => {\n      console.log('组件已卸载')\n    }\n  }, [])\n  \n  // 当 userId 改变时执行\n  useEffect(() => {\n    let isMounted = true // 防止内存泄漏\n    \n    const fetchData = async () => {\n      setLoading(true)\n      try {\n        const response = await fetch(\\`/api/users/\\${userId}\\`)\n        const result = await response.json()\n        \n        if (isMounted) {\n          setData(result)\n        }\n      } catch (err) {\n        if (isMounted) {\n          setError(err)\n        }\n      } finally {\n        if (isMounted) {\n          setLoading(false)\n        }\n      }\n    }\n    \n    fetchData()\n    \n    // 清理函数\n    return () => {\n      isMounted = false\n    }\n  }, [userId])\n  \n  if (loading) return \u003Cp>加载中...\u003C/p>\n  if (error) return \u003Cp>错误: {error.message}\u003C/p>\n  \n  return \u003Cdiv>{data && JSON.stringify(data)}\u003C/div>\n}\n",[1075],{"type":25,"tag":61,"props":1076,"children":1077},{"__ignoreMap":7},[1078],{"type":31,"value":1073},{"type":25,"tag":49,"props":1080,"children":1082},{"id":1081},"usecontext-跨组件通信",[1083],{"type":31,"value":1084},"useContext - 跨组件通信",{"type":25,"tag":212,"props":1086,"children":1089},{"className":1087,"code":1088,"language":1058,"meta":7},[1056],"import { createContext, useContext, useState } from 'react'\n\n// 创建上下文\nconst ThemeContext = createContext()\n\n// 提供者组件\nfunction ThemeProvider({ children }) {\n  const [theme, setTheme] = useState('light')\n  \n  const toggleTheme = () => {\n    setTheme(prev => prev === 'light' ? 'dark' : 'light')\n  }\n  \n  const value = { theme, toggleTheme }\n  \n  return (\n    \u003CThemeContext.Provider value={value}>\n      {children}\n    \u003C/ThemeContext.Provider>\n  )\n}\n\n// 使用 Hook\nfunction useTheme() {\n  const context = useContext(ThemeContext)\n  \n  if (!context) {\n    throw new Error('useTheme 必须在 ThemeProvider 内使用')\n  }\n  \n  return context\n}\n\n// 组件使用\nfunction App() {\n  const { theme, toggleTheme } = useTheme()\n  \n  return (\n    \u003Cdiv style={{\n      background: theme === 'light' ? '#fff' : '#333',\n      color: theme === 'light' ? '#000' : '#fff',\n    }}>\n      \u003Cp>当前主题: {theme}\u003C/p>\n      \u003Cbutton onClick={toggleTheme}>切换主题\u003C/button>\n    \u003C/div>\n  )\n}\n\n// 使用\nexport default function Root() {\n  return (\n    \u003CThemeProvider>\n      \u003CApp />\n    \u003C/ThemeProvider>\n  )\n}\n",[1090],{"type":25,"tag":61,"props":1091,"children":1092},{"__ignoreMap":7},[1093],{"type":31,"value":1088},{"type":25,"tag":26,"props":1095,"children":1097},{"id":1096},"自定义-hooks",[1098],{"type":31,"value":1099},"自定义 Hooks",{"type":25,"tag":49,"props":1101,"children":1103},{"id":1102},"uselocalstorage",[1104],{"type":31,"value":1105},"useLocalStorage",{"type":25,"tag":212,"props":1107,"children":1110},{"className":1108,"code":1109,"language":1058,"meta":7},[1056],"import { useState, useEffect } from 'react'\n\nfunction useLocalStorage(key, initialValue) {\n  // 从本地存储获取初始值\n  const [storedValue, setStoredValue] = useState(() => {\n    try {\n      const item = window.localStorage.getItem(key)\n      return item ? JSON.parse(item) : initialValue\n    } catch (error) {\n      console.error(error)\n      return initialValue\n    }\n  })\n  \n  // 当值改变时更新本地存储\n  const setValue = (value) => {\n    try {\n      const valueToStore = value instanceof Function ? value(storedValue) : value\n      setStoredValue(valueToStore)\n      window.localStorage.setItem(key, JSON.stringify(valueToStore))\n    } catch (error) {\n      console.error(error)\n    }\n  }\n  \n  return [storedValue, setValue]\n}\n\n// 使用\nfunction App() {\n  const [name, setName] = useLocalStorage('name', 'Guest')\n  \n  return (\n    \u003Cdiv>\n      \u003Cp>姓名: {name}\u003C/p>\n      \u003Cinput\n        value={name}\n        onChange={(e) => setName(e.target.value)}\n      />\n    \u003C/div>\n  )\n}\n",[1111],{"type":25,"tag":61,"props":1112,"children":1113},{"__ignoreMap":7},[1114],{"type":31,"value":1109},{"type":25,"tag":49,"props":1116,"children":1118},{"id":1117},"useasync-异步操作",[1119],{"type":31,"value":1120},"useAsync - 异步操作",{"type":25,"tag":212,"props":1122,"children":1125},{"className":1123,"code":1124,"language":1058,"meta":7},[1056],"import { useState, useEffect, useRef } from 'react'\n\nfunction useAsync(asyncFunction, immediate = true) {\n  const [status, setStatus] = useState('idle')\n  const [value, setValue] = useState(null)\n  const [error, setError] = useState(null)\n  \n  // 使用 ref 来防止无限循环\n  const executeRef = useRef(null)\n  \n  const execute = useRef(async () => {\n    setStatus('pending')\n    setValue(null)\n    setError(null)\n    \n    try {\n      const response = await asyncFunction()\n      setValue(response)\n      setStatus('success')\n      return response\n    } catch (error) {\n      setError(error)\n      setStatus('error')\n    }\n  })\n  \n  executeRef.current = execute.current\n  \n  useEffect(() => {\n    if (!immediate) return\n    \n    executeRef.current()\n  }, [immediate])\n  \n  return { execute: executeRef.current, status, value, error }\n}\n\n// 使用\nfunction UserProfile({ userId }) {\n  const { execute, status, value: user, error } = useAsync(\n    () => fetch(\\`/api/users/\\${userId}\\`).then(r => r.json()),\n    true\n  )\n  \n  if (status === 'pending') return \u003Cp>加载中...\u003C/p>\n  if (status === 'error') return \u003Cp>错误: {error?.message}\u003C/p>\n  if (status === 'success') return \u003Cp>用户: {user?.name}\u003C/p>\n  \n  return null\n}\n",[1126],{"type":25,"tag":61,"props":1127,"children":1128},{"__ignoreMap":7},[1129],{"type":31,"value":1124},{"type":25,"tag":49,"props":1131,"children":1133},{"id":1132},"usefetch-数据获取",[1134],{"type":31,"value":1135},"useFetch - 数据获取",{"type":25,"tag":212,"props":1137,"children":1140},{"className":1138,"code":1139,"language":1058,"meta":7},[1056],"import { useState, useEffect } from 'react'\n\nfunction useFetch(url, options = {}) {\n  const [data, setData] = useState(null)\n  const [loading, setLoading] = useState(true)\n  const [error, setError] = useState(null)\n  \n  useEffect(() => {\n    let isMounted = true\n    \n    const fetchData = async () => {\n      try {\n        const response = await fetch(url, {\n          method: 'GET',\n          ...options,\n        })\n        \n        if (!response.ok) {\n          throw new Error(\\`HTTP error! status: \\${response.status}\\`)\n        }\n        \n        const result = await response.json()\n        \n        if (isMounted) {\n          setData(result)\n          setError(null)\n        }\n      } catch (err) {\n        if (isMounted) {\n          setError(err)\n          setData(null)\n        }\n      } finally {\n        if (isMounted) {\n          setLoading(false)\n        }\n      }\n    }\n    \n    fetchData()\n    \n    return () => {\n      isMounted = false\n    }\n  }, [url, options])\n  \n  const refetch = async () => {\n    setLoading(true)\n    try {\n      const response = await fetch(url, options)\n      const result = await response.json()\n      setData(result)\n    } catch (err) {\n      setError(err)\n    } finally {\n      setLoading(false)\n    }\n  }\n  \n  return { data, loading, error, refetch }\n}\n\n// 使用\nfunction UserList() {\n  const { data: users, loading, error, refetch } = useFetch('/api/users')\n  \n  if (loading) return \u003Cp>加载中...\u003C/p>\n  if (error) return \u003Cp>错误: {error.message}\u003C/p>\n  \n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={refetch}>刷新\u003C/button>\n      \u003Cul>\n        {users?.map(user => (\n          \u003Cli key={user.id}>{user.name}\u003C/li>\n        ))}\n      \u003C/ul>\n    \u003C/div>\n  )\n}\n",[1141],{"type":25,"tag":61,"props":1142,"children":1143},{"__ignoreMap":7},[1144],{"type":31,"value":1139},{"type":25,"tag":49,"props":1146,"children":1148},{"id":1147},"useprevious-保存前一个值",[1149],{"type":31,"value":1150},"usePrevious - 保存前一个值",{"type":25,"tag":212,"props":1152,"children":1155},{"className":1153,"code":1154,"language":1058,"meta":7},[1056],"import { useEffect, useRef } from 'react'\n\nfunction usePrevious(value) {\n  const ref = useRef()\n  \n  useEffect(() => {\n    ref.current = value\n  }, [value])\n  \n  return ref.current\n}\n\n// 使用\nfunction Counter() {\n  const [count, setCount] = React.useState(0)\n  const prevCount = usePrevious(count)\n  \n  return (\n    \u003Cdiv>\n      \u003Cp>当前: {count}, 前一个: {prevCount}\u003C/p>\n      \u003Cbutton onClick={() => setCount(count + 1)}>增加\u003C/button>\n    \u003C/div>\n  )\n}\n",[1156],{"type":25,"tag":61,"props":1157,"children":1158},{"__ignoreMap":7},[1159],{"type":31,"value":1154},{"type":25,"tag":26,"props":1161,"children":1163},{"id":1162},"高级模式",[1164],{"type":31,"value":1162},{"type":25,"tag":49,"props":1166,"children":1168},{"id":1167},"usereducer-复杂状态管理",[1169],{"type":31,"value":1170},"useReducer - 复杂状态管理",{"type":25,"tag":212,"props":1172,"children":1175},{"className":1173,"code":1174,"language":1058,"meta":7},[1056],"import { useReducer } from 'react'\n\nconst initialState = {\n  todos: [],\n  filter: 'all',\n  error: null,\n}\n\nfunction todoReducer(state, action) {\n  switch (action.type) {\n    case 'ADD_TODO':\n      return {\n        ...state,\n        todos: [...state.todos, { id: Date.now(), text: action.payload }],\n      }\n    \n    case 'REMOVE_TODO':\n      return {\n        ...state,\n        todos: state.todos.filter(todo => todo.id !== action.payload),\n      }\n    \n    case 'SET_FILTER':\n      return { ...state, filter: action.payload }\n    \n    case 'SET_ERROR':\n      return { ...state, error: action.payload }\n    \n    default:\n      return state\n  }\n}\n\nfunction TodoApp() {\n  const [state, dispatch] = useReducer(todoReducer, initialState)\n  \n  const addTodo = (text) => {\n    dispatch({ type: 'ADD_TODO', payload: text })\n  }\n  \n  const removeTodo = (id) => {\n    dispatch({ type: 'REMOVE_TODO', payload: id })\n  }\n  \n  return (\n    \u003Cdiv>\n      {state.todos.map(todo => (\n        \u003Cdiv key={todo.id}>\n          {todo.text}\n          \u003Cbutton onClick={() => removeTodo(todo.id)}>删除\u003C/button>\n        \u003C/div>\n      ))}\n    \u003C/div>\n  )\n}\n",[1176],{"type":25,"tag":61,"props":1177,"children":1178},{"__ignoreMap":7},[1179],{"type":31,"value":1174},{"type":25,"tag":26,"props":1181,"children":1183},{"id":1182},"最佳实践",[1184],{"type":31,"value":1182},{"type":25,"tag":33,"props":1186,"children":1187},{},[1188,1190,1195],{"type":31,"value":1189},"✅ ",{"type":25,"tag":73,"props":1191,"children":1192},{},[1193],{"type":31,"value":1194},"应该做的事",{"type":31,"value":1196},":",{"type":25,"tag":128,"props":1198,"children":1199},{},[1200,1205,1210,1215,1220],{"type":25,"tag":132,"props":1201,"children":1202},{},[1203],{"type":31,"value":1204},"将相关逻辑提取到自定义 Hooks",{"type":25,"tag":132,"props":1206,"children":1207},{},[1208],{"type":31,"value":1209},"在 useEffect 的依赖数组中包含所有依赖",{"type":25,"tag":132,"props":1211,"children":1212},{},[1213],{"type":31,"value":1214},"使用 useCallback 和 useMemo 优化性能",{"type":25,"tag":132,"props":1216,"children":1217},{},[1218],{"type":31,"value":1219},"为自定义 Hooks 编写文档",{"type":25,"tag":132,"props":1221,"children":1222},{},[1223],{"type":31,"value":1224},"及时清理副作用",{"type":25,"tag":33,"props":1226,"children":1227},{},[1228,1230,1235],{"type":31,"value":1229},"❌ ",{"type":25,"tag":73,"props":1231,"children":1232},{},[1233],{"type":31,"value":1234},"不应该做的事",{"type":31,"value":1196},{"type":25,"tag":128,"props":1237,"children":1238},{},[1239,1244,1249,1254,1259],{"type":25,"tag":132,"props":1240,"children":1241},{},[1242],{"type":31,"value":1243},"在条件或循环中调用 Hooks",{"type":25,"tag":132,"props":1245,"children":1246},{},[1247],{"type":31,"value":1248},"在普通函数中调用 Hooks",{"type":25,"tag":132,"props":1250,"children":1251},{},[1252],{"type":31,"value":1253},"忘记依赖数组",{"type":25,"tag":132,"props":1255,"children":1256},{},[1257],{"type":31,"value":1258},"过度使用 useMemo/useCallback",{"type":25,"tag":132,"props":1260,"children":1261},{},[1262],{"type":31,"value":1263},"在 Hooks 中创建过多的闭包",{"type":25,"tag":26,"props":1265,"children":1267},{"id":1266},"检查清单",[1268],{"type":31,"value":1266},{"type":25,"tag":128,"props":1270,"children":1272},{"className":1271},[642],[1273,1282,1291,1300,1309],{"type":25,"tag":132,"props":1274,"children":1276},{"className":1275},[647],[1277,1280],{"type":25,"tag":650,"props":1278,"children":1279},{"disabled":19,"type":652},[],{"type":31,"value":1281}," Hooks 调用顺序正确",{"type":25,"tag":132,"props":1283,"children":1285},{"className":1284},[647],[1286,1289],{"type":25,"tag":650,"props":1287,"children":1288},{"disabled":19,"type":652},[],{"type":31,"value":1290}," 依赖数组完整",{"type":25,"tag":132,"props":1292,"children":1294},{"className":1293},[647],[1295,1298],{"type":25,"tag":650,"props":1296,"children":1297},{"disabled":19,"type":652},[],{"type":31,"value":1299}," 副作用正确清理",{"type":25,"tag":132,"props":1301,"children":1303},{"className":1302},[647],[1304,1307],{"type":25,"tag":650,"props":1305,"children":1306},{"disabled":19,"type":652},[],{"type":31,"value":1308}," 性能优化得当",{"type":25,"tag":132,"props":1310,"children":1312},{"className":1311},[647],[1313,1316],{"type":25,"tag":650,"props":1314,"children":1315},{"disabled":19,"type":652},[],{"type":31,"value":1317}," 代码易于理解和测试",{"title":7,"searchDepth":974,"depth":974,"links":1319},[1320,1321,1326,1332,1335,1336],{"id":1033,"depth":977,"text":1017},{"id":1043,"depth":977,"text":1046,"children":1322},[1323,1324,1325],{"id":1049,"depth":974,"text":1052},{"id":1066,"depth":974,"text":1069},{"id":1081,"depth":974,"text":1084},{"id":1096,"depth":977,"text":1099,"children":1327},[1328,1329,1330,1331],{"id":1102,"depth":974,"text":1105},{"id":1117,"depth":974,"text":1120},{"id":1132,"depth":974,"text":1135},{"id":1147,"depth":974,"text":1150},{"id":1162,"depth":977,"text":1162,"children":1333},[1334],{"id":1167,"depth":974,"text":1170},{"id":1182,"depth":977,"text":1182},{"id":1266,"depth":977,"text":1266},"content:topics:frontend:react-hooks-guide.md","topics/frontend/react-hooks-guide.md","topics/frontend/react-hooks-guide",{"_path":1341,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1342,"description":1343,"keywords":1344,"image":1350,"author":1026,"date":1027,"readingTime":1351,"topic":5,"body":1352,"_type":1008,"_id":1649,"_source":1010,"_file":1650,"_stem":1651,"_extension":1013},"/topics/frontend/vue3-composition-api","Vue 3 Composition API 深度解析","全面讲解 Vue 3 Composition API 的用法、最佳实践和高级模式",[1345,1346,1347,1348,1349],"Vue 3","Composition API","组合式函数","响应式系统","前端开发","/images/topics/vue3-composition-api.jpg",22,{"type":22,"children":1353,"toc":1625},[1354,1359,1364,1369,1375,1384,1389,1398,1402,1407,1416,1421,1427,1436,1441,1447,1456,1461,1466,1475,1480,1486,1495,1499,1508,1536,1545,1573,1577],{"type":25,"tag":26,"props":1355,"children":1357},{"id":1356},"vue-3-composition-api-深度解析",[1358],{"type":31,"value":1342},{"type":25,"tag":33,"props":1360,"children":1361},{},[1362],{"type":31,"value":1363},"Composition API 让 Vue 应用更易于组织和重用逻辑。本文深入讲解这一核心特性。",{"type":25,"tag":26,"props":1365,"children":1367},{"id":1366},"核心概念",[1368],{"type":31,"value":1366},{"type":25,"tag":49,"props":1370,"children":1372},{"id":1371},"setup-函数",[1373],{"type":31,"value":1374},"setup 函数",{"type":25,"tag":212,"props":1376,"children":1379},{"className":1377,"code":1378,"language":1058,"meta":7},[1056],"import { ref, computed, watch } from 'vue'\n\nexport default {\n  props: ['initialCount'],\n  emits: ['count-changed'],\n  \n  setup(props, { emit, slots, expose }) {\n    // 创建响应式状态\n    const count = ref(props.initialCount)\n    const doubled = computed(() => count.value * 2)\n    \n    // 监听状态变化\n    watch(count, (newVal, oldVal) => {\n      console.log(`Count changed from ${oldVal} to ${newVal}`)\n      emit('count-changed', newVal)\n    })\n    \n    // 定义方法\n    const increment = () => count.value++\n    const decrement = () => count.value--\n    \n    // 返回模板需要的内容\n    return {\n      count,\n      doubled,\n      increment,\n      decrement,\n    }\n  },\n}\n",[1380],{"type":25,"tag":61,"props":1381,"children":1382},{"__ignoreMap":7},[1383],{"type":31,"value":1378},{"type":25,"tag":49,"props":1385,"children":1387},{"id":1386},"响应式基础",[1388],{"type":31,"value":1386},{"type":25,"tag":212,"props":1390,"children":1393},{"className":1391,"code":1392,"language":1058,"meta":7},[1056],"import { ref, reactive, readonly, isRef } from 'vue'\n\n// ref - 用于基本类型\nconst count = ref(0)\nconsole.log(count.value) // 0\ncount.value++\n\n// reactive - 用于对象\nconst state = reactive({\n  name: 'John',\n  age: 30,\n  address: {\n    city: 'Beijing',\n  },\n})\n\nstate.name = 'Jane' // 自动更新，无需 .value\n\n// readonly - 创建只读副本\nconst original = reactive({ count: 0 })\nconst copy = readonly(original)\n// copy.count++ // 错误：不能修改\n\n// isRef 检查\nconsole.log(isRef(count)) // true\nconsole.log(isRef(state)) // false\n",[1394],{"type":25,"tag":61,"props":1395,"children":1396},{"__ignoreMap":7},[1397],{"type":31,"value":1392},{"type":25,"tag":26,"props":1399,"children":1400},{"id":1347},[1401],{"type":31,"value":1347},{"type":25,"tag":49,"props":1403,"children":1405},{"id":1404},"创建可重用逻辑",[1406],{"type":31,"value":1404},{"type":25,"tag":212,"props":1408,"children":1411},{"className":1409,"code":1410,"language":1058,"meta":7},[1056],"// useCounter.js - 组合式函数\nimport { ref, computed } from 'vue'\n\nexport function useCounter(initialValue = 0) {\n  const count = ref(initialValue)\n  const doubled = computed(() => count.value * 2)\n  \n  const increment = () => count.value++\n  const decrement = () => count.value--\n  const reset = () => count.value = initialValue\n  \n  return {\n    count,\n    doubled,\n    increment,\n    decrement,\n    reset,\n  }\n}\n\n// useFetch.js - 数据获取组合式函数\nimport { ref, onMounted } from 'vue'\n\nexport function useFetch(url) {\n  const data = ref(null)\n  const loading = ref(false)\n  const error = ref(null)\n  \n  const fetch = async () => {\n    loading.value = true\n    error.value = null\n    \n    try {\n      const response = await fetch(url)\n      data.value = await response.json()\n    } catch (e) {\n      error.value = e\n    } finally {\n      loading.value = false\n    }\n  }\n  \n  onMounted(fetch)\n  \n  return {\n    data,\n    loading,\n    error,\n    refetch: fetch,\n  }\n}\n\n// 使用\nexport default {\n  setup() {\n    const { count, doubled, increment } = useCounter(10)\n    const { data, loading, refetch } = useFetch('/api/data')\n    \n    return {\n      count,\n      doubled,\n      increment,\n      data,\n      loading,\n      refetch,\n    }\n  },\n}\n",[1412],{"type":25,"tag":61,"props":1413,"children":1414},{"__ignoreMap":7},[1415],{"type":31,"value":1410},{"type":25,"tag":26,"props":1417,"children":1419},{"id":1418},"生命周期钩子",[1420],{"type":31,"value":1418},{"type":25,"tag":49,"props":1422,"children":1424},{"id":1423},"composition-api-中的生命周期",[1425],{"type":31,"value":1426},"Composition API 中的生命周期",{"type":25,"tag":212,"props":1428,"children":1431},{"className":1429,"code":1430,"language":1058,"meta":7},[1056],"import {\n  onBeforeMount,\n  onMounted,\n  onBeforeUpdate,\n  onUpdated,\n  onBeforeUnmount,\n  onUnmounted,\n  onErrorCaptured,\n} from 'vue'\n\nexport default {\n  setup() {\n    onBeforeMount(() => {\n      console.log('组件挂载前')\n    })\n    \n    onMounted(() => {\n      console.log('组件已挂载')\n      // 初始化事件监听器、定时器等\n    })\n    \n    onBeforeUpdate(() => {\n      console.log('组件更新前')\n    })\n    \n    onUpdated(() => {\n      console.log('组件已更新')\n    })\n    \n    onBeforeUnmount(() => {\n      console.log('组件卸载前')\n    })\n    \n    onUnmounted(() => {\n      console.log('组件已卸载')\n      // 清理事件监听器、定时器等\n    })\n    \n    onErrorCaptured((err, instance, info) => {\n      console.log('捕获错误:', err)\n      return false // 返回 false 阻止错误传播\n    })\n    \n    return {}\n  },\n}\n",[1432],{"type":25,"tag":61,"props":1433,"children":1434},{"__ignoreMap":7},[1435],{"type":31,"value":1430},{"type":25,"tag":26,"props":1437,"children":1439},{"id":1438},"模板引用",[1440],{"type":31,"value":1438},{"type":25,"tag":49,"props":1442,"children":1444},{"id":1443},"访问-dom-元素",[1445],{"type":31,"value":1446},"访问 DOM 元素",{"type":25,"tag":212,"props":1448,"children":1451},{"className":1449,"code":1450,"language":1058,"meta":7},[1056],"import { ref, onMounted } from 'vue'\n\nexport default {\n  setup() {\n    const inputRef = ref(null)\n    const listRef = ref(null)\n    const dynamicRef = ref(null)\n    \n    onMounted(() => {\n      // 访问 DOM 元素\n      inputRef.value?.focus()\n      console.log(listRef.value?.offsetHeight)\n    })\n    \n    // 函数式引用\n    const assignRef = el => {\n      if (el) {\n        console.log('元素已赋值', el)\n      } else {\n        console.log('元素已移除')\n      }\n    }\n    \n    return {\n      inputRef,\n      listRef,\n      dynamicRef,\n      assignRef,\n    }\n  },\n  \n  template: \\`\n    \u003Cdiv>\n      \u003Cinput ref=\"inputRef\" />\n      \u003Cul ref=\"listRef\">\n        \u003Cli v-for=\"item in items\" :key=\"item\">{{ item }}\u003C/li>\n      \u003C/ul>\n      \u003Cdiv :ref=\"assignRef\">\u003C/div>\n    \u003C/div>\n  \\`,\n}\n",[1452],{"type":25,"tag":61,"props":1453,"children":1454},{"__ignoreMap":7},[1455],{"type":31,"value":1450},{"type":25,"tag":26,"props":1457,"children":1459},{"id":1458},"依赖注入",[1460],{"type":31,"value":1458},{"type":25,"tag":49,"props":1462,"children":1464},{"id":1463},"跨组件共享数据",[1465],{"type":31,"value":1463},{"type":25,"tag":212,"props":1467,"children":1470},{"className":1468,"code":1469,"language":1058,"meta":7},[1056],"import { provide, inject, ref, readonly } from 'vue'\n\n// 父组件\nexport default {\n  setup() {\n    const theme = ref('light')\n    const user = ref({ name: 'John', role: 'admin' })\n    \n    // 提供数据给子组件\n    provide('theme', readonly(theme))\n    provide('updateTheme', (newTheme) => {\n      theme.value = newTheme\n    })\n    \n    // 使用 Symbol 作为 key 避免命名冲突\n    const userKey = Symbol()\n    provide(userKey, readonly(user))\n    \n    return {\n      theme,\n      updateTheme: (newTheme) => {\n        theme.value = newTheme\n      },\n    }\n  },\n}\n\n// 子组件\nexport default {\n  setup() {\n    // 注入数据\n    const theme = inject('theme')\n    const updateTheme = inject('updateTheme')\n    const user = inject(Symbol.for('user'))\n    \n    // 带默认值的注入\n    const config = inject('config', {\n      apiUrl: 'http://localhost:3000',\n    })\n    \n    return {\n      theme,\n      updateTheme,\n      user,\n      config,\n    }\n  },\n}\n",[1471],{"type":25,"tag":61,"props":1472,"children":1473},{"__ignoreMap":7},[1474],{"type":31,"value":1469},{"type":25,"tag":26,"props":1476,"children":1478},{"id":1477},"高级状态管理",[1479],{"type":31,"value":1477},{"type":25,"tag":49,"props":1481,"children":1483},{"id":1482},"创建小型-store",[1484],{"type":31,"value":1485},"创建小型 store",{"type":25,"tag":212,"props":1487,"children":1490},{"className":1488,"code":1489,"language":1058,"meta":7},[1056],"import { reactive, readonly, computed } from 'vue'\n\n// store.js - 不依赖 Pinia 的简单 store\nexport function createStore() {\n  const state = reactive({\n    items: [],\n    filter: 'all',\n    sortBy: 'date',\n  })\n  \n  const filteredItems = computed(() => {\n    let result = state.items\n    \n    if (state.filter !== 'all') {\n      result = result.filter(item => item.status === state.filter)\n    }\n    \n    if (state.sortBy === 'date') {\n      result.sort((a, b) => new Date(b.date) - new Date(a.date))\n    } else if (state.sortBy === 'name') {\n      result.sort((a, b) => a.name.localeCompare(b.name))\n    }\n    \n    return result\n  })\n  \n  const actions = {\n    addItem(item) {\n      state.items.push({ ...item, id: Date.now() })\n    },\n    \n    removeItem(id) {\n      state.items = state.items.filter(item => item.id !== id)\n    },\n    \n    updateItem(id, updates) {\n      const item = state.items.find(item => item.id === id)\n      if (item) {\n        Object.assign(item, updates)\n      }\n    },\n    \n    setFilter(filter) {\n      state.filter = filter\n    },\n    \n    setSortBy(sortBy) {\n      state.sortBy = sortBy\n    },\n  }\n  \n  return {\n    state: readonly(state),\n    filteredItems,\n    ...actions,\n  }\n}\n\n// 使用\nexport default {\n  setup() {\n    const store = createStore()\n    \n    const handleAdd = (item) => {\n      store.addItem(item)\n    }\n    \n    return {\n      items: store.filteredItems,\n      addItem: handleAdd,\n      setFilter: store.setFilter,\n    }\n  },\n}\n",[1491],{"type":25,"tag":61,"props":1492,"children":1493},{"__ignoreMap":7},[1494],{"type":31,"value":1489},{"type":25,"tag":26,"props":1496,"children":1497},{"id":1182},[1498],{"type":31,"value":1182},{"type":25,"tag":33,"props":1500,"children":1501},{},[1502,1503,1507],{"type":31,"value":1189},{"type":25,"tag":73,"props":1504,"children":1505},{},[1506],{"type":31,"value":1194},{"type":31,"value":1196},{"type":25,"tag":128,"props":1509,"children":1510},{},[1511,1516,1521,1526,1531],{"type":25,"tag":132,"props":1512,"children":1513},{},[1514],{"type":31,"value":1515},"将相关逻辑组织在一起",{"type":25,"tag":132,"props":1517,"children":1518},{},[1519],{"type":31,"value":1520},"创建可重用的组合式函数",{"type":25,"tag":132,"props":1522,"children":1523},{},[1524],{"type":31,"value":1525},"使用 TypeScript 获得更好的类型检查",{"type":25,"tag":132,"props":1527,"children":1528},{},[1529],{"type":31,"value":1530},"合理使用计算属性和监听器",{"type":25,"tag":132,"props":1532,"children":1533},{},[1534],{"type":31,"value":1535},"及时清理事件监听器和定时器",{"type":25,"tag":33,"props":1537,"children":1538},{},[1539,1540,1544],{"type":31,"value":1229},{"type":25,"tag":73,"props":1541,"children":1542},{},[1543],{"type":31,"value":1234},{"type":31,"value":1196},{"type":25,"tag":128,"props":1546,"children":1547},{},[1548,1553,1558,1563,1568],{"type":25,"tag":132,"props":1549,"children":1550},{},[1551],{"type":31,"value":1552},"在 setup 中执行副作用操作（除了生命周期钩子）",{"type":25,"tag":132,"props":1554,"children":1555},{},[1556],{"type":31,"value":1557},"过度使用计算属性",{"type":25,"tag":132,"props":1559,"children":1560},{},[1561],{"type":31,"value":1562},"忘记清理 watch 监听器",{"type":25,"tag":132,"props":1564,"children":1565},{},[1566],{"type":31,"value":1567},"在 reactive 对象中存储引用类型时不谨慎",{"type":25,"tag":132,"props":1569,"children":1570},{},[1571],{"type":31,"value":1572},"过度复杂化组合式函数",{"type":25,"tag":26,"props":1574,"children":1575},{"id":1266},[1576],{"type":31,"value":1266},{"type":25,"tag":128,"props":1578,"children":1580},{"className":1579},[642],[1581,1590,1599,1608,1617],{"type":25,"tag":132,"props":1582,"children":1584},{"className":1583},[647],[1585,1588],{"type":25,"tag":650,"props":1586,"children":1587},{"disabled":19,"type":652},[],{"type":31,"value":1589}," 正确使用 ref 和 reactive",{"type":25,"tag":132,"props":1591,"children":1593},{"className":1592},[647],[1594,1597],{"type":25,"tag":650,"props":1595,"children":1596},{"disabled":19,"type":652},[],{"type":31,"value":1598}," 生命周期钩子正确",{"type":25,"tag":132,"props":1600,"children":1602},{"className":1601},[647],[1603,1606],{"type":25,"tag":650,"props":1604,"children":1605},{"disabled":19,"type":652},[],{"type":31,"value":1607}," 模板引用工作正常",{"type":25,"tag":132,"props":1609,"children":1611},{"className":1610},[647],[1612,1615],{"type":25,"tag":650,"props":1613,"children":1614},{"disabled":19,"type":652},[],{"type":31,"value":1616}," 组合式函数可重用",{"type":25,"tag":132,"props":1618,"children":1620},{"className":1619},[647],[1621,1624],{"type":25,"tag":650,"props":1622,"children":1623},{"disabled":19,"type":652},[],{"type":31,"value":1308},{"title":7,"searchDepth":974,"depth":974,"links":1626},[1627,1628,1632,1635,1638,1641,1644,1647,1648],{"id":1356,"depth":977,"text":1342},{"id":1366,"depth":977,"text":1366,"children":1629},[1630,1631],{"id":1371,"depth":974,"text":1374},{"id":1386,"depth":974,"text":1386},{"id":1347,"depth":977,"text":1347,"children":1633},[1634],{"id":1404,"depth":974,"text":1404},{"id":1418,"depth":977,"text":1418,"children":1636},[1637],{"id":1423,"depth":974,"text":1426},{"id":1438,"depth":977,"text":1438,"children":1639},[1640],{"id":1443,"depth":974,"text":1446},{"id":1458,"depth":977,"text":1458,"children":1642},[1643],{"id":1463,"depth":974,"text":1463},{"id":1477,"depth":977,"text":1477,"children":1645},[1646],{"id":1482,"depth":974,"text":1485},{"id":1182,"depth":977,"text":1182},{"id":1266,"depth":977,"text":1266},"content:topics:frontend:vue3-composition-api.md","topics/frontend/vue3-composition-api.md","topics/frontend/vue3-composition-api",{"_path":1653,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1654,"description":1655,"date":1656,"topic":5,"author":11,"tags":1657,"image":1662,"featured":19,"readingTime":1663,"body":1664,"_type":1008,"_id":2924,"_source":1010,"_file":2925,"_stem":2926,"_extension":1013},"/topics/frontend/rspack-performance-practice","Rspack 构建性能实战","从 Rspack 的架构设计与编译管线出发，系统对比 Webpack/Vite 并给出 Rspack 在大型项目中的迁移路径、性能调优策略与生产级可观测方案。","2026-01-20",[1658,1659,14,1660,1661],"Rspack","构建工具","Webpack","前端工程化","/images/topics/rspack.jpg",25,{"type":22,"children":1665,"toc":2891},[1666,1671,1683,1688,1706,1717,1722,1745,1749,1755,1760,1766,1799,1805,1823,1826,1832,1838,1843,1856,1862,1867,1880,1886,1904,1909,1912,1918,1923,2064,2069,2092,2095,2101,2107,2112,2117,2184,2197,2203,2211,2224,2232,2245,2253,2266,2272,2290,2293,2299,2305,2310,2323,2329,2334,2345,2351,2356,2374,2379,2407,2410,2416,2421,2432,2437,2455,2460,2478,2481,2487,2492,2510,2515,2538,2543,2561,2564,2570,2576,2581,2599,2605,2623,2629,2647,2650,2656,2771,2776,2794,2797,2803,2861,2864,2868,2873],{"type":25,"tag":26,"props":1667,"children":1669},{"id":1668},"rspack-构建性能实战",[1670],{"type":31,"value":1654},{"type":25,"tag":33,"props":1672,"children":1673},{},[1674,1676,1681],{"type":31,"value":1675},"Rspack 不是\"又一个构建工具\"，而是字节跳动在处理",{"type":25,"tag":73,"props":1677,"children":1678},{},[1679],{"type":31,"value":1680},"超大规模前端项目",{"type":31,"value":1682},"时，对 Webpack 生态的 Rust 重写与工程化沉淀。",{"type":25,"tag":33,"props":1684,"children":1685},{},[1686],{"type":31,"value":1687},"它要解决的核心问题是：",{"type":25,"tag":128,"props":1689,"children":1690},{},[1691,1696,1701],{"type":25,"tag":132,"props":1692,"children":1693},{},[1694],{"type":31,"value":1695},"Webpack 的构建速度在大型 monorepo 下（10k+ 模块）已成为开发体验瓶颈",{"type":25,"tag":132,"props":1697,"children":1698},{},[1699],{"type":31,"value":1700},"但你又无法抛弃 Webpack 的插件生态与配置范式",{"type":25,"tag":132,"props":1702,"children":1703},{},[1704],{"type":31,"value":1705},"Vite 虽然快，但在某些场景（大型遗留项目、特定插件依赖）迁移成本高",{"type":25,"tag":33,"props":1707,"children":1708},{},[1709,1711,1716],{"type":31,"value":1710},"Rspack 的定位是：",{"type":25,"tag":73,"props":1712,"children":1713},{},[1714],{"type":31,"value":1715},"Webpack 兼容 API + Rust 性能 + 生产级稳定性",{"type":31,"value":95},{"type":25,"tag":33,"props":1718,"children":1719},{},[1720],{"type":31,"value":1721},"这篇文章不讲\"Hello World\"，而是按\"你要在生产上稳定用 Rspack\"的标准，给出：",{"type":25,"tag":128,"props":1723,"children":1724},{},[1725,1730,1735,1740],{"type":25,"tag":132,"props":1726,"children":1727},{},[1728],{"type":31,"value":1729},"性能收益的真实量化方法",{"type":25,"tag":132,"props":1731,"children":1732},{},[1733],{"type":31,"value":1734},"迁移路径与兼容性边界",{"type":25,"tag":132,"props":1736,"children":1737},{},[1738],{"type":31,"value":1739},"优化策略（缓存、并行、Tree Shaking）",{"type":25,"tag":132,"props":1741,"children":1742},{},[1743],{"type":31,"value":1744},"监控与排障（为什么变慢、为什么产物变大）",{"type":25,"tag":1746,"props":1747,"children":1748},"hr",{},[],{"type":25,"tag":26,"props":1750,"children":1752},{"id":1751},"_1-先回答什么项目值得迁移-rspack",[1753],{"type":31,"value":1754},"1. 先回答：什么项目值得迁移 Rspack？",{"type":25,"tag":33,"props":1756,"children":1757},{},[1758],{"type":31,"value":1759},"不是所有项目都需要 Rspack。",{"type":25,"tag":49,"props":1761,"children":1763},{"id":1762},"_11-高收益场景",[1764],{"type":31,"value":1765},"1.1 高收益场景",{"type":25,"tag":128,"props":1767,"children":1768},{},[1769,1779,1789],{"type":25,"tag":132,"props":1770,"children":1771},{},[1772,1777],{"type":25,"tag":73,"props":1773,"children":1774},{},[1775],{"type":31,"value":1776},"大型 monorepo",{"type":31,"value":1778},"（5k+ 模块，构建时间 > 2 分钟）",{"type":25,"tag":132,"props":1780,"children":1781},{},[1782,1787],{"type":25,"tag":73,"props":1783,"children":1784},{},[1785],{"type":31,"value":1786},"频繁开发迭代",{"type":31,"value":1788},"（HMR 延迟影响体验）",{"type":25,"tag":132,"props":1790,"children":1791},{},[1792,1797],{"type":25,"tag":73,"props":1793,"children":1794},{},[1795],{"type":31,"value":1796},"CI 构建成本高",{"type":31,"value":1798},"（每次 PR 构建超 10 分钟）",{"type":25,"tag":49,"props":1800,"children":1802},{"id":1801},"_12-收益不明显的场景",[1803],{"type":31,"value":1804},"1.2 收益不明显的场景",{"type":25,"tag":128,"props":1806,"children":1807},{},[1808,1813,1818],{"type":25,"tag":132,"props":1809,"children":1810},{},[1811],{"type":31,"value":1812},"小型项目（\u003C 1k 模块）",{"type":25,"tag":132,"props":1814,"children":1815},{},[1816],{"type":31,"value":1817},"已经用 Vite 且体验良好",{"type":25,"tag":132,"props":1819,"children":1820},{},[1821],{"type":31,"value":1822},"高度定制的 Webpack 插件（迁移成本 > 性能收益）",{"type":25,"tag":1746,"props":1824,"children":1825},{},[],{"type":25,"tag":26,"props":1827,"children":1829},{"id":1828},"_2-rspack-的架构为什么能快",[1830],{"type":31,"value":1831},"2. Rspack 的架构：为什么能快？",{"type":25,"tag":49,"props":1833,"children":1835},{"id":1834},"_21-rust-并行编译",[1836],{"type":31,"value":1837},"2.1 Rust 并行编译",{"type":25,"tag":33,"props":1839,"children":1840},{},[1841],{"type":31,"value":1842},"Webpack 是单线程 JavaScript，Rspack 是多线程 Rust。",{"type":25,"tag":128,"props":1844,"children":1845},{},[1846,1851],{"type":25,"tag":132,"props":1847,"children":1848},{},[1849],{"type":31,"value":1850},"模块解析、编译、优化可并行",{"type":25,"tag":132,"props":1852,"children":1853},{},[1854],{"type":31,"value":1855},"I/O 密集型任务（读文件、写产物）异步化",{"type":25,"tag":49,"props":1857,"children":1859},{"id":1858},"_22-更激进的缓存策略",[1860],{"type":31,"value":1861},"2.2 更激进的缓存策略",{"type":25,"tag":33,"props":1863,"children":1864},{},[1865],{"type":31,"value":1866},"Rspack 内置持久化缓存：",{"type":25,"tag":128,"props":1868,"children":1869},{},[1870,1875],{"type":25,"tag":132,"props":1871,"children":1872},{},[1873],{"type":31,"value":1874},"模块级别缓存（类似 Webpack 5 的 cache.type: 'filesystem'）",{"type":25,"tag":132,"props":1876,"children":1877},{},[1878],{"type":31,"value":1879},"但实现更激进：对未变化模块跳过编译",{"type":25,"tag":49,"props":1881,"children":1883},{"id":1882},"_23-内置常用功能减少插件开销",[1884],{"type":31,"value":1885},"2.3 内置常用功能（减少插件开销）",{"type":25,"tag":128,"props":1887,"children":1888},{},[1889,1894,1899],{"type":25,"tag":132,"props":1890,"children":1891},{},[1892],{"type":31,"value":1893},"SWC 替代 Babel（内置 TS/JSX/装饰器）",{"type":25,"tag":132,"props":1895,"children":1896},{},[1897],{"type":31,"value":1898},"CSS Modules、PostCSS 内置",{"type":25,"tag":132,"props":1900,"children":1901},{},[1902],{"type":31,"value":1903},"Tree Shaking 内置",{"type":25,"tag":33,"props":1905,"children":1906},{},[1907],{"type":31,"value":1908},"这让 Rspack 在相同功能下比 Webpack + 插件链路更快。",{"type":25,"tag":1746,"props":1910,"children":1911},{},[],{"type":25,"tag":26,"props":1913,"children":1915},{"id":1914},"_3-性能对比真实场景的量化",[1916],{"type":31,"value":1917},"3. 性能对比：真实场景的量化",{"type":25,"tag":33,"props":1919,"children":1920},{},[1921],{"type":31,"value":1922},"我们用一个典型中型项目（3k 模块，React + TS + CSS Modules）做对比：",{"type":25,"tag":1924,"props":1925,"children":1926},"table",{},[1927,1955],{"type":25,"tag":1928,"props":1929,"children":1930},"thead",{},[1931],{"type":25,"tag":1932,"props":1933,"children":1934},"tr",{},[1935,1941,1946,1950],{"type":25,"tag":1936,"props":1937,"children":1938},"th",{},[1939],{"type":31,"value":1940},"指标",{"type":25,"tag":1936,"props":1942,"children":1943},{},[1944],{"type":31,"value":1945},"Webpack 5",{"type":25,"tag":1936,"props":1947,"children":1948},{},[1949],{"type":31,"value":1658},{"type":25,"tag":1936,"props":1951,"children":1952},{},[1953],{"type":31,"value":1954},"提升",{"type":25,"tag":1956,"props":1957,"children":1958},"tbody",{},[1959,1986,2012,2038],{"type":25,"tag":1932,"props":1960,"children":1961},{},[1962,1968,1973,1978],{"type":25,"tag":1963,"props":1964,"children":1965},"td",{},[1966],{"type":31,"value":1967},"冷启动",{"type":25,"tag":1963,"props":1969,"children":1970},{},[1971],{"type":31,"value":1972},"42s",{"type":25,"tag":1963,"props":1974,"children":1975},{},[1976],{"type":31,"value":1977},"8s",{"type":25,"tag":1963,"props":1979,"children":1980},{},[1981],{"type":25,"tag":73,"props":1982,"children":1983},{},[1984],{"type":31,"value":1985},"5.2x",{"type":25,"tag":1932,"props":1987,"children":1988},{},[1989,1994,1999,2004],{"type":25,"tag":1963,"props":1990,"children":1991},{},[1992],{"type":31,"value":1993},"HMR（热更新）",{"type":25,"tag":1963,"props":1995,"children":1996},{},[1997],{"type":31,"value":1998},"1.2s",{"type":25,"tag":1963,"props":2000,"children":2001},{},[2002],{"type":31,"value":2003},"0.15s",{"type":25,"tag":1963,"props":2005,"children":2006},{},[2007],{"type":25,"tag":73,"props":2008,"children":2009},{},[2010],{"type":31,"value":2011},"8x",{"type":25,"tag":1932,"props":2013,"children":2014},{},[2015,2020,2025,2030],{"type":25,"tag":1963,"props":2016,"children":2017},{},[2018],{"type":31,"value":2019},"生产构建",{"type":25,"tag":1963,"props":2021,"children":2022},{},[2023],{"type":31,"value":2024},"125s",{"type":25,"tag":1963,"props":2026,"children":2027},{},[2028],{"type":31,"value":2029},"28s",{"type":25,"tag":1963,"props":2031,"children":2032},{},[2033],{"type":25,"tag":73,"props":2034,"children":2035},{},[2036],{"type":31,"value":2037},"4.5x",{"type":25,"tag":1932,"props":2039,"children":2040},{},[2041,2046,2051,2056],{"type":25,"tag":1963,"props":2042,"children":2043},{},[2044],{"type":31,"value":2045},"内存峰值",{"type":25,"tag":1963,"props":2047,"children":2048},{},[2049],{"type":31,"value":2050},"1.8GB",{"type":25,"tag":1963,"props":2052,"children":2053},{},[2054],{"type":31,"value":2055},"0.9GB",{"type":25,"tag":1963,"props":2057,"children":2058},{},[2059],{"type":25,"tag":73,"props":2060,"children":2061},{},[2062],{"type":31,"value":2063},"2x",{"type":25,"tag":33,"props":2065,"children":2066},{},[2067],{"type":31,"value":2068},"关键收益：",{"type":25,"tag":128,"props":2070,"children":2071},{},[2072,2082],{"type":25,"tag":132,"props":2073,"children":2074},{},[2075,2080],{"type":25,"tag":73,"props":2076,"children":2077},{},[2078],{"type":31,"value":2079},"开发体验质变",{"type":31,"value":2081},"（HMR \u003C 200ms）",{"type":25,"tag":132,"props":2083,"children":2084},{},[2085,2090],{"type":25,"tag":73,"props":2086,"children":2087},{},[2088],{"type":31,"value":2089},"CI 成本减半",{"type":31,"value":2091},"（构建时间直接影响 Runner 费用）",{"type":25,"tag":1746,"props":2093,"children":2094},{},[],{"type":25,"tag":26,"props":2096,"children":2098},{"id":2097},"_4-迁移路径从-webpack-到-rspack",[2099],{"type":31,"value":2100},"4. 迁移路径：从 Webpack 到 Rspack",{"type":25,"tag":49,"props":2102,"children":2104},{"id":2103},"_41-最小迁移保守策略",[2105],{"type":31,"value":2106},"4.1 最小迁移（保守策略）",{"type":25,"tag":33,"props":2108,"children":2109},{},[2110],{"type":31,"value":2111},"目标：用最小改动换取性能收益。",{"type":25,"tag":33,"props":2113,"children":2114},{},[2115],{"type":31,"value":2116},"步骤：",{"type":25,"tag":250,"props":2118,"children":2119},{},[2120,2139,2158,2179],{"type":25,"tag":132,"props":2121,"children":2122},{},[2123,2125,2131,2133],{"type":31,"value":2124},"安装 ",{"type":25,"tag":61,"props":2126,"children":2128},{"className":2127},[],[2129],{"type":31,"value":2130},"@rspack/cli",{"type":31,"value":2132}," 与 ",{"type":25,"tag":61,"props":2134,"children":2136},{"className":2135},[],[2137],{"type":31,"value":2138},"@rspack/core",{"type":25,"tag":132,"props":2140,"children":2141},{},[2142,2144,2150,2152],{"type":31,"value":2143},"把 ",{"type":25,"tag":61,"props":2145,"children":2147},{"className":2146},[],[2148],{"type":31,"value":2149},"webpack.config.js",{"type":31,"value":2151}," 改为 ",{"type":25,"tag":61,"props":2153,"children":2155},{"className":2154},[],[2156],{"type":31,"value":2157},"rspack.config.js",{"type":25,"tag":132,"props":2159,"children":2160},{},[2161,2163,2169,2171,2177],{"type":31,"value":2162},"替换构建命令（",{"type":25,"tag":61,"props":2164,"children":2166},{"className":2165},[],[2167],{"type":31,"value":2168},"rspack build",{"type":31,"value":2170}," / ",{"type":25,"tag":61,"props":2172,"children":2174},{"className":2173},[],[2175],{"type":31,"value":2176},"rspack dev",{"type":31,"value":2178},"）",{"type":25,"tag":132,"props":2180,"children":2181},{},[2182],{"type":31,"value":2183},"运行并修复兼容性问题",{"type":25,"tag":33,"props":2185,"children":2186},{},[2187,2189,2195],{"type":31,"value":2188},"预计迁移成本：1",{"type":25,"tag":2190,"props":2191,"children":2192},"del",{},[2193],{"type":31,"value":2194},"2 天（小型项目）/ 1",{"type":31,"value":2196},"2 周（大型项目）",{"type":25,"tag":49,"props":2198,"children":2200},{"id":2199},"_42-兼容性边界哪些需要调整",[2201],{"type":31,"value":2202},"4.2 兼容性边界：哪些需要调整",{"type":25,"tag":33,"props":2204,"children":2205},{},[2206],{"type":25,"tag":73,"props":2207,"children":2208},{},[2209],{"type":31,"value":2210},"插件兼容",{"type":25,"tag":128,"props":2212,"children":2213},{},[2214,2219],{"type":25,"tag":132,"props":2215,"children":2216},{},[2217],{"type":31,"value":2218},"Rspack 支持大部分 Webpack 插件（API 兼容）",{"type":25,"tag":132,"props":2220,"children":2221},{},[2222],{"type":31,"value":2223},"但少数复杂插件（例如深度依赖 Webpack 内部 API）需要适配",{"type":25,"tag":33,"props":2225,"children":2226},{},[2227],{"type":25,"tag":73,"props":2228,"children":2229},{},[2230],{"type":31,"value":2231},"Loader 兼容",{"type":25,"tag":128,"props":2233,"children":2234},{},[2235,2240],{"type":25,"tag":132,"props":2236,"children":2237},{},[2238],{"type":31,"value":2239},"常用 loader（babel-loader、css-loader、postcss-loader）兼容",{"type":25,"tag":132,"props":2241,"children":2242},{},[2243],{"type":31,"value":2244},"部分自定义 loader 需要测试",{"type":25,"tag":33,"props":2246,"children":2247},{},[2248],{"type":25,"tag":73,"props":2249,"children":2250},{},[2251],{"type":31,"value":2252},"配置差异",{"type":25,"tag":128,"props":2254,"children":2255},{},[2256,2261],{"type":25,"tag":132,"props":2257,"children":2258},{},[2259],{"type":31,"value":2260},"resolve、output、optimization 等配置与 Webpack 高度一致",{"type":25,"tag":132,"props":2262,"children":2263},{},[2264],{"type":31,"value":2265},"少数高级配置需要查文档",{"type":25,"tag":49,"props":2267,"children":2269},{"id":2268},"_43-推荐的迁移节奏",[2270],{"type":31,"value":2271},"4.3 推荐的迁移节奏",{"type":25,"tag":128,"props":2273,"children":2274},{},[2275,2280,2285],{"type":25,"tag":132,"props":2276,"children":2277},{},[2278],{"type":31,"value":2279},"Week 1：本地开发环境先行",{"type":25,"tag":132,"props":2281,"children":2282},{},[2283],{"type":31,"value":2284},"Week 2：CI 构建切换（并保留 Webpack 作为 fallback）",{"type":25,"tag":132,"props":2286,"children":2287},{},[2288],{"type":31,"value":2289},"Week 3~4：生产构建切换并观测",{"type":25,"tag":1746,"props":2291,"children":2292},{},[],{"type":25,"tag":26,"props":2294,"children":2296},{"id":2295},"_5-性能调优让-rspack-更快",[2297],{"type":31,"value":2298},"5. 性能调优：让 Rspack 更快",{"type":25,"tag":49,"props":2300,"children":2302},{"id":2301},"_51-缓存策略",[2303],{"type":31,"value":2304},"5.1 缓存策略",{"type":25,"tag":33,"props":2306,"children":2307},{},[2308],{"type":31,"value":2309},"默认缓存已经很激进，但你可以：",{"type":25,"tag":128,"props":2311,"children":2312},{},[2313,2318],{"type":25,"tag":132,"props":2314,"children":2315},{},[2316],{"type":31,"value":2317},"显式配置缓存目录（例如挂载 SSD）",{"type":25,"tag":132,"props":2319,"children":2320},{},[2321],{"type":31,"value":2322},"在 CI 上持久化缓存（例如用 actions/cache）",{"type":25,"tag":49,"props":2324,"children":2326},{"id":2325},"_52-并行度调优",[2327],{"type":31,"value":2328},"5.2 并行度调优",{"type":25,"tag":33,"props":2330,"children":2331},{},[2332],{"type":31,"value":2333},"Rspack 默认会用所有 CPU 核心，但在容器环境（例如 CI）可能需要限制：",{"type":25,"tag":212,"props":2335,"children":2340},{"className":2336,"code":2338,"language":2339,"meta":7},[2337],"language-js","module.exports = {\n  experiments: {\n    rspackFuture: {\n      disableTransformByDefault: true, // 减少不必要转换\n    },\n  },\n}\n","js",[2341],{"type":25,"tag":61,"props":2342,"children":2343},{"__ignoreMap":7},[2344],{"type":31,"value":2338},{"type":25,"tag":49,"props":2346,"children":2348},{"id":2347},"_53-tree-shaking-与-dead-code-elimination",[2349],{"type":31,"value":2350},"5.3 Tree Shaking 与 Dead Code Elimination",{"type":25,"tag":33,"props":2352,"children":2353},{},[2354],{"type":31,"value":2355},"Rspack 内置 Tree Shaking，但效果取决于：",{"type":25,"tag":128,"props":2357,"children":2358},{},[2359,2364,2369],{"type":25,"tag":132,"props":2360,"children":2361},{},[2362],{"type":31,"value":2363},"是否使用 ESM（而非 CommonJS）",{"type":25,"tag":132,"props":2365,"children":2366},{},[2367],{"type":31,"value":2368},"副作用标记（sideEffects: false）",{"type":25,"tag":132,"props":2370,"children":2371},{},[2372],{"type":31,"value":2373},"动态 import 的拆分策略",{"type":25,"tag":33,"props":2375,"children":2376},{},[2377],{"type":31,"value":2378},"建议：",{"type":25,"tag":128,"props":2380,"children":2381},{},[2382,2395],{"type":25,"tag":132,"props":2383,"children":2384},{},[2385,2387,2393],{"type":31,"value":2386},"对第三方库检查 ",{"type":25,"tag":61,"props":2388,"children":2390},{"className":2389},[],[2391],{"type":31,"value":2392},"sideEffects",{"type":31,"value":2394}," 配置",{"type":25,"tag":132,"props":2396,"children":2397},{},[2398,2400,2406],{"type":31,"value":2399},"避免\"全量引入后 tree shake\"（例如 ",{"type":25,"tag":61,"props":2401,"children":2403},{"className":2402},[],[2404],{"type":31,"value":2405},"import * from 'lodash'",{"type":31,"value":2178},{"type":25,"tag":1746,"props":2408,"children":2409},{},[],{"type":25,"tag":26,"props":2411,"children":2413},{"id":2412},"_6-产物分析与优化",[2414],{"type":31,"value":2415},"6. 产物分析与优化",{"type":25,"tag":33,"props":2417,"children":2418},{},[2419],{"type":31,"value":2420},"Rspack 提供内置分析工具：",{"type":25,"tag":212,"props":2422,"children":2427},{"className":2423,"code":2425,"language":2426,"meta":7},[2424],"language-bash","rspack build --analyze\n","bash",[2428],{"type":25,"tag":61,"props":2429,"children":2430},{"__ignoreMap":7},[2431],{"type":31,"value":2425},{"type":25,"tag":33,"props":2433,"children":2434},{},[2435],{"type":31,"value":2436},"关键指标：",{"type":25,"tag":128,"props":2438,"children":2439},{},[2440,2445,2450],{"type":25,"tag":132,"props":2441,"children":2442},{},[2443],{"type":31,"value":2444},"各 chunk 体积分布",{"type":25,"tag":132,"props":2446,"children":2447},{},[2448],{"type":31,"value":2449},"重复依赖（例如多个版本的 lodash）",{"type":25,"tag":132,"props":2451,"children":2452},{},[2453],{"type":31,"value":2454},"未被 tree shake 的代码",{"type":25,"tag":33,"props":2456,"children":2457},{},[2458],{"type":31,"value":2459},"优化策略：",{"type":25,"tag":128,"props":2461,"children":2462},{},[2463,2468,2473],{"type":25,"tag":132,"props":2464,"children":2465},{},[2466],{"type":31,"value":2467},"拆分 vendor chunk（按更新频率）",{"type":25,"tag":132,"props":2469,"children":2470},{},[2471],{"type":31,"value":2472},"对大型库按需引入（例如 antd/lodash-es）",{"type":25,"tag":132,"props":2474,"children":2475},{},[2476],{"type":31,"value":2477},"检查动态 import 的粒度",{"type":25,"tag":1746,"props":2479,"children":2480},{},[],{"type":25,"tag":26,"props":2482,"children":2484},{"id":2483},"_7-生产可观测性让构建可量化",[2485],{"type":31,"value":2486},"7. 生产可观测性：让构建可量化",{"type":25,"tag":33,"props":2488,"children":2489},{},[2490],{"type":31,"value":2491},"在 CI/CD 里，你需要能回答：",{"type":25,"tag":128,"props":2493,"children":2494},{},[2495,2500,2505],{"type":25,"tag":132,"props":2496,"children":2497},{},[2498],{"type":31,"value":2499},"这次构建为什么变慢？",{"type":25,"tag":132,"props":2501,"children":2502},{},[2503],{"type":31,"value":2504},"产物为什么变大？",{"type":25,"tag":132,"props":2506,"children":2507},{},[2508],{"type":31,"value":2509},"哪个模块耗时最多？",{"type":25,"tag":33,"props":2511,"children":2512},{},[2513],{"type":31,"value":2514},"建议在 CI 里记录：",{"type":25,"tag":128,"props":2516,"children":2517},{},[2518,2523,2528,2533],{"type":25,"tag":132,"props":2519,"children":2520},{},[2521],{"type":31,"value":2522},"构建总耗时",{"type":25,"tag":132,"props":2524,"children":2525},{},[2526],{"type":31,"value":2527},"各阶段耗时（resolve、compile、optimize、emit）",{"type":25,"tag":132,"props":2529,"children":2530},{},[2531],{"type":31,"value":2532},"产物体积（按 chunk）",{"type":25,"tag":132,"props":2534,"children":2535},{},[2536],{"type":31,"value":2537},"缓存命中率",{"type":25,"tag":33,"props":2539,"children":2540},{},[2541],{"type":31,"value":2542},"落地方式：",{"type":25,"tag":128,"props":2544,"children":2545},{},[2546,2551,2556],{"type":25,"tag":132,"props":2547,"children":2548},{},[2549],{"type":31,"value":2550},"用 Rspack 的 stats 输出",{"type":25,"tag":132,"props":2552,"children":2553},{},[2554],{"type":31,"value":2555},"在 CI 日志里保留关键指标",{"type":25,"tag":132,"props":2557,"children":2558},{},[2559],{"type":31,"value":2560},"对产物体积做 baseline 对比（变化 > 5% 报警）",{"type":25,"tag":1746,"props":2562,"children":2563},{},[],{"type":25,"tag":26,"props":2565,"children":2567},{"id":2566},"_8-常见问题排查",[2568],{"type":31,"value":2569},"8. 常见问题排查",{"type":25,"tag":49,"props":2571,"children":2573},{"id":2572},"_81-迁移后变慢了",[2574],{"type":31,"value":2575},"8.1 \"迁移后变慢了\"",{"type":25,"tag":33,"props":2577,"children":2578},{},[2579],{"type":31,"value":2580},"排查顺序：",{"type":25,"tag":128,"props":2582,"children":2583},{},[2584,2589,2594],{"type":25,"tag":132,"props":2585,"children":2586},{},[2587],{"type":31,"value":2588},"缓存是否生效（首次构建慢正常）",{"type":25,"tag":132,"props":2590,"children":2591},{},[2592],{"type":31,"value":2593},"是否有 loader 拖慢（例如未优化的自定义 loader）",{"type":25,"tag":132,"props":2595,"children":2596},{},[2597],{"type":31,"value":2598},"并行度是否受限（例如 CI 限制 CPU）",{"type":25,"tag":49,"props":2600,"children":2602},{"id":2601},"_82-产物体积变大了",[2603],{"type":31,"value":2604},"8.2 \"产物体积变大了\"",{"type":25,"tag":128,"props":2606,"children":2607},{},[2608,2613,2618],{"type":25,"tag":132,"props":2609,"children":2610},{},[2611],{"type":31,"value":2612},"检查 Tree Shaking 是否生效",{"type":25,"tag":132,"props":2614,"children":2615},{},[2616],{"type":31,"value":2617},"检查是否引入了更多 polyfill",{"type":25,"tag":132,"props":2619,"children":2620},{},[2621],{"type":31,"value":2622},"对比 chunk 分布（用 analyze）",{"type":25,"tag":49,"props":2624,"children":2626},{"id":2625},"_83-某些模块编译失败",[2627],{"type":31,"value":2628},"8.3 \"某些模块编译失败\"",{"type":25,"tag":128,"props":2630,"children":2631},{},[2632,2637,2642],{"type":25,"tag":132,"props":2633,"children":2634},{},[2635],{"type":31,"value":2636},"检查是否依赖 Webpack 特定 API",{"type":25,"tag":132,"props":2638,"children":2639},{},[2640],{"type":31,"value":2641},"查看 Rspack 官方兼容性列表",{"type":25,"tag":132,"props":2643,"children":2644},{},[2645],{"type":31,"value":2646},"在 GitHub Issues 搜索类似问题",{"type":25,"tag":1746,"props":2648,"children":2649},{},[],{"type":25,"tag":26,"props":2651,"children":2653},{"id":2652},"_9-rspack-vs-vite什么时候选哪个",[2654],{"type":31,"value":2655},"9. Rspack vs Vite：什么时候选哪个？",{"type":25,"tag":1924,"props":2657,"children":2658},{},[2659,2679],{"type":25,"tag":1928,"props":2660,"children":2661},{},[2662],{"type":25,"tag":1932,"props":2663,"children":2664},{},[2665,2670,2674],{"type":25,"tag":1936,"props":2666,"children":2667},{},[2668],{"type":31,"value":2669},"维度",{"type":25,"tag":1936,"props":2671,"children":2672},{},[2673],{"type":31,"value":1658},{"type":25,"tag":1936,"props":2675,"children":2676},{},[2677],{"type":31,"value":2678},"Vite",{"type":25,"tag":1956,"props":2680,"children":2681},{},[2682,2700,2717,2735,2753],{"type":25,"tag":1932,"props":2683,"children":2684},{},[2685,2690,2695],{"type":25,"tag":1963,"props":2686,"children":2687},{},[2688],{"type":31,"value":2689},"开发速度",{"type":25,"tag":1963,"props":2691,"children":2692},{},[2693],{"type":31,"value":2694},"极快（Rust 编译）",{"type":25,"tag":1963,"props":2696,"children":2697},{},[2698],{"type":31,"value":2699},"极快（ESM 直连）",{"type":25,"tag":1932,"props":2701,"children":2702},{},[2703,2707,2712],{"type":25,"tag":1963,"props":2704,"children":2705},{},[2706],{"type":31,"value":2019},{"type":25,"tag":1963,"props":2708,"children":2709},{},[2710],{"type":31,"value":2711},"快（全量编译优化）",{"type":25,"tag":1963,"props":2713,"children":2714},{},[2715],{"type":31,"value":2716},"快（Rollup）",{"type":25,"tag":1932,"props":2718,"children":2719},{},[2720,2725,2730],{"type":25,"tag":1963,"props":2721,"children":2722},{},[2723],{"type":31,"value":2724},"Webpack 兼容",{"type":25,"tag":1963,"props":2726,"children":2727},{},[2728],{"type":31,"value":2729},"高",{"type":25,"tag":1963,"props":2731,"children":2732},{},[2733],{"type":31,"value":2734},"低",{"type":25,"tag":1932,"props":2736,"children":2737},{},[2738,2743,2748],{"type":25,"tag":1963,"props":2739,"children":2740},{},[2741],{"type":31,"value":2742},"插件生态",{"type":25,"tag":1963,"props":2744,"children":2745},{},[2746],{"type":31,"value":2747},"Webpack 生态",{"type":25,"tag":1963,"props":2749,"children":2750},{},[2751],{"type":31,"value":2752},"Rollup/Vite 生态",{"type":25,"tag":1932,"props":2754,"children":2755},{},[2756,2761,2766],{"type":25,"tag":1963,"props":2757,"children":2758},{},[2759],{"type":31,"value":2760},"适用项目",{"type":25,"tag":1963,"props":2762,"children":2763},{},[2764],{"type":31,"value":2765},"Webpack 迁移、大型 monorepo",{"type":25,"tag":1963,"props":2767,"children":2768},{},[2769],{"type":31,"value":2770},"新项目、中小型",{"type":25,"tag":33,"props":2772,"children":2773},{},[2774],{"type":31,"value":2775},"选择建议：",{"type":25,"tag":128,"props":2777,"children":2778},{},[2779,2784,2789],{"type":25,"tag":132,"props":2780,"children":2781},{},[2782],{"type":31,"value":2783},"新项目：优先 Vite",{"type":25,"tag":132,"props":2785,"children":2786},{},[2787],{"type":31,"value":2788},"Webpack 遗留项目：Rspack",{"type":25,"tag":132,"props":2790,"children":2791},{},[2792],{"type":31,"value":2793},"大型 monorepo + Webpack 依赖：Rspack",{"type":25,"tag":1746,"props":2795,"children":2796},{},[],{"type":25,"tag":26,"props":2798,"children":2800},{"id":2799},"_10-上线检查清单",[2801],{"type":31,"value":2802},"10. 上线检查清单",{"type":25,"tag":128,"props":2804,"children":2806},{"className":2805},[642],[2807,2816,2825,2834,2843,2852],{"type":25,"tag":132,"props":2808,"children":2810},{"className":2809},[647],[2811,2814],{"type":25,"tag":650,"props":2812,"children":2813},{"disabled":19,"type":652},[],{"type":31,"value":2815}," 本地开发环境已验证（HMR/热更新正常）",{"type":25,"tag":132,"props":2817,"children":2819},{"className":2818},[647],[2820,2823],{"type":25,"tag":650,"props":2821,"children":2822},{"disabled":19,"type":652},[],{"type":31,"value":2824}," CI 构建已切换并观测 3 天以上",{"type":25,"tag":132,"props":2826,"children":2828},{"className":2827},[647],[2829,2832],{"type":25,"tag":650,"props":2830,"children":2831},{"disabled":19,"type":652},[],{"type":31,"value":2833}," 产物体积对比无异常（baseline ± 5%）",{"type":25,"tag":132,"props":2835,"children":2837},{"className":2836},[647],[2838,2841],{"type":25,"tag":650,"props":2839,"children":2840},{"disabled":19,"type":652},[],{"type":31,"value":2842}," 关键页面功能回归测试通过",{"type":25,"tag":132,"props":2844,"children":2846},{"className":2845},[647],[2847,2850],{"type":25,"tag":650,"props":2848,"children":2849},{"disabled":19,"type":652},[],{"type":31,"value":2851}," 有构建耗时与缓存命中率监控",{"type":25,"tag":132,"props":2853,"children":2855},{"className":2854},[647],[2856,2859],{"type":25,"tag":650,"props":2857,"children":2858},{"disabled":19,"type":652},[],{"type":31,"value":2860}," 有回滚方案（保留 Webpack 配置）",{"type":25,"tag":1746,"props":2862,"children":2863},{},[],{"type":25,"tag":26,"props":2865,"children":2866},{"id":850},[2867],{"type":31,"value":850},{"type":25,"tag":33,"props":2869,"children":2870},{},[2871],{"type":31,"value":2872},"Rspack 的核心价值是：",{"type":25,"tag":128,"props":2874,"children":2875},{},[2876,2881,2886],{"type":25,"tag":132,"props":2877,"children":2878},{},[2879],{"type":31,"value":2880},"在 Webpack 生态下获得接近 Vite 的速度",{"type":25,"tag":132,"props":2882,"children":2883},{},[2884],{"type":31,"value":2885},"对大型项目构建成本与开发体验的显著改善",{"type":25,"tag":132,"props":2887,"children":2888},{},[2889],{"type":31,"value":2890},"生产级稳定性（字节跳动内部大规模验证）",{"title":7,"searchDepth":974,"depth":974,"links":2892},[2893,2894,2898,2903,2904,2909,2914,2915,2916,2921,2922,2923],{"id":1668,"depth":977,"text":1654},{"id":1751,"depth":977,"text":1754,"children":2895},[2896,2897],{"id":1762,"depth":974,"text":1765},{"id":1801,"depth":974,"text":1804},{"id":1828,"depth":977,"text":1831,"children":2899},[2900,2901,2902],{"id":1834,"depth":974,"text":1837},{"id":1858,"depth":974,"text":1861},{"id":1882,"depth":974,"text":1885},{"id":1914,"depth":977,"text":1917},{"id":2097,"depth":977,"text":2100,"children":2905},[2906,2907,2908],{"id":2103,"depth":974,"text":2106},{"id":2199,"depth":974,"text":2202},{"id":2268,"depth":974,"text":2271},{"id":2295,"depth":977,"text":2298,"children":2910},[2911,2912,2913],{"id":2301,"depth":974,"text":2304},{"id":2325,"depth":974,"text":2328},{"id":2347,"depth":974,"text":2350},{"id":2412,"depth":977,"text":2415},{"id":2483,"depth":977,"text":2486},{"id":2566,"depth":977,"text":2569,"children":2917},[2918,2919,2920],{"id":2572,"depth":974,"text":2575},{"id":2601,"depth":974,"text":2604},{"id":2625,"depth":974,"text":2628},{"id":2652,"depth":977,"text":2655},{"id":2799,"depth":977,"text":2802},{"id":850,"depth":977,"text":850},"content:topics:frontend:rspack-performance-practice.md","topics/frontend/rspack-performance-practice.md","topics/frontend/rspack-performance-practice",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":2928,"image":18,"featured":19,"readingTime":20,"body":2929,"_type":1008,"_id":1009,"_source":1010,"_file":1011,"_stem":1012,"_extension":1013},[13,14,15,16,17],{"type":22,"children":2930,"toc":3700},[2931,2935,2939,2943,2947,2951,2961,2981,2989,2997,3001,3005,3024,3028,3032,3036,3063,3067,3071,3078,3082,3086,3094,3101,3142,3146,3150,3158,3166,3193,3197,3201,3205,3213,3217,3221,3229,3237,3284,3288,3292,3311,3315,3323,3327,3334,3342,3349,3357,3361,3365,3369,3377,3381,3385,3393,3397,3401,3409,3413,3417,3421,3471,3475,3522,3526,3562,3566,3602,3606,3610,3653,3657,3661],{"type":25,"tag":26,"props":2932,"children":2933},{"id":28},[2934],{"type":31,"value":8},{"type":25,"tag":33,"props":2936,"children":2937},{},[2938],{"type":31,"value":37},{"type":25,"tag":26,"props":2940,"children":2941},{"id":40},[2942],{"type":31,"value":40},{"type":25,"tag":33,"props":2944,"children":2945},{},[2946],{"type":31,"value":47},{"type":25,"tag":49,"props":2948,"children":2949},{"id":51},[2950],{"type":31,"value":54},{"type":25,"tag":33,"props":2952,"children":2953},{},[2954,2955,2960],{"type":31,"value":59},{"type":25,"tag":61,"props":2956,"children":2958},{"className":2957},[],[2959],{"type":31,"value":66},{"type":31,"value":68},{"type":25,"tag":33,"props":2962,"children":2963},{},[2964,2968,2969,2974,2975,2980],{"type":25,"tag":73,"props":2965,"children":2966},{},[2967],{"type":31,"value":77},{"type":31,"value":79},{"type":25,"tag":61,"props":2970,"children":2972},{"className":2971},[],[2973],{"type":31,"value":85},{"type":31,"value":87},{"type":25,"tag":61,"props":2976,"children":2978},{"className":2977},[],[2979],{"type":31,"value":93},{"type":31,"value":95},{"type":25,"tag":33,"props":2982,"children":2983},{},[2984,2988],{"type":25,"tag":73,"props":2985,"children":2986},{},[2987],{"type":31,"value":103},{"type":31,"value":105},{"type":25,"tag":33,"props":2990,"children":2991},{},[2992,2996],{"type":25,"tag":73,"props":2993,"children":2994},{},[2995],{"type":31,"value":113},{"type":31,"value":115},{"type":25,"tag":49,"props":2998,"children":2999},{"id":118},[3000],{"type":31,"value":121},{"type":25,"tag":33,"props":3002,"children":3003},{},[3004],{"type":31,"value":126},{"type":25,"tag":128,"props":3006,"children":3007},{},[3008,3012,3016,3020],{"type":25,"tag":132,"props":3009,"children":3010},{},[3011],{"type":31,"value":136},{"type":25,"tag":132,"props":3013,"children":3014},{},[3015],{"type":31,"value":141},{"type":25,"tag":132,"props":3017,"children":3018},{},[3019],{"type":31,"value":146},{"type":25,"tag":132,"props":3021,"children":3022},{},[3023],{"type":31,"value":151},{"type":25,"tag":33,"props":3025,"children":3026},{},[3027],{"type":31,"value":156},{"type":25,"tag":49,"props":3029,"children":3030},{"id":159},[3031],{"type":31,"value":162},{"type":25,"tag":33,"props":3033,"children":3034},{},[3035],{"type":31,"value":167},{"type":25,"tag":128,"props":3037,"children":3038},{},[3039,3047,3055],{"type":25,"tag":132,"props":3040,"children":3041},{},[3042,3046],{"type":25,"tag":73,"props":3043,"children":3044},{},[3045],{"type":31,"value":178},{"type":31,"value":180},{"type":25,"tag":132,"props":3048,"children":3049},{},[3050,3054],{"type":25,"tag":73,"props":3051,"children":3052},{},[3053],{"type":31,"value":188},{"type":31,"value":190},{"type":25,"tag":132,"props":3056,"children":3057},{},[3058,3062],{"type":25,"tag":73,"props":3059,"children":3060},{},[3061],{"type":31,"value":198},{"type":31,"value":200},{"type":25,"tag":26,"props":3064,"children":3065},{"id":203},[3066],{"type":31,"value":203},{"type":25,"tag":33,"props":3068,"children":3069},{},[3070],{"type":31,"value":210},{"type":25,"tag":212,"props":3072,"children":3073},{"code":214},[3074],{"type":25,"tag":61,"props":3075,"children":3076},{"__ignoreMap":7},[3077],{"type":31,"value":214},{"type":25,"tag":49,"props":3079,"children":3080},{"id":222},[3081],{"type":31,"value":222},{"type":25,"tag":33,"props":3083,"children":3084},{},[3085],{"type":31,"value":229},{"type":25,"tag":212,"props":3087,"children":3089},{"code":232,"language":233,"meta":7,"className":3088},[235],[3090],{"type":25,"tag":61,"props":3091,"children":3092},{"__ignoreMap":7},[3093],{"type":31,"value":232},{"type":25,"tag":33,"props":3095,"children":3096},{},[3097],{"type":25,"tag":73,"props":3098,"children":3099},{},[3100],{"type":31,"value":248},{"type":25,"tag":250,"props":3102,"children":3103},{},[3104,3112,3126,3134],{"type":25,"tag":132,"props":3105,"children":3106},{},[3107,3111],{"type":25,"tag":73,"props":3108,"children":3109},{},[3110],{"type":31,"value":260},{"type":31,"value":262},{"type":25,"tag":132,"props":3113,"children":3114},{},[3115,3119,3120,3125],{"type":25,"tag":73,"props":3116,"children":3117},{},[3118],{"type":31,"value":270},{"type":31,"value":272},{"type":25,"tag":61,"props":3121,"children":3123},{"className":3122},[],[3124],{"type":31,"value":278},{"type":31,"value":280},{"type":25,"tag":132,"props":3127,"children":3128},{},[3129,3133],{"type":25,"tag":73,"props":3130,"children":3131},{},[3132],{"type":31,"value":288},{"type":31,"value":290},{"type":25,"tag":132,"props":3135,"children":3136},{},[3137,3141],{"type":25,"tag":73,"props":3138,"children":3139},{},[3140],{"type":31,"value":298},{"type":31,"value":300},{"type":25,"tag":49,"props":3143,"children":3144},{"id":303},[3145],{"type":31,"value":306},{"type":25,"tag":33,"props":3147,"children":3148},{},[3149],{"type":31,"value":311},{"type":25,"tag":212,"props":3151,"children":3153},{"code":314,"language":233,"meta":7,"className":3152},[235],[3154],{"type":25,"tag":61,"props":3155,"children":3156},{"__ignoreMap":7},[3157],{"type":31,"value":314},{"type":25,"tag":33,"props":3159,"children":3160},{},[3161,3165],{"type":25,"tag":73,"props":3162,"children":3163},{},[3164],{"type":31,"value":328},{"type":31,"value":330},{"type":25,"tag":250,"props":3167,"children":3168},{},[3169,3177,3185],{"type":25,"tag":132,"props":3170,"children":3171},{},[3172,3176],{"type":25,"tag":73,"props":3173,"children":3174},{},[3175],{"type":31,"value":341},{"type":31,"value":343},{"type":25,"tag":132,"props":3178,"children":3179},{},[3180,3184],{"type":25,"tag":73,"props":3181,"children":3182},{},[3183],{"type":31,"value":351},{"type":31,"value":353},{"type":25,"tag":132,"props":3186,"children":3187},{},[3188,3192],{"type":25,"tag":73,"props":3189,"children":3190},{},[3191],{"type":31,"value":361},{"type":31,"value":363},{"type":25,"tag":26,"props":3194,"children":3195},{"id":366},[3196],{"type":31,"value":366},{"type":25,"tag":49,"props":3198,"children":3199},{"id":371},[3200],{"type":31,"value":374},{"type":25,"tag":33,"props":3202,"children":3203},{},[3204],{"type":31,"value":379},{"type":25,"tag":212,"props":3206,"children":3208},{"code":382,"language":233,"meta":7,"className":3207},[235],[3209],{"type":25,"tag":61,"props":3210,"children":3211},{"__ignoreMap":7},[3212],{"type":31,"value":382},{"type":25,"tag":49,"props":3214,"children":3215},{"id":391},[3216],{"type":31,"value":391},{"type":25,"tag":33,"props":3218,"children":3219},{},[3220],{"type":31,"value":398},{"type":25,"tag":212,"props":3222,"children":3224},{"code":401,"language":233,"meta":7,"className":3223},[235],[3225],{"type":25,"tag":61,"props":3226,"children":3227},{"__ignoreMap":7},[3228],{"type":31,"value":401},{"type":25,"tag":33,"props":3230,"children":3231},{},[3232,3236],{"type":25,"tag":73,"props":3233,"children":3234},{},[3235],{"type":31,"value":415},{"type":31,"value":330},{"type":25,"tag":250,"props":3238,"children":3239},{},[3240,3248,3256,3270],{"type":25,"tag":132,"props":3241,"children":3242},{},[3243,3247],{"type":25,"tag":73,"props":3244,"children":3245},{},[3246],{"type":31,"value":427},{"type":31,"value":429},{"type":25,"tag":132,"props":3249,"children":3250},{},[3251,3255],{"type":25,"tag":73,"props":3252,"children":3253},{},[3254],{"type":31,"value":437},{"type":31,"value":439},{"type":25,"tag":132,"props":3257,"children":3258},{},[3259,3263,3264,3269],{"type":25,"tag":73,"props":3260,"children":3261},{},[3262],{"type":31,"value":447},{"type":31,"value":449},{"type":25,"tag":61,"props":3265,"children":3267},{"className":3266},[],[3268],{"type":31,"value":455},{"type":31,"value":457},{"type":25,"tag":132,"props":3271,"children":3272},{},[3273,3277,3278,3283],{"type":25,"tag":73,"props":3274,"children":3275},{},[3276],{"type":31,"value":465},{"type":31,"value":449},{"type":25,"tag":61,"props":3279,"children":3281},{"className":3280},[],[3282],{"type":31,"value":472},{"type":31,"value":474},{"type":25,"tag":26,"props":3285,"children":3286},{"id":477},[3287],{"type":31,"value":477},{"type":25,"tag":33,"props":3289,"children":3290},{},[3291],{"type":31,"value":484},{"type":25,"tag":250,"props":3293,"children":3294},{},[3295,3299,3303,3307],{"type":25,"tag":132,"props":3296,"children":3297},{},[3298],{"type":31,"value":492},{"type":25,"tag":132,"props":3300,"children":3301},{},[3302],{"type":31,"value":497},{"type":25,"tag":132,"props":3304,"children":3305},{},[3306],{"type":31,"value":502},{"type":25,"tag":132,"props":3308,"children":3309},{},[3310],{"type":31,"value":507},{"type":25,"tag":49,"props":3312,"children":3313},{"id":510},[3314],{"type":31,"value":510},{"type":25,"tag":212,"props":3316,"children":3318},{"code":515,"language":233,"meta":7,"className":3317},[235],[3319],{"type":25,"tag":61,"props":3320,"children":3321},{"__ignoreMap":7},[3322],{"type":31,"value":515},{"type":25,"tag":49,"props":3324,"children":3325},{"id":524},[3326],{"type":31,"value":524},{"type":25,"tag":33,"props":3328,"children":3329},{},[3330],{"type":25,"tag":73,"props":3331,"children":3332},{},[3333],{"type":31,"value":534},{"type":25,"tag":212,"props":3335,"children":3337},{"code":537,"language":233,"meta":7,"className":3336},[235],[3338],{"type":25,"tag":61,"props":3339,"children":3340},{"__ignoreMap":7},[3341],{"type":31,"value":537},{"type":25,"tag":33,"props":3343,"children":3344},{},[3345],{"type":25,"tag":73,"props":3346,"children":3347},{},[3348],{"type":31,"value":551},{"type":25,"tag":212,"props":3350,"children":3352},{"code":554,"language":233,"meta":7,"className":3351},[235],[3353],{"type":25,"tag":61,"props":3354,"children":3355},{"__ignoreMap":7},[3356],{"type":31,"value":554},{"type":25,"tag":26,"props":3358,"children":3359},{"id":563},[3360],{"type":31,"value":563},{"type":25,"tag":33,"props":3362,"children":3363},{},[3364],{"type":31,"value":570},{"type":25,"tag":49,"props":3366,"children":3367},{"id":573},[3368],{"type":31,"value":573},{"type":25,"tag":212,"props":3370,"children":3372},{"code":578,"language":233,"meta":7,"className":3371},[235],[3373],{"type":25,"tag":61,"props":3374,"children":3375},{"__ignoreMap":7},[3376],{"type":31,"value":578},{"type":25,"tag":49,"props":3378,"children":3379},{"id":587},[3380],{"type":31,"value":587},{"type":25,"tag":33,"props":3382,"children":3383},{},[3384],{"type":31,"value":594},{"type":25,"tag":212,"props":3386,"children":3388},{"code":597,"language":233,"meta":7,"className":3387},[235],[3389],{"type":25,"tag":61,"props":3390,"children":3391},{"__ignoreMap":7},[3392],{"type":31,"value":597},{"type":25,"tag":26,"props":3394,"children":3395},{"id":606},[3396],{"type":31,"value":609},{"type":25,"tag":33,"props":3398,"children":3399},{},[3400],{"type":31,"value":614},{"type":25,"tag":212,"props":3402,"children":3404},{"code":617,"language":233,"meta":7,"className":3403},[235],[3405],{"type":25,"tag":61,"props":3406,"children":3407},{"__ignoreMap":7},[3408],{"type":31,"value":617},{"type":25,"tag":26,"props":3410,"children":3411},{"id":626},[3412],{"type":31,"value":626},{"type":25,"tag":33,"props":3414,"children":3415},{},[3416],{"type":31,"value":633},{"type":25,"tag":49,"props":3418,"children":3419},{"id":636},[3420],{"type":31,"value":636},{"type":25,"tag":128,"props":3422,"children":3424},{"className":3423},[642],[3425,3433,3441,3449,3463],{"type":25,"tag":132,"props":3426,"children":3428},{"className":3427},[647],[3429,3432],{"type":25,"tag":650,"props":3430,"children":3431},{"disabled":19,"type":652},[],{"type":31,"value":655},{"type":25,"tag":132,"props":3434,"children":3436},{"className":3435},[647],[3437,3440],{"type":25,"tag":650,"props":3438,"children":3439},{"disabled":19,"type":652},[],{"type":31,"value":664},{"type":25,"tag":132,"props":3442,"children":3444},{"className":3443},[647],[3445,3448],{"type":25,"tag":650,"props":3446,"children":3447},{"disabled":19,"type":652},[],{"type":31,"value":673},{"type":25,"tag":132,"props":3450,"children":3452},{"className":3451},[647],[3453,3456,3457,3462],{"type":25,"tag":650,"props":3454,"children":3455},{"disabled":19,"type":652},[],{"type":31,"value":682},{"type":25,"tag":61,"props":3458,"children":3460},{"className":3459},[],[3461],{"type":31,"value":688},{"type":31,"value":690},{"type":25,"tag":132,"props":3464,"children":3466},{"className":3465},[647],[3467,3470],{"type":25,"tag":650,"props":3468,"children":3469},{"disabled":19,"type":652},[],{"type":31,"value":699},{"type":25,"tag":49,"props":3472,"children":3473},{"id":702},[3474],{"type":31,"value":702},{"type":25,"tag":128,"props":3476,"children":3478},{"className":3477},[642],[3479,3487,3506,3514],{"type":25,"tag":132,"props":3480,"children":3482},{"className":3481},[647],[3483,3486],{"type":25,"tag":650,"props":3484,"children":3485},{"disabled":19,"type":652},[],{"type":31,"value":717},{"type":25,"tag":132,"props":3488,"children":3490},{"className":3489},[647],[3491,3494,3495,3500,3501],{"type":25,"tag":650,"props":3492,"children":3493},{"disabled":19,"type":652},[],{"type":31,"value":682},{"type":25,"tag":61,"props":3496,"children":3498},{"className":3497},[],[3499],{"type":31,"value":731},{"type":31,"value":733},{"type":25,"tag":61,"props":3502,"children":3504},{"className":3503},[],[3505],{"type":31,"value":739},{"type":25,"tag":132,"props":3507,"children":3509},{"className":3508},[647],[3510,3513],{"type":25,"tag":650,"props":3511,"children":3512},{"disabled":19,"type":652},[],{"type":31,"value":748},{"type":25,"tag":132,"props":3515,"children":3517},{"className":3516},[647],[3518,3521],{"type":25,"tag":650,"props":3519,"children":3520},{"disabled":19,"type":652},[],{"type":31,"value":757},{"type":25,"tag":49,"props":3523,"children":3524},{"id":760},[3525],{"type":31,"value":760},{"type":25,"tag":128,"props":3527,"children":3529},{"className":3528},[642],[3530,3538,3546,3554],{"type":25,"tag":132,"props":3531,"children":3533},{"className":3532},[647],[3534,3537],{"type":25,"tag":650,"props":3535,"children":3536},{"disabled":19,"type":652},[],{"type":31,"value":775},{"type":25,"tag":132,"props":3539,"children":3541},{"className":3540},[647],[3542,3545],{"type":25,"tag":650,"props":3543,"children":3544},{"disabled":19,"type":652},[],{"type":31,"value":784},{"type":25,"tag":132,"props":3547,"children":3549},{"className":3548},[647],[3550,3553],{"type":25,"tag":650,"props":3551,"children":3552},{"disabled":19,"type":652},[],{"type":31,"value":793},{"type":25,"tag":132,"props":3555,"children":3557},{"className":3556},[647],[3558,3561],{"type":25,"tag":650,"props":3559,"children":3560},{"disabled":19,"type":652},[],{"type":31,"value":802},{"type":25,"tag":49,"props":3563,"children":3564},{"id":805},[3565],{"type":31,"value":805},{"type":25,"tag":128,"props":3567,"children":3569},{"className":3568},[642],[3570,3578,3586,3594],{"type":25,"tag":132,"props":3571,"children":3573},{"className":3572},[647],[3574,3577],{"type":25,"tag":650,"props":3575,"children":3576},{"disabled":19,"type":652},[],{"type":31,"value":820},{"type":25,"tag":132,"props":3579,"children":3581},{"className":3580},[647],[3582,3585],{"type":25,"tag":650,"props":3583,"children":3584},{"disabled":19,"type":652},[],{"type":31,"value":829},{"type":25,"tag":132,"props":3587,"children":3589},{"className":3588},[647],[3590,3593],{"type":25,"tag":650,"props":3591,"children":3592},{"disabled":19,"type":652},[],{"type":31,"value":838},{"type":25,"tag":132,"props":3595,"children":3597},{"className":3596},[647],[3598,3601],{"type":25,"tag":650,"props":3599,"children":3600},{"disabled":19,"type":652},[],{"type":31,"value":847},{"type":25,"tag":26,"props":3603,"children":3604},{"id":850},[3605],{"type":31,"value":850},{"type":25,"tag":33,"props":3607,"children":3608},{},[3609],{"type":31,"value":857},{"type":25,"tag":250,"props":3611,"children":3612},{},[3613,3621,3629,3637,3645],{"type":25,"tag":132,"props":3614,"children":3615},{},[3616,3620],{"type":25,"tag":73,"props":3617,"children":3618},{},[3619],{"type":31,"value":222},{"type":31,"value":869},{"type":25,"tag":132,"props":3622,"children":3623},{},[3624,3628],{"type":25,"tag":73,"props":3625,"children":3626},{},[3627],{"type":31,"value":877},{"type":31,"value":879},{"type":25,"tag":132,"props":3630,"children":3631},{},[3632,3636],{"type":25,"tag":73,"props":3633,"children":3634},{},[3635],{"type":31,"value":887},{"type":31,"value":889},{"type":25,"tag":132,"props":3638,"children":3639},{},[3640,3644],{"type":25,"tag":73,"props":3641,"children":3642},{},[3643],{"type":31,"value":897},{"type":31,"value":899},{"type":25,"tag":132,"props":3646,"children":3647},{},[3648,3652],{"type":25,"tag":73,"props":3649,"children":3650},{},[3651],{"type":31,"value":907},{"type":31,"value":909},{"type":25,"tag":33,"props":3654,"children":3655},{},[3656],{"type":31,"value":914},{"type":25,"tag":26,"props":3658,"children":3659},{"id":917},[3660],{"type":31,"value":917},{"type":25,"tag":128,"props":3662,"children":3663},{},[3664,3673,3682,3691],{"type":25,"tag":132,"props":3665,"children":3666},{},[3667,3672],{"type":25,"tag":927,"props":3668,"children":3670},{"href":929,"rel":3669},[931],[3671],{"type":31,"value":934},{"type":31,"value":936},{"type":25,"tag":132,"props":3674,"children":3675},{},[3676,3681],{"type":25,"tag":927,"props":3677,"children":3679},{"href":942,"rel":3678},[931],[3680],{"type":31,"value":946},{"type":31,"value":948},{"type":25,"tag":132,"props":3683,"children":3684},{},[3685,3690],{"type":25,"tag":927,"props":3686,"children":3688},{"href":954,"rel":3687},[931],[3689],{"type":31,"value":958},{"type":31,"value":960},{"type":25,"tag":132,"props":3692,"children":3693},{},[3694,3699],{"type":25,"tag":927,"props":3695,"children":3697},{"href":966,"rel":3696},[931],[3698],{"type":31,"value":970},{"type":31,"value":972},{"title":7,"searchDepth":974,"depth":974,"links":3701},[3702,3703,3708,3712,3716,3720,3724,3725,3731,3732],{"id":28,"depth":977,"text":8},{"id":40,"depth":977,"text":40,"children":3704},[3705,3706,3707],{"id":51,"depth":974,"text":54},{"id":118,"depth":974,"text":121},{"id":159,"depth":974,"text":162},{"id":203,"depth":977,"text":203,"children":3709},[3710,3711],{"id":222,"depth":974,"text":222},{"id":303,"depth":974,"text":306},{"id":366,"depth":977,"text":366,"children":3713},[3714,3715],{"id":371,"depth":974,"text":374},{"id":391,"depth":974,"text":391},{"id":477,"depth":977,"text":477,"children":3717},[3718,3719],{"id":510,"depth":974,"text":510},{"id":524,"depth":974,"text":524},{"id":563,"depth":977,"text":563,"children":3721},[3722,3723],{"id":573,"depth":974,"text":573},{"id":587,"depth":974,"text":587},{"id":606,"depth":977,"text":609},{"id":626,"depth":977,"text":626,"children":3726},[3727,3728,3729,3730],{"id":636,"depth":974,"text":636},{"id":702,"depth":974,"text":702},{"id":760,"depth":974,"text":760},{"id":805,"depth":974,"text":805},{"id":850,"depth":977,"text":850},{"id":917,"depth":977,"text":917},1778574594186]