From 91756861e14c235bccba434d461ced3f8ac774e5 Mon Sep 17 00:00:00 2001 From: yanyue Date: Wed, 10 May 2023 17:52:15 +0800 Subject: [PATCH 01/16] docs(cn): start to translate 'Separating Events from Effects' --- .../learn/separating-events-from-effects.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index e932e8afd0..d00af2b73a 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -1,37 +1,37 @@ --- -title: 'Separating Events from Effects' +title: 将事件从 Effect 中分开 --- -Event handlers only re-run when you perform the same interaction again. Unlike event handlers, Effects re-synchronize if some value they read, like a prop or a state variable, is different from what it was during the last render. Sometimes, you also want a mix of both behaviors: an Effect that re-runs in response to some values but not others. This page will teach you how to do that. +事件处理函数只有在你再次执行同样的交互时才会重新运行。Effect 和事件处理函数不一样,它只有在读取的 prop 或 state 值和上一次渲染不一样时才会重新同步。有时你需要这两种行为的混合体:即一个 Effect 只在响应某些值时重新运行,但是在其他值变化时不重新运行。本章将会教你怎么实现这一点。 -- How to choose between an event handler and an Effect -- Why Effects are reactive, and event handlers are not -- What to do when you want a part of your Effect's code to not be reactive -- What Effect Events are, and how to extract them from your Effects -- How to read the latest props and state from Effects using Effect Events +- 怎么在事件处理函数和 Effect 之间做选择 +- 为什么 Effects 是响应式的,而事件处理函数不是 +- 当你想要 Effect 的部分代码变成非响应式时要做些什么 +- Effect Event 是什么,以及怎么从 Effect 中提取 +- 怎么使用 Effect Event 读取最新的 props 和 state -## Choosing between event handlers and Effects {/*choosing-between-event-handlers-and-effects*/} +## 在事件处理函数和 Effect 中做选择 {/*choosing-between-event-handlers-and-effects*/} -First, let's recap the difference between event handlers and Effects. +首先让我们回顾一下事件处理函数和 Effect 的区别。 -Imagine you're implementing a chat room component. Your requirements look like this: +假设你正在实现一个聊天室组件,需求如下: -1. Your component should automatically connect to the selected chat room. -1. When you click the "Send" button, it should send a message to the chat. +1. 组件应该自动连接选中的聊天室。 +1. 每当你点击 “Send” 按钮,组件应该在当前聊天界面发送一条消息。 -Let's say you've already implemented the code for them, but you're not sure where to put it. Should you use event handlers or Effects? Every time you need to answer this question, consider [*why* the code needs to run.](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events) +假设你已经实现了这部分代码,但是还没有确定应该放在哪里。你是应该用事件处理函数还是 Effect 呢?每当你需要回答这个问题时,请考虑一下 [**为什么** 代码需要运行](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events)。 -### Event handlers run in response to specific interactions {/*event-handlers-run-in-response-to-specific-interactions*/} +### 事件处理函数只在响应特定的交互操作时运行 {/*event-handlers-run-in-response-to-specific-interactions*/} -From the user's perspective, sending a message should happen *because* the particular "Send" button was clicked. The user will get rather upset if you send their message at any other time or for any other reason. This is why sending a message should be an event handler. Event handlers let you handle specific interactions: +从用户角度出发,发送消息是 **因为** 他点击了特定的 “send” 按钮。如果在任意时间或者因为其他原因发送消息,用户会觉得非常混乱。这就是为什么发送消息应该使用事件处理函数。事件处理函数是让你处理特定的交互操作的: ```js {4-6} function ChatRoom({ roomId }) { @@ -50,10 +50,10 @@ function ChatRoom({ roomId }) { } ``` -With an event handler, you can be sure that `sendMessage(message)` will *only* run if the user presses the button. - -### Effects run whenever synchronization is needed {/*effects-run-whenever-synchronization-is-needed*/} +借助事件处理函数,你可以确保 `sendMessage(message)` **只** 在用户点击按钮的时候运行。 +### 每当需要同步,Effect 就会运行 {/*effects-run-whenever-synchronization-is-needed*/} +todo:暂停 Recall that you also need to keep the component connected to the chat room. Where does that code go? The *reason* to run this code is not some particular interaction. It doesn't matter why or how the user navigated to the chat room screen. Now that they're looking at it and could interact with it, the component needs to stay connected to the selected chat server. Even if the chat room component was the initial screen of your app, and the user has not performed any interactions at all, you would *still* need to connect. This is why it's an Effect: From ca4d0dd1fdb0667eaf35d3e703373f9ef2218a3c Mon Sep 17 00:00:00 2001 From: yanyue Date: Thu, 11 May 2023 17:32:44 +0800 Subject: [PATCH 02/16] docs(cn): translate separating-events-from-effects --- .../learn/separating-events-from-effects.md | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index d00af2b73a..f134cd177b 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -53,10 +53,10 @@ function ChatRoom({ roomId }) { 借助事件处理函数,你可以确保 `sendMessage(message)` **只** 在用户点击按钮的时候运行。 ### 每当需要同步,Effect 就会运行 {/*effects-run-whenever-synchronization-is-needed*/} -todo:暂停 -Recall that you also need to keep the component connected to the chat room. Where does that code go? -The *reason* to run this code is not some particular interaction. It doesn't matter why or how the user navigated to the chat room screen. Now that they're looking at it and could interact with it, the component needs to stay connected to the selected chat server. Even if the chat room component was the initial screen of your app, and the user has not performed any interactions at all, you would *still* need to connect. This is why it's an Effect: +回想一下,你还需要让组件和聊天室保持连接。代码放哪里呢? + +运行这个代码的 **原因** 不是特定的交互操作。用户为什么或怎么导航到聊天室屏幕的都不重要。既然用户正在看它并且能够和它交互,组件就要和选中的聊天服务器保持连接。即使聊天室组件显示的是应用的初始屏幕,用户根本还没有执行任何交互,仍然应该需要保持连接。这就是这里用 Effect 的原因: ```js {3-9} function ChatRoom({ roomId }) { @@ -72,7 +72,7 @@ function ChatRoom({ roomId }) { } ``` -With this code, you can be sure that there is always an active connection to the currently selected chat server, *regardless* of the specific interactions performed by the user. Whether the user has only opened your app, selected a different room, or navigated to another screen and back, your Effect ensures that the component will *remain synchronized* with the currently selected room, and will [re-connect whenever it's necessary.](/learn/lifecycle-of-reactive-effects#why-synchronization-may-need-to-happen-more-than-once) +**无论** 用户是否执行指定交互操作,这段代码都可以保证当前选中的聊天室服务器一直有一个活跃连接。用户是否只启动了应用,或选中了不同的聊天室,又或者导航到另一个屏幕后返回,Effect 都可以确保组件和当前选中的聊天室保持同步,并在必要时 [重新连接](/learn/lifecycle-of-reactive-effects#why-synchronization-may-need-to-happen-more-than-once)。 @@ -136,7 +136,7 @@ export function sendMessage(message) { } export function createConnection(serverUrl, roomId) { - // A real implementation would actually connect to the server + // 真正的实现实际上会连接到服务器 return { connect() { console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); @@ -154,13 +154,13 @@ input, select { margin-right: 20px; } -## Reactive values and reactive logic {/*reactive-values-and-reactive-logic*/} +## 响应式值和响应式逻辑 {/*reactive-values-and-reactive-logic*/} -Intuitively, you could say that event handlers are always triggered "manually", for example by clicking a button. Effects, on the other hand, are "automatic": they run and re-run as often as it's needed to stay synchronized. +直观上,你可以说事件处理函数总是“手动”触发的,例如点击按钮。另一方面, Effect 是自动触发:每当需要保持同步的时候他们就会开始运行和重新运行。 -There is a more precise way to think about this. +有一个更精确的方式来考虑这个问题。 -Props, state, and variables declared inside your component's body are called reactive values. In this example, `serverUrl` is not a reactive value, but `roomId` and `message` are. They participate in the rendering data flow: +组件内部声明的 state 和 props 变量被称为 响应式值。本示例中的 `serverUrl` 不是响应式值,但 `roomId` 和 `message` 是。他们参与组件的渲染数据流: ```js [[2, 3, "roomId"], [2, 4, "message"]] const serverUrl = 'https://localhost:1234'; @@ -172,16 +172,16 @@ function ChatRoom({ roomId }) { } ``` -Reactive values like these can change due to a re-render. For example, the user may edit the `message` or choose a different `roomId` in a dropdown. Event handlers and Effects respond to changes differently: +像这样的响应式值可以因为重新渲染而变化。例如用户可能会编辑 `message` 或者在下拉菜单中选中不同的 `roomId`。事件处理函数和 Effect 对于变化的响应是不一样的: -- **Logic inside event handlers is *not reactive.*** It will not run again unless the user performs the same interaction (e.g. a click) again. Event handlers can read reactive values without "reacting" to their changes. -- **Logic inside Effects is *reactive.*** If your Effect reads a reactive value, [you have to specify it as a dependency.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Then, if a re-render causes that value to change, React will re-run your Effect's logic with the new value. +- **事件处理函数内部的逻辑是非响应式的**。除非用户又执行了同样的操作(例如点击),否则这段逻辑不会再运行。事件处理函数可以在“不响应”他们变化的情况下读取响应式值。 +- **Effect 内部的逻辑是响应式的**。如果 Effect 要读取响应式值,[你必须将它指定为依赖项](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values)。如果接下来的重新渲染引起那个值变化,React 就会使用新值重新运行 Effect 内的逻辑。 -Let's revisit the previous example to illustrate this difference. +让我们重新看看前面的示例来说明差异。 -### Logic inside event handlers is not reactive {/*logic-inside-event-handlers-is-not-reactive*/} +### 事件处理函数内部的逻辑是非响应式的 {/*logic-inside-event-handlers-is-not-reactive*/} -Take a look at this line of code. Should this logic be reactive or not? +看这行代码。这个逻辑是响应式的吗? ```js [[2, 2, "message"]] // ... @@ -189,7 +189,7 @@ Take a look at this line of code. Should this logic be reactive or not? // ... ``` -From the user's perspective, **a change to the `message` does _not_ mean that they want to send a message.** It only means that the user is typing. In other words, the logic that sends a message should not be reactive. It should not run again only because the reactive value has changed. That's why it belongs in the event handler: +从用户角度出发,**`message` 的变化并不意味着他们想要发送消息**。它只能表明用户正在输入。换句话说,发送消息的逻辑不应该是响应式的。它不应该仅仅因为 响应式值 变化而再次运行。这就是应该把它归入事件处理函数的原因: ```js {2} function handleSendClick() { @@ -197,11 +197,11 @@ From the user's perspective, **a change to the `message` does _not_ mean that th } ``` -Event handlers aren't reactive, so `sendMessage(message)` will only run when the user clicks the Send button. +事件处理函数是非响应式的,所以 `sendMessage(message)` 只会在用户点击 Send 按钮的时候运行。 -### Logic inside Effects is reactive {/*logic-inside-effects-is-reactive*/} +### Effect 内部的逻辑是响应式的 {/*logic-inside-effects-is-reactive*/} -Now let's return to these lines: +现在让我们返回这几行代码: ```js [[2, 2, "roomId"]] // ... @@ -210,7 +210,7 @@ Now let's return to these lines: // ... ``` -From the user's perspective, **a change to the `roomId` *does* mean that they want to connect to a different room.** In other words, the logic for connecting to the room should be reactive. You *want* these lines of code to "keep up" with the reactive value, and to run again if that value is different. That's why it belongs in an Effect: +从用户角度出发,**`roomId` 的变化意味着他们的确想要连接到不同的房间**。换句话说,连接房间的逻辑应该是响应式的。你 **需要** 这几行代码和响应式值“保持同步”,并在值不同时再次运行。这就是它被归入 Effect 的原因: ```js {2-3} useEffect(() => { @@ -222,13 +222,13 @@ From the user's perspective, **a change to the `roomId` *does* mean that they wa }, [roomId]); ``` -Effects are reactive, so `createConnection(serverUrl, roomId)` and `connection.connect()` will run for every distinct value of `roomId`. Your Effect keeps the chat connection synchronized to the currently selected room. +Effect 是响应式的,所以 `createConnection(serverUrl, roomId)` 和 `connection.connect()` 会因为 `roomId` 每个不同的值而运行。Effect 让聊天室连接和当前选中的房间保持了同步。 -## Extracting non-reactive logic out of Effects {/*extracting-non-reactive-logic-out-of-effects*/} +## 从 Effect 中提取非响应式逻辑 {/*extracting-non-reactive-logic-out-of-effects*/} -Things get more tricky when you want to mix reactive logic with non-reactive logic. +当你想混合使用响应式逻辑和非响应式逻辑时,事情变得更加棘手。 -For example, imagine that you want to show a notification when the user connects to the chat. You read the current theme (dark or light) from the props so that you can show the notification in the correct color: +例如,假设你想在用户连接到聊天室时展示一个通知。并且通过从 props 中读取当前 theme(dark 或者 light)来展示对应颜色的通知: ```js {1,4-6} function ChatRoom({ roomId, theme }) { @@ -241,7 +241,7 @@ function ChatRoom({ roomId, theme }) { // ... ```` -However, `theme` is a reactive value (it can change as a result of re-rendering), and [every reactive value read by an Effect must be declared as its dependency.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) Now you have to specify `theme` as a dependency of your Effect: +但是 `theme` 是一个响应式值(它会由于重新渲染而变化),并且 [Effect 读取的每一个响应式值都必须在其依赖项中声明](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency)。现在你必须把 `theme` 作为 Effect 的依赖项之一: ```js {5,11} function ChatRoom({ roomId, theme }) { @@ -254,12 +254,12 @@ function ChatRoom({ roomId, theme }) { return () => { connection.disconnect() }; - }, [roomId, theme]); // ✅ All dependencies declared + }, [roomId, theme]); // ✅ 声明所有依赖项 // ... ```` -Play with this example and see if you can spot the problem with this user experience: - +用这个例子试一下,看你能否看出这个用户体验问题: +// todo:翻译进度在此 ```json package.json hidden @@ -335,7 +335,7 @@ export default function App() { ```js chat.js export function createConnection(serverUrl, roomId) { - // A real implementation would actually connect to the server + // 真正的实现实际上会连接到服务器 let connectedCallback; let timeout; return { @@ -435,7 +435,7 @@ function ChatRoom({ roomId, theme }) { }); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ All dependencies declared + }, [roomId]); // ✅ 声明所有依赖项 // ... ``` @@ -523,7 +523,7 @@ export default function App() { ```js chat.js export function createConnection(serverUrl, roomId) { - // A real implementation would actually connect to the server + // 真正的实现实际上会连接到服务器 let connectedCallback; let timeout; return { @@ -603,7 +603,7 @@ Later, you add multiple routes to your site. Now your `Page` component receives function Page({ url }) { useEffect(() => { logVisit(url); - }, []); // 🔴 React Hook useEffect has a missing dependency: 'url' + }, []); // 🔴 React Hook useEffect 缺少一个依赖项: 'url' // ... } ``` @@ -614,7 +614,7 @@ Think about what you want the code to do. You *want* to log a separate visit for function Page({ url }) { useEffect(() => { logVisit(url); - }, [url]); // ✅ All dependencies declared + }, [url]); // ✅ 声明所有依赖项 // ... } ``` @@ -628,7 +628,7 @@ function Page({ url }) { useEffect(() => { logVisit(url, numberOfItems); - }, [url]); // 🔴 React Hook useEffect has a missing dependency: 'numberOfItems' + }, [url]); // 🔴 React Hook useEffect 缺少依赖项: 'numberOfItems' // ... } ``` @@ -648,7 +648,7 @@ function Page({ url }) { useEffect(() => { onVisit(url); - }, [url]); // ✅ All dependencies declared + }, [url]); // ✅ 声明所有依赖项 // ... } ``` @@ -718,7 +718,7 @@ function Page({ url }) { useEffect(() => { logVisit(url, numberOfItems); - // 🔴 Avoid suppressing the linter like this: + // 🔴 避免像这样抑制代码检查: // eslint-disable-next-line react-hooks/exhaustive-deps }, [url]); // ... @@ -899,7 +899,7 @@ function Timer() { setCount(count + 1); }); - useTimer(onTick, 1000); // 🔴 Avoid: Passing Effect Events + useTimer(onTick, 1000); // 🔴 Avoid: 传递 Effect Event return

{count}

} @@ -912,7 +912,7 @@ function useTimer(callback, delay) { return () => { clearInterval(id); }; - }, [delay, callback]); // Need to specify "callback" in dependencies + }, [delay, callback]); // 需要在依赖项中指定“callback” } ``` @@ -934,12 +934,12 @@ function useTimer(callback, delay) { useEffect(() => { const id = setInterval(() => { - onTick(); // ✅ Good: Only called locally inside an Effect + onTick(); // ✅ Good: 只在 Effect 内部局部调用 }, delay); return () => { clearInterval(id); }; - }, [delay]); // No need to specify "onTick" (an Effect Event) as a dependency + }, [delay]); // 不需要指定 “onTick” (Effect Event) 作为依赖项 } ``` @@ -1502,7 +1502,7 @@ export default function App() { ```js chat.js export function createConnection(serverUrl, roomId) { - // A real implementation would actually connect to the server + // 真正的实现实际上会连接到服务器 let connectedCallback; let timeout; return { @@ -1643,7 +1643,7 @@ export default function App() { ```js chat.js export function createConnection(serverUrl, roomId) { - // A real implementation would actually connect to the server + // 真正的实现实际上会连接到服务器 let connectedCallback; let timeout; return { @@ -1786,7 +1786,7 @@ export default function App() { ```js chat.js export function createConnection(serverUrl, roomId) { - // A real implementation would actually connect to the server + // 真正的实现实际上会连接到服务器 let connectedCallback; let timeout; return { From 66c9240e91b0397f2c2b80924c1b660f347f76f1 Mon Sep 17 00:00:00 2001 From: yueyueyan Date: Fri, 12 May 2023 00:22:13 +0800 Subject: [PATCH 03/16] docs(cn): translate part of learn/separating-events-from-effects --- .../learn/separating-events-from-effects.md | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index f134cd177b..e4be340a85 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -259,7 +259,7 @@ function ChatRoom({ roomId, theme }) { ```` 用这个例子试一下,看你能否看出这个用户体验问题: -// todo:翻译进度在此 + ```json package.json hidden @@ -386,9 +386,9 @@ label { display: block; margin-top: 10px; } -When the `roomId` changes, the chat re-connects as you would expect. But since `theme` is also a dependency, the chat *also* re-connects every time you switch between the dark and the light theme. That's not great! +当 `roomId` 变化时,聊天会和预期一样重新连接。但是由于 `theme` 也是一个依赖项,所以每次你在 dark 和 light 主题间切换时,聊天 **也会** 重连。这不是很好! -In other words, you *don't* want this line to be reactive, even though it is inside an Effect (which is reactive): +换言之,即使它在 Effect 内部(这是响应式的),你也不想让这行代码变成响应式: ```js // ... @@ -396,17 +396,17 @@ In other words, you *don't* want this line to be reactive, even though it is ins // ... ``` -You need a way to separate this non-reactive logic from the reactive Effect around it. +你需要一个将这个非响应式逻辑和周围响应式 Effect 隔离开来的方法。 -### Declaring an Effect Event {/*declaring-an-effect-event*/} +### 声明一个 Effect Event {/*declaring-an-effect-event*/} -This section describes an **experimental API that has not yet been released** in a stable version of React. +本章节描述了一个在 React 稳定版中 **还没有发布的实验性 API**。 -Use a special Hook called [`useEffectEvent`](/reference/react/experimental_useEffectEvent) to extract this non-reactive logic out of your Effect: +使用 [`useEffectEvent`](/reference/react/experimental_useEffectEvent) 这个特殊的 Hook 从 Effect 中提取非响应式逻辑: ```js {1,4-6} import { useEffect, useEffectEvent } from 'react'; @@ -418,9 +418,9 @@ function ChatRoom({ roomId, theme }) { // ... ```` -Here, `onConnected` is called an *Effect Event.* It's a part of your Effect logic, but it behaves a lot more like an event handler. The logic inside it is not reactive, and it always "sees" the latest values of your props and state. +这里的 `onConnected` 被称为 **Effect Event**。它是 Effect 逻辑的一部分,但是其行为更像事件处理函数。它内部的逻辑不是响应式的,而且能一直“看见”最新的 props 和 state。 -Now you can call the `onConnected` Effect Event from inside your Effect: +现在你可以在 Effect 内部调用 `onConnected` Effect Event: ```js {2-4,9,13} function ChatRoom({ roomId, theme }) { @@ -439,9 +439,9 @@ function ChatRoom({ roomId, theme }) { // ... ``` -This solves the problem. Note that you had to *remove* `onConnected` from the list of your Effect's dependencies. **Effect Events are not reactive and must be omitted from dependencies.** +这个方法解决了问题。注意你必须从 Effect 依赖项中 **移除** `onConnected`。***Effect Event 是非响应式的并且必须从依赖项中删除**。 -Verify that the new behavior works as you would expect: +验证新表现是否和你预期的一样: @@ -574,19 +574,19 @@ label { display: block; margin-top: 10px; } -You can think of Effect Events as being very similar to event handlers. The main difference is that event handlers run in response to a user interactions, whereas Effect Events are triggered by you from Effects. Effect Events let you "break the chain" between the reactivity of Effects and code that should not be reactive. +你可以将 Effect Event 看成和事件处理函数相似的东西。主要区别是事件处理函数只在响应用户交互的时候运行,而 Effect Event 是你在 Effect 中触发的。Effect Event 让你在 Effect 响应性和不应是响应式的代码间“打破链条”。 -### Reading latest props and state with Effect Events {/*reading-latest-props-and-state-with-effect-events*/} +### 使用 Effect Event 读取最新的 props 和 state {/*reading-latest-props-and-state-with-effect-events*/} -This section describes an **experimental API that has not yet been released** in a stable version of React. +本章节描述了一个在 React 稳定版中 **还没有发布的实验性 API**。 -Effect Events let you fix many patterns where you might be tempted to suppress the dependency linter. +Effect Event 可以修复之前许多你可能试图抑制依赖项检查工具的地方。 -For example, say you have an Effect to log the page visits: +例如,假设你有一个记录页面访问的 Effect: ```js function Page() { @@ -597,7 +597,7 @@ function Page() { } ``` -Later, you add multiple routes to your site. Now your `Page` component receives a `url` prop with the current path. You want to pass the `url` as a part of your `logVisit` call, but the dependency linter complains: +稍后向你的站点添加多个路由。现在 `Page` 组件接收一个包含当前路径的 `url` prop。你想把 `url` 作为 `logVisit` 调用的一部分进行传递,但是依赖项检查工具控诉: ```js {1,3} function Page({ url }) { @@ -608,7 +608,7 @@ function Page({ url }) { } ``` -Think about what you want the code to do. You *want* to log a separate visit for different URLs since each URL represents a different page. In other words, this `logVisit` call *should* be reactive with respect to the `url`. This is why, in this case, it makes sense to follow the dependency linter, and add `url` as a dependency: +想想你想要代码做什么。你 **需要** 为不同的 URL 记录单独的访问,因为每个 URL 代表不同的页面。换言之,`logVisit` 调用对于 `url` **应该** 是响应式的。这就是为什么在这种情况下, 遵循依赖项检查工具并添加 `url` 作为一个依赖项很有意义: ```js {4} function Page({ url }) { @@ -619,7 +619,7 @@ function Page({ url }) { } ``` -Now let's say you want to include the number of items in the shopping cart together with every page visit: +现在假设你想在每次页面访问中包含购物车中的商品数量: ```js {2-3,6} function Page({ url }) { @@ -628,14 +628,14 @@ function Page({ url }) { useEffect(() => { logVisit(url, numberOfItems); - }, [url]); // 🔴 React Hook useEffect 缺少依赖项: 'numberOfItems' + }, [url]); // 🔴 React Hook useEffect 缺少依赖项: ‘numberOfItems’ // ... } ``` You used `numberOfItems` inside the Effect, so the linter asks you to add it as a dependency. However, you *don't* want the `logVisit` call to be reactive with respect to `numberOfItems`. If the user puts something into the shopping cart, and the `numberOfItems` changes, this *does not mean* that the user visited the page again. In other words, *visiting the page* is, in some sense, an "event". It happens at a precise moment in time. -Split the code in two parts: +将代码分割为两部分: ```js {5-7,10} function Page({ url }) { @@ -876,20 +876,20 @@ Read [Removing Effect Dependencies](/learn/removing-effect-dependencies) for oth -### Limitations of Effect Events {/*limitations-of-effect-events*/} +### Effect Event 的局限性 {/*limitations-of-effect-events*/} -This section describes an **experimental API that has not yet been released** in a stable version of React. +本章节描述了一个在 React 稳定版中 **还没有发布的实验性 API**。 -Effect Events are very limited in how you can use them: +Effect Event 的局限性在于你如何使用他们: -* **Only call them from inside Effects.** -* **Never pass them to other components or Hooks.** +* **只在 Effect 内部调用他们**。 +* **永远不要把他们传给其他的组件或者 Hook**。 -For example, don't declare and pass an Effect Event like this: +例如不要像这样声明和传递 Effect Event: ```js {4-6,8} function Timer() { @@ -916,7 +916,7 @@ function useTimer(callback, delay) { } ``` -Instead, always declare Effect Events directly next to the Effects that use them: +取而代之的是,永远直接在使用他们的 Effect 旁边声明 Effect Event: ```js {10-12,16,21} function Timer() { @@ -943,23 +943,23 @@ function useTimer(callback, delay) { } ``` -Effect Events are non-reactive "pieces" of your Effect code. They should be next to the Effect using them. +Effect Event 是 Effect 代码的非响应式“片段”。他们应该在使用他们的 Effect 的旁边。 -- Event handlers run in response to specific interactions. -- Effects run whenever synchronization is needed. -- Logic inside event handlers is not reactive. -- Logic inside Effects is reactive. -- You can move non-reactive logic from Effects into Effect Events. -- Only call Effect Events from inside Effects. -- Don't pass Effect Events to other components or Hooks. +- 事件处理函数在响应特定交互时运行。 +- Effect在需要同步的时候运行。 +- 事件处理函数内部的逻辑是非响应式的。 +- Effect 内部的逻辑是响应式的。 +- 你可以将非响应式逻辑从 Effect 移到 Effect Event 中。 +- 只在 Effect 内部调用 Effect Event。 +- 不要将 Effect Event 传给其他组件或者 Hook。 -#### Fix a variable that doesn't update {/*fix-a-variable-that-doesnt-update*/} +#### 修复一个不更新的变量 {/*fix-a-variable-that-doesnt-update*/} This `Timer` component keeps a `count` state variable which increases every second. The value by which it's increasing is stored in the `increment` state variable. You can control the `increment` variable with the plus and minus buttons. From c28a29b92e5a00850470e88f09e0b35cea6e73d1 Mon Sep 17 00:00:00 2001 From: yanyue Date: Fri, 12 May 2023 17:38:28 +0800 Subject: [PATCH 04/16] docs(cn): translate part of useEventEffect --- .../learn/separating-events-from-effects.md | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index e4be340a85..434d6092f4 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -633,7 +633,7 @@ function Page({ url }) { } ``` -You used `numberOfItems` inside the Effect, so the linter asks you to add it as a dependency. However, you *don't* want the `logVisit` call to be reactive with respect to `numberOfItems`. If the user puts something into the shopping cart, and the `numberOfItems` changes, this *does not mean* that the user visited the page again. In other words, *visiting the page* is, in some sense, an "event". It happens at a precise moment in time. +你在 Effect 内部使用了 `numberOfItems`,所以代码检查工具会让你把它加到依赖项中。但是,你 **不** 想要 `logVisit` 调用响应 `numberOfItems`。如果用户把某样东西放入购物车, `numberOfItems` 会变化,这 **并不意味着** 用户再次访问了这个页面。换句话说,在某种意义上,**访问页面** 是一个“事件”。它发生在某个准确的时刻。 将代码分割为两部分: @@ -653,15 +653,15 @@ function Page({ url }) { } ``` -Here, `onVisit` is an Effect Event. The code inside it isn't reactive. This is why you can use `numberOfItems` (or any other reactive value!) without worrying that it will cause the surrounding code to re-execute on changes. +这里的 `onVisit` 是一个 Effect Event。里面的代码不是响应式的。这就是为什么你可以使用 `numberOfItems`(或者任意响应式值!)而不用担心引起周围代码因为变化而重新执行。 -On the other hand, the Effect itself remains reactive. Code inside the Effect uses the `url` prop, so the Effect will re-run after every re-render with a different `url`. This, in turn, will call the `onVisit` Effect Event. +另一方面,Effect 本身仍然是响应式的。其内部的代码使用了 `url` prop,所以每次因为不同的 `url` 重新渲染后 Effect 都会重新运行。这会依次调用 `onVisit` 这个 Effect Event。 -As a result, you will call `logVisit` for every change to the `url`, and always read the latest `numberOfItems`. However, if `numberOfItems` changes on its own, this will not cause any of the code to re-run. +结果是你会因为 `url` 的变化去调用 `logVisit`,并且读取的一直都是最新的 `numberOfItems`。但是如果 `numberOfItems` 自己变化,不会引起任何代码的重新运行。 -You might be wondering if you could call `onVisit()` with no arguments, and read the `url` inside it: +你可能想知道是否可以无参数调用 `onVisit()` 并且读取内部的 `url`: ```js {2,6} const onVisit = useEffectEvent(() => { @@ -673,7 +673,7 @@ You might be wondering if you could call `onVisit()` with no arguments, and read }, [url]); ``` -This would work, but it's better to pass this `url` to the Effect Event explicitly. **By passing `url` as an argument to your Effect Event, you are saying that visiting a page with a different `url` constitutes a separate "event" from the user's perspective.** The `visitedUrl` is a *part* of the "event" that happened: +这可以起作用,但是更好的方法是将这个 `url` 显式传递给Effect Event。**通过将 `url` 作为参数传给 Effect Event,你可以说从用户角度来看使用不同的 `url` 访问页面构成了一个独立的“事件”**。`visitedUrl` 是发生的“事件”的一部分: ```js {1-2,6} const onVisit = useEffectEvent(visitedUrl => { @@ -685,9 +685,9 @@ This would work, but it's better to pass this `url` to the Effect Event explicit }, [url]); ``` -Since your Effect Event explicitly "asks" for the `visitedUrl`, now you can't accidentally remove `url` from the Effect's dependencies. If you remove the `url` dependency (causing distinct page visits to be counted as one), the linter will warn you about it. You want `onVisit` to be reactive with regards to the `url`, so instead of reading the `url` inside (where it wouldn't be reactive), you pass it *from* your Effect. +由于 Effect 明确“要求” `visitedUrl`,所以现在你不会不小心地从 Effect 的依赖项中移除 `url`。如果你移除了 `url` 依赖项(导致不同的页面访问被认为是一个),代码检查工具会向你提出警告。如果你想要 `onVisit` 能对 `url` 的变化做出响应,不要读取内部的 `url`(这里不是响应式的),而是应该将它 **从** Effect 中传入。 -This becomes especially important if there is some asynchronous logic inside the Effect: +如果 Effect 内部有一些异步逻辑,这就变得非常重要了: ```js {6,8} const onVisit = useEffectEvent(visitedUrl => { @@ -697,19 +697,19 @@ This becomes especially important if there is some asynchronous logic inside the useEffect(() => { setTimeout(() => { onVisit(url); - }, 5000); // Delay logging visits + }, 5000); // 延迟记录访问 }, [url]); ``` -Here, `url` inside `onVisit` corresponds to the *latest* `url` (which could have already changed), but `visitedUrl` corresponds to the `url` that originally caused this Effect (and this `onVisit` call) to run. +在这里,`onVisit` 内的 `url` 对应 **最新的** `url`(可能已经变化了),但是 `visitedUrl` 对应的是最开始引起这个 Effect(并且是本次 `onVisit` 调用)运行的 `url` 。 -#### Is it okay to suppress the dependency linter instead? {/*is-it-okay-to-suppress-the-dependency-linter-instead*/} +#### 抑制依赖项检查是可行的吗? {/*is-it-okay-to-suppress-the-dependency-linter-instead*/} -In the existing codebases, you may sometimes see the lint rule suppressed like this: +在已经存在的代码库中,你可能有时会看见像这样的检查规则抑制: ```js {7-9} function Page({ url }) { @@ -725,13 +725,13 @@ function Page({ url }) { } ``` -After `useEffectEvent` becomes a stable part of React, we recommend **never suppressing the linter**. +等 `useEffectEvent` 成为 React 稳定部分后,我们会推荐 **永远不要抑制代码检查工具**。 -The first downside of suppressing the rule is that React will no longer warn you when your Effect needs to "react" to a new reactive dependency you've introduced to your code. In the earlier example, you added `url` to the dependencies *because* React reminded you to do it. You will no longer get such reminders for any future edits to that Effect if you disable the linter. This leads to bugs. +抑制规则的第一个缺点是当 Effect 需要对一个已经在代码中出现过的新响应式依赖项做出“响应”时,React 不会再发出警告。在稍早之前的示例中,你将 `url` 添加为依赖项,**是因为** React 提醒你去做这件事。如果禁用代码检查,你未来将不会再收到任何关于 Effect 修改的提醒。这引起了 bug。 -Here is an example of a confusing bug caused by suppressing the linter. In this example, the `handleMove` function is supposed to read the current `canMove` state variable value in order to decide whether the dot should follow the cursor. However, `canMove` is always `true` inside `handleMove`. +这个示例展示了一个由抑制代码检查引起的奇怪 bug。在这个示例中,`handleMove` 应该读取当前的 state 变量 `canMove` 的值来决定这个点是否应该跟随光标。但是 `handleMove` 中的 `canMove` 一直是 `true`。 -Can you see why? +你能看出是为什么吗? @@ -790,13 +790,13 @@ body { -The problem with this code is in suppressing the dependency linter. If you remove the suppression, you'll see that this Effect should depend on the `handleMove` function. This makes sense: `handleMove` is declared inside the component body, which makes it a reactive value. Every reactive value must be specified as a dependency, or it can potentially get stale over time! +这段代码的问题在于抑制依赖项检查。如果移除,你可以看到 Effect 应该依赖于 `handleMove` 函数。这非常有意义:`handleMove` 是在组件内声明的,是响应式值。而每个响应式值都必须被指定为依赖项,否则它可能会随着时间而过时! -The author of the original code has "lied" to React by saying that the Effect does not depend (`[]`) on any reactive values. This is why React did not re-synchronize the Effect after `canMove` has changed (and `handleMove` with it). Because React did not re-synchronize the Effect, the `handleMove` attached as a listener is the `handleMove` function created during the initial render. During the initial render, `canMove` was `true`, which is why `handleMove` from the initial render will forever see that value. +原代码的作者对 React “撒谎”说 Effect 不依赖于任何响应式值(`[]`)。这就是为什么 `canMove`(以及 `handleMove`)变化后 React 没有重新同步。因为 React 没有重新同步 Effect,所以作为监听器附加的 `handleMove` 还是初次渲染期间创建的 `handleMove` 函数。初次渲染期间,`canMove` 的值是 `true`,这就是为什么来自初次渲染的 `handleMove` 永远只能看到这个值。 -**If you never suppress the linter, you will never see problems with stale values.** +**如果你从来没有抑制代码检查,就永远不会遇见过期值的问题。** -With `useEffectEvent`, there is no need to "lie" to the linter, and the code works as you would expect: +有了 `useEffectEvent`,就不需要对代码检查工具“说谎”,并且代码也能和你预期的一样工作: @@ -870,9 +870,9 @@ body { -This doesn't mean that `useEffectEvent` is *always* the correct solution. You should only apply it to the lines of code that you don't want to be reactive. In the above sandbox, you didn't want the Effect's code to be reactive with regards to `canMove`. That's why it made sense to extract an Effect Event. +这不意味着 `useEffectEvent` **总是** 正确的解决方案。你只能把它用在你不需要变成响应式的代码上。上面的 sandbox 中,你不需要 Effect 的代码响应 `canMove`。这就是提取 Effect Event很有意义的原因。 -Read [Removing Effect Dependencies](/learn/removing-effect-dependencies) for other correct alternatives to suppressing the linter. +阅读 [移除 Effect 依赖项](/learn/removing-effect-dependencies) 寻找抑制代码检查的其他正确的替代方式。 @@ -961,13 +961,13 @@ Effect Event 是 Effect 代码的非响应式“片段”。他们应该在使 #### 修复一个不更新的变量 {/*fix-a-variable-that-doesnt-update*/} -This `Timer` component keeps a `count` state variable which increases every second. The value by which it's increasing is stored in the `increment` state variable. You can control the `increment` variable with the plus and minus buttons. +`Timer` 组件保存了一个 `count` 的 state 变量,这个变量每秒增加一次。每次增加的值存储在 `increment` state 变量中。你可以使用加减按钮控制 `increment` 变量。 -However, no matter how many times you click the plus button, the counter is still incremented by one every second. What's wrong with this code? Why is `increment` always equal to `1` inside the Effect's code? Find the mistake and fix it. +但是无论你点击加号按钮多少次,计数器每秒都只增加 1。这段代码存在什么问题呢?为什么 Effect 内部的 `increment` 总是等于 `1` 呢?找出错误并修复它。 -To fix this code, it's enough to follow the rules. +修复这段代码,必须足够遵循这些规则。 @@ -1020,9 +1020,9 @@ button { margin: 10px; } -As usual, when you're looking for bugs in Effects, start by searching for linter suppressions. +和往常一样,当你寻找 Effect 中的 bug 时,从寻找代码检查抑制开始。 -If you remove the suppression comment, React will tell you that this Effect's code depends on `increment`, but you "lied" to React by claiming that this Effect does not depend on any reactive values (`[]`). Add `increment` to the dependency array: +如果你移除了抑制注释,React 就会告诉你这个 Effect 的代码依赖于 `increment`,但是你通过宣称这个 Effect 不依赖于响应式值(`[]`)“欺骗”了 React。将 `increment` 添加到依赖项数组: @@ -1070,19 +1070,19 @@ button { margin: 10px; } -Now, when `increment` changes, React will re-synchronize your Effect, which will restart the interval. +现在当 `increment` 变化时,React 会重新同步你的 Effect,这会重启 interval。 -#### Fix a freezing counter {/*fix-a-freezing-counter*/} +#### 修复一个冻结的计数器 {/*fix-a-freezing-counter*/} -This `Timer` component keeps a `count` state variable which increases every second. The value by which it's increasing is stored in the `increment` state variable, which you can control it with the plus and minus buttons. For example, try pressing the plus button nine times, and notice that the `count` now increases each second by ten rather than by one. +`Timer` 组件保存了一个 `count` 的 state 变量,这个变量每秒增加一次。每次增加的值存储在 `increment` state 变量中,你可以使用加减按钮控制它。例如,尝试按压加号按钮九次,注意现在 `count` 每次都增加 10 而不是 1。 -There is a small issue with this user interface. You might notice that if you keep pressing the plus or minus buttons faster than once per second, the timer itself seems to pause. It only resumes after a second passes since the last time you've pressed either button. Find why this is happening, and fix the issue so that the timer ticks on *every* second without interruptions. +这个用户接口有一个小问题。你可能注意到如果你每秒内按压加减按钮不止一次, 那计时器本身似乎就会暂停。它只在你最后一次按压按钮的一秒后恢复。找出为什么会发生这种现象,并修复它以便计时器能 **每** 秒滴答作响而不中断。 -It seems like the Effect which sets up the timer "reacts" to the `increment` value. Does the line that uses the current `increment` value in order to call `setCount` really need to be reactive? +似乎设置计时器的 Effect 对 `increment` 值的变化做出了 “响应”。为了调用 `setCount` 而使用当前 `increment` 值的代码行真的需要是响应式吗? @@ -1151,9 +1151,9 @@ button { margin: 10px; } -The issue is that the code inside the Effect uses the `increment` state variable. Since it's a dependency of your Effect, every change to `increment` causes the Effect to re-synchronize, which causes the interval to clear. If you keep clearing the interval every time before it has a chance to fire, it will appear as if the timer has stalled. +问题在于 Effect 内部的代码使用了 `increment` 这个 state 变量。因为它是 Effect 的一个依赖项,每次 `increment` 变化都会引起 Effect 重新同步,这引起了 interval 清理。如果你每次有机会触发之前就清理 interval,它会表现得好像计时器已经停止了。 -To solve the issue, extract an `onTick` Effect Event from the Effect: +为了解决这个问题,需要从 Effect 中提取一个 Effect Event `onTick`: From 24e398131c627b459e557e9dafb0f33f15c8a940 Mon Sep 17 00:00:00 2001 From: yueyueyan Date: Sat, 13 May 2023 01:31:28 +0800 Subject: [PATCH 05/16] docs(cn):translate all content of separating-events-from-effects --- .../learn/separating-events-from-effects.md | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 434d6092f4..48dc1fabc5 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -1223,17 +1223,17 @@ button { margin: 10px; } -Since `onTick` is an Effect Event, the code inside it isn't reactive. The change to `increment` does not trigger any Effects. +由于 `onTick` 是一个 Effect Event,所以内部的代码是非响应式的。`increment` 的变化不会触发任何 Effect。 -#### Fix a non-adjustable delay {/*fix-a-non-adjustable-delay*/} +#### 修复不可调整的延迟 {/*fix-a-non-adjustable-delay*/} -In this example, you can customize the interval delay. It's stored in a `delay` state variable which is updated by two buttons. However, even if you press the "plus 100 ms" button until the `delay` is 1000 milliseconds (that is, a second), you'll notice that the timer still increments very fast (every 100 ms). It's as if your changes to the `delay` are ignored. Find and fix the bug. +在这个示例中,你可以自定义 interval 延迟。它被储存在一个由两个按钮更新的 `delay` state 变量中。但你即使按了“加 100 ms”按钮到 `delay` 为 1000 毫秒(即 1 秒),可以注意到计时器仍然在快速增加(每 100 ms)。你对 `delay` 的修改好像被忽略了。找到并修复这个 bug。 -Code inside Effect Events is not reactive. Are there cases in which you would _want_ the `setInterval` call to re-run? +Effect Event 内部的代码是非响应式的。哪些情况下你会 **想要** `setInterval` 调用重新运行呢? @@ -1322,7 +1322,7 @@ button { margin: 10px; } -The problem with the above example is that it extracted an Effect Event called `onMount` without considering what the code should actually be doing. You should only extract Effect Events for a specific reason: when you want to make a part of your code non-reactive. However, the `setInterval` call *should* be reactive with respect to the `delay` state variable. If the `delay` changes, you want to set up the interval from scratch! To fix this code, pull all the reactive code back inside the Effect: +上面这个示例的问题在于它没有考虑代码实际正在做什么就直接提取了一个叫做 `onMount` 的 Effect Event。你应该只为特定的原因提取 Effect Event:你想让代码的一部分称为非响应式。但是,`setInterval` 调用 state 变量 `delay` 的变化 **应该** 是响应式的。如果 `delay` 变化了,你想要重新设置 interval!为了修复这个问题,你需要将所有的响应式代码放回到 Effect 内部: @@ -1402,21 +1402,21 @@ button { margin: 10px; } -In general, you should be suspicious of functions like `onMount` that focus on the *timing* rather than the *purpose* of a piece of code. It may feel "more descriptive" at first but it obscures your intent. As a rule of thumb, Effect Events should correspond to something that happens from the *user's* perspective. For example, `onMessage`, `onTick`, `onVisit`, or `onConnected` are good Effect Event names. Code inside them would likely not need to be reactive. On the other hand, `onMount`, `onUpdate`, `onUnmount`, or `onAfterRender` are so generic that it's easy to accidentally put code that *should* be reactive into them. This is why you should name your Effect Events after *what the user thinks has happened,* not when some code happened to run. +总的来说,你应该对像 `onMount` 这样主要关注 **执行时机** 而非 **目的** 的函数持有怀疑态度。开始可能会感觉“更具描述性”,但是可能会模糊你的意图。根据经验来说,Effect Event 应该对应从“用户的”角度发生的事情。例如,`onMessage`,`onTick`,`onVisit` 或者 `onConnected` 是优秀的 Effect Event 名称。它们内部的代码可能不需要是响应式的。另一方面,`onMount`,`onUpdate`,`onUnmount` 或者 `onAfterRender` 太通用了,以至于很容易不小心就把一些"应该"是响应式的代码放入其中。这就是为什么你应该用 **用户想要什么发生** 来给你的 Effect Event 命名,而不是用某些代码正好运行的时机命名。 -#### Fix a delayed notification {/*fix-a-delayed-notification*/} +#### 修复一个延迟的通知 {/*fix-a-delayed-notification*/} -When you join a chat room, this component shows a notification. However, it doesn't show the notification immediately. Instead, the notification is artificially delayed by two seconds so that the user has a chance to look around the UI. +当你加入一个聊天室时,这个组件展示一个通知。但是它不会立刻展示通知。相反,把通知人工延迟 2 秒钟,以便用户有机会查看 UI。 -This almost works, but there is a bug. Try changing the dropdown from "general" to "travel" and then to "music" very quickly. If you do it fast enough, you will see two notifications (as expected!) but they will *both* say "Welcome to music". +这几乎生效了,但还是有一个 bug。尝试将下拉菜单从“general”变成“travel”并且接下来非常快速的变成“music”。如果你动作足够快,你会看到两个通知(和预期一样!),但是他们 **都是** 展示 “Welcome to music”。 -Fix it so that when you switch from "general" to "travel" and then to "music" very quickly, you see two notifications, the first one being "Welcome to travel" and the second one being "Welcome to music". (For an additional challenge, assuming you've *already* made the notifications show the correct rooms, change the code so that only the latter notification is displayed.) +修复它,让它能在你快速从“general”切换到“travel”再到“music”的时候看见两个通知,第一个是“Welcome to travel” ,第二个是“Welcome to music”。(有一个额外的挑战,假设你 **已经** 让通知显示了正确的房间,请修改代码只展示后面的通知。) -Your Effect knows which room it connected to. Is there any information that you might want to pass to your Effect Event? +你的 Effect 知道它连接的是哪一个房间。有任何你可能想要传给 Effect Event 的信息吗? @@ -1555,11 +1555,11 @@ label { display: block; margin-top: 10px; } -Inside your Effect Event, `roomId` is the value *at the time Effect Event was called.* +在 Effect Event 内部,`roomId` 是 **Effect Event 被调用时刻** 的值。 -Your Effect Event is called with a two second delay. If you're quickly switching from the travel to the music room, by the time the travel room's notification shows, `roomId` is already `"music"`. This is why both notifications say "Welcome to music". +Effect Event 伴随着两秒的延迟被调用。如果你快速地从 travel 切换到 music 聊天室,直到 travel 聊天室的通知显示出来,`roomId` 已经是 `“music”` 了。这就是为什么两个通知都是 “Welcome to music”。 -To fix the issue, instead of reading the *latest* `roomId` inside the Effect Event, make it a parameter of your Effect Event, like `connectedRoomId` below. Then pass `roomId` from your Effect by calling `onConnected(roomId)`: +为了修复这个问题,不要读取 Effect Event 内部 **最新的** `roomId`,而是如同下面的 `connectedRoomId` 一样让它成为 Effect Event 的参数。然后通过调用 `onConnected(roomId)` 将 `roomId` 从 Effect 中传入: @@ -1694,9 +1694,10 @@ label { display: block; margin-top: 10px; } -The Effect that had `roomId` set to `"travel"` (so it connected to the `"travel"` room) will show the notification for `"travel"`. The Effect that had `roomId` set to `"music"` (so it connected to the `"music"` room) will show the notification for `"music"`. In other words, `connectedRoomId` comes from your Effect (which is reactive), while `theme` always uses the latest value. -To solve the additional challenge, save the notification timeout ID and clear it in the cleanup function of your Effect: +将 `roomId` 设置为 `“travel”`(所以它连接到了 `“travel”` 聊天室)的 Effect 将会展示 `“travel”` 的通知。将 `roomId` 设置为 `“music”`(所以它连接到了 `“music”` 聊天室)的 Effect 将会展示 `"music"` 的通知。换言之,`connectedRoomId` 来自 Effect(是响应式的),而 `theme` 总是使用最新值。 + +为了解决额外的挑战,保存通知的 timeout ID,并在 Effect 的清理函数中进行清理: @@ -1837,7 +1838,7 @@ label { display: block; margin-top: 10px; } -This ensures that already scheduled (but not yet displayed) notifications get cancelled when you change rooms. +这确保了当你修改聊天室时,已经安排好(但还没展示)的通知会被取消。 From eaac9e2d3ebe9fc39652598399a14eb5d23c1728 Mon Sep 17 00:00:00 2001 From: earthaYan Date: Mon, 26 Jun 2023 14:20:05 +0800 Subject: [PATCH 06/16] Update src/content/learn/separating-events-from-effects.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/separating-events-from-effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 48dc1fabc5..bc7c782455 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -948,7 +948,7 @@ Effect Event 是 Effect 代码的非响应式“片段”。他们应该在使 - 事件处理函数在响应特定交互时运行。 -- Effect在需要同步的时候运行。 +- Effect 在需要同步的时候运行。 - 事件处理函数内部的逻辑是非响应式的。 - Effect 内部的逻辑是响应式的。 - 你可以将非响应式逻辑从 Effect 移到 Effect Event 中。 From 117f705013a56bccbcaa9f0c27f5ab663ab0d3a4 Mon Sep 17 00:00:00 2001 From: yanyue Date: Mon, 26 Jun 2023 14:52:30 +0800 Subject: [PATCH 07/16] fix from review suggestion --- src/content/learn/separating-events-from-effects.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 5bd02c2855..b000b7b3c0 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -11,7 +11,7 @@ title: 将事件从 Effect 中分开 - 怎么在事件处理函数和 Effect 之间做选择 -- 为什么 Effects 是响应式的,而事件处理函数不是 +- 为什么 Effect 是响应式的,而事件处理函数不是 - 当你想要 Effect 的部分代码变成非响应式时要做些什么 - Effect Event 是什么,以及怎么从 Effect 中提取 - 怎么使用 Effect Event 读取最新的 props 和 state @@ -31,7 +31,7 @@ title: 将事件从 Effect 中分开 ### 事件处理函数只在响应特定的交互操作时运行 {/*event-handlers-run-in-response-to-specific-interactions*/} -从用户角度出发,发送消息是 **因为** 他点击了特定的 “send” 按钮。如果在任意时间或者因为其他原因发送消息,用户会觉得非常混乱。这就是为什么发送消息应该使用事件处理函数。事件处理函数是让你处理特定的交互操作的: +从用户角度出发,发送消息是 **因为** 他点击了特定的 “Send” 按钮。如果在任意时间或者因为其他原因发送消息,用户会觉得非常混乱。这就是为什么发送消息应该使用事件处理函数。事件处理函数是让你处理特定的交互操作的: ```js {4-6} function ChatRoom({ roomId }) { From 8a93cda0eb6ce81a1a76a8829d41d7206396affe Mon Sep 17 00:00:00 2001 From: earthaYan Date: Tue, 27 Jun 2023 22:18:47 +0800 Subject: [PATCH 08/16] Update src/content/learn/separating-events-from-effects.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/separating-events-from-effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index b000b7b3c0..20e7f82946 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -27,7 +27,7 @@ title: 将事件从 Effect 中分开 1. 组件应该自动连接选中的聊天室。 1. 每当你点击 “Send” 按钮,组件应该在当前聊天界面发送一条消息。 -假设你已经实现了这部分代码,但是还没有确定应该放在哪里。你是应该用事件处理函数还是 Effect 呢?每当你需要回答这个问题时,请考虑一下 [**为什么** 代码需要运行](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events)。 +假设你已经实现了这部分代码,但是还没有确定应该放在哪里。你是应该用事件处理函数还是 Effect 呢?每当你需要回答这个问题时,请考虑一下 [为什么代码需要运行](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events)。 ### 事件处理函数只在响应特定的交互操作时运行 {/*event-handlers-run-in-response-to-specific-interactions*/} From 09ff0c3447883ad2861e5f64813640c41e081a41 Mon Sep 17 00:00:00 2001 From: yueyueyan Date: Tue, 27 Jun 2023 22:43:56 +0800 Subject: [PATCH 09/16] adjust quote --- src/content/learn/separating-events-from-effects.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 20e7f82946..b924a38cb6 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -25,13 +25,13 @@ title: 将事件从 Effect 中分开 假设你正在实现一个聊天室组件,需求如下: 1. 组件应该自动连接选中的聊天室。 -1. 每当你点击 “Send” 按钮,组件应该在当前聊天界面发送一条消息。 +1. 每当你点击“Send”按钮,组件应该在当前聊天界面发送一条消息。 假设你已经实现了这部分代码,但是还没有确定应该放在哪里。你是应该用事件处理函数还是 Effect 呢?每当你需要回答这个问题时,请考虑一下 [为什么代码需要运行](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events)。 ### 事件处理函数只在响应特定的交互操作时运行 {/*event-handlers-run-in-response-to-specific-interactions*/} -从用户角度出发,发送消息是 **因为** 他点击了特定的 “Send” 按钮。如果在任意时间或者因为其他原因发送消息,用户会觉得非常混乱。这就是为什么发送消息应该使用事件处理函数。事件处理函数是让你处理特定的交互操作的: +从用户角度出发,发送消息是 **因为** 他点击了特定的“Send”按钮。如果在任意时间或者因为其他原因发送消息,用户会觉得非常混乱。这就是为什么发送消息应该使用事件处理函数。事件处理函数是让你处理特定的交互操作的: ```js {4-6} function ChatRoom({ roomId }) { @@ -197,7 +197,7 @@ function ChatRoom({ roomId }) { } ``` -事件处理函数是非响应式的,所以 `sendMessage(message)` 只会在用户点击 Send 按钮的时候运行。 +事件处理函数是非响应式的,所以 `sendMessage(message)` 只会在用户点击“Send”按钮的时候运行。 ### Effect 内部的逻辑是响应式的 {/*logic-inside-effects-is-reactive*/} From cff1129c9be6c62fbaacdc8995bbd909f9634181 Mon Sep 17 00:00:00 2001 From: yueyueyan Date: Tue, 27 Jun 2023 23:16:15 +0800 Subject: [PATCH 10/16] fix from review suggestion --- src/content/learn/separating-events-from-effects.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index b924a38cb6..7d141acfc4 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -439,7 +439,7 @@ function ChatRoom({ roomId, theme }) { // ... ``` -这个方法解决了问题。注意你必须从 Effect 依赖项中 **移除** `onConnected`。***Effect Event 是非响应式的并且必须从依赖项中删除**。 +这个方法解决了问题。注意你必须从 Effect 依赖项中 **移除** `onConnected`。**Effect Event 是非响应式的并且必须从依赖项中删除**。 验证新表现是否和你预期的一样: @@ -870,7 +870,7 @@ body {
-这不意味着 `useEffectEvent` **总是** 正确的解决方案。你只能把它用在你不需要变成响应式的代码上。上面的 sandbox 中,你不需要 Effect 的代码响应 `canMove`。这就是提取 Effect Event很有意义的原因。 +这不意味着 `useEffectEvent` **总是** 正确的解决方案。你只能把它用在你不需要变成响应式的代码上。上面的 sandbox 中,你不需要 Effect 的代码响应 `canMove`。这就是提取 Effect Event 很有意义的原因。 阅读 [移除 Effect 依赖项](/learn/removing-effect-dependencies) 寻找抑制代码检查的其他正确的替代方式。 @@ -1076,7 +1076,7 @@ button { margin: 10px; } #### 修复一个冻结的计数器 {/*fix-a-freezing-counter*/} -`Timer` 组件保存了一个 `count` 的 state 变量,这个变量每秒增加一次。每次增加的值存储在 `increment` state 变量中,你可以使用加减按钮控制它。例如,尝试按压加号按钮九次,注意现在 `count` 每次都增加 10 而不是 1。 +`Timer` 组件保存了一个 `count` 的 state 变量,这个变量每秒增加一次。每次增加的值存储在 `increment` state 变量中,你可以使用加减按钮控制它。例如,尝试点击加号按钮九次,注意现在 `count` 每次都增加 10 而不是 1。 这个用户接口有一个小问题。你可能注意到如果你每秒内按压加减按钮不止一次, 那计时器本身似乎就会暂停。它只在你最后一次按压按钮的一秒后恢复。找出为什么会发生这种现象,并修复它以便计时器能 **每** 秒滴答作响而不中断。 @@ -1555,11 +1555,11 @@ label { display: block; margin-top: 10px; } -在 Effect Event 内部,`roomId` 是 **Effect Event 被调用时刻** 的值。 +在 Effect Event 内部,`roomId` 是 **Effect Event 被调用时刻** 的值。 Effect Event 伴随着两秒的延迟被调用。如果你快速地从 travel 切换到 music 聊天室,直到 travel 聊天室的通知显示出来,`roomId` 已经是 `“music”` 了。这就是为什么两个通知都是 “Welcome to music”。 -为了修复这个问题,不要读取 Effect Event 内部 **最新的** `roomId`,而是如同下面的 `connectedRoomId` 一样让它成为 Effect Event 的参数。然后通过调用 `onConnected(roomId)` 将 `roomId` 从 Effect 中传入: +为了修复这个问题,不要在 Effect Event 里面读取 **最新的** `roomId`,而是如同下面的 `connectedRoomId` 一样让它成为 Effect Event 的参数。然后通过调用 `onConnected(roomId)` 将 `roomId` 从 Effect 中传入: From 50163845955394259da02c14b1e83239b0a00cb4 Mon Sep 17 00:00:00 2001 From: Yucohny <79147654+Yucohny@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:20:22 +0800 Subject: [PATCH 11/16] Update src/content/learn/separating-events-from-effects.md --- src/content/learn/separating-events-from-effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 7d141acfc4..2c4dea8aba 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -4,7 +4,7 @@ title: 将事件从 Effect 中分开 -事件处理函数只有在你再次执行同样的交互时才会重新运行。Effect 和事件处理函数不一样,它只有在读取的 prop 或 state 值和上一次渲染不一样时才会重新同步。有时你需要这两种行为的混合体:即一个 Effect 只在响应某些值时重新运行,但是在其他值变化时不重新运行。本章将会教你怎么实现这一点。 +事件处理函数只有在你再次执行同样的交互时才会重新运行。Effect 和事件处理函数不一样,它只有在读取的 props 或 state 值和上一次渲染不一样时才会重新同步。有时你需要这两种行为的混合体:即一个 Effect 只在响应某些值时重新运行,但是在其他值变化时不重新运行。本章将会教你怎么实现这一点。 From 2feecb07f481286f363b14b57f2295f47e3def97 Mon Sep 17 00:00:00 2001 From: Yucohny <79147654+Yucohny@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:20:29 +0800 Subject: [PATCH 12/16] Update src/content/learn/separating-events-from-effects.md --- src/content/learn/separating-events-from-effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 2c4dea8aba..0ac683c2f8 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -597,7 +597,7 @@ function Page() { } ``` -稍后向你的站点添加多个路由。现在 `Page` 组件接收一个包含当前路径的 `url` prop。你想把 `url` 作为 `logVisit` 调用的一部分进行传递,但是依赖项检查工具控诉: +稍后向你的站点添加多个路由。现在 `Page` 组件接收包含当前路径的 `url` props。你想把 `url` 作为 `logVisit` 调用的一部分进行传递,但是依赖项检查工具会提示: ```js {1,3} function Page({ url }) { From be8d611909c3dd9984a8d7d6bb8cd595504a79d2 Mon Sep 17 00:00:00 2001 From: Yucohny <79147654+Yucohny@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:20:36 +0800 Subject: [PATCH 13/16] Update src/content/learn/separating-events-from-effects.md --- src/content/learn/separating-events-from-effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 0ac683c2f8..f55a1e229b 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -655,7 +655,7 @@ function Page({ url }) { 这里的 `onVisit` 是一个 Effect Event。里面的代码不是响应式的。这就是为什么你可以使用 `numberOfItems`(或者任意响应式值!)而不用担心引起周围代码因为变化而重新执行。 -另一方面,Effect 本身仍然是响应式的。其内部的代码使用了 `url` prop,所以每次因为不同的 `url` 重新渲染后 Effect 都会重新运行。这会依次调用 `onVisit` 这个 Effect Event。 +另一方面,Effect 本身仍然是响应式的。其内部的代码使用了 `url` props,所以每次因为不同的 `url` 重新渲染后 Effect 都会重新运行。这会依次调用 `onVisit` 这个 Effect Event。 结果是你会因为 `url` 的变化去调用 `logVisit`,并且读取的一直都是最新的 `numberOfItems`。但是如果 `numberOfItems` 自己变化,不会引起任何代码的重新运行。 From 0eb60175d7d8476928cfad68fb8da8f8faaaa319 Mon Sep 17 00:00:00 2001 From: Yucohny <79147654+Yucohny@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:20:43 +0800 Subject: [PATCH 14/16] Update src/content/learn/separating-events-from-effects.md --- src/content/learn/separating-events-from-effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index f55a1e229b..473c3461d8 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -1082,7 +1082,7 @@ button { margin: 10px; } -似乎设置计时器的 Effect 对 `increment` 值的变化做出了 “响应”。为了调用 `setCount` 而使用当前 `increment` 值的代码行真的需要是响应式吗? +似乎设置计时器的 Effect 对 `increment` 值的变化做出了“响应”。为了调用 `setCount` 而使用当前 `increment` 值的代码行真的需要是响应式吗? From f7ef7aaf7822c3647488531ad6a84be17ee89d6d Mon Sep 17 00:00:00 2001 From: Yucohny <79147654+Yucohny@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:20:50 +0800 Subject: [PATCH 15/16] Update src/content/learn/separating-events-from-effects.md --- src/content/learn/separating-events-from-effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 473c3461d8..430e481aac 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -1406,7 +1406,7 @@ button { margin: 10px; } -#### 修复一个延迟的通知 {/*fix-a-delayed-notification*/} +#### 修复延迟通知 {/*fix-a-delayed-notification*/} 当你加入一个聊天室时,这个组件展示一个通知。但是它不会立刻展示通知。相反,把通知人工延迟 2 秒钟,以便用户有机会查看 UI。 From 6ed24606c7cd0d115cd0cbf1b6bbc3175230f7be Mon Sep 17 00:00:00 2001 From: Yucohny <79147654+Yucohny@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:20:57 +0800 Subject: [PATCH 16/16] Update src/content/learn/separating-events-from-effects.md --- src/content/learn/separating-events-from-effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 430e481aac..cd1dee80c6 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -1412,7 +1412,7 @@ button { margin: 10px; } 这几乎生效了,但还是有一个 bug。尝试将下拉菜单从“general”变成“travel”并且接下来非常快速的变成“music”。如果你动作足够快,你会看到两个通知(和预期一样!),但是他们 **都是** 展示 “Welcome to music”。 -修复它,让它能在你快速从“general”切换到“travel”再到“music”的时候看见两个通知,第一个是“Welcome to travel” ,第二个是“Welcome to music”。(有一个额外的挑战,假设你 **已经** 让通知显示了正确的房间,请修改代码只展示后面的通知。) +修复它,让它能在你快速从“general”切换到“travel”再到“music”的时候看见两个通知,第一个是“Welcome to travel” ,第二个是“Welcome to music”(有一个额外的挑战,假设你 **已经** 让通知显示了正确的房间,请修改代码只展示后面的通知)。