[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Ftest":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Ftest":1073,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Ftest":1095},{"source":4,"connector":5,"page":6},"sqlite","better-sqlite3",{"path":7,"title":8,"description":9,"category":10,"tags":11,"date":14,"pinned":15,"draft":15,"body":16,"seo":1069,"stem":1070,"id":1071,"extension":1072},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Ftest","测试","React 组件测试推荐 React Testing Library（RTL）：从用户可见行为断言，而非实现细节。运行器常用 Vitest 或 Jest。涵盖安装配置、查询优先级、异步测试、Mock 网络请求、最佳实践与常见坑。","技能小册",[12,13],"react","test","2025-09-26",false,{"type":17,"value":18,"toc":1055},"minimark",[19,22,45,49,52,56,95,111,229,253,256,334,349,352,355,392,421,424,487,494,497,528,758,765,768,792,799,802,817,820,851,854,949,952,975,978,981,1051],[20,21,8],"h1",{"id":8},[23,24,25,26,36,37,40,41,44],"p",{},"React 组件测试推荐 ",[27,28,29],"strong",{},[30,31,35],"a",{"href":32,"rel":33},"https:\u002F\u002Ftesting-library.com\u002Fdocs\u002Freact-testing-library\u002Fintro\u002F",[34],"nofollow","React Testing Library","（RTL）：从用户可见行为断言，而非实现细节。运行器常用 ",[27,38,39],{},"Vitest"," 或 ",[27,42,43],{},"Jest","。",[46,47,48],"h2",{"id":48},"核心概念",[23,50,51],{},"RTL 的设计哲学是**「测试软件的使用方式越接近用户的使用方式，测试就越可靠」**。它不提供直接访问组件内部 state\u002Fprops 的 API，而是强制你通过 DOM 查询（按角色、文本、标签）和模拟用户交互（点击、输入）来编写测试——这样测试更贴近真实使用场景，也更能容忍内部实现的重构。",[46,53,55],{"id":54},"安装vitest-示例","安装（Vitest 示例）",[57,58,63],"pre",{"className":59,"code":60,"language":61,"meta":62,"style":62},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","pnpm add -D vitest @testing-library\u002Freact @testing-library\u002Fjest-dom jsdom\n","bash","",[64,65,66],"code",{"__ignoreMap":62},[67,68,71,75,79,83,86,89,92],"span",{"class":69,"line":70},"line",1,[67,72,74],{"class":73},"sbgvK","pnpm",[67,76,78],{"class":77},"s_sjI"," add",[67,80,82],{"class":81},"stzsN"," -D",[67,84,85],{"class":77}," vitest",[67,87,88],{"class":77}," @testing-library\u002Freact",[67,90,91],{"class":77}," @testing-library\u002Fjest-dom",[67,93,94],{"class":77}," jsdom\n",[23,96,97,100,101,104,105,110],{},[64,98,99],{},"vite.config.ts"," 中配置 ",[64,102,103],{},"test.environment: \"jsdom\"","（见 ",[30,106,109],{"href":107,"rel":108},"https:\u002F\u002Fvitest.dev\u002Fconfig\u002F",[34],"Vitest 文档","）：",[57,112,116],{"className":113,"code":114,"language":115,"meta":62,"style":62},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F vite.config.ts\nexport default defineConfig({\n  test: {\n    environment: \"jsdom\",\n    setupFiles: \".\u002Fsrc\u002Ftest\u002Fsetup.ts\",\n    globals: true,\n  },\n});\n","ts",[64,117,118,124,146,159,180,197,211,217],{"__ignoreMap":62},[67,119,120],{"class":69,"line":70},[67,121,123],{"class":122},"sutJx","\u002F\u002F vite.config.ts\n",[67,125,127,131,134,138,142],{"class":69,"line":126},2,[67,128,130],{"class":129},"sVHd0","export",[67,132,133],{"class":129}," default",[67,135,137],{"class":136},"sGLFI"," defineConfig",[67,139,141],{"class":140},"su5hD","(",[67,143,145],{"class":144},"sP7_E","{\n",[67,147,149,153,156],{"class":69,"line":148},3,[67,150,152],{"class":151},"skxfh","  test",[67,154,155],{"class":144},":",[67,157,158],{"class":144}," {\n",[67,160,162,165,167,171,174,177],{"class":69,"line":161},4,[67,163,164],{"class":151},"    environment",[67,166,155],{"class":144},[67,168,170],{"class":169},"sjJ54"," \"",[67,172,173],{"class":77},"jsdom",[67,175,176],{"class":169},"\"",[67,178,179],{"class":144},",\n",[67,181,183,186,188,190,193,195],{"class":69,"line":182},5,[67,184,185],{"class":151},"    setupFiles",[67,187,155],{"class":144},[67,189,170],{"class":169},[67,191,192],{"class":77},".\u002Fsrc\u002Ftest\u002Fsetup.ts",[67,194,176],{"class":169},[67,196,179],{"class":144},[67,198,200,203,205,209],{"class":69,"line":199},6,[67,201,202],{"class":151},"    globals",[67,204,155],{"class":144},[67,206,208],{"class":207},"syTEX"," true",[67,210,179],{"class":144},[67,212,214],{"class":69,"line":213},7,[67,215,216],{"class":144},"  },\n",[67,218,220,223,226],{"class":69,"line":219},8,[67,221,222],{"class":144},"}",[67,224,225],{"class":140},")",[67,227,228],{"class":144},";\n",[57,230,232],{"className":113,"code":231,"language":115,"meta":62,"style":62},"\u002F\u002F src\u002Ftest\u002Fsetup.ts\nimport \"@testing-library\u002Fjest-dom\u002Fvitest\";\n",[64,233,234,239],{"__ignoreMap":62},[67,235,236],{"class":69,"line":70},[67,237,238],{"class":122},"\u002F\u002F src\u002Ftest\u002Fsetup.ts\n",[67,240,241,244,246,249,251],{"class":69,"line":126},[67,242,243],{"class":129},"import",[67,245,170],{"class":169},[67,247,248],{"class":77},"@testing-library\u002Fjest-dom\u002Fvitest",[67,250,176],{"class":169},[67,252,228],{"class":144},[46,254,255],{"id":255},"基本用例",[57,257,261],{"className":258,"code":259,"language":260,"meta":62,"style":62},"language-tsx shiki shiki-themes material-theme-lighter github-light github-dark","import { render, screen } from \"@testing-library\u002Freact\";\nimport userEvent from \"@testing-library\u002Fuser-event\";\nimport { describe, it, expect } from \"vitest\";\nimport { Counter } from \".\u002FCounter\";\n\ndescribe(\"Counter\", () => {\n  it(\"点击后计数增加\", async () => {\n    const user = userEvent.setup();\n    render(\u003CCounter \u002F>);\n    await user.click(screen.getByRole(\"button\", { name: \u002F加一\u002Fi }));\n    expect(screen.getByText(\"1\")).toBeInTheDocument();\n  });\n});\n","tsx",[64,262,263,268,273,278,283,289,294,299,304,310,316,322,328],{"__ignoreMap":62},[67,264,265],{"class":69,"line":70},[67,266,267],{},"import { render, screen } from \"@testing-library\u002Freact\";\n",[67,269,270],{"class":69,"line":126},[67,271,272],{},"import userEvent from \"@testing-library\u002Fuser-event\";\n",[67,274,275],{"class":69,"line":148},[67,276,277],{},"import { describe, it, expect } from \"vitest\";\n",[67,279,280],{"class":69,"line":161},[67,281,282],{},"import { Counter } from \".\u002FCounter\";\n",[67,284,285],{"class":69,"line":182},[67,286,288],{"emptyLinePlaceholder":287},true,"\n",[67,290,291],{"class":69,"line":199},[67,292,293],{},"describe(\"Counter\", () => {\n",[67,295,296],{"class":69,"line":213},[67,297,298],{},"  it(\"点击后计数增加\", async () => {\n",[67,300,301],{"class":69,"line":219},[67,302,303],{},"    const user = userEvent.setup();\n",[67,305,307],{"class":69,"line":306},9,[67,308,309],{},"    render(\u003CCounter \u002F>);\n",[67,311,313],{"class":69,"line":312},10,[67,314,315],{},"    await user.click(screen.getByRole(\"button\", { name: \u002F加一\u002Fi }));\n",[67,317,319],{"class":69,"line":318},11,[67,320,321],{},"    expect(screen.getByText(\"1\")).toBeInTheDocument();\n",[67,323,325],{"class":69,"line":324},12,[67,326,327],{},"  });\n",[67,329,331],{"class":69,"line":330},13,[67,332,333],{},"});\n",[23,335,336,337,340,341,344,345,348],{},"优先使用 ",[27,338,339],{},"role、label、text"," 查询（",[64,342,343],{},"getByRole","、",[64,346,347],{},"getByLabelText","），与可访问性一致。",[46,350,351],{"id":351},"查询方法优先级",[23,353,354],{},"RTL 官方建议按以下优先级选择查询方式（越靠前越推荐）：",[356,357,358,364,372,382],"ol",{},[359,360,361,363],"li",{},[64,362,343],{},"：按 ARIA 角色（button、textbox、heading），最贴近用户\u002F屏幕阅读器的感知方式。",[359,365,366,368,369,44],{},[64,367,347],{},"：表单控件配合 ",[64,370,371],{},"\u003Clabel>",[359,373,374,377,378,381],{},[64,375,376],{},"getByPlaceholderText"," \u002F ",[64,379,380],{},"getByText","：无更好选择时的次优方案。",[359,383,384,387,388,391],{},[64,385,386],{},"getByTestId","：",[27,389,390],{},"最后手段","，仅当以上都不适用（如没有语义化角色的容器）。",[57,393,395],{"className":258,"code":394,"language":260,"meta":62,"style":62},"\u002F\u002F 推荐\nscreen.getByRole(\"textbox\", { name: \"邮箱\" });\n\n\u002F\u002F 避免（除非无法用语义化查询）\nscreen.getByTestId(\"email-input\");\n",[64,396,397,402,407,411,416],{"__ignoreMap":62},[67,398,399],{"class":69,"line":70},[67,400,401],{},"\u002F\u002F 推荐\n",[67,403,404],{"class":69,"line":126},[67,405,406],{},"screen.getByRole(\"textbox\", { name: \"邮箱\" });\n",[67,408,409],{"class":69,"line":148},[67,410,288],{"emptyLinePlaceholder":287},[67,412,413],{"class":69,"line":161},[67,414,415],{},"\u002F\u002F 避免（除非无法用语义化查询）\n",[67,417,418],{"class":69,"line":182},[67,419,420],{},"screen.getByTestId(\"email-input\");\n",[46,422,423],{"id":423},"测试带依赖注入的组件",[57,425,427],{"className":258,"code":426,"language":260,"meta":62,"style":62},"function renderWithProviders(ui: ReactNode) {\n  return render(\n    \u003CQueryClientProvider client={new QueryClient()}>\n      \u003CThemeProvider>{ui}\u003C\u002FThemeProvider>\n    \u003C\u002FQueryClientProvider>\n  );\n}\n\nit(\"展示用户名\", async () => {\n  renderWithProviders(\u003CUserProfile userId=\"1\" \u002F>);\n  expect(await screen.findByText(\"Test User\")).toBeInTheDocument();\n});\n",[64,428,429,434,439,444,449,454,459,464,468,473,478,483],{"__ignoreMap":62},[67,430,431],{"class":69,"line":70},[67,432,433],{},"function renderWithProviders(ui: ReactNode) {\n",[67,435,436],{"class":69,"line":126},[67,437,438],{},"  return render(\n",[67,440,441],{"class":69,"line":148},[67,442,443],{},"    \u003CQueryClientProvider client={new QueryClient()}>\n",[67,445,446],{"class":69,"line":161},[67,447,448],{},"      \u003CThemeProvider>{ui}\u003C\u002FThemeProvider>\n",[67,450,451],{"class":69,"line":182},[67,452,453],{},"    \u003C\u002FQueryClientProvider>\n",[67,455,456],{"class":69,"line":199},[67,457,458],{},"  );\n",[67,460,461],{"class":69,"line":213},[67,462,463],{},"}\n",[67,465,466],{"class":69,"line":219},[67,467,288],{"emptyLinePlaceholder":287},[67,469,470],{"class":69,"line":306},[67,471,472],{},"it(\"展示用户名\", async () => {\n",[67,474,475],{"class":69,"line":312},[67,476,477],{},"  renderWithProviders(\u003CUserProfile userId=\"1\" \u002F>);\n",[67,479,480],{"class":69,"line":318},[67,481,482],{},"  expect(await screen.findByText(\"Test User\")).toBeInTheDocument();\n",[67,484,485],{"class":69,"line":324},[67,486,333],{},[23,488,489,490,493],{},"抽出 ",[64,491,492],{},"renderWithProviders"," 帮助函数，避免每个测试文件重复包裹相同的 Context\u002FProvider。",[46,495,496],{"id":496},"异步与副作用",[498,499,500,510,515],"ul",{},[359,501,502,505,506,509],{},[64,503,504],{},"findBy*","：等待元素出现（内部封装了 ",[64,507,508],{},"waitFor","，适合断言异步渲染结果）。",[359,511,512,514],{},[64,513,508],{},"：等待断言条件。",[359,516,517,518,521,522,527],{},"涉及 ",[64,519,520],{},"useEffect"," 的请求可用 ",[30,523,526],{"href":524,"rel":525},"https:\u002F\u002Fmswjs.io\u002F",[34],"MSW"," 模拟网络：",[57,529,531],{"className":113,"code":530,"language":115,"meta":62,"style":62},"import { http, HttpResponse } from \"msw\";\nimport { setupServer } from \"msw\u002Fnode\";\n\nconst server = setupServer(\n  http.get(\"\u002Fapi\u002Fusers\u002F:id\", () => HttpResponse.json({ id: \"1\", name: \"Test User\" }))\n);\n\nbeforeAll(() => server.listen());\nafterEach(() => server.resetHandlers());\nafterAll(() => server.close());\n",[64,532,533,564,586,590,609,680,686,690,714,736],{"__ignoreMap":62},[67,534,535,537,540,543,546,549,552,555,557,560,562],{"class":69,"line":70},[67,536,243],{"class":129},[67,538,539],{"class":144}," {",[67,541,542],{"class":140}," http",[67,544,545],{"class":144},",",[67,547,548],{"class":140}," HttpResponse",[67,550,551],{"class":144}," }",[67,553,554],{"class":129}," from",[67,556,170],{"class":169},[67,558,559],{"class":77},"msw",[67,561,176],{"class":169},[67,563,228],{"class":144},[67,565,566,568,570,573,575,577,579,582,584],{"class":69,"line":126},[67,567,243],{"class":129},[67,569,539],{"class":144},[67,571,572],{"class":140}," setupServer",[67,574,551],{"class":144},[67,576,554],{"class":129},[67,578,170],{"class":169},[67,580,581],{"class":77},"msw\u002Fnode",[67,583,176],{"class":169},[67,585,228],{"class":144},[67,587,588],{"class":69,"line":148},[67,589,288],{"emptyLinePlaceholder":287},[67,591,592,596,600,604,606],{"class":69,"line":161},[67,593,595],{"class":594},"sbsja","const",[67,597,599],{"class":598},"s_hVV"," server",[67,601,603],{"class":602},"smGrS"," =",[67,605,572],{"class":136},[67,607,608],{"class":140},"(\n",[67,610,611,614,617,620,622,624,627,629,631,634,637,639,641,644,646,649,652,654,656,659,661,663,666,668,670,673,675,677],{"class":69,"line":182},[67,612,613],{"class":140},"  http",[67,615,616],{"class":144},".",[67,618,619],{"class":136},"get",[67,621,141],{"class":140},[67,623,176],{"class":169},[67,625,626],{"class":77},"\u002Fapi\u002Fusers\u002F:id",[67,628,176],{"class":169},[67,630,545],{"class":144},[67,632,633],{"class":144}," ()",[67,635,636],{"class":594}," =>",[67,638,548],{"class":140},[67,640,616],{"class":144},[67,642,643],{"class":136},"json",[67,645,141],{"class":140},[67,647,648],{"class":144},"{",[67,650,651],{"class":151}," id",[67,653,155],{"class":144},[67,655,170],{"class":169},[67,657,658],{"class":77},"1",[67,660,176],{"class":169},[67,662,545],{"class":144},[67,664,665],{"class":151}," name",[67,667,155],{"class":144},[67,669,170],{"class":169},[67,671,672],{"class":77},"Test User",[67,674,176],{"class":169},[67,676,551],{"class":144},[67,678,679],{"class":140},"))\n",[67,681,682,684],{"class":69,"line":199},[67,683,225],{"class":140},[67,685,228],{"class":144},[67,687,688],{"class":69,"line":213},[67,689,288],{"emptyLinePlaceholder":287},[67,691,692,695,697,700,702,704,706,709,712],{"class":69,"line":219},[67,693,694],{"class":136},"beforeAll",[67,696,141],{"class":140},[67,698,699],{"class":144},"()",[67,701,636],{"class":594},[67,703,599],{"class":140},[67,705,616],{"class":144},[67,707,708],{"class":136},"listen",[67,710,711],{"class":140},"())",[67,713,228],{"class":144},[67,715,716,719,721,723,725,727,729,732,734],{"class":69,"line":306},[67,717,718],{"class":136},"afterEach",[67,720,141],{"class":140},[67,722,699],{"class":144},[67,724,636],{"class":594},[67,726,599],{"class":140},[67,728,616],{"class":144},[67,730,731],{"class":136},"resetHandlers",[67,733,711],{"class":140},[67,735,228],{"class":144},[67,737,738,741,743,745,747,749,751,754,756],{"class":69,"line":312},[67,739,740],{"class":136},"afterAll",[67,742,141],{"class":140},[67,744,699],{"class":144},[67,746,636],{"class":594},[67,748,599],{"class":140},[67,750,616],{"class":144},[67,752,753],{"class":136},"close",[67,755,711],{"class":140},[67,757,228],{"class":144},[23,759,760,761,764],{},"MSW 在网络层拦截请求，比手动 mock ",[64,762,763],{},"fetch"," 更真实，也能在开发环境复用同一套 mock 定义。",[46,766,767],{"id":767},"快照测试的取舍",[57,769,771],{"className":258,"code":770,"language":260,"meta":62,"style":62},"it(\"匹配快照\", () => {\n  const { asFragment } = render(\u003CBadge type=\"success\">已完成\u003C\u002FBadge>);\n  expect(asFragment()).toMatchSnapshot();\n});\n",[64,772,773,778,783,788],{"__ignoreMap":62},[67,774,775],{"class":69,"line":70},[67,776,777],{},"it(\"匹配快照\", () => {\n",[67,779,780],{"class":69,"line":126},[67,781,782],{},"  const { asFragment } = render(\u003CBadge type=\"success\">已完成\u003C\u002FBadge>);\n",[67,784,785],{"class":69,"line":148},[67,786,787],{},"  expect(asFragment()).toMatchSnapshot();\n",[67,789,790],{"class":69,"line":161},[67,791,333],{},[23,793,794,795,798],{},"快照测试对",[27,796,797],{},"小型、稳定的展示组件","（如 Badge、Icon）有一定价值，但大段 DOM 快照难以人工审查、容易被无脑更新（\"snapshot rubber-stamping\"），核心业务逻辑应优先用行为断言而非快照。",[46,800,801],{"id":801},"避免",[498,803,804,807,810],{},[359,805,806],{},"依赖组件内部 state 变量名或私有方法。",[359,808,809],{},"快照测试滥用（大段 DOM 快照难以维护）。",[359,811,812,813,816],{},"用 ",[64,814,815],{},"container.querySelector"," 代替语义化查询（除非确实没有更好的方式）。",[46,818,819],{"id":819},"最佳实践",[498,821,822,832,839,842,848],{},[359,823,824,825,828,829,44],{},"测试文件贴近「用户故事」组织：",[64,826,827],{},"it(\"输入非法邮箱后显示错误提示\")","，而不是 ",[64,830,831],{},"it(\"email state 更新\")",[359,833,834,835,838],{},"表单类组件重点测校验逻辑与提交结果，不测内部 ",[64,836,837],{},"useState"," 的字段名。",[359,840,841],{},"网络请求统一用 MSW 模拟，同一套 handlers 可以在开发环境（mock server）和测试中复用。",[359,843,844,845,847],{},"组件依赖多个 Context\u002FProvider 时，封装 ",[64,846,492],{}," 减少重复代码。",[359,849,850],{},"CI 上锁定 Node 版本与依赖版本，避免 jsdom\u002F浏览器行为差异导致的偶发失败。",[46,852,853],{"id":853},"常见坑",[855,856,857,873],"table",{},[858,859,860],"thead",{},[861,862,863,867,870],"tr",{},[864,865,866],"th",{},"现象",[864,868,869],{},"常见原因",[864,871,872],{},"处理",[874,875,876,898,914,925,936],"tbody",{},[861,877,878,884,887],{},[879,880,881,883],"td",{},[64,882,343],{}," 找不到元素但页面上明明有",[879,885,886],{},"元素缺少正确的 ARIA role 或语义化标签",[879,888,889,890,893,894,897],{},"检查是否用了语义化 HTML（",[64,891,892],{},"\u003Cbutton>"," 而非 ",[64,895,896],{},"\u003Cdiv onClick>","）",[861,899,900,903,906],{},[879,901,902],{},"测试报「更新未包裹在 act(...) 中」",[879,904,905],{},"异步状态更新未被测试工具等待完成",[879,907,812,908,910,911,913],{},[64,909,504],{},"\u002F",[64,912,508],{}," 等待更新完成，而非同步断言",[861,915,916,919,922],{},[879,917,918],{},"Mock 请求不生效",[879,920,921],{},"MSW handler 与实际请求 URL\u002F方法不匹配",[879,923,924],{},"核对拦截规则的 path 与真实请求一致",[861,926,927,930,933],{},[879,928,929],{},"快照测试频繁失败又被无脑更新",[879,931,932],{},"快照过大、覆盖了不相关的样式细节",[879,934,935],{},"缩小快照范围或改用具体断言",[861,937,938,941,944],{},[879,939,940],{},"测试之间状态互相影响",[879,942,943],{},"全局 mock（如 MSW handlers）未在每个测试后重置",[879,945,812,946,948],{},[64,947,718],{}," 清理\u002F重置 mock 状态",[46,950,951],{"id":951},"延伸阅读",[498,953,954,961,968],{},[359,955,956,960],{},[30,957,959],{"href":958},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Feffect","Effect 与副作用","：需要 mock 的典型场景",[359,962,963,967],{},[30,964,966],{"href":965},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fcontext","Context","：测试时如何包裹 Provider",[359,969,970,974],{},[30,971,973],{"href":972},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fproject","工程化","：Vite 测试环境配置",[46,976,977],{"id":977},"参考文献",[23,979,980],{},"以下链接在编写时均可正常访问：",[855,982,983,993],{},[858,984,985],{},[861,986,987,990],{},[864,988,989],{},"资料",[864,991,992],{},"说明",[874,994,995,1006,1017,1029,1039],{},[861,996,997,1003],{},[879,998,999],{},[30,1000,1002],{"href":32,"rel":1001},[34],"Testing Library：React",[879,1004,1005],{},"RTL 指南",[861,1007,1008,1014],{},[879,1009,1010],{},[30,1011,109],{"href":1012,"rel":1013},"https:\u002F\u002Fvitest.dev\u002F",[34],[879,1015,1016],{},"测试运行器",[861,1018,1019,1026],{},[879,1020,1021],{},[30,1022,1025],{"href":1023,"rel":1024},"https:\u002F\u002Fzh-hans.react.dev\u002Flearn\u002Ftesting",[34],"React：测试 Recipes",[879,1027,1028],{},"官方概览",[861,1030,1031,1036],{},[879,1032,1033],{},[30,1034,526],{"href":524,"rel":1035},[34],[879,1037,1038],{},"网络请求 Mock",[861,1040,1041,1048],{},[879,1042,1043],{},[30,1044,1047],{"href":1045,"rel":1046},"https:\u002F\u002Ftesting-library.com\u002Fdocs\u002Fqueries\u002Fabout\u002F#priority",[34],"Testing Library：查询优先级",[879,1049,1050],{},"查询方式指南",[1052,1053,1054],"style",{},"html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":62,"searchDepth":126,"depth":126,"links":1056},[1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068],{"id":48,"depth":126,"text":48},{"id":54,"depth":126,"text":55},{"id":255,"depth":126,"text":255},{"id":351,"depth":126,"text":351},{"id":423,"depth":126,"text":423},{"id":496,"depth":126,"text":496},{"id":767,"depth":126,"text":767},{"id":801,"depth":126,"text":801},{"id":819,"depth":126,"text":819},{"id":853,"depth":126,"text":853},{"id":951,"depth":126,"text":951},{"id":977,"depth":126,"text":977},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FReact\u002Ftest","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FReact\u002Ftest.md","md",{"items":1074},[1075,1079,1081,1085,1089,1091],{"path":1076,"title":1077,"description":1078,"category":10,"score":199},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Frendering","渲染与协调","React 将组件渲染为虚拟 DOM 树，再协调（reconciliation）到浏览器 DOM。理解该过程有助于解释 key、memo 与性能优化。涵盖渲染流程、Fiber、startTransition、Suspense、最佳实践与常见坑。",{"path":972,"title":973,"description":1080,"category":10,"score":199},"新建 React 项目时，官方与社区主流工具为 Vite；全栈场景常用 Next.js。涵盖 Vite + React、Create React App 现状、Next.js、目录结构、环境变量、部署、最佳实践与常见坑。",{"path":1082,"title":1083,"description":1084,"category":10,"score":199},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Ftypescript","TypeScript","React 官方推荐 TypeScript。类型主要落在 Props、事件、Ref 与 Hooks 返回值上。见 组件。",{"path":1086,"title":1087,"description":1088,"category":10,"score":199},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fbuild","构建与分包","通过代码分割与构建配置减小首屏 JavaScript 体积。涵盖 React.lazy\u002FSuspense、路由级分割、Tree Shaking、Vite 生产构建、体积分析、最佳实践与常见坑。运行时优化见 runtime。",{"path":958,"title":959,"description":1090,"category":10,"score":199},"useEffect 用于在组件渲染后执行与外部系统同步的逻辑（请求、订阅、手动改 DOM 等）。涵盖基本用法、依赖数组、适用\u002F不适用场景、useLayoutEffect、最佳实践与常见坑。",{"path":1092,"title":1093,"description":1094,"category":10,"score":199},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fstate","状态管理","React 组件内 state 用 Hooks；跨组件共享状态可选 Context、Redux Toolkit、Zustand 等。见 Context Hook。",{"series":1096,"title":12,"items":1097,"index":1145,"prev":1146,"next":1147},"booklet\u002Fframe\u002Freact",[1098,1102,1104,1108,1110,1114,1116,1120,1124,1126,1128,1132,1136,1140,1142,1143],{"path":1099,"title":1100,"date":1101,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fbasics","React 基础","2020-02-12",{"path":1086,"title":1087,"date":1103,"pinned":15},"2025-07-23",{"path":1105,"title":1106,"date":1107,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fcomponents","组件与 Props","2020-06-11",{"path":965,"title":966,"date":1109,"pinned":15},"2021-10-16",{"path":1111,"title":1112,"date":1113,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fecology","生态","2023-02-04",{"path":958,"title":959,"date":1115,"pinned":15},"2024-10-11",{"path":1117,"title":1118,"date":1119,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fjsx","JSX 与语法","2020-02-29",{"path":1121,"title":1122,"date":1123,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fmemo","Memo、Callback 与 Ref","2021-02-28",{"path":972,"title":973,"date":1125,"pinned":15},"2026-05-08",{"path":1076,"title":1077,"date":1127,"pinned":15},"2026-07-10",{"path":1129,"title":1130,"date":1131,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Frouter","路由","2023-09-06",{"path":1133,"title":1134,"date":1135,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fruntime","运行时优化","2020-02-05",{"path":1137,"title":1138,"date":1139,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fstate","State 与 Reducer","2021-02-21",{"path":1092,"title":1093,"date":1141,"pinned":15},"2024-02-07",{"path":7,"title":8,"date":14,"pinned":15},{"path":1082,"title":1083,"date":1144,"pinned":15},"2026-03-13",14,{"path":1092,"title":1093,"date":1141,"pinned":15},{"path":1082,"title":1083,"date":1144,"pinned":15}]