diff --git a/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md b/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md index df5971b52a..c4447188a1 100644 --- a/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md +++ b/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md @@ -11469,7 +11469,7 @@ root.render( **`` 现在可以在 React Canary 版本使用。** -[了解更多关于 React 版本发布的内容。](/community/versioning-policy#all-release-channels) +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 @@ -14303,7 +14303,7 @@ useEffect(() => { }); // 编译器插入的依赖项。 ``` -使用这段代码,React 编译器可以为你推断依赖项并自动插入它们,这样你就不需要看到或编写它们。通过像[IDE 扩展](#compiler-ide-extension)和[`useEffectEvent`](/reference/react/experimental_useEffectEvent)这样的功能,我们可以提供一个 CodeLens 来显示编译器在你需要调试时插入的内容,或通过移除依赖项来优化。这有助于强化编写 Effects 的正确心智模型,即 Effects 可以在任何时候运行,以将你的组件或 hook 的状态与其他内容同步。 +使用这段代码,React 编译器可以为你推断依赖项并自动插入它们,这样你就不需要看到或编写它们。通过像[IDE 扩展](#compiler-ide-extension)和[`useEffectEvent`](/reference/react/useEffectEvent)这样的功能,我们可以提供一个 CodeLens 来显示编译器在你需要调试时插入的内容,或通过移除依赖项来优化。这有助于强化编写 Effects 的正确心智模型,即 Effects 可以在任何时候运行,以将你的组件或 hook 的状态与其他内容同步。 我们希望自动插入依赖项不仅更容易编写,而且通过迫使你从 Effect 的作用角度思考,而不是从组件生命周期角度思考,使它们更容易理解。 diff --git a/src/content/learn/escape-hatches.md b/src/content/learn/escape-hatches.md index 4438ab487e..e54b0dc6d7 100644 --- a/src/content/learn/escape-hatches.md +++ b/src/content/learn/escape-hatches.md @@ -455,8 +455,8 @@ label { display: block; margin-top: 10px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -471,7 +471,7 @@ label { display: block; margin-top: 10px; } ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; diff --git a/src/content/learn/removing-effect-dependencies.md b/src/content/learn/removing-effect-dependencies.md index 0b3f0cc1f0..5ffb55aae5 100644 --- a/src/content/learn/removing-effect-dependencies.md +++ b/src/content/learn/removing-effect-dependencies.md @@ -610,11 +610,13 @@ function ChatRoom({ roomId }) { ### 你想读取一个值而不对其变化做出“反应”吗? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/} - + -本节描述了一个在稳定版本的 React 中 **尚未发布的实验性** API。 +**`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 - +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 + + 假设你希望在用户收到新消息时播放声音,`isMuted` 为 `true` 除外: @@ -1261,8 +1263,8 @@ Effect 中是否有一行代码不应该是响应式的?如何将非响应式 ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -1276,7 +1278,7 @@ Effect 中是否有一行代码不应该是响应式的?如何将非响应式 ```js import { useState, useEffect, useRef } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; import { FadeInAnimation } from './animation.js'; function Welcome({ duration }) { @@ -1388,8 +1390,8 @@ Effect 需要读取 `duration` 的最新值,但你不希望它对 `duration` ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -1404,7 +1406,7 @@ Effect 需要读取 `duration` 的最新值,但你不希望它对 `duration` ```js import { useState, useEffect, useRef } from 'react'; import { FadeInAnimation } from './animation.js'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; function Welcome({ duration }) { const ref = useRef(null); @@ -1824,8 +1826,8 @@ label, button { display: block; margin-bottom: 5px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -1906,7 +1908,7 @@ export default function App() { ```js src/ChatRoom.js active import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export default function ChatRoom({ roomId, createConnection, onMessage }) { useEffect(() => { @@ -2119,8 +2121,8 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reacti ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -2188,7 +2190,7 @@ export default function App() { ```js src/ChatRoom.js active import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; import { createEncryptedConnection, createUnencryptedConnection, diff --git a/src/content/learn/reusing-logic-with-custom-hooks.md b/src/content/learn/reusing-logic-with-custom-hooks.md index 7b24f7b10f..830b5b5fa0 100644 --- a/src/content/learn/reusing-logic-with-custom-hooks.md +++ b/src/content/learn/reusing-logic-with-custom-hooks.md @@ -837,11 +837,13 @@ export default function ChatRoom({ roomId }) { ### 把事件处理函数传到自定义 Hook 中 {/*passing-event-handlers-to-custom-hooks*/} - + -这个章节描述了 React 稳定版 **还未发布的一个实验性 API**。 +**`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 - +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 + + 当你在更多组件中使用 `useChatRoom` 时,你可能希望组件能定制它的行为。例如现在 Hook 内部收到消息的处理逻辑是硬编码: @@ -985,7 +987,7 @@ export default function ChatRoom({ roomId }) { ```js src/useChatRoom.js import { useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; import { createConnection } from './chat.js'; export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) { @@ -1070,8 +1072,8 @@ export function showNotification(message, theme = 'dark') { ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -1666,7 +1668,7 @@ export default function App() { ```js src/useFadeIn.js active import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export function useFadeIn(ref, duration) { const [isRunning, setIsRunning] = useState(true); @@ -1719,8 +1721,8 @@ html, body { min-height: 300px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -2208,8 +2210,8 @@ export function useInterval(onTick, delay) { ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -2252,7 +2254,7 @@ export function useCounter(delay) { ```js src/useInterval.js import { useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export function useInterval(onTick, delay) { useEffect(() => { @@ -2279,8 +2281,8 @@ export function useInterval(onTick, delay) { ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -2324,7 +2326,7 @@ export function useCounter(delay) { ```js src/useInterval.js active import { useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export function useInterval(callback, delay) { const onTick = useEffectEvent(callback); diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 44f95af854..6d2991e4c5 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -400,13 +400,15 @@ label { display: block; margin-top: 10px; } ### 声明一个 Effect Event {/*declaring-an-effect-event*/} - + -本章节描述了一个在 React 稳定版中 **还没有发布的实验性 API**。 +**`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 - +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 -使用 [`useEffectEvent`](/reference/react/experimental_useEffectEvent) 这个特殊的 Hook 从 Effect 中提取非响应式逻辑: + + +使用 [`useEffectEvent`](/reference/react/useEffectEvent) 这个特殊的 Hook 从 Effect 中提取非响应式逻辑: ```js {1,4-6} import { useEffect, useEffectEvent } from 'react'; @@ -448,8 +450,8 @@ function ChatRoom({ roomId, theme }) { ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -464,7 +466,7 @@ function ChatRoom({ roomId, theme }) { ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; @@ -578,11 +580,13 @@ label { display: block; margin-top: 10px; } ### 使用 Effect Event 读取最新的 props 和 state {/*reading-latest-props-and-state-with-effect-events*/} - + + +**`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 -本章节描述了一个在 React 稳定版中 **还没有发布的实验性 API**。 +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 - + Effect Event 可以修复之前许多你可能试图抑制依赖项检查工具的地方。 @@ -803,8 +807,8 @@ body { ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -818,7 +822,7 @@ body { ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }); @@ -878,11 +882,13 @@ body { ### Effect Event 的局限性 {/*limitations-of-effect-events*/} - + + +**`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 -本章节描述了一个在 React 稳定版中 **还没有发布的实验性 API**。 +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 - + Effect Event 的局限性在于你如何使用他们: @@ -976,8 +982,8 @@ Effect Event 是 Effect 代码的非响应式“片段”。他们应该在使 ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -1046,8 +1052,8 @@ button { margin: 10px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -1124,8 +1130,8 @@ button { margin: 10px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -1139,7 +1145,7 @@ button { margin: 10px; } ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export default function Timer() { const [count, setCount] = useState(0); @@ -1193,8 +1199,8 @@ button { margin: 10px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -1208,7 +1214,7 @@ button { margin: 10px; } ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export default function Timer() { const [count, setCount] = useState(0); @@ -1275,8 +1281,8 @@ Effect Event 内部的代码是非响应式的。哪些情况下你会 **想要* ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -1290,7 +1296,7 @@ Effect Event 内部的代码是非响应式的。哪些情况下你会 **想要* ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export default function Timer() { const [count, setCount] = useState(0); @@ -1362,8 +1368,8 @@ button { margin: 10px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest" }, "scripts": { @@ -1377,7 +1383,7 @@ button { margin: 10px; } ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; export default function Timer() { const [count, setCount] = useState(0); @@ -1458,8 +1464,8 @@ button { margin: 10px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -1474,7 +1480,7 @@ button { margin: 10px; } ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; @@ -1599,8 +1605,8 @@ Effect Event 伴随着两秒的延迟被调用。如果你快速地从 travel ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -1615,7 +1621,7 @@ Effect Event 伴随着两秒的延迟被调用。如果你快速地从 travel ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; @@ -1737,8 +1743,8 @@ label { display: block; margin-top: 10px; } ```json package.json hidden { "dependencies": { - "react": "experimental", - "react-dom": "experimental", + "react": "canary", + "react-dom": "canary", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -1753,7 +1759,7 @@ label { display: block; margin-top: 10px; } ```js import { useState, useEffect } from 'react'; -import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; diff --git a/src/content/reference/react/Activity.md b/src/content/reference/react/Activity.md index c7d513afc3..ffd9ce5002 100644 --- a/src/content/reference/react/Activity.md +++ b/src/content/reference/react/Activity.md @@ -7,7 +7,7 @@ version: canary **The `` 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) +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 diff --git a/src/content/reference/react/useEffect.md b/src/content/reference/react/useEffect.md index e080789104..df70801f95 100644 --- a/src/content/reference/react/useEffect.md +++ b/src/content/reference/react/useEffect.md @@ -1691,11 +1691,13 @@ button { margin-left: 10px; } ### 从 Effect 读取最新的 props 和 state {/*reading-the-latest-props-and-state-from-an-effect*/} - + -本节描述了一个 **实验性的 API**,它还没有在一个稳定的 React 版本中发布。 +**`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 - +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 + + 默认情况下,在 Effect 中读取响应式值时,必须将其添加为依赖项。这样可以确保你的 Effect 对该值的每次更改都“作出响应”。对于大多数依赖项,这是你想要的行为。 @@ -1710,7 +1712,7 @@ function Page({ url, shoppingCart }) { } ``` -**如果你想在每次 `url` 更改后记录一次新的页面访问,而不是在 `shoppingCart` 更改后记录,该怎么办**?你不能在不违反 [响应规则](#specifying-reactive-dependencies) 的情况下将 `shoppingCart` 从依赖项中移除。然而,你可以表达你 **不希望** 某些代码对更改做出“响应”,即使它是在 Effect 内部调用的。使用 [`useEffectEvent`](/reference/react/experimental_useEffectEvent) Hook [声明 **Effect 事件**](/learn/separating-events-from-effects#declaring-an-effect-event),并将读取 `shoppingCart` 的代码移入其中: +**如果你想在每次 `url` 更改后记录一次新的页面访问,而不是在 `shoppingCart` 更改后记录,该怎么办**?你不能在不违反 [响应规则](#specifying-reactive-dependencies) 的情况下将 `shoppingCart` 从依赖项中移除。然而,你可以表达你 **不希望** 某些代码对更改做出“响应”,即使它是在 Effect 内部调用的。使用 [`useEffectEvent`](/reference/react/useEffectEvent) Hook [声明 **Effect 事件**](/learn/separating-events-from-effects#declaring-an-effect-event),并将读取 `shoppingCart` 的代码移入其中: ```js {2-4,7,8} function Page({ url, shoppingCart }) { diff --git a/src/content/reference/react/useEffectEvent.md b/src/content/reference/react/useEffectEvent.md new file mode 100644 index 0000000000..5f16af0386 --- /dev/null +++ b/src/content/reference/react/useEffectEvent.md @@ -0,0 +1,102 @@ +--- +title: useEffectEvent +version: canary +--- + + + + +**`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 + +[了解更多关于 React 版本发布的内容](/community/versioning-policy#all-release-channels)。 + + + + + +`useEffectEvent` is a React Hook that lets you extract non-reactive logic from your Effects into a reusable function called an [Effect Event](/learn/separating-events-from-effects#declaring-an-effect-event). + +```js +const onSomething = useEffectEvent(callback) +``` + + + + + +## Reference {/*reference*/} + +### `useEffectEvent(callback)` {/*useeffectevent*/} + +Call `useEffectEvent` at the top level of your component to declare an Effect Event. Effect Events are functions you can call inside Effects, such as `useEffect`: + +```js {4-6,11} +import { useEffectEvent, useEffect } from 'react'; + +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(() => { + showNotification('Connected!', theme); + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + onConnected(); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + // ... +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +- `callback`: A function containing the logic for your Effect Event. When you define an Effect Event with `useEffectEvent`, the `callback` always accesses the latest values from props and state when it is invoked. This helps avoid issues with stale closures. + +#### Returns {/*returns*/} + +Returns an Effect Event function. You can call this function inside `useEffect`, `useLayoutEffect`, or `useInsertionEffect`. + +#### Caveats {/*caveats*/} + +- **Only call inside Effects:** Effect Events should only be called within Effects. Define them just before the Effect that uses them. Do not pass them to other components or hooks. +- **Not a dependency shortcut:** Do not use `useEffectEvent` to avoid specifying dependencies in your Effect's dependency array. This can hide bugs and make your code harder to understand. Prefer explicit dependencies or use refs to compare previous values if needed. +- **Use for non-reactive logic:** Only use `useEffectEvent` to extract logic that does not depend on changing values. + +___ + +## Usage {/*usage*/} + +### Reading the latest props and state {/*reading-the-latest-props-and-state*/} + +Typically, when you access a reactive value inside an Effect, you must include it in the dependency array. This makes sure your Effect runs again whenever that value changes, which is usually the desired behavior. + +But in some cases, you may want to read the most recent props or state inside an Effect without causing the Effect to re-run when those values change. + +To [read the latest props or state](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) in your Effect, without making those values reactive, include them in an Effect Event. + +```js {7-9,12} +import { useEffect, useContext, useEffectEvent } from 'react'; + +function Page({ url }) { + const { items } = useContext(ShoppingCartContext); + const numberOfItems = items.length; + + const onNavigate = useEffectEvent((visitedUrl) => { + logVisit(visitedUrl, numberOfItems); + }); + + useEffect(() => { + onNavigate(url); + }, [url]); + + // ... +} +``` + +You can pass reactive values like `url` as arguments to the Effect Event. This lets you access the latest values without making your Effect re-run for every change. + diff --git a/src/sidebarReference.json b/src/sidebarReference.json index ecb48687cc..2a8b762437 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -38,6 +38,11 @@ "title": "useEffect", "path": "/reference/react/useEffect" }, + { + "title": "useEffectEvent", + "path": "/reference/react/useEffectEvent", + "version": "canary" + }, { "title": "useId", "path": "/reference/react/useId" diff --git a/vercel.json b/vercel.json index 1a55e20634..70ddefbe8b 100644 --- a/vercel.json +++ b/vercel.json @@ -248,6 +248,11 @@ "source": "/feed.xml", "destination": "/rss.xml", "permanent": true + }, + { + "source": "/reference/react/experimental_useEffectEvent", + "destination": "/reference/react/useEffectEvent", + "permanent": true } ], "headers": [