Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: translate Queueing a Series of State Updates #417

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 71 additions & 71 deletions src/content/learn/queueing-a-series-of-state-updates.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
---
title: Queueing a Series of State Updates
title: 將一系列的 State 更新加入隊列
---

<Intro>

Setting a state variable will queue another render. But sometimes you might want to perform multiple operations on the value before queueing the next render. To do this, it helps to understand how React batches state updates.
設置 state 變數將使另一個 render 加入隊列。但有時後你可能希望在加入隊列之前對該變數進行多個操作。為此,了解 React 如何批次更新 state 會有所幫助。

</Intro>

<YouWillLearn>

* What "batching" is and how React uses it to process multiple state updates
* How to apply several updates to the same state variable in a row
* 什麼是「批次處理」以及 React 如何使用它來處理多個 state 更新
* 如何連續對同一個 state 變數進行多次更新

</YouWillLearn>

## React batches state updates {/*react-batches-state-updates*/}
## React 批次更新 state {/*react-batches-state-updates*/}

You might expect that clicking the "+3" button will increment the counter three times because it calls `setNumber(number + 1)` three times:
你可能預期點擊「+3」按鈕將增加計數三次,因為它調用了 `setNumber(number + 1)` 三次:

<Sandpack>

Expand Down Expand Up @@ -47,29 +47,29 @@ h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; }

</Sandpack>

However, as you might recall from the previous section, [each render's state values are fixed](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time), so the value of `number` inside the first render's event handler is always `0`, no matter how many times you call `setNumber(1)`:
但是,正如你可能還記得上一節中所提到, [每個 renderstate 值都是固定的](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time),因此在第一個 render 事件處理程序的 `number` 值始終皆為`0`,無論呼叫多少次`setNumber(1)`

```js
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
```

But there is one other factor at play here. **React waits until *all* code in the event handlers has run before processing your state updates.** This is why the re-render only happens *after* all these `setNumber()` calls.
但這裡還有另一個原因。**React 會等到*所有*在事件處理程序中的程式碼都運行完畢後才更新 state** 這就是為什麼重新 render 只有在呼叫所有`setNumber()`*之後*發生。

This might remind you of a waiter taking an order at the restaurant. A waiter doesn't run to the kitchen at the mention of your first dish! Instead, they let you finish your order, let you make changes to it, and even take orders from other people at the table.
這可能會讓你想起餐廳的服務生點菜。服務生不會在你點第一道菜時就跑到廚房!相反,他們會讓你一次點完餐,可以讓你進行更改,甚至接受餐桌上其他人的點餐。

<Illustration src="/images/docs/illustrations/i_react-batching.png" alt="An elegant cursor at a restaurant places and order multiple times with React, playing the part of the waiter. After she calls setState() multiple times, the waiter writes down the last one she requested as her final order." />

This lets you update multiple state variables--even from multiple components--without triggering too many [re-renders.](/learn/render-and-commit#re-renders-when-state-updates) But this also means that the UI won't be updated until _after_ your event handler, and any code in it, completes. This behavior, also known as **batching,** makes your React app run much faster. It also avoids dealing with confusing "half-finished" renders where only some of the variables have been updated.
這使你可以更新多個 state 變數——甚至可以從多個 component 進行更新——而不會觸發太多[重新 render。](/learn/render-and-commit#re-renders-when-state-updates) 這也意味著在事件處理程序及其中的任何程式碼執行完成*之前*, UI 不會進行更新。這種行為也稱為*批次處理*,使你的 React 應用程式執行得更快。它還避免了處理令人困惑的「半完成」render,也就是只更新了一些變數。

**React does not batch across *multiple* intentional events like clicks**--each click is handled separately. Rest assured that React only does batching when it's generally safe to do. This ensures that, for example, if the first button click disables a form, the second click would not submit it again.
**React 不會批次處理*多個*主動事件(例如點擊)**——每次點擊都是單獨處理的。請放心,React 通常只在安全的情況下才進行批次處理。例如,如果第一次點擊按鈕禁用了表單,則第二次點擊將不會再次提交該表單。

## Updating the same state multiple times before the next render {/*updating-the-same-state-multiple-times-before-the-next-render*/}
## 在下一次 Render 之前多次更新相同的 state {/*updating-the-same-state-multiple-times-before-the-next-render*/}

It is an uncommon use case, but if you would like to update the same state variable multiple times before the next render, instead of passing the *next state value* like `setNumber(number + 1)`, you can pass a *function* that calculates the next state based on the previous one in the queue, like `setNumber(n => n + 1)`. It is a way to tell React to "do something with the state value" instead of just replacing it.
這是一個不常見的範例,但如果你想在下一次 render 之前多次更新相同的 state 變數,像是`setNumber(n => n + 1)`,可以傳遞一個*函數*,該函數根據前一個在隊列中的 state 來計算下一個 state,而不是像`setNumber(number + 1)`傳遞*下一個 state 的值*。這是一種告訴 React「用 state 值做某事」而不只是替換它的方法。

Try incrementing the counter now:
現在嘗試增加計數:

<Sandpack>

Expand Down Expand Up @@ -99,37 +99,37 @@ h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; }

</Sandpack>

Here, `n => n + 1` is called an **updater function.** When you pass it to a state setter:
這裡的 `n => n + 1` 稱為**更新函數**。當你將其傳遞給 state 設置器時:

1. React queues this function to be processed after all the other code in the event handler has run.
2. During the next render, React goes through the queue and gives you the final updated state.
1. React 將此函數加入隊列,以便在事件處理器程序的所有其他代碼運行後進行處理。
2. 在下一次 Render 期間,React 會遍歷隊列並為你提供最終的更新 state

```js
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
```

Here's how React works through these lines of code while executing the event handler:
以下是 React 在執行事件處理程序時如何處理這些程式碼:

1. `setNumber(n => n + 1)`: `n => n + 1` is a function. React adds it to a queue.
1. `setNumber(n => n + 1)`: `n => n + 1` is a function. React adds it to a queue.
1. `setNumber(n => n + 1)`: `n => n + 1` is a function. React adds it to a queue.
1. `setNumber(n => n + 1)`: `n => n + 1` 是一個函數。React 將其添加到隊列中。
1. `setNumber(n => n + 1)`: `n => n + 1` 是一個函數。React 將其添加到隊列中。
1. `setNumber(n => n + 1)`: `n => n + 1` 是一個函數。React 將其添加到隊列中。

When you call `useState` during the next render, React goes through the queue. The previous `number` state was `0`, so that's what React passes to the first updater function as the `n` argument. Then React takes the return value of your previous updater function and passes it to the next updater as `n`, and so on:
當你在下一次 render 期間呼叫 `useState` React 會遍歷隊列。前一個 `number` state `0`,所以這就是 React 傳遞給第一個更新函數作為 `n` 參數的內容。然後 React 會獲取前一個更新函數的回傳值並將其作為參數 `n` 傳遞給下一個更新函式,以此類推:

| queued update | `n` | returns |
| 更新隊列 | `n` | 回傳 |
|--------------|---------|-----|
| `n => n + 1` | `0` | `0 + 1 = 1` |
| `n => n + 1` | `1` | `1 + 1 = 2` |
| `n => n + 1` | `2` | `2 + 1 = 3` |

React stores `3` as the final result and returns it from `useState`.
React 從 `useState` 存儲 `3` 最為最終的結果。

This is why clicking "+3" in the above example correctly increments the value by 3.
### What happens if you update state after replacing it {/*what-happens-if-you-update-state-after-replacing-it*/}
這就是為什麼在前面的案例中點擊「+3」會正確地將值增加 3。
### 如果在替換後更新 state 會發生什麼 {/*what-happens-if-you-update-state-after-replacing-it*/}

What about this event handler? What do you think `number` will be in the next render?
這個事件處理程序如何?你認為下一個 render 時 `number` 會是什麼?

```js
<button onClick={() => {
Expand Down Expand Up @@ -165,29 +165,29 @@ h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; }

</Sandpack>

Here's what this event handler tells React to do:
以下是這個事件處理程序告訴 React 要做的事情:

1. `setNumber(number + 5)`: `number` is `0`, so `setNumber(0 + 5)`. React adds *"replace with `5`"* to its queue.
2. `setNumber(n => n + 1)`: `n => n + 1` is an updater function. React adds *that function* to its queue.
1. `setNumber(number + 5)``number` `0`,所以 `setNumber(0 + 5)`React 將 _「替換為`5`」_ 添加到其隊列中。
2. `setNumber(n => n + 1)``n => n + 1` 是一個更新函數。React 將該函數添加到其隊列中。

During the next render, React goes through the state queue:
在下一次 render 期間,React 會遍歷隊列的 state

| queued update | `n` | returns |
| 更新隊列 | `n` | 回傳 |
|--------------|---------|-----|
| "replace with `5`" | `0` (unused) | `5` |
| 「替換為 `5` | `0` (未使用) | `5` |
| `n => n + 1` | `5` | `5 + 1 = 6` |

React stores `6` as the final result and returns it from `useState`.
React 從 `useState` 存儲 `6` 作為最終結果。

<Note>

You may have noticed that `setState(5)` actually works like `setState(n => 5)`, but `n` is unused!
你可能已經註意到,`setState(5)` 的運作原理實際上與 `setState(n => 5)` 類似,但未使用 `n`

</Note>

### What happens if you replace state after updating it {/*what-happens-if-you-replace-state-after-updating-it*/}
### 如果在替換後更新 state 會發生什麼 {/*what-happens-if-you-replace-state-after-updating-it*/}

Let's try one more example. What do you think `number` will be in the next render?
讓我們再嘗試一個例子。你認為在下一個 render 時 `number` 會是什麼?

```js
<button onClick={() => {
Expand Down Expand Up @@ -225,60 +225,60 @@ h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; }

</Sandpack>

Here's how React works through these lines of code while executing this event handler:
以下是 React 在執行此事件處理程序時會如何處理這些程式碼:

1. `setNumber(number + 5)`: `number` is `0`, so `setNumber(0 + 5)`. React adds *"replace with `5`"* to its queue.
2. `setNumber(n => n + 1)`: `n => n + 1` is an updater function. React adds *that function* to its queue.
3. `setNumber(42)`: React adds *"replace with `42`"* to its queue.
1. `setNumber(number + 5)``number` `0`,所以 `setNumber(0 + 5)`React 將 _「替換為 `5`」_ 添加到其隊列中。
2. `setNumber(n => n + 1)``n => n + 1`是一個更新函數。React 將該函數添加到其隊列中。
3. `setNumber(42)`React 將 _「替換為 `42`」_ 添加到其隊列中。

During the next render, React goes through the state queue:
在下一次 render 期間,React 會遍歷隊列的 state

| queued update | `n` | returns |
| 更新對列 | `n` | 回傳 |
|--------------|---------|-----|
| "replace with `5`" | `0` (unused) | `5` |
| 「替換為 `5` | `0` (未使用) | `5` |
| `n => n + 1` | `5` | `5 + 1 = 6` |
| "replace with `42`" | `6` (unused) | `42` |
| 「替換為 `42` | `6` (未使用) | `42` |

Then React stores `42` as the final result and returns it from `useState`.
然後 React 從 `useState` 將其存儲的 `42` 作為最終結果。

To summarize, here's how you can think of what you're passing to the `setNumber` state setter:
總而言之,以下是你如何考慮要傳遞給 `setNumber` state 設置器的內容:

* **An updater function** (e.g. `n => n + 1`) gets added to the queue.
* **Any other value** (e.g. number `5`) adds "replace with `5`" to the queue, ignoring what's already queued.
* **更新函數** (例如 `n => n + 1`)被添加到隊列中。
* **任何其他值** (例如 number `5`)都會將「替換為 `5`」添加到隊列中,忽略已經在排隊的內容。

After the event handler completes, React will trigger a re-render. During the re-render, React will process the queue. Updater functions run during rendering, so **updater functions must be [pure](/learn/keeping-components-pure)** and only *return* the result. Don't try to set state from inside of them or run other side effects. In Strict Mode, React will run each updater function twice (but discard the second result) to help you find mistakes.
事件處理程序完成後,React 將觸發重新 render。在重新 render 期間,React 將處理隊列。更新函數在 render 期間運行,因此更新函數必須是[純函數](/learn/keeping-components-pure)並且僅*返回*結果。不要嘗試從它們內部設置 state 或運行其他的副作用。在嚴格模式下,React 將運行每個更新函式兩次(但丟棄第二次的結果)以幫助你發現錯誤。

### Naming conventions {/*naming-conventions*/}
### 命名慣例 {/*naming-conventions*/}

It's common to name the updater function argument by the first letters of the corresponding state variable:
通常透過相應 state 變數的第一個字母來命名更新函式的參數:

```js
setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);
```

If you prefer more verbose code, another common convention is to repeat the full state variable name, like `setEnabled(enabled => !enabled)`, or to use a prefix like `setEnabled(prevEnabled => !prevEnabled)`.
如果你比較喜歡更詳細的命名,另一個常見慣例是重複完整的 state 變數名稱,例如 `setEnabled(enabled => !enabled)`,或是使用前綴例如 `setEnabled(prevEnabled => !prevEnabled)`

<Recap>

* Setting state does not change the variable in the existing render, but it requests a new render.
* React processes state updates after event handlers have finished running. This is called batching.
* To update some state multiple times in one event, you can use `setNumber(n => n + 1)` updater function.
* 設置 state 不會更改現有 render 中的變數,但它會請求新的 render
* React 在執行完事件處理後所執行的 state 更新。稱為批次處理。
* 要在一個事件中多次更新某個 state,可以使用 `setNumber(n => n + 1)` 更新函數。

</Recap>



<Challenges>

#### Fix a request counter {/*fix-a-request-counter*/}
#### 修復請求計數器 {/*fix-a-request-counter*/}

You're working on an art marketplace app that lets the user submit multiple orders for an art item at the same time. Each time the user presses the "Buy" button, the "Pending" counter should increase by one. After three seconds, the "Pending" counter should decrease, and the "Completed" counter should increase.
你正在開發一個藝術品商店應用程式,該應用程式允許用戶同時提交一件藝術品的多個訂單。每次用戶按下「購買」按鈕,「待處理」計數器就會增加一。三秒後,「待處理」數量應減少,「已完成」數量應增加。

However, the "Pending" counter does not behave as intended. When you press "Buy", it decreases to `-1` (which should not be possible!). And if you click fast twice, both counters seem to behave unpredictably.
然而,「待處理」計數的行為並不如預期。當你按下「購買」時,它會減少到 `-1`(這應該是不可能的!)。如果你快速點擊兩次,兩個計數器的行為似乎都不可預測。

Why does this happen? Fix both counters.
為什麼會出現這種情況?修復這兩個計數器。

<Sandpack>

Expand Down Expand Up @@ -322,7 +322,7 @@ function delay(ms) {

<Solution>

Inside the `handleClick` event handler, the values of `pending` and `completed` correspond to what they were at the time of the click event. For the first render, `pending` was `0`, so `setPending(pending - 1)` becomes `setPending(-1)`, which is wrong. Since you want to *increment* or *decrement* the counters, rather than set them to a concrete value determined during the click, you can instead pass the updater functions:
在 `handlerClick` 事件處理程序內部,`padding` 跟 `completed` 的值對應於點擊事件發生時的值。對於第一個 render,`padding` 是 `0`,所以 `setPending(pending - 1)` 變為 `setPending(-1)`,這是錯誤的。由於你想要*遞增*或*遞減*計數器,而不是將它們設置為點擊期間確定的具體值,你可以藉由傳遞更新函式來解決錯誤:

<Sandpack>

Expand Down Expand Up @@ -364,23 +364,23 @@ function delay(ms) {

</Sandpack>

This ensures that when you increment or decrement a counter, you do it in relation to its *latest* state rather than what the state was at the time of the click.
這確保了當你遞增或遞減計數器時,是根據其*最新*的 state 而不是點擊時的 state 來執行此操作。

</Solution>

#### Implement the state queue yourself {/*implement-the-state-queue-yourself*/}
#### 自己實現隊列的 state {/*implement-the-state-queue-yourself*/}

In this challenge, you will reimplement a tiny part of React from scratch! It's not as hard as it sounds.
在本次挑戰中,你將從頭開始重新實現 React 的一小部分!這並不像聽起來那麼難。

Scroll through the sandbox preview. Notice that it shows **four test cases.** They correspond to the examples you've seen earlier on this page. Your task is to implement the `getFinalState` function so that it returns the correct result for each of those cases. If you implement it correctly, all four tests should pass.
滾動瀏覽 sandbox 的預覽。請注意,它顯示了**四個測試案例**。它們與你之前在此頁面上看到的範例相對應。你的任務是實做 `getFinalState` 函式,以便為每種情況回傳正確的結果。如果你實作正確,所有四個測試都應該會通過。

You will receive two arguments: `baseState` is the initial state (like `0`), and the `queue` is an array which contains a mix of numbers (like `5`) and updater functions (like `n => n + 1`) in the order they were added.
你將收到兩個參數:`baseState` 是初始的 state(例如 `0`),`queue` 是一個陣列,其中包含混合的數字(例如 `5`)和更新函式(例如 `n => n + 1`),按添加順序排列。

Your task is to return the final state, just like the tables on this page show!
你的任務是回傳最終 state,就像本頁上的表格所示!

<Hint>

If you're feeling stuck, start with this code structure:
如果你感到困惑,可以從以下程式碼結構開始:

```js
export function getFinalState(baseState, queue) {
Expand All @@ -398,7 +398,7 @@ export function getFinalState(baseState, queue) {
}
```

Fill out the missing lines!
填寫缺失的程式碼!

</Hint>

Expand Down Expand Up @@ -495,7 +495,7 @@ function TestCase({

<Solution>

This is the exact algorithm described on this page that React uses to calculate the final state:
這一頁是 React 用來計算最終 state 的精確算法:

<Sandpack>

Expand Down Expand Up @@ -596,7 +596,7 @@ function TestCase({

</Sandpack>

Now you know how this part of React works!
現在你知道這一部分 React 是如何運作的了!

</Solution>

Expand Down