diff --git a/.gitignore b/.gitignore index d8bec488ba..7bf71dbc5d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ yarn-error.log* # external fonts public/fonts/**/Optimistic_*.woff2 + +# rss +public/rss.xml diff --git a/package.json b/package.json index d752017808..ddbe925053 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,12 @@ "prettier:diff": "yarn nit:source", "lint-heading-ids": "node scripts/headingIdLinter.js", "fix-headings": "node scripts/headingIdLinter.js --fix", - "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids", + "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids rss", "tsc": "tsc --noEmit", "start": "next start", "postinstall": "patch-package && (is-ci || husky install .husky)", - "check-all": "npm-run-all prettier lint:fix tsc" + "check-all": "npm-run-all prettier lint:fix tsc rss", + "rss": "node scripts/generateRss.js" }, "dependencies": { "@codesandbox/sandpack-react": "2.13.5", diff --git a/public/images/team/lauren.jpg b/public/images/team/lauren.jpg index 1485cf8ff5..cb08b97254 100644 Binary files a/public/images/team/lauren.jpg and b/public/images/team/lauren.jpg differ diff --git a/scripts/generateRss.js b/scripts/generateRss.js new file mode 100644 index 0000000000..e0f3d5561d --- /dev/null +++ b/scripts/generateRss.js @@ -0,0 +1,6 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ +const {generateRssFeed} = require('../src/utils/rss'); + +generateRssFeed(); diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index b1cafd966a..f22af9a386 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -21,6 +21,7 @@ import type {RouteItem} from 'components/Layout/getRouteMeta'; import {HomeContent} from './HomeContent'; import {TopNav} from './TopNav'; import cn from 'classnames'; +import Head from 'next/head'; import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock'); @@ -117,7 +118,21 @@ export function Page({children, toc, routeTree, meta, section}: PageProps) { image={`/images/og-` + section + '.png'} searchOrder={searchOrder} /> +<<<<<<< HEAD {/* */} +======= + {(isHomePage || isBlogIndex) && ( + + + + )} + +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 ); -function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { +export function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { let message: React.ReactNode | null; if (typeof children === 'string') { message = children; @@ -38,7 +42,10 @@ function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { } return ( -
+
@@ -73,4 +80,72 @@ function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { ); } -export default ConsoleBlock; +export function ConsoleBlockMulti({children}: ConsoleBlockMultiProps) { + return ( +
+
+
+ +
+
+
+ Console +
+
+ + + +
+
+
+
+ {children} +
+
+ ); +} + +export function ConsoleLogLine({children, level}: ConsoleBlockProps) { + let message: React.ReactNode | null; + if (typeof children === 'string') { + message = children; + } else if (isValidElement(children)) { + message = children.props.children; + } else if (Array.isArray(children)) { + message = children.reduce((result, child) => { + if (typeof child === 'string') { + result += child; + } else if (isValidElement(child)) { + // @ts-ignore + result += child.props.children; + } + return result; + }, ''); + } + + return ( +
+ {level === 'error' && ( + + )} + {level === 'warning' && ( + + )} +
+ {message} +
+
+ ); +} diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx index db85a6398d..c8002ada61 100644 --- a/src/components/MDX/MDXComponents.tsx +++ b/src/components/MDX/MDXComponents.tsx @@ -8,7 +8,7 @@ import cn from 'classnames'; import CodeBlock from './CodeBlock'; import {CodeDiagram} from './CodeDiagram'; -import ConsoleBlock from './ConsoleBlock'; +import {ConsoleBlock, ConsoleLogLine, ConsoleBlockMulti} from './ConsoleBlock'; import ExpandableCallout from './ExpandableCallout'; import ExpandableExample from './ExpandableExample'; import {H1, H2, H3, H4, H5} from './Heading'; @@ -420,6 +420,8 @@ export const MDXComponents = { pre: CodeBlock, CodeDiagram, ConsoleBlock, + ConsoleBlockMulti, + ConsoleLogLine, DeepDive: (props: { children: React.ReactNode; title: string; diff --git a/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md b/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md index b0e67b6cd2..4f2501e7d5 100644 --- a/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md +++ b/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "介绍零打包大小的 React 服务器组件" +======= +title: "Introducing Zero-Bundle-Size React Server Components" +author: Dan Abramov, Lauren Tan, Joseph Savona, and Sebastian Markbage +date: 2020/12/21 +description: 2020 has been a long year. As it comes to an end we wanted to share a special Holiday Update on our research into zero-bundle-size React Server Components. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2020 年 12 月 21 日 [Dan Abramov](https://twitter.com/dan_abramov)、[Lauren Tan](https://twitter.com/potetotes)、[Joseph Savona](https://twitter.com/en_JS) 与 [Sebastian Markbåge](https://twitter.com/sebmarkbage) 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 f41836ac73..d484ddb83d 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 @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "React 18 发布计划" +======= +title: "The Plan for React 18" +author: Andrew Clark, Brian Vaughn, Christine Abernathy, Dan Abramov, Rachel Nabors, Rick Hanlon, Sebastian Markbage, and Seth Webster +date: 2021/06/08 +description: The React team is excited to share a few updates. We’ve started work on the React 18 release, which will be our next major version. We’ve created a Working Group to prepare the community for gradual adoption of new features in React 18. We’ve published a React 18 Alpha so that library authors can try it and provide feedback... +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2021 年 7 月 8 日 [Andrew Clark](https://twitter.com/acdlite)、[Brian Vaughn](https://github.com/bvaughn)、[Christine Abernathy](https://twitter.com/abernathyca)、[Dan Abramov](https://twitter.com/dan_abramov)、[Rachel Nabors](https://twitter.com/rachelnabors)、[Rick Hanlon](https://twitter.com/rickhanlonii)、[Sebastian Markbåge](https://twitter.com/sebmarkbage) 与 [Seth Webster](https://twitter.com/sethwebster) diff --git a/src/content/blog/2021/12/17/react-conf-2021-recap.md b/src/content/blog/2021/12/17/react-conf-2021-recap.md index b9c1f99ee1..27b05a0817 100644 --- a/src/content/blog/2021/12/17/react-conf-2021-recap.md +++ b/src/content/blog/2021/12/17/react-conf-2021-recap.md @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "回顾 React Conf 2021" +======= +title: "React Conf 2021 Recap" +author: Jesslyn Tannady and Rick Hanlon +date: 2021/12/17 +description: Last week we hosted our 6th React Conf. In previous years, we've used the React Conf stage to deliver industry changing announcements such as React Native and React Hooks. This year, we shared our multi-platform vision for React, starting with the release of React 18 and gradual adoption of concurrent features. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2021 年 12 月 17 日 [Jesslyn Tannady](https://twitter.com/jtannady) 与 [Rick Hanlon](https://twitter.com/rickhanlonii) 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 8bf263688d..a3b0525216 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 @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "如何升级到 React 18" +======= +title: "How to Upgrade to React 18" +author: Rick Hanlon +date: 2022/03/08 +description: As we shared in the release post, React 18 introduces features powered by our new concurrent renderer, with a gradual adoption strategy for existing applications. In this post, we will guide you through the steps for upgrading to React 18. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2022 年 8 月 3 日 [Rick Hanlon](https://twitter.com/rickhanlonii) diff --git a/src/content/blog/2022/03/29/react-v18.md b/src/content/blog/2022/03/29/react-v18.md index 8452f558a5..eb419fb63f 100644 --- a/src/content/blog/2022/03/29/react-v18.md +++ b/src/content/blog/2022/03/29/react-v18.md @@ -1,5 +1,8 @@ --- title: "React v18.0" +author: The React Team +date: 2022/03/08 +description: React 18 is now available on npm! In our last post, we shared step-by-step instructions for upgrading your app to React 18. In this post, we'll give an overview of what's new in React 18, and what it means for the future. --- 2022 年 3 月 29 日,由 [React 团队](/community/team) 发布 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 04209debfe..f8dcfaa8ea 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 @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "React Labs:我们正在努力的方向——2022 年 6 月" +======= +title: "React Labs: What We've Been Working On – June 2022" +author: Andrew Clark, Dan Abramov, Jan Kassens, Joseph Savona, Josh Story, Lauren Tan, Luna Ruan, Mengdi Chen, Rick Hanlon, Robert Zhang, Sathya Gunasekaran, Sebastian Markbage, and Xuan Huang +date: 2022/06/15 +description: React 18 was years in the making, and with it brought valuable lessons for the React team. Its release was the result of many years of research and exploring many paths. Some of those paths were successful; many more were dead-ends that led to new insights. One lesson we’ve learned is that it’s frustrating for the community to wait for new features without having insight into these paths that we’re exploring. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2022 年 6 月 15 日 [Andrew Clark](https://twitter.com/acdlite)、[Dan Abramov](https://twitter.com/dan_abramov)、[Jan Kassens](https://twitter.com/kassens)、[Joseph Savona](https://twitter.com/en_JS)、[Josh Story](https://twitter.com/joshcstory)、[Lauren Tan](https://twitter.com/potetotes)、[Luna Ruan](https://twitter.com/lunaruan)、[Mengdi Chen](https://twitter.com/mengdi_en)、[Rick Hanlon](https://twitter.com/rickhanlonii)、[Robert Zhang](https://twitter.com/jiaxuanzhang01)、[Sathya Gunasekaran](https://twitter.com/_gsathya)、[Sebastian Markbåge](https://twitter.com/sebmarkbage) 与 [Xuan Huang](https://twitter.com/Huxpro) diff --git a/src/content/blog/2023/03/16/introducing-react-dev.md b/src/content/blog/2023/03/16/introducing-react-dev.md index 2ada4945f9..126be7bbea 100644 --- a/src/content/blog/2023/03/16/introducing-react-dev.md +++ b/src/content/blog/2023/03/16/introducing-react-dev.md @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "介绍 react.dev" +======= +title: "Introducing react.dev" +author: Dan Abramov and Rachel Nabors +date: 2023/03/16 +description: Today we are thrilled to launch react.dev, the new home for React and its documentation. In this post, we would like to give you a tour of the new site. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2023 年 3 月 16 日 [Dan Abramov](https://twitter.com/dan_abramov) 与 [Rachel Nabors](https://twitter.com/rachelnabors) diff --git a/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md b/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md index b5666fabd0..b56daab752 100644 --- a/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md +++ b/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "React Labs:我们正在努力的方向——2023 年 3 月" +======= +title: "React Labs: What We've Been Working On – March 2023" +author: Joseph Savona, Josh Story, Lauren Tan, Mengdi Chen, Samuel Susla, Sathya Gunasekaran, Sebastian Markbage, and Andrew Clark +date: 2023/03/22 +description: In React Labs posts, we write about projects in active research and development. We've made significant progress on them since our last update, and we'd like to share what we learned. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2023 年 3 月 22 日 [Joseph Savona](https://twitter.com/en_JS)、[Josh Story](https://twitter.com/joshcstory)、[Lauren Tan](https://twitter.com/potetotes)、[Mengdi Chen](https://twitter.com/mengdi_en)、[Samuel Susla](https://twitter.com/SamuelSusla)、[Sathya Gunasekaran](https://twitter.com/_gsathya)、[Sebastian Markbåge](https://twitter.com/sebmarkbage) 与 [Andrew Clark](https://twitter.com/acdlite) diff --git a/src/content/blog/2023/05/03/react-canaries.md b/src/content/blog/2023/05/03/react-canaries.md index 94fcea2fac..a299c251d3 100644 --- a/src/content/blog/2023/05/03/react-canaries.md +++ b/src/content/blog/2023/05/03/react-canaries.md @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "React Canaries:在 Meta 之外逐步推出新功能" +======= +title: "React Canaries: Enabling Incremental Feature Rollout Outside Meta" +author: Dan Abramov, Sophie Alpert, Rick Hanlon, Sebastian Markbage, and Andrew Clark +date: 2023/05/03 +description: We'd like to offer the React community an option to adopt individual new features as soon as their design is close to final, before they're released in a stable version--similar to how Meta has long used bleeding-edge versions of React internally. We are introducing a new officially supported [Canary release channel](/community/versioning-policy#canary-channel). It lets curated setups like frameworks decouple adoption of individual React features from the React release schedule. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2023 年 5 月 3 日 [Dan Abramov](https://twitter.com/dan_abramov)、[Sophie Alpert](https://twitter.com/sophiebits)、[Rick Hanlon](https://twitter.com/rickhanlonii)、[Sebastian Markbåge](https://twitter.com/sebmarkbage) 与 [Andrew Clark](https://twitter.com/acdlite) diff --git a/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md b/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md index 85a3b0ac6e..204098d4d3 100644 --- a/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md +++ b/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md @@ -1,5 +1,12 @@ --- +<<<<<<< HEAD title: "React Labs:我们正在努力的方向——2024 年 2 月" +======= +title: "React Labs: What We've Been Working On – February 2024" +author: Joseph Savona, Ricky Hanlon, Andrew Clark, Matt Carroll, and Dan Abramov +date: 2024/02/15 +description: In React Labs posts, we write about projects in active research and development. We’ve made significant progress since our last update, and we’d like to share our progress. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- 2024 年 2 月 15 日 [Joseph Savona](https://twitter.com/en_JS)、[Ricky Hanlon](https://twitter.com/rickhanlonii)、[Andrew Clark](https://twitter.com/acdlite)、[Matt Carroll](https://twitter.com/mattcarrollcode) 与 [Dan Abramov](https://twitter.com/dan_abramov) @@ -52,7 +59,11 @@ JavaScript 是一个因其松散规则和动态特性而闻名的具有挑战性 ``` +<<<<<<< HEAD `action` 函数可以同步或异步执行。你可以在客户端使用标准 JavaScript 定义它们,也可以在服务器上使用 [`'use server'`](/reference/react/use-server) 指示符。当使用 action 时,React 将帮助管理数据提交的生命周期,提供类似 [`useFormStatus`](/reference/react-dom/hooks/useFormStatus) 和 [`useFormState`](/reference/react-dom/hooks/useFormState) 的 Hook,以访问表单操作的当前 state 与响应。 +======= +The `action` function can operate synchronously or asynchronously. You can define them on the client side using standard JavaScript or on the server with the [`'use server'`](/reference/rsc/use-server) directive. When using an action, React will manage the life cycle of the data submission for you, providing hooks like [`useFormStatus`](/reference/react-dom/hooks/useFormStatus), and [`useActionState`](/reference/react/useActionState) to access the current state and response of the form action. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 默认情况下,Action 在 [transition](/reference/react/useTransition) 中提交,使当前页面在操作处理过程中保持交互性。由于 Action 支持异步函数,我们还添加了在 transitions 中使用 `async/await` 的功能,这允许在异步请求(如 `fetch`)开始时使用转换的 `isPending` 状态显示待处理 UI,并在应用更新时始终显示待处理 UI。 @@ -72,13 +83,21 @@ Canaries 是我们开发 React 的一种变化。以前,功能会在 Meta 内 React 服务器组件、资源加载、文档元数据与 Action 都已经加入了 React Canary,并且我们已经在 react.dev 上为这些功能添加了文档: +<<<<<<< HEAD - **指示符**:[`"use client"`](/reference/react/use-client) 与 [`"use server"`](/reference/react/use-server) 是设计用于全栈 React 框架的打包功能。它们标记了两个环境之间的“分割点”:use client 指示符指示打包工具生成一个 ` +``` + +### Libraries depending on React internals may block upgrades {/*libraries-depending-on-react-internals-may-block-upgrades*/} + +This release includes changes to React internals that may impact libraries that ignore our pleas to not use internals like `SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`. These changes are necessary to land improvements in React 19, and will not break libraries that follow our guidelines. + +Based on our [Versioning Policy](https://react.dev/community/versioning-policy#what-counts-as-a-breaking-change), these updates are not listed as breaking changes, and we are not including docs for how to upgrade them. The recommendation is to remove any code that depends on internals. + +To reflect the impact of using internals, we have renamed the `SECRET_INTERNALS` suffix to: + +`_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE` + +In the future we will more aggressively block accessing internals from React to discourage usage and ensure users are not blocked from upgrading. + +## TypeScript changes {/*typescript-changes*/} + +### Removed deprecated TypeScript types {/*removed-deprecated-typescript-types*/} + +We've cleaned up the TypeScript types based on the removed APIs in React 19. Some of the removed have types been moved to more relevant packages, and others are no longer needed to describe React's behavior. + + +We've published [`types-react-codemod`](https://github.com/eps1lon/types-react-codemod/) to migrate most type related breaking changes: + +```bash +npx types-react-codemod@latest preset-19 ./path-to-app +``` + +If you have a lot of unsound access to `element.props`, you can run this additional codemod: + +```bash +npx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files +``` + + + +Check out [`types-react-codemod`](https://github.com/eps1lon/types-react-codemod/) for a list of supported replacements. If you feel a codemod is missing, it can be tracked in the [list of missing React 19 codemods](https://github.com/eps1lon/types-react-codemod/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22React+19%22+label%3Aenhancement). + + +### `ref` cleanups required {/*ref-cleanup-required*/} + +_This change is included in the `react-19` codemod preset as [`no-implicit-ref-callback-return +`](https://github.com/eps1lon/types-react-codemod/#no-implicit-ref-callback-return)._ + +Due to the introduction of ref cleanup functions, returning anything else from a ref callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns: + +```diff [[1, 1, "("], [1, 1, ")"], [2, 2, "{", 15], [2, 2, "}", 1]] +-
(instance = current)} /> ++
{instance = current}} /> +``` + +The original code returned the instance of the `HTMLDivElement` and TypeScript wouldn't know if this was supposed to be a cleanup function or not. + +### `useRef` requires an argument {/*useref-requires-argument*/} + +_This change is included in the `react-19` codemod preset as [`refobject-defaults`](https://github.com/eps1lon/types-react-codemod/#refobject-defaults)._ + +A long-time complaint of how TypeScript and React work has been `useRef`. We've changed the types so that `useRef` now requires an argument. This significantly simplifies its type signature. It'll now behave more like `createContext`. + +```ts +// @ts-expect-error: Expected 1 argument but saw none +useRef(); +// Passes +useRef(undefined); +// @ts-expect-error: Expected 1 argument but saw none +createContext(); +// Passes +createContext(undefined); +``` + +This now also means that all refs are mutable. You'll no longer hit the issue where you can't mutate a ref because you initialised it with `null`: + +```ts +const ref = useRef(null); + +// Cannot assign to 'current' because it is a read-only property +ref.current = 1; +``` + +`MutableRef` is now deprecated in favor of a single `RefObject` type which `useRef` will always return: + +```ts +interface RefObject { + current: T +} + +declare function useRef: RefObject +``` + +`useRef` still has a convenience overload for `useRef(null)` that automatically returns `RefObject`. To ease migration due to the required argument for `useRef`, a convenience overload for `useRef(undefined)` was added that automatically returns `RefObject`. + +Check out [[RFC] Make all refs mutable](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/64772) for prior discussions about this change. + +### Changes to the `ReactElement` TypeScript type {/*changes-to-the-reactelement-typescript-type*/} + +_This change is included in the [`react-element-default-any-props`](https://github.com/eps1lon/types-react-codemod#react-element-default-any-props) codemod._ + +The `props` of React elements now default to `unknown` instead of `any` if the element is typed as `ReactElement`. This does not affect you if you pass a type argument to `ReactElement`: + +```ts +type Example2 = ReactElement<{ id: string }>["props"]; +// ^? { id: string } +``` + +But if you relied on the default, you now have to handle `unknown`: + +```ts +type Example = ReactElement["props"]; +// ^? Before, was 'any', now 'unknown' +``` + +You should only need it if you have a lot of legacy code relying on unsound access of element props. Element introspection only exists as an escape hatch, and you should make it explicit that your props access is unsound via an explicit `any`. + +### The JSX namespace in TypeScript {/*the-jsx-namespace-in-typescript*/} +This change is included in the `react-19` codemod preset as [`scoped-jsx`](https://github.com/eps1lon/types-react-codemod#scoped-jsx) + +A long-time request is to remove the global `JSX` namespace from our types in favor of `React.JSX`. This helps prevent pollution of global types which prevents conflicts between different UI libraries that leverage JSX. + +You'll now need to wrap module augmentation of the JSX namespace in `declare module "....": + +```diff +// global.d.ts ++ declare module "react" { + namespace JSX { + interface IntrinsicElements { + "my-element": { + myElementProps: string; + }; + } + } ++ } +``` + +The exact module specifier depends on the JSX runtime you specified in the `compilerOptions` of your `tsconfig.json`: + +- For `"jsx": "react-jsx"` it would be `react/jsx-runtime`. +- For `"jsx": "react-jsxdev"` it would be `react/jsx-dev-runtime`. +- For `"jsx": "react"` and `"jsx": "preserve"` it would be `react`. + +### Better `useReducer` typings {/*better-usereducer-typings*/} + +`useReducer` now has improved type inference thanks to [@mfp22](https://github.com/mfp22). + +However, this required a breaking change where `useReducer` doesn't accept the full reducer type as a type parameter but instead either needs none (and rely on contextual typing) or needs both the state and action type. + +The new best practice is _not_ to pass type arguments to `useReducer`. +```diff +- useReducer>(reducer) ++ useReducer(reducer) +``` +This may not work in edge cases where you can explicitly type the state and action, by passing in the `Action` in a tuple: +```diff +- useReducer>(reducer) ++ useReducer(reducer) +``` +If you define the reducer inline, we encourage to annotate the function parameters instead: +```diff +- useReducer>((state, action) => state) ++ useReducer((state: State, action: Action) => state) +``` +This is also what you'd also have to do if you move the reducer outside of the `useReducer` call: + +```ts +const reducer = (state: State, action: Action) => state; +``` + +## Changelog {/*changelog*/} + +### Other breaking changes {/*other-breaking-changes*/} + +- **react-dom**: Error for javascript URLs in src/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_runWithPrioirty` [#28271](https://github.com/facebook/react/pull/28271) +- **react-is**: Remove deprecated methods from `react-is` [28224](https://github.com/facebook/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) + +We'll publish the full changelog with the stable release of React 19. + +--- + +Thanks to [Andrew Clark](https://twitter.com/acdlite), [Eli White](https://twitter.com/Eli_White), [Jack Pope](https://github.com/jackpope), [Jan Kassens](https://github.com/kassens), [Josh Story](https://twitter.com/joshcstory), [Matt Carroll](https://twitter.com/mattcarrollcode), [Noah Lemen](https://twitter.com/noahlemen), [Sophie Alpert](https://twitter.com/sophiebits), and [Sebastian Silbermann](https://twitter.com/sebsilbermann) for reviewing and editing this post. diff --git a/src/content/blog/2024/04/25/react-19.md b/src/content/blog/2024/04/25/react-19.md new file mode 100644 index 0000000000..8d6749c4dc --- /dev/null +++ b/src/content/blog/2024/04/25/react-19.md @@ -0,0 +1,776 @@ +--- +title: "React 19 Beta" +author: The React Team +date: 2024/04/25 +description: React 19 Beta is now available on npm! In this post, we'll give an overview of the new features in React 19, and how you can adopt them. +--- + +April 25, 2024 by [The React Team](/community/team) + +--- + + + +This beta release is for libraries to prepare for React 19. App developers should upgrade to 18.3.0 and wait for React 19 stable as we work with libraries and make changes based on feedback. + + + + + +React 19 Beta is now available on npm! + + + +In our [React 19 Beta Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide), we shared step-by-step instructions for upgrading your app to React 19 Beta. In this post, we'll give an overview of the new features in React 19, and how you can adopt them. + +- [What's new in React 19](#whats-new-in-react-19) +- [Improvements in React 19](#improvements-in-react-19) +- [How to upgrade](#how-to-upgrade) + +For a list of breaking changes, see the [Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide). + +--- + +## What's new in React 19 {/*whats-new-in-react-19*/} + +### Actions {/*actions*/} + +A common use case in React apps is to perform a data mutation and then update state in response. For example, when a user submits a form to change their name, you will make an API request, and then handle the response. In the past, you would need to handle pending states, errors, optimistic updates, and sequential requests manually. + +For example, you could handle the pending and error state in `useState`: + +```js +// Before Actions +function UpdateName({}) { + const [name, setName] = useState(""); + const [error, setError] = useState(null); + const [isPending, setIsPending] = useState(false); + + const handleSubmit = async () => { + setIsPending(true); + const error = await updateName(name); + setIsPending(false); + if (error) { + setError(error); + return; + } + redirect("/path"); + }; + + return ( +
+ setName(event.target.value)} /> + + {error &&

{error}

} +
+ ); +} +``` + +In React 19, we're adding support for using async functions in transitions to handle pending states, errors, forms, and optimistic updates automatically. + +For example, you can use `useTransition` to handle the pending state for you: + +```js +// Using pending state from Actions +function UpdateName({}) { + const [name, setName] = useState(""); + const [error, setError] = useState(null); + const [isPending, startTransition] = useTransition(); + + const handleSubmit = async () => { + startTransition(async () => { + const error = await updateName(name); + if (error) { + setError(error); + return; + } + redirect("/path"); + }) + }; + + return ( +
+ setName(event.target.value)} /> + + {error &&

{error}

} +
+ ); +} +``` + +The async transition will immediately set the `isPending` state to true, make the async request(s), and switch `isPending` to false after any transitions. This allows you to keep the current UI responsive and interactive while the data is changing. + + + +#### By convention, functions that use async transitions are called "Actions". {/*by-convention-functions-that-use-async-transitions-are-called-actions*/} + +Actions automatically manage submitting data for you: + +- **Pending state**: Actions provide a pending state that starts at the beginning of a request and automatically resets when the final state update is committed. +- **Optimistic updates**: Actions support the new [`useOptimistic`](#new-feature-optimistic-updates) hook so you can show users instant feedback while the requests are submitting. +- **Error handling**: Actions provide error handling so you can display Error Boundaries when a request fails, and revert optimistic updates to their original value automatically. +- **Forms**: `
` elements now support passing functions to the `action` and `formAction` props. Passing functions to the `action` props use Actions by default and reset the form automatically after submission. + + + +Building on top of Actions, React 19 introduces [`useOptimistic`](#new-hook-optimistic-updates) to manage optimistic updates, and a new hook [`React.useActionState`](#new-hook-useactionstate) to handle common cases for Actions. In `react-dom` we're adding [`` Actions](#form-actions) to manage forms automatically and [`useFormStatus`](#new-hook-useformstatus) to support the common cases for Actions in forms. + +In React 19, the above example can be simplified to: + +```js +// Using Actions and useActionState +function ChangeName({ name, setName }) { + const [error, submitAction, isPending] = useActionState( + async (previousState, formData) => { + const error = await updateName(formData.get("name")); + if (error) { + return error; + } + redirect("/path"); + } + ); + + return ( + + + + {error &&

{error}

} +
+ ); +} +``` + +In the next section, we'll break down each of the new Action features in React 19. + +### New hook: `useActionState` {/*new-hook-useactionstate*/} + +To make the common cases easier for Actions, we've added a new hook called `useActionState`: + +```js +const [error, submitAction, isPending] = useActionState(async (previousState, newName) => { + const error = await updateName(newName); + if (error) { + // You can return any result of the action. + // Here, we return only the error. + return error; + } + + // handle success +}); +``` + +`useActionState` accepts a function (the "Action"), and returns a wrapped Action to call. This works because Actions compose. When the wrapped Action is called, `useActionState` will return the last result of the Action as `data`, and the pending state of the Action as `pending`. + + + +`React.useActionState` was previously called `ReactDOM.useFormState` in the Canary releases, but we've renamed it and deprecated `useFormState`. + +See [#28491](https://github.com/facebook/react/pull/28491) for more info. + + + +For more information, see the docs for [`useActionState`](/reference/react/useActionState). + +### React DOM: `
` Actions {/*form-actions*/} + +Actions are also integrated with React 19's new `` features for `react-dom`. We've added support for passing functions as the `action` and `formAction` props of ``, ``, and `
}> + + + ) +} +``` + + + +#### `use` does not support promises created in render. {/*use-does-not-support-promises-created-in-render*/} + +If you try to pass a promise created in render to `use`, React will warn: + + + + + +A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework. + + + + + +To fix, you need to pass a promise from a suspense powered library or framework that supports caching for promises. In the future we plan to ship features to make it easier to cache promises in render. + + + +You can also read context with `use`, allowing you to read Context conditionally such as after early returns: + +```js {1,11} +import {use} from 'react'; +import ThemeContext from './ThemeContext' + +function Heading({children}) { + if (children == null) { + return null; + } + + // This would not work with useContext + // because of the early return. + const theme = use(ThemeContext); + return ( +

+ {children} +

+ ); +} +``` + +The `use` API can only be called in render, similar to hooks. Unlike hooks, `use` can be called conditionally. In the future we plan to support more ways to consume resources in render with `use`. + +For more information, see the docs for [`use`](/reference/react/use). + + +## React Server Components {/*react-server-components*/} + +### Server Components {/*server-components*/} + +Server Components are a new option that allows rendering components ahead of time, before bundling, in an environment separate from your client application or SSR server. This separate environment is the "server" in React Server Components. Server Components can run once at build time on your CI server, or they can be run for each request using a web server. + +React 19 includes all of the React Server Components features included from the Canary channel. This means libraries that ship with Server Components can now target React 19 as a peer dependency with a `react-server` [export condition](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) for use in frameworks that support the [Full-stack React Architecture](/learn/start-a-new-react-project#which-features-make-up-the-react-teams-full-stack-architecture-vision). + + + + +#### How do I build support for Server Components? {/*how-do-i-build-support-for-server-components*/} + +While React Server Components in React 19 are stable and will not break between major versions, the underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. + +To support React Server Components as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement React Server Components in the future. + + + + +For more, see the docs for [React Server Components](/reference/rsc/server-components). + +### Server Actions {/*server-actions*/} + +Server Actions allow Client Components to call async functions executed on the server. + +When a Server Action is defined with the `"use server"` directive, your framework will automatically create a reference to the server function, and pass that reference to the Client Component. When that function is called on the client, React will send a request to the server to execute the function, and return the result. + + + +#### There is no directive for Server Components. {/*there-is-no-directive-for-server-components*/} + +A common misunderstanding is that Server Components are denoted by `"use server"`, but there is no directive for Server Components. The `"use server"` directive is used for Server Actions. + +For more info, see the docs for [Directives](/reference/rsc/directives). + + + +Server Actions can be created in Server Components and passed as props to Client Components, or they can be imported and used in Client Components. + +For more, see the docs for [React Server Actions](/reference/rsc/server-actions). + +## Improvements in React 19 {/*improvements-in-react-19*/} + +### `ref` as a prop {/*ref-as-a-prop*/} + +Starting in React 19, you can now access `ref` as a prop for function components: + +```js [[1, 1, "ref"], [1, 2, "ref", 45], [1, 6, "ref", 14]] +function MyInput({placeholder, ref}) { + return +} + +//... + +``` + +New function components will no longer need `forwardRef`, and we will be publishing a codemod to automatically update your components to use the new `ref` prop. In future versions we will deprecate and remove `forwardRef`. + + + +`refs` passed to classes are not passed as props since they reference the component instance. + + + +### Diffs for hydration errors {/*diffs-for-hydration-errors*/} + +We also improved error reporting for hydration errors in `react-dom`. For example, instead of logging multiple errors in DEV without any information about the mismatch: + + + + + +Warning: Text content did not match. Server: "Server" Client: "Client" +{' '}at span +{' '}at App + + + + + +Warning: An error occurred during hydration. The server HTML was replaced with client content in \. + + + + + +Warning: Text content did not match. Server: "Server" Client: "Client" +{' '}at span +{' '}at App + + + + + +Warning: An error occurred during hydration. The server HTML was replaced with client content in \. + + + + + +Uncaught Error: Text content does not match server-rendered HTML. +{' '}at checkForUnmatchedText +{' '}... + + + + + +We now log a single message with a diff of the mismatch: + + + + + + +Uncaught Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if an SSR-ed Client Component used:{'\n'} +\- A server/client branch `if (typeof window !== 'undefined')`. +\- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called. +\- Date formatting in a user's locale which doesn't match the server. +\- External changing data without sending a snapshot of it along with the HTML. +\- Invalid HTML tag nesting.{'\n'} +It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.{'\n'} +https://react.dev/link/hydration-mismatch {'\n'} +{' '}\ +{' '}\ +{'+ '}Client +{'- '}Server{'\n'} +{' '}at throwOnHydrationMismatch +{' '}... + + + + + +### `` as a provider {/*context-as-a-provider*/} + +In React 19, you can render `` as a provider instead of ``: + + +```js {5,7} +const ThemeContext = createContext(''); + +function App({children}) { + return ( + + {children} + + ); +} +``` + +New Context providers can use `` and we will be publishing a codemod to convert existing providers. In future versions we will deprecate ``. + +### Cleanup functions for refs {/*cleanup-functions-for-refs*/} + +We now support returning a cleanup function from `ref` callbacks: + +```js {7-9} + { + // ref created + + // NEW: return a cleanup function to reset + // the ref when element is removed from DOM. + return () => { + // ref cleanup + }; + }} +/> +``` + +When the component unmounts, React will call the cleanup function returned from the `ref` callback. This works for DOM refs, refs to class components, and `useImperativeHandle`. + + + +Previously, React would call `ref` functions with `null` when unmounting the component. If your `ref` returns a cleanup function, React will now skip this step. + +In future versions, we will deprecate calling refs with `null` when unmounting components. + + + +Due to the introduction of ref cleanup functions, returning anything else from a `ref` callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns, for example: + +```diff [[1, 1, "("], [1, 1, ")"], [2, 2, "{", 15], [2, 2, "}", 1]] +-
(instance = current)} /> ++
{instance = current}} /> +``` + +The original code returned the instance of the `HTMLDivElement` and TypeScript wouldn't know if this was _supposed_ to be a cleanup function or if you didn't want to return a cleanup function. + +You can codemod this pattern with [`no-implicit-ref-callback-return +`](https://github.com/eps1lon/types-react-codemod/#no-implicit-ref-callback-return). + +### `useDeferredValue` initial value {/*use-deferred-value-initial-value*/} + +We've added an `initialValue` option to `useDeferredValue`: + +```js [[1, 1, "deferredValue"], [1, 4, "deferredValue"], [2, 4, "''"]] +function Search({deferredValue}) { + // On initial render the value is ''. + // Then a re-render is scheduled with the deferredValue. + const value = useDeferredValue(deferredValue, ''); + + return ( + + ); +} +```` + +When initialValue is provided, `useDeferredValue` will return it as `value` for the initial render of the component, and schedules a re-render in the background with the deferredValue returned. + +For more, see [`useDeferredValue`](/reference/react/useDeferredValue). + +### Support for Document Metadata {/*support-for-metadata-tags*/} + +In HTML, document metadata tags like ``, `<link>`, and `<meta>` are reserved for placement in the `<head>` section of the document. In React, the component that decides what metadata is appropriate for the app may be very far from the place where you render the `<head>` or React does not render the `<head>` at all. In the past, these elements would need to be inserted manually in an effect, or by libraries like [`react-helmet`](https://github.com/nfl/react-helmet), and required careful handling when server rendering a React application. + +In React 19, we're adding support for rendering document metadata tags in components natively: + +```js {5-8} +function BlogPost({post}) { + return ( + <article> + <h1>{post.title}</h1> + <title>{post.title} + + + +

+ Eee equals em-see-squared... +

+ + ); +} +``` + +When React renders this component, it will see the `` `<link>` and `<meta>` tags, and automatically hoist them to the `<head>` section of document. By supporting these metadata tags natively, we're able to ensure they work with client-only apps, streaming SSR, and Server Components. + +<Note> + +#### You may still want a Metadata library {/*you-may-still-want-a-metadata-library*/} + +For simple use cases, rendering Document Metadata as tags may be suitable, but libraries can offer more powerful features like overriding generic metadata with specific metadata based on the current route. These features make it easier for frameworks and libraries like [`react-helmet`](https://github.com/nfl/react-helmet) to support metadata tags, rather than replace them. + +</Note> + +For more info, see the docs for [`<title>`](/reference/react-dom/components/title), [`<link>`](/reference/react-dom/components/link), and [`<meta>`](/reference/react-dom/components/meta). + +### Support for stylesheets {/*support-for-stylesheets*/} + +Stylesheets, both externally linked (`<link rel="stylesheet" href="...">`) and inline (`<style>...</style>`), require careful positioning in the DOM due to style precedence rules. Building a stylesheet capability that allows for composability within components is hard, so users often end up either loading all of their styles far from the components that may depend on them, or they use a style library which encapsulates this complexity. + +In React 19, we're addressing this complexity and providing even deeper integration into Concurrent Rendering on the Client and Streaming Rendering on the Server with built in support for stylesheets. If you tell React the `precedence` of your stylesheet it will manage the insertion order of the stylesheet in the DOM and ensure that the stylesheet (if external) is loaded before revealing content that depends on those style rules. + +```js {4,5,17} +function ComponentOne() { + return ( + <Suspense fallback="loading..."> + <link rel="stylesheet" href="foo" precedence="default" /> + <link rel="stylesheet" href="bar" precedence="high" /> + <article class="foo-class bar-class"> + {...} + </article> + </Suspense> + ) +} + +function ComponentTwo() { + return ( + <div> + <p>{...}</p> + <link rel="stylesheet" href="baz" precedence="default" /> <-- will be inserted between foo & bar + </div> + ) +} +``` + +During Server Side Rendering React will include the stylesheet in the `<head>`, which ensures that the browser will not paint until it has loaded. If the stylesheet is discovered late after we've already started streaming, React will ensure that the stylesheet is inserted into the `<head>` on the client before revealing the content of a Suspense boundary that depends on that stylesheet. + +During Client Side Rendering React will wait for newly rendered stylesheets to load before committing the render. If you render this component from multiple places within your application React will only include the stylesheet once in the document: + +```js {5} +function App() { + return <> + <ComponentOne /> + ... + <ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM + </> +} +``` + +For users accustomed to loading stylesheets manually this is an opportunity to locate those stylesheets alongside the components that depend on them allowing for better local reasoning and an easier time ensuring you only load the stylesheets that you actually depend on. + +Style libraries and style integrations with bundlers can also adopt this new capability so even if you don't directly render your own stylesheets, you can still benefit as your tools are upgraded to use this feature. + +For more details, read the docs for [`<link>`](/reference/react-dom/components/link) and [`<style>`](/reference/react-dom/components/style). + +### Support for async scripts {/*support-for-async-scripts*/} + +In HTML normal scripts (`<script src="...">`) and deferred scripts (`<script defer="" src="...">`) load in document order which makes rendering these kinds of scripts deep within your component tree challenging. Async scripts (`<script async="" src="...">`) however will load in arbitrary order. + +In React 19 we've included better support for async scripts by allowing you to render them anywhere in your component tree, inside the components that actually depend on the script, without having to manage relocating and deduplicating script instances. + +```js {4,15} +function MyComponent() { + return ( + <div> + <script async={true} src="..." /> + Hello World + </div> + ) +} + +function App() { + <html> + <body> + <MyComponent> + ... + <MyComponent> // won't lead to duplicate script in the DOM + </body> + </html> +} +``` + +In all rendering environments, async scripts will be deduplicated so that React will only load and execute the script once even if it is rendered by multiple different components. + +In Server Side Rendering, async scripts will be included in the `<head>` and prioritized behind more critical resources that block paint such as stylesheets, fonts, and image preloads. + +For more details, read the docs for [`<script>`](/reference/react-dom/components/script). + +### Support for preloading resources {/*support-for-preloading-resources*/} + +During initial document load and on client side updates, telling the Browser about resources that it will likely need to load as early as possible can have a dramatic effect on page performance. + +React 19 includes a number of new APIs for loading and preloading Browser resources to make it as easy as possible to build great experiences that aren't held back by inefficient resource loading. + +```js +import { prefetchDNS, preconnect, preload, preinit } from 'react-dom' +function MyComponent() { + preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly + preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font + preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet + prefetchDNS('https://...') // when you may not actually request anything from this host + preconnect('https://...') // when you will request something but aren't sure what +} +``` +```html +<!-- the above would result in the following DOM/HTML --> +<html> + <head> + <!-- links/scripts are prioritized by their utility to early loading, not call order --> + <link rel="prefetch-dns" href="https://..."> + <link rel="preconnect" href="https://..."> + <link rel="preload" as="font" href="https://.../path/to/font.woff"> + <link rel="preload" as="style" href="https://.../path/to/stylesheet.css"> + <script async="" src="https://.../path/to/some/script.js"></script> + </head> + <body> + ... + </body> +</html> +``` + +These APIs can be used to optimize initial page loads by moving discovery of additional resources like fonts out of stylesheet loading. They can also make client updates faster by prefetching a list of resources used by an anticipated navigation and then eagerly preloading those resources on click or even on hover. + +For more details see [Resource Preloading APIs](/reference/react-dom#resource-preloading-apis). + +### Compatibility with third-party scripts and extensions {/*compatibility-with-third-party-scripts-and-extensions*/} + +We've improved hydration to account for third-party scripts and browser extensions. + +When hydrating, if an element that renders on the client doesn't match the element found in the HTML from the server, React will force a client re-render to fix up the content. Previously, if an element was inserted by third-party scripts or browser extensions, it would trigger a mismatch error and client render. + +In React 19, unexpected tags in the `<head>` and `<body>` will be skipped over, avoiding the mismatch errors. If React needs to re-render the entire document due to an unrelated hydration mismatch, it will leave in place stylesheets inserted by third-party scripts and browser extensions. + +### Better error reporting {/*error-handling*/} + +We improved error handling in React 19 to remove duplication and provide options for handling caught and uncaught errors. For example, when there's an error in render caught by an Error Boundary, previously React would throw the error twice (once for the original error, then again after failing to automatically recover), and then call `console.error` with info about where the error occurred. + +This resulted in three errors for every caught error: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Uncaught Error: hit +{' '}at Throws +{' '}at renderWithHooks +{' '}... + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +Uncaught Error: hit<span className="ms-2 text-gray-30">{' <--'} Duplicate</span> +{' '}at Throws +{' '}at renderWithHooks +{' '}... + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +The above error occurred in the Throws component: +{' '}at Throws +{' '}at ErrorBoundary +{' '}at App{'\n'} +React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +In React 19, we log a single error with all the error information included: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Error: hit +{' '}at Throws +{' '}at renderWithHooks +{' '}...{'\n'} +The above error occurred in the Throws component: +{' '}at Throws +{' '}at ErrorBoundary +{' '}at App{'\n'} +React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. +{' '}at ErrorBoundary +{' '}at App + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +Additionally, we've added two new root options to complement `onRecoverableError`: + +- `onCaughtError`: called when React catches an error in an Error Boundary. +- `onUncaughtError`: called when an error is thrown and not caught by an Error Boundary. +- `onRecoverableError`: called when an error is thrown and automatically recovered. + +For more info and examples, see the docs for [`createRoot`](/reference/react-dom/client/createRoot) and [`hydrateRoot`](/reference/react-dom/client/hydrateRoot). + +### Support for Custom Elements {/*support-for-custom-elements*/} + +React 19 adds full support for custom elements and passes all tests on [Custom Elements Everywhere](https://custom-elements-everywhere.com/). + +In past versions, using Custom Elements in React has been difficult because React treated unrecognized props as attributes rather than properties. In React 19, we've added support for properties that works on the client and during SSR with the following strategy: + +- **Server Side Rendering**: props passed to a custom element will render as attributes if their type is a primitive value like `string`, `number`, or the value is `true`. Props with non-primitive types like `object`, `symbol`, `function`, or value `false` will be omitted. +- **Client Side Rendering**: props that match a property on the Custom Element instance will be assigned as properties, otherwise they will be assigned as attributes. + +Thanks to [Joey Arhar](https://github.com/josepharhar) for driving the design and implementation of Custom Element support in React. + + +#### How to upgrade {/*how-to-upgrade*/} +See the [React 19 Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide) for step-by-step instructions and a full list of breaking and notable changes. + + + diff --git a/src/content/blog/index.md b/src/content/blog/index.md index 67f39a25e4..d2efa3133c 100644 --- a/src/content/blog/index.md +++ b/src/content/blog/index.md @@ -10,7 +10,23 @@ title: React Blog <div className="sm:-mx-5 flex flex-col gap-5 mt-12"> +<<<<<<< HEAD <BlogCard title="React Labs:我们正在努力的方向——2024 年 2 月" date="February 15, 2024" url="/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024"> +======= +<BlogCard title="React 19 Beta " date="April 25, 2024" url="/blog/2024/04/25/react-19"> + +In the React 19 Beta Upgrade Guide, we shared step-by-step instructions for upgrading your app to React 19 Beta. In this post, we'll give an overview of the new features in React 19, and how you can adopt them. + +</BlogCard> + +<BlogCard title="React 19 Beta Upgrade Guide" date="April 25, 2024" url="/blog/2024/04/25/react-19-upgrade-guide"> + +The improvements added to React 19 require some breaking changes, but we've worked to make the upgrade as smooth as possible and we don't expect the changes to impact most apps. In this post, we will guide you through the steps for upgrading libraries to React 19 beta. + +</BlogCard> + +<BlogCard title="React Labs: What We've Been Working On – February 2024" date="February 15, 2024" url="/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024"> +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 在 React Labs 的文章中,我们讲述了正在进行研究与开发的项目。自从上次更新以来,我们在 React 编译器、新功能和 React 19 方面取得了重大进展,除此之外我们还想要分享我们从中学到的东西。 diff --git a/src/content/community/team.md b/src/content/community/team.md index c69a98360c..d3a2db92b1 100644 --- a/src/content/community/team.md +++ b/src/content/community/team.md @@ -43,11 +43,19 @@ React 核心团队成员全职致力于核心组件 API 的开发,负责 React </TeamMember> <TeamMember name="Lauren Tan" permalink="lauren-tan" photo="/images/team/lauren.jpg" github="poteto" twitter="potetotes" threads="potetotes" personal="no.lol" title="Engineer at Meta"> +<<<<<<< HEAD Lauren 的编程生涯在她首次发现 `<marquee>` 标签时达到了巅峰,自此以后,她就一直追求这种感觉。在为 React 除错之余,她喜欢在聊天中发送风趣的表情包,还喜欢和伴侣以及她的狗狗 Zelda 一起玩各种电子游戏。 </TeamMember> <TeamMember name="Luna Wei" permalink="luna-wei" photo="/images/team/luna-wei.jpg" github="lunaleaps" twitter="lunaleaps" threads="lunaleaps" title="Engineer at Meta"> Luna 在仅有 6 岁的时候,就从父亲那里学到了 Python 的基本原理。从那以后,她的学习之路一直畅通无阻。Luna 有志成为 Z 世代的一员,她的成功之路充满了对环境的倡导,城市园艺的热爱,以及与她的 Voo-Doo'd(如图)共享的宝贵时光。 +======= + 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 instead of Java. Lauren enjoys dropping cheeky memes in chat, playing video games with her partner, and petting her dog Zelda. +</TeamMember> + +<TeamMember name="Luna Wei" permalink="luna-wei" photo="/images/team/luna-wei.jpg" github="lunaleaps" twitter="lunaleaps" threads="lunaleaps" title="Engineer at Meta"> + Luna first learnt the fundamentals of python at the age of 6 from her father. Since then, she has been unstoppable. Luna aspires to be a gen z, and the road to success is paved with environmental advocacy, urban gardening and lots of quality time with her Voo-Doo’d (as pictured). +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 </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"> diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index 9477f9d2a4..52b6c5f366 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -220,18 +220,19 @@ li { <Sandpack> ```js -import { useRef } from 'react'; +import { useRef, useState } from "react"; export default function CatFriends() { const itemsRef = useRef(null); + const [catList, setCatList] = useState(setupCatList); - function scrollToId(itemId) { + function scrollToCat(cat) { const map = getMap(); - const node = map.get(itemId); + const node = map.get(cat); node.scrollIntoView({ - behavior: 'smooth', - block: 'nearest', - inline: 'center' + behavior: "smooth", + block: "nearest", + inline: "center", }); } @@ -246,34 +247,25 @@ export default function CatFriends() { return ( <> <nav> - <button onClick={() => scrollToId(0)}> - Tom - </button> - <button onClick={() => scrollToId(5)}> - Maru - </button> - <button onClick={() => scrollToId(9)}> - Jellylorum - </button> + <button onClick={() => scrollToCat(catList[0])}>Tom</button> + <button onClick={() => scrollToCat(catList[5])}>Maru</button> + <button onClick={() => scrollToCat(catList[9])}>Jellylorum</button> </nav> <div> <ul> - {catList.map(cat => ( + {catList.map((cat) => ( <li - key={cat.id} + key={cat} ref={(node) => { const map = getMap(); if (node) { - map.set(cat.id, node); + map.set(cat, node); } else { - map.delete(cat.id); + map.delete(cat); } }} > - <img - src={cat.imageUrl} - alt={'Cat #' + cat.id} - /> + <img src={cat} /> </li> ))} </ul> @@ -282,12 +274,13 @@ export default function CatFriends() { ); } -const catList = []; -for (let i = 0; i < 10; i++) { - catList.push({ - id: i, - imageUrl: 'https://placekitten.com/250/200?image=' + i - }); +function setupCatList() { + const catList = []; + for (let i = 0; i < 10; i++) { + catList.push("https://loremflickr.com/320/240/cat?lock=" + i); + } + + return catList; } ``` @@ -318,6 +311,16 @@ li { } ``` +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + } +} +``` + </Sandpack> 在这个例子中,`itemsRef` 保存的不是单个 DOM 节点,而是保存了包含列表项 ID 和 DOM 节点的 [Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)。([Ref 可以保存任何值!](/learn/referencing-values-with-refs)) 每个列表项上的 [`ref` 回调](/reference/react-dom/components/common#ref-callback)负责更新 Map: @@ -328,11 +331,19 @@ li { ref={node => { const map = getMap(); if (node) { +<<<<<<< HEAD // 添加到 Map map.set(cat.id, node); } else { // 从 Map 删除 map.delete(cat.id); +======= + // Add to the Map + map.set(cat, node); + } else { + // Remove from the Map + map.delete(cat); +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 } }} > @@ -340,6 +351,28 @@ li { 这使你可以之后从 Map 读取单个 DOM 节点。 +<Canary> + +This example shows another approach for managing the Map with a `ref` callback cleanup function. + +```js +<li + key={cat.id} + ref={node => { + const map = getMap(); + // Add to the Map + map.set(cat, node); + + return () => { + // Remove from the Map + map.delete(cat); + }; + }} +> +``` + +</Canary> + </DeepDive> ## 访问另一个组件的 DOM 节点 {/*accessing-another-components-dom-nodes*/} diff --git a/src/content/reference/react-dom/components/common.md b/src/content/reference/react-dom/components/common.md index 6fd6a35164..175b467cf0 100644 --- a/src/content/reference/react-dom/components/common.md +++ b/src/content/reference/react-dom/components/common.md @@ -257,11 +257,40 @@ title: "普通组件(例如 <div>)" #### 参数 {/*ref-callback-parameters*/} +<<<<<<< HEAD * `node`:DOM 节点或 `null`。当回调函数被附加在 `ref` 属性后,触发回调时,该参数为对应的 DOM 节点。当 ref 被分离时值为 `null`。除非在每次渲染时都传递相同的函数引用作为 `ref` 回调,否则该回调将在组件的每次重新渲染期间被暂时分离和重新连接。 +======= +* `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the `ref` gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during every re-render of the component. + +<Canary> +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 #### 返回值 {/*returns*/} +<<<<<<< HEAD 不要从 `ref` 回调函数中返回任何内容。 +======= +* **optional** `cleanup function`: When the `ref` is detached, React will call the cleanup function. If a function is not returned by the `ref` callback, React will call the callback again with `null` as the argument when the `ref` gets detached. + +```js + +<div ref={(node) => { + console.log(node); + + return () => { + console.log('Clean up', node) + } +}}> + +``` + +#### Caveats {/*caveats*/} + +* When Strict Mode is on, React will **run one extra development-only setup+cleanup cycle** before the first real setup. This is a stress-test that ensures that your cleanup logic "mirrors" your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, implement the cleanup function. +* When you pass a *different* `ref` callback, React will call the *previous* callback's cleanup function if provided. If not cleanup function is defined, the `ref` callback will be called with `null` as the argument. The *next* function will be called with the DOM node. + +</Canary> +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- diff --git a/src/content/reference/react-dom/components/form.md b/src/content/reference/react-dom/components/form.md index 4b7d5d8a07..afdddc749c 100644 --- a/src/content/reference/react-dom/components/form.md +++ b/src/content/reference/react-dom/components/form.md @@ -93,7 +93,7 @@ export default function Search() { ### Handle form submission with a Server Action {/*handle-form-submission-with-a-server-action*/} -Render a `<form>` with an input and submit button. Pass a Server Action (a function marked with [`'use server'`](/reference/react/use-server)) to the `action` prop of form to run the function when the form is submitted. +Render a `<form>` with an input and submit button. Pass a Server Action (a function marked with [`'use server'`](/reference/rsc/use-server)) to the `action` prop of form to run the function when the form is submitted. Passing a Server Action to `<form action>` allow users to submit forms without JavaScript enabled or before the code has loaded. This is beneficial to users who have a slow connection, device, or have JavaScript disabled and is similar to the way forms work when a URL is passed to the `action` prop. @@ -137,7 +137,7 @@ function AddToCart({productId}) { } ``` -When `<form>` is rendered by a [Server Component](/reference/react/use-client), and a [Server Action](/reference/react/use-server) is passed to the `<form>`'s `action` prop, the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). +When `<form>` is rendered by a [Server Component](/reference/rsc/use-client), and a [Server Action](/reference/rsc/use-server) is passed to the `<form>`'s `action` prop, the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). ### Display a pending state during form submission {/*display-a-pending-state-during-form-submission*/} To display a pending state when a form is being submitted, you can call the `useFormStatus` Hook in a component rendered in a `<form>` and read the `pending` property returned. @@ -322,16 +322,16 @@ export default function Search() { Displaying a form submission error message before the JavaScript bundle loads for progressive enhancement requires that: -1. `<form>` be rendered by a [Server Component](/reference/react/use-client) -1. the function passed to the `<form>`'s `action` prop be a [Server Action](/reference/react/use-server) -1. the `useFormState` Hook be used to display the error message +1. `<form>` be rendered by a [Server Component](/reference/rsc/use-client) +1. the function passed to the `<form>`'s `action` prop be a [Server Action](/reference/rsc/use-server) +1. the `useActionState` Hook be used to display the error message -`useFormState` takes two parameters: a [Server Action](/reference/react/use-server) and an initial state. `useFormState` returns two values, a state variable and an action. The action returned by `useFormState` should be passed to the `action` prop of the form. The state variable returned by `useFormState` can be used to displayed an error message. The value returned by the [Server Action](/reference/react/use-server) passed to `useFormState` will be used to update the state variable. +`useActionState` takes two parameters: a [Server Action](/reference/rsc/use-server) and an initial state. `useActionState` returns two values, a state variable and an action. The action returned by `useActionState` should be passed to the `action` prop of the form. The state variable returned by `useActionState` can be used to displayed an error message. The value returned by the [Server Action](/reference/rsc/use-server) passed to `useActionState` will be used to update the state variable. <Sandpack> ```js src/App.js -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { signUpNewUser } from "./api"; export default function Page() { @@ -345,12 +345,12 @@ export default function Page() { return err.toString(); } } - const [message, formAction] = useFormState(signup, null); + const [message, signupAction] = useActionState(signup, null); return ( <> <h1>Signup for my newsletter</h1> <p>Signup with the same email twice to see an error</p> - <form action={formAction} id="signup-form"> + <form action={signupAction} id="signup-form"> <label htmlFor="email">Email: </label> <input name="email" id="email" placeholder="react@example.com" /> <button>Sign up</button> @@ -386,7 +386,7 @@ export async function signUpNewUser(newEmail) { </Sandpack> -Learn more about updating state from a form action with the [`useFormState`](/reference/react-dom/hooks/useFormState) docs +Learn more about updating state from a form action with the [`useActionState`](/reference/react/hooks/useActionState) docs ### Handling multiple submission types {/*handling-multiple-submission-types*/} diff --git a/src/content/reference/react-dom/hooks/index.md b/src/content/reference/react-dom/hooks/index.md index 082edf86a0..7027351374 100644 --- a/src/content/reference/react-dom/hooks/index.md +++ b/src/content/reference/react-dom/hooks/index.md @@ -20,15 +20,19 @@ Form Hooks 目前仅在 React canary 与 experimental 渠道中可用。在此 **Form** 允许创建用于提交信息的交互式控件。要在组件中管理表单,请使用以下其中一个 Hook: +<<<<<<< HEAD * [`useFormStatus`](/reference/react-dom/hooks/useFormStatus) 允许根据表单的状态更新用户界面。 * `useFormState`(/reference/react-dom/hooks/useFormState) 允许管理表单内部的状态。 +======= +* [`useFormStatus`](/reference/react-dom/hooks/useFormStatus) allows you to make updates to the UI based on the status of the a form. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 ```js function Form({ action }) { async function increment(n) { return n + 1; } - const [count, incrementFormAction] = useFormState(increment, 0); + const [count, incrementFormAction] = useActionState(increment, 0); return ( <form action={action}> <button formAction={incrementFormAction}>Count: {count}</button> @@ -46,4 +50,3 @@ function Button() { ); } ``` - diff --git a/src/content/reference/react/apis.md b/src/content/reference/react/apis.md index 2f90ec8ecf..ae3730fee4 100644 --- a/src/content/reference/react/apis.md +++ b/src/content/reference/react/apis.md @@ -13,8 +13,33 @@ translators: --- +<<<<<<< HEAD * [`createContext`](/reference/react/createContext) API 可以创建一个 context,你可以将其提供给子组件,通常会与 [`useContext`](/reference/react/useContext) 一起配合使用。 * [`forwardRef`](/reference/react/forwardRef) 允许组件将 DOM 节点作为 ref 暴露给父组件。 * [`lazy`](/reference/react/lazy) 允许你延迟加载组件,直到该组件需要第一次被渲染。 * [`memo`](/reference/react/memo) 允许你在 props 没有变化的情况下跳过组件的重渲染。通常 [`useMemo`](/reference/react/useMemo) 与 [`useCallback`](/reference/react/useCallback) 会一起配合使用。 * [`startTransition`](/reference/react/startTransition) 允许你可以标记一个状态更新是不紧急的。类似于 [`useTransition`](/reference/react/useTransition)。 +======= +* [`createContext`](/reference/react/createContext) lets you define and provide context to the child components. Used with [`useContext`.](/reference/react/useContext) +* [`forwardRef`](/reference/react/forwardRef) lets your component expose a DOM node as a ref to the parent. Used with [`useRef`.](/reference/react/useRef) +* [`lazy`](/reference/react/lazy) lets you defer loading a component's code until it's rendered for the first time. +* [`memo`](/reference/react/memo) lets your component skip re-renders with same props. Used with [`useMemo`](/reference/react/useMemo) and [`useCallback`.](/reference/react/useCallback) +* [`startTransition`](/reference/react/startTransition) lets you mark a state update as non-urgent. Similar to [`useTransition`.](/reference/react/useTransition) + +--- + +## Resource APIs {/*resource-apis*/} + +*Resources* can be accessed by a component without having them as part of their state. For example, a component can read a message from a Promise or read styling information from a context. + +To read a value from a resource, use this API: + +* [`use`](/reference/react/use) lets you read the value of a resource like a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). +```js +function MessageComponent({ messagePromise }) { + const message = use(messagePromise); + const theme = use(ThemeContext); + // ... +} +``` +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 diff --git a/src/content/reference/react/experimental_taintUniqueValue.md b/src/content/reference/react/experimental_taintUniqueValue.md index 3057ea5c8e..11fe1273ec 100644 --- a/src/content/reference/react/experimental_taintUniqueValue.md +++ b/src/content/reference/react/experimental_taintUniqueValue.md @@ -14,7 +14,11 @@ title: experimental_taintUniqueValue React 的实验版本可能有一些问题,请勿在生产环境中使用。 +<<<<<<< HEAD 此 API 仅在 [React 服务器组件](/reference/react/use-client) 内可用。 +======= +This API is only available inside [React Server Components](/reference/rsc/use-client). +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 </Wip> diff --git a/src/content/reference/react/hooks.md b/src/content/reference/react/hooks.md index 066999a404..e1413cabe6 100644 --- a/src/content/reference/react/hooks.md +++ b/src/content/reference/react/hooks.md @@ -106,6 +106,7 @@ function TodoList({ todos, tab, theme }) { --- +<<<<<<< HEAD ## 资源 Hook {/*resource-hooks*/} 资源可以被组件访问,而无需将它们作为状态的一部分。例如,组件可以从 Promise 中读取消息,或从上下文中读取样式信息。 @@ -125,12 +126,22 @@ function MessageComponent({ messagePromise }) { --- ## 其他 Hook {/*other-hooks*/} +======= +## Other Hooks {/*other-hooks*/} +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 这些 Hook 主要适用于库作者,不常在应用程序代码中使用。 +<<<<<<< HEAD - 使用 [`useDebugValue`](/reference/react/useDebugValue) 自定义 React 开发者工具为自定义 Hook 添加的标签。 - 使用 [`useId`](/reference/react/useId) 将唯一的 ID 与组件相关联,其通常与可访问性 API 一起使用。 - 使用 [`useSyncExternalStore`](/reference/react/useSyncExternalStore) 订阅外部 store。 +======= +- [`useDebugValue`](/reference/react/useDebugValue) lets you customize the label React DevTools displays for your custom Hook. +- [`useId`](/reference/react/useId) lets a component associate a unique ID with itself. Typically used with accessibility APIs. +- [`useSyncExternalStore`](/reference/react/useSyncExternalStore) lets a component subscribe to an external store. +* [`useActionState`](/reference/react/useActionState) allows you to manage state of actions. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- diff --git a/src/content/reference/react/index.md b/src/content/reference/react/index.md index 41c88adbe2..2f678e17bd 100644 --- a/src/content/reference/react/index.md +++ b/src/content/reference/react/index.md @@ -14,10 +14,17 @@ React 参考文档分为以下内容: 编程式 React 功能: +<<<<<<< HEAD * [Hook](/reference/react/hooks) —— 在组件中使用不同的 React 特性。 * [组件](/reference/react/components) —— 记录了可以在 JSX 中使用的内置组件。 * [API](/reference/react/apis) —— 用于定义组件的有用 API。 * [指示符](/reference/react/directives) —— 为与 React 服务器组件兼容的捆绑器提供指示。 +======= +* [Hooks](/reference/react/hooks) - Use different React features from your components. +* [Components](/reference/react/components) - Documents built-in components that you can use in your JSX. +* [APIs](/reference/react/apis) - APIs that are useful for defining components. +* [Directives](/reference/rsc/directives) - Provide instructions to bundlers compatible with React Server Components. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 ## React DOM {/*react-dom*/} diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index e795557cfd..4a6b688fab 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -5,13 +5,21 @@ canary: true <Canary> +<<<<<<< HEAD `use` Hook 仅在 Canary 与 experimental 渠道中可用。参阅 [React 发布渠道](/community/versioning-policy#all-release-channels) 以了解更多信息。 +======= +The `use` API is currently only available in React's Canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 </Canary> <Intro> +<<<<<<< HEAD `use` 是一个 React Hook,它可以让你读取类似于 [Promise](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 或 [context](/learn/passing-data-deeply-with-context) 的资源的值。 +======= +`use` is a React API that lets you read the value of a resource like a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 ```js const value = use(resource); @@ -38,9 +46,15 @@ function MessageComponent({ messagePromise }) { // ... ``` +<<<<<<< HEAD 与其他 React Hook 不同的是,可以在循环和条件语句(如 `if`)中调用 `use`。但需要注意的是,调用 `use` 的函数仍然必须是一个组件或 Hook。 当使用 Promise 调用 `use` Hook 时,它会与 [`Suspense`](/reference/react/Suspense) 和 [错误边界](/reference/react/Component#catching-rendering-errors-with-an-error-boundary) 集成。当传递给 `use` 的 Promise 处于 pending 时,调用 `use` 的组件也会 **挂起**。如果调用 `use` 的组件被包装在 Suspense 边界内,将显示后备 UI。一旦 Promise 被解决,Suspense 后备方案将被使用 `use` Hook 返回的数据替换。如果传递给 `use` 的 Promise 被拒绝,将显示最近错误边界的后备 UI。 +======= +Unlike React Hooks, `use` can be called within loops and conditional statements like `if`. Like React Hooks, the function that calls `use` must be a Component or Hook. + +When called with a Promise, the `use` API integrates with [`Suspense`](/reference/react/Suspense) and [error boundaries](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). The component calling `use` *suspends* while the Promise passed to `use` is pending. If the component that calls `use` is wrapped in a Suspense boundary, the fallback will be displayed. Once the Promise is resolved, the Suspense fallback is replaced by the rendered components using the data returned by the `use` API. If the Promise passed to `use` is rejected, the fallback of the nearest Error Boundary will be displayed. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 [参见下方更多示例](#usage)。 @@ -50,13 +64,23 @@ function MessageComponent({ messagePromise }) { #### 返回值 {/*returns*/} +<<<<<<< HEAD `use` Hook 返回从资源中读取的值,类似于 fullfilled [Promise](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 或 [context](/learn/passing-data-deeply-with-context)。 +======= +The `use` API returns the value that was read from the resource like the resolved value of a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 #### 注意 {/*caveats*/} +<<<<<<< HEAD * `use` Hook 必须在组件或 Hook 内部调用。 * 在 [服务器组件](/reference/react/use-server) 中获取数据时,应优先使用 `async` 和 `await` 而不是 `use`。`async` 和 `await` 会从调用 `await` 的点开始渲染,而 `use` 会在数据获取到后重新渲染组件。 * 在 [服务器组件](/reference/react/use-server) 中创建 Promise 并将其传递给 [客户端组件](/reference/react/use-client) 优于在客户端组件中创建 Promise。在客户端组件中创建的 Promise 每次渲染都会重新创建。从服务器组件传递到客户端组件的 Promise 在重新渲染时保持稳定。[请参阅此示例](#streaming-data-from-server-to-client)。 +======= +* The `use` API must be called inside a Component or a Hook. +* When fetching data in a [Server Component](/reference/rsc/use-server), prefer `async` and `await` over `use`. `async` and `await` pick up rendering from the point where `await` was invoked, whereas `use` re-renders the component after the data is resolved. +* Prefer creating Promises in [Server Components](/reference/rsc/use-server) and passing them to [Client Components](/reference/rsc/use-client) over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders. [See this example](#streaming-data-from-server-to-client). +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 --- @@ -230,7 +254,11 @@ export default function App() { } ``` +<<<<<<< HEAD <CodeStep step={2}>客户端组件</CodeStep>将<CodeStep step={4}> 从 prop 中接收到的 Promise </CodeStep> 传递给 <CodeStep step={5}>`use`</CodeStep> Hook。这允许 <CodeStep step={2}>客户端组件</CodeStep> 从最初由服务器组件创建的 <CodeStep step={4}>Promise</CodeStep> 中读取值。 +======= +The <CodeStep step={2}>Client Component</CodeStep> then takes <CodeStep step={4}>the Promise it received as a prop</CodeStep> and passes it to the <CodeStep step={5}>`use`</CodeStep> API. This allows the <CodeStep step={2}>Client Component</CodeStep> to read the value from <CodeStep step={4}>the Promise</CodeStep> that was initially created by the Server Component. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 ```js [[2, 6, "Message"], [4, 6, "messagePromise"], [4, 7, "messagePromise"], [5, 7, "use"]] // message.js @@ -243,7 +271,11 @@ export function Message({ messagePromise }) { return <p>Here is the message: {messageContent}</p>; } ``` +<<<<<<< HEAD 由于 <CodeStep step={2}>`Message`</CodeStep> 被包裹在 <CodeStep step={3}>[`Suspense`](/reference/react/Suspense)</CodeStep> 中,所以在 Promise 解决之前将显示后备方案。当 Promise 被解决后,<CodeStep step={5}>`use`</CodeStep> Hook 将读取值,然后 <CodeStep step={2}>`Message`</CodeStep> 组件将替换 Suspense 后备方案。 +======= +Because <CodeStep step={2}>`Message`</CodeStep> is wrapped in <CodeStep step={3}>[`Suspense`](/reference/react/Suspense)</CodeStep>, the fallback will be displayed until the Promise is resolved. When the Promise is resolved, the value will be read by the <CodeStep step={5}>`use`</CodeStep> API and the <CodeStep step={2}>`Message`</CodeStep> component will replace the Suspense fallback. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 <Sandpack> @@ -291,9 +323,15 @@ export default function App() { ``` ```js src/index.js hidden +<<<<<<< HEAD // TODO: 在稳定版本中更新 import // 以替代此处 canary 版本中的导入方式,一旦 `use` // Hook 在 React 稳定版本中发布 +======= +// TODO: update to import from stable +// react instead of canary once the `use` +// API is in a stable release of React +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 import React, { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './styles.css'; @@ -334,7 +372,11 @@ root.render( #### 应该在服务器组件还是客户端组件解析 Promise? {/*resolve-promise-in-server-or-client-component*/} +<<<<<<< HEAD Promise 可以从服务器组件传递至客户端组件,并且可以在客户端组件中使用 `use` Hook 解析它。也可以在服务器组件中使用 `await` 解析 Promise,并将所需的数据作为 prop 传递给客户端组件。 +======= +A Promise can be passed from a Server Component to a Client Component and resolved in the Client Component with the `use` API. You can also resolve the Promise in a Server Component with `await` and pass the required data to the Client Component as a prop. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 ```js export default async function App() { @@ -360,7 +402,11 @@ export default async function App() { #### 使用错误边界将错误展示给用户 {/*displaying-an-error-to-users-with-error-boundary*/} +<<<<<<< HEAD 如果希望在 Promise 被拒绝(rejected)时向用户显示错误信息,可以使用 [错误边界](/reference/react/Component#catching-rendering-errors-with-an-error-boundary)。如果需要使用错误边界,请将调用 `use` Hook 的组件包装在错误边界中。如果传递给 `use` 的 Promise 被拒绝(rejected),将显示错误边界的后备方案。 +======= +If you'd like to display an error to your users when a Promise is rejected, you can use an [error boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). To use an error boundary, wrap the component where you are calling the `use` API in an error boundary. If the Promise passed to `use` is rejected the fallback for the error boundary will be displayed. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 <Sandpack> @@ -411,9 +457,15 @@ export default function App() { ``` ```js src/index.js hidden +<<<<<<< HEAD // TODO: 在稳定版本中更新 import // 以替代此处 canary 版本中的导入方式,一旦 `use` // Hook 在 React 稳定版本中发布 +======= +// TODO: update to import from stable +// react instead of canary once the `use` +// API is in a stable release of React +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 import React, { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './styles.css'; @@ -474,9 +526,15 @@ export default function App() { ### "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/} +<<<<<<< HEAD 你要么是在 React 组件或 Hook 函数之外调用了 `use`,或者在 try-catch 块中调用了 `use`。如果你在 try-catch 块中调用 `use`,请将组件包裹在错误边界中,或者使用 Promise 的 `catch` 方法来捕获错误并提供给替代值。[参见这些示例](#dealing-with-rejected-promises)。 如果在 React 组件或 Hook 函数之外调用 `use`,请将 `use` 调用移至 React 组件或 Hook 函数中。 +======= +You are either calling `use` outside of a React Component or Hook function, or calling `use` in a try–catch block. If you are calling `use` inside a try–catch block, wrap your component in an error boundary, or call the Promise's `catch` to catch the error and resolve the Promise with another value. [See these examples](#dealing-with-rejected-promises). + +If you are calling `use` outside a React Component or Hook function, move the `use` call to a React Component or Hook function. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 ```jsx function MessageComponent({messagePromise}) { @@ -486,7 +544,11 @@ function MessageComponent({messagePromise}) { // ... ``` +<<<<<<< HEAD 相反,请在任何组件封闭区域之外调用 `use`,而调用 `use` 的函数本身应为组件或 Hook。 +======= +Instead, call `use` outside any component closures, where the function that calls `use` is a Component or Hook. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 ```jsx function MessageComponent({messagePromise}) { diff --git a/src/content/reference/react-dom/hooks/useFormState.md b/src/content/reference/react/useActionState.md similarity index 58% rename from src/content/reference/react-dom/hooks/useFormState.md rename to src/content/reference/react/useActionState.md index e808aaec83..62e7d7db5a 100644 --- a/src/content/reference/react-dom/hooks/useFormState.md +++ b/src/content/reference/react/useActionState.md @@ -1,20 +1,34 @@ --- -title: useFormState +title: useActionState canary: true --- <Canary> +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md `useFormState` Hook 当前仅在 React Canary 与 experimental 渠道中可用。请点此了解更多关于 [React 发布渠道](/community/versioning-policy#all-release-channels) 的信息。此外,需要一款完全支持 [React 服务器组件](/reference/react/use-client) 特性的框架才可以使用 `useFormState` 的所有特性。 +======= +The `useActionState` Hook is currently only available in React's Canary and experimental channels. Learn more about [release channels here](/community/versioning-policy#all-release-channels). In addition, you need to use a framework that supports [React Server Components](/reference/rsc/use-client) to get the full benefit of `useActionState`. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md </Canary> +<Note> + +In earlier React Canary versions, this API was part of React DOM and called `useFormState`. + +</Note> + <Intro> +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md `useFormState` 是一个可以根据某个表单动作的结果更新 state 的 Hook。 +======= +`useActionState` is a Hook that allows you to update state based on the result of a form action. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md ```js -const [state, formAction] = useFormState(fn, initialState, permalink?); +const [state, formAction] = useActionState(fn, initialState, permalink?); ``` </Intro> @@ -25,21 +39,25 @@ const [state, formAction] = useFormState(fn, initialState, permalink?); ## 参考 {/*reference*/} -### `useFormState(action, initialState, permalink?)` {/*useformstate*/} +### `useActionState(action, initialState, permalink?)` {/*useactionstate*/} {/* TODO T164397693: link to actions documentation once it exists */} +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md 在组件的顶层调用 `useFormState` 即可创建一个随 [表单动作被调用](/reference/react-dom/components/form) 而更新的 state。在调用 `useFormState` 时在参数中传入现有的表单动作函数以及一个初始状态,它就会返回一个新的 action 函数和一个 form state 以供在 form 中使用。这个新的 form state 也会作为参数传入提供的表单动作函数。 +======= +Call `useActionState` at the top level of your component to create component state that is updated [when a form action is invoked](/reference/react-dom/components/form). You pass `useActionState` an existing form action function as well as an initial state, and it returns a new action that you use in your form, along with the latest form state. The latest form state is also passed to the function that you provided. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md ```js -import { useFormState } from "react-dom"; +import { useActionState } from "react"; async function increment(previousState, formData) { return previousState + 1; } function StatefulForm({}) { - const [state, formAction] = useFormState(increment, 0); + const [state, formAction] = useActionState(increment, 0); return ( <form> {state} @@ -51,11 +69,23 @@ function StatefulForm({}) { form state 是一个只在表单被提交触发 action 后才会被更新的值。如果该表单没有被提交,该值会保持传入的初始值不变。 +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md 如果配合 Server Action 一起使用,`useFormState` 允许与表单交互的服务器的返回值在 hydration 完成前显示。 +======= +If used with a Server Action, `useActionState` allows the server's response from submitting the form to be shown even before hydration has completed. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md [请参阅下方更多示例](#usage)。 +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md #### 参数 {/*parameters*/} +======= +#### Parameters {/*parameters*/} + +* `fn`: The function to be called when the form is submitted or button pressed. When the function is called, it will receive the previous state of the form (initially the `initialState` that you pass, subsequently its previous return value) as its initial argument, followed by the arguments that a form action normally receives. +* `initialState`: The value you want the state to be initially. It can be any serializable value. This argument is ignored after the action is first invoked. +* **optional** `permalink`: A string containing the unique page URL that this form modifies. For use on pages with dynamic content (eg: feeds) in conjunction with progressive enhancement: if `fn` is a [server action](/reference/rsc/use-server) and the form is submitted before the JavaScript bundle loads, the browser will navigate to the specified permalink URL, rather than the current page's URL. Ensure that the same form component is rendered on the destination page (including the same action `fn` and `permalink`) so that React knows how to pass the state through. Once the form has been hydrated, this parameter has no effect. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md * `fn`:当按钮被按下或者表单被提交时触发的函数。当函数被调用时,该函数会接收到表单的上一个 state(初始值为传入的 `initialState` 参数,否则为上一次执行完该函数的结果)作为函数的第一个参数,余下参数为普通表单动作接到的参数。 * `initialState`:state 的初始值。任何可序列化的值都可接收。当 action 被调用一次后该参数会被忽略。 @@ -64,15 +94,24 @@ form state 是一个只在表单被提交触发 action 后才会被更新的值 #### 返回值 {/*returns*/} +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md `useFormState` 返回一个包含两个值的数组: +======= +`useActionState` returns an array with exactly two values: +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md 1. 当前的 state。第一次渲染期间,该值为传入的 `initialState` 参数值。在 action 被调用后该值会变为 action 的返回值。 2. 一个新的 action 函数用于在你的 `form` 组件的 `action` 参数或表单中任意一个 `button` 组件的 `formAction` 参数中传递。 #### 注意 {/*caveats*/} +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md * 在支持 React 服务器组件的框架中使用该功能时,`useFormState` 允许表单在服务器渲染阶段时获得部分交互性。当不使用服务器组件时,它的特性与本地 state 相同。 * 与直接通过表单动作调用的函数不同,传入 `useFormState` 的函数被调用时,会多传入一个代表 state 的上一个值或初始值的参数作为该函数的第一个参数。 +======= +* When used with a framework that supports React Server Components, `useActionState` lets you make forms interactive before JavaScript has executed on the client. When used without Server Components, it is equivalent to component local state. +* The function passed to `useActionState` receives an extra argument, the previous or initial state, as its first argument. This makes its signature different than if it were used directly as a form action without using `useActionState`. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md --- @@ -80,14 +119,18 @@ form state 是一个只在表单被提交触发 action 后才会被更新的值 ### 使用某个表单动作返回的信息 {/*using-information-returned-by-a-form-action*/} +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md 在组件的顶层调用 `useFormState` 以获取上一次表单被提交时触发的 action 的返回值。 +======= +Call `useActionState` at the top level of your component to access the return value of an action from the last time a form was submitted. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md ```js [[1, 5, "state"], [2, 5, "formAction"], [3, 5, "action"], [4, 5, "null"], [2, 8, "formAction"]] -import { useFormState } from 'react-dom'; +import { useActionState } from 'react'; import { action } from './actions.js'; function MyComponent() { - const [state, formAction] = useFormState(action, null); + const [state, formAction] = useActionState(action, null); // ... return ( <form action={formAction}> @@ -97,14 +140,22 @@ function MyComponent() { } ``` +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md `useFormState` 返回一个包含两个值的数组: +======= +`useActionState` returns an array with exactly two items: +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md 1. 该表单的 <CodeStep step={1}>当前 state</CodeStep>,初始值为提供的 <CodeStep step={4}>初始 state</CodeStep>,当表单被提交后则改为传入的 <CodeStep step={3}>action</CodeStep> 的返回值。 2. 传入 `<form>` 标签的 `action` 属性的 <CodeStep step={2}>新 action</CodeStep>。 表单被提交后,传入的 <CodeStep step={3}>action</CodeStep> 函数会被执行。返回值将会作为该表单的新的 <CodeStep step={1}>当前 state</CodeStep>。 +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md 传入的 <CodeStep step={3}>action</CodeStep> 接受到的第一个参数将会变为该表单的 <CodeStep step={1}>当前 state</CodeStep>。当表单第一次被提交时将会传入提供的 <CodeStep step={4}>初始 state</CodeStep>,之后都将传入上一次调用 <CodeStep step={3}>action</CodeStep> 函数的返回值。余下参数与未使用 `useFormState` 前接受的参数别无二致<sup><a href="#note1">[1]</a></sup>。 +======= +The <CodeStep step={3}>action</CodeStep> that you provide will also receive a new first argument, namely the <CodeStep step={1}>current state</CodeStep> of the form. The first time the form is submitted, this will be the <CodeStep step={4}>initial state</CodeStep> you provided, while with subsequent submissions, it will be the return value from the last time the action was called. The rest of the arguments are the same as if `useActionState` had not been used. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md ```js [[3, 1, "action"], [1, 1, "currentState"]] function action(currentState, formData) { @@ -117,17 +168,20 @@ function action(currentState, formData) { #### 展示表单错误 {/*display-form-errors*/} +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md 将 action 包裹进 `useFormState` 即可展示诸如错误信息或 Server Action 返回的 toast 等信息。 +======= +To display messages such as an error message or toast that's returned by a Server Action, wrap the action in a call to `useActionState`. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md <Sandpack> ```js src/App.js -import { useState } from "react"; -import { useFormState } from "react-dom"; +import { useActionState, useState } from "react"; import { addToCart } from "./actions.js"; function AddToCartForm({itemID, itemTitle}) { - const [message, formAction] = useFormState(addToCart, null); + const [message, formAction] = useActionState(addToCart, null); return ( <form action={formAction}> <h2>{itemTitle}</h2> @@ -195,12 +249,11 @@ Server Actions 的返回值可以为任意可序列化的值。例如,可以 <Sandpack> ```js src/App.js -import { useState } from "react"; -import { useFormState } from "react-dom"; +import { useActionState, useState } from "react"; import { addToCart } from "./actions.js"; function AddToCartForm({itemID, itemTitle}) { - const [formState, formAction] = useFormState(addToCart, {}); + const [formState, formAction] = useActionState(addToCart, {}); return ( <form action={formAction}> <h2>{itemTitle}</h2> @@ -282,7 +335,11 @@ form button { ### 我的 action 无法再获取提交的 form data 了 {/*my-action-can-no-longer-read-the-submitted-form-data*/} +<<<<<<< HEAD:src/content/reference/react-dom/hooks/useFormState.md 当使用 `useFormState` 包裹 action 时,第一个参数变为了 form 的当前 state,提交的表单数据被顺移到了第二个参数中,与直接使用表单动作是不同的。 +======= +When you wrap an action with `useActionState`, it gets an extra argument *as its first argument*. The submitted form data is therefore its *second* argument instead of its first as it would usually be. The new first argument that gets added is the current state of the form. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/react/useActionState.md ```js function action(currentState, formData) { diff --git a/src/content/reference/react/directives.md b/src/content/reference/rsc/directives.md similarity index 64% rename from src/content/reference/react/directives.md rename to src/content/reference/rsc/directives.md index f817c50131..91a474a824 100644 --- a/src/content/reference/react/directives.md +++ b/src/content/reference/rsc/directives.md @@ -19,5 +19,10 @@ canary: true ## 源码命令 {/*source-code-directives*/} +<<<<<<< HEAD:src/content/reference/react/directives.md * 使用 [`'use client'`](/reference/react/use-client) 标记运行在客户端的代码。 * 使用 [`'use server'`](/reference/react/use-server) 标记可以被客户端代码调用的服务端函数。 +======= +* [`'use client'`](/reference/rsc/use-client) lets you mark what code runs on the client. +* [`'use server'`](/reference/rsc/use-server) marks server-side functions that can be called from client-side code. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/rsc/directives.md diff --git a/src/content/reference/rsc/server-actions.md b/src/content/reference/rsc/server-actions.md new file mode 100644 index 0000000000..f958c4ce51 --- /dev/null +++ b/src/content/reference/rsc/server-actions.md @@ -0,0 +1,213 @@ +--- +title: Server Actions +canary: true +--- + +<Intro> + +Server Actions allow Client Components to call async functions executed on the server. + +</Intro> + +<InlineToc /> + +<Note> + +#### How do I build support for Server Actions? {/*how-do-i-build-support-for-server-actions*/} + +While Server Actions in React 19 are stable and will not break between major versions, the underlying APIs used to implement Server Actions in a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. + +To support Server Actions as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement Server Actions in the future. + +</Note> + +When a Server Action is defined with the `"use server"` directive, your framework will automatically create a reference to the server function, and pass that reference to the Client Component. When that function is called on the client, React will send a request to the server to execute the function, and return the result. + +Server Actions can be created in Server Components and passed as props to Client Components, or they can be imported and used in Client Components. + +### Creating a Server Action from a Server Component {/*creating-a-server-action-from-a-server-component*/} + +Server Components can define Server Actions with the `"use server"` directive: + +```js [[2, 7, "'use server'"], [1, 5, "createNoteAction"], [1, 12, "createNoteAction"]] +// Server Component +import Button from './Button'; + +function EmptyNote () { + async function createNoteAction() { + // Server Action + 'use server'; + + await db.notes.create(); + } + + return <Button onClick={createNoteAction}/>; +} +``` + +When React renders the `EmptyNote` Server Component, it will create a reference to the `createNoteAction` function, and pass that reference to the `Button` Client Component. When the button is clicked, React will send a request to the server to execute the `createNoteAction` function with the reference provided: + +```js {5} +"use client"; + +export default function Button({onClick}) { + console.log(onClick); + // {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'} + return <button onClick={onClick}>Create Empty Note</button> +} +``` + +For more, see the docs for [`"use server"`](/reference/rsc/use-server). + + +### Importing Server Actions from Client Components {/*importing-server-actions-from-client-components*/} + +Client Components can import Server Actions from files that use the `"use server"` directive: + +```js [[1, 3, "createNoteAction"]] +"use server"; + +export async function createNoteAction() { + await db.notes.create(); +} + +``` + +When the bundler builds the `EmptyNote` Client Component, it will create a reference to the `createNoteAction` function in the bundle. When the `button` is clicked, React will send a request to the server to execute the `createNoteAction` function using the reference provided: + +```js [[1, 2, "createNoteAction"], [1, 5, "createNoteAction"], [1, 7, "createNoteAction"]] +"use client"; +import {createNoteAction} from './actions'; + +function EmptyNote() { + console.log(createNoteAction); + // {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'} + <button onClick={createNoteAction} /> +} +``` + +For more, see the docs for [`"use server"`](/reference/rsc/use-server). + +### Composing Server Actions with Actions {/*composing-server-actions-with-actions*/} + +Server Actions can be composed with Actions on the client: + +```js [[1, 3, "updateName"]] +"use server"; + +export async function updateName(name) { + if (!name) { + return {error: 'Name is required'}; + } + await db.users.updateName(name); +} +``` + +```js [[1, 3, "updateName"], [1, 13, "updateName"], [2, 11, "submitAction"], [2, 23, "submitAction"]] +"use client"; + +import {updateName} from './actions'; + +function UpdateName() { + const [name, setName] = useState(''); + const [error, setError] = useState(null); + + const [isPending, startTransition] = useTransition(); + + const submitAction = async () => { + startTransition(async () => { + const {error} = await updateName(name); + if (!error) { + setError(error); + } else { + setName(''); + } + }) + } + + return ( + <form action={submitAction}> + <input type="text" name="name" disabled={isPending}/> + {state.error && <span>Failed: {state.error}</span>} + </form> + ) +} +``` + +This allows you to access the `isPending` state of the Server Action by wrapping it in an Action on the client. + +For more, see the docs for [Calling a Server Action outside of `<form>`](/reference/rsc/use-server#calling-a-server-action-outside-of-form) + +### Form Actions with Server Actions {/*form-actions-with-server-actions*/} + +Server Actions work with the new Form features in React 19. + +You can pass a Server Action to a Form to automatically submit the form to the server: + + +```js [[1, 3, "updateName"], [1, 7, "updateName"]] +"use client"; + +import {updateName} from './actions'; + +function UpdateName() { + return ( + <form action={updateName}> + <input type="text" name="name" /> + </form> + ) +} +``` + +When the Form submission succeeds, React will automatically reset the form. You can add `useActionState` to access the pending state, last response, or to support progressive enhancement. + +For more, see the docs for [Server Actions in Forms](/reference/rsc/use-server#server-actions-in-forms). + +### Server Actions with `useActionState` {/*server-actions-with-use-action-state*/} + +You can compose Server Actions with `useActionState` for the common case where you just need access to the action pending state and last returned response: + +```js [[1, 3, "updateName"], [1, 6, "updateName"], [2, 6, "submitAction"], [2, 9, "submitAction"]] +"use client"; + +import {updateName} from './actions'; + +function UpdateName() { + const [submitAction, state, isPending] = useActionState(updateName); + + return ( + <form action={submitAction}> + <input type="text" name="name" disabled={isPending}/> + {state.error && <span>Failed: {state.error}</span>} + </form> + ); +} +``` + +When using `useActionState` with Server Actions, React will also automatically replay form submissions entered before hydration finishes. This means users can interact with your app even before the app has hydrated. + +For more, see the docs for [`useActionState`](/reference/react-dom/hooks/useFormState). + +### Progressive enhancement with `useActionState` {/*progressive-enhancement-with-useactionstate*/} + +Server Actions also support progressive enhancement with the second argument of `useActionState`. + +```js [[1, 3, "updateName"], [1, 6, "updateName"], [2, 6, "/name/update"], [3, 6, "submitAction"], [3, 9, "submitAction"]] +"use client"; + +import {updateName} from './actions'; + +function UpdateName() { + const [submitAction] = useActionState(updateName, `/name/update`); + + return ( + <form action={submitAction}> + ... + </form> + ); +} +``` + +When the <CodeStep step={2}>permalink</CodeStep> is provided to `useActionState`, React will redirect to the provided URL if the form is submitted before the JavaScript bundle loads. + +For more, see the docs for [`useActionState`](/reference/react-dom/hooks/useFormState). diff --git a/src/content/reference/rsc/server-components.md b/src/content/reference/rsc/server-components.md new file mode 100644 index 0000000000..d130104a80 --- /dev/null +++ b/src/content/reference/rsc/server-components.md @@ -0,0 +1,297 @@ +--- +title: React Server Components +canary: true +--- + +<Intro> + +Server Components are a new type of Component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server. + +</Intro> + +This separate environment is the "server" in React Server Components. Server Components can run once at build time on your CI server, or they can be run for each request using a web server. + +<InlineToc /> + +<Note> + +#### How do I build support for Server Components? {/*how-do-i-build-support-for-server-components*/} + +While React Server Components in React 19 are stable and will not break between major versions, the underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. + +To support React Server Components as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement React Server Components in the future. + +</Note> + +### Server Components without a Server {/*server-components-without-a-server*/} +Server components can run at build time to read from the filesystem or fetch static content, so a web server is not required. For example, you may want to read static data from a content management system. + +Without Server Components, it's common to fetch static data on the client with an Effect: +```js +// bundle.js +import marked from 'marked'; // 35.9K (11.2K gzipped) +import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped) + +function Page({page}) { + const [content, setContent] = useState(''); + // NOTE: loads *after* first page render. + useEffect(() => { + fetch(`/api/content/${page}`).then((data) => { + setContent(data.content); + }); + }, [page]); + + return <div>{sanitizeHtml(marked(content))}</div>; +} +``` +```js +// api.js +app.get(`/api/content/:page`, async (req, res) => { + const page = req.params.page; + const content = await file.readFile(`${page}.md`); + res.send({content}); +}); +``` + +This pattern means users need to download and parse an additional 75K (gzipped) of libraries, and wait for a second request to fetch the data after the page loads, just to render static content that will not change for the lifetime of the page. + +With Server Components, you can render these components once at build time: + +```js +import marked from 'marked'; // Not included in bundle +import sanitizeHtml from 'sanitize-html'; // Not included in bundle + +async function Page({page}) { + // NOTE: loads *during* render, when the app is built. + const content = await file.readFile(`${page}.md`); + + return <div>{sanitizeHtml(marked(content))}</div>; +} +``` + +The rendered output can then be server-side rendered (SSR) to HTML and uploaded to a CDN. When the app loads, the client will not see the original `Page` component, or the expensive libraries for rendering the markdown. The client will only see the rendered output: + +```js +<div><!-- html for markdown --></div> +``` + +This means the content is visible during first page load, and the bundle does not include the expensive libraries needed to render the static content. + +<Note> + +You may notice that the Server Component above is an async function: + +```js +async function Page({page}) { + //... +} +``` + +Async Components are a new feature of Server Components that allow you to `await` in render. + +See [Async components with Server Components](#async-components-with-server-components) below. + +</Note> + +### Server Components with a Server {/*server-components-with-a-server*/} +Server Components can also run on a web server during a request for a page, letting you access your data layer without having to build an API. They are rendered before your application is bundled, and can pass data and JSX as props to Client Components. + +Without Server Components, it's common to fetch dynamic data on the client in an Effect: + +```js +// bundle.js +function Note({id}) { + const [note, setNote] = useState(''); + // NOTE: loads *after* first render. + useEffect(() => { + fetch(`/api/notes/${id}`).then(data => { + setNote(data.note); + }); + }, [id]); + + return ( + <div> + <Author id={note.authorId} /> + <p>{note}</p> + </div> + ); +} + +function Author({id}) { + const [author, setAuthor] = useState(''); + // NOTE: loads *after* Note renders. + // Causing an expensive client-server waterfall. + useEffect(() => { + fetch(`/api/authors/${id}`).then(data => { + setAuthor(data.author); + }); + }, [id]); + + return <span>By: {author.name}</span>; +} +``` +```js +// api +import db from './database'; + +app.get(`/api/notes/:id`, async (req, res) => { + const note = await db.notes.get(id); + res.send({note}); +}); + +app.get(`/api/authors/:id`, async (req, res) => { + const author = await db.authors.get(id); + res.send({author}); +}); +``` + +With Server Components, you can read the data and render it in the component: + +```js +import db from './database'; + +async function Note({id}) { + // NOTE: loads *during* render. + const note = await db.notes.get(id); + return ( + <div> + <Author id={note.authorId} /> + <p>{note}</p> + </div> + ); +} + +async function Author({id}) { + // NOTE: loads *after* Node, + // but is fast if data is co-located. + const author = await db.authors.get(id); + return <span>By: {author.name}</span>; +} +``` + +The bundler then combines the data, rendered Server Components and dynamic Client Components into a bundle. Optionally, that bundle can then be server-side rendered (SSR) to create the initial HTML for the page. When the page loads, the browser does not see the original `Note` and `Author` components; only the rendered output is sent to the client: + +```js +<div> + <span>By: The React Team</span> + <p>React 19 Beta is...</p> +</div> +``` + +Server Components can be made dynamic by re-fetching them from a server, where they can access the data and render again. This new application architecture combines the simple “request/response” mental model of server-centric Multi-Page Apps with the seamless interactivity of client-centric Single-Page Apps, giving you the best of both worlds. + +### Adding interactivity to Server Components {/*adding-interactivity-to-server-components*/} + +Server Components are not sent to the browser, so they cannot use interactive APIs like `useState`. To add interactivity to Server Components, you can compose them with Client Component using the `"use client"` directive. + +<Note> + +#### There is no directive for Server Components. {/*there-is-no-directive-for-server-components*/} + +A common misunderstanding is that Server Components are denoted by `"use server"`, but there is no directive for Server Components. The `"use server"` directive is used for Server Actions. + +For more info, see the docs for [Directives](/reference/rsc/directives). + +</Note> + + +In the following example, the `Notes` Server Component imports an `Expandable` Client Component that uses state to toggle its `expanded` state: +```js +// Server Component +import Exapandable from './Expandable'; + +async function Notes() { + const notes = await db.notes.getAll(); + return ( + <div> + {notes.map(note => ( + <Expandable key={note.id}> + <p note={note} /> + </Expandable> + ))} + </div> + ) +} +``` +```js +// Client Component +"use client" + +export default function Expandable({children}) { + const [expanded, setExpanded] = useState(false); + return ( + <div> + <button + onClick={() => setExpanded(!expanded)} + > + Toggle + </button> + {expanded && children} + </div> + ) +} +``` + +This works by first rendering `Notes` as a Server Component, and then instructing the bundler to create a bundle for the Client Component `Expandable`. In the browser, the Client Components will see output of the Server Components passed as props: + +```js +<head> + <!-- the bundle for Client Components --> + <script src="bundle.js" /> +</head> +<body> + <div> + <Expandable key={1}> + <p>this is the first note</p> + </Expandable> + <Expandable key={2}> + <p>this is the second note</p> + </Expandable> + <!--...--> + </div> +</body> +``` + +### Async components with Server Components {/*async-components-with-server-components*/} + +Server Components introduce a new way to write Components using async/await. When you `await` in an async component, React will suspend and wait for the promise to resolve before resuming rendering. This works across server/client boundaries with streaming support for Suspense. + +You can even create a promise on the server, and await it on the client: + +```js +// Server Component +import db from './database'; + +async function Page({id}) { + // Will suspend the Server Component. + const note = await db.notes.get(id); + + // NOTE: not awaited, will start here and await on the client. + const commentsPromise = db.comments.get(note.id); + return ( + <div> + {note} + <Suspense fallback={<p>Loading Comments...</p>}> + <Comments commentsPromise={commentsPromise} /> + </Suspense> + </div> + ); +} +``` + +```js +// Client Component +"use client"; +import {use} from 'react'; + +function Comments({commentsPromise}) { + // NOTE: this will resume the promise from the server. + // It will suspend until the data is available. + const comments = use(commentsPromise); + return comments.map(commment => <p>{comment}</p>); +} +``` + +The `note` content is important data for the page to render, so we `await` it on the server. The comments are below the fold and lower-priority, so we start the promise on the server, and wait for it on the client with the `use` API. This will Suspend on the client, without blocking the `note` content from rendering. + +Since async components are [not supported on the client](#why-cant-i-use-async-components-on-the-client), we await the promise with `use`. diff --git a/src/content/reference/react/use-client.md b/src/content/reference/rsc/use-client.md similarity index 87% rename from src/content/reference/react/use-client.md rename to src/content/reference/rsc/use-client.md index 07da6cb2e7..153126b458 100644 --- a/src/content/reference/react/use-client.md +++ b/src/content/reference/rsc/use-client.md @@ -253,6 +253,7 @@ function App() { 从服务器组件传递给客户端组件的属性值必须是可序列化的。 +<<<<<<< HEAD:src/content/reference/react/use-client.md 可序列化属性包括: * 原始类型 * [string](https://developer.mozilla.org/zh-CN/docs/Glossary/String) @@ -279,6 +280,34 @@ function App() { * [类](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Classes_in_JavaScript) * 任何类的实例对象(除了提到的内置类)或 [使用 null 作为原型](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object#null-prototype_objects) 的对象 * 未全局注册的符号,例如 `Symbol('my new symbol')` +======= +Serializable props include: +* Primitives + * [string](https://developer.mozilla.org/en-US/docs/Glossary/String) + * [number](https://developer.mozilla.org/en-US/docs/Glossary/Number) + * [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) + * [boolean](https://developer.mozilla.org/en-US/docs/Glossary/Boolean) + * [undefined](https://developer.mozilla.org/en-US/docs/Glossary/Undefined) + * [null](https://developer.mozilla.org/en-US/docs/Glossary/Null) + * [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol), only symbols registered in the global Symbol registry via [`Symbol.for`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for) +* Iterables containing serializable values + * [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) + * [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) + * [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) + * [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) + * [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) and [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) +* [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) +* Plain [objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object): those created with [object initializers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer), with serializable properties +* Functions that are [Server Actions](/reference/rsc/use-server) +* Client or Server Component elements (JSX) +* [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) + +Notably, these are not supported: +* [Functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) that are not exported from client-marked modules or marked with [`'use server'`](/reference/rsc/use-server) +* [Classes](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Classes_in_JavaScript) +* Objects that are instances of any class (other than the built-ins mentioned) or objects with [a null prototype](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#null-prototype_objects) +* Symbols not registered globally, ex. `Symbol('my new symbol')` +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/rsc/use-client.md ## 用法 {/*usage*/} diff --git a/src/content/reference/react/use-server.md b/src/content/reference/rsc/use-server.md similarity index 79% rename from src/content/reference/react/use-server.md rename to src/content/reference/rsc/use-server.md index 1d304b8b09..6e5dc511b1 100644 --- a/src/content/reference/react/use-server.md +++ b/src/content/reference/rsc/use-server.md @@ -38,6 +38,7 @@ async function addToCart(data) { 可以将 `'use server'` 指示符添加到文件顶部来代替逐个在函数中添加它。位于文件顶部的指示符会将所有导出都标记为可在任何地方使用的 Server Action,包括在客户端代码中导入。 +<<<<<<< HEAD:src/content/reference/react/use-server.md #### 注意 {/*caveats*/} * `'use server'` 必须位于函数或模块的顶部,在任何导入或其他代码之前(可以位于代码顶部的注释之后)。它们必须用单引号或双引号编写,但不能用反引号。 * `'use server'` 只能在服务器端文件中使用。生成的 Server Action 可以通过 props 传递给客户端组件。请参阅支持的 [序列化参数和返回值类型](#serializable-parameters-and-return-values)。 @@ -46,6 +47,16 @@ async function addToCart(data) { * 始终将 Server Action 的参数视为不受信任的输入,并授权任何变更。请参阅 [安全考虑](#security)。 * 应在 [transition](/reference/react/useTransition) 中调用 Server Action。传递给 [`<form action>`](/reference/react-dom/components/form#props) 或 [`formAction`](/reference/react-dom/components/input#props) 的 Server Action 将自动在 Transition 中被调用。 * Server Action 专为更新服务器端状态的变更而设计,不建议用于数据获取。因此,实现 Server Action 的框架通常一次只处理一个 Action,没有缓存返回值的方式。 +======= +#### Caveats {/*caveats*/} +* `'use server'` must be at the very beginning of their function or module; above any other code including imports (comments above directives are OK). They must be written with single or double quotes, not backticks. +* `'use server'` can only be used in server-side files. The resulting Server Actions can be passed to Client Components through props. See supported [types for serialization](#serializable-parameters-and-return-values). +* To import a Server Action from [client code](/reference/rsc/use-client), the directive must be used on a module level. +* Because the underlying network calls are always asynchronous, `'use server'` can only be used on async functions. +* Always treat arguments to Server Actions as untrusted input and authorize any mutations. See [security considerations](#security). +* Server Actions should be called in a [Transition](/reference/react/useTransition). Server Actions passed to [`<form action>`](/reference/react-dom/components/form#props) or [`formAction`](/reference/react-dom/components/input#props) will automatically be called in a transition. +* Server Actions are designed for mutations that update server-side state; they are not recommended for data fetching. Accordingly, frameworks implementing Server Actions typically process one action at a time and do not have a way to cache the return value. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/rsc/use-server.md ### 安全考虑 {/*security*/} @@ -95,7 +106,11 @@ Server Action 的参数完全由客户端控制。出于安全考虑,始终将 * 未全局注册的符号,例如 `Symbol('my new symbol')` +<<<<<<< HEAD:src/content/reference/react/use-server.md 支持的可序列化返回值与边界客户端组件的 [可序列化 props](/reference/react/use-client#passing-props-from-server-to-client-components) 相同。 +======= +Supported serializable return values are the same as [serializable props](/reference/rsc/use-client#passing-props-from-server-to-client-components) for a boundary Client Component. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/rsc/use-server.md ## 用法 {/*usage*/} @@ -133,7 +148,11 @@ export default function App() { 在用户名请求表单中,可能存在用户名不可用的情况。`requestUsername` 应该告诉我们它是否失败。 +<<<<<<< HEAD:src/content/reference/react/use-server.md 请使用 [`useFormState`](/reference/react-dom/hooks/useFormState) 以根据 Server Action 的结果更新 UI 并支持逐步增强。 +======= +To update the UI based on the result of a Server Action while supporting progressive enhancement, use [`useActionState`](/reference/react/useActionState). +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/rsc/use-server.md ```js // requestUsername.js @@ -153,11 +172,11 @@ export default async function requestUsername(formData) { // UsernameForm.js 'use client'; -import { useFormState } from 'react-dom'; +import { useActionState } from 'react'; import requestUsername from './requestUsername'; function UsernameForm() { - const [returnValue, action] = useFormState(requestUsername, 'n/a'); + const [returnValue, action] = useActionState(requestUsername, 'n/a'); return ( <> @@ -171,7 +190,11 @@ function UsernameForm() { } ``` +<<<<<<< HEAD:src/content/reference/react/use-server.md 请注意,与大多数 Hook 一样,`useFormState` 只能在 <CodeStep step={1}>[客户端代码](/reference/react/use-client)</CodeStep> 中调用。 +======= +Note that like most Hooks, `useActionState` can only be called in <CodeStep step={1}>[client code](/reference/rsc/use-client)</CodeStep>. +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3:src/content/reference/rsc/use-server.md ### 在 `<form>` 之外调用 Server Action {/*calling-a-server-action-outside-of-form*/} diff --git a/src/pages/[[...markdownPath]].js b/src/pages/[[...markdownPath]].js index 76532361b1..79f7970ad1 100644 --- a/src/pages/[[...markdownPath]].js +++ b/src/pages/[[...markdownPath]].js @@ -12,6 +12,7 @@ import sidebarCommunity from '../sidebarCommunity.json'; import sidebarBlog from '../sidebarBlog.json'; import {MDXComponents} from 'components/MDX/MDXComponents'; import compileMDX from 'utils/compileMDX'; +import {generateRssFeed} from '../utils/rss'; export default function Layout({content, toc, meta}) { const parsedContent = useMemo( () => JSON.parse(content, reviveNodeOnClient), @@ -96,6 +97,7 @@ function reviveNodeOnClient(key, val) { // Put MDX output into JSON for client. export async function getStaticProps(context) { + generateRssFeed(); const fs = require('fs'); const rootDir = process.cwd() + '/src/content/'; diff --git a/src/sidebarBlog.json b/src/sidebarBlog.json index eb4ad54391..54dec03418 100644 --- a/src/sidebarBlog.json +++ b/src/sidebarBlog.json @@ -12,8 +12,27 @@ "skipBreadcrumb": true, "routes": [ { +<<<<<<< HEAD "title": "React Labs:我们正在努力的方向——2024 年 2 月", "titleForHomepage": "React Labs:2024 年 2 月", +======= + "title": "React 19 Beta", + "titleForHomepage": "React 19 Beta", + "icon": "blog", + "date": "April 25, 2024", + "path": "/blog/2024/04/25/react-19" + }, + { + "title": "React 19 Beta Upgrade Guide", + "titleForHomepage": "React 19 Beta Upgrade Guide", + "icon": "blog", + "date": "April 25, 2024", + "path": "/blog/2024/04/25/react-19-upgrade-guide" + }, + { + "title": "React Labs: What We've Been Working On – February 2024", + "titleForHomepage": "React Labs: February 2024", +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 "icon": "labs", "date": "February 15, 2024", "path": "/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024" diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 0e701e6e9c..c37d82de61 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -15,8 +15,8 @@ "path": "/reference/react/hooks", "routes": [ { - "title": "use", - "path": "/reference/react/use", + "title": "useActionState", + "path": "/reference/react/useActionState", "canary": true }, { @@ -137,6 +137,11 @@ "title": "startTransition", "path": "/reference/react/startTransition" }, + { + "title": "use", + "path": "/reference/react/use", + "canary": true + }, { "title": "experimental_taintObjectReference", "path": "/reference/react/experimental_taintObjectReference", @@ -150,6 +155,7 @@ ] }, { +<<<<<<< HEAD "title": "指示符", "path": "/reference/react/directives", "canary": true, @@ -167,6 +173,8 @@ ] }, { +======= +>>>>>>> 01edd5c1070c2c563f86ee508cff3e8cca6a36c3 "hasSectionHeader": true, "sectionHeader": "react-dom@18.2.0" }, @@ -174,11 +182,6 @@ "title": "Hook", "path": "/reference/react-dom/hooks", "routes": [ - { - "title": "useFormState", - "path": "/reference/react-dom/hooks/useFormState", - "canary": true - }, { "title": "useFormStatus", "path": "/reference/react-dom/hooks/useFormStatus", @@ -356,19 +359,52 @@ }, { "title": "Overview", - "path": "/reference/rules" + "path": "/reference/rules", + "routes": [ + { + "title": "Components and Hooks must be pure", + "path": "/reference/rules/components-and-hooks-must-be-pure" + }, + { + "title": "React calls Components and Hooks", + "path": "/reference/rules/react-calls-components-and-hooks" + }, + { + "title": "Rules of Hooks", + "path": "/reference/rules/rules-of-hooks" + } + ] + }, + { + "hasSectionHeader": true, + "sectionHeader": "React Server Components" }, { - "title": "Components and Hooks must be pure", - "path": "/reference/rules/components-and-hooks-must-be-pure" + "title": "Server Components", + "path": "/reference/rsc/server-components", + "canary": true }, { - "title": "React calls Components and Hooks", - "path": "/reference/rules/react-calls-components-and-hooks" + "title": "Server Actions", + "path": "/reference/rsc/server-actions", + "canary": true }, { - "title": "Rules of Hooks", - "path": "/reference/rules/rules-of-hooks" + "title": "Directives", + "path": "/reference/rsc/directives", + "canary": true, + "routes": [ + { + "title": "'use client'", + "path": "/reference/rsc/use-client", + "canary": true + }, + { + "title": "'use server'", + "path": "/reference/rsc/use-server", + "canary": true + } + ] }, { "hasSectionHeader": true, diff --git a/src/styles/index.css b/src/styles/index.css index cfd82dc0bd..d05d08b284 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -559,6 +559,17 @@ background: none !important; padding: 2px !important; } + +.dark .console-block code { + background: rgba(235 236 240 / 0.05) !important; + color: rgba(208, 125, 119) !important; +} + +.console-block code { + background: rgba(235 236 240 / 0.95) !important; + color: rgb(166, 66, 58) !important; +} + html.dark .code-step * { color: inherit !important; } diff --git a/src/utils/rss.js b/src/utils/rss.js new file mode 100644 index 0000000000..c6fb824101 --- /dev/null +++ b/src/utils/rss.js @@ -0,0 +1,82 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ +const Feed = require('rss'); +const fs = require('fs'); +const path = require('path'); +const matter = require('gray-matter'); + +const getAllFiles = function (dirPath, arrayOfFiles) { + const files = fs.readdirSync(dirPath); + + arrayOfFiles = arrayOfFiles || []; + + files.forEach(function (file) { + if (fs.statSync(dirPath + '/' + file).isDirectory()) { + arrayOfFiles = getAllFiles(dirPath + '/' + file, arrayOfFiles); + } else { + arrayOfFiles.push(path.join(dirPath, '/', file)); + } + }); + + return arrayOfFiles; +}; + +exports.generateRssFeed = function () { + const feed = new Feed({ + title: 'React Blog', + description: + 'This blog is the official source for the updates from the React team. Anything important, including release notes or deprecation notices, will be posted here first.', + feed_url: 'https://react.dev/rss.xml', + site_url: 'https://react.dev/', + language: 'en', + favicon: 'https://react.dev/favicon.ico', + pubDate: new Date(), + generator: 'react.dev rss module', + }); + + const dirPath = path.join(process.cwd(), 'src/content/blog'); + const filesByOldest = getAllFiles(dirPath); + const files = filesByOldest.reverse(); + + for (const filePath of files) { + const id = filePath.split('/').slice(-1).join(''); + if (id !== 'index.md') { + const content = fs.readFileSync(filePath, 'utf-8'); + const {data} = matter(content); + const slug = filePath.split('/').slice(-4).join('/').replace('.md', ''); + + if (data.title == null || data.title.trim() === '') { + throw new Error( + `${id}: Blog posts must include a title in the metadata, for RSS feeds` + ); + } + if (data.author == null || data.author.trim() === '') { + throw new Error( + `${id}: Blog posts must include an author in the metadata, for RSS feeds` + ); + } + if (data.date == null || data.date.trim() === '') { + throw new Error( + `${id}: Blog posts must include a date in the metadata, for RSS feeds` + ); + } + if (data.description == null || data.description.trim() === '') { + throw new Error( + `${id}: Blog posts must include a description in the metadata, for RSS feeds` + ); + } + + feed.item({ + id, + title: data.title, + author: data.author || '', + date: new Date(data.date), + url: `https://react.dev/blog/${slug}`, + description: data.description, + }); + } + } + + fs.writeFileSync('./public/rss.xml', feed.xml({indent: true})); +}; diff --git a/vercel.json b/vercel.json index 8d610c26e0..957c250278 100644 --- a/vercel.json +++ b/vercel.json @@ -9,6 +9,11 @@ "destination": "/reference/react", "permanent": true }, + { + "source": "/reference/react-dom/hooks/useFormState", + "destination": "/reference/react/useActionState", + "permanent": true + }, { "source": "/learn/meet-the-team", "destination": "/community/team", @@ -19,7 +24,6 @@ "destination": "/learn/rendering-lists#keeping-list-items-in-order-with-key", "permanent": false }, - { "source": "/link/invalid-hook-call", "destination": "/warnings/invalid-hook-call-warning", @@ -149,6 +153,26 @@ "source": "/link/setstate-in-render", "destination": "https://github.com/facebook/react/issues/18178#issuecomment-595846312", "permanent": false + }, + { + "source": "/link/new-jsx-transform", + "destination": "https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html", + "permanent": false + }, + { + "source": "/reference/react/directives", + "destination": "/reference/rsc/directives", + "permanent": true + }, + { + "source": "/reference/react/use-client", + "destination": "/reference/rsc/use-client", + "permanent": true + }, + { + "source": "/reference/react/use-server", + "destination": "/reference/rsc/use-server", + "permanent": true } ], "headers": [