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