diff --git a/src/content/learn/removing-effect-dependencies.md b/src/content/learn/removing-effect-dependencies.md
index 0a5151daaa..5b47b15674 100644
--- a/src/content/learn/removing-effect-dependencies.md
+++ b/src/content/learn/removing-effect-dependencies.md
@@ -1,26 +1,26 @@
---
-title: 'Removing Effect Dependencies'
+title: 移除 Effect 依赖项
---
-When you write an Effect, the linter will verify that you've included every reactive value (like props and state) that the Effect reads in the list of your Effect's dependencies. This ensures that your Effect remains synchronized with the latest props and state of your component. Unnecessary dependencies may cause your Effect to run too often, or even create an infinite loop. Follow this guide to review and remove unnecessary dependencies from your Effects.
+当你写 Effect 的时候,代码检查工具会检查依赖项列表是否已经包含了 Effect 读取的每个响应式值(例如 props 和 state)。这可以保证 Effect 和组件最新的 props 以及 state 保持同步。不必要的依赖可能会导致 Effect 频繁运行,甚至造成无限循环。请跟随这篇指南来检查和移除你的 Effect 中不必要的依赖项。
-- How to fix infinite Effect dependency loops
-- What to do when you want to remove a dependency
-- How to read a value from your Effect without "reacting" to it
-- How and why to avoid object and function dependencies
-- Why suppressing the dependency linter is dangerous, and what to do instead
+- 怎样修复 Effect 依赖的无限循环
+- 移除一个依赖项的时候要做些什么
+- 怎样从 Effect 中读取一个值而不需要对他“做出响应”
+- 怎样以及为什么要避免对象和函数依赖项
+- 为什么抑制依赖项检查是危险的,以及应该怎么做
-## Dependencies should match the code {/*dependencies-should-match-the-code*/}
+## 依赖项应该和代码匹配 {/*dependencies-should-match-the-code*/}
-When you write an Effect, you first specify how to [start and stop](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) whatever you want your Effect to be doing:
+当你写 Effect 时,无论想要 Effect 做什么,首先要做的就是指明如何 [开始和结束](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect):
```js {5-7}
const serverUrl = 'https://localhost:1234';
@@ -34,7 +34,7 @@ function ChatRoom({ roomId }) {
}
```
-Then, if you leave the Effect dependencies empty (`[]`), the linter will suggest the correct dependencies:
+如果你将 Effect 依赖项置为空(`[]`),代码检查工具就会建议正确的依赖项:
@@ -49,7 +49,7 @@ function ChatRoom({ roomId }) {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
- }, []); // <-- Fix the mistake here!
+ }, []); // <-- 修复这里的错误!
return Welcome to the {roomId} room!
;
}
@@ -77,7 +77,7 @@ export default function App() {
```js chat.js
export function createConnection(serverUrl, roomId) {
- // A real implementation would actually connect to the server
+ // 真正的实现会真的连接服务器
return {
connect() {
console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
@@ -96,7 +96,7 @@ button { margin-left: 10px; }
-Fill them in according to what the linter says:
+根据代码检查工具的建议填写依赖项:
```js {6}
function ChatRoom({ roomId }) {
@@ -104,12 +104,12 @@ function ChatRoom({ roomId }) {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
- }, [roomId]); // ✅ All dependencies declared
+ }, [roomId]); // ✅ 所有依赖项被声明
// ...
}
```
-[Effects "react" to reactive values.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Since `roomId` is a reactive value (it can change due to a re-render), the linter verifies that you've specified it as a dependency. If `roomId` receives a different value, React will re-synchronize your Effect. This ensures that the chat stays connected to the selected room and "reacts" to the dropdown:
+[Effect 会对响应式值“做出响应”](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values)。由于 roomId 是响应式值(它会因为重新渲染而变化),代码检查工具会验证你是否已经将它指定为依赖项。每当 roomId 接收到一个不同的值,React 就会重新同步对应的 Effect。这可以保证聊天会一直和选中的房间保持连接,并对下拉框的变化“做出响应”:
@@ -152,7 +152,7 @@ export default function App() {
```js chat.js
export function createConnection(serverUrl, roomId) {
- // A real implementation would actually connect to the server
+ // 真正的实现会真的连接服务器
return {
connect() {
console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
@@ -171,24 +171,24 @@ button { margin-left: 10px; }
-### To remove a dependency, prove that it's not a dependency {/*to-remove-a-dependency-prove-that-its-not-a-dependency*/}
+### 移除一个依赖,需要先证明它不是依赖项 {/*to-remove-a-dependency-prove-that-its-not-a-dependency*/}
-Notice that you can't "choose" the dependencies of your Effect. Every reactive value used by your Effect's code must be declared in your dependency list. The dependency list is determined by the surrounding code:
+注意你不能“选择”Effect 的依赖项。Effect 代码中用到的每一个 响应式值 都必须在依赖项列表中声明。依赖项列表是由周围的代码决定的:
```js [[2, 3, "roomId"], [2, 5, "roomId"], [2, 8, "roomId"]]
const serverUrl = 'https://localhost:1234';
-function ChatRoom({ roomId }) { // This is a reactive value
+function ChatRoom({ roomId }) { // 这是响应式值
useEffect(() => {
- const connection = createConnection(serverUrl, roomId); // This Effect reads that reactive value
+ const connection = createConnection(serverUrl, roomId); // 这个 Effect 读取了响应式值
connection.connect();
return () => connection.disconnect();
- }, [roomId]); // ✅ So you must specify that reactive value as a dependency of your Effect
+ }, [roomId]); // ✅ 所以你必须将这个响应式值指定为 Effect 的依赖项
// ...
}
```
-[Reactive values](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) include props and all variables and functions declared directly inside of your component. Since `roomId` is a reactive value, you can't remove it from the dependency list. The linter wouldn't allow it:
+[响应式值](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) 包括 props 和直接在组件内部声明的所有变量和函数。因为 roomId 是响应式值,所以不能将它从依赖项列表中移除。这在代码检查工具中是不会通过的:
```js {8}
const serverUrl = 'https://localhost:1234';
@@ -198,30 +198,30 @@ function ChatRoom({ roomId }) {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
- }, []); // 🔴 React Hook useEffect has a missing dependency: 'roomId'
+ }, []); // 🔴 React Hook useEffect 缺少依赖项: 'roomId'
// ...
}
```
-And the linter would be right! Since `roomId` may change over time, this would introduce a bug in your code.
+代码检查工具是对的!因为 `roomId` 可能随着时间的过去而变化,这将在代码中引入 bug。
-**To remove a dependency, "prove" to the linter that it *doesn't need* to be a dependency.** For example, you can move `roomId` out of your component to prove that it's not reactive and won't change on re-renders:
+**移除依赖项需要向代码检查工具证明这个值不需要成为依赖项**。例如,你可以从组件中移除 `roomId` 来证明它不是响应式值且在重新渲染时不会变化:
```js {2,9}
const serverUrl = 'https://localhost:1234';
-const roomId = 'music'; // Not a reactive value anymore
+const roomId = 'music'; // 不再是响应式值
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
- }, []); // ✅ All dependencies declared
+ }, []); // ✅ 声明的所有依赖
// ...
}
```
-Now that `roomId` is not a reactive value (and can't change on a re-render), it doesn't need to be a dependency:
+既然 `roomId` 不是响应式值(并且在重渲染时不会变化),它就不需要作为依赖项:
@@ -244,7 +244,7 @@ export default function ChatRoom() {
```js chat.js
export function createConnection(serverUrl, roomId) {
- // A real implementation would actually connect to the server
+ // 真正的实现会真的连接服务器
return {
connect() {
console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
@@ -263,43 +263,43 @@ button { margin-left: 10px; }
-This is why you could now specify an [empty (`[]`) dependency list.](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) Your Effect *really doesn't* depend on any reactive value anymore, so it *really doesn't* need to re-run when any of the component's props or state change.
+这就是为什么你现在可以指定一个 [空 (`[]`) 依赖项列表](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means)。Effect **实际上不再**依赖任何响应式值,所以当组件的任何 props 和 state 变化时,它**并不**需要重新运行。
-### To change the dependencies, change the code {/*to-change-the-dependencies-change-the-code*/}
+### 修改依赖项之前先修改代码 {/*to-change-the-dependencies-change-the-code*/}
-You might have noticed a pattern in your workflow:
+你可能已经注意到工作流中的一个模式:
-1. First, you **change the code** of your Effect or how your reactive values are declared.
-2. Then, you follow the linter and adjust the dependencies to **match the code you have changed.**
-3. If you're not happy with the list of dependencies, you **go back to the first step** (and change the code again).
+1. 首先你要 **修改代码**,包括 Effect 或者声明响应式值的方式。
+2. 然后遵循代码检查工具的建议并且调整依赖项使其 **匹配刚刚修改的代码**。
+3. 如果你不满意依赖项列表,则 **返回第一步**(再次修改代码)。
-The last part is important. **If you want to change the dependencies, change the surrounding code first.** You can think of the dependency list as [a list of all the reactive values used by your Effect's code.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) You don't *choose* what to put on that list. The list *describes* your code. To change the dependency list, change the code.
+最后一部分很重要。**如果你想修改依赖项,就要先修改周围的代码**。你可以把依赖项列表当成 Effect 代码中 [用到的所有响应式值的列表](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency)。不是你 **选择** 放什么到列表,而是列表 **描述** 了你的代码。想要修改依赖项列表,就要先修改代码。
-This might feel like solving an equation. You might start with a goal (for example, to remove a dependency), and you need to "find" the code matching that goal. Not everyone finds solving equations fun, and the same thing could be said about writing Effects! Luckily, there is a list of common recipes that you can try below.
+这可能感觉像是解决一个方程式。你也许会从目标着手(例如移除依赖项),需要“找到”匹配目标的代码。不是每个人都对解决方程式感兴趣,写 Effect 也是这样!幸运的是下面有一些你可以尝试的常用方法列表。
-If you have an existing codebase, you might have some Effects that suppress the linter like this:
+如果你有一个已经存在的代码库,可能有像这样存在抑制代码检查工具的 Effect:
```js {3-4}
useEffect(() => {
// ...
- // 🔴 Avoid suppressing the linter like this:
+ // 🔴 避免像这样抑制代码检查工具:
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);
```
-**When dependencies don't match the code, there is a very high risk of introducing bugs.** By suppressing the linter, you "lie" to React about the values your Effect depends on.
+**当依赖项不匹配代码时,会增加引入 bug 的风险**。通过抑制代码检查工具,可以就 Effect 依赖的值对 React“撒谎”。
-Instead, use the techniques below.
+取而代之的是,使用下面的技巧。
-#### Why is suppressing the dependency linter so dangerous? {/*why-is-suppressing-the-dependency-linter-so-dangerous*/}
+#### 为什么抑制依赖项检查会很危险? {/*why-is-suppressing-the-dependency-linter-so-dangerous*/}
-Suppressing the linter leads to very unintuitive bugs that are hard to find and fix. Here's one example:
+抑制代码检查会导致非常不直观的 bug,它们很难被找到并修复。这里是一个案例:
@@ -348,31 +348,31 @@ button { margin: 10px; }
-Let's say that you wanted to run the Effect "only on mount". You've read that [empty (`[]`) dependencies](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) do that, so you've decided to ignore the linter, and forcefully specified `[]` as the dependencies.
+假设你想要Effect“只在组件挂载”的时候运行。你知道 [空 (`[]`)依赖项](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) 可以达到这个目的,所以你已经决定忽略代码检查工具的建议并且强制指定 `[]` 为依赖。
-This counter was supposed to increment every second by the amount configurable with the two buttons. However, since you "lied" to React that this Effect doesn't depend on anything, React forever keeps using the `onTick` function from the initial render. [During that render,](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` was `0` and `increment` was `1`. This is why `onTick` from that render always calls `setCount(0 + 1)` every second, and you always see `1`. Bugs like this are harder to fix when they're spread across multiple components.
+这个计数器本应该每秒增加一个数,这个数由两个按钮配置。但是因为你对 React“谎称”这个 Effect 不依赖任何值,所以 React 一直使用初始渲染时的 `onTick` 函数。[在这次渲染期间](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time),`count` 是 `0`,`increment` 为 `1`。这就是为什么此次渲染的 `onTick` 总是每秒调用一次 `setCount(0 + 1)`,且你看到的总是 `1`。像这样的 bug,当它们跨越多个组件的时候更难修复。
-There's always a better solution than ignoring the linter! To fix this code, you need to add `onTick` to the dependency list. (To ensure the interval is only setup once, [make `onTick` an Effect Event.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events))
+比起忽略代码检查,有一个更好的解决方法!你需要向依赖项列表中加入 `onTick` 来修复这段代码。(为了确保 interval 只设置一次,需要 [让 `onTick` 成为 Effect Event](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)。)
-**We recommend treating the dependency lint error as a compilation error. If you don't suppress it, you will never see bugs like this.** The rest of this page documents the alternatives for this and other cases.
+**我们推荐你像对待编译错误一样对待依赖项检查错误。如果你不抑制它,你就永远不会看到像这样的 bug**。这篇文档的剩余部分记录了这种这种场景下的一些替代方案。
-## Removing unnecessary dependencies {/*removing-unnecessary-dependencies*/}
+## 移除非必要的依赖项 {/*removing-unnecessary-dependencies*/}
-Every time you adjust the Effect's dependencies to reflect the code, look at the dependency list. Does it make sense for the Effect to re-run when any of these dependencies change? Sometimes, the answer is "no":
+每次调整 Effect 的依赖项以反映代码时,看看依赖项列表。当任意依赖项变化时,Effect 重新运行有意义吗?有时候答案是“no”:
-* You might want to re-execute *different parts* of your Effect under different conditions.
-* You might want to only read the *latest value* of some dependency instead of "reacting" to its changes.
-* A dependency may change too often *unintentionally* because it's an object or a function.
+* 你可能想要在不同条件下重新执行 Effect 的 **不同部分**。
+* 你可能想要只读取一些依赖项的 **最新值** 而不是对它的变化“做出响应”。
+* 因为它是一个对象或者函数,所以一个依赖可能 **无意中** 会频繁变化。
-To find the right solution, you'll need to answer a few questions about your Effect. Let's walk through them.
+为了找到合适的解决方案,你需要回答一些关于你的 Effect 的一些问题。让我们开始吧。
-### Should this code move to an event handler? {/*should-this-code-move-to-an-event-handler*/}
+### 这段代码应该移动事件处理函数中吗? {/*should-this-code-move-to-an-event-handler*/}
-The first thing you should think about is whether this code should be an Effect at all.
+你首先应该思考的是这段代码是否应该是一个 Effect。
-Imagine a form. On submit, you set the `submitted` state variable to `true`. You need to send a POST request and show a notification. You've put this logic inside an Effect that "reacts" to `submitted` being `true`:
+假设有一个表单。在提交的时候,设置 `submitted` state 变量为 `true`。你需要发送一个 POST 请求并且展示一个通知。你已经把逻辑放在了 Effect 里面,会对 `submitted` 变为 `true` “做出响应”:
```js {6-8}
function Form() {
@@ -380,7 +380,7 @@ function Form() {
useEffect(() => {
if (submitted) {
- // 🔴 Avoid: Event-specific logic inside an Effect
+ // 🔴 Avoid: Effect 内部的 Event-specific 逻辑
post('/api/register');
showNotification('Successfully registered!');
}
@@ -394,7 +394,7 @@ function Form() {
}
```
-Later, you want to style the notification message according to the current theme, so you read the current theme. Since `theme` is declared in the component body, it is a reactive value, so you add it as a dependency:
+之后你需要根据当前的主题给通知信息设置样式,所以你需要读取当前的主题。由于 `theme` 在组件内声明,所以它是一个响应式值,所以你需要将它添加到依赖项:
```js {3,9,11}
function Form() {
@@ -403,11 +403,11 @@ function Form() {
useEffect(() => {
if (submitted) {
- // 🔴 Avoid: Event-specific logic inside an Effect
+ // 🔴 Avoid: Effect 内部的 Event-specific 逻辑
post('/api/register');
showNotification('Successfully registered!', theme);
}
- }, [submitted, theme]); // ✅ All dependencies declared
+ }, [submitted, theme]); // ✅ 声明的所有依赖
function handleSubmit() {
setSubmitted(true);
@@ -417,16 +417,16 @@ function Form() {
}
```
-By doing this, you've introduced a bug. Imagine you submit the form first and then switch between Dark and Light themes. The `theme` will change, the Effect will re-run, and so it will display the same notification again!
+这样做会引入一个 bug。假设你先提交了表单,然后在 Dark 和 Light 主题间切换。`theme` 会变化,Effect 就会重新运行,所以它又会展示同样的通知消息!
-**The problem here is that this shouldn't be an Effect in the first place.** You want to send this POST request and show the notification in response to *submitting the form,* which is a particular interaction. To run some code in response to particular interaction, put that logic directly into the corresponding event handler:
+**这里的问题首先是这不应该是一个 Effect** 。你想要发送这个 POST 请求并且作为对“提交表单”这个特殊交互的响应展示通知。为了响应特殊交互而运行的一些代码,直接把这段逻辑放在相应的事件处理函数中:
```js {6-7}
function Form() {
const theme = useContext(ThemeContext);
function handleSubmit() {
- // ✅ Good: Event-specific logic is called from event handlers
+ // ✅ Good: Event-specific 逻辑是从事件处理函数调用的
post('/api/register');
showNotification('Successfully registered!', theme);
}
@@ -435,13 +435,13 @@ function Form() {
}
```
-Now that the code is in an event handler, it's not reactive--so it will only run when the user submits the form. Read more about [choosing between event handlers and Effects](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) and [how to delete unnecessary Effects.](/learn/you-might-not-need-an-effect)
+既然代码是在一个事件处理函数里,所以它不是响应式的 — 所以它只会在用户提交表单的时候运行。了解更多关于 [如何选择事件处理函数和 Effect](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) 以及 [如何删除不必要的Effect](/learn/you-might-not-need-an-effect)。
-### Is your Effect doing several unrelated things? {/*is-your-effect-doing-several-unrelated-things*/}
+### 你的 Effect 正在做若干不相关的事情吗? {/*is-your-effect-doing-several-unrelated-things*/}
-The next question you should ask yourself is whether your Effect is doing several unrelated things.
+你应该扪心自问的下一个问题是你的 Effect 是否正在做若干不相关的事情。
-Imagine you're creating a shipping form where the user needs to choose their city and area. You fetch the list of `cities` from the server according to the selected `country` to show them in a dropdown:
+假设你正在创建一个 shipping 表单,用户在里面需要选择他们的城市和地区。你根据选中的 `country` 从服务器获取 `cities` 列表并且在下拉菜单中展示:
```js
function ShippingForm({ country }) {
@@ -460,14 +460,14 @@ function ShippingForm({ country }) {
return () => {
ignore = true;
};
- }, [country]); // ✅ All dependencies declared
+ }, [country]); // ✅ 声明的所有依赖
// ...
```
-This is a good example of [fetching data in an Effect.](/learn/you-might-not-need-an-effect#fetching-data) You are synchronizing the `cities` state with the network according to the `country` prop. You can't do this in an event handler because you need to fetch as soon as `ShippingForm` is displayed and whenever the `country` changes (no matter which interaction causes it).
+这是一个 [在Effect中获取数据](/learn/you-might-not-need-an-effect#fetching-data) 的优秀示例。你正在根据 `country` prop 借助网络同步 `cities` state。你无法在一个事件函数中去做这件事情,因为你需要 `ShippingForm` 只要展示就去获取数据,并在 `country` 变化时立即重新获取(无论是什么交互导致的)。
-Now let's say you're adding a second select box for city areas, which should fetch the `areas` for the currently selected `city`. You might start by adding a second `fetch` call for the list of areas inside the same Effect:
+假设你现在正在因为添加城市区域二级选择框,这个选择框获取当前选中的 `city` 的 `areas`。你可能会从在同一个 Effect 内部添加第二个 `fetch` 调用获取区域列表开始:
```js {15-24,28}
function ShippingForm({ country }) {
@@ -484,7 +484,7 @@ function ShippingForm({ country }) {
setCities(json);
}
});
- // 🔴 Avoid: A single Effect synchronizes two independent processes
+ // 🔴 Avoid: 同一个 Effect 同步两个独立的进程
if (city) {
fetch(`/api/areas?city=${city}`)
.then(response => response.json())
@@ -497,19 +497,19 @@ function ShippingForm({ country }) {
return () => {
ignore = true;
};
- }, [country, city]); // ✅ All dependencies declared
+ }, [country, city]); // ✅ 声明的所有依赖项
// ...
```
-However, since the Effect now uses the `city` state variable, you've had to add `city` to the list of dependencies. That, in turn, introduced a problem: when the user selects a different city, the Effect will re-run and call `fetchCities(country)`. As a result, you will be unnecessarily refetching the list of cities many times.
+但是由于 Effect 现在使用 `city` state 变量,你必须将 `city` 添加到依赖项列表中。相对地,这会引起一个问题:每当用户选择不同的城市,Effect 就会重新运行和调用 `fetchCities(country)`。结果就是,你需要多次不必要地重新获取城市列表。
-**The problem with this code is that you're synchronizing two different unrelated things:**
+**这段代码的问题在于你同时同步两个不相关的事物:**
-1. You want to synchronize the `cities` state to the network based on the `country` prop.
-1. You want to synchronize the `areas` state to the network based on the `city` state.
+1. 你想要基于 `country` prop 将 `cities` state 同步到网络。
+1. 你想要基于 `city` state 将 `areas` state 同步到网络。
-Split the logic into two Effects, each of which reacts to the prop that it needs to synchronize with:
+将这段逻辑拆分成两个 Effect,每个 Effect 只对它需要同步的 prop 做出响应:
```js {19-33}
function ShippingForm({ country }) {
@@ -526,7 +526,7 @@ function ShippingForm({ country }) {
return () => {
ignore = true;
};
- }, [country]); // ✅ All dependencies declared
+ }, [country]); // ✅ 声明的所有依赖
const [city, setCity] = useState(null);
const [areas, setAreas] = useState(null);
@@ -544,18 +544,18 @@ function ShippingForm({ country }) {
ignore = true;
};
}
- }, [city]); // ✅ All dependencies declared
+ }, [city]); // ✅ 声明的所有依赖
// ...
```
-Now the first Effect only re-runs if the `country` changes, while the second Effect re-runs when the `city` changes. You've separated them by purpose: two different things are synchronized by two separate Effects. Two separate Effects have two separate dependency lists, so they won't trigger each other unintentionally.
+现在第一个 Effect 只在 `country` 变化时重新运行,而第二个 Effect 只在 `city` 变化时重新运行。你已经根据目的将他们进行了拆分:两个不同的事物由两个单独的 Effect 进行同步。两个独立的 Effect 有各自的依赖项列表,所以不会无意中相互触发。
-The final code is longer than the original, but splitting these Effects is still correct. [Each Effect should represent an independent synchronization process.](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) In this example, deleting one Effect doesn't break the other Effect's logic. This means they *synchronize different things,* and it's good to split them up. If you're concerned about duplication, you can improve this code by [extracting repetitive logic into a custom Hook.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks)
+最终代码比原本的代码更长,但是分割这些 Effect 的做法仍然是非常正确的。[每个 Effect 应该表示一个独立的同步进程](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process)。在这个例子中,删除一个 Effect 不会破坏其他 Effect 的逻辑。这意味着他们 **同步不同的事物**,并且拆分它们是有好处的。如果你担心重复,可以通过 [提取重复逻辑到自定义 Hook ](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks)来改进这段代码。
-### Are you reading some state to calculate the next state? {/*are-you-reading-some-state-to-calculate-the-next-state*/}
+### 你正在读取一些 state 来计算下一个 state 吗? {/*are-you-reading-some-state-to-calculate-the-next-state*/}
-This Effect updates the `messages` state variable with a newly created array every time a new message arrives:
+这个 Effect 会在每次新消息到达时通过新建数组来更新 state 变量 `messages`:
```js {2,6-8}
function ChatRoom({ roomId }) {
@@ -569,7 +569,7 @@ function ChatRoom({ roomId }) {
// ...
```
-It uses the `messages` variable to [create a new array](/learn/updating-arrays-in-state) starting with all the existing messages and adds the new message at the end. However, since `messages` is a reactive value read by an Effect, it must be a dependency:
+它使用 `messages` 变量来 [创建一个以所有已经存在的消息开头的新数组](/learn/updating-arrays-in-state),并且在末尾添加新消息。但是因为 `messages` 是在 Effect 中读取的响应式值,所以它必须被设置为依赖项:
```js {7,10}
function ChatRoom({ roomId }) {
@@ -581,15 +581,15 @@ function ChatRoom({ roomId }) {
setMessages([...messages, receivedMessage]);
});
return () => connection.disconnect();
- }, [roomId, messages]); // ✅ All dependencies declared
+ }, [roomId, messages]); // ✅ 声明的所有依赖
// ...
```
-And making `messages` a dependency introduces a problem.
+让 `messages` 成为依赖项会引发一个问题。
-Every time you receive a message, `setMessages()` causes the component to re-render with a new `messages` array that includes the received message. However, since this Effect now depends on `messages`, this will *also* re-synchronize the Effect. So every new message will make the chat re-connect. The user would not like that!
+每当收到一个消息,`setMessages()` 会因为新 `messages` 数组包含接收到的消息而导致组件重新渲染。但是由于这个 Effect 现在依赖于 `messages`,这 **也** 会重新同步这个 Effect。所以每条新的消息都会让聊天室重新连接。用户并不希望这样!
-To fix the issue, don't read `messages` inside the Effect. Instead, pass an [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) to `setMessages`:
+为了修复这个问题,请不要在 Effect 内部读取 `messages` 值。而是传递一个 [更新函数](/reference/react/useState#updating-state-based-on-the-previous-state) 来 `setMessages`:
```js {7,10}
function ChatRoom({ roomId }) {
@@ -601,21 +601,21 @@ function ChatRoom({ roomId }) {
setMessages(msgs => [...msgs, receivedMessage]);
});
return () => connection.disconnect();
- }, [roomId]); // ✅ All dependencies declared
+ }, [roomId]); // ✅ 声明的所有依赖
// ...
```
-**Notice how your Effect does not read the `messages` variable at all now.** You only need to pass an updater function like `msgs => [...msgs, receivedMessage]`. React [puts your updater function in a queue](/learn/queueing-a-series-of-state-updates) and will provide the `msgs` argument to it during the next render. This is why the Effect itself doesn't need to depend on `messages` anymore. As a result of this fix, receiving a chat message will no longer make the chat re-connect.
+**注意你的 Effect 现在完全不会读取 `messages` 变量**。你只需要传递一个像 `msgs => [...msgs, receivedMessage]` 这样的更新函数。React [把你的更新函数放置在一个队列中](/learn/queueing-a-series-of-state-updates) 且在下一次渲染中提供 `msgs` 参数执行。这就是为什么 Effect 本身不再需要依赖 `messages` 的原因。这个修复的结果就是收到聊天消息将不会在使得聊天重新连接。
-### Do you want to read a value without "reacting" to its changes? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/}
+### 你想要只读取值而不对它的变化“做出响应”吗? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/}
-This section describes an **experimental API that has not yet been released** in a stable version of React.
+本章节描述了一个在 React 稳定版本中 **还没有发布的试验性 API**:
-Suppose that you want to play a sound when the user receives a new message unless `isMuted` is `true`:
+假设你想要 `isMuted` 不是 `true` 的时候在用户收到一个新消息的时候播放声音:
```js {3,10-12}
function ChatRoom({ roomId }) {
@@ -634,7 +634,7 @@ function ChatRoom({ roomId }) {
// ...
```
-Since your Effect now uses `isMuted` in its code, you have to add it to the dependencies:
+由于 Effect 现在在代码里使用了 `isMuted` ,所以必须把它加到依赖项中:
```js {10,15}
function ChatRoom({ roomId }) {
@@ -651,13 +651,13 @@ function ChatRoom({ roomId }) {
}
});
return () => connection.disconnect();
- }, [roomId, isMuted]); // ✅ All dependencies declared
+ }, [roomId, isMuted]); // ✅ 声明的所有依赖
// ...
```
-The problem is that every time `isMuted` changes (for example, when the user presses the "Muted" toggle), the Effect will re-synchronize, and reconnect to the chat. This is not the desired user experience! (In this example, even disabling the linter would not work--if you do that, `isMuted` would get "stuck" with its old value.)
+问题是每次 `isMuted` 变化时(例如用户按下“静音”按钮),Effect 会重新同步,并且聊天会重新连接。这不是预期的用户体验!(在这个示例中,即使禁用了代码检查也不会生效--如果你这么做,`isMuted` 会卡在旧值)。
-To solve this problem, you need to extract the logic that shouldn't be reactive out of the Effect. You don't want this Effect to "react" to the changes in `isMuted`. [Move this non-reactive piece of logic into an Effect Event:](/learn/separating-events-from-effects#declaring-an-effect-event)
+为了解决这个问题,你需要从 Effect 中提取出不应该是响应式的逻辑。你不希望这个 Effect 对 `isMuted` 的变化“做出响应”。[将这段非响应式代码移入一个Effect Event 中](/learn/separating-events-from-effects#declaring-an-effect-event):
```js {1,7-12,18,21}
import { useState, useEffect, useEffectEvent } from 'react';
@@ -680,15 +680,15 @@ function ChatRoom({ roomId }) {
onMessage(receivedMessage);
});
return () => connection.disconnect();
- }, [roomId]); // ✅ All dependencies declared
+ }, [roomId]); // ✅ 声明的所有依赖
// ...
```
-Effect Events let you split an Effect into reactive parts (which should "react" to reactive values like `roomId` and their changes) and non-reactive parts (which only read their latest values, like `onMessage` reads `isMuted`). **Now that you read `isMuted` inside an Effect Event, it doesn't need to be a dependency of your Effect.** As a result, the chat won't re-connect when you toggle the "Muted" setting on and off, solving the original issue!
+Effect Event 让你将一个 Effect 拆分成响应式部分(这部分应对像 `roomId` 这样的响应式值值以及他们的变化“做出响应”)和非响应式部分(这部分只读取它们的最新值,比如 `onMessage` 读取 `isMuted`)。**既然你在 Effect Event 内部读取了 `isMuted`,就不需要将它作为 Effect 的依赖项之一了**。最终结果是当你切换“静音”状态的开关时,聊天不会重新连接,解决了初始的问题!
-#### Wrapping an event handler from the props {/*wrapping-an-event-handler-from-the-props*/}
+#### 封装一个来自 props 的事件处理函数 {/*wrapping-an-event-handler-from-the-props*/}
-You might run into a similar problem when your component receives an event handler as a prop:
+当组件收到一个作为 prop 的事件处理函数时,你可能会遇到一个类似的问题:
```js {1,8,11}
function ChatRoom({ roomId, onReceiveMessage }) {
@@ -701,11 +701,11 @@ function ChatRoom({ roomId, onReceiveMessage }) {
onReceiveMessage(receivedMessage);
});
return () => connection.disconnect();
- }, [roomId, onReceiveMessage]); // ✅ All dependencies declared
+ }, [roomId, onReceiveMessage]); // ✅ 声明的所有依赖
// ...
```
-Suppose that the parent component passes a *different* `onReceiveMessage` function on every render:
+假设父组件在每次渲染时都传递了一个 **不同的** `onReceiveMessage` 函数:
```js {3-5}
```
-Since `onReceiveMessage` is a dependency, it would cause the Effect to re-synchronize after every parent re-render. This would make it re-connect to the chat. To solve this, wrap the call in an Effect Event:
+由于 `onReceiveMessage` 是一个依赖项,所以它会在每次父组件重新渲染后引发 Effect 重新同步。这会让聊天要重新连接。为了解决这个问题,需要将其调用封装在一个 Effect Event 中:
```js {4-6,12,15}
function ChatRoom({ roomId, onReceiveMessage }) {
@@ -733,17 +733,17 @@ function ChatRoom({ roomId, onReceiveMessage }) {
onMessage(receivedMessage);
});
return () => connection.disconnect();
- }, [roomId]); // ✅ All dependencies declared
+ }, [roomId]); // ✅ 声明的所有依赖
// ...
```
-Effect Events aren't reactive, so you don't need to specify them as dependencies. As a result, the chat will no longer re-connect even if the parent component passes a function that's different on every re-render.
+Effect Event 是非响应式的,所以你不需要将它们指定为依赖项。最终,即使父组件传在每次重新渲染中都传递了一个函数,聊天也不会再重新连接。
-#### Separating reactive and non-reactive code {/*separating-reactive-and-non-reactive-code*/}
+#### 拆分响应式代码和非响应式代码 {/*separating-reactive-and-non-reactive-code*/}
-In this example, you want to log a visit every time `roomId` changes. You want to include the current `notificationCount` with every log, but you *don't* want a change to `notificationCount` to trigger a log event.
+在这个示例中,你需要在每次 `roomId` 变化时记录一次访问。且需要在每个记录中包含当前的 `notificationCount`,但是你 **不** 希望 `notificationCount` 的变化触发 log 事件。
-The solution is again to split out the non-reactive code into an Effect Event:
+解决方案就是再将非响应式代码分割到一个 Effect Event 中:
```js {2-4,7}
function Chat({ roomId, notificationCount }) {
@@ -753,16 +753,16 @@ function Chat({ roomId, notificationCount }) {
useEffect(() => {
onVisit(roomId);
- }, [roomId]); // ✅ All dependencies declared
+ }, [roomId]); // ✅ 声明的所有依赖
// ...
}
```
-You want your logic to be reactive with regards to `roomId`, so you read `roomId` inside of your Effect. However, you don't want a change to `notificationCount` to log an extra visit, so you read `notificationCount` inside of the Effect Event. [Learn more about reading the latest props and state from Effects using Effect Events.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)
+你希望关于 `roomId` 的逻辑是响应式的,所以你在 Effect 内部读取 `roomId` 值。但是你不想因为 `notificationCount` 的变化而记录一次额外的访问,所以你在 Effect Event 内部读取 `notificationCount`。[了解更多如何通过 Effect Event 从 Effect 中读取最新的 props 和 state 值](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)。
-### Does some reactive value change unintentionally? {/*does-some-reactive-value-change-unintentionally*/}
+### 会有一些响应式值无意中变化吗? {/*does-some-reactive-value-change-unintentionally*/}
-Sometimes, you *do* want your Effect to "react" to a certain value, but that value changes more often than you'd like--and might not reflect any actual change from the user's perspective. For example, let's say that you create an `options` object in the body of your component, and then read that object from inside of your Effect:
+有时候你 **确实** 希望 Effect 对某个值“做出响应”,但是那个值比预期的变化频率要高--并且从用户角度来说并没有实际变化。举个例子,假设你在组件内创建一个 `options` 对象,然后从 Effect 内部读取这个对象:
```js {3-6,9}
function ChatRoom({ roomId }) {
@@ -778,7 +778,7 @@ function ChatRoom({ roomId }) {
// ...
```
-This object is declared in the component body, so it's a [reactive value.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) When you read a reactive value like this inside an Effect, you declare it as a dependency. This ensures your Effect "reacts" to its changes:
+这个对象是在组件内部声明的,所以它是一个[响应式值](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values)。当你在 Effect 内部读取像这样的响应式值时,需要将它声明为依赖项之一。这保证了 Effect 一定会对它的变化“做出响应”:
```js {3,6}
// ...
@@ -786,11 +786,11 @@ This object is declared in the component body, so it's a [reactive value.](/lear
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
- }, [options]); // ✅ All dependencies declared
+ }, [options]); // ✅ 声明的所有依赖
// ...
```
-It is important to declare it as a dependency! This ensures, for example, that if the `roomId` changes, your Effect will re-connect to the chat with the new `options`. However, there is also a problem with the code above. To see it, try typing into the input in the sandbox below, and watch what happens in the console:
+将它声明为依赖非常重要!例如,这保证了如果 `roomId` 变化,Effect 会使用新的 `options` 重新连接聊天。但是上面的代码也存在一个问题。为了找到它,尝试在下面的输入框输入并且查看 console 处发生了什么:
@@ -803,7 +803,7 @@ const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
- // Temporarily disable the linter to demonstrate the problem
+ // 临时禁用代码检查演示问题
// eslint-disable-next-line react-hooks/exhaustive-deps
const options = {
serverUrl: serverUrl,
@@ -848,7 +848,7 @@ export default function App() {
```js chat.js
export function createConnection({ serverUrl, roomId }) {
- // A real implementation would actually connect to the server
+ // 现实的实现会真的连接到一个服务器
return {
connect() {
console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
@@ -867,30 +867,30 @@ button { margin-left: 10px; }
-In the sandbox above, the input only updates the `message` state variable. From the user's perspective, this should not affect the chat connection. However, every time you update the `message`, your component re-renders. When your component re-renders, the code inside of it runs again from scratch.
+上面的沙盒输入框只更新了 state 变量 `message`。从用户角度来看,这不应该影响到聊天连接。但是每当你更新 `message`,组件就会重新渲染。而当组件重新渲染时,内部的代码又会重新开始。
-A new `options` object is created from scratch on every re-render of the `ChatRoom` component. React sees that the `options` object is a *different object* from the `options` object created during the last render. This is why it re-synchronizes your Effect (which depends on `options`), and the chat re-connects as you type.
+每次重新渲染 `ChatRoom` 组件都会创建一个新的 `options` 对象。React 认为本次渲染期间创建的 `options` 和上一次渲染期间创建的 `options` 是 **不一样的**。这就是为什么你的 Effect(依赖于 `options`)重新渲染,并且当你输入的时候聊天会重新连接。
-**This problem only affects objects and functions. In JavaScript, each newly created object and function is considered distinct from all the others. It doesn't matter that the contents inside of them may be the same!**
+**这个问题只影响对象和函数。在 JavaScript 中,每一个新创建的对象和函数都被认为是和其他的对象和函数不一样。内部的内容是否相同并不会影响这一结果!**
```js {7-8}
-// During the first render
+// 第一次渲染期间
const options1 = { serverUrl: 'https://localhost:1234', roomId: 'music' };
-// During the next render
+// 第二次渲染期间
const options2 = { serverUrl: 'https://localhost:1234', roomId: 'music' };
-// These are two different objects!
+// 这是两个不同的对象!
console.log(Object.is(options1, options2)); // false
```
-**Object and function dependencies can make your Effect re-synchronize more often than you need.**
+**对象和函数依赖让 Effect 的重新同步频率高于你的需求**。
-This is why, whenever possible, you should try to avoid objects and functions as your Effect's dependencies. Instead, try moving them outside the component, inside the Effect, or extracting primitive values out of them.
+这就是为什么你应该尽可能避免将对象和函数作为 Effect 的依赖项。而是应该尝试将它们移动到组件外部,移入 Effect 内部或者从中提取初始值。
-#### Move static objects and functions outside your component {/*move-static-objects-and-functions-outside-your-component*/}
+#### 从组件中移出静态对象和函数 {/*move-static-objects-and-functions-outside-your-component*/}
-If the object does not depend on any props and state, you can move that object outside your component:
+如果这个对象不依赖于任何 props 和 state,你就可以将它们从组件中移出去:
```js {1-4,13}
const options = {
@@ -905,13 +905,13 @@ function ChatRoom() {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
- }, []); // ✅ All dependencies declared
+ }, []); // ✅ 声明的所有依赖
// ...
```
-This way, you *prove* to the linter that it's not reactive. It can't change as a result of a re-render, so it doesn't need to be a dependency. Now re-rendering `ChatRoom` won't cause your Effect to re-synchronize.
+这样,你就可以向代码检查工具“证明”它不是响应式的。它不会因为重新渲染而变化,所以它不需要成为依赖项之一。现在重新渲染 `ChatRoom` 组件将不会让 Effect 重新同步。
-This works for functions too:
+这对函数也有用:
```js {1-6,12}
function createOptions() {
@@ -929,15 +929,15 @@ function ChatRoom() {
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
- }, []); // ✅ All dependencies declared
+ }, []); // ✅ 声明的所有依赖
// ...
```
-Since `createOptions` is declared outside your component, it's not a reactive value. This is why it doesn't need to be specified in your Effect's dependencies, and why it won't ever cause your Effect to re-synchronize.
+因为 `createOptions` 是在组件外部声明的,所以它不是响应式值。这就是为什么它不需要被指定为 Effect 的依赖项,并且不会引起 Effect 重新同步。
-#### Move dynamic objects and functions inside your Effect {/*move-dynamic-objects-and-functions-inside-your-effect*/}
+#### 将动态对象和函数移入 Effect {/*move-dynamic-objects-and-functions-inside-your-effect*/}
-If your object depends on some reactive value that may change as a result of a re-render, like a `roomId` prop, you can't pull it *outside* your component. You can, however, move its creation *inside* of your Effect's code:
+如果你的对象依赖于某些像 `roomId` prop 这样会因为重新渲染而变化的响应式值,你就不能将它移动到组件 **外部**。但是你可以将它的创建移动到 Effect 代码的内部:
```js {7-10,11,14}
const serverUrl = 'https://localhost:1234';
@@ -953,24 +953,24 @@ function ChatRoom({ roomId }) {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
- }, [roomId]); // ✅ All dependencies declared
+ }, [roomId]); // ✅ 声明的所有依赖
// ...
```
-Now that `options` is declared inside of your Effect, it is no longer a dependency of your Effect. Instead, the only reactive value used by your Effect is `roomId`. Since `roomId` is not an object or function, you can be sure that it won't be *unintentionally* different. In JavaScript, numbers and strings are compared by their content:
+既然 `options` 是在 Effect 内部声明的,它就不再是依赖项了。而 Effect 唯一使用的响应式值是 `roomId`。因为 `roomId` 不是对象或者函数,你可以确保它不会有 **不符合预期的** 不同。在 JavaScript 中,number 和 string 是通过内容进行比较的:
```js {7-8}
-// During the first render
+// 第一次渲染期间
const roomId1 = 'music';
-// During the next render
+// 第二次渲染期间
const roomId2 = 'music';
-// These two strings are the same!
+// 这两个字符串是一样的!
console.log(Object.is(roomId1, roomId2)); // true
```
-Thanks to this fix, the chat no longer re-connects if you edit the input:
+由于这个修复,如果你再修改输入值,聊天不会再重新连接了:
@@ -1025,7 +1025,7 @@ export default function App() {
```js chat.js
export function createConnection({ serverUrl, roomId }) {
- // A real implementation would actually connect to the server
+ // 真正的实现会真的连接服务器
return {
connect() {
console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
@@ -1044,9 +1044,9 @@ button { margin-left: 10px; }
-However, it *does* re-connect when you change the `roomId` dropdown, as you would expect.
+但是当你修改 `roomId` 时,**还是** 会和预期的一样重新连接。
-This works for functions, too:
+这对函数也有效:
```js {7-12,14}
const serverUrl = 'https://localhost:1234';
@@ -1066,15 +1066,15 @@ function ChatRoom({ roomId }) {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
- }, [roomId]); // ✅ All dependencies declared
+ }, [roomId]); // ✅ 声明的所有依赖
// ...
```
-You can write your own functions to group pieces of logic inside your Effect. As long as you also declare them *inside* your Effect, they're not reactive values, and so they don't need to be dependencies of your Effect.
+你可以写自己的函数来对 Effect 内部的逻辑进行分组。只要你还在 Effect **内部** 声明了它们,就不是响应式值,所以也不需要成为 Effect 的依赖项。
-#### Read primitive values from objects {/*read-primitive-values-from-objects*/}
+#### 从对象中读取基本值 {/*read-primitive-values-from-objects*/}
-Sometimes, you may receive an object from props:
+有时候你可能会从 props 中接收到一个对象:
```js {1,5,8}
function ChatRoom({ options }) {
@@ -1084,11 +1084,11 @@ function ChatRoom({ options }) {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
- }, [options]); // ✅ All dependencies declared
+ }, [options]); // ✅ 声明的所有依赖
// ...
```
-The risk here is that the parent component will create the object during rendering:
+这里的风险在于父组件会在渲染期间创建这个对象:
```js {3-6}
```
-This would cause your Effect to re-connect every time the parent component re-renders. To fix this, read information from the object *outside* the Effect, and avoid having object and function dependencies:
+这会导致 Effect 会在每次父组件重新渲染时重新连接。为了修复这个问题,从 Effect **外部** 的对象读取信息,并且避免拥有对象和函数依赖:
```js {4,7-8,12}
function ChatRoom({ options }) {
@@ -1114,15 +1114,15 @@ function ChatRoom({ options }) {
});
connection.connect();
return () => connection.disconnect();
- }, [roomId, serverUrl]); // ✅ All dependencies declared
+ }, [roomId, serverUrl]); // ✅ 声明的所有依赖
// ...
```
-The logic gets a little repetitive (you read some values from an object outside an Effect, and then create an object with the same values inside the Effect). But it makes it very explicit what information your Effect *actually* depends on. If an object is re-created unintentionally by the parent component, the chat would not re-connect. However, if `options.roomId` or `options.serverUrl` really are different, the chat would re-connect.
+这段逻辑有一点重复了(你从 Effect 外部读取了某些值,然后在内部又创建了一个有同样值的对象)。但是它会明确 Effect **实际** 依赖的是什么。如果父组件意外地重新创建了一个对象,聊天也不会重新连接。但是如果 `options.roomId` 或者 `options.serverUrl` 真的变化了,聊天就会重新连接。
-#### Calculate primitive values from functions {/*calculate-primitive-values-from-functions*/}
+#### 通过函数计算基本值 {/*calculate-primitive-values-from-functions*/}
-The same approach can work for functions. For example, suppose the parent component passes a function:
+同样的方法对函数也有效。例如假设父组件传递了一个函数:
```js {3-8}
```
-To avoid making it a dependency (and causing it to re-connect on re-renders), call it outside the Effect. This gives you the `roomId` and `serverUrl` values that aren't objects, and that you can read from inside your Effect:
+为了避免让它成为依赖项(会导致聊天在重新渲染中重新连接),而是在 Effect 外部调用。这会给你一个非对象的 `roomId` 和 `serverUrl` 值,并且你可以从 Effect 内部读取这个值:
```js {1,4}
function ChatRoom({ getOptions }) {
@@ -1150,36 +1150,36 @@ function ChatRoom({ getOptions }) {
});
connection.connect();
return () => connection.disconnect();
- }, [roomId, serverUrl]); // ✅ All dependencies declared
+ }, [roomId, serverUrl]); // ✅ 声明的所有依赖
// ...
```
-This only works for [pure](/learn/keeping-components-pure) functions because they are safe to call during rendering. If your function is an event handler, but you don't want its changes to re-synchronize your Effect, [wrap it into an Effect Event instead.](#do-you-want-to-read-a-value-without-reacting-to-its-changes)
+这只对 [纯](/learn/keeping-components-pure) 函数有效,因为他们在渲染期间调用是安全的。如果你的函数是一个事件处理函数,但是你不想它的变化重新同步 Effect,[那就把它封装进一个 Effect Event](#do-you-want-to-read-a-value-without-reacting-to-its-changes)。
-- Dependencies should always match the code.
-- When you're not happy with your dependencies, what you need to edit is the code.
-- Suppressing the linter leads to very confusing bugs, and you should always avoid it.
-- To remove a dependency, you need to "prove" to the linter that it's not necessary.
-- If some code should run in response to a specific interaction, move that code to an event handler.
-- If different parts of your Effect should re-run for different reasons, split it into several Effects.
-- If you want to update some state based on the previous state, pass an updater function.
-- If you want to read the latest value without "reacting" it, extract an Effect Event from your Effect.
-- In JavaScript, objects and functions are considered different if they were created at different times.
-- Try to avoid object and function dependencies. Move them outside the component or inside the Effect.
+- 依赖应该永远和代码匹配。
+- 当你不满意依赖项时,你需要做的就是修改代码。
+- 抑制代码检查工具会引起令人疑惑的 bug,你应该永远避免这种行为。
+- 为了移除依赖项,你需要向代码检查工具“证明”它不是必要的。
+- 如果一些代码应该只在特定交互的时候运行,那就将这段代码移动到事件处理函数。
+- 如果你的 Effect 中部分代码需要因为不同的原因重新运行,那你需要将它分割成若干个 Effect。
+- 如果你想要更新一些基于之前 state 值的state,那就传递一个更新函数。
+- 如果你想要读取最新的值而不用对它“做出响应”,那就从你的 Effect 中提取出一个 Effect Event 。
+- 在 JavaScript 中,对象和函数如果是在不同时间创建的就会被认为是不一样的。
+- 尝试避免对象和函数依赖。把它们移动到组件外部或者 Effect 内部。
-#### Fix a resetting interval {/*fix-a-resetting-interval*/}
+#### 修复重置时间间隔 {/*fix-a-resetting-interval*/}
-This Effect sets up an interval that ticks every second. You've noticed something strange happening: it seems like the interval gets destroyed and re-created every time it ticks. Fix the code so that the interval doesn't get constantly re-created.
+这个 Effect 设置了一个每秒 tick 一次的时间间隔。你已经注意到发生了一些奇怪的现象:每次 tick 的时候看上去像 interval 被销毁又被重新创建。修复这段代码,这样不会一直重新创建 interval。
-It seems like this Effect's code depends on `count`. Is there some way to not need this dependency? There should be a way to update the `count` state based on its previous value without adding a dependency on that value.
+这个 Effect 的代码似乎依赖于 `count`。有什么方法可以不需要这个依赖吗?应该有一个方法可以基于之前的值更新 `count` state,而不需要将这个值添加为依赖项。
@@ -1211,9 +1211,9 @@ export default function Timer() {
-You want to update the `count` state to be `count + 1` from inside the Effect. However, this makes your Effect depend on `count`, which changes with every tick, and that's why your interval gets re-created on every tick.
+你想要在 Effect 内部将 `count` state 更新为 `count + 1` 。但是这会让你的 Effect 依赖于 `count`,它每次 tick 的时候都会变化,这也是为什么每一个 tick 你的 interval 都会被重新创建的原因。
-To solve this, use the [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) and write `setCount(c => c + 1)` instead of `setCount(count + 1)`:
+为了解决这个问题,我们使用 [更新函数](/reference/react/useState#updating-state-based-on-the-previous-state) 并且编写时使用 `setCount(count + 1)` 代替 `setCount(c => c + 1)`:
@@ -1241,19 +1241,19 @@ export default function Timer() {
-Instead of reading `count` inside the Effect, you pass a `c => c + 1` instruction ("increment this number!") to React. React will apply it on the next render. And since you don't need to read the value of `count` inside your Effect anymore, so you can keep your Effect's dependencies empty (`[]`). This prevents your Effect from re-creating the interval on every tick.
+你传递了一个 `c => c + 1` 指令(“增加这个数字!”)给 React,而不是在 Effect 内部读取 `count`。React 将在下一次渲染中使用。并且因为你不再需要在 Effect 内部读取 `count` 的值,所以可以保持 Effect 依赖为空 (`[]`)。这会阻止 Effect 在每一次的 tick 中重新创建 interval。
-#### Fix a retriggering animation {/*fix-a-retriggering-animation*/}
+#### 修复一个重新触发动画 {/*fix-a-retriggering-animation*/}
-In this example, when you press "Show", a welcome message fades in. The animation takes a second. When you press "Remove", the welcome message immediately disappears. The logic for the fade-in animation is implemented in the `animation.js` file as plain JavaScript [animation loop.](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) You don't need to change that logic. You can treat it as a third-party library. Your Effect creates an instance of `FadeInAnimation` for the DOM node, and then calls `start(duration)` or `stop()` to control the animation. The `duration` is controlled by a slider. Adjust the slider and see how the animation changes.
+在这个示例中,当你点击“Show”,一个欢迎信息会淡入式出现。这个动画需要 1 秒钟。当你点击“Remove”,欢迎信息会立刻消失。渐入动画的逻辑在 `animation.js` 文件中以普通的 JavaScript [动画循环](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) 实现。你不需要修改这段逻辑。可以将它看作是第三方库。Effect 为 DOM 节点创建了 `FadeInAnimation` 实例,然后调用 `start(duration)` 或者 `stop()` 来控制动画。`duration` 由一个滑块控制。调整滑块看动画如何变化。
-This code already works, but there is something you want to change. Currently, when you move the slider that controls the `duration` state variable, it retriggers the animation. Change the behavior so that the Effect does not "react" to the `duration` variable. When you press "Show", the Effect should use the current `duration` on the slider. However, moving the slider itself should not by itself retrigger the animation.
+这段代码已经达到目的了,但是你需要修改一些东西。当你现在移动 state 变量 `duration` 控制滑块时,它会重新触发动画。修改这个行为,让 Effect 不会对 `duration` 变量的变化“做出响应”。当你按“Show”, Effect 应该使用滑块当前的 `duration`。但是单独移动滑块本身不应该重新触发动画。
-Is there a line of code inside the Effect that should not be reactive? How can you move non-reactive code out of the Effect?
+这个 Effect 有代码不应该是响应式的吗?如何从 Effect 中移出非响应式代码呢?
@@ -1343,11 +1343,11 @@ export class FadeInAnimation {
start(duration) {
this.duration = duration;
if (this.duration === 0) {
- // Jump to end immediately
+ // 立刻跳转到结束
this.onProgress(1);
} else {
this.onProgress(0);
- // Start animating
+ // 开始动画
this.startTime = performance.now();
this.frameId = requestAnimationFrame(() => this.onFrame());
}
@@ -1357,7 +1357,7 @@ export class FadeInAnimation {
const progress = Math.min(timePassed / this.duration, 1);
this.onProgress(progress);
if (progress < 1) {
- // We still have more frames to paint
+ // 我们仍然有更多的帧需要绘制
this.frameId = requestAnimationFrame(() => this.onFrame());
}
}
@@ -1382,7 +1382,7 @@ html, body { min-height: 300px; }
-Your Effect needs to read the latest value of `duration`, but you don't want it to "react" to changes in `duration`. You use `duration` to start the animation, but starting animation isn't reactive. Extract the non-reactive line of code into an Effect Event, and call that function from your Effect.
+你的 Effect 需要读取 `duration` 的最新值,但你不想要对 `duration` 的变化做出响应。你用 `duration` 启动动画,但是启动动画不是响应式的。所以需要提取非响应式代码到 Effect Event,并且在你的 Effect 中调用这个函数。
@@ -1482,7 +1482,7 @@ export class FadeInAnimation {
const progress = Math.min(timePassed / this.duration, 1);
this.onProgress(progress);
if (progress < 1) {
- // We still have more frames to paint
+ //我们仍还有更多的帧需要绘制
this.frameId = requestAnimationFrame(() => this.onFrame());
}
}
@@ -1505,19 +1505,19 @@ html, body { min-height: 300px; }
-Effect Events like `onAppear` are not reactive, so you can read `duration` inside without retriggering the animation.
+像 `onAppear` 这样的 Effect Event 是非响应式的,所以你可以在不重新触发动画的情况下读取到内部的 `duration`。
-#### Fix a reconnecting chat {/*fix-a-reconnecting-chat*/}
+#### 修复一个聊天重新连接问题 {/*fix-a-reconnecting-chat*/}
-In this example, every time you press "Toggle theme", the chat re-connects. Why does this happen? Fix the mistake so that the chat re-connects only when you edit the Server URL or choose a different chat room.
+在这个示例中,每次你按压 “Toggle theme”,聊天就会重新连接。为什么会这样呢?修复这个错误,让它只在你修改 Server URL 或选择不同聊天室的时候重新连接。
-Treat `chat.js` as an external third-party library: you can consult it to check its API, but don't edit it.
+将 `chat.js` 看成是一个外部的第三方库:你可以查询它的 API,但不可以修改。
-There's more than one way to fix this, but ultimately you want to avoid having an object as your dependency.
+有不止一个方法修复这个问题,但是最终你需要避免将一个对象作为依赖项。
@@ -1584,7 +1584,7 @@ export default function ChatRoom({ options }) {
```js chat.js
export function createConnection({ serverUrl, roomId }) {
- // A real implementation would actually connect to the server
+ // 真正的实现会实际连接到服务器
if (typeof serverUrl !== 'string') {
throw Error('Expected serverUrl to be a string. Received: ' + serverUrl);
}
@@ -1611,9 +1611,9 @@ label, button { display: block; margin-bottom: 5px; }
-Your Effect is re-running because it depends on the `options` object. Objects can be re-created unintentionally, you should try to avoid them as dependencies of your Effects whenever possible.
+你的 Effect 由于依赖 `options` 对象所以正在重新运行。对象可以意外被创建,你应该尽可能避免用它们作为 Effect 的依赖项。
-The least invasive fix is to read `roomId` and `serverUrl` right outside the Effect, and then make the Effect depend on those primitive values (which can't change unintentionally). Inside the Effect, create an object and it pass to `createConnection`:
+侵入性最小的修复方案是在 Effect 外部读取 `roomId` 和 `serverUrl`,然后使得这个 Effect 依赖于这些基本值(不会意外被修改)。在 Effect 内部创建一个对象并传递给 `createConnection`:
@@ -1682,7 +1682,7 @@ export default function ChatRoom({ options }) {
```js chat.js
export function createConnection({ serverUrl, roomId }) {
- // A real implementation would actually connect to the server
+ // 真正的实现会实际连接到服务器
if (typeof serverUrl !== 'string') {
throw Error('Expected serverUrl to be a string. Received: ' + serverUrl);
}
@@ -1707,7 +1707,7 @@ label, button { display: block; margin-bottom: 5px; }
-It would be even better to replace the object `options` prop with the more specific `roomId` and `serverUrl` props:
+更好的方法是使用更多指定的 `roomId` 和 `serverUrl` props 来取代对象类型的 `options` prop:
@@ -1773,7 +1773,7 @@ export default function ChatRoom({ roomId, serverUrl }) {
```js chat.js
export function createConnection({ serverUrl, roomId }) {
- // A real implementation would actually connect to the server
+ // 真正的实现会实际连接到服务器
if (typeof serverUrl !== 'string') {
throw Error('Expected serverUrl to be a string. Received: ' + serverUrl);
}
@@ -1798,25 +1798,25 @@ label, button { display: block; margin-bottom: 5px; }
-Sticking to primitive props where possible makes it easier to optimize your components later.
+尽可能坚持使用基本类型的 props 会让之后的组件优化更加容易。
-#### Fix a reconnecting chat, again {/*fix-a-reconnecting-chat-again*/}
+#### 再次修复一个聊天重新连接问题 {/*fix-a-reconnecting-chat-again*/}
-This example connects to the chat either with or without encryption. Toggle the checkbox and notice the different messages in the console when the encryption is on and off. Try changing the room. Then, try toggling the theme. When you're connected to a chat room, you will receive new messages every few seconds. Verify that their color matches the theme you've picked.
+这个示例使用加密或非加密形式连接到聊天室。切换复选框并且注意当加密为 on 和 off 时 console 中的不同信息。尝试修改聊天室。然后切换主题。当你连接到一个聊天室,你将每隔几秒就会收到一条新的信息。验证他们的颜色是否和你选择的主题匹配。
-In this example, the chat re-connects every time you try to change the theme. Fix this. After the fix, changing the theme should not re-connect the chat, but toggling encryption settings or changing the room should re-connect.
+在这个示例中,每当你试图修改主题,聊天就会重新连接。修复这个问题。修复结束之后,主题变化应该不会使得聊天重新连接,但是切换加密设置或者变更聊天室应该使聊天重新连接。
-Don't change any code in `chat.js`. Other than that, you can change any code as long as it results in the same behavior. For example, you may find it helpful to change which props are being passed down.
+不要修改 `chat.js` 中的任何代码。除此之外,你可以修改任何会导致同样行为的代码。例如,你可能发现修改正在传递的 props 会有帮助。
-You're passing down two functions: `onMessage` and `createConnection`. Both of them are created from scratch every time `App` re-renders. They are considered to be new values every time, which is why they re-trigger your Effect.
+你正在传递两个函数: `onMessage` 和 `createConnection`。这两个函数都是每次 `App` 重新渲染的时候重新创建的。它们每次都被认为是新的值,这就是会重新触发 Effect 的原因。
-One of these functions is an event handler. Do you know some way to call an event handler an Effect without "reacting" to the new values of the event handler function? That would come in handy!
+这些函数之一是事件处理函数。你知道哪些方法可以在 Effect 中调用事件处理函数而不对事件处理函数的新值“做出响应”吗?这会派上用场!
-Another of these functions only exists to pass some state to an imported API method. Is this function really necessary? What is the essential information that's being passed down? You might need to move some imports from `App.js` to `ChatRoom.js`.
+其中另一个函数仅用于将某些 state 传递给导入的 API 方法。这个函数真的有必要吗?正在传递的基本信息是什么?你可能需要将某些导入从 `App.js` 移动到 `ChatRoom.js`。
@@ -2031,11 +2031,11 @@ label, button { display: block; margin-bottom: 5px; }
-There's more than one correct way to solve this, but here is one possible solution.
+解决这个问题的正确方法不止这一种,但是这里只给出了一个可能的解决方案。
-In the original example, toggling the theme caused different `onMessage` and `createConnection` functions to be created and passed down. Since the Effect depended on these functions, the chat would re-connect every time you toggle the theme.
+在原来的代码中,切换主题会导致重新创建和传递不同的 `onMessage` 和 `createConnection` 函数。因为 Effect 依赖于这些函数,所以每次切换主题,聊天都会重新连接。
-To fix the problem with `onMessage`, you needed to wrap it into an Effect Event:
+为了修复 `onMessage` 这个问题,你需要将其封装进一个 Effect Event:
```js {1,2,6}
export default function ChatRoom({ roomId, createConnection, onMessage }) {
@@ -2047,9 +2047,9 @@ export default function ChatRoom({ roomId, createConnection, onMessage }) {
// ...
```
-Unlike the `onMessage` prop, the `onReceiveMessage` Effect Event is not reactive. This is why it doesn't need to be a dependency of your Effect. As a result, changes to `onMessage` won't cause the chat to re-connect.
+和 `onMessage` prop 不一样,`onReceiveMessage` Effect Event 不是响应式的。这就是为什么它不需要成为 Effect 的依赖项。最终结果是 `onMessage` 的变化不会引起聊天重新连接。
-You can't do the same with `createConnection` because it *should* be reactive. You *want* the Effect to re-trigger if the user switches between an encrypted and an unencryption connection, or if the user switches the current room. However, because `createConnection` is a function, you can't check whether the information it reads has *actually* changed or not. To solve this, instead of passing `createConnection` down from the `App` component, pass the raw `roomId` and `isEncrypted` values:
+你不可以对 `createConnection` 做同样的事情,因为它 **应该是** 响应式的。如果用户切换加密和非加密连接或者切换当前聊天室的时候,你 **想要** Effect 重新触发。但是因为 `createConnection` 是一个函数,所以你不能检测它读取的信息是否 **实际** 变化了。为了解决这个问题,你需要传原始的 `roomId` 和 `isEncrypted` 值,而不是从 `App` 组件传递 `createConnection`:
```js {2-3}
```
-Now you can move the `createConnection` function *inside* the Effect instead of passing it down from the `App`:
+现在你可以移动 `createConnection` 函数到 Effect **内部**,而不是将它从 `App` 中传递下去:
```js {1-4,6,10-20}
import {
@@ -2087,7 +2087,7 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) {
// ...
```
-After these two changes, your Effect no longer depends on any function values:
+经过这两个修改后,你的 Effect 不再依赖任何函数值:
```js {1,8,10,21}
export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reactive values
@@ -2113,7 +2113,7 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reacti
}, [roomId, isEncrypted]); // ✅ All dependencies declared
```
-As a result, the chat re-connects only when something meaningful (`roomId` or `isEncrypted`) changes:
+最终,只有在某些有意义的值(`roomId` 或 `isEncrypted`)变化时,聊天才会重新连接:
@@ -2223,7 +2223,7 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) {
```js chat.js
export function createEncryptedConnection({ serverUrl, roomId }) {
- // A real implementation would actually connect to the server
+ // 真正的实现会实际连接到服务器
if (typeof serverUrl !== 'string') {
throw Error('Expected serverUrl to be a string. Received: ' + serverUrl);
}