From c003ac4eb130fca70b88cf3a1b80ce5f76c51ae3 Mon Sep 17 00:00:00 2001 From: Ricky Date: Sun, 22 Sep 2024 13:19:03 -0400 Subject: [PATCH 1/2] Add stable fn notes to useMemo, useTransition, useState, and useReducer (#7181) --- src/content/reference/react/useCallback.md | 8 +- src/content/reference/react/useMemo.md | 77 ++++++++++++++++++++ src/content/reference/react/useReducer.md | 1 + src/content/reference/react/useState.md | 2 + src/content/reference/react/useTransition.md | 2 + 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/content/reference/react/useCallback.md b/src/content/reference/react/useCallback.md index 93ce700e43..abcd474dfd 100644 --- a/src/content/reference/react/useCallback.md +++ b/src/content/reference/react/useCallback.md @@ -711,7 +711,7 @@ function ChatRoom({ roomId }) { useEffect(() => { const options = createOptions(); - const connection = createConnection(); + const connection = createConnection(options); connection.connect(); // ... ``` @@ -722,7 +722,7 @@ This creates a problem. [Every reactive value must be declared as a dependency o ```js {6} useEffect(() => { const options = createOptions(); - const connection = createConnection(); + const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [createOptions]); // 🔴 Problem: This dependency changes on every render @@ -744,7 +744,7 @@ function ChatRoom({ roomId }) { useEffect(() => { const options = createOptions(); - const connection = createConnection(); + const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [createOptions]); // ✅ Only changes when createOptions changes @@ -766,7 +766,7 @@ function ChatRoom({ roomId }) { } const options = createOptions(); - const connection = createConnection(); + const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]); // ✅ Only changes when roomId changes diff --git a/src/content/reference/react/useMemo.md b/src/content/reference/react/useMemo.md index a44c86fc8e..33193ee3ba 100644 --- a/src/content/reference/react/useMemo.md +++ b/src/content/reference/react/useMemo.md @@ -1056,6 +1056,83 @@ Keep in mind that you need to run React in production mode, disable [React Devel --- +### Preventing an Effect from firing too often {/*preventing-an-effect-from-firing-too-often*/} + +Sometimes, you might want to use a value inside an [Effect:](/learn/synchronizing-with-effects) + +```js {4-7,10} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + const options = { + serverUrl: 'https://localhost:1234', + roomId: roomId + } + + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + // ... +``` + +This creates a problem. [Every reactive value must be declared as a dependency of your Effect.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) However, if you declare `options` as a dependency, it will cause your Effect to constantly reconnect to the chat room: + + +```js {5} + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); // 🔴 Problem: This dependency changes on every render + // ... +``` + +To solve this, you can wrap the object you need to call from an Effect in `useMemo`: + +```js {4-9,16} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + const options = useMemo(() => { + return { + serverUrl: 'https://localhost:1234', + roomId: roomId + }; + }, [roomId]); // ✅ Only changes when roomId changes + + useEffect(() => { + const options = createOptions(); + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); // ✅ Only changes when createOptions changes + // ... +``` + +This ensures that the `options` object is the same between re-renders if `useMemo` returns the cached object. + +However, since `useMemo` is performance optimization, not a semantic guarantee, React may throw away the cached value if [there is a specific reason to do that](#caveats). This will also cause the effect to re-fire, **so it's even better to remove the need for a function dependency** by moving your object *inside* the Effect: + +```js {5-8,13} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const options = { // ✅ No need for useMemo or object dependencies! + serverUrl: 'https://localhost:1234', + roomId: roomId + } + + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ Only changes when roomId changes + // ... +``` + +Now your code is simpler and doesn't need `useMemo`. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) + + ### Memoizing a dependency of another Hook {/*memoizing-a-dependency-of-another-hook*/} Suppose you have a calculation that depends on an object created directly in the component body: diff --git a/src/content/reference/react/useReducer.md b/src/content/reference/react/useReducer.md index dbd18f6b8d..cfd0fb856e 100644 --- a/src/content/reference/react/useReducer.md +++ b/src/content/reference/react/useReducer.md @@ -52,6 +52,7 @@ function MyComponent() { #### Caveats {/*caveats*/} * `useReducer` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. +* The `dispatch` function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) * In Strict Mode, React will **call your reducer and initializer twice** in order to [help you find accidental impurities.](#my-reducer-or-initializer-function-runs-twice) This is development-only behavior and does not affect production. If your reducer and initializer are pure (as they should be), this should not affect your logic. The result from one of the calls is ignored. --- diff --git a/src/content/reference/react/useState.md b/src/content/reference/react/useState.md index 48d96f8eec..4aa9d59112 100644 --- a/src/content/reference/react/useState.md +++ b/src/content/reference/react/useState.md @@ -85,6 +85,8 @@ function handleClick() { * React [batches state updates.](/learn/queueing-a-series-of-state-updates) It updates the screen **after all the event handlers have run** and have called their `set` functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use [`flushSync`.](/reference/react-dom/flushSync) +* The `set` function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) + * Calling the `set` function *during rendering* is only allowed from within the currently rendering component. React will discard its output and immediately attempt to render it again with the new state. This pattern is rarely needed, but you can use it to **store information from the previous renders**. [See an example below.](#storing-information-from-previous-renders) * In Strict Mode, React will **call your updater function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your updater function is pure (as it should be), this should not affect the behavior. The result from one of the calls will be ignored. diff --git a/src/content/reference/react/useTransition.md b/src/content/reference/react/useTransition.md index 77c2cdad51..5066fe6378 100644 --- a/src/content/reference/react/useTransition.md +++ b/src/content/reference/react/useTransition.md @@ -80,6 +80,8 @@ function TabContainer() { * The function you pass to `startTransition` must be synchronous. React immediately executes this function, marking all state updates that happen while it executes as Transitions. If you try to perform more state updates later (for example, in a timeout), they won't be marked as Transitions. +* The `startTransition` function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) + * A state update marked as a Transition will be interrupted by other state updates. For example, if you update a chart component inside a Transition, but then start typing into an input while the chart is in the middle of a re-render, React will restart the rendering work on the chart component after handling the input update. * Transition updates can't be used to control text inputs. From d16fd3b8fe0fe6326b5f4a20acb7b62df04bc440 Mon Sep 17 00:00:00 2001 From: Xleine Date: Tue, 1 Oct 2024 15:52:35 +0800 Subject: [PATCH 2/2] fix merge conflict && translate new content --- src/content/reference/react/useMemo.md | 30 +++++++++----------- src/content/reference/react/useReducer.md | 7 +---- src/content/reference/react/useState.md | 8 ++---- src/content/reference/react/useTransition.md | 8 ++---- 4 files changed, 18 insertions(+), 35 deletions(-) diff --git a/src/content/reference/react/useMemo.md b/src/content/reference/react/useMemo.md index af2474c6c5..8ffeee2ea7 100644 --- a/src/content/reference/react/useMemo.md +++ b/src/content/reference/react/useMemo.md @@ -1056,12 +1056,9 @@ label { --- -<<<<<<< HEAD -### 记忆另一个 Hook 的依赖 {/*memoizing-a-dependency-of-another-hook*/} -======= -### Preventing an Effect from firing too often {/*preventing-an-effect-from-firing-too-often*/} +### 防止过于频繁地触发 Effect {/*preventing-an-effect-from-firing-too-often*/} -Sometimes, you might want to use a value inside an [Effect:](/learn/synchronizing-with-effects) +有时你可能会想要在 [Effect](/learn/synchronizing-with-effects) 中使用变量: ```js {4-7,10} function ChatRoom({ roomId }) { @@ -1078,7 +1075,7 @@ function ChatRoom({ roomId }) { // ... ``` -This creates a problem. [Every reactive value must be declared as a dependency of your Effect.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) However, if you declare `options` as a dependency, it will cause your Effect to constantly reconnect to the chat room: +但是这样做会带来一些问题。因为 [Effect 中的每一个响应式值都应该声明为其依赖。](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) 然而如果你将 `options` 声明为依赖,会导致在 Effect 中不断地重新连接到聊天室: ```js {5} @@ -1086,11 +1083,11 @@ This creates a problem. [Every reactive value must be declared as a dependency o const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [options]); // 🔴 Problem: This dependency changes on every render + }, [options]); // 🔴 问题:每次渲染这个依赖项都会发生改变 // ... ``` -To solve this, you can wrap the object you need to call from an Effect in `useMemo`: +为了解决这个场景,你可以使用 `useMemo` 将 Effect 中使用的对象包装起来: ```js {4-9,16} function ChatRoom({ roomId }) { @@ -1101,27 +1098,27 @@ function ChatRoom({ roomId }) { serverUrl: 'https://localhost:1234', roomId: roomId }; - }, [roomId]); // ✅ Only changes when roomId changes + }, [roomId]); // ✅ 只有当 roomId 改变时才会被改变 useEffect(() => { const options = createOptions(); const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [options]); // ✅ Only changes when createOptions changes + }, [options]); // ✅ 只有当 createOptions 改变时才会被改变 // ... ``` -This ensures that the `options` object is the same between re-renders if `useMemo` returns the cached object. +因为 `useMemo` 返回了缓存的对象,所以这将确保 `options` 对象在重新渲染期间保持不变。 -However, since `useMemo` is performance optimization, not a semantic guarantee, React may throw away the cached value if [there is a specific reason to do that](#caveats). This will also cause the effect to re-fire, **so it's even better to remove the need for a function dependency** by moving your object *inside* the Effect: +然而,因为 `useMemo` 只是一个性能优化手段,而并不是语义上的保证,所以 React 在 [特定场景下](#caveats) 会丢弃缓存值。这也会导致重新触发 Effect,因此 **最好通过将对象移动到 Effect 内部来消除对函数的依赖**: ```js {5-8,13} function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { - const options = { // ✅ No need for useMemo or object dependencies! + const options = { // ✅ 不需要将 useMemo 或对象作为依赖! serverUrl: 'https://localhost:1234', roomId: roomId } @@ -1129,15 +1126,14 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ Only changes when roomId changes + }, [roomId]); // ✅ 只有当 roomId 改变时才会被改变 // ... ``` -Now your code is simpler and doesn't need `useMemo`. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) +现在你的代码不需要使用 `useMemo` 并且更加简洁。[了解移除 Effect 依赖项的更多信息。](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) -### Memoizing a dependency of another Hook {/*memoizing-a-dependency-of-another-hook*/} ->>>>>>> c003ac4eb130fca70b88cf3a1b80ce5f76c51ae3 +### 记忆另一个 Hook 的依赖 {/*memoizing-a-dependency-of-another-hook*/} 假设你有一个计算函数依赖于直接在组件主体中创建的对象: diff --git a/src/content/reference/react/useReducer.md b/src/content/reference/react/useReducer.md index 857734f400..399bb75cd3 100644 --- a/src/content/reference/react/useReducer.md +++ b/src/content/reference/react/useReducer.md @@ -51,14 +51,9 @@ function MyComponent() { #### 注意事项 {/*caveats*/} -<<<<<<< HEAD * `useReducer` 是一个 Hook,所以只能在 **组件的顶层作用域** 或自定义 Hook 中调用,而不能在循环或条件语句中调用。如果你有这种需求,可以创建一个新的组件,并把 state 移入其中。 +* `dispatch` 函数具有稳定的标识,所以你经常会看到 Effect 的依赖数组中会省略它,即使包含它也不会导致 Effect 重新触发。如果 linter 允许你省略依赖项并且没有报错,那么你就可以安全地省略它。[了解移除 Effect 依赖项的更多信息。](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) * 严格模式下 React 会 **调用两次 reducer 和初始化函数**,这可以 [帮助你发现意外的副作用](#my-reducer-or-initializer-function-runs-twice)。这只是开发模式下的行为,并不会影响生产环境。只要 reducer 和初始化函数是纯函数(理应如此)就不会改变你的逻辑。其中一个调用结果会被忽略。 -======= -* `useReducer` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. -* The `dispatch` function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) -* In Strict Mode, React will **call your reducer and initializer twice** in order to [help you find accidental impurities.](#my-reducer-or-initializer-function-runs-twice) This is development-only behavior and does not affect production. If your reducer and initializer are pure (as they should be), this should not affect your logic. The result from one of the calls is ignored. ->>>>>>> c003ac4eb130fca70b88cf3a1b80ce5f76c51ae3 --- diff --git a/src/content/reference/react/useState.md b/src/content/reference/react/useState.md index 3bc23c0258..49300a9435 100644 --- a/src/content/reference/react/useState.md +++ b/src/content/reference/react/useState.md @@ -85,13 +85,9 @@ function handleClick() { * React 会 [批量处理状态更新](/learn/queueing-a-series-of-state-updates)。它会在所有 **事件处理函数运行** 并调用其 `set` 函数后更新屏幕。这可以防止在单个事件期间多次重新渲染。在某些罕见情况下,你需要强制 React 更早地更新屏幕,例如访问 DOM,你可以使用 [`flushSync`](/reference/react-dom/flushSync)。 -<<<<<<< HEAD -* 在渲染期间,只允许在当前渲染组件内部调用 `set` 函数。React 将丢弃其输出并立即尝试使用新状态重新渲染。这种方式很少需要,但你可以使用它来存储 **先前渲染中的信息**。[请参见下面的示例](#storing-information-from-previous-renders)。 -======= -* The `set` function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) +* `set` 函数具有稳定的标识,所以你经常会看到 Effect 的依赖数组中会省略它,即使包含它也不会导致 Effect 重新触发。如果 linter 允许你省略依赖项并且没有报错,那么你就可以安全地省略它。[了解移除 Effect 依赖项的更多信息。](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) -* Calling the `set` function *during rendering* is only allowed from within the currently rendering component. React will discard its output and immediately attempt to render it again with the new state. This pattern is rarely needed, but you can use it to **store information from the previous renders**. [See an example below.](#storing-information-from-previous-renders) ->>>>>>> c003ac4eb130fca70b88cf3a1b80ce5f76c51ae3 +* 在渲染期间,只允许在当前渲染组件内部调用 `set` 函数。React 将丢弃其输出并立即尝试使用新状态重新渲染。这种方式很少需要,但你可以使用它来存储 **先前渲染中的信息**。[请参见下面的示例](#storing-information-from-previous-renders)。 * 在严格模式中,React 将 **两次调用你的更新函数**,以帮助你找到 [意外的不纯性](#my-initializer-or-updater-function-runs-twice)。这只是开发时的行为,不影响生产。如果你的更新函数是纯函数(本该是这样),就不应影响该行为。其中一次调用的结果将被忽略。 diff --git a/src/content/reference/react/useTransition.md b/src/content/reference/react/useTransition.md index fe30f3307b..4be5c574d8 100644 --- a/src/content/reference/react/useTransition.md +++ b/src/content/reference/react/useTransition.md @@ -80,13 +80,9 @@ function TabContainer() { * 传递给 `startTransition` 的函数必须是同步的。React 会立即执行此函数,并将在其执行期间发生的所有状态更新标记为 transition。如果在其执行期间,尝试稍后执行状态更新(例如在一个定时器中执行状态更新),这些状态更新不会被标记为 transition。 -<<<<<<< HEAD -* 标记为 Transition 的状态更新将被其他状态更新打断。例如在 Transition 中更新图表组件,并在图表组件仍在重新渲染时继续在输入框中输入,React 将首先处理输入框的更新,之后再重新启动对图表组件的渲染工作。 -======= -* The `startTransition` function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) +* `startTransition` 函数具有稳定的标识,所以你经常会看到 Effect 的依赖数组中会省略它,即使包含它也不会导致 Effect 重新触发。如果 linter 允许你省略依赖项并且没有报错,那么你就可以安全地省略它。[了解移除 Effect 依赖项的更多信息。](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) -* A state update marked as a Transition will be interrupted by other state updates. For example, if you update a chart component inside a Transition, but then start typing into an input while the chart is in the middle of a re-render, React will restart the rendering work on the chart component after handling the input update. ->>>>>>> c003ac4eb130fca70b88cf3a1b80ce5f76c51ae3 +* 标记为 Transition 的状态更新将被其他状态更新打断。例如在 Transition 中更新图表组件,并在图表组件仍在重新渲染时继续在输入框中输入,React 将首先处理输入框的更新,之后再重新启动对图表组件的渲染工作。 * Transition 更新不能用于控制文本输入。