[{"data":1,"prerenderedAt":2639},["ShallowReactive",2],{"article-/topics/frontend/react-testing-library-complete-guide":3,"related-frontend":414,"content-query-dTVME0WlNv":2336},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":12,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":23,"_type":408,"_id":409,"_source":410,"_file":411,"_stem":412,"_extension":413},"/topics/frontend/react-testing-library-complete-guide","frontend",false,"","React Testing Library 完整指南：用更接近用户的方式构建可维护测试","React Testing Library 的价值不只是换一套 API，而是帮助团队围绕真实交互、可访问性和可维护性重建前端测试边界。本文从测试原则、常见误区和工程落地出发，系统讲清 RTL 实践方法。","2026-04-28","HTMLPAGE 团队",[13,14,15,16,17],"React","Testing Library","Frontend Testing","Accessibility","Component Testing","/images/topics/frontend/react-testing-library-complete-guide.jpg","frontend testing code on laptop screen software testing",34803988,"https://www.pexels.com/photo/close-up-of-programming-code-on-computer-screen-34803988/",16,{"type":24,"children":25,"toc":396},"root",[26,34,39,46,51,56,81,86,104,109,114,127,148,153,158,163,181,190,196,201,206,234,239,245,250,273,278,284,289,294,312,317,322,350,355,360,365],{"type":27,"tag":28,"props":29,"children":30},"element","p",{},[31],{"type":32,"value":33},"text","React Testing Library 这些年越来越常见，不是因为它让测试写起来更短，而是因为它迫使团队回到一个更健康的问题上：用户到底能不能完成操作。",{"type":27,"tag":28,"props":35,"children":36},{},[37],{"type":32,"value":38},"如果测试主要围绕组件实例、内部 state 和实现细节展开，重构时测试通常会先坏掉。RTL 的价值，就在于尽量让测试站在用户视角，而不是站在组件内部视角。",{"type":27,"tag":40,"props":41,"children":43},"h2",{"id":42},"先理解-rtl-的核心原则测试行为不测试实现细节",[44],{"type":32,"value":45},"先理解 RTL 的核心原则：测试行为，不测试实现细节",{"type":27,"tag":28,"props":47,"children":48},{},[49],{"type":32,"value":50},"RTL 最重要的心智模型只有一句话：越接近用户如何使用页面，测试就越有价值。",{"type":27,"tag":28,"props":52,"children":53},{},[54],{"type":32,"value":55},"这意味着团队更应该验证：",{"type":27,"tag":57,"props":58,"children":59},"ul",{},[60,66,71,76],{"type":27,"tag":61,"props":62,"children":63},"li",{},[64],{"type":32,"value":65},"文本是否出现",{"type":27,"tag":61,"props":67,"children":68},{},[69],{"type":32,"value":70},"按钮是否可点击",{"type":27,"tag":61,"props":72,"children":73},{},[74],{"type":32,"value":75},"表单报错是否可感知",{"type":27,"tag":61,"props":77,"children":78},{},[79],{"type":32,"value":80},"异步加载后页面是否进入正确状态",{"type":27,"tag":28,"props":82,"children":83},{},[84],{"type":32,"value":85},"而不是验证：",{"type":27,"tag":57,"props":87,"children":88},{},[89,94,99],{"type":27,"tag":61,"props":90,"children":91},{},[92],{"type":32,"value":93},"某个 hook 是否被调用几次",{"type":27,"tag":61,"props":95,"children":96},{},[97],{"type":32,"value":98},"某个 state 内部值是否变化",{"type":27,"tag":61,"props":100,"children":101},{},[102],{"type":32,"value":103},"某个子组件实例是否存在",{"type":27,"tag":40,"props":105,"children":107},{"id":106},"查询顺序决定测试的稳定性",[108],{"type":32,"value":106},{"type":27,"tag":28,"props":110,"children":111},{},[112],{"type":32,"value":113},"RTL 推荐的查询顺序，本质上是在提醒团队优先使用真实语义。",{"type":27,"tag":115,"props":116,"children":121},"pre",{"className":117,"code":119,"language":120,"meta":7},[118],"language-tsx","import { render, screen } from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\n\nit('submits the form after valid input', async () => {\n  const user = userEvent.setup()\n  render(\u003CLoginForm />)\n\n  await user.type(screen.getByLabelText(/email/i), 'team@htmlpage.cn')\n  await user.type(screen.getByLabelText(/password/i), 'safe-password')\n  await user.click(screen.getByRole('button', { name: /sign in/i }))\n\n  expect(await screen.findByText(/welcome back/i)).toBeInTheDocument()\n})\n","tsx",[122],{"type":27,"tag":123,"props":124,"children":125},"code",{"__ignoreMap":7},[126],{"type":32,"value":119},{"type":27,"tag":28,"props":128,"children":129},{},[130,132,138,140,146],{"type":32,"value":131},"优先使用 ",{"type":27,"tag":123,"props":133,"children":135},{"className":134},[],[136],{"type":32,"value":137},"getByRole",{"type":32,"value":139},"、",{"type":27,"tag":123,"props":141,"children":143},{"className":142},[],[144],{"type":32,"value":145},"getByLabelText",{"type":32,"value":147}," 这类查询，不只是为了写法规范，更是因为它会逼迫组件本身具备更好的可访问性。",{"type":27,"tag":40,"props":149,"children":151},{"id":150},"异步测试要验证用户看到的状态变化",[152],{"type":32,"value":150},{"type":27,"tag":28,"props":154,"children":155},{},[156],{"type":32,"value":157},"很多团队在写异步测试时，容易直接等待某个 mock 被调用，然后结束断言。这样做能测到实现，却未必测到用户体验。",{"type":27,"tag":28,"props":159,"children":160},{},[161],{"type":32,"value":162},"更稳的做法是把状态变化写完整：",{"type":27,"tag":57,"props":164,"children":165},{},[166,171,176],{"type":27,"tag":61,"props":167,"children":168},{},[169],{"type":32,"value":170},"加载态是否出现",{"type":27,"tag":61,"props":172,"children":173},{},[174],{"type":32,"value":175},"成功后是否出现结果",{"type":27,"tag":61,"props":177,"children":178},{},[179],{"type":32,"value":180},"失败后是否显示明确错误",{"type":27,"tag":115,"props":182,"children":185},{"className":183,"code":184,"language":120,"meta":7},[118],"it('shows error message when request fails', async () => {\n  server.use(http.post('/api/login', () => HttpResponse.json({ message: 'Invalid credentials' }, { status: 401 })))\n\n  render(\u003CLoginForm />)\n\n  await userEvent.type(screen.getByLabelText(/email/i), 'wrong@example.com')\n  await userEvent.type(screen.getByLabelText(/password/i), 'bad-pass')\n  await userEvent.click(screen.getByRole('button', { name: /sign in/i }))\n\n  expect(await screen.findByRole('alert')).toHaveTextContent(/invalid credentials/i)\n})\n",[186],{"type":27,"tag":123,"props":187,"children":188},{"__ignoreMap":7},[189],{"type":32,"value":184},{"type":27,"tag":40,"props":191,"children":193},{"id":192},"测试数据和-mock-要围绕边界场景组织",[194],{"type":32,"value":195},"测试数据和 mock 要围绕边界场景组织",{"type":27,"tag":28,"props":197,"children":198},{},[199],{"type":32,"value":200},"RTL 并不自动减少测试脆弱性。如果 mock 数据过于理想化，测试一样会失真。",{"type":27,"tag":28,"props":202,"children":203},{},[204],{"type":32,"value":205},"更好的组织方式通常是围绕边界场景：",{"type":27,"tag":57,"props":207,"children":208},{},[209,214,219,224,229],{"type":27,"tag":61,"props":210,"children":211},{},[212],{"type":32,"value":213},"空数据",{"type":27,"tag":61,"props":215,"children":216},{},[217],{"type":32,"value":218},"超长文本",{"type":27,"tag":61,"props":220,"children":221},{},[222],{"type":32,"value":223},"权限不足",{"type":27,"tag":61,"props":225,"children":226},{},[227],{"type":32,"value":228},"网络失败",{"type":27,"tag":61,"props":230,"children":231},{},[232],{"type":32,"value":233},"慢请求下的加载状态",{"type":27,"tag":28,"props":235,"children":236},{},[237],{"type":32,"value":238},"这样测试资产才会真正覆盖高风险交互，而不是只覆盖 happy path。",{"type":27,"tag":40,"props":240,"children":242},{"id":241},"一个常见失败案例迁移到-rtl-了但测试仍然很脆弱",[243],{"type":32,"value":244},"一个常见失败案例：迁移到 RTL 了，但测试仍然很脆弱",{"type":27,"tag":28,"props":246,"children":247},{},[248],{"type":32,"value":249},"常见原因不是工具没选对，而是团队只是把旧思路换了 API：",{"type":27,"tag":57,"props":251,"children":252},{},[253,258,263,268],{"type":27,"tag":61,"props":254,"children":255},{},[256],{"type":32,"value":257},"继续大量断言实现细节",{"type":27,"tag":61,"props":259,"children":260},{},[261],{"type":32,"value":262},"继续依赖 test id 替代语义查询",{"type":27,"tag":61,"props":264,"children":265},{},[266],{"type":32,"value":267},"继续只测成功路径",{"type":27,"tag":61,"props":269,"children":270},{},[271],{"type":32,"value":272},"没把可访问性纳入测试标准",{"type":27,"tag":28,"props":274,"children":275},{},[276],{"type":32,"value":277},"结果看起来“已经是 RTL”，本质上仍然是旧测试思路。",{"type":27,"tag":40,"props":279,"children":281},{"id":280},"工程落地建议把组件测试边界说清楚",[282],{"type":32,"value":283},"工程落地建议：把组件测试边界说清楚",{"type":27,"tag":28,"props":285,"children":286},{},[287],{"type":32,"value":288},"React Testing Library 很适合用来覆盖组件与页面层的真实交互，但并不意味着一切都该塞进组件测试。",{"type":27,"tag":28,"props":290,"children":291},{},[292],{"type":32,"value":293},"建议团队明确分层：",{"type":27,"tag":57,"props":295,"children":296},{},[297,302,307],{"type":27,"tag":61,"props":298,"children":299},{},[300],{"type":32,"value":301},"单元测试：纯函数、格式化、状态转换",{"type":27,"tag":61,"props":303,"children":304},{},[305],{"type":32,"value":306},"组件测试：用户输入、状态反馈、可访问性",{"type":27,"tag":61,"props":308,"children":309},{},[310],{"type":32,"value":311},"E2E 测试：关键业务流程与跨页面链路",{"type":27,"tag":28,"props":313,"children":314},{},[315],{"type":32,"value":316},"层级清楚以后，RTL 才不会被错误地用来承接所有测试职责。",{"type":27,"tag":40,"props":318,"children":320},{"id":319},"一份可直接复用的检查清单",[321],{"type":32,"value":319},{"type":27,"tag":57,"props":323,"children":324},{},[325,330,335,340,345],{"type":27,"tag":61,"props":326,"children":327},{},[328],{"type":32,"value":329},"测试是否优先验证用户行为和可见结果",{"type":27,"tag":61,"props":331,"children":332},{},[333],{"type":32,"value":334},"查询方式是否优先使用 role、label、text 等语义入口",{"type":27,"tag":61,"props":336,"children":337},{},[338],{"type":32,"value":339},"异步测试是否覆盖加载、成功和失败状态",{"type":27,"tag":61,"props":341,"children":342},{},[343],{"type":32,"value":344},"mock 和测试数据是否覆盖高风险边界场景",{"type":27,"tag":61,"props":346,"children":347},{},[348],{"type":32,"value":349},"团队是否明确了 RTL 在整体测试体系中的职责边界",{"type":27,"tag":40,"props":351,"children":353},{"id":352},"总结",[354],{"type":32,"value":352},{"type":27,"tag":28,"props":356,"children":357},{},[358],{"type":32,"value":359},"React Testing Library 的真正价值，不是“更现代”，而是让前端测试重新围绕真实使用方式组织。只要先把查询策略、异步状态和测试层级建立清楚，RTL 就会帮助团队获得更稳定、更可维护的测试资产。",{"type":27,"tag":28,"props":361,"children":362},{},[363],{"type":32,"value":364},"进一步阅读：",{"type":27,"tag":57,"props":366,"children":367},{},[368,378,387],{"type":27,"tag":61,"props":369,"children":370},{},[371],{"type":27,"tag":372,"props":373,"children":375},"a",{"href":374},"/topics/frontend/frontend-automation-testing-strategy",[376],{"type":32,"value":377},"前端自动化测试策略",{"type":27,"tag":61,"props":379,"children":380},{},[381],{"type":27,"tag":372,"props":382,"children":384},{"href":383},"/topics/frontend/e2e-testing-comparison",[385],{"type":32,"value":386},"E2E 测试框架对比",{"type":27,"tag":61,"props":388,"children":389},{},[390],{"type":27,"tag":372,"props":391,"children":393},{"href":392},"/topics/ai/ai-driven-test-case-generation",[394],{"type":32,"value":395},"AI 驱动的测试用例生成",{"title":7,"searchDepth":397,"depth":397,"links":398},3,[399,401,402,403,404,405,406,407],{"id":42,"depth":400,"text":45},2,{"id":106,"depth":400,"text":106},{"id":150,"depth":400,"text":150},{"id":192,"depth":400,"text":195},{"id":241,"depth":400,"text":244},{"id":280,"depth":400,"text":283},{"id":319,"depth":400,"text":319},{"id":352,"depth":400,"text":352},"markdown","content:topics:frontend:react-testing-library-complete-guide.md","content","topics/frontend/react-testing-library-complete-guide.md","topics/frontend/react-testing-library-complete-guide","md",[415,746,1058],{"_path":416,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":417,"description":418,"keywords":419,"image":424,"author":425,"date":426,"readingTime":427,"topic":5,"body":428,"_type":408,"_id":743,"_source":410,"_file":744,"_stem":745,"_extension":413},"/topics/frontend/react-hooks-guide","React Hooks 完全指南","全面讲解 React Hooks，包括内置钩子、自定义钩子和最佳实践",[13,420,421,422,423],"Hooks","自定义钩子","状态管理","函数组件","/images/topics/react-hooks-guide.jpg","AI Content Team","2025-12-08",23,{"type":24,"children":429,"toc":724},[430,435,440,446,453,464,470,479,485,494,500,506,515,521,530,536,545,551,560,565,571,580,585,598,626,637,665,670],{"type":27,"tag":40,"props":431,"children":433},{"id":432},"react-hooks-完全指南",[434],{"type":32,"value":417},{"type":27,"tag":28,"props":436,"children":437},{},[438],{"type":32,"value":439},"Hooks 改变了 React 的开发方式。本文全面讲解如何使用和创建 Hooks。",{"type":27,"tag":40,"props":441,"children":443},{"id":442},"内置-hooks",[444],{"type":32,"value":445},"内置 Hooks",{"type":27,"tag":447,"props":448,"children":450},"h3",{"id":449},"usestate-状态管理",[451],{"type":32,"value":452},"useState - 状态管理",{"type":27,"tag":115,"props":454,"children":459},{"className":455,"code":457,"language":458,"meta":7},[456],"language-javascript","import { useState } from 'react'\n\nfunction Counter() {\n  const [count, setCount] = useState(0)\n  const [name, setName] = useState('John')\n  const [user, setUser] = useState({\n    age: 30,\n    email: 'john@example.com',\n  })\n  \n  // 使用函数初始化状态（对于复杂初始值）\n  const [data, setData] = useState(() => {\n    console.log('初始化数据...')\n    return fetchInitialData() // 仅在首次渲染时调用\n  })\n  \n  return (\n    \u003Cdiv>\n      \u003Cp>计数: {count}\u003C/p>\n      \u003Cbutton onClick={() => setCount(count + 1)}>增加\u003C/button>\n      \n      {/* 函数式更新 */}\n      \u003Cbutton onClick={() => setCount(prev => prev + 1)}>\n        函数式增加\n      \u003C/button>\n      \n      {/* 更新对象 */}\n      \u003Cbutton onClick={() => setUser({ ...user, age: user.age + 1 })}>\n        增加年龄\n      \u003C/button>\n    \u003C/div>\n  )\n}\n","javascript",[460],{"type":27,"tag":123,"props":461,"children":462},{"__ignoreMap":7},[463],{"type":32,"value":457},{"type":27,"tag":447,"props":465,"children":467},{"id":466},"useeffect-副作用处理",[468],{"type":32,"value":469},"useEffect - 副作用处理",{"type":27,"tag":115,"props":471,"children":474},{"className":472,"code":473,"language":458,"meta":7},[456],"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",[475],{"type":27,"tag":123,"props":476,"children":477},{"__ignoreMap":7},[478],{"type":32,"value":473},{"type":27,"tag":447,"props":480,"children":482},{"id":481},"usecontext-跨组件通信",[483],{"type":32,"value":484},"useContext - 跨组件通信",{"type":27,"tag":115,"props":486,"children":489},{"className":487,"code":488,"language":458,"meta":7},[456],"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",[490],{"type":27,"tag":123,"props":491,"children":492},{"__ignoreMap":7},[493],{"type":32,"value":488},{"type":27,"tag":40,"props":495,"children":497},{"id":496},"自定义-hooks",[498],{"type":32,"value":499},"自定义 Hooks",{"type":27,"tag":447,"props":501,"children":503},{"id":502},"uselocalstorage",[504],{"type":32,"value":505},"useLocalStorage",{"type":27,"tag":115,"props":507,"children":510},{"className":508,"code":509,"language":458,"meta":7},[456],"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",[511],{"type":27,"tag":123,"props":512,"children":513},{"__ignoreMap":7},[514],{"type":32,"value":509},{"type":27,"tag":447,"props":516,"children":518},{"id":517},"useasync-异步操作",[519],{"type":32,"value":520},"useAsync - 异步操作",{"type":27,"tag":115,"props":522,"children":525},{"className":523,"code":524,"language":458,"meta":7},[456],"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",[526],{"type":27,"tag":123,"props":527,"children":528},{"__ignoreMap":7},[529],{"type":32,"value":524},{"type":27,"tag":447,"props":531,"children":533},{"id":532},"usefetch-数据获取",[534],{"type":32,"value":535},"useFetch - 数据获取",{"type":27,"tag":115,"props":537,"children":540},{"className":538,"code":539,"language":458,"meta":7},[456],"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",[541],{"type":27,"tag":123,"props":542,"children":543},{"__ignoreMap":7},[544],{"type":32,"value":539},{"type":27,"tag":447,"props":546,"children":548},{"id":547},"useprevious-保存前一个值",[549],{"type":32,"value":550},"usePrevious - 保存前一个值",{"type":27,"tag":115,"props":552,"children":555},{"className":553,"code":554,"language":458,"meta":7},[456],"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",[556],{"type":27,"tag":123,"props":557,"children":558},{"__ignoreMap":7},[559],{"type":32,"value":554},{"type":27,"tag":40,"props":561,"children":563},{"id":562},"高级模式",[564],{"type":32,"value":562},{"type":27,"tag":447,"props":566,"children":568},{"id":567},"usereducer-复杂状态管理",[569],{"type":32,"value":570},"useReducer - 复杂状态管理",{"type":27,"tag":115,"props":572,"children":575},{"className":573,"code":574,"language":458,"meta":7},[456],"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",[576],{"type":27,"tag":123,"props":577,"children":578},{"__ignoreMap":7},[579],{"type":32,"value":574},{"type":27,"tag":40,"props":581,"children":583},{"id":582},"最佳实践",[584],{"type":32,"value":582},{"type":27,"tag":28,"props":586,"children":587},{},[588,590,596],{"type":32,"value":589},"✅ ",{"type":27,"tag":591,"props":592,"children":593},"strong",{},[594],{"type":32,"value":595},"应该做的事",{"type":32,"value":597},":",{"type":27,"tag":57,"props":599,"children":600},{},[601,606,611,616,621],{"type":27,"tag":61,"props":602,"children":603},{},[604],{"type":32,"value":605},"将相关逻辑提取到自定义 Hooks",{"type":27,"tag":61,"props":607,"children":608},{},[609],{"type":32,"value":610},"在 useEffect 的依赖数组中包含所有依赖",{"type":27,"tag":61,"props":612,"children":613},{},[614],{"type":32,"value":615},"使用 useCallback 和 useMemo 优化性能",{"type":27,"tag":61,"props":617,"children":618},{},[619],{"type":32,"value":620},"为自定义 Hooks 编写文档",{"type":27,"tag":61,"props":622,"children":623},{},[624],{"type":32,"value":625},"及时清理副作用",{"type":27,"tag":28,"props":627,"children":628},{},[629,631,636],{"type":32,"value":630},"❌ ",{"type":27,"tag":591,"props":632,"children":633},{},[634],{"type":32,"value":635},"不应该做的事",{"type":32,"value":597},{"type":27,"tag":57,"props":638,"children":639},{},[640,645,650,655,660],{"type":27,"tag":61,"props":641,"children":642},{},[643],{"type":32,"value":644},"在条件或循环中调用 Hooks",{"type":27,"tag":61,"props":646,"children":647},{},[648],{"type":32,"value":649},"在普通函数中调用 Hooks",{"type":27,"tag":61,"props":651,"children":652},{},[653],{"type":32,"value":654},"忘记依赖数组",{"type":27,"tag":61,"props":656,"children":657},{},[658],{"type":32,"value":659},"过度使用 useMemo/useCallback",{"type":27,"tag":61,"props":661,"children":662},{},[663],{"type":32,"value":664},"在 Hooks 中创建过多的闭包",{"type":27,"tag":40,"props":666,"children":668},{"id":667},"检查清单",[669],{"type":32,"value":667},{"type":27,"tag":57,"props":671,"children":674},{"className":672},[673],"contains-task-list",[675,688,697,706,715],{"type":27,"tag":61,"props":676,"children":679},{"className":677},[678],"task-list-item",[680,686],{"type":27,"tag":681,"props":682,"children":685},"input",{"disabled":683,"type":684},true,"checkbox",[],{"type":32,"value":687}," Hooks 调用顺序正确",{"type":27,"tag":61,"props":689,"children":691},{"className":690},[678],[692,695],{"type":27,"tag":681,"props":693,"children":694},{"disabled":683,"type":684},[],{"type":32,"value":696}," 依赖数组完整",{"type":27,"tag":61,"props":698,"children":700},{"className":699},[678],[701,704],{"type":27,"tag":681,"props":702,"children":703},{"disabled":683,"type":684},[],{"type":32,"value":705}," 副作用正确清理",{"type":27,"tag":61,"props":707,"children":709},{"className":708},[678],[710,713],{"type":27,"tag":681,"props":711,"children":712},{"disabled":683,"type":684},[],{"type":32,"value":714}," 性能优化得当",{"type":27,"tag":61,"props":716,"children":718},{"className":717},[678],[719,722],{"type":27,"tag":681,"props":720,"children":721},{"disabled":683,"type":684},[],{"type":32,"value":723}," 代码易于理解和测试",{"title":7,"searchDepth":397,"depth":397,"links":725},[726,727,732,738,741,742],{"id":432,"depth":400,"text":417},{"id":442,"depth":400,"text":445,"children":728},[729,730,731],{"id":449,"depth":397,"text":452},{"id":466,"depth":397,"text":469},{"id":481,"depth":397,"text":484},{"id":496,"depth":400,"text":499,"children":733},[734,735,736,737],{"id":502,"depth":397,"text":505},{"id":517,"depth":397,"text":520},{"id":532,"depth":397,"text":535},{"id":547,"depth":397,"text":550},{"id":562,"depth":400,"text":562,"children":739},[740],{"id":567,"depth":397,"text":570},{"id":582,"depth":400,"text":582},{"id":667,"depth":400,"text":667},"content:topics:frontend:react-hooks-guide.md","topics/frontend/react-hooks-guide.md","topics/frontend/react-hooks-guide",{"_path":747,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":748,"description":749,"keywords":750,"image":756,"author":425,"date":426,"readingTime":757,"topic":5,"body":758,"_type":408,"_id":1055,"_source":410,"_file":1056,"_stem":1057,"_extension":413},"/topics/frontend/vue3-composition-api","Vue 3 Composition API 深度解析","全面讲解 Vue 3 Composition API 的用法、最佳实践和高级模式",[751,752,753,754,755],"Vue 3","Composition API","组合式函数","响应式系统","前端开发","/images/topics/vue3-composition-api.jpg",22,{"type":24,"children":759,"toc":1031},[760,765,770,775,781,790,795,804,808,813,822,827,833,842,847,853,862,867,872,881,886,892,901,905,914,942,951,979,983],{"type":27,"tag":40,"props":761,"children":763},{"id":762},"vue-3-composition-api-深度解析",[764],{"type":32,"value":748},{"type":27,"tag":28,"props":766,"children":767},{},[768],{"type":32,"value":769},"Composition API 让 Vue 应用更易于组织和重用逻辑。本文深入讲解这一核心特性。",{"type":27,"tag":40,"props":771,"children":773},{"id":772},"核心概念",[774],{"type":32,"value":772},{"type":27,"tag":447,"props":776,"children":778},{"id":777},"setup-函数",[779],{"type":32,"value":780},"setup 函数",{"type":27,"tag":115,"props":782,"children":785},{"className":783,"code":784,"language":458,"meta":7},[456],"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",[786],{"type":27,"tag":123,"props":787,"children":788},{"__ignoreMap":7},[789],{"type":32,"value":784},{"type":27,"tag":447,"props":791,"children":793},{"id":792},"响应式基础",[794],{"type":32,"value":792},{"type":27,"tag":115,"props":796,"children":799},{"className":797,"code":798,"language":458,"meta":7},[456],"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",[800],{"type":27,"tag":123,"props":801,"children":802},{"__ignoreMap":7},[803],{"type":32,"value":798},{"type":27,"tag":40,"props":805,"children":806},{"id":753},[807],{"type":32,"value":753},{"type":27,"tag":447,"props":809,"children":811},{"id":810},"创建可重用逻辑",[812],{"type":32,"value":810},{"type":27,"tag":115,"props":814,"children":817},{"className":815,"code":816,"language":458,"meta":7},[456],"// 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",[818],{"type":27,"tag":123,"props":819,"children":820},{"__ignoreMap":7},[821],{"type":32,"value":816},{"type":27,"tag":40,"props":823,"children":825},{"id":824},"生命周期钩子",[826],{"type":32,"value":824},{"type":27,"tag":447,"props":828,"children":830},{"id":829},"composition-api-中的生命周期",[831],{"type":32,"value":832},"Composition API 中的生命周期",{"type":27,"tag":115,"props":834,"children":837},{"className":835,"code":836,"language":458,"meta":7},[456],"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",[838],{"type":27,"tag":123,"props":839,"children":840},{"__ignoreMap":7},[841],{"type":32,"value":836},{"type":27,"tag":40,"props":843,"children":845},{"id":844},"模板引用",[846],{"type":32,"value":844},{"type":27,"tag":447,"props":848,"children":850},{"id":849},"访问-dom-元素",[851],{"type":32,"value":852},"访问 DOM 元素",{"type":27,"tag":115,"props":854,"children":857},{"className":855,"code":856,"language":458,"meta":7},[456],"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",[858],{"type":27,"tag":123,"props":859,"children":860},{"__ignoreMap":7},[861],{"type":32,"value":856},{"type":27,"tag":40,"props":863,"children":865},{"id":864},"依赖注入",[866],{"type":32,"value":864},{"type":27,"tag":447,"props":868,"children":870},{"id":869},"跨组件共享数据",[871],{"type":32,"value":869},{"type":27,"tag":115,"props":873,"children":876},{"className":874,"code":875,"language":458,"meta":7},[456],"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",[877],{"type":27,"tag":123,"props":878,"children":879},{"__ignoreMap":7},[880],{"type":32,"value":875},{"type":27,"tag":40,"props":882,"children":884},{"id":883},"高级状态管理",[885],{"type":32,"value":883},{"type":27,"tag":447,"props":887,"children":889},{"id":888},"创建小型-store",[890],{"type":32,"value":891},"创建小型 store",{"type":27,"tag":115,"props":893,"children":896},{"className":894,"code":895,"language":458,"meta":7},[456],"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",[897],{"type":27,"tag":123,"props":898,"children":899},{"__ignoreMap":7},[900],{"type":32,"value":895},{"type":27,"tag":40,"props":902,"children":903},{"id":582},[904],{"type":32,"value":582},{"type":27,"tag":28,"props":906,"children":907},{},[908,909,913],{"type":32,"value":589},{"type":27,"tag":591,"props":910,"children":911},{},[912],{"type":32,"value":595},{"type":32,"value":597},{"type":27,"tag":57,"props":915,"children":916},{},[917,922,927,932,937],{"type":27,"tag":61,"props":918,"children":919},{},[920],{"type":32,"value":921},"将相关逻辑组织在一起",{"type":27,"tag":61,"props":923,"children":924},{},[925],{"type":32,"value":926},"创建可重用的组合式函数",{"type":27,"tag":61,"props":928,"children":929},{},[930],{"type":32,"value":931},"使用 TypeScript 获得更好的类型检查",{"type":27,"tag":61,"props":933,"children":934},{},[935],{"type":32,"value":936},"合理使用计算属性和监听器",{"type":27,"tag":61,"props":938,"children":939},{},[940],{"type":32,"value":941},"及时清理事件监听器和定时器",{"type":27,"tag":28,"props":943,"children":944},{},[945,946,950],{"type":32,"value":630},{"type":27,"tag":591,"props":947,"children":948},{},[949],{"type":32,"value":635},{"type":32,"value":597},{"type":27,"tag":57,"props":952,"children":953},{},[954,959,964,969,974],{"type":27,"tag":61,"props":955,"children":956},{},[957],{"type":32,"value":958},"在 setup 中执行副作用操作（除了生命周期钩子）",{"type":27,"tag":61,"props":960,"children":961},{},[962],{"type":32,"value":963},"过度使用计算属性",{"type":27,"tag":61,"props":965,"children":966},{},[967],{"type":32,"value":968},"忘记清理 watch 监听器",{"type":27,"tag":61,"props":970,"children":971},{},[972],{"type":32,"value":973},"在 reactive 对象中存储引用类型时不谨慎",{"type":27,"tag":61,"props":975,"children":976},{},[977],{"type":32,"value":978},"过度复杂化组合式函数",{"type":27,"tag":40,"props":980,"children":981},{"id":667},[982],{"type":32,"value":667},{"type":27,"tag":57,"props":984,"children":986},{"className":985},[673],[987,996,1005,1014,1023],{"type":27,"tag":61,"props":988,"children":990},{"className":989},[678],[991,994],{"type":27,"tag":681,"props":992,"children":993},{"disabled":683,"type":684},[],{"type":32,"value":995}," 正确使用 ref 和 reactive",{"type":27,"tag":61,"props":997,"children":999},{"className":998},[678],[1000,1003],{"type":27,"tag":681,"props":1001,"children":1002},{"disabled":683,"type":684},[],{"type":32,"value":1004}," 生命周期钩子正确",{"type":27,"tag":61,"props":1006,"children":1008},{"className":1007},[678],[1009,1012],{"type":27,"tag":681,"props":1010,"children":1011},{"disabled":683,"type":684},[],{"type":32,"value":1013}," 模板引用工作正常",{"type":27,"tag":61,"props":1015,"children":1017},{"className":1016},[678],[1018,1021],{"type":27,"tag":681,"props":1019,"children":1020},{"disabled":683,"type":684},[],{"type":32,"value":1022}," 组合式函数可重用",{"type":27,"tag":61,"props":1024,"children":1026},{"className":1025},[678],[1027,1030],{"type":27,"tag":681,"props":1028,"children":1029},{"disabled":683,"type":684},[],{"type":32,"value":714},{"title":7,"searchDepth":397,"depth":397,"links":1032},[1033,1034,1038,1041,1044,1047,1050,1053,1054],{"id":762,"depth":400,"text":748},{"id":772,"depth":400,"text":772,"children":1035},[1036,1037],{"id":777,"depth":397,"text":780},{"id":792,"depth":397,"text":792},{"id":753,"depth":400,"text":753,"children":1039},[1040],{"id":810,"depth":397,"text":810},{"id":824,"depth":400,"text":824,"children":1042},[1043],{"id":829,"depth":397,"text":832},{"id":844,"depth":400,"text":844,"children":1045},[1046],{"id":849,"depth":397,"text":852},{"id":864,"depth":400,"text":864,"children":1048},[1049],{"id":869,"depth":397,"text":869},{"id":883,"depth":400,"text":883,"children":1051},[1052],{"id":888,"depth":397,"text":891},{"id":582,"depth":400,"text":582},{"id":667,"depth":400,"text":667},"content:topics:frontend:vue3-composition-api.md","topics/frontend/vue3-composition-api.md","topics/frontend/vue3-composition-api",{"_path":1059,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1060,"description":1061,"date":1062,"topic":5,"author":11,"tags":1063,"image":1069,"featured":683,"readingTime":1070,"body":1071,"_type":408,"_id":2333,"_source":410,"_file":2334,"_stem":2335,"_extension":413},"/topics/frontend/rspack-performance-practice","Rspack 构建性能实战","从 Rspack 的架构设计与编译管线出发，系统对比 Webpack/Vite 并给出 Rspack 在大型项目中的迁移路径、性能调优策略与生产级可观测方案。","2026-01-20",[1064,1065,1066,1067,1068],"Rspack","构建工具","性能优化","Webpack","前端工程化","/images/topics/rspack.jpg",25,{"type":24,"children":1072,"toc":2300},[1073,1078,1090,1095,1113,1125,1130,1153,1157,1163,1168,1174,1207,1213,1231,1234,1240,1246,1251,1264,1270,1275,1288,1294,1312,1317,1320,1326,1331,1472,1477,1500,1503,1509,1515,1520,1525,1593,1606,1612,1620,1633,1641,1654,1662,1675,1681,1699,1702,1708,1714,1719,1732,1738,1743,1754,1760,1765,1783,1788,1816,1819,1825,1830,1841,1846,1864,1869,1887,1890,1896,1901,1919,1924,1947,1952,1970,1973,1979,1985,1990,2008,2014,2032,2038,2056,2059,2065,2180,2185,2203,2206,2212,2270,2273,2277,2282],{"type":27,"tag":40,"props":1074,"children":1076},{"id":1075},"rspack-构建性能实战",[1077],{"type":32,"value":1060},{"type":27,"tag":28,"props":1079,"children":1080},{},[1081,1083,1088],{"type":32,"value":1082},"Rspack 不是\"又一个构建工具\"，而是字节跳动在处理",{"type":27,"tag":591,"props":1084,"children":1085},{},[1086],{"type":32,"value":1087},"超大规模前端项目",{"type":32,"value":1089},"时，对 Webpack 生态的 Rust 重写与工程化沉淀。",{"type":27,"tag":28,"props":1091,"children":1092},{},[1093],{"type":32,"value":1094},"它要解决的核心问题是：",{"type":27,"tag":57,"props":1096,"children":1097},{},[1098,1103,1108],{"type":27,"tag":61,"props":1099,"children":1100},{},[1101],{"type":32,"value":1102},"Webpack 的构建速度在大型 monorepo 下（10k+ 模块）已成为开发体验瓶颈",{"type":27,"tag":61,"props":1104,"children":1105},{},[1106],{"type":32,"value":1107},"但你又无法抛弃 Webpack 的插件生态与配置范式",{"type":27,"tag":61,"props":1109,"children":1110},{},[1111],{"type":32,"value":1112},"Vite 虽然快，但在某些场景（大型遗留项目、特定插件依赖）迁移成本高",{"type":27,"tag":28,"props":1114,"children":1115},{},[1116,1118,1123],{"type":32,"value":1117},"Rspack 的定位是：",{"type":27,"tag":591,"props":1119,"children":1120},{},[1121],{"type":32,"value":1122},"Webpack 兼容 API + Rust 性能 + 生产级稳定性",{"type":32,"value":1124},"。",{"type":27,"tag":28,"props":1126,"children":1127},{},[1128],{"type":32,"value":1129},"这篇文章不讲\"Hello World\"，而是按\"你要在生产上稳定用 Rspack\"的标准，给出：",{"type":27,"tag":57,"props":1131,"children":1132},{},[1133,1138,1143,1148],{"type":27,"tag":61,"props":1134,"children":1135},{},[1136],{"type":32,"value":1137},"性能收益的真实量化方法",{"type":27,"tag":61,"props":1139,"children":1140},{},[1141],{"type":32,"value":1142},"迁移路径与兼容性边界",{"type":27,"tag":61,"props":1144,"children":1145},{},[1146],{"type":32,"value":1147},"优化策略（缓存、并行、Tree Shaking）",{"type":27,"tag":61,"props":1149,"children":1150},{},[1151],{"type":32,"value":1152},"监控与排障（为什么变慢、为什么产物变大）",{"type":27,"tag":1154,"props":1155,"children":1156},"hr",{},[],{"type":27,"tag":40,"props":1158,"children":1160},{"id":1159},"_1-先回答什么项目值得迁移-rspack",[1161],{"type":32,"value":1162},"1. 先回答：什么项目值得迁移 Rspack？",{"type":27,"tag":28,"props":1164,"children":1165},{},[1166],{"type":32,"value":1167},"不是所有项目都需要 Rspack。",{"type":27,"tag":447,"props":1169,"children":1171},{"id":1170},"_11-高收益场景",[1172],{"type":32,"value":1173},"1.1 高收益场景",{"type":27,"tag":57,"props":1175,"children":1176},{},[1177,1187,1197],{"type":27,"tag":61,"props":1178,"children":1179},{},[1180,1185],{"type":27,"tag":591,"props":1181,"children":1182},{},[1183],{"type":32,"value":1184},"大型 monorepo",{"type":32,"value":1186},"（5k+ 模块，构建时间 > 2 分钟）",{"type":27,"tag":61,"props":1188,"children":1189},{},[1190,1195],{"type":27,"tag":591,"props":1191,"children":1192},{},[1193],{"type":32,"value":1194},"频繁开发迭代",{"type":32,"value":1196},"（HMR 延迟影响体验）",{"type":27,"tag":61,"props":1198,"children":1199},{},[1200,1205],{"type":27,"tag":591,"props":1201,"children":1202},{},[1203],{"type":32,"value":1204},"CI 构建成本高",{"type":32,"value":1206},"（每次 PR 构建超 10 分钟）",{"type":27,"tag":447,"props":1208,"children":1210},{"id":1209},"_12-收益不明显的场景",[1211],{"type":32,"value":1212},"1.2 收益不明显的场景",{"type":27,"tag":57,"props":1214,"children":1215},{},[1216,1221,1226],{"type":27,"tag":61,"props":1217,"children":1218},{},[1219],{"type":32,"value":1220},"小型项目（\u003C 1k 模块）",{"type":27,"tag":61,"props":1222,"children":1223},{},[1224],{"type":32,"value":1225},"已经用 Vite 且体验良好",{"type":27,"tag":61,"props":1227,"children":1228},{},[1229],{"type":32,"value":1230},"高度定制的 Webpack 插件（迁移成本 > 性能收益）",{"type":27,"tag":1154,"props":1232,"children":1233},{},[],{"type":27,"tag":40,"props":1235,"children":1237},{"id":1236},"_2-rspack-的架构为什么能快",[1238],{"type":32,"value":1239},"2. Rspack 的架构：为什么能快？",{"type":27,"tag":447,"props":1241,"children":1243},{"id":1242},"_21-rust-并行编译",[1244],{"type":32,"value":1245},"2.1 Rust 并行编译",{"type":27,"tag":28,"props":1247,"children":1248},{},[1249],{"type":32,"value":1250},"Webpack 是单线程 JavaScript，Rspack 是多线程 Rust。",{"type":27,"tag":57,"props":1252,"children":1253},{},[1254,1259],{"type":27,"tag":61,"props":1255,"children":1256},{},[1257],{"type":32,"value":1258},"模块解析、编译、优化可并行",{"type":27,"tag":61,"props":1260,"children":1261},{},[1262],{"type":32,"value":1263},"I/O 密集型任务（读文件、写产物）异步化",{"type":27,"tag":447,"props":1265,"children":1267},{"id":1266},"_22-更激进的缓存策略",[1268],{"type":32,"value":1269},"2.2 更激进的缓存策略",{"type":27,"tag":28,"props":1271,"children":1272},{},[1273],{"type":32,"value":1274},"Rspack 内置持久化缓存：",{"type":27,"tag":57,"props":1276,"children":1277},{},[1278,1283],{"type":27,"tag":61,"props":1279,"children":1280},{},[1281],{"type":32,"value":1282},"模块级别缓存（类似 Webpack 5 的 cache.type: 'filesystem'）",{"type":27,"tag":61,"props":1284,"children":1285},{},[1286],{"type":32,"value":1287},"但实现更激进：对未变化模块跳过编译",{"type":27,"tag":447,"props":1289,"children":1291},{"id":1290},"_23-内置常用功能减少插件开销",[1292],{"type":32,"value":1293},"2.3 内置常用功能（减少插件开销）",{"type":27,"tag":57,"props":1295,"children":1296},{},[1297,1302,1307],{"type":27,"tag":61,"props":1298,"children":1299},{},[1300],{"type":32,"value":1301},"SWC 替代 Babel（内置 TS/JSX/装饰器）",{"type":27,"tag":61,"props":1303,"children":1304},{},[1305],{"type":32,"value":1306},"CSS Modules、PostCSS 内置",{"type":27,"tag":61,"props":1308,"children":1309},{},[1310],{"type":32,"value":1311},"Tree Shaking 内置",{"type":27,"tag":28,"props":1313,"children":1314},{},[1315],{"type":32,"value":1316},"这让 Rspack 在相同功能下比 Webpack + 插件链路更快。",{"type":27,"tag":1154,"props":1318,"children":1319},{},[],{"type":27,"tag":40,"props":1321,"children":1323},{"id":1322},"_3-性能对比真实场景的量化",[1324],{"type":32,"value":1325},"3. 性能对比：真实场景的量化",{"type":27,"tag":28,"props":1327,"children":1328},{},[1329],{"type":32,"value":1330},"我们用一个典型中型项目（3k 模块，React + TS + CSS Modules）做对比：",{"type":27,"tag":1332,"props":1333,"children":1334},"table",{},[1335,1363],{"type":27,"tag":1336,"props":1337,"children":1338},"thead",{},[1339],{"type":27,"tag":1340,"props":1341,"children":1342},"tr",{},[1343,1349,1354,1358],{"type":27,"tag":1344,"props":1345,"children":1346},"th",{},[1347],{"type":32,"value":1348},"指标",{"type":27,"tag":1344,"props":1350,"children":1351},{},[1352],{"type":32,"value":1353},"Webpack 5",{"type":27,"tag":1344,"props":1355,"children":1356},{},[1357],{"type":32,"value":1064},{"type":27,"tag":1344,"props":1359,"children":1360},{},[1361],{"type":32,"value":1362},"提升",{"type":27,"tag":1364,"props":1365,"children":1366},"tbody",{},[1367,1394,1420,1446],{"type":27,"tag":1340,"props":1368,"children":1369},{},[1370,1376,1381,1386],{"type":27,"tag":1371,"props":1372,"children":1373},"td",{},[1374],{"type":32,"value":1375},"冷启动",{"type":27,"tag":1371,"props":1377,"children":1378},{},[1379],{"type":32,"value":1380},"42s",{"type":27,"tag":1371,"props":1382,"children":1383},{},[1384],{"type":32,"value":1385},"8s",{"type":27,"tag":1371,"props":1387,"children":1388},{},[1389],{"type":27,"tag":591,"props":1390,"children":1391},{},[1392],{"type":32,"value":1393},"5.2x",{"type":27,"tag":1340,"props":1395,"children":1396},{},[1397,1402,1407,1412],{"type":27,"tag":1371,"props":1398,"children":1399},{},[1400],{"type":32,"value":1401},"HMR（热更新）",{"type":27,"tag":1371,"props":1403,"children":1404},{},[1405],{"type":32,"value":1406},"1.2s",{"type":27,"tag":1371,"props":1408,"children":1409},{},[1410],{"type":32,"value":1411},"0.15s",{"type":27,"tag":1371,"props":1413,"children":1414},{},[1415],{"type":27,"tag":591,"props":1416,"children":1417},{},[1418],{"type":32,"value":1419},"8x",{"type":27,"tag":1340,"props":1421,"children":1422},{},[1423,1428,1433,1438],{"type":27,"tag":1371,"props":1424,"children":1425},{},[1426],{"type":32,"value":1427},"生产构建",{"type":27,"tag":1371,"props":1429,"children":1430},{},[1431],{"type":32,"value":1432},"125s",{"type":27,"tag":1371,"props":1434,"children":1435},{},[1436],{"type":32,"value":1437},"28s",{"type":27,"tag":1371,"props":1439,"children":1440},{},[1441],{"type":27,"tag":591,"props":1442,"children":1443},{},[1444],{"type":32,"value":1445},"4.5x",{"type":27,"tag":1340,"props":1447,"children":1448},{},[1449,1454,1459,1464],{"type":27,"tag":1371,"props":1450,"children":1451},{},[1452],{"type":32,"value":1453},"内存峰值",{"type":27,"tag":1371,"props":1455,"children":1456},{},[1457],{"type":32,"value":1458},"1.8GB",{"type":27,"tag":1371,"props":1460,"children":1461},{},[1462],{"type":32,"value":1463},"0.9GB",{"type":27,"tag":1371,"props":1465,"children":1466},{},[1467],{"type":27,"tag":591,"props":1468,"children":1469},{},[1470],{"type":32,"value":1471},"2x",{"type":27,"tag":28,"props":1473,"children":1474},{},[1475],{"type":32,"value":1476},"关键收益：",{"type":27,"tag":57,"props":1478,"children":1479},{},[1480,1490],{"type":27,"tag":61,"props":1481,"children":1482},{},[1483,1488],{"type":27,"tag":591,"props":1484,"children":1485},{},[1486],{"type":32,"value":1487},"开发体验质变",{"type":32,"value":1489},"（HMR \u003C 200ms）",{"type":27,"tag":61,"props":1491,"children":1492},{},[1493,1498],{"type":27,"tag":591,"props":1494,"children":1495},{},[1496],{"type":32,"value":1497},"CI 成本减半",{"type":32,"value":1499},"（构建时间直接影响 Runner 费用）",{"type":27,"tag":1154,"props":1501,"children":1502},{},[],{"type":27,"tag":40,"props":1504,"children":1506},{"id":1505},"_4-迁移路径从-webpack-到-rspack",[1507],{"type":32,"value":1508},"4. 迁移路径：从 Webpack 到 Rspack",{"type":27,"tag":447,"props":1510,"children":1512},{"id":1511},"_41-最小迁移保守策略",[1513],{"type":32,"value":1514},"4.1 最小迁移（保守策略）",{"type":27,"tag":28,"props":1516,"children":1517},{},[1518],{"type":32,"value":1519},"目标：用最小改动换取性能收益。",{"type":27,"tag":28,"props":1521,"children":1522},{},[1523],{"type":32,"value":1524},"步骤：",{"type":27,"tag":1526,"props":1527,"children":1528},"ol",{},[1529,1548,1567,1588],{"type":27,"tag":61,"props":1530,"children":1531},{},[1532,1534,1540,1542],{"type":32,"value":1533},"安装 ",{"type":27,"tag":123,"props":1535,"children":1537},{"className":1536},[],[1538],{"type":32,"value":1539},"@rspack/cli",{"type":32,"value":1541}," 与 ",{"type":27,"tag":123,"props":1543,"children":1545},{"className":1544},[],[1546],{"type":32,"value":1547},"@rspack/core",{"type":27,"tag":61,"props":1549,"children":1550},{},[1551,1553,1559,1561],{"type":32,"value":1552},"把 ",{"type":27,"tag":123,"props":1554,"children":1556},{"className":1555},[],[1557],{"type":32,"value":1558},"webpack.config.js",{"type":32,"value":1560}," 改为 ",{"type":27,"tag":123,"props":1562,"children":1564},{"className":1563},[],[1565],{"type":32,"value":1566},"rspack.config.js",{"type":27,"tag":61,"props":1568,"children":1569},{},[1570,1572,1578,1580,1586],{"type":32,"value":1571},"替换构建命令（",{"type":27,"tag":123,"props":1573,"children":1575},{"className":1574},[],[1576],{"type":32,"value":1577},"rspack build",{"type":32,"value":1579}," / ",{"type":27,"tag":123,"props":1581,"children":1583},{"className":1582},[],[1584],{"type":32,"value":1585},"rspack dev",{"type":32,"value":1587},"）",{"type":27,"tag":61,"props":1589,"children":1590},{},[1591],{"type":32,"value":1592},"运行并修复兼容性问题",{"type":27,"tag":28,"props":1594,"children":1595},{},[1596,1598,1604],{"type":32,"value":1597},"预计迁移成本：1",{"type":27,"tag":1599,"props":1600,"children":1601},"del",{},[1602],{"type":32,"value":1603},"2 天（小型项目）/ 1",{"type":32,"value":1605},"2 周（大型项目）",{"type":27,"tag":447,"props":1607,"children":1609},{"id":1608},"_42-兼容性边界哪些需要调整",[1610],{"type":32,"value":1611},"4.2 兼容性边界：哪些需要调整",{"type":27,"tag":28,"props":1613,"children":1614},{},[1615],{"type":27,"tag":591,"props":1616,"children":1617},{},[1618],{"type":32,"value":1619},"插件兼容",{"type":27,"tag":57,"props":1621,"children":1622},{},[1623,1628],{"type":27,"tag":61,"props":1624,"children":1625},{},[1626],{"type":32,"value":1627},"Rspack 支持大部分 Webpack 插件（API 兼容）",{"type":27,"tag":61,"props":1629,"children":1630},{},[1631],{"type":32,"value":1632},"但少数复杂插件（例如深度依赖 Webpack 内部 API）需要适配",{"type":27,"tag":28,"props":1634,"children":1635},{},[1636],{"type":27,"tag":591,"props":1637,"children":1638},{},[1639],{"type":32,"value":1640},"Loader 兼容",{"type":27,"tag":57,"props":1642,"children":1643},{},[1644,1649],{"type":27,"tag":61,"props":1645,"children":1646},{},[1647],{"type":32,"value":1648},"常用 loader（babel-loader、css-loader、postcss-loader）兼容",{"type":27,"tag":61,"props":1650,"children":1651},{},[1652],{"type":32,"value":1653},"部分自定义 loader 需要测试",{"type":27,"tag":28,"props":1655,"children":1656},{},[1657],{"type":27,"tag":591,"props":1658,"children":1659},{},[1660],{"type":32,"value":1661},"配置差异",{"type":27,"tag":57,"props":1663,"children":1664},{},[1665,1670],{"type":27,"tag":61,"props":1666,"children":1667},{},[1668],{"type":32,"value":1669},"resolve、output、optimization 等配置与 Webpack 高度一致",{"type":27,"tag":61,"props":1671,"children":1672},{},[1673],{"type":32,"value":1674},"少数高级配置需要查文档",{"type":27,"tag":447,"props":1676,"children":1678},{"id":1677},"_43-推荐的迁移节奏",[1679],{"type":32,"value":1680},"4.3 推荐的迁移节奏",{"type":27,"tag":57,"props":1682,"children":1683},{},[1684,1689,1694],{"type":27,"tag":61,"props":1685,"children":1686},{},[1687],{"type":32,"value":1688},"Week 1：本地开发环境先行",{"type":27,"tag":61,"props":1690,"children":1691},{},[1692],{"type":32,"value":1693},"Week 2：CI 构建切换（并保留 Webpack 作为 fallback）",{"type":27,"tag":61,"props":1695,"children":1696},{},[1697],{"type":32,"value":1698},"Week 3~4：生产构建切换并观测",{"type":27,"tag":1154,"props":1700,"children":1701},{},[],{"type":27,"tag":40,"props":1703,"children":1705},{"id":1704},"_5-性能调优让-rspack-更快",[1706],{"type":32,"value":1707},"5. 性能调优：让 Rspack 更快",{"type":27,"tag":447,"props":1709,"children":1711},{"id":1710},"_51-缓存策略",[1712],{"type":32,"value":1713},"5.1 缓存策略",{"type":27,"tag":28,"props":1715,"children":1716},{},[1717],{"type":32,"value":1718},"默认缓存已经很激进，但你可以：",{"type":27,"tag":57,"props":1720,"children":1721},{},[1722,1727],{"type":27,"tag":61,"props":1723,"children":1724},{},[1725],{"type":32,"value":1726},"显式配置缓存目录（例如挂载 SSD）",{"type":27,"tag":61,"props":1728,"children":1729},{},[1730],{"type":32,"value":1731},"在 CI 上持久化缓存（例如用 actions/cache）",{"type":27,"tag":447,"props":1733,"children":1735},{"id":1734},"_52-并行度调优",[1736],{"type":32,"value":1737},"5.2 并行度调优",{"type":27,"tag":28,"props":1739,"children":1740},{},[1741],{"type":32,"value":1742},"Rspack 默认会用所有 CPU 核心，但在容器环境（例如 CI）可能需要限制：",{"type":27,"tag":115,"props":1744,"children":1749},{"className":1745,"code":1747,"language":1748,"meta":7},[1746],"language-js","module.exports = {\n  experiments: {\n    rspackFuture: {\n      disableTransformByDefault: true, // 减少不必要转换\n    },\n  },\n}\n","js",[1750],{"type":27,"tag":123,"props":1751,"children":1752},{"__ignoreMap":7},[1753],{"type":32,"value":1747},{"type":27,"tag":447,"props":1755,"children":1757},{"id":1756},"_53-tree-shaking-与-dead-code-elimination",[1758],{"type":32,"value":1759},"5.3 Tree Shaking 与 Dead Code Elimination",{"type":27,"tag":28,"props":1761,"children":1762},{},[1763],{"type":32,"value":1764},"Rspack 内置 Tree Shaking，但效果取决于：",{"type":27,"tag":57,"props":1766,"children":1767},{},[1768,1773,1778],{"type":27,"tag":61,"props":1769,"children":1770},{},[1771],{"type":32,"value":1772},"是否使用 ESM（而非 CommonJS）",{"type":27,"tag":61,"props":1774,"children":1775},{},[1776],{"type":32,"value":1777},"副作用标记（sideEffects: false）",{"type":27,"tag":61,"props":1779,"children":1780},{},[1781],{"type":32,"value":1782},"动态 import 的拆分策略",{"type":27,"tag":28,"props":1784,"children":1785},{},[1786],{"type":32,"value":1787},"建议：",{"type":27,"tag":57,"props":1789,"children":1790},{},[1791,1804],{"type":27,"tag":61,"props":1792,"children":1793},{},[1794,1796,1802],{"type":32,"value":1795},"对第三方库检查 ",{"type":27,"tag":123,"props":1797,"children":1799},{"className":1798},[],[1800],{"type":32,"value":1801},"sideEffects",{"type":32,"value":1803}," 配置",{"type":27,"tag":61,"props":1805,"children":1806},{},[1807,1809,1815],{"type":32,"value":1808},"避免\"全量引入后 tree shake\"（例如 ",{"type":27,"tag":123,"props":1810,"children":1812},{"className":1811},[],[1813],{"type":32,"value":1814},"import * from 'lodash'",{"type":32,"value":1587},{"type":27,"tag":1154,"props":1817,"children":1818},{},[],{"type":27,"tag":40,"props":1820,"children":1822},{"id":1821},"_6-产物分析与优化",[1823],{"type":32,"value":1824},"6. 产物分析与优化",{"type":27,"tag":28,"props":1826,"children":1827},{},[1828],{"type":32,"value":1829},"Rspack 提供内置分析工具：",{"type":27,"tag":115,"props":1831,"children":1836},{"className":1832,"code":1834,"language":1835,"meta":7},[1833],"language-bash","rspack build --analyze\n","bash",[1837],{"type":27,"tag":123,"props":1838,"children":1839},{"__ignoreMap":7},[1840],{"type":32,"value":1834},{"type":27,"tag":28,"props":1842,"children":1843},{},[1844],{"type":32,"value":1845},"关键指标：",{"type":27,"tag":57,"props":1847,"children":1848},{},[1849,1854,1859],{"type":27,"tag":61,"props":1850,"children":1851},{},[1852],{"type":32,"value":1853},"各 chunk 体积分布",{"type":27,"tag":61,"props":1855,"children":1856},{},[1857],{"type":32,"value":1858},"重复依赖（例如多个版本的 lodash）",{"type":27,"tag":61,"props":1860,"children":1861},{},[1862],{"type":32,"value":1863},"未被 tree shake 的代码",{"type":27,"tag":28,"props":1865,"children":1866},{},[1867],{"type":32,"value":1868},"优化策略：",{"type":27,"tag":57,"props":1870,"children":1871},{},[1872,1877,1882],{"type":27,"tag":61,"props":1873,"children":1874},{},[1875],{"type":32,"value":1876},"拆分 vendor chunk（按更新频率）",{"type":27,"tag":61,"props":1878,"children":1879},{},[1880],{"type":32,"value":1881},"对大型库按需引入（例如 antd/lodash-es）",{"type":27,"tag":61,"props":1883,"children":1884},{},[1885],{"type":32,"value":1886},"检查动态 import 的粒度",{"type":27,"tag":1154,"props":1888,"children":1889},{},[],{"type":27,"tag":40,"props":1891,"children":1893},{"id":1892},"_7-生产可观测性让构建可量化",[1894],{"type":32,"value":1895},"7. 生产可观测性：让构建可量化",{"type":27,"tag":28,"props":1897,"children":1898},{},[1899],{"type":32,"value":1900},"在 CI/CD 里，你需要能回答：",{"type":27,"tag":57,"props":1902,"children":1903},{},[1904,1909,1914],{"type":27,"tag":61,"props":1905,"children":1906},{},[1907],{"type":32,"value":1908},"这次构建为什么变慢？",{"type":27,"tag":61,"props":1910,"children":1911},{},[1912],{"type":32,"value":1913},"产物为什么变大？",{"type":27,"tag":61,"props":1915,"children":1916},{},[1917],{"type":32,"value":1918},"哪个模块耗时最多？",{"type":27,"tag":28,"props":1920,"children":1921},{},[1922],{"type":32,"value":1923},"建议在 CI 里记录：",{"type":27,"tag":57,"props":1925,"children":1926},{},[1927,1932,1937,1942],{"type":27,"tag":61,"props":1928,"children":1929},{},[1930],{"type":32,"value":1931},"构建总耗时",{"type":27,"tag":61,"props":1933,"children":1934},{},[1935],{"type":32,"value":1936},"各阶段耗时（resolve、compile、optimize、emit）",{"type":27,"tag":61,"props":1938,"children":1939},{},[1940],{"type":32,"value":1941},"产物体积（按 chunk）",{"type":27,"tag":61,"props":1943,"children":1944},{},[1945],{"type":32,"value":1946},"缓存命中率",{"type":27,"tag":28,"props":1948,"children":1949},{},[1950],{"type":32,"value":1951},"落地方式：",{"type":27,"tag":57,"props":1953,"children":1954},{},[1955,1960,1965],{"type":27,"tag":61,"props":1956,"children":1957},{},[1958],{"type":32,"value":1959},"用 Rspack 的 stats 输出",{"type":27,"tag":61,"props":1961,"children":1962},{},[1963],{"type":32,"value":1964},"在 CI 日志里保留关键指标",{"type":27,"tag":61,"props":1966,"children":1967},{},[1968],{"type":32,"value":1969},"对产物体积做 baseline 对比（变化 > 5% 报警）",{"type":27,"tag":1154,"props":1971,"children":1972},{},[],{"type":27,"tag":40,"props":1974,"children":1976},{"id":1975},"_8-常见问题排查",[1977],{"type":32,"value":1978},"8. 常见问题排查",{"type":27,"tag":447,"props":1980,"children":1982},{"id":1981},"_81-迁移后变慢了",[1983],{"type":32,"value":1984},"8.1 \"迁移后变慢了\"",{"type":27,"tag":28,"props":1986,"children":1987},{},[1988],{"type":32,"value":1989},"排查顺序：",{"type":27,"tag":57,"props":1991,"children":1992},{},[1993,1998,2003],{"type":27,"tag":61,"props":1994,"children":1995},{},[1996],{"type":32,"value":1997},"缓存是否生效（首次构建慢正常）",{"type":27,"tag":61,"props":1999,"children":2000},{},[2001],{"type":32,"value":2002},"是否有 loader 拖慢（例如未优化的自定义 loader）",{"type":27,"tag":61,"props":2004,"children":2005},{},[2006],{"type":32,"value":2007},"并行度是否受限（例如 CI 限制 CPU）",{"type":27,"tag":447,"props":2009,"children":2011},{"id":2010},"_82-产物体积变大了",[2012],{"type":32,"value":2013},"8.2 \"产物体积变大了\"",{"type":27,"tag":57,"props":2015,"children":2016},{},[2017,2022,2027],{"type":27,"tag":61,"props":2018,"children":2019},{},[2020],{"type":32,"value":2021},"检查 Tree Shaking 是否生效",{"type":27,"tag":61,"props":2023,"children":2024},{},[2025],{"type":32,"value":2026},"检查是否引入了更多 polyfill",{"type":27,"tag":61,"props":2028,"children":2029},{},[2030],{"type":32,"value":2031},"对比 chunk 分布（用 analyze）",{"type":27,"tag":447,"props":2033,"children":2035},{"id":2034},"_83-某些模块编译失败",[2036],{"type":32,"value":2037},"8.3 \"某些模块编译失败\"",{"type":27,"tag":57,"props":2039,"children":2040},{},[2041,2046,2051],{"type":27,"tag":61,"props":2042,"children":2043},{},[2044],{"type":32,"value":2045},"检查是否依赖 Webpack 特定 API",{"type":27,"tag":61,"props":2047,"children":2048},{},[2049],{"type":32,"value":2050},"查看 Rspack 官方兼容性列表",{"type":27,"tag":61,"props":2052,"children":2053},{},[2054],{"type":32,"value":2055},"在 GitHub Issues 搜索类似问题",{"type":27,"tag":1154,"props":2057,"children":2058},{},[],{"type":27,"tag":40,"props":2060,"children":2062},{"id":2061},"_9-rspack-vs-vite什么时候选哪个",[2063],{"type":32,"value":2064},"9. Rspack vs Vite：什么时候选哪个？",{"type":27,"tag":1332,"props":2066,"children":2067},{},[2068,2088],{"type":27,"tag":1336,"props":2069,"children":2070},{},[2071],{"type":27,"tag":1340,"props":2072,"children":2073},{},[2074,2079,2083],{"type":27,"tag":1344,"props":2075,"children":2076},{},[2077],{"type":32,"value":2078},"维度",{"type":27,"tag":1344,"props":2080,"children":2081},{},[2082],{"type":32,"value":1064},{"type":27,"tag":1344,"props":2084,"children":2085},{},[2086],{"type":32,"value":2087},"Vite",{"type":27,"tag":1364,"props":2089,"children":2090},{},[2091,2109,2126,2144,2162],{"type":27,"tag":1340,"props":2092,"children":2093},{},[2094,2099,2104],{"type":27,"tag":1371,"props":2095,"children":2096},{},[2097],{"type":32,"value":2098},"开发速度",{"type":27,"tag":1371,"props":2100,"children":2101},{},[2102],{"type":32,"value":2103},"极快（Rust 编译）",{"type":27,"tag":1371,"props":2105,"children":2106},{},[2107],{"type":32,"value":2108},"极快（ESM 直连）",{"type":27,"tag":1340,"props":2110,"children":2111},{},[2112,2116,2121],{"type":27,"tag":1371,"props":2113,"children":2114},{},[2115],{"type":32,"value":1427},{"type":27,"tag":1371,"props":2117,"children":2118},{},[2119],{"type":32,"value":2120},"快（全量编译优化）",{"type":27,"tag":1371,"props":2122,"children":2123},{},[2124],{"type":32,"value":2125},"快（Rollup）",{"type":27,"tag":1340,"props":2127,"children":2128},{},[2129,2134,2139],{"type":27,"tag":1371,"props":2130,"children":2131},{},[2132],{"type":32,"value":2133},"Webpack 兼容",{"type":27,"tag":1371,"props":2135,"children":2136},{},[2137],{"type":32,"value":2138},"高",{"type":27,"tag":1371,"props":2140,"children":2141},{},[2142],{"type":32,"value":2143},"低",{"type":27,"tag":1340,"props":2145,"children":2146},{},[2147,2152,2157],{"type":27,"tag":1371,"props":2148,"children":2149},{},[2150],{"type":32,"value":2151},"插件生态",{"type":27,"tag":1371,"props":2153,"children":2154},{},[2155],{"type":32,"value":2156},"Webpack 生态",{"type":27,"tag":1371,"props":2158,"children":2159},{},[2160],{"type":32,"value":2161},"Rollup/Vite 生态",{"type":27,"tag":1340,"props":2163,"children":2164},{},[2165,2170,2175],{"type":27,"tag":1371,"props":2166,"children":2167},{},[2168],{"type":32,"value":2169},"适用项目",{"type":27,"tag":1371,"props":2171,"children":2172},{},[2173],{"type":32,"value":2174},"Webpack 迁移、大型 monorepo",{"type":27,"tag":1371,"props":2176,"children":2177},{},[2178],{"type":32,"value":2179},"新项目、中小型",{"type":27,"tag":28,"props":2181,"children":2182},{},[2183],{"type":32,"value":2184},"选择建议：",{"type":27,"tag":57,"props":2186,"children":2187},{},[2188,2193,2198],{"type":27,"tag":61,"props":2189,"children":2190},{},[2191],{"type":32,"value":2192},"新项目：优先 Vite",{"type":27,"tag":61,"props":2194,"children":2195},{},[2196],{"type":32,"value":2197},"Webpack 遗留项目：Rspack",{"type":27,"tag":61,"props":2199,"children":2200},{},[2201],{"type":32,"value":2202},"大型 monorepo + Webpack 依赖：Rspack",{"type":27,"tag":1154,"props":2204,"children":2205},{},[],{"type":27,"tag":40,"props":2207,"children":2209},{"id":2208},"_10-上线检查清单",[2210],{"type":32,"value":2211},"10. 上线检查清单",{"type":27,"tag":57,"props":2213,"children":2215},{"className":2214},[673],[2216,2225,2234,2243,2252,2261],{"type":27,"tag":61,"props":2217,"children":2219},{"className":2218},[678],[2220,2223],{"type":27,"tag":681,"props":2221,"children":2222},{"disabled":683,"type":684},[],{"type":32,"value":2224}," 本地开发环境已验证（HMR/热更新正常）",{"type":27,"tag":61,"props":2226,"children":2228},{"className":2227},[678],[2229,2232],{"type":27,"tag":681,"props":2230,"children":2231},{"disabled":683,"type":684},[],{"type":32,"value":2233}," CI 构建已切换并观测 3 天以上",{"type":27,"tag":61,"props":2235,"children":2237},{"className":2236},[678],[2238,2241],{"type":27,"tag":681,"props":2239,"children":2240},{"disabled":683,"type":684},[],{"type":32,"value":2242}," 产物体积对比无异常（baseline ± 5%）",{"type":27,"tag":61,"props":2244,"children":2246},{"className":2245},[678],[2247,2250],{"type":27,"tag":681,"props":2248,"children":2249},{"disabled":683,"type":684},[],{"type":32,"value":2251}," 关键页面功能回归测试通过",{"type":27,"tag":61,"props":2253,"children":2255},{"className":2254},[678],[2256,2259],{"type":27,"tag":681,"props":2257,"children":2258},{"disabled":683,"type":684},[],{"type":32,"value":2260}," 有构建耗时与缓存命中率监控",{"type":27,"tag":61,"props":2262,"children":2264},{"className":2263},[678],[2265,2268],{"type":27,"tag":681,"props":2266,"children":2267},{"disabled":683,"type":684},[],{"type":32,"value":2269}," 有回滚方案（保留 Webpack 配置）",{"type":27,"tag":1154,"props":2271,"children":2272},{},[],{"type":27,"tag":40,"props":2274,"children":2275},{"id":352},[2276],{"type":32,"value":352},{"type":27,"tag":28,"props":2278,"children":2279},{},[2280],{"type":32,"value":2281},"Rspack 的核心价值是：",{"type":27,"tag":57,"props":2283,"children":2284},{},[2285,2290,2295],{"type":27,"tag":61,"props":2286,"children":2287},{},[2288],{"type":32,"value":2289},"在 Webpack 生态下获得接近 Vite 的速度",{"type":27,"tag":61,"props":2291,"children":2292},{},[2293],{"type":32,"value":2294},"对大型项目构建成本与开发体验的显著改善",{"type":27,"tag":61,"props":2296,"children":2297},{},[2298],{"type":32,"value":2299},"生产级稳定性（字节跳动内部大规模验证）",{"title":7,"searchDepth":397,"depth":397,"links":2301},[2302,2303,2307,2312,2313,2318,2323,2324,2325,2330,2331,2332],{"id":1075,"depth":400,"text":1060},{"id":1159,"depth":400,"text":1162,"children":2304},[2305,2306],{"id":1170,"depth":397,"text":1173},{"id":1209,"depth":397,"text":1212},{"id":1236,"depth":400,"text":1239,"children":2308},[2309,2310,2311],{"id":1242,"depth":397,"text":1245},{"id":1266,"depth":397,"text":1269},{"id":1290,"depth":397,"text":1293},{"id":1322,"depth":400,"text":1325},{"id":1505,"depth":400,"text":1508,"children":2314},[2315,2316,2317],{"id":1511,"depth":397,"text":1514},{"id":1608,"depth":397,"text":1611},{"id":1677,"depth":397,"text":1680},{"id":1704,"depth":400,"text":1707,"children":2319},[2320,2321,2322],{"id":1710,"depth":397,"text":1713},{"id":1734,"depth":397,"text":1737},{"id":1756,"depth":397,"text":1759},{"id":1821,"depth":400,"text":1824},{"id":1892,"depth":400,"text":1895},{"id":1975,"depth":400,"text":1978,"children":2326},[2327,2328,2329],{"id":1981,"depth":397,"text":1984},{"id":2010,"depth":397,"text":2013},{"id":2034,"depth":397,"text":2037},{"id":2061,"depth":400,"text":2064},{"id":2208,"depth":400,"text":2211},{"id":352,"depth":400,"text":352},"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":2337,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":2338,"_type":408,"_id":409,"_source":410,"_file":411,"_stem":412,"_extension":413},[13,14,15,16,17],{"type":24,"children":2339,"toc":2629},[2340,2344,2348,2352,2356,2360,2379,2383,2398,2402,2406,2414,2430,2434,2438,2442,2457,2465,2469,2473,2477,2500,2504,2508,2512,2531,2535,2539,2543,2547,2562,2566,2570,2593,2597,2601,2605],{"type":27,"tag":28,"props":2341,"children":2342},{},[2343],{"type":32,"value":33},{"type":27,"tag":28,"props":2345,"children":2346},{},[2347],{"type":32,"value":38},{"type":27,"tag":40,"props":2349,"children":2350},{"id":42},[2351],{"type":32,"value":45},{"type":27,"tag":28,"props":2353,"children":2354},{},[2355],{"type":32,"value":50},{"type":27,"tag":28,"props":2357,"children":2358},{},[2359],{"type":32,"value":55},{"type":27,"tag":57,"props":2361,"children":2362},{},[2363,2367,2371,2375],{"type":27,"tag":61,"props":2364,"children":2365},{},[2366],{"type":32,"value":65},{"type":27,"tag":61,"props":2368,"children":2369},{},[2370],{"type":32,"value":70},{"type":27,"tag":61,"props":2372,"children":2373},{},[2374],{"type":32,"value":75},{"type":27,"tag":61,"props":2376,"children":2377},{},[2378],{"type":32,"value":80},{"type":27,"tag":28,"props":2380,"children":2381},{},[2382],{"type":32,"value":85},{"type":27,"tag":57,"props":2384,"children":2385},{},[2386,2390,2394],{"type":27,"tag":61,"props":2387,"children":2388},{},[2389],{"type":32,"value":93},{"type":27,"tag":61,"props":2391,"children":2392},{},[2393],{"type":32,"value":98},{"type":27,"tag":61,"props":2395,"children":2396},{},[2397],{"type":32,"value":103},{"type":27,"tag":40,"props":2399,"children":2400},{"id":106},[2401],{"type":32,"value":106},{"type":27,"tag":28,"props":2403,"children":2404},{},[2405],{"type":32,"value":113},{"type":27,"tag":115,"props":2407,"children":2409},{"className":2408,"code":119,"language":120,"meta":7},[118],[2410],{"type":27,"tag":123,"props":2411,"children":2412},{"__ignoreMap":7},[2413],{"type":32,"value":119},{"type":27,"tag":28,"props":2415,"children":2416},{},[2417,2418,2423,2424,2429],{"type":32,"value":131},{"type":27,"tag":123,"props":2419,"children":2421},{"className":2420},[],[2422],{"type":32,"value":137},{"type":32,"value":139},{"type":27,"tag":123,"props":2425,"children":2427},{"className":2426},[],[2428],{"type":32,"value":145},{"type":32,"value":147},{"type":27,"tag":40,"props":2431,"children":2432},{"id":150},[2433],{"type":32,"value":150},{"type":27,"tag":28,"props":2435,"children":2436},{},[2437],{"type":32,"value":157},{"type":27,"tag":28,"props":2439,"children":2440},{},[2441],{"type":32,"value":162},{"type":27,"tag":57,"props":2443,"children":2444},{},[2445,2449,2453],{"type":27,"tag":61,"props":2446,"children":2447},{},[2448],{"type":32,"value":170},{"type":27,"tag":61,"props":2450,"children":2451},{},[2452],{"type":32,"value":175},{"type":27,"tag":61,"props":2454,"children":2455},{},[2456],{"type":32,"value":180},{"type":27,"tag":115,"props":2458,"children":2460},{"className":2459,"code":184,"language":120,"meta":7},[118],[2461],{"type":27,"tag":123,"props":2462,"children":2463},{"__ignoreMap":7},[2464],{"type":32,"value":184},{"type":27,"tag":40,"props":2466,"children":2467},{"id":192},[2468],{"type":32,"value":195},{"type":27,"tag":28,"props":2470,"children":2471},{},[2472],{"type":32,"value":200},{"type":27,"tag":28,"props":2474,"children":2475},{},[2476],{"type":32,"value":205},{"type":27,"tag":57,"props":2478,"children":2479},{},[2480,2484,2488,2492,2496],{"type":27,"tag":61,"props":2481,"children":2482},{},[2483],{"type":32,"value":213},{"type":27,"tag":61,"props":2485,"children":2486},{},[2487],{"type":32,"value":218},{"type":27,"tag":61,"props":2489,"children":2490},{},[2491],{"type":32,"value":223},{"type":27,"tag":61,"props":2493,"children":2494},{},[2495],{"type":32,"value":228},{"type":27,"tag":61,"props":2497,"children":2498},{},[2499],{"type":32,"value":233},{"type":27,"tag":28,"props":2501,"children":2502},{},[2503],{"type":32,"value":238},{"type":27,"tag":40,"props":2505,"children":2506},{"id":241},[2507],{"type":32,"value":244},{"type":27,"tag":28,"props":2509,"children":2510},{},[2511],{"type":32,"value":249},{"type":27,"tag":57,"props":2513,"children":2514},{},[2515,2519,2523,2527],{"type":27,"tag":61,"props":2516,"children":2517},{},[2518],{"type":32,"value":257},{"type":27,"tag":61,"props":2520,"children":2521},{},[2522],{"type":32,"value":262},{"type":27,"tag":61,"props":2524,"children":2525},{},[2526],{"type":32,"value":267},{"type":27,"tag":61,"props":2528,"children":2529},{},[2530],{"type":32,"value":272},{"type":27,"tag":28,"props":2532,"children":2533},{},[2534],{"type":32,"value":277},{"type":27,"tag":40,"props":2536,"children":2537},{"id":280},[2538],{"type":32,"value":283},{"type":27,"tag":28,"props":2540,"children":2541},{},[2542],{"type":32,"value":288},{"type":27,"tag":28,"props":2544,"children":2545},{},[2546],{"type":32,"value":293},{"type":27,"tag":57,"props":2548,"children":2549},{},[2550,2554,2558],{"type":27,"tag":61,"props":2551,"children":2552},{},[2553],{"type":32,"value":301},{"type":27,"tag":61,"props":2555,"children":2556},{},[2557],{"type":32,"value":306},{"type":27,"tag":61,"props":2559,"children":2560},{},[2561],{"type":32,"value":311},{"type":27,"tag":28,"props":2563,"children":2564},{},[2565],{"type":32,"value":316},{"type":27,"tag":40,"props":2567,"children":2568},{"id":319},[2569],{"type":32,"value":319},{"type":27,"tag":57,"props":2571,"children":2572},{},[2573,2577,2581,2585,2589],{"type":27,"tag":61,"props":2574,"children":2575},{},[2576],{"type":32,"value":329},{"type":27,"tag":61,"props":2578,"children":2579},{},[2580],{"type":32,"value":334},{"type":27,"tag":61,"props":2582,"children":2583},{},[2584],{"type":32,"value":339},{"type":27,"tag":61,"props":2586,"children":2587},{},[2588],{"type":32,"value":344},{"type":27,"tag":61,"props":2590,"children":2591},{},[2592],{"type":32,"value":349},{"type":27,"tag":40,"props":2594,"children":2595},{"id":352},[2596],{"type":32,"value":352},{"type":27,"tag":28,"props":2598,"children":2599},{},[2600],{"type":32,"value":359},{"type":27,"tag":28,"props":2602,"children":2603},{},[2604],{"type":32,"value":364},{"type":27,"tag":57,"props":2606,"children":2607},{},[2608,2615,2622],{"type":27,"tag":61,"props":2609,"children":2610},{},[2611],{"type":27,"tag":372,"props":2612,"children":2613},{"href":374},[2614],{"type":32,"value":377},{"type":27,"tag":61,"props":2616,"children":2617},{},[2618],{"type":27,"tag":372,"props":2619,"children":2620},{"href":383},[2621],{"type":32,"value":386},{"type":27,"tag":61,"props":2623,"children":2624},{},[2625],{"type":27,"tag":372,"props":2626,"children":2627},{"href":392},[2628],{"type":32,"value":395},{"title":7,"searchDepth":397,"depth":397,"links":2630},[2631,2632,2633,2634,2635,2636,2637,2638],{"id":42,"depth":400,"text":45},{"id":106,"depth":400,"text":106},{"id":150,"depth":400,"text":150},{"id":192,"depth":400,"text":195},{"id":241,"depth":400,"text":244},{"id":280,"depth":400,"text":283},{"id":319,"depth":400,"text":319},{"id":352,"depth":400,"text":352},1777334672953]