diff --git a/.claude/skills/docs-writer-blog/SKILL.md b/.claude/skills/docs-writer-blog/SKILL.md index ef28225f81..baa21c34d9 100644 --- a/.claude/skills/docs-writer-blog/SKILL.md +++ b/.claude/skills/docs-writer-blog/SKILL.md @@ -159,7 +159,7 @@ See [How to Upgrade to React X.Y](/blog/YYYY/MM/DD/react-xy-upgrade-guide) for s ### React {/*react*/} -* Add `useNewHook` for [purpose]. ([#12345](https://github.com/facebook/react/pull/12345) by [@contributor](https://github.com/contributor)) +* Add `useNewHook` for [purpose]. ([#12345](https://github.com/react/react/pull/12345) by [@contributor](https://github.com/contributor)) --- @@ -603,7 +603,7 @@ npm install react@latest react-dom@latest | Type | Pattern | |------|---------| -| GitHub PR | `[#12345](https://github.com/facebook/react/pull/12345)` | +| GitHub PR | `[#12345](https://github.com/react/react/pull/12345)` | | GitHub user | `[@username](https://github.com/username)` | | Twitter/X | `[@username](https://x.com/username)` | | Bluesky | `[Name](https://bsky.app/profile/handle)` | @@ -623,8 +623,8 @@ For more information, see the docs for [`useActionState`](/reference/react/useAc ### Bullet Pattern ```markdown -* Add `useTransition` for concurrent rendering. ([#10426](https://github.com/facebook/react/pull/10426) by [@acdlite](https://github.com/acdlite)) -* Fix `useReducer` observing incorrect props. ([#22445](https://github.com/facebook/react/pull/22445) by [@josephsavona](https://github.com/josephsavona)) +* Add `useTransition` for concurrent rendering. ([#10426](https://github.com/react/react/pull/10426) by [@acdlite](https://github.com/acdlite)) +* Fix `useReducer` observing incorrect props. ([#22445](https://github.com/react/react/pull/22445) by [@josephsavona](https://github.com/josephsavona)) ``` **Structure:** `Verb` + backticked API + description + `([#PR](url) by [@user](url))` diff --git a/.claude/skills/react-expert/SKILL.md b/.claude/skills/react-expert/SKILL.md index 5ebcdee37d..c252f6ce0a 100644 --- a/.claude/skills/react-expert/SKILL.md +++ b/.claude/skills/react-expert/SKILL.md @@ -32,7 +32,7 @@ This skill produces exhaustive documentation research on any React API or concep 2. **React Source Code** - Warnings, errors, implementation details 3. **Git History** - Commit messages with context 4. **GitHub PRs & Comments** - Design rationale (via `gh` CLI) -5. **GitHub Issues** - Confusion/questions (facebook/react + reactjs/react.dev) +5. **GitHub Issues** - Confusion/questions (react/react + reactjs/react.dev) 6. **React Working Group** - Design discussions for newer APIs 7. **Flow Types** - Source of truth for type signatures 8. **TypeScript Types** - Note discrepancies with Flow @@ -51,7 +51,7 @@ First, ensure the React repo is available locally: if [ -d ".claude/react" ]; then cd .claude/react && git pull origin main else - git clone --depth=100 https://github.com/facebook/react.git .claude/react + git clone --depth=100 https://github.com/react/react.git .claude/react fi ``` @@ -71,8 +71,8 @@ Spawn these agents IN PARALLEL using the Task tool. Each agent receives the skep | test-explorer | Explore | Test files for usage patterns | Search `.claude/react/packages/*/src/__tests__/` for test files mentioning the topic. Extract actual usage examples WITH file paths and line numbers. | | source-explorer | Explore | Warnings/errors in source | Search `.claude/react/packages/*/src/` for console.error, console.warn, and error messages mentioning the topic. Document trigger conditions. | | git-historian | Explore | Commit messages | Run `git log --all --grep="" --oneline -50` in `.claude/react`. Read full commit messages for context. | -| pr-researcher | Explore | PRs introducing/modifying API | Run `gh pr list -R facebook/react --search "" --state all --limit 20`. Read key PR descriptions and comments. | -| issue-hunter | Explore | Issues showing confusion | Search issues in both `facebook/react` and `reactjs/react.dev` repos. Look for common questions and misunderstandings. | +| pr-researcher | Explore | PRs introducing/modifying API | Run `gh pr list -R react/react --search "" --state all --limit 20`. Read key PR descriptions and comments. | +| issue-hunter | Explore | Issues showing confusion | Search issues in both `react/react` and `reactjs/react.dev` repos. Look for common questions and misunderstandings. | | types-inspector | Explore | Flow + TypeScript signatures | Find Flow types in `.claude/react/packages/*/src/*.js` (look for `@flow` annotations). Find TS types in `.claude/react/packages/*/index.d.ts`. Note discrepancies. | ### Step 3: Agent Prompts @@ -164,8 +164,8 @@ CRITICAL: Do NOT rely on your prior knowledge. Only report what you find in PRs. Your task: Find PRs that introduced or modified . -1. Run: gh pr list -R facebook/react --search "" --state all --limit 20 --json number,title,url -2. For promising PRs, read details: gh pr view -R facebook/react +1. Run: gh pr list -R react/react --search "" --state all --limit 20 --json number,title,url +2. For promising PRs, read details: gh pr view -R react/react 3. Look for: - The original RFC/motivation - Design discussions in comments @@ -189,7 +189,7 @@ CRITICAL: Do NOT rely on your prior knowledge. Only report what you find in issu Your task: Find issues that reveal common confusion about . -1. Search facebook/react: gh issue list -R facebook/react --search "" --state all --limit 20 --json number,title,url +1. Search react/react: gh issue list -R react/react --search "" --state all --limit 20 --json number,title,url 2. Search reactjs/react.dev: gh issue list -R reactjs/react.dev --search "" --state all --limit 20 --json number,title,url 3. For each issue, identify: - What the user was confused about @@ -199,7 +199,7 @@ Your task: Find issues that reveal common confusion about . Format your output as: ## Common Confusion ### Issue #: -**Repo:** <facebook/react or reactjs/react.dev> +**Repo:** <react/react or reactjs/react.dev> **Confusion:** <what they misunderstood> **Resolution:** <correct understanding> **Gotcha:** <if applicable> diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..de2bfc3625 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +contact_links: + - name: 📃 Bugs in React + url: https://github.com/react/react/issues/new/choose + about: This issue tracker is not for bugs in React. Please file React issues here. + - name: 🤔 Questions and Help + url: https://reactjs.org/community/support.html + about: This issue tracker is not for support questions. Please refer to the React community's help and discussion forums. diff --git a/.github/workflows/discord_notify.yml b/.github/workflows/discord_notify.yml index 2f5b2a497e..97f8d183b1 100644 --- a/.github/workflows/discord_notify.yml +++ b/.github/workflows/discord_notify.yml @@ -8,7 +8,7 @@ permissions: {} jobs: check_maintainer: - uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main + uses: react/react/.github/workflows/shared_check_maintainer.yml@main permissions: # Used by check_maintainer contents: read diff --git a/.github/workflows/label_core_team_prs.yml b/.github/workflows/label_core_team_prs.yml index f9b3328ee5..4cb6fbc712 100644 --- a/.github/workflows/label_core_team_prs.yml +++ b/.github/workflows/label_core_team_prs.yml @@ -12,7 +12,7 @@ env: jobs: check_maintainer: - uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main + uses: react/react/.github/workflows/shared_check_maintainer.yml@main permissions: # Used by check_maintainer contents: read diff --git a/.gitignore b/.gitignore index 92626a1e63..ed9efe38d0 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ public/rss.xml # worktrees .worktrees/ + +# Generated OG images (scripts/generateOgImages.mjs) +public/images/og/ diff --git a/package.json b/package.json index 359f30d3e2..97bc1432c5 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "analyze": "ANALYZE=true next build", "dev": "next-remote-watch ./src/content", "prebuild:rsc": "node scripts/buildRscWorker.mjs", - "build": "node scripts/buildRscWorker.mjs && next build && node --experimental-modules ./scripts/downloadFonts.mjs", + "build": "node scripts/buildRscWorker.mjs && next build && node --experimental-modules ./scripts/downloadFonts.mjs && node ./scripts/generateOgImages.mjs", "lint": "next lint && eslint \"src/content/**/*.md\"", "lint:fix": "next lint --fix && eslint \"src/content/**/*.md\" --fix", "format:source": "prettier --config .prettierrc --write \"{plugins,src}/**/*.{js,ts,jsx,tsx,css}\"", @@ -51,6 +51,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.18.6", "@babel/preset-react": "^7.18.6", "@mdx-js/mdx": "^2.1.3", + "@resvg/resvg-js": "^2.6.2", "@types/body-scroll-lock": "^2.6.1", "@types/classnames": "^2.2.10", "@types/debounce": "^1.2.1", @@ -102,6 +103,7 @@ "retext": "^7.0.1", "retext-smartypants": "^4.0.0", "rss": "^1.2.2", + "satori": "^0.26.0", "tailwindcss": "^3.4.1", "typescript": "^5.7.2", "unist-util-visit": "^2.0.3", diff --git a/public/fonts/Optimistic_Display_W_Bd.ttf b/public/fonts/Optimistic_Display_W_Bd.ttf new file mode 100644 index 0000000000..9d0cff450f Binary files /dev/null and b/public/fonts/Optimistic_Display_W_Bd.ttf differ diff --git a/public/fonts/Optimistic_Display_W_Md.ttf b/public/fonts/Optimistic_Display_W_Md.ttf new file mode 100644 index 0000000000..3fbdb483db Binary files /dev/null and b/public/fonts/Optimistic_Display_W_Md.ttf differ diff --git a/public/images/team/gh-aurorascharff.jpg b/public/images/team/gh-aurorascharff.jpg new file mode 100644 index 0000000000..10476e5e88 Binary files /dev/null and b/public/images/team/gh-aurorascharff.jpg differ diff --git a/public/images/team/gh-brentvatne.jpg b/public/images/team/gh-brentvatne.jpg new file mode 100644 index 0000000000..c66a5cd6db Binary files /dev/null and b/public/images/team/gh-brentvatne.jpg differ diff --git a/public/images/team/gh-captbaritone.jpg b/public/images/team/gh-captbaritone.jpg new file mode 100644 index 0000000000..0100fa16b6 Binary files /dev/null and b/public/images/team/gh-captbaritone.jpg differ diff --git a/public/images/team/gh-cipolleschi.png b/public/images/team/gh-cipolleschi.png new file mode 100644 index 0000000000..a90bec66eb Binary files /dev/null and b/public/images/team/gh-cipolleschi.png differ diff --git a/public/images/team/gh-cortinico.jpg b/public/images/team/gh-cortinico.jpg new file mode 100644 index 0000000000..e806a103b8 Binary files /dev/null and b/public/images/team/gh-cortinico.jpg differ diff --git a/public/images/team/gh-fbmal7.jpg b/public/images/team/gh-fbmal7.jpg new file mode 100644 index 0000000000..0f79e181d5 Binary files /dev/null and b/public/images/team/gh-fbmal7.jpg differ diff --git a/public/images/team/gh-fkgozali.jpg b/public/images/team/gh-fkgozali.jpg new file mode 100644 index 0000000000..9f87e7efbf Binary files /dev/null and b/public/images/team/gh-fkgozali.jpg differ diff --git a/public/images/team/gh-huntie.jpg b/public/images/team/gh-huntie.jpg new file mode 100644 index 0000000000..a4de758019 Binary files /dev/null and b/public/images/team/gh-huntie.jpg differ diff --git a/public/images/team/gh-javache.jpg b/public/images/team/gh-javache.jpg new file mode 100644 index 0000000000..1f63907eab Binary files /dev/null and b/public/images/team/gh-javache.jpg differ diff --git a/public/images/team/gh-khalef1.jpg b/public/images/team/gh-khalef1.jpg new file mode 100644 index 0000000000..94d96f6703 Binary files /dev/null and b/public/images/team/gh-khalef1.jpg differ diff --git a/public/images/team/gh-kmagiera.jpg b/public/images/team/gh-kmagiera.jpg new file mode 100644 index 0000000000..0e03ecb779 Binary files /dev/null and b/public/images/team/gh-kmagiera.jpg differ diff --git a/public/images/team/gh-motiz88.jpg b/public/images/team/gh-motiz88.jpg new file mode 100644 index 0000000000..a67e30b1b0 Binary files /dev/null and b/public/images/team/gh-motiz88.jpg differ diff --git a/public/images/team/gh-rh389.jpg b/public/images/team/gh-rh389.jpg new file mode 100644 index 0000000000..4d5a5e8303 Binary files /dev/null and b/public/images/team/gh-rh389.jpg differ diff --git a/public/images/team/gh-rubennorte.jpg b/public/images/team/gh-rubennorte.jpg new file mode 100644 index 0000000000..459c267e45 Binary files /dev/null and b/public/images/team/gh-rubennorte.jpg differ diff --git a/public/images/team/gh-samselikoff.jpg b/public/images/team/gh-samselikoff.jpg new file mode 100644 index 0000000000..7887765b48 Binary files /dev/null and b/public/images/team/gh-samselikoff.jpg differ diff --git a/public/images/team/gh-simek.png b/public/images/team/gh-simek.png new file mode 100644 index 0000000000..23a950103c Binary files /dev/null and b/public/images/team/gh-simek.png differ diff --git a/public/images/team/gh-stmoy.jpg b/public/images/team/gh-stmoy.jpg new file mode 100644 index 0000000000..7694a7fb69 Binary files /dev/null and b/public/images/team/gh-stmoy.jpg differ diff --git a/public/images/team/gh-thymikee.jpg b/public/images/team/gh-thymikee.jpg new file mode 100644 index 0000000000..d0debc5bee Binary files /dev/null and b/public/images/team/gh-thymikee.jpg differ diff --git a/public/images/team/gh-vzaidman.jpg b/public/images/team/gh-vzaidman.jpg new file mode 100644 index 0000000000..c769056152 Binary files /dev/null and b/public/images/team/gh-vzaidman.jpg differ diff --git a/public/images/team/jimmy-lai.jpg b/public/images/team/jimmy-lai.jpg new file mode 100644 index 0000000000..57b50c800a Binary files /dev/null and b/public/images/team/jimmy-lai.jpg differ diff --git a/public/images/tutorial/codesandbox-devtools.png b/public/images/tutorial/codesandbox-devtools.png deleted file mode 100644 index 1f7ebd500e..0000000000 Binary files a/public/images/tutorial/codesandbox-devtools.png and /dev/null differ diff --git a/public/images/tutorial/components-tab.png b/public/images/tutorial/components-tab.png new file mode 100644 index 0000000000..90ce0770ee Binary files /dev/null and b/public/images/tutorial/components-tab.png differ diff --git a/public/images/tutorial/devtools-inspect.gif b/public/images/tutorial/devtools-inspect.gif new file mode 100644 index 0000000000..5c623e6a73 Binary files /dev/null and b/public/images/tutorial/devtools-inspect.gif differ diff --git a/public/images/tutorial/devtools-select.gif b/public/images/tutorial/devtools-select.gif deleted file mode 100644 index dd1e1aa61e..0000000000 Binary files a/public/images/tutorial/devtools-select.gif and /dev/null differ diff --git a/public/images/tutorial/sandbox-new-tab.png b/public/images/tutorial/sandbox-new-tab.png new file mode 100644 index 0000000000..093a363fb9 Binary files /dev/null and b/public/images/tutorial/sandbox-new-tab.png differ diff --git a/scripts/deadLinkChecker.js b/scripts/deadLinkChecker.js index 46a21cdc9e..287dde36ae 100644 --- a/scripts/deadLinkChecker.js +++ b/scripts/deadLinkChecker.js @@ -311,7 +311,7 @@ async function buildContributorMap() { async function fetchErrorCodes() { try { const response = await fetch( - 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json' + 'https://raw.githubusercontent.com/react/react/main/scripts/error-codes/codes.json' ); if (!response.ok) { throw new Error(`Failed to fetch error codes: ${response.status}`); diff --git a/scripts/generateOgImages.mjs b/scripts/generateOgImages.mjs new file mode 100644 index 0000000000..937727a1cb --- /dev/null +++ b/scripts/generateOgImages.mjs @@ -0,0 +1,219 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Generates a static OG image for every content page, so social +// cards show the page title without any runtime image generation. +// Run after `next build` (see the `build` script in package.json). + +import fs from 'fs'; +import path from 'path'; +import satori from 'satori'; +import {Resvg} from '@resvg/resvg-js'; +import matter from 'gray-matter'; + +const ROOT = process.cwd(); +const CONTENT_DIR = path.join(ROOT, 'src', 'content'); +const OUT_DIR = path.join(ROOT, 'public', 'images', 'og'); +const CONCURRENCY = 8; + +const SECTION_LABELS = { + learn: 'Learn React', + reference: 'API Reference', + community: 'Community', + blog: 'Blog', +}; + +const bold = fs.readFileSync( + path.join(ROOT, 'public', 'fonts', 'Optimistic_Display_W_Bd.ttf') +); +const medium = fs.readFileSync( + path.join(ROOT, 'public', 'fonts', 'Optimistic_Display_W_Md.ttf') +); + +function el(type, style, children) { + return {type, props: {style, children}}; +} + +function card(title, pagePath) { + const section = pagePath.split('/')[1] ?? ''; + const label = SECTION_LABELS[section] ?? 'React'; + const logo = { + type: 'svg', + props: { + width: 80, + height: 72, + viewBox: '-10.5 -9.45 21 18.9', + fill: 'none', + children: [ + {type: 'circle', props: {cx: 0, cy: 0, r: 2, fill: '#58c4dc'}}, + { + type: 'g', + props: { + stroke: '#58c4dc', + strokeWidth: 1, + fill: 'none', + children: [ + {type: 'ellipse', props: {rx: 10, ry: 4.5}}, + { + type: 'ellipse', + props: {rx: 10, ry: 4.5, transform: 'rotate(60)'}, + }, + { + type: 'ellipse', + props: {rx: 10, ry: 4.5, transform: 'rotate(120)'}, + }, + ], + }, + }, + ], + }, + }; + return el( + 'div', + { + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + padding: '72px 80px', + backgroundColor: '#23272f', + backgroundImage: + 'radial-gradient(circle at 25% 30%, #343a46 0%, #23272f 55%)', + }, + [ + el('div', {display: 'flex', alignItems: 'center', gap: '20px'}, [ + logo, + el( + 'div', + { + fontSize: 48, + fontFamily: 'Optimistic Display Bold', + color: '#f6f7f9', + }, + 'React' + ), + ]), + el( + 'div', + { + flexGrow: 1, + display: 'flex', + alignItems: 'center', + fontSize: title.length > 24 ? 72 : 96, + fontFamily: 'Optimistic Display Bold', + color: '#f6f7f9', + lineHeight: 1.1, + wordBreak: 'break-word', + }, + title + ), + el( + 'div', + { + fontSize: 40, + fontFamily: 'Optimistic Display Medium', + color: '#99a1b3', + }, + label + ), + ] + ); +} + +async function renderCard(title, pagePath) { + const svg = await satori(card(title, pagePath), { + width: 1200, + height: 630, + fonts: [ + { + name: 'Optimistic Display Bold', + data: bold, + weight: 700, + style: 'normal', + }, + { + name: 'Optimistic Display Medium', + data: medium, + weight: 500, + style: 'normal', + }, + ], + }); + return new Resvg(svg, {fitTo: {mode: 'width', value: 1200}}).render().asPng(); +} + +function collectSidebarTitles() { + const titles = new Map(); + for (const name of fs.readdirSync(path.join(ROOT, 'src'))) { + if (!/^sidebar.*\.json$/.test(name)) continue; + const walk = (node) => { + if (node.path && node.title) { + titles.set(node.path, node.title); + } + for (const child of node.routes ?? []) walk(child); + }; + walk(JSON.parse(fs.readFileSync(path.join(ROOT, 'src', name), 'utf8'))); + } + return titles; +} + +const sidebarTitles = collectSidebarTitles(); + +function collectPages(dir, out) { + for (const entry of fs.readdirSync(dir, {withFileTypes: true})) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + collectPages(full, out); + } else if (entry.name.endsWith('.md')) { + const rel = path.relative(CONTENT_DIR, full).replace(/\.md$/, ''); + const segments = rel.split(path.sep); + if (segments[segments.length - 1] === 'index') { + segments.pop(); + } + const pagePath = '/' + segments.join('/'); + if (pagePath === '/' || pagePath.startsWith('/errors')) { + continue; + } + const {data} = matter(fs.readFileSync(full, 'utf8')); + const title = data.title ?? sidebarTitles.get(pagePath); + if (title) { + out.push({title: String(title), pagePath}); + } + } + } + return out; +} + +export function ogImageFileName(pagePath) { + return pagePath.replace(/^\//, '').replace(/\//g, '-') + '.png'; +} + +async function main() { + const pages = collectPages(CONTENT_DIR, []); + fs.mkdirSync(OUT_DIR, {recursive: true}); + let done = 0; + const queue = [...pages]; + async function worker() { + for (;;) { + const page = queue.shift(); + if (!page) return; + const png = await renderCard(page.title, page.pagePath); + fs.writeFileSync(path.join(OUT_DIR, ogImageFileName(page.pagePath)), png); + done++; + if (done % 100 === 0) { + console.log(`og-images: ${done}/${pages.length}`); + } + } + } + await Promise.all(Array.from({length: CONCURRENCY}, worker)); + console.log(`og-images: generated ${done} images in public/images/og`); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/src/components/ButtonLink.tsx b/src/components/ButtonLink.tsx index bd98d5b38e..dff09ed3a7 100644 --- a/src/components/ButtonLink.tsx +++ b/src/components/ButtonLink.tsx @@ -33,7 +33,7 @@ function ButtonLink({ className, 'active:scale-[.98] transition-transform inline-flex font-bold items-center outline-none focus:outline-none focus-visible:outline focus-visible:outline-link focus:outline-offset-2 focus-visible:dark:focus:outline-link-dark leading-snug', { - 'bg-link text-white dark:bg-brand-dark dark:text-secondary hover:bg-opacity-80': + 'bg-link text-white dark:bg-brand-dark dark:text-gray-90 hover:bg-opacity-80': type === 'primary', 'text-primary dark:text-primary-dark shadow-secondary-button-stroke dark:shadow-secondary-button-stroke-dark hover:bg-gray-40/5 active:bg-gray-40/10 hover:dark:bg-gray-60/5 active:dark:bg-gray-60/10': type === 'secondary', diff --git a/src/components/Layout/Footer.tsx b/src/components/Layout/Footer.tsx index ac01835a7e..8570c28fe9 100644 --- a/src/components/Layout/Footer.tsx +++ b/src/components/Layout/Footer.tsx @@ -341,8 +341,13 @@ export function Footer() { <FooterLink href="/community" isHeader={true}> 社区 </FooterLink> +<<<<<<< HEAD <FooterLink href="https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md"> 行为准则 +======= + <FooterLink href="https://github.com/react/react/blob/main/CODE_OF_CONDUCT.md"> + Code of Conduct +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </FooterLink> <FooterLink href="/community/team">团队</FooterLink> <FooterLink href="/community/docs-contributors"> @@ -381,7 +386,7 @@ export function Footer() { </ExternalLink> <ExternalLink aria-label="React on Github" - href="https://github.com/facebook/react" + href="https://github.com/react/react" className={socialLinkClasses}> <IconGitHub /> </ExternalLink> diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index aa39fe5fc2..59cf4386d8 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -129,7 +129,18 @@ export function Page({ title={title} titleForTitleTag={meta.titleForTitleTag} isHomePage={isHomePage} - image={`/images/og-` + section + '.png'} + image={ + // OG images are generated per page at build time by + // scripts/generateOgImages.mjs. Pages without a generated + // card (home, errors, 404, 500) use the static section image. + isHomePage || + !title || + cleanedPath.startsWith('/errors') || + cleanedPath === '/404' || + cleanedPath === '/500' + ? `/images/og-${section ?? 'unknown'}.png` + : `/images/og/${cleanedPath.slice(1).replace(/\//g, '-')}.png` + } searchOrder={searchOrder} /> {(isHomePage || isBlogIndex) && ( diff --git a/src/components/Layout/Toc.tsx b/src/components/Layout/Toc.tsx index 80a5c5535e..abbd92e1b3 100644 --- a/src/components/Layout/Toc.tsx +++ b/src/components/Layout/Toc.tsx @@ -11,6 +11,7 @@ import cx from 'classnames'; import {useTocHighlight} from './useTocHighlight'; +import {IsInTocContext} from '../MDX/TocContext'; import type {Toc} from '../MDX/TocContext'; export function Toc({headings}: {headings: Toc}) { @@ -32,37 +33,39 @@ export function Toc({headings}: {headings: Toc}) { overscrollBehavior: 'contain', }}> <ul className="space-y-2 pb-16"> - {headings.length > 0 && - headings.map((h, i) => { - if (!h.url && process.env.NODE_ENV === 'development') { - console.error('Heading does not have URL'); - } - return ( - <li - key={`heading-${h.url}-${i}`} - className={cx( - 'text-sm px-2 rounded-s-xl', - selectedIndex === i - ? 'bg-highlight dark:bg-highlight-dark' - : null, - { - 'ps-4': h?.depth === 3, - hidden: h.depth && h.depth > 3, - } - )}> - <a + <IsInTocContext.Provider value={true}> + {headings.length > 0 && + headings.map((h, i) => { + if (!h.url && process.env.NODE_ENV === 'development') { + console.error('Heading does not have URL'); + } + return ( + <li + key={`heading-${h.url}-${i}`} className={cx( + 'text-sm px-2 rounded-s-xl', selectedIndex === i - ? 'text-link dark:text-link-dark font-bold' - : 'text-secondary dark:text-secondary-dark', - 'block hover:text-link dark:hover:text-link-dark leading-normal py-2' - )} - href={h.url}> - {h.text} - </a> - </li> - ); - })} + ? 'bg-highlight dark:bg-highlight-dark' + : null, + { + 'ps-4': h?.depth === 3, + hidden: h.depth && h.depth > 3, + } + )}> + <a + className={cx( + selectedIndex === i + ? 'text-link dark:text-link-dark font-bold' + : 'text-secondary dark:text-secondary-dark', + 'block hover:text-link dark:hover:text-link-dark leading-normal py-2' + )} + href={h.url}> + {h.text} + </a> + </li> + ); + })} + </IsInTocContext.Provider> </ul> </div> </nav> diff --git a/src/components/Layout/TopNav/TopNav.tsx b/src/components/Layout/TopNav/TopNav.tsx index 15247bb7a7..6dc4239181 100644 --- a/src/components/Layout/TopNav/TopNav.tsx +++ b/src/components/Layout/TopNav/TopNav.tsx @@ -386,7 +386,7 @@ export default function TopNav({ </div> <div className="flex"> <Link - href="https://github.com/facebook/react/releases" + href="https://github.com/react/react/releases" target="_blank" rel="noreferrer noopener" aria-label="Open on GitHub" diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx index 70f0bb05ff..2442e775e0 100644 --- a/src/components/MDX/MDXComponents.tsx +++ b/src/components/MDX/MDXComponents.tsx @@ -36,7 +36,7 @@ import YouWillLearnCard from './YouWillLearnCard'; import {Challenges, Hint, Solution} from './Challenges'; import {IconNavArrow} from '../Icon/IconNavArrow'; import ButtonLink from 'components/ButtonLink'; -import {TocContext} from './TocContext'; +import {TocContext, IsInTocContext} from './TocContext'; import type {Toc, TocItem} from './TocContext'; import {TeamMember} from './TeamMember'; import {LanguagesContext} from './LanguagesContext'; @@ -122,33 +122,57 @@ const RSC = ({children}: {children: React.ReactNode}) => ( <ExpandableCallout type="rsc">{children}</ExpandableCallout> ); -const CanaryBadge = ({title}: {title: string}) => ( - <span - title={title} - className={ - 'text-base font-display px-1 py-0.5 font-bold bg-gray-10 dark:bg-gray-60 text-gray-60 dark:text-gray-10 rounded' - }> - <IconCanary - size="s" - className={'inline me-1 mb-0.5 text-sm text-gray-60 dark:text-gray-10'} - /> - Canary only - </span> -); +const CanaryBadge = ({title}: {title: string}) => { + const isInToc = useContext(IsInTocContext); + if (isInToc) { + return ( + <IconCanary + size="s" + title={title} + className="inline me-1 mb-0.5 text-gray-60 dark:text-gray-10" + /> + ); + } + return ( + <span + title={title} + className={ + 'text-base font-display px-1 py-0.5 font-bold bg-gray-10 dark:bg-gray-60 text-gray-60 dark:text-gray-10 rounded' + }> + <IconCanary + size="s" + className={'inline me-1 mb-0.5 text-sm text-gray-60 dark:text-gray-10'} + /> + Canary only + </span> + ); +}; -const ExperimentalBadge = ({title}: {title: string}) => ( - <span - title={title} - className={ - 'text-base font-display px-1 py-0.5 font-bold bg-gray-10 dark:bg-gray-60 text-gray-60 dark:text-gray-10 rounded' - }> - <IconExperimental - size="s" - className={'inline me-1 mb-0.5 text-sm text-gray-60 dark:text-gray-10'} - /> - Experimental only - </span> -); +const ExperimentalBadge = ({title}: {title: string}) => { + const isInToc = useContext(IsInTocContext); + if (isInToc) { + return ( + <IconExperimental + size="s" + title={title} + className="inline me-1 mb-0.5 text-gray-60 dark:text-gray-10" + /> + ); + } + return ( + <span + title={title} + className={ + 'text-base font-display px-1 py-0.5 font-bold bg-gray-10 dark:bg-gray-60 text-gray-60 dark:text-gray-10 rounded' + }> + <IconExperimental + size="s" + className={'inline me-1 mb-0.5 text-sm text-gray-60 dark:text-gray-10'} + /> + Experimental only + </span> + ); +}; const NextMajorBadge = ({title}: {title: string}) => ( <span @@ -422,7 +446,11 @@ function InlineToc() { if (root.children.length < 2) { return null; } - return <InlineTocItem items={root.children} />; + return ( + <IsInTocContext.Provider value={true}> + <InlineTocItem items={root.children} /> + </IsInTocContext.Provider> + ); } function InlineTocItem({items}: {items: Array<NestedTocNode>}) { diff --git a/src/components/MDX/Sandpack/DownloadButton.tsx b/src/components/MDX/Sandpack/DownloadButton.tsx index 11b424036a..d14bf25279 100644 --- a/src/components/MDX/Sandpack/DownloadButton.tsx +++ b/src/components/MDX/Sandpack/DownloadButton.tsx @@ -19,7 +19,7 @@ let supportsImportMap = false; function subscribe(cb: () => void) { // This shouldn't actually need to update, but this works around - // https://github.com/facebook/react/issues/26095 + // https://github.com/react/react/issues/26095 let timeout = setTimeout(() => { supportsImportMap = (HTMLScriptElement as any).supports && diff --git a/src/components/MDX/TeamMember.tsx b/src/components/MDX/TeamMember.tsx index 2d0c65537e..2716fa2d1f 100644 --- a/src/components/MDX/TeamMember.tsx +++ b/src/components/MDX/TeamMember.tsx @@ -23,13 +23,49 @@ interface TeamMemberProps { name: string; title: string; permalink: string; - children: React.ReactNode; + children?: React.ReactNode; photo: string; twitter?: string; threads?: string; bsky?: string; github?: string; personal?: string; + // Comma-separated list of working groups. Suffix a group with `*` to mark + // that the member represents it on the Leadership Council, e.g. "Fiber*, DOM". + group?: string; +} + +function GroupBadges({group}: {group: string}) { + const groups = group + .split(',') + .map((g) => g.trim()) + .filter(Boolean); + if (groups.length === 0) { + return null; + } + return ( + <div className="flex flex-row flex-wrap gap-2 my-3"> + {groups.map((g) => { + const isLead = g.endsWith('*'); + const label = isLead ? g.slice(0, -1).trim() : g; + return ( + <span + key={g} + className="inline-flex items-center rounded-full bg-blue-10 dark:bg-gray-80 text-link dark:text-link-dark px-3 py-1 text-sm font-medium whitespace-nowrap"> + {label} + {isLead && ( + <span + className="ps-1 text-yellow-50" + aria-label="Leadership Council" + title="Leadership Council"> + ★ + </span> + )} + </span> + ); + })} + </div> + ); } // TODO: good alt text for images/links @@ -44,12 +80,11 @@ export function TeamMember({ threads, bsky, personal, + group, }: TeamMemberProps) { - if (name == null || title == null || permalink == null || children == null) { + if (name == null || title == null || permalink == null) { const identifier = name ?? title ?? permalink ?? 'unknown'; - throw new Error( - `Expected name, title, permalink, and children for ${identifier}` - ); + throw new Error(`Expected name, title, and permalink for ${identifier}`); } return ( <div className="pb-6 sm:pb-10"> @@ -69,6 +104,7 @@ export function TeamMember({ {name} </H3> {title && <div>{title}</div>} + {group && <GroupBadges group={group} />} {children} <div className="sm:flex sm:flex-row flex-wrap text-secondary dark:text-secondary-dark"> {twitter && ( diff --git a/src/components/MDX/TocContext.tsx b/src/components/MDX/TocContext.tsx index 924e6e09ee..8048953693 100644 --- a/src/components/MDX/TocContext.tsx +++ b/src/components/MDX/TocContext.tsx @@ -20,3 +20,6 @@ export type TocItem = { export type Toc = Array<TocItem>; export const TocContext = createContext<Toc>([]); + +// Lets badge components render compactly when inside the table of contents. +export const IsInTocContext = createContext(false); diff --git a/src/content/blog/2021/06/08/the-plan-for-react-18.md b/src/content/blog/2021/06/08/the-plan-for-react-18.md index 72e2a57cd8..a4556722fa 100644 --- a/src/content/blog/2021/06/08/the-plan-for-react-18.md +++ b/src/content/blog/2021/06/08/the-plan-for-react-18.md @@ -51,7 +51,11 @@ React 团队很高兴分享一些更新: 我们预计对工作组感兴趣的人数会激增,所以目前只允许被邀请的成员可以创建或评论主题。不过,这些过程是完全公开的,所以每个人都能得到一致的信息。我们相信这是一个很好的折衷方案,既能为工作组的成员创造一个利于工作的环境,又能保持对广大社区的开放性。 +<<<<<<< HEAD 和从前一样,你仍然可以在我们的 [issue](https://github.com/facebook/react/issues) 中发布错误报告、疑难问题和反馈等信息。 +======= +As always, you can submit bug reports, questions, and general feedback to our [issue tracker](https://github.com/react/react/issues). +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ## 如何体验 React 18 alpha {/*how-to-try-react-18-alpha-today*/} diff --git a/src/content/blog/2022/03/08/react-18-upgrade-guide.md b/src/content/blog/2022/03/08/react-18-upgrade-guide.md index 05d834e454..cfa936a987 100644 --- a/src/content/blog/2022/03/08/react-18-upgrade-guide.md +++ b/src/content/blog/2022/03/08/react-18-upgrade-guide.md @@ -13,7 +13,11 @@ description: 正如我们在发布报告中分享的那样,React 18 借助新 正如我们在 [发布报告](/blog/2022/03/29/react-v18) 中分享的那样,React 18 借助新的并发渲染引入了许多新特性,对于已经存在的应用可以采用渐进式策略。在这篇文章中,我们会指导你如何逐步升级到 React 18。 +<<<<<<< HEAD 如果你在升级的过程中遇到任何问题,可以在 GitHub [提 issue](https://github.com/facebook/react/issues/new/choose)。 +======= +Please [report any issues](https://github.com/react/react/issues/new/choose) you encounter while upgrading to React 18. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </Intro> @@ -315,11 +319,19 @@ globalThis.IS_REACT_ACT_ENVIRONMENT = true; ### React {/*react*/} +<<<<<<< HEAD * **组件现在可以渲染 `undefined`**:如果你从组件返回 `undefined`,React 不会再发出告警。这使得允许的组件返回值与组件树中间允许的值能够保持一致。我们建议使用代码检查工具来防止像忘记在 JSX 前面的 `return` 语句这样的错误。 * **在测试中,`act` 告警现在是可选的**:如果你正在运行端对端的测试,`act` 告警是非必要的。我们已经引入了一个 [可选](https://github.com/reactwg/react-18/discussions/102) 机制,这样你就可以只在有用且有益的单元测试开启它们。 * **未加载的组件取消了关于 `setState` 的告警**:之前每当你在未加载的组件中调用 `setState`,React 就会发出内存泄漏告警。这个告警是为订阅添加的,但是人们经常在设置状态完好遇见它并且解决方法会让代码变得更加糟糕。所以我们已经 [移除](https://github.com/facebook/react/pull/22114) 了这个告警。 * **不抑制控制台打印**:当你使用 Strict Mode 时,React 会将每个组件渲染两次来帮助你找到不符合预期的副作用。在 React 17 中,我们已经抑制了两次渲染之一的控制台打印让其更容易阅读。为了响应关于这会令人难以理解的 [社区反馈](https://github.com/facebook/react/issues/21783),我们移除了这个抑制。取而代之的是,如果你安装了 React DevTool,第二次记录的渲染将会以灰色的文字展示并且会有一个选项(默认关闭)来抑制它们。 * **改进了内存使用**:React 现在在卸载的时候会清理更多内部区域,这使得可能存在于应用代码中的未修复内存泄露的影响不那么严重。 +======= +* **Components can now render `undefined`:** React no longer warns if you return `undefined` from a component. This makes the allowed component return values consistent with values that are allowed in the middle of a component tree. We suggest to use a linter to prevent mistakes like forgetting a `return` statement before JSX. +* **In tests, `act` warnings are now opt-in:** If you're running end-to-end tests, the `act` warnings are unnecessary. We've introduced an [opt-in](https://github.com/reactwg/react-18/discussions/102) mechanism so you can enable them only for unit tests where they are useful and beneficial. +* **No warning about `setState` on unmounted components:** Previously, React warned about memory leaks when you call `setState` on an unmounted component. This warning was added for subscriptions, but people primarily run into it in scenarios where setting state is fine, and workarounds make the code worse. We've [removed](https://github.com/react/react/pull/22114) this warning. +* **No suppression of console logs:** When you use Strict Mode, React renders each component twice to help you find unexpected side effects. In React 17, we've suppressed console logs for one of the two renders to make the logs easier to read. In response to [community feedback](https://github.com/react/react/issues/21783) about this being confusing, we've removed the suppression. Instead, if you have React DevTools installed, the second log's renders will be displayed in grey, and there will be an option (off by default) to suppress them completely. +* **Improved memory usage:** React now cleans up more internal fields on unmount, making the impact from unfixed memory leaks that may exist in your application code less severe. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ### React DOM Server {/*react-dom-server*/} @@ -328,4 +340,8 @@ globalThis.IS_REACT_ACT_ENVIRONMENT = true; ## 更新日志 {/*changelog*/} +<<<<<<< HEAD 你可以在这里查看 [完整更新日志](https://github.com/facebook/react/blob/main/CHANGELOG.md)。 +======= +You can view the [full changelog here](https://github.com/react/react/blob/main/CHANGELOG.md). +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca diff --git a/src/content/blog/2022/03/29/react-v18.md b/src/content/blog/2022/03/29/react-v18.md index 542bf8a5c6..2aedc46551 100644 --- a/src/content/blog/2022/03/29/react-v18.md +++ b/src/content/blog/2022/03/29/react-v18.md @@ -270,6 +270,7 @@ React 18 中的 Suspense 在与 Transition API 结合时效果最好。如果你 ### React {/*react*/} +<<<<<<< HEAD * 添加 `useTransition` 和 `useDeferredValue` 以将紧急更新和过渡更新分开。([#10426](https://github.com/facebook/react/pull/10426),[#10715](https://github.com/facebook/react/pull/10715),[#15593](https://github.com/facebook/react/pull/15593),[#15272](https://github.com/facebook/react/pull/15272),[#15578](https://github.com/facebook/react/pull/15578),[#15769](https://github.com/facebook/react/pull/15769),[#17058](https://github.com/facebook/react/pull/17058),[#18796](https://github.com/facebook/react/pull/18796),[#19121](https://github.com/facebook/react/pull/19121),[#19703](https://github.com/facebook/react/pull/19703),[#19719](https://github.com/facebook/react/pull/19719),[#19724](https://github.com/facebook/react/pull/19724),[#20672](https://github.com/facebook/react/pull/20672),[#20976](https://github.com/facebook/react/pull/20976) [@acdlite](https://github.com/acdlite),[@lunaruan](https://github.com/lunaruan),[@rickhanlonii](https://github.com/rickhanlonii),and [@sebmarkbage](https://github.com/sebmarkbage)) * 添加 `useId` 用于生成唯一 ID。([#17322](https://github.com/facebook/react/pull/17322),[#18576](https://github.com/facebook/react/pull/18576),[#22644](https://github.com/facebook/react/pull/22644),[#22672](https://github.com/facebook/react/pull/22672),[#21260](https://github.com/facebook/react/pull/21260) [@acdlite](https://github.com/acdlite),[@lunaruan](https://github.com/lunaruan),and [@sebmarkbage](https://github.com/sebmarkbage)) * 添加 `useSyncExternalStore` 以帮助外部存储库与 React 集成。([#15022](https://github.com/facebook/react/pull/15022),[#18000](https://github.com/facebook/react/pull/18000),[#18771](https://github.com/facebook/react/pull/18771),[#22211](https://github.com/facebook/react/pull/22211),[#22292](https://github.com/facebook/react/pull/22292),[#22239](https://github.com/facebook/react/pull/22239),[#22347](https://github.com/facebook/react/pull/22347),[#23150](https://github.com/facebook/react/pull/23150) [@acdlite](https://github.com/acdlite),[@bvaughn](https://github.com/bvaughn),and [@drarmstr](https://github.com/drarmstr)) @@ -334,11 +335,85 @@ React 18 中的 Suspense 在与 Transition API 结合时效果最好。如果你 * 在快速刷新中跟踪后期装载的 root。([#22740](https://github.com/facebook/react/pull/22740) [@anc95](https://github.com/anc95)) * 在 `package.json` 中添加 `exports` 字段。([#23087](https://github.com/facebook/react/pull/23087) [@otakustay](https://github.com/otakustay)) +======= +* Add `useTransition` and `useDeferredValue` to separate urgent updates from transitions. ([#10426](https://github.com/react/react/pull/10426), [#10715](https://github.com/react/react/pull/10715), [#15593](https://github.com/react/react/pull/15593), [#15272](https://github.com/react/react/pull/15272), [#15578](https://github.com/react/react/pull/15578), [#15769](https://github.com/react/react/pull/15769), [#17058](https://github.com/react/react/pull/17058), [#18796](https://github.com/react/react/pull/18796), [#19121](https://github.com/react/react/pull/19121), [#19703](https://github.com/react/react/pull/19703), [#19719](https://github.com/react/react/pull/19719), [#19724](https://github.com/react/react/pull/19724), [#20672](https://github.com/react/react/pull/20672), [#20976](https://github.com/react/react/pull/20976) by [@acdlite](https://github.com/acdlite), [@lunaruan](https://github.com/lunaruan), [@rickhanlonii](https://github.com/rickhanlonii), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `useId` for generating unique IDs. ([#17322](https://github.com/react/react/pull/17322), [#18576](https://github.com/react/react/pull/18576), [#22644](https://github.com/react/react/pull/22644), [#22672](https://github.com/react/react/pull/22672), [#21260](https://github.com/react/react/pull/21260) by [@acdlite](https://github.com/acdlite), [@lunaruan](https://github.com/lunaruan), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `useSyncExternalStore` to help external store libraries integrate with React. ([#15022](https://github.com/react/react/pull/15022), [#18000](https://github.com/react/react/pull/18000), [#18771](https://github.com/react/react/pull/18771), [#22211](https://github.com/react/react/pull/22211), [#22292](https://github.com/react/react/pull/22292), [#22239](https://github.com/react/react/pull/22239), [#22347](https://github.com/react/react/pull/22347), [#23150](https://github.com/react/react/pull/23150) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), and [@drarmstr](https://github.com/drarmstr)) +* Add `startTransition` as a version of `useTransition` without pending feedback. ([#19696](https://github.com/react/react/pull/19696) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Add `useInsertionEffect` for CSS-in-JS libraries. ([#21913](https://github.com/react/react/pull/21913) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Make Suspense remount layout effects when content reappears. ([#19322](https://github.com/react/react/pull/19322), [#19374](https://github.com/react/react/pull/19374), [#19523](https://github.com/react/react/pull/19523), [#20625](https://github.com/react/react/pull/20625), [#21079](https://github.com/react/react/pull/21079) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), and [@lunaruan](https://github.com/lunaruan)) +* Make `<StrictMode>` re-run effects to check for restorable state. ([#19523](https://github.com/react/react/pull/19523) , [#21418](https://github.com/react/react/pull/21418) by [@bvaughn](https://github.com/bvaughn) and [@lunaruan](https://github.com/lunaruan)) +* Assume Symbols are always available. ([#23348](https://github.com/react/react/pull/23348) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Remove `object-assign` polyfill. ([#23351](https://github.com/react/react/pull/23351) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Remove unsupported `unstable_changedBits` API. ([#20953](https://github.com/react/react/pull/20953) by [@acdlite](https://github.com/acdlite)) +* Allow components to render undefined. ([#21869](https://github.com/react/react/pull/21869) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Flush `useEffect` resulting from discrete events like clicks synchronously. ([#21150](https://github.com/react/react/pull/21150) by [@acdlite](https://github.com/acdlite)) +* Suspense `fallback={undefined}` now behaves the same as `null` and isn't ignored. ([#21854](https://github.com/react/react/pull/21854) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Consider all `lazy()` resolving to the same component equivalent. ([#20357](https://github.com/react/react/pull/20357) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Don't patch console during first render. ([#22308](https://github.com/react/react/pull/22308) by [@lunaruan](https://github.com/lunaruan)) +* Improve memory usage. ([#21039](https://github.com/react/react/pull/21039) by [@bgirard](https://github.com/bgirard)) +* Improve messages if string coercion throws (Temporal.*, Symbol, etc.) ([#22064](https://github.com/react/react/pull/22064) by [@justingrant](https://github.com/justingrant)) +* Use `setImmediate` when available over `MessageChannel`. ([#20834](https://github.com/react/react/pull/20834) by [@gaearon](https://github.com/gaearon)) +* Fix context failing to propagate inside suspended trees. ([#23095](https://github.com/react/react/pull/23095) by [@gaearon](https://github.com/gaearon)) +* Fix `useReducer` observing incorrect props by removing the eager bailout mechanism. ([#22445](https://github.com/react/react/pull/22445) by [@josephsavona](https://github.com/josephsavona)) +* Fix `setState` being ignored in Safari when appending iframes. ([#23111](https://github.com/react/react/pull/23111) by [@gaearon](https://github.com/gaearon)) +* Fix a crash when rendering `ZonedDateTime` in the tree. ([#20617](https://github.com/react/react/pull/20617) by [@dimaqq](https://github.com/dimaqq)) +* Fix a crash when document is set to `null` in tests. ([#22695](https://github.com/react/react/pull/22695) by [@SimenB](https://github.com/SimenB)) +* Fix `onLoad` not triggering when concurrent features are on. ([#23316](https://github.com/react/react/pull/23316) by [@gnoff](https://github.com/gnoff)) +* Fix a warning when a selector returns `NaN`. ([#23333](https://github.com/react/react/pull/23333) by [@hachibeeDI](https://github.com/hachibeeDI)) +* Fix a crash when document is set to `null` in tests. ([#22695](https://github.com/react/react/pull/22695) by [@SimenB](https://github.com/SimenB)) +* Fix the generated license header. ([#23004](https://github.com/react/react/pull/23004) by [@vitaliemiron](https://github.com/vitaliemiron)) +* Add `package.json` as one of the entry points. ([#22954](https://github.com/react/react/pull/22954) by [@Jack](https://github.com/Jack-Works)) +* Allow suspending outside a Suspense boundary. ([#23267](https://github.com/react/react/pull/23267) by [@acdlite](https://github.com/acdlite)) +* Log a recoverable error whenever hydration fails. ([#23319](https://github.com/react/react/pull/23319) by [@acdlite](https://github.com/acdlite)) + +### React DOM {/*react-dom*/} + +* Add `createRoot` and `hydrateRoot`. ([#10239](https://github.com/react/react/pull/10239), [#11225](https://github.com/react/react/pull/11225), [#12117](https://github.com/react/react/pull/12117), [#13732](https://github.com/react/react/pull/13732), [#15502](https://github.com/react/react/pull/15502), [#15532](https://github.com/react/react/pull/15532), [#17035](https://github.com/react/react/pull/17035), [#17165](https://github.com/react/react/pull/17165), [#20669](https://github.com/react/react/pull/20669), [#20748](https://github.com/react/react/pull/20748), [#20888](https://github.com/react/react/pull/20888), [#21072](https://github.com/react/react/pull/21072), [#21417](https://github.com/react/react/pull/21417), [#21652](https://github.com/react/react/pull/21652), [#21687](https://github.com/react/react/pull/21687), [#23207](https://github.com/react/react/pull/23207), [#23385](https://github.com/react/react/pull/23385) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), [@gaearon](https://github.com/gaearon), [@lunaruan](https://github.com/lunaruan), [@rickhanlonii](https://github.com/rickhanlonii), [@trueadm](https://github.com/trueadm), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add selective hydration. ([#14717](https://github.com/react/react/pull/14717), [#14884](https://github.com/react/react/pull/14884), [#16725](https://github.com/react/react/pull/16725), [#16880](https://github.com/react/react/pull/16880), [#17004](https://github.com/react/react/pull/17004), [#22416](https://github.com/react/react/pull/22416), [#22629](https://github.com/react/react/pull/22629), [#22448](https://github.com/react/react/pull/22448), [#22856](https://github.com/react/react/pull/22856), [#23176](https://github.com/react/react/pull/23176) by [@acdlite](https://github.com/acdlite), [@gaearon](https://github.com/gaearon), [@salazarm](https://github.com/salazarm), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `aria-description` to the list of known ARIA attributes. ([#22142](https://github.com/react/react/pull/22142) by [@mahyareb](https://github.com/mahyareb)) +* Add `onResize` event to video elements. ([#21973](https://github.com/react/react/pull/21973) by [@rileyjshaw](https://github.com/rileyjshaw)) +* Add `imageSizes` and `imageSrcSet` to known props. ([#22550](https://github.com/react/react/pull/22550) by [@eps1lon](https://github.com/eps1lon)) +* Allow non-string `<option>` children if `value` is provided. ([#21431](https://github.com/react/react/pull/21431) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix `aspectRatio` style not being applied. ([#21100](https://github.com/react/react/pull/21100) by [@gaearon](https://github.com/gaearon)) +* Warn if `renderSubtreeIntoContainer` is called. ([#23355](https://github.com/react/react/pull/23355) by [@acdlite](https://github.com/acdlite)) + +### React DOM Server {/*react-dom-server-1*/} + +* Add the new streaming renderer. ([#14144](https://github.com/react/react/pull/14144), [#20970](https://github.com/react/react/pull/20970), [#21056](https://github.com/react/react/pull/21056), [#21255](https://github.com/react/react/pull/21255), [#21200](https://github.com/react/react/pull/21200), [#21257](https://github.com/react/react/pull/21257), [#21276](https://github.com/react/react/pull/21276), [#22443](https://github.com/react/react/pull/22443), [#22450](https://github.com/react/react/pull/22450), [#23247](https://github.com/react/react/pull/23247), [#24025](https://github.com/react/react/pull/24025), [#24030](https://github.com/react/react/pull/24030) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix context providers in SSR when handling multiple requests. ([#23171](https://github.com/react/react/pull/23171) by [@frandiox](https://github.com/frandiox)) +* Revert to client render on text mismatch. ([#23354](https://github.com/react/react/pull/23354) by [@acdlite](https://github.com/acdlite)) +* Deprecate `renderToNodeStream`. ([#23359](https://github.com/react/react/pull/23359) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix a spurious error log in the new server renderer. ([#24043](https://github.com/react/react/pull/24043) by [@eps1lon](https://github.com/eps1lon)) +* Fix a bug in the new server renderer. ([#22617](https://github.com/react/react/pull/22617) by [@shuding](https://github.com/shuding)) +* Ignore function and symbol values inside custom elements on the server. ([#21157](https://github.com/react/react/pull/21157) by [@sebmarkbage](https://github.com/sebmarkbage)) + +### React DOM Test Utils {/*react-dom-test-utils*/} + +* Throw when `act` is used in production. ([#21686](https://github.com/react/react/pull/21686) by [@acdlite](https://github.com/acdlite)) +* Support disabling spurious act warnings with `global.IS_REACT_ACT_ENVIRONMENT`. ([#22561](https://github.com/react/react/pull/22561) by [@acdlite](https://github.com/acdlite)) +* Expand act warning to cover all APIs that might schedule React work. ([#22607](https://github.com/react/react/pull/22607) by [@acdlite](https://github.com/acdlite)) +* Make `act` batch updates. ([#21797](https://github.com/react/react/pull/21797) by [@acdlite](https://github.com/acdlite)) +* Remove warning for dangling passive effects. ([#22609](https://github.com/react/react/pull/22609) by [@acdlite](https://github.com/acdlite)) + +### React Refresh {/*react-refresh*/} + +* Track late-mounted roots in Fast Refresh. ([#22740](https://github.com/react/react/pull/22740) by [@anc95](https://github.com/anc95)) +* Add `exports` field to `package.json`. ([#23087](https://github.com/react/react/pull/23087) by [@otakustay](https://github.com/otakustay)) +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ### 实验性的服务器组件 {/*server-components-experimental*/} +<<<<<<< HEAD * 增加服务端上下文支持。([#23244](https://github.com/facebook/react/pull/23244) [@salazarm](https://github.com/salazarm)) * 增加对 `lazy` 的支持。 ([#24068](https://github.com/facebook/react/pull/24068) [@gnoff](https://github.com/gnoff)) * 更新 webpack 插件以支持 webpack 5。([#22739](https://github.com/facebook/react/pull/22739) [@michenly](https://github.com/michenly)) * 修正 Noder loader 中的一个错误。([#22537](https://github.com/facebook/react/pull/22537) [@btea](https://github.com/btea)) * 在边缘环境中使用 `globalThis` 而不是 `window`。([#22777](https://github.com/facebook/react/pull/22777) [@huozhi](https://github.com/huozhi)) +======= +* Add Server Context support. ([#23244](https://github.com/react/react/pull/23244) by [@salazarm](https://github.com/salazarm)) +* Add `lazy` support. ([#24068](https://github.com/react/react/pull/24068) by [@gnoff](https://github.com/gnoff)) +* Update webpack plugin for webpack 5 ([#22739](https://github.com/react/react/pull/22739) by [@michenly](https://github.com/michenly)) +* Fix a mistake in the Node loader. ([#22537](https://github.com/react/react/pull/22537) by [@btea](https://github.com/btea)) +* Use `globalThis` instead of `window` for edge environments. ([#22777](https://github.com/react/react/pull/22777) by [@huozhi](https://github.com/huozhi)) +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca diff --git a/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md b/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md index 62a2f8985d..9c8a93a5c6 100644 --- a/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md +++ b/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md @@ -70,7 +70,11 @@ description: React 18 经过多年的努力才得以问世,它为 React 团队 我们意识到,开发人员并不认为单独了解缓慢的提交或组件是有用的,更有用的是了解导致缓慢提交的实际原因。开发人员希望能够跟踪特定的交互(例如按钮点击、初始加载或页面导航),以便观察性能回归,并理解为什么交互缓慢以及如何修复它。 +<<<<<<< HEAD 我们之前尝试通过创建一个 [交互追踪 API](https://gist.github.com/bvaughn/8de925562903afd2e7a12554adcdda16) 来解决这个问题,但它存在一些基本的设计缺陷,降低了追踪交互缓慢原因的准确性,有时导致交互永远无法结束。由于这些问题,我们最终 [移除了这个API](https://github.com/facebook/react/pull/20037)。 +======= +We previously tried to solve this issue by creating an [Interaction Tracing API](https://gist.github.com/bvaughn/8de925562903afd2e7a12554adcdda16), but it had some fundamental design flaws that reduced the accuracy of tracking why an interaction was slow and sometimes resulted in interactions never ending. We ended up [removing this API](https://github.com/react/react/pull/20037) because of these issues. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca 我们正在开发一个新版本的交互追踪 API(由于它通过 `startTransition` 发起,我们将其暂时称为 Transition Tracing),来解决这些问题。 diff --git a/src/content/blog/2024/04/25/react-19-upgrade-guide.md b/src/content/blog/2024/04/25/react-19-upgrade-guide.md index 1823ebdfbd..6f918a2a97 100644 --- a/src/content/blog/2024/04/25/react-19-upgrade-guide.md +++ b/src/content/blog/2024/04/25/react-19-upgrade-guide.md @@ -24,7 +24,7 @@ To help make the upgrade to React 19 easier, we've published a `react@18.3` rele We recommend upgrading to React 18.3 first to help identify any issues before upgrading to React 19. -For a list of changes in 18.3 see the [Release Notes](https://github.com/facebook/react/blob/main/CHANGELOG.md#1830-april-25-2024). +For a list of changes in 18.3 see the [Release Notes](https://github.com/react/react/blob/main/CHANGELOG.md#1830-april-25-2024). </Note> @@ -38,7 +38,7 @@ In this post, we will guide you through the steps for upgrading to React 19: - [TypeScript changes](#typescript-changes) - [Changelog](#changelog) -If you'd like to help us test React 19, follow the steps in this upgrade guide and [report any issues](https://github.com/facebook/react/issues/new?assignees=&labels=React+19&projects=&template=19.md&title=%5BReact+19%5D) you encounter. For a list of new features added to React 19, see the [React 19 release post](/blog/2024/12/05/react-19). +If you'd like to help us test React 19, follow the steps in this upgrade guide and [report any issues](https://github.com/react/react/issues/new?assignees=&labels=React+19&projects=&template=19.md&title=%5BReact+19%5D) you encounter. For a list of new features added to React 19, see the [React 19 release post](/blog/2024/12/05/react-19). --- ## Installing {/*installing*/} @@ -256,7 +256,7 @@ class Child extends React.Component { #### Removed: string refs {/*removed-string-refs*/} String refs were deprecated in [March, 2018 (v16.3.0)](https://legacy.reactjs.org/blog/2018/03/27/update-on-async-rendering.html). -Class components supported string refs before being replaced by ref callbacks due to [multiple downsides](https://github.com/facebook/react/issues/1373). In React 19, we're removing string refs to make React simpler and easier to understand. +Class components supported string refs before being replaced by ref callbacks due to [multiple downsides](https://github.com/react/react/issues/1373). In React 19, we're removing string refs to make React simpler and easier to understand. If you're still using string refs in class components, you'll need to migrate to ref callbacks: @@ -730,24 +730,24 @@ const reducer = (state: State, action: Action) => state; ### Other breaking changes {/*other-breaking-changes*/} -- **react-dom**: Error for javascript URLs in `src` and `href` [#26507](https://github.com/facebook/react/pull/26507) -- **react-dom**: Remove `errorInfo.digest` from `onRecoverableError` [#28222](https://github.com/facebook/react/pull/28222) -- **react-dom**: Remove `unstable_flushControlled` [#26397](https://github.com/facebook/react/pull/26397) -- **react-dom**: Remove `unstable_createEventHandle` [#28271](https://github.com/facebook/react/pull/28271) -- **react-dom**: Remove `unstable_renderSubtreeIntoContainer` [#28271](https://github.com/facebook/react/pull/28271) -- **react-dom**: Remove `unstable_runWithPriority` [#28271](https://github.com/facebook/react/pull/28271) -- **react-is**: Remove deprecated methods from `react-is` [28224](https://github.com/facebook/react/pull/28224) +- **react-dom**: Error for javascript URLs in `src` and `href` [#26507](https://github.com/react/react/pull/26507) +- **react-dom**: Remove `errorInfo.digest` from `onRecoverableError` [#28222](https://github.com/react/react/pull/28222) +- **react-dom**: Remove `unstable_flushControlled` [#26397](https://github.com/react/react/pull/26397) +- **react-dom**: Remove `unstable_createEventHandle` [#28271](https://github.com/react/react/pull/28271) +- **react-dom**: Remove `unstable_renderSubtreeIntoContainer` [#28271](https://github.com/react/react/pull/28271) +- **react-dom**: Remove `unstable_runWithPriority` [#28271](https://github.com/react/react/pull/28271) +- **react-is**: Remove deprecated methods from `react-is` [28224](https://github.com/react/react/pull/28224) ### Other notable changes {/*other-notable-changes*/} -- **react**: Batch sync, default and continuous lanes [#25700](https://github.com/facebook/react/pull/25700) -- **react**: Don't prerender siblings of suspended component [#26380](https://github.com/facebook/react/pull/26380) -- **react**: Detect infinite update loops caused by render phase updates [#26625](https://github.com/facebook/react/pull/26625) -- **react-dom**: Transitions in popstate are now synchronous [#26025](https://github.com/facebook/react/pull/26025) -- **react-dom**: Remove layout effect warning during SSR [#26395](https://github.com/facebook/react/pull/26395) -- **react-dom**: Warn and don’t set empty string for src/href (except anchor tags) [#28124](https://github.com/facebook/react/pull/28124) +- **react**: Batch sync, default and continuous lanes [#25700](https://github.com/react/react/pull/25700) +- **react**: Don't prerender siblings of suspended component [#26380](https://github.com/react/react/pull/26380) +- **react**: Detect infinite update loops caused by render phase updates [#26625](https://github.com/react/react/pull/26625) +- **react-dom**: Transitions in popstate are now synchronous [#26025](https://github.com/react/react/pull/26025) +- **react-dom**: Remove layout effect warning during SSR [#26395](https://github.com/react/react/pull/26395) +- **react-dom**: Warn and don’t set empty string for src/href (except anchor tags) [#28124](https://github.com/react/react/pull/28124) -For a full list of changes, please see the [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md#1900-december-5-2024). +For a full list of changes, please see the [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md#1900-december-5-2024). --- diff --git a/src/content/blog/2024/05/22/react-conf-2024-recap.md b/src/content/blog/2024/05/22/react-conf-2024-recap.md index 73fde1ba16..9dfb285b43 100644 --- a/src/content/blog/2024/05/22/react-conf-2024-recap.md +++ b/src/content/blog/2024/05/22/react-conf-2024-recap.md @@ -45,7 +45,11 @@ _[点击这里观看第一天完整直播。](https://www.youtube.com/watch?v=T8 - [为两台计算机设计的 React](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=18825s),来自 [Dan Abramov](https://bsky.app/profile/danabra.mov) - [现在你应该理解了 React 服务器组件](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=11256s),来自 [Kent C. Dodds](https://twitter.com/kentcdodds) +<<<<<<< HEAD 最后,[Joe Savona](https://twitter.com/en_JS)、[Sathya Gunasekaran](https://twitter.com/_gsathya) 和 [Mofei Zhang](https://twitter.com/zmofei) 宣布 React 编译器现已 [开源](https://github.com/facebook/react/pull/29061),并分享了一个实验版本的 React 编译器供大家尝试。 +======= +Finally, we ended the keynote with [Joe Savona](https://twitter.com/en_JS), [Sathya Gunasekaran](https://twitter.com/_gsathya), and [Mofei Zhang](https://twitter.com/zmofei) announcing that the React Compiler is now [Open Source](https://github.com/react/react/pull/29061), and sharing an experimental version of the React Compiler to try out. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca 有关使用 React Compile 及其工作原理的更多信息,请查看[文档](/learn/react-compiler)以及如下演讲: diff --git a/src/content/blog/2024/10/21/react-compiler-beta-release.md b/src/content/blog/2024/10/21/react-compiler-beta-release.md index 89502ff71a..0851a55fdc 100644 --- a/src/content/blog/2024/10/21/react-compiler-beta-release.md +++ b/src/content/blog/2024/10/21/react-compiler-beta-release.md @@ -92,7 +92,11 @@ React 编译器还可以用来编译库。由于 React 编译器需要在代码 我们之前在 React Conf 上宣布成立邀请制的 [React 编译器工作组](https://github.com/reactwg/react-compiler) 以提供反馈、提出问题并就编译器的实验版本进行协作。 +<<<<<<< HEAD 从今天开始,随着 React 编译器的测试版发布,我们向所有人开放工作组成员资格。React 编译器工作组的目标是为生态系统做好准备,以便现有应用程序和库顺利、逐步采用 React 编译器。请继续在 [React 仓库中](https://github.com/facebook/react) 提交错误报告,但是在 [工作组论坛](https://github.com/reactwg/react-compiler/discussions) 中留下反馈、提出问题或分享想法。 +======= +From today, together with the Beta release of React Compiler, we are opening up Working Group membership to everyone. The goal of the React Compiler Working Group is to prepare the ecosystem for a smooth, gradual adoption of React Compiler by existing applications and libraries. Please continue to file bug reports in the [React repo](https://github.com/react/react), but please leave feedback, ask questions, or share ideas in the [Working Group discussion forum](https://github.com/reactwg/react-compiler/discussions). +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca 核心团队还将使用工作组论坛来分享我们的研究成果。随着稳定版本的临近,任何重要信息也将发布在该论坛上。 @@ -127,7 +131,11 @@ React 编译器还可以用来编译库。由于 React 编译器需要在代码 --- +<<<<<<< HEAD [^1]: 感谢 [@nikeee](https://github.com/facebook/react/pulls?q=is%3Apr+author%3Anikeee)、[@henryqdineen](https://github.com/facebook/react/pulls?q=is%3Apr+author%3Ahenryqdineen)、[@TrickyPi](https://github.com/facebook/react/pulls?q=is%3Apr+author%3ATrickyPi) 以及其他为编译器做出贡献的人。 +======= +[^1]: Thanks [@nikeee](https://github.com/react/react/pulls?q=is%3Apr+author%3Anikeee), [@henryqdineen](https://github.com/react/react/pulls?q=is%3Apr+author%3Ahenryqdineen), [@TrickyPi](https://github.com/react/react/pulls?q=is%3Apr+author%3ATrickyPi), and several others for their contributions to the compiler. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca [^2]: 感谢 [Vaishali Garg](https://www.linkedin.com/in/vaishaligarg09) 在 Meta 领导这项关于 React 编译器的研究,并审阅这篇文章。 diff --git a/src/content/blog/2024/12/05/react-19.md b/src/content/blog/2024/12/05/react-19.md index 78b6dbacd5..ae229a1c37 100644 --- a/src/content/blog/2024/12/05/react-19.md +++ b/src/content/blog/2024/12/05/react-19.md @@ -182,7 +182,11 @@ const [error, submitAction, isPending] = useActionState( `React.useActionState` 在 Canary 版本中曾被称为 `ReactDOM.useFormState`,但我们已经将其重命名并弃用了 `useFormState`。 +<<<<<<< HEAD 有关更多信息,请参见 [#28491](https://github.com/facebook/react/pull/28491)。 +======= +See [#28491](https://github.com/react/react/pull/28491) for more info. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </Note> diff --git a/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md b/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md index 90674e88fa..fb9d9a8c31 100644 --- a/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md +++ b/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md @@ -11458,7 +11458,11 @@ root.render( 如果你想了解更多关于它们如何工作的信息,请查看文档中的[`<ViewTransition>` 如何工作](/reference/react/ViewTransition#how-does-viewtransition-work)。 +<<<<<<< HEAD **关于我们如何构建视图过渡的更多背景信息,请参阅:[#31975](https://github.com/facebook/react/pull/31975)、[#32105](https://github.com/facebook/react/pull/32105)、[#32041](https://github.com/facebook/react/pull/32041)、[#32734](https://github.com/facebook/react/pull/32734)、[#32797](https://github.com/facebook/react/pull/32797)、[#31999](https://github.com/facebook/react/pull/31999)、[#32031](https://github.com/facebook/react/pull/32031)、[#32050](https://github.com/facebook/react/pull/32050)、[#32820](https://github.com/facebook/react/pull/32820)、[#32029](https://github.com/facebook/react/pull/32029)、[#32028](https://github.com/facebook/react/pull/32028) 和 [#32038](https://github.com/facebook/react/pull/32038),由 [@sebmarkbage](https://twitter.com/sebmarkbage) 完成(感谢 Seb!)。** +======= +_For more background on how we built View Transitions, see: [#31975](https://github.com/react/react/pull/31975), [#32105](https://github.com/react/react/pull/32105), [#32041](https://github.com/react/react/pull/32041), [#32734](https://github.com/react/react/pull/32734), [#32797](https://github.com/react/react/pull/32797) [#31999](https://github.com/react/react/pull/31999), [#32031](https://github.com/react/react/pull/32031), [#32050](https://github.com/react/react/pull/32050), [#32820](https://github.com/react/react/pull/32820), [#32029](https://github.com/react/react/pull/32029), [#32028](https://github.com/react/react/pull/32028), and [#32038](https://github.com/react/react/pull/32038) by [@sebmarkbage](https://twitter.com/sebmarkbage) (thanks Seb!)._ +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca --- diff --git a/src/content/blog/2025/10/01/react-19-2.md b/src/content/blog/2025/10/01/react-19-2.md index 51c30f70a2..feee6cdb62 100644 --- a/src/content/blog/2025/10/01/react-19-2.md +++ b/src/content/blog/2025/10/01/react-19-2.md @@ -300,7 +300,7 @@ To continue using the legacy config, you can change to `recommended-legacy`: For a full list of compiler enabled rules, [check out the linter docs](/reference/eslint-plugin-react-hooks#recommended). -Check out the `eslint-plugin-react-hooks` [changelog for a full list of changes](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md#610). +Check out the `eslint-plugin-react-hooks` [changelog for a full list of changes](https://github.com/react/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md#610). --- @@ -315,23 +315,23 @@ The original intent of using a special character that was not valid for CSS sele ## Changelog {/*changelog*/} Other notable changes -- `react-dom`: Allow nonce to be used on hoistable styles [#32461](https://github.com/facebook/react/pull/32461) -- `react-dom`: Warn for using a React owned node as a Container if it also has text content [#32774](https://github.com/facebook/react/pull/32774) +- `react-dom`: Allow nonce to be used on hoistable styles [#32461](https://github.com/react/react/pull/32461) +- `react-dom`: Warn for using a React owned node as a Container if it also has text content [#32774](https://github.com/react/react/pull/32774) Notable bug fixes -- `react`: Stringify context as "SomeContext" instead of "SomeContext.Provider" [#33507](https://github.com/facebook/react/pull/33507) -- `react`: Fix infinite useDeferredValue loop in popstate event [#32821](https://github.com/facebook/react/pull/32821) -- `react`: Fix a bug when an initial value was passed to useDeferredValue [#34376](https://github.com/facebook/react/pull/34376) -- `react`: Fix a crash when submitting forms with Client Actions [#33055](https://github.com/facebook/react/pull/33055) -- `react`: Hide/unhide the content of dehydrated suspense boundaries if they resuspend [#32900](https://github.com/facebook/react/pull/32900) -- `react`: Avoid stack overflow on wide trees during Hot Reload [#34145](https://github.com/facebook/react/pull/34145) -- `react`: Improve component stacks in various places [#33629](https://github.com/facebook/react/pull/33629), [#33724](https://github.com/facebook/react/pull/33724), [#32735](https://github.com/facebook/react/pull/32735), [#33723](https://github.com/facebook/react/pull/33723) -- `react`: Fix a bug with React.use inside React.lazy-ed Component [#33941](https://github.com/facebook/react/pull/33941) -- `react-dom`: Stop warning when ARIA 1.3 attributes are used [#34264](https://github.com/facebook/react/pull/34264) -- `react-dom`: Fix a bug with deeply nested Suspense inside Suspense fallbacks [#33467](https://github.com/facebook/react/pull/33467) -- `react-dom`: Avoid hanging when suspending after aborting while rendering [#34192](https://github.com/facebook/react/pull/34192) - -For a full list of changes, please see the [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md). +- `react`: Stringify context as "SomeContext" instead of "SomeContext.Provider" [#33507](https://github.com/react/react/pull/33507) +- `react`: Fix infinite useDeferredValue loop in popstate event [#32821](https://github.com/react/react/pull/32821) +- `react`: Fix a bug when an initial value was passed to useDeferredValue [#34376](https://github.com/react/react/pull/34376) +- `react`: Fix a crash when submitting forms with Client Actions [#33055](https://github.com/react/react/pull/33055) +- `react`: Hide/unhide the content of dehydrated suspense boundaries if they resuspend [#32900](https://github.com/react/react/pull/32900) +- `react`: Avoid stack overflow on wide trees during Hot Reload [#34145](https://github.com/react/react/pull/34145) +- `react`: Improve component stacks in various places [#33629](https://github.com/react/react/pull/33629), [#33724](https://github.com/react/react/pull/33724), [#32735](https://github.com/react/react/pull/32735), [#33723](https://github.com/react/react/pull/33723) +- `react`: Fix a bug with React.use inside React.lazy-ed Component [#33941](https://github.com/react/react/pull/33941) +- `react-dom`: Stop warning when ARIA 1.3 attributes are used [#34264](https://github.com/react/react/pull/34264) +- `react-dom`: Fix a bug with deeply nested Suspense inside Suspense fallbacks [#33467](https://github.com/react/react/pull/33467) +- `react-dom`: Avoid hanging when suspending after aborting while rendering [#34192](https://github.com/react/react/pull/34192) + +For a full list of changes, please see the [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md). --- diff --git a/src/content/blog/2025/10/07/react-compiler-1.md b/src/content/blog/2025/10/07/react-compiler-1.md index 080f3586ea..ee5ffa4fd4 100644 --- a/src/content/blog/2025/10/07/react-compiler-1.md +++ b/src/content/blog/2025/10/07/react-compiler-1.md @@ -132,7 +132,7 @@ export default defineConfig([ } ``` -To enable React Compiler rules, we recommend using the `recommended` preset. You can also check out the [README](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md) for more instructions. Here are a few examples we featured at React Conf: +To enable React Compiler rules, we recommend using the `recommended` preset. You can also check out the [README](https://github.com/react/react/blob/main/packages/eslint-plugin-react-hooks/README.md) for more instructions. Here are a few examples we featured at React Conf: - Catching `setState` patterns that cause render loops with [`set-state-in-render`](/reference/eslint-plugin-react-hooks/lints/set-state-in-render). - Flagging expensive work inside effects via [`set-state-in-effect`](/reference/eslint-plugin-react-hooks/lints/set-state-in-effect). diff --git a/src/content/blog/2025/12/03/critical-security-vulnerability-in-react-server-components.md b/src/content/blog/2025/12/03/critical-security-vulnerability-in-react-server-components.md index 310a841161..a669a2c4e7 100644 --- a/src/content/blog/2025/12/03/critical-security-vulnerability-in-react-server-components.md +++ b/src/content/blog/2025/12/03/critical-security-vulnerability-in-react-server-components.md @@ -34,7 +34,7 @@ The vulnerability is present in versions 19.0, 19.1.0, 19.1.1, and 19.2.0 of: ## Immediate Action Required {/*immediate-action-required*/} -A fix was introduced in versions [19.0.1](https://github.com/facebook/react/releases/tag/v19.0.1), [19.1.2](https://github.com/facebook/react/releases/tag/v19.1.2), and [19.2.1](https://github.com/facebook/react/releases/tag/v19.2.1). If you are using any of the above packages please upgrade to any of the fixed versions immediately. +A fix was introduced in versions [19.0.1](https://github.com/react/react/releases/tag/v19.0.1), [19.1.2](https://github.com/react/react/releases/tag/v19.1.2), and [19.2.1](https://github.com/react/react/releases/tag/v19.2.1). If you are using any of the above packages please upgrade to any of the fixed versions immediately. If your app’s React code does not use a server, your app is not affected by this vulnerability. If your app does not use a framework, bundler, or bundler plugin that supports React Server Components, your app is not affected by this vulnerability. @@ -193,7 +193,7 @@ If you are using React Native in a monorepo, you should update _only_ the impact This is required to mitigate the security advisory, but you do not need to update `react` and `react-dom` so this will not cause the version mismatch error in React Native. -See [this issue](https://github.com/facebook/react-native/issues/54772#issuecomment-3617929832) for more information. +See [this issue](https://github.com/react/react-native/issues/54772#issuecomment-3617929832) for more information. ## Timeline {/*timeline*/} diff --git a/src/content/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components.md b/src/content/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components.md index 70e5c2e658..206d49e391 100644 --- a/src/content/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components.md +++ b/src/content/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components.md @@ -98,7 +98,7 @@ If you are using React Native in a monorepo, you should update _only_ the impact This is required to mitigate the security advisories, but you do not need to update `react` and `react-dom` so this will not cause the version mismatch error in React Native. -See [this issue](https://github.com/facebook/react-native/issues/54772#issuecomment-3617929832) for more information. +See [this issue](https://github.com/react/react-native/issues/54772#issuecomment-3617929832) for more information. --- diff --git a/src/content/blog/index.md b/src/content/blog/index.md index fe7c71f6c9..a1417b92da 100644 --- a/src/content/blog/index.md +++ b/src/content/blog/index.md @@ -149,7 +149,11 @@ React 团队很高兴分享一些更新: ### 所有的发布笔记 {/*all-release-notes*/} +<<<<<<< HEAD 并不是每个 React 版本都值得拥有专属博客文章,但是你可以在 React 仓库的 `CHANGELOG.md` 或 [发布页面](https://github.com/facebook/react/releases) 中找到每个版本的详细变更。 +======= +Not every React release deserves its own blog post, but you can find a detailed changelog for every release in the [`CHANGELOG.md`](https://github.com/react/react/blob/main/CHANGELOG.md) file in the React repository, as well as on the [Releases](https://github.com/react/react/releases) page. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca --- diff --git a/src/content/community/acknowledgements.md b/src/content/community/acknowledgements.md index 7c8012b1e8..a1cb8d43c8 100644 --- a/src/content/community/acknowledgements.md +++ b/src/content/community/acknowledgements.md @@ -4,7 +4,11 @@ title: 鸣谢 <Intro> +<<<<<<< HEAD React 最初由 [Jordan Walke](https://github.com/jordwalke) 创立。如今 React 拥有 [专门的全职团队](/community/team)以及超过 1000 名的 [开源贡献者](https://github.com/facebook/react/graphs/contributors)。 +======= +React was originally created by [Jordan Walke.](https://github.com/jordwalke) Today, React has a [dedicated full-time team working on it](/community/team), as well as over a thousand [open source contributors.](https://github.com/react/react/graphs/contributors) +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </Intro> diff --git a/src/content/community/index.md b/src/content/community/index.md index 4135584de9..2331356215 100644 --- a/src/content/community/index.md +++ b/src/content/community/index.md @@ -10,7 +10,11 @@ React 拥有数百万开发者的社区。本章节将为你列出一些与 Reac ## 行为准则 {/*code-of-conduct*/} +<<<<<<< HEAD 在参与 React 社区前,[请阅读我们的行为准则](https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md)。我们制订了 [贡献者公约](https://www.contributor-covenant.org/),我们希望所有社区成员都能遵守其中的准则。 +======= +Before participating in React's communities, [please read our Code of Conduct.](https://github.com/react/react/blob/main/CODE_OF_CONDUCT.md) We have adopted the [Contributor Covenant](https://www.contributor-covenant.org/) and we expect that all community members adhere to the guidelines within. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ## Stack Overflow {/*stack-overflow*/} diff --git a/src/content/community/team.md b/src/content/community/team.md index fa1505077c..46b3e9e097 100644 --- a/src/content/community/team.md +++ b/src/content/community/team.md @@ -4,6 +4,7 @@ title: "认识我们的团队" <Intro> +<<<<<<< HEAD React 的开发工作由 Meta 全职团队负责领导,并得到来自世界各地人们的贡献。 </Intro> @@ -27,17 +28,128 @@ React 核心团队成员全职致力于核心组件 API 的开发,负责 React </TeamMember> <TeamMember name="Hendrik Liebau" permalink="hendrik-liebau" photo="/images/team/hendrik.jpg" github="unstubbable" bsky="unstubbable.bsky.social" twitter="unstubbable" title="Engineer at Vercel"> - Hendrik’s journey in tech started in the late 90s when he built his first websites with Netscape Communicator. After earning a diploma in computer science and working at digital agencies, he built a React Server Components bundler and library, paving the way to his role on the Next.js team. Outside of work, he enjoys cycling and tinkering in his workshop. +======= +React development is led by contributors from companies and communities all over the world. + +</Intro> + +React work is organized into working groups, each responsible for an area of the project such as Server, DOM, Fiber, Docs & Community, Compiler, DevX, and React Native. Each working group is represented on the React Leadership Council, which coordinates direction across the project. + +## Leadership Council {/*leadership-council*/} + +<TeamMember name="Andrew Clark" permalink="andrew-clark" photo="/images/team/acdlite.jpg" github="acdlite" twitter="acdlite" threads="acdlite" title="Engineer at Vercel" group="Fiber*, DOM"> + Andrew got started with web development by making sites with WordPress, and eventually tricked himself into doing JavaScript. His favorite pastime is karaoke. Andrew is either a Disney villain or a Disney princess, depending on the day. </TeamMember> -<TeamMember name="Jack Pope" permalink="jack-pope" photo="/images/team/jack-pope.jpg" github="jackpope" personal="jackpope.me" title="Engineer at Meta"> +<TeamMember name="Jack Pope" permalink="jack-pope" photo="/images/team/jack-pope.jpg" github="jackpope" personal="jackpope.me" title="Engineer at Meta" group="DOM*, Fiber"> Shortly after being introduced to AutoHotkey, Jack had written scripts to automate everything he could think of. When reaching limitations there, he dove headfirst into web app development and hasn't looked back. Most recently, Jack worked on the web platform at Instagram before moving to React. His favorite programming language is JSX. </TeamMember> +<TeamMember name="Mofei Zhang" permalink="mofei-zhang" photo="/images/team/mofei-zhang.png" github="mofeiZ" threads="z_mofei" title="Engineer at Meta" group="Compiler*"> + Mofei started programming when she realized it can help her cheat in video games. She focused on operating systems in undergrad / grad school, but now finds herself happily tinkering on React. Outside of work, she enjoys debugging bouldering problems and planning her next backpacking trip(s). +</TeamMember> + +<TeamMember name="Moti Zilberman" permalink="moti-zilberman" photo="/images/team/gh-motiz88.jpg" github="motiz88" title="Engineer at Meta" group="DevX*" /> + +<TeamMember name="Nicola Corti" permalink="nicola-corti" photo="/images/team/gh-cortinico.jpg" github="cortinico" title="Engineer at Meta" group="React Native*, Docs & Community" /> + +<TeamMember name="Rick Hanlon" permalink="rick-hanlon" photo="/images/team/rickhanlonii.jpg" github="rickhanlonii" twitter="rickyfm" threads="rickhanlonii" bsky="ricky.fm" title="Engineer at Meta" group="Docs & Community*, DOM, Fiber, Server, DevX"> + Ricky majored in theoretical math and somehow found himself on the React Native team for a couple years before joining the React team. When he's not programming you can find him snowboarding, biking, climbing, golfing, or closing GitHub issues that do not match the issue template. +</TeamMember> + +<TeamMember name="Sebastian Silbermann" permalink="sebastian-silbermann" photo="/images/team/sebsilbermann.jpg" github="eps1lon" twitter="sebsilbermann" threads="sebsilbermann" title="Engineer at Vercel" group="Server*, DOM, Fiber, DevX"> + Sebastian learned programming to make the browser games he played during class more enjoyable. Eventually this lead to contributing to as much open source code as possible. Outside of coding he's busy making sure people don't confuse him with the other Sebastians and Zilberman of the React community. +</TeamMember> + +## Working Group members {/*working-group-members*/} + +<TeamMember name="Alex Hunt" permalink="alex-hunt" photo="/images/team/gh-huntie.jpg" github="huntie" title="Engineer at Meta" group="DevX, React Native" /> + +<TeamMember name="Aurora Scharff" permalink="aurora-scharff" photo="/images/team/gh-aurorascharff.jpg" github="aurorascharff" title="Engineer at Vercel" group="Docs & Community" /> + +<TeamMember name="Brent Vatne" permalink="brent-vatne" photo="/images/team/gh-brentvatne.jpg" github="brentvatne" title="Engineer at Expo" group="React Native" /> + +<TeamMember name="Dan Abramov" permalink="dan-abramov" photo="/images/team/gaearon.jpg" github="gaearon" bsky="danabra.mov" title="Engineer at Vercel" group="Docs & Community"> + Dan got into programming after he accidentally discovered Visual Basic inside Microsoft PowerPoint. He has found his true calling in turning [Sebastian](#sebastian-markbåge)'s tweets into long-form blog posts. Dan occasionally wins at Fortnite by hiding in a bush until the game ends. +</TeamMember> + +<TeamMember name="Hendrik Liebau" permalink="hendrik-liebau" photo="/images/team/hendrik.jpg" github="unstubbable" bsky="unstubbable.bsky.social" twitter="unstubbable" title="Engineer at Vercel" group="Server"> +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca + Hendrik’s journey in tech started in the late 90s when he built his first websites with Netscape Communicator. After earning a diploma in computer science and working at digital agencies, he built a React Server Components bundler and library, paving the way to his role on the Next.js team. Outside of work, he enjoys cycling and tinkering in his workshop. +</TeamMember> + +<TeamMember name="Jordan Eldredge" permalink="jordan-eldredge" photo="/images/team/gh-captbaritone.jpg" github="captbaritone" title="Engineer at Meta" group="Server" /> + +<TeamMember name="Josh Story" permalink="josh-story" photo="/images/team/josh.jpg" github="gnoff" bsky="storyhb.com" title="Engineer at Vercel" group="DOM, Fiber, Server"> + Josh majored in Mathematics and discovered programming while in college. His first professional developer job was to program insurance rate calculations in Microsoft Excel, the paragon of Reactive Programming which must be why he now works on React. In between that time Josh has been an IC, Manager, and Executive at a few startups. outside of work he likes to push his limits with cooking. +</TeamMember> + +<TeamMember name="Kevin Gozali" permalink="kevin-gozali" photo="/images/team/gh-fkgozali.jpg" github="fkgozali" title="Engineer at Meta" group="React Native" /> + +<TeamMember name="Khalef Hosany" permalink="khalef-hosany" photo="/images/team/gh-khalef1.jpg" github="Khalef1" title="Engineer at Microsoft" group="React Native" /> + +<TeamMember name="Krzysztof Magiera" permalink="krzysztof-magiera" photo="/images/team/gh-kmagiera.jpg" github="kmagiera" title="Engineer at Software Mansion" group="React Native" /> + +<TeamMember name="Lauren Tan" permalink="lauren-tan" photo="/images/team/lauren.jpg" github="poteto" twitter="potetotes" threads="potetotes" bsky="no.lol" title="Engineer at Cursor" group="Compiler"> + Lauren's programming career peaked when she first discovered the `<marquee>` tag. She’s been chasing that high ever since. She studied Finance instead of CS in college, so she learned to code using Excel. Lauren enjoys dropping cheeky memes in chat, playing video games with her partner, learning Korean, and petting her dog Zelda. +</TeamMember> + +<TeamMember name="Matt Carroll" permalink="matt-carroll" photo="/images/team/matt-carroll.png" github="mattcarrollcode" twitter="mattcarrollcode" threads="mattcarrollcode" title="Developer Advocate at Meta" group="Docs & Community"> + Matt stumbled into coding, and since then, has become enamored with creating things in communities that can’t be created alone. Prior to React, he worked on YouTube, the Google Assistant, Fuchsia, and Google Cloud AI and Evernote. When he's not trying to make better developer tools he enjoys the mountains, jazz, and spending time with his family. +</TeamMember> + +<TeamMember name="Michael Leon" permalink="michael-leon" photo="/images/team/gh-fbmal7.jpg" github="fbmal7" title="Engineer at Meta" group="DevX" /> + +<TeamMember name="Michał Pierzchała" permalink="michal-pierzchala" photo="/images/team/gh-thymikee.jpg" github="thymikee" title="Engineer at Callstack" group="React Native" /> + +<TeamMember name="Mike Vitousek" permalink="mike-vitousek" photo="/images/team/mike.jpg" github="mvitousek" title="Engineer at Meta" group="Compiler"> + Mike went to grad school dreaming of becoming a professor but realized that he liked building things a lot more than writing grant applications. Mike joined Meta to work on Javascript infrastructure, which ultimately led him to work on the React Compiler. When not hacking on either Javascript or OCaml, Mike can often be found hiking or skiing in the Pacific Northwest. +</TeamMember> + +<TeamMember name="Pieter De Baets" permalink="pieter-de-baets" photo="/images/team/gh-javache.jpg" github="javache" title="Engineer at Meta" group="Compiler, Fiber, React Native" /> + +<TeamMember name="Riccardo Cipolleschi" permalink="riccardo-cipolleschi" photo="/images/team/gh-cipolleschi.png" github="cipolleschi" title="Engineer at Meta" group="Docs & Community, React Native" /> + +<TeamMember name="Rob Hogan" permalink="rob-hogan" photo="/images/team/gh-rh389.jpg" github="rh389" title="Engineer at Meta" group="DevX" /> + +<TeamMember name="Rubén Norte" permalink="ruben-norte" photo="/images/team/gh-rubennorte.jpg" github="rubennorte" title="Engineer at Meta" group="Fiber" /> + +<TeamMember name="Ruslan Lesiutin" permalink="ruslan-lesiutin" photo="/images/team/lesiutin.jpg" github="hoxyq" twitter="ruslanlesiutin" threads="lesiutin" title="Engineer at Meta" group="DOM, DevX"> + Ruslan's introduction to UI programming started when he was a kid by manually editing HTML templates for his custom gaming forums. Somehow, he ended up majoring in Computer Science. He enjoys music, games, and memes. Mostly memes. +</TeamMember> + +<TeamMember name="Sam Selikoff" permalink="sam-selikoff" photo="/images/team/gh-samselikoff.jpg" github="samselikoff" title="Engineer at Vercel" group="Docs & Community" /> + +<TeamMember name="Sophie Alpert" permalink="sophie-alpert" photo="/images/team/sophiebits.jpg" github="sophiebits" twitter="sophiebits" threads="sophiebits" personal="sophiebits.com" title="Independent Engineer" group="Docs & Community, DOM, Fiber"> + Four days after React was released, Sophie rewrote the entirety of her then-current project to use it, which she now realizes was perhaps a bit reckless. After she became the project's #1 committer, she wondered why she wasn't getting paid by Facebook like everyone else was and joined the team officially to lead React through its adolescent years. Though she quit that job years ago, somehow she's still in the team's group chats and “providing value”. +</TeamMember> + +<TeamMember name="Steven Moyes" permalink="steven-moyes" photo="/images/team/gh-stmoy.jpg" github="stmoy" title="Engineer at Amazon" group="React Native" /> + +<TeamMember name="Vitali Zaidman" permalink="vitali-zaidman" photo="/images/team/gh-vzaidman.jpg" github="vzaidman" title="Engineer at Meta" group="DevX" /> + +<TeamMember name="Vojtěch Šimek" permalink="vojtech-simek" photo="/images/team/gh-simek.png" github="simek" title="Developer" group="Docs & Community" /> + +## Advisors {/*advisors*/} + +<TeamMember name="Eli White" permalink="eli-white" photo="/images/team/eli-white.jpg" github="elicwhite" twitter="Eli_White" threads="elicwhite" title="Engineer at Meta"> + Eli got into programming after he got suspended from middle school for hacking. He has been working on React and React Native since 2017. He enjoys eating treats, especially ice cream and apple pie. You can find Eli trying quirky activities like parkour, indoor skydiving, and aerial silks. +</TeamMember> + <TeamMember name="Jason Bonta" permalink="jason-bonta" photo="/images/team/jasonbonta.jpg" threads="someextent" title="Engineering Manager at Meta"> Jason abandoned embedded C for a career in front-end engineering and never looked back. Armed with esoteric CSS knowledge and a passion for beautiful UI, Jason joined Facebook in 2010, where he now feels privileged to have seen JavaScript development come of age. Though he may not understand how `for...of` loops work, he loves getting to work with brilliant people on projects that enable amazing UX. </TeamMember> +<TeamMember name="Jimmy Lai" permalink="jimmy-lai" photo="/images/team/jimmy-lai.jpg" github="feedthejim" title="Engineering Director, Next.js at Vercel"> + Like many others, Jimmy started programming with the hopes of being able to work in the gaming industry. Fast forward a few years, he somehow decided that React and Javascript were pretty fun and that helping other developers build fast experiences was a more interesting life goal. After starting his career at Meta, working on product infrastructure and (briefly) on React Native, Jimmy now works at Vercel, where he helps his team build Next.js. He sadly does not get much time for video games anymore. +</TeamMember> + +<TeamMember name="Seth Webster" permalink="seth-webster" photo="/images/team/seth.jpg" github="sethwebster" twitter="sethwebster" threads="sethwebster" personal="sethwebster.com" title="Executive Director, React Foundation, Chief Developer Evangelist at Expo"> + Seth started programming as a kid growing up in Tucson, AZ. After school, he was bitten by the music bug and was a touring musician for about 10 years before returning to *work*, starting with Intuit. In his spare time, he loves [taking pictures](https://www.sethwebster.com) and flying for animal rescues in the northeastern United States. +</TeamMember> + +## Emeritus {/*emeritus*/} + <TeamMember name="Joe Savona" permalink="joe-savona" photo="/images/team/joe.jpg" github="josephsavona" twitter="en_JS" threads="joesavona" title="Engineer at Meta"> Joe 原计划专攻数学和哲学,然而在用 Matlab 编写物理模拟后,他转行投入了计算机科学领域。在投身 React 之前,他曾先后在 Relay、RSocket.js 以及 Skip 编程语言上有过工作经验。闲暇之余,乔喜欢跑步、学习日语,还有陪伴家人。 </TeamMember> @@ -46,6 +158,7 @@ React 核心团队成员全职致力于核心组件 API 的开发,负责 React Jordan started coding by building iPhone apps, where he was pushing and popping view controllers before he knew that for-loops were a thing. He enjoys working on technology that developers love, which naturally drew him to React. Outside of work he enjoys reading, kiteboarding, and playing guitar. </TeamMember> +<<<<<<< HEAD <TeamMember name="Josh Story" permalink="josh-story" photo="/images/team/josh.jpg" github="gnoff" bsky="storyhb.com" title="Engineer at Vercel"> Josh 大学主修数学,并在那时期初次接触到编程。他的职业生涯起步于在 Microsoft Excel 上编程进行保险费率计算,这是反应式编程的典型案例,或许也是他后来投身 React 的原因。此后,Josh 在数家创业公司中担任过独立贡献者、经理甚至高级管理者。工作之外,他享受烹饪的挑战。 </TeamMember> @@ -66,10 +179,13 @@ React 核心团队成员全职致力于核心组件 API 的开发,负责 React Mofei 在意识到编程可以帮她在视频游戏中作弊时开始了编程生涯。她在本科和研究生期间专注于操作系统的研究,但现在她发现自己非常享受在 React 上的编程工作。在工作之外,她喜欢解决攀岩中遇到的问题,并且正在计划她的下一次背包旅行。 </TeamMember> +======= +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca <TeamMember name="Pieter Vanderwerff" permalink="pieter-vanderwerff" photo="/images/team/pieter.jpg" github="pieterv" threads="pietervanderwerff" title="Engineer at Meta"> Pieter studied building science but after failing to get a job he made himself a website and things escalated from there. At Meta, he enjoys working on performance, languages and now React. When he's not programming you can find him off-road in the mountains. </TeamMember> +<<<<<<< HEAD <TeamMember name="Rick Hanlon" permalink="rick-hanlon" photo="/images/team/rickhanlonii.jpg" github="rickhanlonii" twitter="rickhanlonii" threads="rickhanlonii" bsky="ricky.fm" title="Engineer at Meta"> Ricky 主修理论数学,然而他却在 React Native 团队工作了几年,之后又加入了 React 团队。当他不在编程时,你可以找到他在滑雪,骑自行车,攀岩,打高尔夫球,或者在 GitHub 上关闭一些不符合问题模板的问题。 </TeamMember> @@ -78,10 +194,13 @@ React 核心团队成员全职致力于核心组件 API 的开发,负责 React Ruslan's introduction to UI programming started when he was a kid by manually editing HTML templates for his custom gaming forums. Somehow, he ended up majoring in Computer Science. He enjoys music, games, and memes. Mostly memes. </TeamMember> +======= +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca <TeamMember name="Sebastian Markbåge" permalink="sebastian-markbåge" photo="/images/team/sebmarkbage.jpg" github="sebmarkbage" twitter="sebmarkbage" threads="sebmarkbage" title="Engineer at Vercel"> Sebastian 主修心理学。他通常比较沉默寡言。即使他说些什么,我们其他人通常也要等几个月后才能理解他的意思。他的姓氏正确的发音是“mark-boa-geh”,但出于实用考虑,他接受了“mark-beige”的发音——这也是他处理 React 问题的方式。 </TeamMember> +<<<<<<< HEAD <TeamMember name="Sebastian Silbermann" permalink="sebastian-silbermann" photo="/images/team/sebsilbermann.jpg" github="eps1lon" twitter="sebsilbermann" threads="sebsilbermann" title="Engineer at Vercel"> Sebastian 学习编程的目的是让他在课堂上玩的浏览器游戏变得更有趣。最终,这引领他尽可能多地为开源代码做贡献。在编程之外,他忙于确保人们不会把他和 React 社区的其他 Sebastians 和 Zilberman 混淆。 </TeamMember> @@ -94,10 +213,16 @@ React 核心团队成员全职致力于核心组件 API 的开发,负责 React 在 React 刚刚发布的四天后,Sophie 就将她当时的整个项目全部改写成了使用 React,现在回想起来,她认为这可能有些鲁莽。但在成为该项目的最大贡献者之后,她开始思考为何自己没有像其他人那样从 Facebook 那里得到报酬。于是,她正式加入了 Facebook 的团队,带领 React 度过了它的青少年时期。虽然她在几年前已经离开了那个职位,但她仍然是团队群聊的一员,继续“提供价值”。 </TeamMember> +======= +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca <TeamMember name="Yuzhi Zheng" permalink="yuzhi-zheng" photo="/images/team/yuzhi.jpg" github="yuzhi" twitter="yuzhiz" threads="yuzhiz" title="Engineering Manager at Meta"> Yuzhi 在学校期间学习的是计算机科学。她喜欢看到自己的代码能在无需实验室的情况下就能实现效果,这给她带来了即时的满足感。现在,她是 React 组织的一名经理。在成为经理之前,她曾在 Relay 数据获取框架上有过工作经历。在业余时间,Yuzhi 享受通过园艺和家居改善项目来优化自己的生活。 </TeamMember> +<<<<<<< HEAD ## 过去的贡献者 {/*past-contributors*/} 你可以访问我们的 [鸣谢](/community/acknowledgements) 页面,查看那些曾在过去为 React 付出过重要贡献的团队成员和其他人士。 +======= +You can find the past team members and other people who significantly contributed to React over the years on the [acknowledgements](/community/acknowledgements) page. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca diff --git a/src/content/community/versioning-policy.md b/src/content/community/versioning-policy.md index b6df847e7e..89ea7769fc 100644 --- a/src/content/community/versioning-policy.md +++ b/src/content/community/versioning-policy.md @@ -134,9 +134,15 @@ Canary 渠道还支持 React 与其他项目之间的集成测试。 ```console yarn upgrade react@canary react-dom@canary ``` +<<<<<<< HEAD - 运行测试套件以测试更新后的包。 - 如果一切顺利,那太好了!可以开始期望你的项目将与下一个次要版本的 React 配合使用。 - 如果出现意外的错误,请通过 [提交 issue](https://github.com/facebook/react/issues) 告诉我们。 +======= +- Run your test suite against the updated packages. +- If everything passes, great! You can expect that your project will work with the next minor React release. +- If something breaks unexpectedly, please let us know by [filing an issue](https://github.com/react/react/issues). +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca Next.js 使用了此工作流程,你可以参考他们的 [CircleCI 配置](https://github.com/zeit/next.js/blob/c0a1c0f93966fe33edd93fb53e5fafb0dcd80a9e/.circleci/config.yml) 作为示例。 @@ -166,4 +172,8 @@ Next.js 使用了此工作流程,你可以参考他们的 [CircleCI 配置](ht 当我们准备好宣布新的实验性功能时,我们会在 [React 博客](/blog) 上发布相关信息,但这并不意味着我们会宣传每一个实验性功能。 +<<<<<<< HEAD 你可以随时参考我们公开的 GitHub 仓库的 [历史记录](https://github.com/facebook/react/commits/main) 以获取完整的变更列表。 +======= +You can always refer to our public GitHub repository's [history](https://github.com/react/react/commits/main) for a comprehensive list of changes. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca diff --git a/src/content/learn/react-compiler/debugging.md b/src/content/learn/react-compiler/debugging.md index d8f4744ad7..c4c8c84cd0 100644 --- a/src/content/learn/react-compiler/debugging.md +++ b/src/content/learn/react-compiler/debugging.md @@ -43,10 +43,17 @@ React Complier 可能导致你的应用出错的一个主要方式是,如果 ### 编译器构建错误 {/*compiler-build-errors*/} +<<<<<<< HEAD 如果你遇到一个意外中断构建的编译器错误,这很可能是编译器中的一个 bug。请将以下信息报告到 [facebook/react](https://github.com/facebook/react/issues) 仓库: - 错误信息 - 导致错误的代码 - 你使用的 React 和编译器版本 +======= +If you encounter a compiler error that unexpectedly breaks your build, this is likely a bug in the compiler. Report it to the [react/react](https://github.com/react/react/issues) repository with: +- The error message +- The code that caused the error +- Your React and compiler versions +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ### 运行时问题 {/*runtime-issues*/} @@ -78,6 +85,7 @@ function ProblematicComponent() { 如果你认为发现了一个编译器错误: +<<<<<<< HEAD 1. **确认不是违反 React 规则的问题** - 使用 ESLint 进行检查 2. **创建最小的复现代码** - 在一个小示例中隔离问题 3. **在不使用编译器的情况下测试** - 确认问题仅在编译时出现 @@ -86,6 +94,16 @@ function ProblematicComponent() { - 最小复现代码 - 预期行为与实际行为 - 任何错误信息 +======= +1. **Verify it's not a Rules of React violation** - Check with ESLint +2. **Create a minimal reproduction** - Isolate the issue in a small example +3. **Test without the compiler** - Confirm the issue only occurs with compilation +4. **File an [issue](https://github.com/react/react/issues/new?template=compiler_bug_report.yml)**: + - React and compiler versions + - Minimal reproduction code + - Expected vs actual behavior + - Any error messages +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ## 下一步 {/*next-steps*/} diff --git a/src/content/learn/react-compiler/installation.md b/src/content/learn/react-compiler/installation.md index 45237f147d..4a35b2e43d 100644 --- a/src/content/learn/react-compiler/installation.md +++ b/src/content/learn/react-compiler/installation.md @@ -190,7 +190,11 @@ React 编译器包含一条 ESLint 规则,可帮助识别无法优化的代码 npm install -D eslint-plugin-react-hooks@latest </TerminalBlock> +<<<<<<< HEAD 如果你尚未配置好 eslint-plugin-react-hooks,参考 [readme 的安装说明](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md#installation) 来进行配置。编译器规则可在 `recommended-latest` 预设中找到。 +======= +If you haven't already configured eslint-plugin-react-hooks, follow the [installation instructions in the readme](https://github.com/react/react/blob/main/packages/eslint-plugin-react-hooks/README.md#installation). The compiler rules are available in the `recommended-latest` preset. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ESLint 规则将会: - 识别对 [React 规则](/reference/rules) 的违反情况 diff --git a/src/content/learn/tutorial-tic-tac-toe.md b/src/content/learn/tutorial-tic-tac-toe.md index f8974a67f0..b44b1ad105 100644 --- a/src/content/learn/tutorial-tic-tac-toe.md +++ b/src/content/learn/tutorial-tic-tac-toe.md @@ -899,6 +899,7 @@ body { ### React 开发者工具 {/*react-developer-tools*/} +<<<<<<< HEAD React 开发者工具可以检查 React 组件的 props 和 state。可以在 CodeSandbox 的 *Browser* 部分底部找到 React DevTools 选项卡: ![CodeSandbox 中的 React 开发者工具](../images/tutorial/codesandbox-devtools.png) @@ -906,12 +907,26 @@ React 开发者工具可以检查 React 组件的 props 和 state。可以在 Co 要检查屏幕上的特定组件,请使用 React 开发者工具左上角的按钮: ![用 React 开发者工具选中组件](../images/tutorial/devtools-select.gif) +======= +React Developer Tools let you check the props and the state of your React components. It is available as a [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en), [Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/), and [Edge](https://microsoftedge.microsoft.com/addons/detail/react-developer-tools/gpphkfbcpidddadnkolkpfckpihlkkil) browser extension. -<Note> +After you install it, a new *Components* tab will appear in your browser Developer Tools for sites using React. If you're following along in CodeSandbox, you'd need to first open your sandbox preview in a new tab: + +![opening in new tab](../images/tutorial/sandbox-new-tab.png) + +Then, on the preview page, open your browser's DevTools and find the *Components* tab: +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca +![components tab](../images/tutorial/components-tab.png) + +<<<<<<< HEAD 对于本地开发,React 开发工具可作为 [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)、[Firefox](https://addons.mozilla.org/zh-CN/firefox/addon/react-devtools/) 和 [Edge](https://microsoftedge.microsoft.com/addons/detail/react-developer-tools/gpphkfbcpidddadnkolkpfckpihlkkil) 的浏览器扩展来使用。安装它,*Component* 选项卡将出现在你的浏览器开发者工具中,将被用于使用 React 的站点。 +======= +To inspect a particular component on the screen, use the button in the top left corner of the Components tab: + +![inspecting with devtools](../images/tutorial/devtools-inspect.gif) +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca -</Note> ## 完成这个游戏 {/*completing-the-game*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md b/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md index e057e1978d..6acffb227f 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md @@ -16,7 +16,7 @@ These libraries were designed before React's memoization rules were fully docume ## Rule Details {/*rule-details*/} -Some libraries use patterns that aren't supported by React. When the linter detects usages of these APIs from a [known list](https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts), it flags them under this rule. This means that React Compiler can automatically skip over components that use these incompatible APIs, in order to avoid breaking your app. +Some libraries use patterns that aren't supported by React. When the linter detects usages of these APIs from a [known list](https://github.com/react/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts), it flags them under this rule. This means that React Compiler can automatically skip over components that use these incompatible APIs, in order to avoid breaking your app. ```js // Example of how memoization breaks with these libraries @@ -135,4 +135,4 @@ function Component() { } ``` -Some other libraries do not yet have alternative APIs that are compatible with React's memoization model. If the linter doesn't automatically skip over your components or hooks that call these APIs, please [file an issue](https://github.com/facebook/react/issues) so we can add it to the linter. +Some other libraries do not yet have alternative APIs that are compatible with React's memoization model. If the linter doesn't automatically skip over your components or hooks that call these APIs, please [file an issue](https://github.com/react/react/issues) so we can add it to the linter. diff --git a/src/content/reference/react-dom/components/common.md b/src/content/reference/react-dom/components/common.md index 3dcc01ebc2..c4a7244af1 100644 --- a/src/content/reference/react-dom/components/common.md +++ b/src/content/reference/react-dom/components/common.md @@ -36,7 +36,11 @@ title: "普通组件(例如 <div>)" * `suppressHydrationWarning`:布尔值。如果你使用 [服务器渲染](/reference/react-dom/server),通常会在服务器和客户端渲染不同内容时发出警告。在一些罕见的情况下(比如时间戳),很难或者不可能保证完全匹配。如果你设置 `suppressHydrationWarning` 为 `true`,React 不会在元素属性和内容不匹配时发出警告。它只能在同级工作,并被作为脱围机制。[阅读有关抑制激活错误的内容](/reference/react-dom/client/hydrateRoot#suppressing-unavoidable-hydration-mismatch-errors)。 +<<<<<<< HEAD * `style`:CSS 样式对象,如 `{ fontWeight:'bold',margin:20 }`。与 DOM [`style`](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/style) 属性类似,CSS 属性应该使用像 `camelCase` 这样的驼峰命名法,如应该使用 `fontWeight` 而不是 `font-weight`。你可以将字符串或数字作为值传递,类似 `width: 100`,React 会自动将值附加为 `px`(“像素”),除非它是一个 [无单位的属性](https://github.com/facebook/react/blob/81d4ee9ca5c405dce62f64e61506b8e155f38d8d/packages/react-dom-bindings/src/shared/CSSProperty.js#L8-L57)。我们建议仅在动态样式中使用 `style`,即事先不知道样式值。在其他情况下,使用普通的 CSS 类和 `className` 更有效。[了解有关 `className` 和 `style` 的更多信息](#applying-css-styles)。 +======= +* `style`: An object with CSS styles, for example `{ fontWeight: 'bold', margin: 20 }`. Similarly to the DOM [`style`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) property, the CSS property names need to be written as `camelCase`, for example `fontWeight` instead of `font-weight`. You can pass strings or numbers as values. If you pass a number, like `width: 100`, React will automatically append `px` ("pixels") to the value unless it's a [unitless property.](https://github.com/react/react/blob/81d4ee9ca5c405dce62f64e61506b8e155f38d8d/packages/react-dom-bindings/src/shared/CSSProperty.js#L8-L57) We recommend using `style` only for dynamic styles where you don't know the style values ahead of time. In other cases, applying plain CSS classes with `className` is more efficient. [Read more about `className` and `style`.](#applying-css-styles) +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca 所有内置组件也支持这些标准的 DOM 属性: diff --git a/src/content/reference/react-dom/components/form.md b/src/content/reference/react-dom/components/form.md index 1043b13a0d..10e9c67940 100644 --- a/src/content/reference/react-dom/components/form.md +++ b/src/content/reference/react-dom/components/form.md @@ -48,9 +48,47 @@ To create interactive controls for submitting information, render the [built-in ## Usage {/*usage*/} -### Handle form submission on the client {/*handle-form-submission-on-the-client*/} +### Handle form submission with an event handler {/*handle-form-submission-with-an-event-handler*/} -Pass a function to the `action` prop of form to run the function when the form is submitted. [`formData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) will be passed to the function as an argument so you can access the data submitted by the form. This differs from the conventional [HTML action](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#action), which only accepts URLs. After the `action` function succeeds, all uncontrolled field elements in the form are reset. +Pass a function to the `onSubmit` event handler to run code when the form is submitted. By default, the browser sends the form data to the current URL and refreshes the page, so call [`e.preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) to override that behavior. + +This example reads the submitted values with [`new FormData(e.target)`](https://developer.mozilla.org/en-US/docs/Web/API/FormData), which collects every field by its `name`. This keeps the inputs [uncontrolled](/reference/react-dom/components/input#reading-the-input-values-when-submitting-a-form). If you instead [control an input with state](/reference/react-dom/components/input#controlling-an-input-with-a-state-variable), read from that state on submit rather than from `FormData`. + +<Sandpack> + +```js src/App.js +export default function Search() { + function handleSubmit(e) { + // Prevent the browser from reloading the page + e.preventDefault(); + + // Read the form data + const form = e.target; + const formData = new FormData(form); + const query = formData.get("query"); + alert(`You searched for '${query}'`); + } + + return ( + <form onSubmit={handleSubmit}> + <input name="query" /> + <button type="submit">Search</button> + </form> + ); +} +``` + +</Sandpack> + +<Note> + +Reading form data with `onSubmit` works in every version of React and gives you direct access to the [submit event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event), so you can call `e.preventDefault()` and read the data yourself. Passing the function to the `action` prop instead runs the submission in a [Transition](/reference/react/useTransition). React then tracks the pending state, sends thrown errors to the nearest error boundary, and lets the form work with [`useActionState`](/reference/react/useActionState) and [`useOptimistic`](/reference/react/useOptimistic). An `action` can also be a [Server Function](/reference/rsc/server-functions), which `onSubmit` does not support. + +</Note> + +### Handle form submission with an action prop {/*handle-form-submission-with-an-action-prop*/} + +Pass a function to the `action` prop of form to run the function when the form is submitted. [`formData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) will be passed to the function as an argument so you can access the data submitted by the form. This differs from the conventional [HTML action](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#action), which only accepts URLs. Unlike `onSubmit`, an `action` runs in a [Transition](/reference/react/useTransition) and calling `e.preventDefault()` isn't needed. After the `action` function succeeds, all uncontrolled field elements in the form are reset. <Sandpack> diff --git a/src/content/reference/react-dom/server/renderToPipeableStream.md b/src/content/reference/react-dom/server/renderToPipeableStream.md index d027882cd4..92b37ba37c 100644 --- a/src/content/reference/react-dom/server/renderToPipeableStream.md +++ b/src/content/reference/react-dom/server/renderToPipeableStream.md @@ -48,6 +48,7 @@ const { pipe } = renderToPipeableStream(<App />, { * `reactNode`:想要将其渲染为 HTML 的 React 节点,比如像 `<App />` 这样的 JSX 元素。这样做意味着整个页面文档都将被渲染,所以这里提到的 `App` 组件将渲染 `<html>` 标签. +<<<<<<< HEAD * **可选** `options`:用于配置流的对象. * **可选** `bootstrapScriptContent`:指定一个字符串,这个字符串将被放入 `<script>` 标签中作为其内容。 * **可选** `bootstrapScripts`:一个 URL 字符串数组,它们将被转化为 `<script>` 标签嵌入页面。请将那些调用了 [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) 的 `<script>` 对应的 URL 放入这个数组中。但是如果你不想让客户都端运行 React 的话,请省略这个参数。 @@ -60,6 +61,20 @@ const { pipe } = renderToPipeableStream(<App />, { * **可选** `onShellReady`:一个回调函数,在 [shell 初始化](#specifying-what-goes-into-the-shell) 渲染后立即调用。你可以 [设置状态码](#setting-the-status-code) 然后在这里调用 `pipe` 方法启用流式传输。这样一来,React 将会初始化 shell 渲染完毕后,通过上面提到的 `<script>` 进行 [流式传输更多内容](#streaming-more-content-as-it-loads),用这些内容替换掉 HTML 的加载中的后备方案。 * **可选** `onShellError`:一个回调函数,在初始化 shell 发生错误渲染时调用。它的第一个参数将自动接收捕获到的异常错误。此时,这个流中的任何内容都不会被发送,并且 `onShellReady` 和 `onAllReady` 都不会被调用,所以你还可以 [输出一段后备 HTML shell](#recovering-from-errors-inside-the-shell) 作为兜底。 * **可选** `progressiveChunkSize`:一个块中的字节数。[查阅更多关于该参数默认值的信息](https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225)。 +======= +* **optional** `options`: An object with streaming options. + * **optional** `bootstrapScriptContent`: If specified, this string will be placed in an inline `<script>` tag. + * **optional** `bootstrapScripts`: An array of string URLs for the `<script>` tags to emit on the page. Use this to include the `<script>` that calls [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Omit it if you don't want to run React on the client at all. + * **optional** `bootstrapModules`: Like `bootstrapScripts`, but emits [`<script type="module">`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) instead. + * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed to [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot#parameters) + * **optional** `namespaceURI`: A string with the root [namespace URI](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris) for the stream. Defaults to regular HTML. Pass `'http://www.w3.org/2000/svg'` for SVG or `'http://www.w3.org/1998/Math/MathML'` for MathML. + * **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). + * **optional** `onAllReady`: A callback that fires when all rendering is complete, including both the [shell](#specifying-what-goes-into-the-shell) and all additional [content.](#streaming-more-content-as-it-loads) You can use this instead of `onShellReady` [for crawlers and static generation.](#waiting-for-all-content-to-load-for-crawlers-and-static-generation) If you start streaming here, you won't get any progressive loading. The stream will contain the final HTML. + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](#recovering-from-errors-outside-the-shell) or [not.](#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](#logging-crashes-on-the-server) make sure that you still call `console.error`. You can also use it to [adjust the status code](#setting-the-status-code) before the shell is emitted. + * **optional** `onShellReady`: A callback that fires right after the [initial shell](#specifying-what-goes-into-the-shell) has been rendered. You can [set the status code](#setting-the-status-code) and call `pipe` here to start streaming. React will [stream the additional content](#streaming-more-content-as-it-loads) after the shell along with the inline `<script>` tags that replace the HTML loading fallbacks with the content. + * **optional** `onShellError`: A callback that fires if there was an error rendering the initial shell. It receives the error as an argument. No bytes were emitted from the stream yet, and neither `onShellReady` nor `onAllReady` will get called, so you can [output a fallback HTML shell.](#recovering-from-errors-inside-the-shell) + * **optional** `progressiveChunkSize`: The number of bytes in a chunk. [Read more about the default heuristic.](https://github.com/react/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225) +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca #### 返回值 {/*returns*/} @@ -284,6 +299,7 @@ function ProfilePage() { <Note> +<<<<<<< HEAD **只有支持 Suspense 的数据源才会激活 Suspense 组件**。它们包括: - 使用像 [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) 和 [Next.js](https://nextjs.org/docs/getting-started/react-essentials) 这样支持用 Suspence 获取数据的框架。 @@ -295,6 +311,9 @@ function ProfilePage() { 在上述的 `Post` 组件中加载数据的具体方式取决于你使用的框架。如果你使用了一个支持 Suspense 的框架,你可以它的文档中找到获取数据的详细方式。 并不一定就得使用某个框架才能使用支持 Suspense 的获取数据方式。但实现一个支持 Suspense 的数据源的具体要求并不明确而且目前没有参考实例。不过在 React 未来的版本中,官方将推出一个能够通过 Suspense 来聚合数据源的 API。 +======= +Only data read from a source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise read with [`use`](/reference/react/use), will suspend during rendering. Suspense does not detect data fetched inside an Effect or event handler. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </Note> diff --git a/src/content/reference/react-dom/server/renderToReadableStream.md b/src/content/reference/react-dom/server/renderToReadableStream.md index 98b9180e7c..4dec0d0791 100644 --- a/src/content/reference/react-dom/server/renderToReadableStream.md +++ b/src/content/reference/react-dom/server/renderToReadableStream.md @@ -49,6 +49,7 @@ async function handler(request) { * `reactNode`:要渲染为 HTML 的 React 节点。例如,类似 `<App />` 的 JSX 元素。它应该表示整个文档,因此 `App` 组件应该渲染 `<html>` 标签。 +<<<<<<< HEAD * **可选参数** `options`:一个对象,用于对流进行配置。 * **可选属性** `bootstrapScriptContent`:如果指定了这个属性值,那么这个字符串会被放置在内联 `<script>` 标签中。 * **可选属性** `bootstrapScripts`:一个字符串 URL 的数组, 用于在页面上生成 `<script>` 标签。使用它的话,可在 `<script>` 中调用 [`hydrateRoot`](/reference/react-dom/client/hydrateRoot)。如果你根本不想在客户端上运行 React,则忽略它。 @@ -59,6 +60,18 @@ async function handler(request) { * **可选属性** `onError`:一个回调函数,只要服务器出错就会触发,无论错误是 [否](#recovering-from-errors-inside-the-shell) 可 [恢复](#recovering-from-errors-outside-the-shell)。默认情况下,它只调用 `console.error`。如果你用 [log crash reports](#logging-crashes-on-the-server) 重写,请确保你仍然调用 `console.error`。你还可以使用它在 shell 触发之前 [调整状态代码](#setting-the-status-code)。 * **可选属性** `progressiveChunkSize`:块中的字节数。[阅读更多默认启发式方法](https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225)。 * **可选属性** `signal`:一个 [中止信号](https://developer.mozilla.org/zh-CN/docs/Web/API/AbortSignal),用于 [中止服务端渲染](#aborting-server-rendering) 并在客户端上渲染其余部分。 +======= +* **optional** `options`: An object with streaming options. + * **optional** `bootstrapScriptContent`: If specified, this string will be placed in an inline `<script>` tag. + * **optional** `bootstrapScripts`: An array of string URLs for the `<script>` tags to emit on the page. Use this to include the `<script>` that calls [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Omit it if you don't want to run React on the client at all. + * **optional** `bootstrapModules`: Like `bootstrapScripts`, but emits [`<script type="module">`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) instead. + * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed to [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot#parameters) + * **optional** `namespaceURI`: A string with the root [namespace URI](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris) for the stream. Defaults to regular HTML. Pass `'http://www.w3.org/2000/svg'` for SVG or `'http://www.w3.org/1998/Math/MathML'` for MathML. + * **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](#recovering-from-errors-outside-the-shell) or [not.](#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](#logging-crashes-on-the-server) make sure that you still call `console.error`. You can also use it to [adjust the status code](#setting-the-status-code) before the shell is emitted. + * **optional** `progressiveChunkSize`: The number of bytes in a chunk. [Read more about the default heuristic.](https://github.com/react/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225) + * **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca #### 返回值 {/*returns*/} @@ -283,6 +296,7 @@ function ProfilePage() { <Note> +<<<<<<< HEAD **只有支持 Suspense 的数据源将会激活 Suspense 组件**。它们包括: - 使用支持 Suspense 的框架获取数据,如 [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) 和 [Next.js](https://nextjs.org/docs/getting-started/react-essentials) @@ -294,6 +308,9 @@ suspense **不会去探测** Effect 内部或事件处理器中获取的数据 在上面的 `Posts` 组件中加载数据的具体方式取决于你的框架。如果你使用一个支持 Suspense 的框架,你可以在其数据获取文档中找到详细信息。 目前还不支持在不使用已提到的框架的情况下使用 Suspense 功能的数据提取。实现支持 Suspense 数据源的需求不稳定且没有记录。用 Suspense 集成数据源的官方 API 将在 React 的未来版本中发布。 +======= +Only data read from a source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise read with [`use`](/reference/react/use), will suspend during rendering. Suspense does not detect data fetched inside an Effect or event handler. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </Note> diff --git a/src/content/reference/react-dom/static/prerender.md b/src/content/reference/react-dom/static/prerender.md index eb513b3b7d..b0f0779fd1 100644 --- a/src/content/reference/react-dom/static/prerender.md +++ b/src/content/reference/react-dom/static/prerender.md @@ -49,6 +49,7 @@ async function handler(request, response) { * `reactNode`:需要渲染为 HTML 的 React 节点。例如,一个像 `<App />` 的 JSX 节点。它应表示整个文档,因此 App 组件应渲染 `<html>` 标签。 +<<<<<<< HEAD * **可选** `options`:一个包含静态生成选项的对象。 * **可选** `bootstrapScriptContent`:如果指定,此字符串将被放置在一个内联的 `<script>` 标签中。 * **可选** `bootstrapScripts`:一个字符串 URL 的数组,用于在页面上生成 `<script>` 标签。使用此选项包含调用 [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) 的 `<script>`。如果不希望在客户端运行 React,可以省略此选项。 @@ -58,6 +59,17 @@ async function handler(request, response) { * **可选** `onError`:每当发生服务器错误时触发的回调,无论是 [可恢复的](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) 还是 [不可恢复的](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell)。默认情况下,它只调用 `console.error`。如果你重写它用来 [记录崩溃报告](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) ,请确保仍然调用 `console.error`。你还可以使用它在 shell 被生成之前 [调整状态码](/reference/react-dom/server/renderToReadableStream#setting-the-status-code)。 * **可选** `progressiveChunkSize`:每个块的字节数。[阅读更多关于默认启发式的信息。](https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225) * **可选** `signal`:一个 [中止信号](https://developer.mozilla.org/zh-CN/docs/Web/API/AbortSignal),允许你 [中止预渲染](#aborting-prerendering) 并在客户端渲染剩余内容。 +======= +* **optional** `options`: An object with static generation options. + * **optional** `bootstrapScriptContent`: If specified, this string will be placed in an inline `<script>` tag. + * **optional** `bootstrapScripts`: An array of string URLs for the `<script>` tags to emit on the page. Use this to include the `<script>` that calls [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Omit it if you don't want to run React on the client at all. + * **optional** `bootstrapModules`: Like `bootstrapScripts`, but emits [`<script type="module">`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) instead. + * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed to [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot#parameters) + * **optional** `namespaceURI`: A string with the root [namespace URI](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris) for the stream. Defaults to regular HTML. Pass `'http://www.w3.org/2000/svg'` for SVG or `'http://www.w3.org/1998/Math/MathML'` for MathML. + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) make sure that you still call `console.error`. You can also use it to [adjust the status code](/reference/react-dom/server/renderToReadableStream#setting-the-status-code) before the shell is emitted. + * **optional** `progressiveChunkSize`: The number of bytes in a chunk. [Read more about the default heuristic.](https://github.com/react/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225) + * **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort prerendering](#aborting-prerendering) and render the rest on the client. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca #### 返回值 {/*returns*/} @@ -275,6 +287,7 @@ function ProfilePage() { <Note> +<<<<<<< HEAD **只有支持 Suspense 的数据源才能触发 Suspense 组件。** 包括: - 使用支持 Suspense 的框架(如:[Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) 和 [Next.js](https://nextjs.org/docs/getting-started/react-essentials))进行数据获取 @@ -286,6 +299,9 @@ Suspense **无法** 检测在 Effect 或事件处理程序中获取的数据。 在上述 `Posts` 组件中加载数据的具体方式取决于你使用的框架。如果你使用支持 Suspense 的框架,可以在其数据获取文档中找到详细信息。 在没有使用特定框架的情况下,支持 Suspense 的数据获取尚未得到支持。实现支持 Suspense 的数据源的要求目前不稳定且未记录。React 未来版本将发布用于集成数据源与 Suspense 的官方 API。 +======= +Only data read from a source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise read with [`use`](/reference/react/use), will suspend during rendering. Suspense does not detect data fetched inside an Effect or event handler. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </Note> diff --git a/src/content/reference/react-dom/static/prerenderToNodeStream.md b/src/content/reference/react-dom/static/prerenderToNodeStream.md index a282e51d2d..9b405d3ebd 100644 --- a/src/content/reference/react-dom/static/prerenderToNodeStream.md +++ b/src/content/reference/react-dom/static/prerenderToNodeStream.md @@ -50,6 +50,7 @@ app.use('/', async (request, response) => { * `reactNode`:要渲染为 HTML 的 React 节点。例如 JSX 节点 `<App />`。它应代表整个文档,因此 `App` 组件应渲染 `<html>` 标签。 +<<<<<<< HEAD * **可选** `options`:一个用于静态生成的选项对象。 * **可选** `bootstrapScriptContent`:若指定,该字符串会放入内联 `<script>` 标签中。 * **可选** `bootstrapScripts`:要在页面中输出的 `<script>` 标签 URL 字符串数组。用于包含调用 [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) 的脚本;如果不希望在客户端运行 React,可省略此项。 @@ -59,6 +60,17 @@ app.use('/', async (request, response) => { * **可选** `onError`:当服务器发生错误(可恢复或不可恢复)时触发的回调。默认仅会调用 `console.error`。如果你重写它以记录崩溃报告,请确保仍然调用 `console.error`。你也可以在 shell 发出之前使用它来调整响应状态码。 * **可选** `progressiveChunkSize`:每个 chunk 的字节数。 [了解默认启发式的更多信息。](https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225) * **可选** `signal`:一个 [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal),可以用来 [中止 prerender](#aborting-prerendering),并在客户端渲染剩余部分。 +======= +* **optional** `options`: An object with static generation options. + * **optional** `bootstrapScriptContent`: If specified, this string will be placed in an inline `<script>` tag. + * **optional** `bootstrapScripts`: An array of string URLs for the `<script>` tags to emit on the page. Use this to include the `<script>` that calls [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Omit it if you don't want to run React on the client at all. + * **optional** `bootstrapModules`: Like `bootstrapScripts`, but emits [`<script type="module">`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) instead. + * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed to [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot#parameters) + * **optional** `namespaceURI`: A string with the root [namespace URI](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris) for the stream. Defaults to regular HTML. Pass `'http://www.w3.org/2000/svg'` for SVG or `'http://www.w3.org/1998/Math/MathML'` for MathML. + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToPipeableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToPipeableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToPipeableStream#logging-crashes-on-the-server) make sure that you still call `console.error`. You can also use it to [adjust the status code](/reference/react-dom/server/renderToPipeableStream#setting-the-status-code) before the shell is emitted. + * **optional** `progressiveChunkSize`: The number of bytes in a chunk. [Read more about the default heuristic.](https://github.com/react/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225) + * **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort prerendering](#aborting-prerendering) and render the rest on the client. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca #### 返回 {/*returns*/} @@ -276,6 +288,7 @@ function ProfilePage() { <Note> +<<<<<<< HEAD **只有支持 Suspense 的数据源才会触发 Suspense 组件。** 包括: - 使用像 [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) 和 [Next.js](https://nextjs.org/docs/getting-started/react-essentials) 等支持 Suspense 的框架进行数据获取 @@ -287,6 +300,9 @@ Suspense **不会** 检测在 Effect 或事件处理器中执行的数据请求 如何在上例的 `Posts` 组件中加载数据取决于你使用的框架。若使用支持 Suspense 的框架,请参阅该框架的数据获取文档。 在不使用特定框架的情况下实现支持 Suspense 的数据获取尚不稳定且未文档化。用于与 Suspense 集成的数据源官方 API 将在未来的 React 版本中发布。 +======= +Only data read from a source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise read with [`use`](/reference/react/use), will suspend during rendering. Suspense does not detect data fetched inside an Effect or event handler. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </Note> diff --git a/src/content/reference/react/Activity.md b/src/content/reference/react/Activity.md index 5ecf30db73..e8ea9e33d7 100644 --- a/src/content/reference/react/Activity.md +++ b/src/content/reference/react/Activity.md @@ -755,6 +755,7 @@ video { width: 300px; margin-top: 10px; aspect-ratio: 16/9; } <Note> +<<<<<<< HEAD **只有支持 `Suspense` 的数据源才会在预渲染期间被获取。** 它们包括: - 使用支持 `Suspense` 的框架进行数据获取,例如 [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) 和 [Next.js](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming#streaming-with-suspense) @@ -766,6 +767,9 @@ video { width: 300px; margin-top: 10px; aspect-ratio: 16/9; } 你在上述 `Posts` 组件中加载数据的具体方式取决于你所使用的框架。如果你使用的是支持 `Suspense` 的框架,你可以在其数据获取文档中找到相关细节。 目前尚不支持在不使用集成框架的情况下,直接进行支持 `Suspense` 的数据获取。实现支持 `Suspense` 的数据源的相关要求尚不稳定且未记录在文档中。用于将数据源与 `Suspense` 集成的官方 API 将在 React 的未来版本中发布。 +======= +Only data read from a source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise read with [`use`](/reference/react/use), is fetched during pre-rendering. Activity does not detect data fetched inside an Effect. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca </Note> diff --git a/src/content/reference/react/Component.md b/src/content/reference/react/Component.md index 0c2cecc1a6..a7cb3cdffe 100644 --- a/src/content/reference/react/Component.md +++ b/src/content/reference/react/Component.md @@ -1009,7 +1009,11 @@ class Form extends Component { #### 注意 {/*static-getderivedstatefromprops-caveats*/} +<<<<<<< HEAD - 无论什么原因,此方法都会在 **每次** 渲染时触发。这与 [`UNSAFE_componentWillReceiveProps`](#unsafe_cmoponentwillreceiveprops) 不同,后者仅在父组件不是因为调用了本地的 `setState` 而重新渲染时触发。 +======= +- This method is fired on *every* render, regardless of the cause. This is different from [`UNSAFE_componentWillReceiveProps`](#unsafe_componentwillreceiveprops), which only fires when the parent causes a re-render and not as a result of a local `setState`. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca - 此方法无权访问组件实例。如果你愿意,你可以在 `static getDerivedStateFromProps` 和其他类方法之间重用一些代码,也就是提取类定义之外的组件 props 和 state 的纯函数。 diff --git a/src/content/reference/react/Fragment.md b/src/content/reference/react/Fragment.md index 805b2e26e0..6d0b81ae12 100644 --- a/src/content/reference/react/Fragment.md +++ b/src/content/reference/react/Fragment.md @@ -6,7 +6,7 @@ title: <Fragment> (<>...</>) `<Fragment>` 通常使用 `<>...</>` 代替,它们都允许你在不添加额外节点的情况下将子元素组合。 -<Canary> Fragments can also accept refs, which enable interacting with underlying DOM nodes without adding wrapper elements. See reference and usage below.</Canary> +<Canary>Fragments can also accept refs, which enable interacting with underlying DOM nodes without adding wrapper elements.</Canary> ```js <> @@ -29,6 +29,7 @@ title: <Fragment> (<>...</>) #### 参数 {/*props*/} +<<<<<<< HEAD - **可选** `key`:列表中 `<Fragment>` 的可以拥有 [keys](/learn/rendering-lists#keeping-list-items-in-order-with-key)。 - <CanaryBadge /> **optional** `ref`: A ref object (e.g. from [`useRef`](/reference/react/useRef)) or [callback function](/reference/react-dom/components/common#ref-callback). React provides a `FragmentInstance` as the ref value that implements methods for interacting with the DOM nodes wrapped by the Fragment. @@ -57,14 +58,267 @@ When you pass a ref to a fragment, React provides a `FragmentInstance` object wi **Observer methods:** - `observeUsing(observer)`: Starts observing the Fragment's DOM children with an IntersectionObserver or ResizeObserver. - `unobserveUsing(observer)`: Stops observing the Fragment's DOM children with the specified observer. +======= +- **optional** `key`: Fragments declared with the explicit `<Fragment>` syntax may have [keys.](/learn/rendering-lists#keeping-list-items-in-order-with-key) +- <CanaryBadge /> **optional** `ref`: A ref object (e.g. from [`useRef`](/reference/react/useRef)) or [callback function](/reference/react-dom/components/common#ref-callback). React provides a `FragmentInstance` as the ref value that implements methods for interacting with the DOM nodes wrapped by the Fragment. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca #### 注意事项 {/*caveats*/} +<<<<<<< HEAD - 如果你要传递 `key` 给一个 `<Fragment>`,你不能使用 `<>...</>`,你必须从 `'react'` 中导入 `Fragment` 且表示为`<Fragment key={yourKey}>...</Fragment>`。 - 当你要从 `<><Child /></>` 转换为 `[<Child />]` 或 `<><Child /></>` 转换为 `<Child />`,React 并不会[重置 state](/learn/preserving-and-resetting-state)。这个规则只在一层深度的情况下生效,如果从 `<><><Child /></></>` 转换为 `<Child />` 则会重置 state。在[这里](https://gist.github.com/clemmy/b3ef00f9507909429d8aa0d3ee4f986b)查看更详细的介绍。 +======= +* If you want to pass `key` to a Fragment, you can't use the `<>...</>` syntax. You have to explicitly import `Fragment` from `'react'` and render `<Fragment key={yourKey}>...</Fragment>`. -- <CanaryBadge /> If you want to pass `ref` to a Fragment, you can't use the `<>...</>` syntax. You have to explicitly import `Fragment` from `'react'` and render `<Fragment ref={yourRef}>...</Fragment>`. +* React does not [reset state](/learn/preserving-and-resetting-state) when you go from rendering `<><Child /></>` to `[<Child />]` or back, or when you go from rendering `<><Child /></>` to `<Child />` and back. This only works a single level deep: for example, going from `<><><Child /></></>` to `<Child />` resets the state. See the precise semantics [here.](https://gist.github.com/clemmy/b3ef00f9507909429d8aa0d3ee4f986b) +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca + +* <CanaryBadge /> If you want to pass `ref` to a Fragment, you can't use the `<>...</>` syntax. You have to explicitly import `Fragment` from `'react'` and render `<Fragment ref={yourRef}>...</Fragment>`. + +--- + +### <CanaryBadge /> `FragmentInstance` {/*fragmentinstance*/} + +When you pass a `ref` to a Fragment, React provides a `FragmentInstance` object. It implements methods for interacting with the first-level DOM children wrapped by the Fragment. + +* [`addEventListener`](#addeventlistener) and [`removeEventListener`](#removeeventlistener) manage event listeners across all first-level DOM children. +* [`dispatchEvent`](#dispatchevent) dispatches an event on the Fragment, which can bubble to the DOM parent. +* [`focus`](#focus), [`focusLast`](#focuslast), and [`blur`](#blur) manage focus across all nested children depth-first. +* [`observeUsing`](#observeusing) and [`unobserveUsing`](#unobserveusing) attach and detach `IntersectionObserver` or `ResizeObserver` instances. +* [`getClientRects`](#getclientrects) returns bounding rectangles of all first-level DOM children. +* [`getRootNode`](#getrootnode) returns the root node of the Fragment's parent. +* [`compareDocumentPosition`](#comparedocumentposition) compares the Fragment's position with another node. +* [`scrollIntoView`](#scrollintoview) scrolls the Fragment's children into view. + +--- + +#### `addEventListener(type, listener, options?)` {/*addeventlistener*/} + +Adds an event listener to all first-level DOM children of the Fragment. + +```js +fragmentRef.current.addEventListener('click', handleClick); +``` + +##### Parameters {/*addeventlistener-parameters*/} + +* `type`: A string representing the event type to listen for (e.g. `'click'`, `'focus'`). +* `listener`: The event handler function. +* **optional** `options`: An options object or boolean for capture, matching the [DOM `addEventListener` API.](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) + +##### Returns {/*addeventlistener-returns*/} + +`addEventListener` does not return anything (`undefined`). + +--- + +#### `removeEventListener(type, listener, options?)` {/*removeeventlistener*/} + +Removes an event listener from all first-level DOM children of the Fragment. + +```js +fragmentRef.current.removeEventListener('click', handleClick); +``` + +##### Parameters {/*removeeventlistener-parameters*/} + +* `type`: The event type string. +* `listener`: The event handler function to remove. +* **optional** `options`: An options object or boolean, matching the [DOM `removeEventListener` API.](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) + +##### Returns {/*removeeventlistener-returns*/} + +`removeEventListener` does not return anything (`undefined`). + +--- + +#### `dispatchEvent(event)` {/*dispatchevent*/} + +Dispatches an event on the Fragment. Added event listeners are called, and the event can bubble to the Fragment's DOM parent. + +```js +fragmentRef.current.dispatchEvent(new Event('custom', { bubbles: true })); +``` + +##### Parameters {/*dispatchevent-parameters*/} + +* `event`: An [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) object to dispatch. If `bubbles` is `true`, the event bubbles to the Fragment's parent DOM node. + +##### Returns {/*dispatchevent-returns*/} + +`true` if the event was not cancelled, `false` if `preventDefault()` was called. + +--- + +#### `focus(options?)` {/*focus*/} + +Focuses the first focusable DOM node in the Fragment. Unlike calling `element.focus()` on a DOM element, this method searches *all* nested children depth-first until it finds a focusable element—not just the element itself or its direct children. + +```js +fragmentRef.current.focus(); +``` + +##### Parameters {/*focus-parameters*/} + +* **optional** `options`: A [`FocusOptions`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#options) object (e.g. `{ preventScroll: true }`). + +##### Returns {/*focus-returns*/} + +`focus` does not return anything (`undefined`). + +--- + +#### `focusLast(options?)` {/*focuslast*/} + +Focuses the last focusable DOM node in the Fragment. Searches nested children depth-first, then iterates in reverse. + +```js +fragmentRef.current.focusLast(); +``` + +##### Parameters {/*focuslast-parameters*/} + +* **optional** `options`: A [`FocusOptions`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#options) object. + +##### Returns {/*focuslast-returns*/} + +`focusLast` does not return anything (`undefined`). + +--- + +#### `blur()` {/*blur*/} + +Removes focus from the active element if it is within the Fragment. If `document.activeElement` is not within the Fragment, `blur` does nothing. + +```js +fragmentRef.current.blur(); +``` + +##### Returns {/*blur-returns*/} + +`blur` does not return anything (`undefined`). + +--- + +#### `observeUsing(observer)` {/*observeusing*/} + +Starts observing all first-level DOM children of the Fragment with the provided observer. + +```js +const observer = new IntersectionObserver(callback, options); +fragmentRef.current.observeUsing(observer); +``` + +##### Parameters {/*observeusing-parameters*/} + +* `observer`: An [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) or [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) instance. + +##### Returns {/*observeusing-returns*/} + +`observeUsing` does not return anything (`undefined`). + +--- + +#### `unobserveUsing(observer)` {/*unobserveusing*/} + +Stops observing the Fragment's DOM children with the specified observer. + +```js +fragmentRef.current.unobserveUsing(observer); +``` + +##### Parameters {/*unobserveusing-parameters*/} + +* `observer`: The same `IntersectionObserver` or `ResizeObserver` instance previously passed to [`observeUsing`](#observeusing). + +##### Returns {/*unobserveusing-returns*/} + +`unobserveUsing` does not return anything (`undefined`). + +--- + +#### `getClientRects()` {/*getclientrects*/} + +Returns a flat array of [`DOMRect`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect) objects representing the bounding rectangles of all first-level DOM children. + +```js +const rects = fragmentRef.current.getClientRects(); +``` + +##### Returns {/*getclientrects-returns*/} + +An `Array<DOMRect>` containing the bounding rectangles of all children. + +--- + +#### `getRootNode(options?)` {/*getrootnode*/} + +Returns the root node containing the Fragment's parent DOM node, matching the behavior of [`Node.getRootNode()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode). + +```js +const root = fragmentRef.current.getRootNode(); +``` + +##### Parameters {/*getrootnode-parameters*/} + +* **optional** `options`: An object with a `composed` boolean property, matching the [DOM `getRootNode` API.](https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode#options) + +##### Returns {/*getrootnode-returns*/} + +A `Document`, `ShadowRoot`, or the `FragmentInstance` itself if there is no parent DOM node. + +--- + +#### `compareDocumentPosition(otherNode)` {/*comparedocumentposition*/} + +Compares the document position of the Fragment with another node, returning a bitmask matching the behavior of [`Node.compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition). + +```js +const position = fragmentRef.current.compareDocumentPosition(otherElement); +``` + +##### Parameters {/*comparedocumentposition-parameters*/} + +* `otherNode`: The DOM node to compare against. + +##### Returns {/*comparedocumentposition-returns*/} + +A bitmask of [position flags](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition#return_value). Empty Fragments and Fragments with children rendered through a [portal](/reference/react-dom/createPortal) include `Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC` in the result. + +--- + +#### `scrollIntoView(alignToTop?)` {/*scrollintoview*/} + +Scrolls the Fragment's children into view. When `alignToTop` is `true` or omitted, scrolls to align the first child with the top of the scrollable ancestor. When `alignToTop` is `false`, scrolls to align the last child with the bottom. + +```js +fragmentRef.current.scrollIntoView(); +``` + +##### Parameters {/*scrollintoview-parameters*/} + +* **optional** `alignToTop`: A boolean. If `true` (the default), scrolls the first child to the top of the scrollable area. If `false`, scrolls the last child to the bottom. Unlike [`Element.scrollIntoView()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView), this method does not accept a `ScrollIntoViewOptions` object. + +##### Returns {/*scrollintoview-returns*/} + +`scrollIntoView` does not return anything (`undefined`). + +##### Caveats {/*scrollintoview-caveats*/} + +* `scrollIntoView` does not accept an options object. Passing one throws an error. Use the `alignToTop` boolean instead. +* When the Fragment has no children, `scrollIntoView` scrolls the nearest sibling or parent into view as a fallback. + +--- + +#### `FragmentInstance` Caveats {/*fragmentinstance-caveats*/} + +* Methods that target children (such as `addEventListener`, `observeUsing`, and `getClientRects`) operate on *first-level host (DOM) children* of the Fragment. They do not directly target children nested inside another DOM element. +* `focus` and `focusLast` search nested children depth-first for focusable elements, unlike event and observer methods which only target first-level host children. +* `observeUsing` does not work on text nodes. React logs a warning in development if the Fragment contains only text children. +* React does not apply event listeners added via `addEventListener` to hidden [`<Activity>`](/reference/react/Activity) trees. When an `Activity` boundary switches from hidden to visible, listeners are applied automatically. +* Each first-level DOM child of a Fragment with a `ref` gets a `reactFragments` property—a `Set<FragmentInstance>` containing all Fragment instances that own the element. This enables [caching a shared observer](#caching-global-intersection-observer) across multiple Fragments. --- @@ -242,47 +496,312 @@ function PostBody({ body }) { --- -### <CanaryBadge /> Using Fragment refs for DOM interaction {/*using-fragment-refs-for-dom-interaction*/} +### <CanaryBadge /> Adding event listeners without a wrapper element {/*adding-event-listeners-without-wrapper*/} -Fragment refs allow you to interact with the DOM nodes wrapped by a Fragment without adding extra wrapper elements. This is useful for event handling, visibility tracking, focus management, and replacing deprecated patterns like `ReactDOM.findDOMNode()`. +Fragment `ref`s let you add event listeners to a group of elements without adding a wrapper DOM node. Use a [ref callback](/reference/react-dom/components/common#ref-callback) to attach and clean up listeners: + +<Sandpack> ```js -import { Fragment } from 'react'; +import { Fragment, useState, useRef, useEffect } from 'react'; function ClickableFragment({ children, onClick }) { + const fragmentRef = useRef(null); + useEffect(() => { + const fragmentInstance = fragmentRef.current; + if (fragmentInstance === null) { + return; + } + fragmentInstance.addEventListener('click', onClick); + return () => { + fragmentInstance.removeEventListener( + 'click', + onClick + ); + }; + }, [onClick]) return ( - <Fragment ref={fragmentInstance => { - fragmentInstance.addEventListener('click', handleClick); - return () => fragmentInstance.removeEventListener('click', handleClick); - }}> + <Fragment ref={fragmentRef}> {children} </Fragment> ); } + +export default function App() { + const [clicks, setClicks] = useState(0); + + return ( + <> + <p>Total clicks: {clicks}</p> + <ClickableFragment onClick={() => { + setClicks(c => c + 1); + }}> + <button>Button A</button> + <button>Button B</button> + <button>Button C</button> + </ClickableFragment> + </> + ); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "latest" + } +} +``` + +</Sandpack> + +The `addEventListener` call applies the listener to every first-level DOM child of the Fragment. When children are dynamically added or removed, the `FragmentInstance` automatically adds or removes the listener. + +<DeepDive> + +#### Which children does a Fragment ref target? {/*which-children-does-a-fragment-ref-target*/} + +A `FragmentInstance` targets the **first-level host (DOM) children** of the Fragment. Consider this tree: + +```js +<Fragment ref={ref}> + <div id="A" /> + <Wrapper> + <div id="B"> + <div id="C" /> + </div> + </Wrapper> + <div id="D" /> +</Fragment> +``` + +`Wrapper` is a React component, so the `FragmentInstance` looks through it to find DOM nodes. The targeted children are `A`, `B`, and `D`. `C` is not targeted because it is nested inside the DOM element `B`. + +Methods like `addEventListener`, `observeUsing`, and `getClientRects` operate on these first-level DOM children. `focus` and `focusLast` are different—they search *all* nested children depth-first to find focusable elements. + +</DeepDive> + +--- + +### <CanaryBadge /> Managing focus across a group of elements {/*managing-focus-across-elements*/} + +Fragment `ref`s provide `focus`, `focusLast`, and `blur` methods that operate across all DOM nodes within the Fragment: + +<Sandpack> + +```js +import { Fragment, useRef } from 'react'; + +function FormFields({ children }) { + const fragmentRef = useRef(null); + + return ( + <> + <div className="buttons"> + <button onClick={() => { + fragmentRef.current.focus(); + }}> + Focus first + </button> + <button onClick={() => { + fragmentRef.current.focusLast(); + }}> + Focus last + </button> + <button onClick={() => { + fragmentRef.current.blur(); + }}> + Blur + </button> + </div> + <Fragment ref={fragmentRef}> + {children} + </Fragment> + </> + ); +} + +// Even though the inputs are deeply nested, +// focus() searches depth-first to find them. +export default function App() { + return ( + <FormFields> + <fieldset> + <legend>Shipping</legend> + <label> + Street: <input name="street" /> + </label> + <label> + City: <input name="city" /> + </label> + </fieldset> + </FormFields> + ); +} +``` + +```css +.buttons { + display: flex; + gap: 8px; + margin-bottom: 10px; +} + +label { + display: inline-block; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "latest" + } +} +``` + +</Sandpack> + +Calling `focus()` focuses the `street` input—even though it is nested inside a `<fieldset>` and `<label>`. `focus()` searches depth-first through all nested children, not just direct children of the Fragment. `focusLast()` does the same in reverse, and `blur()` removes focus if the currently focused element is within the Fragment. + +--- + +### <CanaryBadge /> Scrolling a group of elements into view {/*scrolling-group-into-view*/} + +Use `scrollIntoView` to scroll a Fragment's children into view without a wrapper element. Pass `true` (or omit the argument) to scroll the first child to the top. Pass `false` to scroll the last child to the bottom: + +<Sandpack> + +```js +import { Fragment, useRef } from 'react'; + +function ScrollableSection({ children }) { + const fragmentRef = useRef(null); + + return ( + <> + <div className="buttons"> + <button onClick={() => { + fragmentRef.current.scrollIntoView(); + }}> + Scroll to top + </button> + <button onClick={() => { + fragmentRef.current.scrollIntoView(false); + }}> + Scroll to bottom + </button> + </div> + <div className="container"> + <Fragment ref={fragmentRef}> + {children} + </Fragment> + </div> + </> + ); +} + +const items = []; +for (let i = 1; i <= 25; i++) { + items.push('Item ' + i); +} + +export default function App() { + return ( + <ScrollableSection> + <h3>Section Start</h3> + {items.map((item) => ( + <p key={item}>{item}</p> + ))} + <h3>Section End</h3> + </ScrollableSection> + ); +} +``` + +```css +.buttons { + display: flex; + gap: 8px; + margin-bottom: 10px; +} + +.container { + height: 200px; + overflow-y: auto; + border: 2px solid #c4c4c4; + border-radius: 4px; + padding: 10px; +} + +h3 { + margin: 4px 0; + /* Padding to handle offset of global sticky nav when scrolling for example */ + padding-top: 4em; + color: #1a73e8; +} + +p { + margin: 4px 0; +} ``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "latest" + } +} +``` + +</Sandpack> + --- -### <CanaryBadge /> Tracking visibility with Fragment refs {/*tracking-visibility-with-fragment-refs*/} +### <CanaryBadge /> Observing visibility without a wrapper element {/*observing-visibility-without-wrapper*/} -Fragment refs are useful for visibility tracking and intersection observation. This enables you to monitor when content becomes visible without requiring the child Components to expose refs: +Use `observeUsing` to attach an `IntersectionObserver` to all first-level DOM children of a Fragment. This lets you track visibility without requiring child components to expose `ref`s or adding a wrapper element: -```js {19,21,31-34} -import { Fragment, useRef, useLayoutEffect } from 'react'; +<Sandpack> -function VisibilityObserverFragment({ threshold = 0.5, onVisibilityChange, children }) { +```js +import { + Fragment, + useRef, + useLayoutEffect, + useState, +} from 'react'; +import Card from './Card'; + +function VisibleGroup({ onVisibilityChange, children }) { const fragmentRef = useRef(null); useLayoutEffect(() => { + const visibleElements = new Set(); const observer = new IntersectionObserver( (entries) => { - onVisibilityChange(entries.some(entry => entry.isIntersecting)) - }, - { threshold } + entries.forEach(e => { + if (e.isIntersecting) { + visibleElements.add(e.target); + } else { + visibleElements.delete(e.target); + } + }); + onVisibilityChange(visibleElements.size > 0); + } ); - - fragmentRef.current.observeUsing(observer); - return () => fragmentRef.current.unobserveUsing(observer); - }, [threshold, onVisibilityChange]); + const fragmentInstance = fragmentRef.current; + fragmentInstance.observeUsing(observer); + return () => { + fragmentInstance.unobserveUsing(observer); + }; + }, [onVisibilityChange]); return ( <Fragment ref={fragmentRef}> @@ -291,38 +810,258 @@ function VisibilityObserverFragment({ threshold = 0.5, onVisibilityChange, child ); } -function MyComponent() { - const handleVisibilityChange = (isVisible) => { - console.log('Component is', isVisible ? 'visible' : 'hidden'); - }; +export default function App() { + const [isVisible, setIsVisible] = useState(true); return ( - <VisibilityObserverFragment onVisibilityChange={handleVisibilityChange}> - <SomeThirdPartyComponent /> - <AnotherComponent /> - </VisibilityObserverFragment> + <div className={isVisible ? 'page visible' : 'page'}> + <div className="filler">Scroll down</div> + <VisibleGroup onVisibilityChange={setIsVisible}> + <Card title="First section" /> + <Card title="Second section" /> + </VisibleGroup> + <div className="filler">Scroll up</div> + </div> ); } ``` -This pattern is an alternative to Effect-based visibility logging, which is an anti-pattern in most cases. Relying on Effects alone does not guarantee that the rendered Component is observable by the user. +```css +.page { + transition: background 0.3s; +} + +.page.visible { + background: #d4edda; +} + +.filler { + height: 500px; + display: flex; + align-items: center; + justify-content: center; + color: #aaa; + font-size: 14px; +} + +.card { + padding: 16px; + background: white; + border: 1px solid #ddd; + border-radius: 8px; + margin: 8px 16px; + box-shadow: 0 1px 3px rgba(0,0,0,0.08); + font-weight: 600; + font-size: 14px; +} +``` + +```js src/Card.js hidden +export default function Card({ title }) { + return <div className="card">{title}</div>; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "latest" + } +} +``` + +</Sandpack> --- -### <CanaryBadge /> Focus management with Fragment refs {/*focus-management-with-fragment-refs*/} +### <CanaryBadge /> Caching a global IntersectionObserver {/*caching-global-intersection-observer*/} -Fragment refs provide focus management methods that work across all DOM nodes within the Fragment: +A common performance optimization for sites with many observers is to share a single IntersectionObserver per config and route its entries to the correct callbacks based on which element intersected. Fragment `ref`s support this same pattern through the `reactFragments` property. -```js -import { Fragment, useRef } from 'react'; +Each first-level DOM child of a Fragment with a `ref` has a `reactFragments` property: a `Set` of `FragmentInstance` objects that contain that element. When the shared observer fires, you can use this property to look up which `FragmentInstance` owns the intersecting element and run the right callbacks. + +<Sandpack> + +```js src/App.js active +import { useState, useCallback } from 'react'; +import ObservedGroup from './ObservedGroup'; +import Card from './Card'; + +export default function App() { + const [bgColor, setBgColor] = useState(null); + + const onGreen = useCallback((entry) => { + if (entry.isIntersecting) { + setBgColor('#d4edda'); + } + }, []); + + const onBlue = useCallback((entry) => { + if (entry.isIntersecting) { + setBgColor('#cce5ff'); + } + }, []); -function FocusFragment({ children }) { return ( - <Fragment ref={(fragmentInstance) => fragmentInstance?.focus()}> + <div className="page" style={{ + background: bgColor || 'white', + }}> + <div className="filler">Scroll down</div> + <ObservedGroup onIntersection={onGreen}> + <Card title="Green section" className="green" /> + </ObservedGroup> + <div className="filler" /> + <ObservedGroup onIntersection={onBlue}> + <Card title="Blue section" className="blue" /> + </ObservedGroup> + <div className="filler">Scroll up</div> + </div> + ); +} +``` + +```js src/ObservedGroup.js +import { + Fragment, + useRef, + useLayoutEffect, +} from 'react'; + +const callbackMap = new WeakMap(); +const observerCache = new Map(); + +function getOptionsKey(options) { + const root = options?.root ?? null; + const rootMargin = options?.rootMargin ?? '0px'; + const threshold = options?.threshold ?? 0; + return `${rootMargin}|${threshold}`; +} + +function getSharedObserver( + fragmentInstance, + onIntersection, + options, +) { + // Register this callback for the + // fragment instance. + const existing = + callbackMap.get(fragmentInstance); + callbackMap.set( + fragmentInstance, + existing + ? [...existing, onIntersection] + : [onIntersection], + ); + + const key = getOptionsKey(options); + if (observerCache.has(key)) { + return observerCache.get(key); + } + + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + // Look up which FragmentInstances own + // this element. + const fragmentInstances = + entry.target.reactFragments; + if (fragmentInstances) { + for (const inst of fragmentInstances) { + const callbacks = + callbackMap.get(inst) || []; + callbacks.forEach(cb => cb(entry)); + } + } + } + }, + options, + ); + + observerCache.set(key, observer); + return observer; +} + +export default function ObservedGroup({ + onIntersection, + options, + children, +}) { + const fragmentRef = useRef(null); + + useLayoutEffect(() => { + const fragmentInstance = fragmentRef.current; + const observer = getSharedObserver( + fragmentInstance, + onIntersection, + options, + ); + fragmentInstance.observeUsing(observer); + return () => { + fragmentInstance.unobserveUsing(observer); + callbackMap.delete(fragmentInstance); + }; + }, [onIntersection, options]); + + return ( + <Fragment ref={fragmentRef}> {children} </Fragment> ); } ``` -The `focus()` method focuses the first focusable element within the Fragment, while `focusLast()` focuses the last focusable element. +```css +.page { + transition: background 0.3s; +} + +.filler { + height: 500px; + display: flex; + align-items: center; + justify-content: center; + color: #aaa; + font-size: 14px; +} + +.card { + padding: 16px; + background: white; + border: 1px solid #ddd; + border-radius: 8px; + margin: 0 16px; + box-shadow: 0 1px 3px rgba(0,0,0,0.08); + font-weight: 600; + font-size: 14px; +} + +.card.green { + border-left: 3px solid #28a745; +} + +.card.blue { + border-left: 3px solid #007bff; +} +``` + +```js src/Card.js hidden +export default function Card({ title, className }) { + return <div className={'card' + (className ? ' ' + className : '')}>{title}</div>; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "latest" + } +} +``` + +</Sandpack> + +Multiple `ObservedGroup` components with the same options reuse a single `IntersectionObserver`. When either section scrolls into view, the shared observer fires and uses `reactFragments` to route the entry to the correct callback. diff --git a/src/content/reference/react/Suspense.md b/src/content/reference/react/Suspense.md index 1d7bd8f559..232ac469b9 100644 --- a/src/content/reference/react/Suspense.md +++ b/src/content/reference/react/Suspense.md @@ -23,12 +23,20 @@ title: <Suspense> ### `<Suspense>` {/*suspense*/} +<<<<<<< HEAD #### 参数 {/*props*/} * `children`:真正的 UI 渲染内容。如果 `children` 在渲染中被挂起,Suspense 边界将会渲染 `fallback`。 * `fallback`:真正的 UI 未渲染完成时代替其渲染的备用 UI,它可以是任何有效的 React 节点。后备方案通常是一个轻量的占位符,例如表示加载中的图标或者骨架屏。当 `children` 被挂起时,Suspense 将自动切换至渲染 `fallback`;当数据准备好时,又会自动切换至渲染 `children`。如果 `fallback` 在渲染中被挂起,那么将自动激活最近的 Suspense 边界。 +======= +#### Props {/*props*/} +* `children`: The actual UI you intend to render. If `children` suspends while rendering, the Suspense boundary will switch to rendering `fallback`. +* `fallback`: An alternate UI to render in place of the actual UI if it has not finished loading. Any valid React node is accepted, though in practice, a fallback is a lightweight placeholder view, such as a loading spinner or skeleton. Suspense will automatically switch to `fallback` when `children` suspends, and back to `children` when the data is ready. If `fallback` suspends while rendering, it will activate the closest parent Suspense boundary. +* <ExperimentalBadge /> **optional** `defer`: A boolean. When `true`, React may show the `fallback` first and render or stream `children` later, even when nothing in them suspends. Use it for content that is expensive to render. Defaults to `false`. +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca #### 注意 {/*caveats*/} +<<<<<<< HEAD - 在组件首次挂载前,如果组件被挂起,那么 React 将不会保留其任何状态。当组件完成加载后,React 将从头开始重新尝试渲染被挂起的组件树。 - 如果 Suspense 正在展示 React 组件树中的内容,那么当再次被挂起时,除非导致此处更新是由 [`startTransition`](/reference/react/startTransition) 或 [`useDeferredValue`](/reference/react/useDeferredValue) 引起,否则 Suspense 将展示 `fallback`。 - 如果 React 需要隐藏被再次挂起的可见内容,它将清理内容树中的 [layout Effect](/reference/react/useLayoutEffect)。当内容可以被再次展示时,React 将重新触发 layout Effect。这确保了测量 DOM 布局的 Effect 不会在内容不可见时运行。 @@ -37,6 +45,42 @@ title: <Suspense> --- ## 用法 {/*usage*/} +======= +- Suspense does not detect when data is fetched inside an Effect or event handler. It only activates in the [cases listed below.](#what-activates-a-suspense-boundary) +- React does not preserve any state for renders that got suspended before they were able to mount for the first time. When the component has loaded, React will retry rendering the suspended tree from scratch. +- If Suspense was displaying content for the tree, but then it suspended again, the `fallback` will be shown again unless the update causing it was caused by [`startTransition`](/reference/react/startTransition) or [`useDeferredValue`](/reference/react/useDeferredValue). +- React reveals suspended content at most once every 300ms, measured from the last reveal. Boundaries that become ready within that window are [revealed together](/blog/2025/10/01/react-19-2#batching-suspense-boundaries-for-ssr) rather than one at a time. +- If React needs to hide the already visible content because it suspended again, it will clean up [layout Effects](/reference/react/useLayoutEffect) in the content tree. When the content is ready to be shown again, React will fire the layout Effects again. This ensures that Effects measuring the DOM layout don't try to do this while the content is hidden. +- React includes under-the-hood optimizations like *Streaming Server Rendering* and *Selective Hydration* that are integrated with Suspense. Read [an architectural overview](https://github.com/reactwg/react-18/discussions/37) and watch [a technical talk](https://www.youtube.com/watch?v=pj5N-Khihgc) to learn more. + +--- + +### What activates a Suspense boundary {/*what-activates-a-suspense-boundary*/} + +A Suspense boundary waits for its content to be ready before revealing it. Any of the following keeps a boundary from revealing its content: + +- Lazy-loading component code with [`lazy`](/reference/react/lazy). +- Reading a Promise with [`use`](/reference/react/use), including data streamed from [Server Components](/reference/rsc/server-components) or loaded through a [Suspense-enabled framework](#suspense-enabled-frameworks). +- Loading a stylesheet rendered with [`<link rel="stylesheet">` and a `precedence` prop.](/reference/react-dom/components/link#special-rendering-behavior) React blocks the boundary until the stylesheet loads, up to a timeout. [See an example below.](#waiting-for-a-stylesheet-to-load) +- Waiting for a large boundary's HTML to arrive during streaming server rendering. Sending HTML takes time, so a boundary with enough content activates even when nothing in it suspends. React reveals the content as the HTML arrives. +- <CanaryBadge /> Loading fonts. Suspense doesn't wait for fonts by default, but a [`<ViewTransition>`](/reference/react/ViewTransition) update waits for new fonts to load, up to a timeout, so text doesn't flash with a fallback font. [See an example below.](#waiting-for-a-font-to-load) +- <CanaryBadge /> Loading images. Suspense doesn't wait for images by default, but during a [`<ViewTransition>`](/reference/react/ViewTransition) update, React blocks the boundary until the image loads, up to a timeout. Adding an `onLoad` handler opts a specific image out. [See an example below.](#waiting-for-an-image-to-load) +- <ExperimentalBadge /> Performing CPU-bound render work inside a [`<Suspense defer>`](#props) boundary. + +<Note> + +#### Suspense-enabled frameworks {/*suspense-enabled-frameworks*/} + +A *Suspense-enabled framework* gives you a way to read data in your component in a way that activates the closest Suspense boundary. The exact way you load your data depends on your framework, and you'll find the details in its documentation. Under the hood, a Suspense-enabled framework maintains a cache of Promises and calls [`use`](/reference/react/use) to suspend on a Promise. + +Without a framework, you can read a Promise with `use` directly, as long as the Promise is [cached so the same instance is reused across renders.](/reference/react/use#caching-promises-for-client-components) + +</Note> + +--- + +## Usage {/*usage*/} +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ### 当内容正在加载时显示后备方案 {/*displaying-a-fallback-while-content-is-loading*/} @@ -203,8 +247,9 @@ async function getAlbums() { </Sandpack> -<Note> +By contrast, code that fetches data outside of `use`, such as inside an Effect, does not activate the boundary: +<<<<<<< HEAD **只有启用了 Suspense 的数据源才会激活 Suspense 组件**,它们包括: - 支持 Suspense 的框架如 [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) 和 [Next.js](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming#streaming-with-suspense)。 @@ -216,8 +261,256 @@ Suspense **无法** 检测在 Effect 或事件处理程序中获取数据的情 在上面的 `Albums` 组件中,正确的数据加载方法取决于你使用的框架。如果你使用了支持 Suspense 的框架,你会在其数据获取文档中找到详细信息。 目前尚不支持在不使用固定框架的情况下进行启用 Suspense 的数据获取。实现支持 Suspense 数据源的要求是不稳定的,也没有文档。React 将在未来的版本中发布官方 API,用于与 Suspense 集成数据源。 +======= +<Sandpack> -</Note> +```js src/App.js hidden +import { useState } from 'react'; +import ArtistPage from './ArtistPage.js'; + +export default function App() { + const [show, setShow] = useState(false); + if (show) { + return ( + <ArtistPage + artist={{ + id: 'the-beatles', + name: 'The Beatles', + }} + /> + ); + } else { + return ( + <button onClick={() => setShow(true)}> + Open The Beatles artist page + </button> + ); + } +} +``` + +```js src/ArtistPage.js active +import { Suspense } from 'react'; +import EffectAlbums from './EffectAlbums.js'; + +export default function ArtistPage({ artist }) { + return ( + <> + <h1>{artist.name}</h1> + <Suspense fallback={<Loading />}> + <EffectAlbums artistId={artist.id} /> + </Suspense> + </> + ); +} +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca + +function Loading() { + return <h2>🌀 Loading...</h2>; +} +``` + +```js src/EffectAlbums.js +import { useState, useEffect } from 'react'; +import { fetchData } from './data.js'; + +export default function EffectAlbums({ artistId }) { + const [albums, setAlbums] = useState([]); + + useEffect(() => { + let active = true; + fetchData(`/${artistId}/albums`).then(result => { + if (active) { + setAlbums(result); + } + }); + return () => { + active = false; + }; + }, [artistId]); + + // Suspense can't see this fetch, so its fallback never + // shows. The list stays empty until the data arrives. + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} +``` + +```js src/data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url === '/the-beatles/albums') { + return await getAlbums(); + } else { + throw Error('Not implemented'); + } +} + +async function getAlbums() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 3000); + }); + + return [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; +} +``` + +</Sandpack> + +During streaming server rendering, a boundary also activates while its HTML is still streaming in. With any streaming server rendering API, React sends [the shell](/reference/react-dom/server/renderToPipeableStream#specifying-what-goes-into-the-shell) with the `fallback` first, then streams in each boundary's HTML and swaps out its `fallback` as that content arrives. Press "Render the page" to watch the page stream in: + +<Sandpack> + +```js src/App.js hidden +``` + +```html public/index.html +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8" /> + <title>Streaming SSR + + + +

+ + + +``` + +```js src/index.js +import { flushReadableStreamToFrame } from './demo-helpers.js'; +import { Suspense, use } from 'react'; +import { renderToReadableStream } from 'react-dom/server'; + +let posts = null; + +function Posts() { + const text = use(posts.promise); + return

{text}

; +} + +function ProfilePage() { + return ( + + +

Alice

+

Photographer and traveler.

+ ⌛ Loading posts...

}> + +
+ + + ); +} + +async function main(frame) { + posts = Promise.withResolvers(); + const stream = await renderToReadableStream(); + + // The posts resolve after the shell has streamed, so React + // streams their HTML in and swaps out the fallback. + setTimeout(() => { + posts.resolve( + 'Just got back from two weeks along the coast. The drive ' + + 'was longer than expected, but every stop was worth it. ' + + 'A full write-up and more photos are coming soon.' + ); + }, 1500); + + await flushReadableStreamToFrame(stream, frame); +} + +document.getElementById('render').addEventListener('click', () => { + main(document.getElementById('container')); +}); +``` + +```js src/demo-helpers.js hidden +export async function flushReadableStreamToFrame(readable, frame) { + const doc = frame.contentWindow.document; + const decoder = new TextDecoder(); + for await (const chunk of readable) { + doc.write(decoder.decode(chunk, { stream: true })); + } + doc.close(); +} +``` + + --- @@ -1992,15 +2285,108 @@ main { ### 在导航时重置 Suspense 边界 {/*resetting-suspense-boundaries-on-navigation*/} +<<<<<<< HEAD 在 Transition 发生时,React 将避免隐藏已经显示的内容。但是,如果你导航到具有不同参数的路由,你可能想告诉 React 它是 **不同** 的内容。你可以用 `key` 表示这一点: +======= +During a Transition, React avoids hiding already revealed content. However, when you navigate to *different* content, such as another user's profile, you'll want the boundary to show the fallback instead of the previous content. You can express this with a `key`: +>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca ```js ``` +<<<<<<< HEAD 想想在用户的个人资料页面中导航,然后暂停了。如果更新被包装在 Transition 中,它将不会触发已经可见内容的后备方案。这是预期的行为。 然而,现在想象一下你在两个不同的用户资料之间导航。在这种情况下,显示后备方案是有意义的。例如,一个用户的时间线是与另一个用户的时间线是 **不同的内容**。通过指定一个 `key`,你可以确保 React 将不同用户的个人资料视为不同的组件,并在导航期间重置 Suspense 边界。集成 Suspense 的路由应该自动执行此操作。 +======= +With a different `key`, React treats the profiles as different content and resets the Suspense boundary during navigation. The `key` can go on the boundary itself or on a component above it. Suspense-integrated routers should do this automatically. + +In the example below, opening the profile page loads the first profile. Pressing "Bob" navigates to a different profile, and the `key` resets the boundary, so the fallback shows instead of the previous user's bio. Try removing the `key`: the previous bio stays visible while the next one loads: + + + +```js src/App.js hidden +import { useState } from 'react'; +import ProfilePage from './ProfilePage.js'; + +export default function App() { + const [show, setShow] = useState(false); + if (show) { + return ; + } + return ( + + ); +} +``` + +```js src/ProfilePage.js active +import { Suspense, useState, startTransition } from 'react'; +import Bio from './Bio.js'; +import { fetchBio } from './data.js'; + +export default function ProfilePage() { + const [user, setUser] = useState(() => ({ + id: 'alice', + bioPromise: fetchBio('alice'), + })); + function navigate(id) { + startTransition(() => { + setUser({ id, bioPromise: fetchBio(id) }); + }); + } + return ( + <> + + + ⌛ Loading profile...

}> + +
+ + ); +} +``` + +```js src/Bio.js +import { use } from 'react'; + +export default function Bio({ bioPromise }) { + const bio = use(bioPromise); + return

{bio}

; +} +``` + +```js src/data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. + +export async function fetchBio(userId) { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 1500); + }); + + return userId === 'alice' + ? 'Alice is a photographer and traveler.' + : 'Bob collects vintage synthesizers.'; +} +``` + +```css +button { + margin-right: 8px; +} +``` + +
+>>>>>>> 6be2b020a0cabf2fd6dbff5c42c399b8ac323bca --- @@ -2029,7 +2415,876 @@ function Chat() { --- +<<<<<<< HEAD ## 故障排除 {/*troubleshooting*/} +======= +### Waiting for a stylesheet to load {/*waiting-for-a-stylesheet-to-load*/} + +A stylesheet rendered with [`` and a `precedence` prop](/reference/react-dom/components/link#special-rendering-behavior) blocks the Suspense boundary until the stylesheet loads, up to a timeout, so the content doesn't appear unstyled. + +In the example below, the `Card` component renders a stylesheet with `precedence`. Press "Show card": React shows the fallback until the stylesheet has loaded, and then reveals the card with its styles applied. + +For comparison, the second button performs the same update without React, in a separate document. Nothing waits for the stylesheet, so the card's text appears in a fallback font first and then switches: + + + +```js +import { Suspense, useState, startTransition } from 'react'; +import { freshStylesheetUrl } from './styles.js'; +import VanillaCard from './VanillaCard.js'; + +function Card({ href }) { + return ( + <> + +
This card uses a font from the stylesheet.
+ + ); +} + +export default function App() { + const [href, setHref] = useState(null); + return ( + <> + + {href && ( + ⌛ Loading styles...

}> + +
+ )} +
+ + + ); +} +``` + +```js src/VanillaCard.js +import { useRef } from 'react'; +import { freshStylesheetUrl } from './styles.js'; + +export default function VanillaCard() { + const ref = useRef(null); + function show() { + const doc = ref.current.contentWindow.document; + doc.open(); + doc.write(` + +
This card uses a font from the stylesheet.
+ + `); + doc.close(); + } + return ( + <> + +