diff --git a/src/content/learn/synchronizing-with-effects.md b/src/content/learn/synchronizing-with-effects.md
index 24b9f9eb1..962be5abc 100644
--- a/src/content/learn/synchronizing-with-effects.md
+++ b/src/content/learn/synchronizing-with-effects.md
@@ -1,97 +1,97 @@
---
-title: 'Synchronizing with Effects'
+title: 'Effect로 동기화하기'
---
-Some components need to synchronize with external systems. For example, you might want to control a non-React component based on the React state, set up a server connection, or send an analytics log when a component appears on the screen. *Effects* let you run some code after rendering so that you can synchronize your component with some system outside of React.
+일부 컴포넌트에서는 외부 시스템과 동기화해야 할 수 있습니다. 예를 들어 React의 state을 기준으로 React와 상관없는 구성 요소를 제어하거나, 서버 연결을 설정하거나, 구성 요소가 화면에 나타날 때 분석 목적의 로그를 전송할 수도 있습니다. *Effect*를 사용하면 렌더링 후 특정 코드를 실행하여 React 외부의 시스템과 컴포넌트를 동기화할 수 있습니다.
-- What Effects are
-- How Effects are different from events
-- How to declare an Effect in your component
-- How to skip re-running an Effect unnecessarily
-- Why Effects run twice in development and how to fix them
+- Effect가 무엇인지
+- Effect가 이벤트와 다른 점
+- 컴포넌트에서 Effect를 선언하는 방법
+- 불필요한 Effect 재실행을 건너뛰는 방법
+- 개발 중에 Effect가 두 번 실행되는 이유와 해결 방법
-## What are Effects and how are they different from events? {/*what-are-effects-and-how-are-they-different-from-events*/}
+## Effect란 무엇이고 이벤트와는 어떻게 다른가요? {/*what-are-effects-and-how-are-they-different-from-events*/}
-Before getting to Effects, you need to be familiar with two types of logic inside React components:
+Effect에 대해 자세히 알아보기 전에, 컴포넌트 내부의 2가지 로직 유형에 대해 알아야 합니다.
-- **Rendering code** (introduced in [Describing the UI](/learn/describing-the-ui)) lives at the top level of your component. This is where you take the props and state, transform them, and return the JSX you want to see on the screen. [Rendering code must be pure.](/learn/keeping-components-pure) Like a math formula, it should only _calculate_ the result, but not do anything else.
+- **렌더링 코드**([UI 표현하기](/learn/description-the-UI)에 소개됨)를 주관하는 로직은 컴포넌트의 최상단에 위치하며, props와 state를 적절히 변형해 결과적으로 JSX를 반환합니다. [렌더링 코드 로직은 순수해야 합니다.](/learn/keep-components-pure) 수학 공식처럼 결과만 계산해야 하고, 그 외에는 아무것도 하지 말아야 합니다.
-- **Event handlers** (introduced in [Adding Interactivity](/learn/adding-interactivity)) are nested functions inside your components that *do* things rather than just calculate them. An event handler might update an input field, submit an HTTP POST request to buy a product, or navigate the user to another screen. Event handlers contain ["side effects"](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) (they change the program's state) caused by a specific user action (for example, a button click or typing).
+- **이벤트 핸들러**([상호작용 더하기](/learn/adding-interactivity)에 소개됨)는 단순한 계산 용도가 아닌 무언가를 *하는* 컴포넌트 내부의 중첩 함수입니다. 이벤트 핸들러는 입력 필드를 업데이트하거나, 제품을 구입하기 위해 HTTP POST 요청을 보내거나, 사용자를 다른 화면으로 이동시킬 수 있습니다. 이벤트 핸들러에는 특정 사용자 작업(예: 버튼 클릭 또는 입력)으로 인해 발생하는 ["부수 효과"](https://en.wikipedia.org/wiki/Side_effect_(computer_science))(이러한 부수 효과가 프로그램 상태를 변경합니다.)를 포함합니다.
-Sometimes this isn't enough. Consider a `ChatRoom` component that must connect to the chat server whenever it's visible on the screen. Connecting to a server is not a pure calculation (it's a side effect) so it can't happen during rendering. However, there is no single particular event like a click that causes `ChatRoom` to be displayed.
+가끔은 이것으로 충분하지 않습니다. 화면에 보일 때마다 채팅 서버에 접속해야 하는 `ChatRoom` 컴포넌트를 생각해 보세요. 서버에 접속하는 것은 순수한 계산이 아니고 부수 효과를 발생시키기 때문에 렌더링 중에는 할 수 없습니다. 하지만 클릭 한 번으로 `ChatRoom`이 표시되는 특정 이벤트는 하나도 없습니다.
-***Effects* let you specify side effects that are caused by rendering itself, rather than by a particular event.** Sending a message in the chat is an *event* because it is directly caused by the user clicking a specific button. However, setting up a server connection is an *Effect* because it should happen no matter which interaction caused the component to appear. Effects run at the end of a [commit](/learn/render-and-commit) after the screen updates. This is a good time to synchronize the React components with some external system (like network or a third-party library).
+**Effect**는 렌더링 자체에 의해 발생하는 부수 효과를 특정하는 것으로, 특정 이벤트가 아닌 렌더링에 의해 직접 발생합니다. 채팅에서 메시지를 보내는 것은 *이벤트*입니다. 왜냐하면 이것은 사용자가 특정 버튼을 클릭함에 따라 직접적으로 발생합니다. 그러나 서버 연결 설정은 *Effect*입니다. 왜냐하면 이것은 컴포넌트의 표시를 주관하는 어떤 상호 작용과도 상관없이 발생해야 합니다. Effect는 [커밋](/learn/render-and-commit)이 끝난 후에 화면 업데이트가 이루어지고 나서 실행됩니다. 이 시점이 React 컴포넌트를 외부 시스템(네트워크 또는 써드파티 라이브러리와 같은)과 동기화하기 좋은 타이밍입니다.
-Here and later in this text, capitalized "Effect" refers to the React-specific definition above, i.e. a side effect caused by rendering. To refer to the broader programming concept, we'll say "side effect".
+이 텍스트에서의 대문자 "Effect"는 위에서 언급한 React에 특화된 정의를 나타내며, 곧 렌더링에 의한 부수 효과를 의미합니다. 보다 일반적인 프로그래밍 개념을 언급할 때에는 "부수 효과"라고 말하겠습니다.
-## You might not need an Effect {/*you-might-not-need-an-effect*/}
+## Effect가 필요 없을지도 모릅니다 {/*you-might-not-need-an-effect*/}
-**Don't rush to add Effects to your components.** Keep in mind that Effects are typically used to "step out" of your React code and synchronize with some *external* system. This includes browser APIs, third-party widgets, network, and so on. If your Effect only adjusts some state based on other state, [you might not need an Effect.](/learn/you-might-not-need-an-effect)
+**컴포넌트에 Effect를 무작정 추가하지 마세요.** Effect는 주로 React 코드를 벗어난 특정 *외부* 시스템과 동기화하기 위해 사용됩니다. 이는 브라우저 API, 써드파티 위젯, 네트워크 등을 포함합니다. 만약 당신의 Effect가 단순히 다른 상태에 기반하여 일부 상태를 조정하는 경우에는 [Effect가 필요하지 않을 수 있습니다.](/learn/you-might-not-need-an-effect)
-## How to write an Effect {/*how-to-write-an-effect*/}
+## Effect를 작성하는 법 {/*how-to-write-an-effect*/}
-To write an Effect, follow these three steps:
+Effect를 작성하기 위해서는 다음 세 단계를 따릅니다.
-1. **Declare an Effect.** By default, your Effect will run after every render.
-2. **Specify the Effect dependencies.** Most Effects should only re-run *when needed* rather than after every render. For example, a fade-in animation should only trigger when a component appears. Connecting and disconnecting to a chat room should only happen when the component appears and disappears, or when the chat room changes. You will learn how to control this by specifying *dependencies.*
-3. **Add cleanup if needed.** Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, "connect" needs "disconnect", "subscribe" needs "unsubscribe", and "fetch" needs either "cancel" or "ignore". You will learn how to do this by returning a *cleanup function*.
+1. **Effect 선언.** 기본적으로 Effect는 모든 렌더링 후에 실행됩니다.
+2. **Effect 의존성 지정.** 대부분의 Effect는 모든 렌더링 후가 아닌 *필요할 때*만 다시 실행되어야 합니다. 예를 들어, 페이드 인 애니메이션은 컴포넌트가 나타날 때에만 트리거 되어야 합니다. 채팅 방에 연결, 연결 해제하는 것은 컴포넌트가 나타나거나 사라질 때 또는 채팅 방이 변경될 때만 발생해야 합니다. *의존성*을 지정하여 이를 제어하는 방법을 배우게 될 것입니다.
+3. **필요한 경우 클린업 함수 추가.** 일부 Effect는 수행 중이던 작업을 중지, 취소 또는 정리하는 방법을 지정해야 할 수 있습니다. 예를 들어, "연결"은 "연결 해제"가 필요하며, "구독"은 "구독 취소"가 필요하고, "불러오기(fetch)"는 "취소" 또는 "무시"가 필요합니다. 이런 경우에 Effect에서 *클린업 함수(cleanup function)*를 반환하여 어떻게 수행하는지 배우게 될 것입니다.
-Let's look at each of these steps in detail.
+각 단계를 자세히 살펴보겠습니다.
-### Step 1: Declare an Effect {/*step-1-declare-an-effect*/}
+### 1단계: Effect 선언하기 {/*step-1-declare-an-effect*/}
-To declare an Effect in your component, import the [`useEffect` Hook](/reference/react/useEffect) from React:
+컴포넌트 내에서 Effect를 선언하려면, React에서 [`useEffect` 훅](/reference/react/useEffect)을 import 하세요.
```js
import { useEffect } from 'react';
```
-Then, call it at the top level of your component and put some code inside your Effect:
+그런 다음, 컴포넌트의 최상위 레벨에서 호출하고 Effect 내부에 코드를 넣으세요.
```js {2-4}
function MyComponent() {
useEffect(() => {
- // Code here will run after *every* render
+ // 이곳의 코드는 *모든* 렌더링 후에 실행됩니다
});
return
;
}
```
-Every time your component renders, React will update the screen *and then* run the code inside `useEffect`. In other words, **`useEffect` "delays" a piece of code from running until that render is reflected on the screen.**
+컴포넌트가 렌더링 될 때마다 React는 화면을 업데이트한 다음 `useEffect` 내부의 코드를 실행합니다. 다시 말해, **`useEffect`는 화면에 렌더링이 반영될 때까지 코드 실행을 "지연"시킵니다.**
-Let's see how you can use an Effect to synchronize with an external system. Consider a `` React component. It would be nice to control whether it's playing or paused by passing an `isPlaying` prop to it:
+이제 외부 시스템과 동기화하기 위해 어떻게 Effect를 사용할 수 있는지 알아보겠습니다. ``라는 React 컴포넌트를 살펴보겠습니다. 이 컴포넌트를 `isPlaying`이라는 props를 통해 재생 중인지 일시 정지 상태인지 제어하는 것이 좋아 보이네요.
```js
;
```
-Your custom `VideoPlayer` component renders the built-in browser [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) tag:
+커스텀 `VideoPlayer` 컴포넌트는 내장 브라우저 [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) 태그를 렌더링 합니다.
```js
function VideoPlayer({ src, isPlaying }) {
- // TODO: do something with isPlaying
+ // TODO: isPlaying을 활용하여 무언가 수행하기
return ;
}
```
-However, the browser `` tag does not have an `isPlaying` prop. The only way to control it is to manually call the [`play()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) and [`pause()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause) methods on the DOM element. **You need to synchronize the value of `isPlaying` prop, which tells whether the video _should_ currently be playing, with calls like `play()` and `pause()`.**
+그러나 `` 태그에는 `isPlaying` prop이 없습니다. 이를 제어하는 유일한 방법은 DOM 요소에서 수동으로 [`play()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) 및 [`pause()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause) 메서드를 호출하는 것입니다. **`isPlaying` prop의 값(현재 비디오가 재생 중인지 여부)을 `play()` 및 `pause()`와 같은 호출과 동기화해야 합니다.**
-We'll need to first [get a ref](/learn/manipulating-the-dom-with-refs) to the `` DOM node.
+먼저 `` DOM 노드의 [ref를 가져와야](/learn/manipulating-the-dom-with-refs) 합니다.
-You might be tempted to try to call `play()` or `pause()` during rendering, but that isn't correct:
+`play()` 또는 `pause()`를 렌더링 중에 호출하려고 시도할 수 있겠지만, 이는 올바른 접근이 아닙니다.
@@ -102,9 +102,9 @@ function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
if (isPlaying) {
- ref.current.play(); // Calling these while rendering isn't allowed.
+ ref.current.play(); // 렌더링 중에 이를 호출하는 것이 허용되지 않습니다.
} else {
- ref.current.pause(); // Also, this crashes.
+ ref.current.pause(); // 역시 이렇게 호출하면 바로 위의 호출과 충돌이 발생합니다.
}
return ;
@@ -115,7 +115,7 @@ export default function App() {
return (
<>
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? '일시정지' : '재생'}
-The reason this code isn't correct is that it tries to do something with the DOM node during rendering. In React, [rendering should be a pure calculation](/learn/keeping-components-pure) of JSX and should not contain side effects like modifying the DOM.
+이 코드가 올바르지 않은 이유는 렌더링 중에 DOM 노드를 조작하려고 시도하기 때문입니다. React에서는 [렌더링이 JSX의 순수한 계산](/learn/keeping-components-pure)이어야 하며, DOM 수정과 같은 부수 효과를 포함해서는 안됩니다.
-Moreover, when `VideoPlayer` is called for the first time, its DOM does not exist yet! There isn't a DOM node yet to call `play()` or `pause()` on, because React doesn't know what DOM to create until you return the JSX.
+게다가, 처음으로 `VideoPlayer`가 호출될 때 해당 DOM이 아직 존재하지 않습니다! React는 컴포넌트가 JSX를 반환할 때까지 어떤 DOM을 생성할지 모르기 때문에 `play()` 또는 `pause()`를 호출할 DOM 노드가 아직 없습니다.
-The solution here is to **wrap the side effect with `useEffect` to move it out of the rendering calculation:**
+해결책은 **부수 효과를 렌더링 연산에서 분리하기 위해 `useEffect`로 감싸는 것입니다.**
```js {6,12}
import { useEffect, useRef } from 'react';
@@ -157,11 +157,11 @@ function VideoPlayer({ src, isPlaying }) {
}
```
-By wrapping the DOM update in an Effect, you let React update the screen first. Then your Effect runs.
+DOM 업데이트를 Effect로 감싸면 React가 화면을 업데이트한 다음에 Effect가 실행됩니다.
-When your `VideoPlayer` component renders (either the first time or if it re-renders), a few things will happen. First, React will update the screen, ensuring the `` tag is in the DOM with the right props. Then React will run your Effect. Finally, your Effect will call `play()` or `pause()` depending on the value of `isPlaying`.
+`VideoPlayer` 컴포넌트가 렌더링 될 때(처음 호출하거나 다시 렌더링 할 때) 몇 가지 일이 발생합니다. 먼저 React는 화면을 업데이트하여 `` 태그가 올바른 속성과 함께 DOM에 있는지 확인합니다. 그런 다음 React는 Effect를 실행합니다. 마지막으로 Effect에서는 `isPlaying` 값에 따라 `play()` 또는 `pause()`를 호출합니다.
-Press Play/Pause multiple times and see how the video player stays synchronized to the `isPlaying` value:
+"재생" 또는 "일시 정지"를 여러 번 눌러보고 비디오 플레이어가 `isPlaying` 값과 동기화되는지 확인해 보세요.
@@ -187,7 +187,7 @@ export default function App() {
return (
<>
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? '일시 정지' : '재생'}
-In this example, the "external system" you synchronized to React state was the browser media API. You can use a similar approach to wrap legacy non-React code (like jQuery plugins) into declarative React components.
+이 예제에서 React 상태와 동기화된 "외부 시스템"은 브라우저 미디어 API였습니다. 이와 비슷한 접근 방식으로 React가 아닌 레거시 코드(예: jQuery 플러그인)를 선언적인 React 컴포넌트로 감싸는 데에도 사용할 수 있습니다.
-Note that controlling a video player is much more complex in practice. Calling `play()` may fail, the user might play or pause using the built-in browser controls, and so on. This example is very simplified and incomplete.
+실제로 비디오 플레이어를 제어하는 것은 훨씬 복잡합니다. play()를 호출하는 것이 실패할 수 있으며, 사용자는 컴포넌트의 UI가 아닌 브라우저 내장 컨트롤을 사용하여 동영상을 재생 또는 일시 정지할 수 있습니다. 이 예제는 매우 단순화되었고 불완전한 것임을 유의해주세요.
-By default, Effects run after *every* render. This is why code like this will **produce an infinite loop:**
+기본적으로, Effect는 *모든* 렌더링 후에 실행됩니다. 이러한 이유로 다음과 같은 코드는 **무한 루프를 만들어낼** 것입니다.
```js
const [count, setCount] = useState(0);
@@ -220,20 +220,20 @@ useEffect(() => {
});
```
-Effects run as a *result* of rendering. Setting state *triggers* rendering. Setting state immediately in an Effect is like plugging a power outlet into itself. The Effect runs, it sets the state, which causes a re-render, which causes the Effect to run, it sets the state again, this causes another re-render, and so on.
+Effect는 렌더링의 *결과*로 실행됩니다. state를 설정하면 렌더링이 *트리거*됩니다. Effect 안에서 즉시 상태를 설정하는 것은 기계의 전원 플러그를 기계 그 자체에 연결하는 것과 비슷합니다. Effect가 실행되고 상태가 설정되면 재렌더링이 발생하고, Effect가 다시 실행되고 상태가 설정되면 또 다른 재렌더링이 발생하며, 이런 식으로 계속됩니다.
-Effects should usually synchronize your components with an *external* system. If there's no external system and you only want to adjust some state based on other state, [you might not need an Effect.](/learn/you-might-not-need-an-effect)
+Effect는 일반적으로 컴포넌트를 *외부* 시스템과 동기화하는 데 사용됩니다. 외부 시스템이 없고 다른 상태에 기반하여 상태를 조정하려는 경우에는 [Effect가 필요하지 않을 수 있습니다.](/learn/you-might-not-need-an-effect)
-### Step 2: Specify the Effect dependencies {/*step-2-specify-the-effect-dependencies*/}
+### 2단계: Effect의 의존성 지정하기 {/*step-2-specify-the-effect-dependencies*/}
-By default, Effects run after *every* render. Often, this is **not what you want:**
+기본적으로, Effect는 *모든* 렌더링 후에 실행됩니다. 이는 종종 **원하는 동작이 아닐 수 있습니다:**
-- Sometimes, it's slow. Synchronizing with an external system is not always instant, so you might want to skip doing it unless it's necessary. For example, you don't want to reconnect to the chat server on every keystroke.
-- Sometimes, it's wrong. For example, you don't want to trigger a component fade-in animation on every keystroke. The animation should only play once when the component appears for the first time.
+- 때때로 느릴 수 있습니다. 외부 시스템과 동기화하는 것이 항상 즉시 이루어지지 않기 때문에 필요하지 않을 경우에는 실행을 건너뛰고 싶을 수 있습니다. 예를 들어, 모든 키 입력마다 채팅 서버에 다시 연결하길 원하지 않을 것입니다.
+- 때때로 잘못될 수 있습니다. 예를 들어, 모든 키 입력마다 컴포넌트 fade-in 애니메이션을 트리거하길 원하지 않을 것입니다. 애니메이션은 컴포넌트가 처음 나타날 때에만 한 번 실행되어야 합니다.
-To demonstrate the issue, here is the previous example with a few `console.log` calls and a text input that updates the parent component's state. Notice how typing causes the Effect to re-run:
+이 문제를 설명하기 위해 이전 예제에 몇 가지 `console.log` 호출과 부모 컴포넌트의 상태를 업데이트하는 텍스트 입력을 추가한 예제를 살펴보겠습니다. 입력할 때 Effect가 다시 실행되는 것을 주목하세요.
@@ -245,10 +245,10 @@ function VideoPlayer({ src, isPlaying }) {
useEffect(() => {
if (isPlaying) {
- console.log('Calling video.play()');
+ console.log('video.play() 호출');
ref.current.play();
} else {
- console.log('Calling video.pause()');
+ console.log('video.pause() 호출');
ref.current.pause();
}
});
@@ -263,7 +263,7 @@ export default function App() {
<>
setText(e.target.value)} />
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? '일시 정지' : '재생'}
-You can tell React to **skip unnecessarily re-running the Effect** by specifying an array of *dependencies* as the second argument to the `useEffect` call. Start by adding an empty `[]` array to the above example on line 14:
+React에게 Effect를 **불필요하게 다시 실행하지 않도록 지시**하려면 `useEffect` 호출의 두 번째 인자로 *의존성(dependencies)* 배열을 지정하세요. 먼저 위의 예제에 빈 `[]` 배열을 14번째 줄에 추가하면 됩니다.
```js {3}
useEffect(() => {
@@ -289,7 +289,7 @@ You can tell React to **skip unnecessarily re-running the Effect** by specifying
}, []);
```
-You should see an error saying `React Hook useEffect has a missing dependency: 'isPlaying'`:
+`'isPlaying'`에 대한 의존성이 누락되었다는 오류가 표시될 것입니다.
@@ -301,13 +301,13 @@ function VideoPlayer({ src, isPlaying }) {
useEffect(() => {
if (isPlaying) {
- console.log('Calling video.play()');
+ console.log('video.play() 호출');
ref.current.play();
} else {
- console.log('Calling video.pause()');
+ console.log('video.pause() 호출');
ref.current.pause();
}
- }, []); // This causes an error
+ }, []); // 이 코드는 에러를 유발합니다
return ;
}
@@ -319,7 +319,7 @@ export default function App() {
<>
setText(e.target.value)} />
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? '일시 정지' : '재생'}
-The problem is that the code inside of your Effect *depends on* the `isPlaying` prop to decide what to do, but this dependency was not explicitly declared. To fix this issue, add `isPlaying` to the dependency array:
+문제는 Effect 내부의 코드가 어떤 작업을 수행할지 결정하기 위해 `isPlaying` prop에 *의존*하지만 이 의존성이 명시적으로 선언되지 않았다는 것입니다. 이 문제를 해결하려면 의존성 배열에 `isPlaying`을 추가하세요.
```js {2,7}
useEffect(() => {
- if (isPlaying) { // It's used here...
+ if (isPlaying) { // 여기서 사용하니까...
// ...
} else {
// ...
}
- }, [isPlaying]); // ...so it must be declared here!
+ }, [isPlaying]); // ...여기에 선언되어야겠네!
```
-Now all dependencies are declared, so there is no error. Specifying `[isPlaying]` as the dependency array tells React that it should skip re-running your Effect if `isPlaying` is the same as it was during the previous render. With this change, typing into the input doesn't cause the Effect to re-run, but pressing Play/Pause does:
+이제 모든 의존성이 의존성 배열 안에 선언되어 오류가 없을 것입니다. 의존성 배열로 `[isPlaying]`을 지정하면 React에게 이전 렌더링 중에 `isPlaying`이 이전과 동일하다면 Effect를 다시 실행하지 않도록 해야 한다고 알려줍니다. 이 변경으로 입력란에 입력을 입력하면 Effect가 다시 실행되지 않고, 재생/일시 정지 버튼을 누르면 Effect가 실행됩니다.
@@ -361,10 +361,10 @@ function VideoPlayer({ src, isPlaying }) {
useEffect(() => {
if (isPlaying) {
- console.log('Calling video.play()');
+ console.log('video.play() 호출');
ref.current.play();
} else {
- console.log('Calling video.pause()');
+ console.log('video.pause() 호출');
ref.current.pause();
}
}, [isPlaying]);
@@ -379,7 +379,7 @@ export default function App() {
<>
setText(e.target.value)} />
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? '일시 정지' : '재생'}
-The dependency array can contain multiple dependencies. React will only skip re-running the Effect if *all* of the dependencies you specify have exactly the same values as they had during the previous render. React compares the dependency values using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. See the [`useEffect` reference](/reference/react/useEffect#reference) for details.
+의존성 배열에는 여러 개의 종속성을 포함할 수 있습니다. React는 지정한 모든 종속성이 이전 렌더링의 그것과 정확히 동일한 값을 가진 경우에만 Effect를 다시 실행하지 않습니다. React는 [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) 비교를 사용하여 종속성 값을 비교합니다. 자세한 내용은 [`useEffect` 참조 문서](/reference/react/useEffect#reference)를 참조하세요.
-**Notice that you can't "choose" your dependencies.** You will get a lint error if the dependencies you specified don't match what React expects based on the code inside your Effect. This helps catch many bugs in your code. If you don't want some code to re-run, [*edit the Effect code itself* to not "need" that dependency.](/learn/lifecycle-of-reactive-effects#what-to-do-when-you-dont-want-to-re-synchronize)
+**의존성을 "선택"할 수 없다는 점에 유의하세요.** 의존성 배열에 지정한 종속성이 Effect 내부의 코드를 기반으로 React가 기대하는 것과 일치하지 않으면 린트 에러가 발생합니다. 이를 통해 코드 내의 많은 버그를 잡을 수 있습니다. 코드가 다시 실행되길 원하지 않는 경우, [*Effect 내부를 수정하여* 그 종속성이 "필요"하지 않도록 만드세요.](/learn/lifecycle-of-reactive-effects#what-to-do-when-you-dont-want-to-re-synchronize)
-The behaviors without the dependency array and with an *empty* `[]` dependency array are different:
+의존성 배열이 없는 경우와 *빈* `[]` 의존성 배열이 있는 경우의 동작이 다릅니다.
```js {3,7,11}
useEffect(() => {
- // This runs after every render
+ // 모든 렌더링 후에 실행됩니다
});
useEffect(() => {
- // This runs only on mount (when the component appears)
+ // 마운트될 때만 실행됩니다 (컴포넌트가 나타날 때)
}, []);
useEffect(() => {
- // This runs on mount *and also* if either a or b have changed since the last render
+ // 마운트될 때 실행되며, *또한* 렌더링 이후에 a 또는 b 중 하나라도 변경된 경우에도 실행됩니다
}, [a, b]);
```
-We'll take a close look at what "mount" means in the next step.
+다음 단계에서 "마운트(mount)"가 무엇을 의미하는지 자세히 살펴보겠습니다.
-#### Why was the ref omitted from the dependency array? {/*why-was-the-ref-omitted-from-the-dependency-array*/}
+#### 왜 ref는 의존성 배열에서 생략해도 되나요? {/*why-was-the-ref-omitted-from-the-dependency-array*/}
-This Effect uses _both_ `ref` and `isPlaying`, but only `isPlaying` is declared as a dependency:
+이 Effect는 `ref`와 `isPlaying`을 _모두_ 사용하지만, 의존성 배열 안에 선언된 것은 `isPlaying` 뿐입니다.
```js {9}
function VideoPlayer({ src, isPlaying }) {
@@ -441,7 +441,7 @@ function VideoPlayer({ src, isPlaying }) {
}, [isPlaying]);
```
-This is because the `ref` object has a *stable identity:* React guarantees [you'll always get the same object](/reference/react/useRef#returns) from the same `useRef` call on every render. It never changes, so it will never by itself cause the Effect to re-run. Therefore, it does not matter whether you include it or not. Including it is fine too:
+이것은 `ref` 객체가 *안정된 식별성(stable identity)*을 가지기 때문입니다. React는 동일한 `useRef` 호출에서 항상 [같은 객체를 얻을 수 있음을](/reference/react/useRef#returns) 보장합니다. 이 객체는 절대 변경되지 않기 때문에 자체적으로 Effect를 다시 실행시키지 않습니다. 따라서 `ref`는 의존성 배열에 포함하든 포함하지 않든 상관없습니다. 포함해도 문제없습니다.
```js {9}
function VideoPlayer({ src, isPlaying }) {
@@ -455,17 +455,17 @@ function VideoPlayer({ src, isPlaying }) {
}, [isPlaying, ref]);
```
-The [`set` functions](/reference/react/useState#setstate) returned by `useState` also have stable identity, so you will often see them omitted from the dependencies too. If the linter lets you omit a dependency without errors, it is safe to do.
+[`useState`](/reference/react/useState#setstate)로 반환되는 `set` 함수들도 안정된 식별성을 가지기 때문에, 종종 이러한 함수들도 의존성에서 생략되는 것을 볼 수 있습니다. 린터가 의존성을 생략해도 오류를 표시하지 않는다면 그렇게 해도 안전합니다.
-Omitting always-stable dependencies only works when the linter can "see" that the object is stable. For example, if `ref` was passed from a parent component, you would have to specify it in the dependency array. However, this is good because you can't know whether the parent component always passes the same ref, or passes one of several refs conditionally. So your Effect _would_ depend on which ref is passed.
+안정된 식별성을 가진 의존성을 생략하는 것은 린터가 해당 객체가 안정적임을 "알 수" 있는 경우에만 작동합니다. 예를 들어, `ref`가 부모 컴포넌트에서 전달되었다면, 의존성 배열에 명시해야 합니다. 이것은 좋은 접근 방식입니다. 왜냐하면 부모 컴포넌트가 항상 동일한 ref를 전달하는지 또는 여러 ref 중 하나를 조건부로 전달하는지 알 수 없기 때문입니다. 따라서 당신의 Effect는 전달되는 ref에 따라 달라질 것입니다.
-### Step 3: Add cleanup if needed {/*step-3-add-cleanup-if-needed*/}
+### 3단계: 필요하다면 클린업을 추가하세요 {/*step-3-add-cleanup-if-needed*/}
-Consider a different example. You're writing a `ChatRoom` component that needs to connect to the chat server when it appears. You are given a `createConnection()` API that returns an object with `connect()` and `disconnect()` methods. How do you keep the component connected while it is displayed to the user?
+다른 예시를 고려해 보겠습니다. 사용자에게 표시될 때 채팅 서버에 연결해야 하는 `ChatRoom` 컴포넌트를 작성 중입니다. `createConnection()` API가 주어지며, 이 API는 `connect()` 및 `disconnect()` 메서드를 가진 객체를 반환합니다. 사용자에게 표시되는 동안 컴포넌트가 채팅 서버와의 연결을 유지하려면 어떻게 해야 할까요?
-Start by writing the Effect logic:
+먼저 Effect를 작성해 보겠습니다.
```js
useEffect(() => {
@@ -474,7 +474,7 @@ useEffect(() => {
});
```
-It would be slow to connect to the chat after every re-render, so you add the dependency array:
+매번 재렌더링 후에 채팅 서버에 연결하는 것은 느리므로 의존성 배열을 추가합니다.
```js {4}
useEffect(() => {
@@ -483,9 +483,9 @@ useEffect(() => {
}, []);
```
-**The code inside the Effect does not use any props or state, so your dependency array is `[]` (empty). This tells React to only run this code when the component "mounts", i.e. appears on the screen for the first time.**
+**Effect 내부의 코드는 어떠한 props나 상태도 사용하지 않으므로, 의존성 배열은 `[]` (빈 배열)입니다. 이는 React에게 이 코드를 컴포넌트가 "마운트"될 때만 실행하도록 알려줍니다. 즉, 화면에 처음으로 나타날 때에만 실행되게 됩니다.**
-Let's try running this code:
+이 코드를 실행해 보겠습니다.
@@ -498,19 +498,19 @@ export default function ChatRoom() {
const connection = createConnection();
connection.connect();
}, []);
- return Welcome to the chat! ;
+ return 채팅에 오신걸 환영합니다! ;
}
```
```js chat.js
export function createConnection() {
- // A real implementation would actually connect to the server
+ // 실제 구현은 정말로 채팅 서버에 연결하는 것이 되어야 합니다.
return {
connect() {
- console.log('✅ Connecting...');
+ console.log('✅ 연결 중...');
},
disconnect() {
- console.log('❌ Disconnected.');
+ console.log('❌ 연결이 끊겼습니다.');
}
};
}
@@ -522,15 +522,15 @@ input { display: block; margin-bottom: 20px; }
-This Effect only runs on mount, so you might expect `"✅ Connecting..."` to be printed once in the console. **However, if you check the console, `"✅ Connecting..."` gets printed twice. Why does it happen?**
+이 Effect는 마운트될 때만 실행되므로 콘솔에 "✅ 연결 중..."이 한 번 출력될 것으로 예상할 수 있습니다. 그러나 콘솔을 확인해 보면 "✅ 연결 중..."이 두 번 출력됩니다. 왜 그럴까요?
-Imagine the `ChatRoom` component is a part of a larger app with many different screens. The user starts their journey on the `ChatRoom` page. The component mounts and calls `connection.connect()`. Then imagine the user navigates to another screen--for example, to the Settings page. The `ChatRoom` component unmounts. Finally, the user clicks Back and `ChatRoom` mounts again. This would set up a second connection--but the first connection was never destroyed! As the user navigates across the app, the connections would keep piling up.
+ChatRoom 컴포넌트가 여러 화면으로 구성된 큰 앱의 일부라고 가정해 보겠습니다. 사용자가 ChatRoom 페이지에서 여정을 시작합니다. 컴포넌트가 마운트되고 connection.connect()를 호출합니다. 그런 다음 사용자가 다른 화면으로 이동한다고 상상해보세요. 예를 들어, 설정 페이지로 이동할 수 있습니다. ChatRoom 컴포넌트가 언마운트됩니다. 마지막으로 사용자가 뒤로 가기 버튼을 클릭하고 ChatRoom이 다시 마운트됩니다. 이렇게 되면 두 번째 연결이 설정되지만 첫 번째 연결은 종료되지 않았습니다! 사용자가 앱을 탐색하는 동안 연결은 종료되지 않고 계속 쌓일 것입니다.
-Bugs like this are easy to miss without extensive manual testing. To help you spot them quickly, in development React remounts every component once immediately after its initial mount.
+이와 같은 버그는 앱의 이곳저곳을 수동으로 테스트해보지 않으면 놓치기 쉽습니다. 이러한 문제를 빠르게 파악할 수 있도록 React는 개발 모드에서 초기 마운트 후 모든 컴포넌트를 한 번 다시 마운트합니다.
-Seeing the `"✅ Connecting..."` log twice helps you notice the real issue: your code doesn't close the connection when the component unmounts.
+"✅ 연결 중..." 로그가 두 번 출력되는 것을 보면 결국 무엇이 문제인지 알 수 있습니다. 컴포넌트가 언마운트될 때 연결을 닫지 않는 문제가 바로 그것이죠.
-To fix the issue, return a *cleanup function* from your Effect:
+이 문제를 해결하려면 Effect에서 클린업 함수를 반환하면 됩니다.
```js {4-6}
useEffect(() => {
@@ -542,7 +542,7 @@ To fix the issue, return a *cleanup function* from your Effect:
}, []);
```
-React will call your cleanup function each time before the Effect runs again, and one final time when the component unmounts (gets removed). Let's see what happens when the cleanup function is implemented:
+React는 Effect가 다시 실행되기 전마다 클린업 함수를 호출하고, 컴포넌트가 언마운트(제거)될 때에도 마지막으로 호출합니다. 클린업 함수가 구현된 경우 어떤 일이 일어나는지 살펴보겠습니다.
@@ -556,19 +556,19 @@ export default function ChatRoom() {
connection.connect();
return () => connection.disconnect();
}, []);
- return Welcome to the chat! ;
+ return 채팅에 오신걸 환영합니다! ;
}
```
```js chat.js
export function createConnection() {
- // A real implementation would actually connect to the server
+ // 실제 구현은 정말로 채팅 서버에 연결하는 것이 되어야 합니다.
return {
connect() {
- console.log('✅ Connecting...');
+ console.log('✅ 연결 중...');
},
disconnect() {
- console.log('❌ Disconnected.');
+ console.log('❌ 연결 해제됨');
}
};
}
@@ -580,27 +580,27 @@ input { display: block; margin-bottom: 20px; }
-Now you get three console logs in development:
+이제 개발 모드에서 세 개의 콘솔 로그를 확인할 수 있습니다:
-1. `"✅ Connecting..."`
-2. `"❌ Disconnected."`
-3. `"✅ Connecting..."`
+1. `"✅ 연결 중..."`
+2. `"❌ 연결 해제됨"`
+3. `"✅ 연결 중..."`
-**This is the correct behavior in development.** By remounting your component, React verifies that navigating away and back would not break your code. Disconnecting and then connecting again is exactly what should happen! When you implement the cleanup well, there should be no user-visible difference between running the Effect once vs running it, cleaning it up, and running it again. There's an extra connect/disconnect call pair because React is probing your code for bugs in development. This is normal--don't try to make it go away!
+**이것이 개발 모드에서 올바른 동작입니다.** 컴포넌트를 다시 마운트함으로써 React는 사용자가 다른 부분을 탐색하고 다시 돌아와도 코드가 깨지지 않을 것임을 확인합니다. 연결을 해제하고 다시 연결하는 것이 바로 일어나는 일입니다! 클린업을 잘 구현하면 Effect를 한 번 실행하는 것과 실행, 클린업, 이후 다시 실행하는 것 사이에 사용자에게 보이는 차이가 없어야 합니다. 개발 중에는 연결/해제 호출이 하나 더 있는데, 이는 React가 개발 중에 코드를 검사하여 버그를 찾는 것입니다. 이것은 정상적인 동작입니다 - 이것을 없애려고 하지 마세요!
-**In production, you would only see `"✅ Connecting..."` printed once.** Remounting components only happens in development to help you find Effects that need cleanup. You can turn off [Strict Mode](/reference/react/StrictMode) to opt out of the development behavior, but we recommend keeping it on. This lets you find many bugs like the one above.
+**배포 환경에서는 `"✅ 연결 중..."`이 한 번만 출력됩니다.** 컴포넌트를 다시 마운트하는 것은 개발 중에만 발생하며 클린업이 필요한 Effect를 찾아주는 데 도움을 줍니다. 개발 동작에서 벗어나려면 [Strict Mode](/reference/react/StrictMode)를 끄는 것도 가능하지만, 켜둘 것을 권장합니다. 이렇게 하면 위와 같은 많은 버그를 찾을 수 있습니다.
-## How to handle the Effect firing twice in development? {/*how-to-handle-the-effect-firing-twice-in-development*/}
+## 개발 중에 Effect가 두 번 실행되는 경우를 다루는 방법 {/*개발-중에-effect가-두-번-실행되는-경우를-다루는-방법*/}
-React intentionally remounts your components in development to find bugs like in the last example. **The right question isn't "how to run an Effect once", but "how to fix my Effect so that it works after remounting".**
+React는 마지막 예시와 같은 버그를 찾기 위해 개발 중에 컴포넌트를 명시적으로 다시 마운트합니다. **"Effect를 한 번 실행하는 방법"이 아니라 "어떻게 Effect가 다시 마운트된 후에도 작동하도록 고칠 것인가"라는 것이 옳은 질문입니다.**
-Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn't be able to distinguish between the Effect running once (as in production) and a _setup → cleanup → setup_ sequence (as you'd see in development).
+일반적으로 정답은 클린업 함수를 구현하는 것입니다. 클린업 함수는 Effect가 수행하던 작업을 중단하거나 되돌리는 역할을 합니다. 기본 원칙은 사용자가 Effect가 한 번 실행되는 것(배포 환경과 같이)과 _설정 → 클린업 → 설정_ 순서(개발 중에 볼 수 있는 것) 간에 차이를 느끼지 못해야 합니다.
-Most of the Effects you'll write will fit into one of the common patterns below.
+작성할 대부분의 Effect는 아래의 일반적인 패턴 중 하나에 해당될 것입니다.
-### Controlling non-React widgets {/*controlling-non-react-widgets*/}
+### React로 작성되지 않은 위젯 제어하기 {/*controlling-non-react-widgets*/}
-Sometimes you need to add UI widgets that aren't written to React. For example, let's say you're adding a map component to your page. It has a `setZoomLevel()` method, and you'd like to keep the zoom level in sync with a `zoomLevel` state variable in your React code. Your Effect would look similar to this:
+가끔씩 React로 작성되지 않은 UI 위젯을 추가해야 할 때가 있습니다. 예를 들어, 페이지에 지도 컴포넌트를 추가한다고 가정해 보겠습니다. 이 지도 컴포넌트에는 `setZoomLevel()` 메서드가 있으며, `zoomLevel` state 변수와 동기화하려고 할 것입니다. Effect는 다음과 비슷할 것입니다.
```js
useEffect(() => {
@@ -609,9 +609,9 @@ useEffect(() => {
}, [zoomLevel]);
```
-Note that there is no cleanup needed in this case. In development, React will call the Effect twice, but this is not a problem because calling `setZoomLevel` twice with the same value does not do anything. It may be slightly slower, but this doesn't matter because it won't remount needlessly in production.
+이 경우에는 클린업이 필요하지 않음을 유의하세요. 개발 모드에서 React는 Effect를 두 번 호출하지만, 동일한 값을 가지고 `setZoomLevel`을 두 번 호출하는 것은 아무런 문제가 되지 않습니다. 약간 느릴 수 있지만, 이것은 제품 환경에서 불필요하게 다시 마운트되지 않기 때문에 문제가 되지 않습니다.
-Some APIs may not allow you to call them twice in a row. For example, the [`showModal`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) method of the built-in [``](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement) element throws if you call it twice. Implement the cleanup function and make it close the dialog:
+일부 API는 연속해서 두 번 호출하는 것을 허용하지 않을 수도 있습니다. 예를 들어 내장된 [``](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement) 요소의 [`showModal`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) 메서드는 두 번 호출하면 예외를 던집니다. 클린업 함수를 구현하고 이 함수에서 대화 상자를 닫도록 만들어보세요.
```js {4}
useEffect(() => {
@@ -621,11 +621,11 @@ useEffect(() => {
}, []);
```
-In development, your Effect will call `showModal()`, then immediately `close()`, and then `showModal()` again. This has the same user-visible behavior as calling `showModal()` once, as you would see in production.
+개발 중에는 Effect가 `showModal()`을 호출한 다음 즉시 `close()`를 호출하고 다시 `showModal()`을 호출합니다. 이것은 사용자가 확인할 수 있는 동작이며 제품 환경에서 볼 수 있는 것과 동일합니다.
-### Subscribing to events {/*subscribing-to-events*/}
+### 이벤트 구독하기 {/*subscribing-to-events*/}
-If your Effect subscribes to something, the cleanup function should unsubscribe:
+만약 Effect가 어떤 것을 구독한다면, 클린업 함수에서 구독을 해지해야 합니다.
```js {6}
useEffect(() => {
@@ -637,11 +637,11 @@ useEffect(() => {
}, []);
```
-In development, your Effect will call `addEventListener()`, then immediately `removeEventListener()`, and then `addEventListener()` again with the same handler. So there would be only one active subscription at a time. This has the same user-visible behavior as calling `addEventListener()` once, as in production.
+개발 중에는 Effect가 `addEventListener()`를 호출한 다음 즉시 `removeEventListener()`를 호출하고, 그다음 동일한 핸들러로 `addEventListener()`를 호출합니다. 따라서 한 번에 하나의 활성 구독만 있게 됩니다. 이것은 제품 환경에서 한 번 `addEventListener()`를 호출하는 것과 동일한 동작을 가집니다.
-### Triggering animations {/*triggering-animations*/}
+### 애니메이션 트리거 {/*triggering-animations*/}
-If your Effect animates something in, the cleanup function should reset the animation to the initial values:
+Effect가 어떤 요소를 애니메이션으로 표시하는 경우, 클린업 함수에서 애니메이션을 초기 값으로 재설정해야 합니다.
```js {4-6}
useEffect(() => {
@@ -653,11 +653,11 @@ useEffect(() => {
}, []);
```
-In development, opacity will be set to `1`, then to `0`, and then to `1` again. This should have the same user-visible behavior as setting it to `1` directly, which is what would happen in production. If you use a third-party animation library with support for tweening, your cleanup function should reset the timeline to its initial state.
+개발 중에는 불투명도가 `1`로 설정되고, 그런 다음 `0`으로 설정되고, 다시 `1`로 설정됩니다. 이것은 제품 환경에서 `1`로 직접 설정하는 것과 동일한 동작을 가집니다. tweening을 지원하는 서드파티 애니메이션 라이브러리를 사용하는 경우 클린업 함수에서 타임라인을 초기 상태로 재설정해야 합니다.
-### Fetching data {/*fetching-data*/}
+### 데이터 페칭 {/*fetching-data*/}
-If your Effect fetches something, the cleanup function should either [abort the fetch](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) or ignore its result:
+만약 Effect가 어떤 데이터를 가져온다면, 클린업 함수에서는 [fetch를 중단](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)하거나 결과를 무시해야 합니다.
```js {2,6,13-15}
useEffect(() => {
@@ -678,11 +678,11 @@ useEffect(() => {
}, [userId]);
```
-You can't "undo" a network request that already happened, but your cleanup function should ensure that the fetch that's _not relevant anymore_ does not keep affecting your application. If the `userId` changes from `'Alice'` to `'Bob'`, cleanup ensures that the `'Alice'` response is ignored even if it arrives after `'Bob'`.
+이미 발생한 네트워크 요청을 "실행 취소"할 수는 없지만, 클린업 함수는 더 이상 관련이 없는 페치가 애플리케이션에 계속 영향을 미치지 않도록 보장해야 합니다. `userId`가 `'Alice'`에서 `'Bob'`으로 변경되면 클린업은 `'Bob'`이후에 도착하더라도 `'Alice'` 응답을 무시하도록 보장합니다.
-**In development, you will see two fetches in the Network tab.** There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned up so its copy of the `ignore` variable will be set to `true`. So even though there is an extra request, it won't affect the state thanks to the `if (!ignore)` check.
+**개발 중에는 네트워크 탭에서 두 개의 페치가 표시됩니다.** 이는 문제가 없습니다. 위의 접근 방식을 사용하면 첫 번째 Effect는 즉시 클린업되어 `ignore` 변수의 복사본이 `true`로 설정됩니다. 따라서 추가 요청이 있더라도 `if (!ignore)` 검사 덕분에 state에 영향을 미치지 않습니다.
-**In production, there will only be one request.** If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
+**제품 환경에서는 하나의 요청만 있을 것입니다.** 개발 중에 두 번째 요청이 문제라면, 가장 좋은 방법은 중복 요청을 제거하고 컴포넌트 간에 응답을 캐시하는 솔루션을 사용하는 것입니다:
```js
function TodoList() {
@@ -690,50 +690,50 @@ function TodoList() {
// ...
```
-This will not only improve the development experience, but also make your application feel faster. For example, the user pressing the Back button won't have to wait for some data to load again because it will be cached. You can either build such a cache yourself or use one of the many alternatives to manual fetching in Effects.
+이렇게 하면 개발 환경을 개선하는데 도움이 될 뿐만 아니라 애플리케이션의 반응 속도도 향상됩니다. 예를 들어 사용자가 뒤로 가기 버튼을 눌렀을 때 데이터를 다시 로드하는 것을 기다릴 필요가 없습니다. 데이터가 캐시되기 때문입니다. 이러한 캐시를 직접 구축하거나 비슷한 효과를 누릴 수 있는 여러 대안 중 하나를 사용할 수 있습니다.
-#### What are good alternatives to data fetching in Effects? {/*what-are-good-alternatives-to-data-fetching-in-effects*/}
+#### Effect에서 데이터를 가져오는 좋은 대안은 무엇인가요? {/*what-are-good-alternatives-to-data-fetching-in-effects*/}
-Writing `fetch` calls inside Effects is a [popular way to fetch data](https://www.robinwieruch.de/react-hooks-fetch-data/), especially in fully client-side apps. This is, however, a very manual approach and it has significant downsides:
+Effect 안에서 `fetch` 호출을 작성하는 것은 [데이터를 가져오는](https://www.robinwieruch.de/react-hooks-fetch-data/) [인기 있는 방법](https://www.robinwieruch.de/react-hooks-fetch-data/)입니다, 특히 완전히 클라이언트 측 앱에서는요. 하지만 이는 매우 수동적인 접근 방식이며 중요한 단점이 있습니다.
-- **Effects don't run on the server.** This means that the initial server-rendered HTML will only include a loading state with no data. The client computer will have to download all JavaScript and render your app only to discover that now it needs to load the data. This is not very efficient.
-- **Fetching directly in Effects makes it easy to create "network waterfalls".** You render the parent component, it fetches some data, renders the child components, and then they start fetching their data. If the network is not very fast, this is significantly slower than fetching all data in parallel.
-- **Fetching directly in Effects usually means you don't preload or cache data.** For example, if the component unmounts and then mounts again, it would have to fetch the data again.
-- **It's not very ergonomic.** There's quite a bit of boilerplate code involved when writing `fetch` calls in a way that doesn't suffer from bugs like [race conditions.](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect)
+- **Effect는 서버에서 실행되지 않습니다.** 따라서 초기 서버 렌더링된 HTML은 데이터가 없는 로딩 상태만 포함하게 됩니다. 클라이언트 컴퓨터는 모든 JavaScript를 다운로드하고 앱을 렌더링해야만 데이터를 로드해야 한다는 것을 알게 될 것입니다. 이는 효율적이지 않습니다.
+- **Effect 안에서 직접 가져오면 "네트워크 폭포"를 쉽게 만들 수 있습니다.** 부모 컴포넌트를 렌더링하면 일부 데이터를 가져오고 자식 컴포넌트를 렌더링한 다음 그들이 데이터를 가져오기 시작합니다. 네트워크가 빠르지 않으면 이는 모든 데이터를 병렬로 가져오는 것보다 훨씬 느립니다.
+- **Effect 안에서 직접 가져오는 것은 일반적으로 데이터를 미리 로드하거나 캐시하지 않음을 의미합니다.** 예를 들어 컴포넌트가 언마운트되고 다시 마운트되면 데이터를 다시 가져와야 합니다.
+- **그리 편리하지 않습니다.** `fetch` 호출을 작성할 때 [경쟁 상태](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect)와 같은 버그에 영향을 받지 않는 방식으로 작성하는 데 꽤 많은 보일러플레이트 코드가 필요합니다.
-This list of downsides is not specific to React. It applies to fetching data on mount with any library. Like with routing, data fetching is not trivial to do well, so we recommend the following approaches:
+이 단점 목록은 React에만 해당되는 것은 아닙니다. 어떤 라이브러리에서든 마운트 시에 데이터를 가져온다면 비슷한 단점이 존재합니다. 마운트 시에 데이터를 페칭하는 것도 라우팅과 마찬가지로 잘 수행하기 어려운 작업이므로 다음 접근 방식을 권장합니다.
-- **If you use a [framework](/learn/start-a-new-react-project#production-grade-react-frameworks), use its built-in data fetching mechanism.** Modern React frameworks have integrated data fetching mechanisms that are efficient and don't suffer from the above pitfalls.
-- **Otherwise, consider using or building a client-side cache.** Popular open source solutions include [React Query](https://tanstack.com/query/latest), [useSWR](https://swr.vercel.app/), and [React Router 6.4+.](https://beta.reactrouter.com/en/main/start/overview) You can build your own solution too, in which case you would use Effects under the hood, but add logic for deduplicating requests, caching responses, and avoiding network waterfalls (by preloading data or hoisting data requirements to routes).
+- **[프레임워크](/learn/start-a-new-react-project#production-grade-react-frameworks)를 사용하는 경우 해당 프레임워크의 내장 데이터 페칭 메커니즘을 사용하세요.** 현대적인 React 프레임워크에는 위의 단점을 겪지 않는 효율적이고 통합적인 데이터 페칭 메커니즘이 포함되어 있습니다.
+- **그렇지 않은 경우 클라이언트 측 캐시를 사용하거나 구축하는 것을 고려하세요.** 인기 있는 오픈 소스 솔루션으로는 [React Query](https://tanstack.com/query/latest), [useSWR](https://swr.vercel.app/) 및 [React Router 6.4+](https://beta.reactrouter.com/en/main/start/overview)이 있습니다. 직접 솔루션을 구축할 수도 있으며 이 경우 Effect를 내부적으로 사용하면서 요청 중복을 제거하고 응답을 캐시하고 네트워크 폭포를 피하는 로직을 추가할 것입니다. (데이터를 사전에 로드하거나 데이터 요구 사항을 라우트)
-You can continue fetching data directly in Effects if neither of these approaches suit you.
+이러한 접근 방식 중 어느 것도 적합하지 않은 경우, Effect 내에서 데이터를 직접 가져오는 것을 계속하셔도 됩니다.
-### Sending analytics {/*sending-analytics*/}
+### 분석 보내기 {/*sending-analytics*/}
-Consider this code that sends an analytics event on the page visit:
+페이지 방문 시 분석 이벤트를 보내는 다음 코드를 고려해보세요.
```js
useEffect(() => {
- logVisit(url); // Sends a POST request
+ logVisit(url); // POST 요청을 보냄
}, [url]);
```
-In development, `logVisit` will be called twice for every URL, so you might be tempted to try to fix that. **We recommend keeping this code as is.** Like with earlier examples, there is no *user-visible* behavior difference between running it once and running it twice. From a practical point of view, `logVisit` should not do anything in development because you don't want the logs from the development machines to skew the production metrics. Your component remounts every time you save its file, so it logs extra visits in development anyway.
+개발 환경에서는 `logVisit`가 각 URL에 대해 두 번 호출될 것입니다. 그래서 이를 수정하고 싶을 수 있습니다. **우리는 이 코드를 그대로 유지하는 것을 권장합니다.** 이전 예제와 마찬가지로 한 번 실행하거나 두 번 실행하는 것 사이에서 *사용자가 볼 수 있는* 동작 차이가 없습니다. 실제로 개발 환경에서는 `logVisit`가 아무 작업도 수행하지 않아야 합니다. 왜냐하면 개발 환경의 로그가 제품 지표를 왜곡시키지 않도록 하기 위함입니다. 컴포넌트는 파일을 저장할 때마다 재마운트되므로 개발 환경에서는 추가적인 방문 기록을 로그에 남기게 됩니다.
-**In production, there will be no duplicate visit logs.**
+**제품 환경에서는 중복된 방문 로그가 없을 것입니다.**
-To debug the analytics events you're sending, you can deploy your app to a staging environment (which runs in production mode) or temporarily opt out of [Strict Mode](/reference/react/StrictMode) and its development-only remounting checks. You may also send analytics from the route change event handlers instead of Effects. For more precise analytics, [intersection observers](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) can help track which components are in the viewport and how long they remain visible.
+보내는 분석 이벤트를 디버깅하려면 앱을 스테이징 환경(제품 모드로 실행)에 배포하거나 [Strict Mode](/reference/react/StrictMode)를 일시적으로 사용 중지하여 개발 환경 전용의 재마운팅 검사를 수행할 수 있습니다. 또한 Effect 대신 라우트 변경 이벤트 핸들러에서 분석을 보낼 수도 있습니다. 더 정밀한 분석을 위해 [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)를 사용하여 어떤 컴포넌트가 뷰포트에 있는지와 얼마나 오래 보이는지 추적하는 데 도움이 될 수 있습니다.
-### Not an Effect: Initializing the application {/*not-an-effect-initializing-the-application*/}
+### Effect가 아닌 경우: 애플리케이션 초기화 {/*not-an-effect-initializing-the-application*/}
-Some logic should only run once when the application starts. You can put it outside your components:
+일부 로직은 애플리케이션 시작 시에 한 번만 실행되어야 합니다. 이러한 로직은 컴포넌트 외부에 배치할 수 있습니다.
```js {2-3}
-if (typeof window !== 'undefined') { // Check if we're running in the browser.
+if (typeof window !== 'undefined') { // 브라우저에서 실행 중인지 확인합니다.
checkAuthToken();
loadDataFromLocalStorage();
}
@@ -743,37 +743,37 @@ function App() {
}
```
-This guarantees that such logic only runs once after the browser loads the page.
+위와 같이 컴포넌트 외부에서 해당 로직을 실행하면, 해당 로직은 브라우저가 페이지를 로드한 후 한 번만 실행됨이 보장됩니다.
-### Not an Effect: Buying a product {/*not-an-effect-buying-a-product*/}
+### Effect가 아닌 경우: 제품 구입하기 {/*not-an-effect-buying-a-product*/}
-Sometimes, even if you write a cleanup function, there's no way to prevent user-visible consequences of running the Effect twice. For example, maybe your Effect sends a POST request like buying a product:
+가끔은 클린업 함수를 작성하더라도 Effect가 두 번 실행되는 것에 대해 사용자가 확인할 수 있는 결과를 방지할 방법이 없을 수 있습니다. 예를 들어, 아래와 같이 제품을 구매하는 POST 요청을 보내는 Effect가 있다고 가정해 보겠습니다.
```js {2-3}
useEffect(() => {
- // 🔴 Wrong: This Effect fires twice in development, exposing a problem in the code.
+ // 🔴 잘못된 방법: 이 Effect는 개발 환경에서 두 번 실행되며 코드에 문제가 드러납니다.
fetch('/api/buy', { method: 'POST' });
}, []);
```
-You wouldn't want to buy the product twice. However, this is also why you shouldn't put this logic in an Effect. What if the user goes to another page and then presses Back? Your Effect would run again. You don't want to buy the product when the user *visits* a page; you want to buy it when the user *clicks* the Buy button.
+사용자는 제품을 두 번 구매하고 싶지 않을 것입니다. 그러나 이것은 이러한 로직을 Effect에 넣지 않아야 하는 이유입니다. 사용자가 다른 페이지로 이동한 다음 뒤로 가기 버튼을 누르는 경우 어떻게 될까요? Effect가 다시 실행됩니다. 사용자가 페이지를 방문할 때 제품을 구매하려고 하지 않으며, 사용자가 "구매" 버튼을 클릭할 때 제품을 구매하고 싶은 것입니다.
-Buying is not caused by rendering; it's caused by a specific interaction. It should run only when the user presses the button. **Delete the Effect and move your `/api/buy` request into the Buy button event handler:**
+구매는 렌더링에 의해 발생하는 것이 아니라 특정 상호 작용에 의해 발생합니다. 사용자가 버튼을 누를 때만 실행되어야 합니다. **Effect를 삭제하고 `/api/buy` 요청을 Buy 버튼의 이벤트 핸들러로 이동하세요.**
```js {2-3}
function handleClick() {
- // ✅ Buying is an event because it is caused by a particular interaction.
+ // ✅ 구매는 특정 상호 작용에 의해 발생하는 이벤트입니다.
fetch('/api/buy', { method: 'POST' });
}
```
-**This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs.** From the user's perspective, visiting a page shouldn't be different from visiting it, clicking a link, and pressing Back. React verifies that your components abide by this principle by remounting them once in development.
+**만약 컴포넌트를 다시 마운트했을 때 애플리케이션의 논리가 깨진다면, 기존에 존재하던 버그가 드러난 것입니다.** 사용자의 관점에서 페이지를 방문하는 것과 페이지를 방문하고 링크를 클릭한 다음 뒤로 가기 버튼을 누르는 것 사이에는 차이가 없어야 합니다. React는 개발 환경에서 컴포넌트를 한 번 다시 마운트하여 이 원칙을 준수하는지 확인합니다.
-## Putting it all together {/*putting-it-all-together*/}
+## 위에서 설명한 모든 것들 적용해보기 {/*putting-it-all-together*/}
-This playground can help you "get a feel" for how Effects work in practice.
+이 플레이그라운드를 살펴보면 실제로 Effect가 어떻게 작동하는지에 대한 "느낌을 얻을" 수 있습니다.
-This example uses [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) to schedule a console log with the input text to appear three seconds after the Effect runs. The cleanup function cancels the pending timeout. Start by pressing "Mount the component":
+이 예제는 [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout)을 사용하여 Effect가 실행된 후 3초 후에 입력 텍스트와 함께 콘솔 로그가 표시되도록 합니다. 클린업 함수는 실행을 기다리는 타임아웃을 취소합니다. "컴포넌트 마운트" 버튼을 눌러 시작하세요.
@@ -788,11 +788,11 @@ function Playground() {
console.log('⏰ ' + text);
}
- console.log('🔵 Schedule "' + text + '" log');
+ console.log('🔵 스케줄 로그 "' + text);
const timeoutId = setTimeout(onTimeout, 3000);
return () => {
- console.log('🟡 Cancel "' + text + '" log');
+ console.log('🟡 취소 로그 "' + text);
clearTimeout(timeoutId);
};
}, [text]);
@@ -816,7 +816,7 @@ export default function App() {
return (
<>
setShow(!show)}>
- {show ? 'Unmount' : 'Mount'} the component
+ 컴포넌트 {show ? '언마운트' : '마운트'}
{show && }
{show && }
@@ -827,21 +827,21 @@ export default function App() {
-You will see three logs at first: `Schedule "a" log`, `Cancel "a" log`, and `Schedule "a" log` again. Three second later there will also be a log saying `a`. As you learned earlier, the extra schedule/cancel pair is because React remounts the component once in development to verify that you've implemented cleanup well.
+처음에는 `Schedule "a" log`, `Cancel "a" log`, 그리고 다시 `Schedule "a" log` 라는 세 가지 로그를 볼 수 있을 것입니다. 몇 초 후에는 `a`라는 로그가 나타날 것입니다. 이전에 배운 내용처럼 추가된 스케줄/취소 쌍은 React가 컴포넌트를 개발 중에 한 번 다시 마운트하여 정리를 제대로 구현했는지 확인하기 때문입니다.
-Now edit the input to say `abc`. If you do it fast enough, you'll see `Schedule "ab" log` immediately followed by `Cancel "ab" log` and `Schedule "abc" log`. **React always cleans up the previous render's Effect before the next render's Effect.** This is why even if you type into the input fast, there is at most one timeout scheduled at a time. Edit the input a few times and watch the console to get a feel for how Effects get cleaned up.
+이제 입력란을 `abc`로 수정해 보세요. 충분히 빠르게 입력하면 `Schedule "ab" log` 바로 뒤에 `Cancel "ab" log`와 `Schedule "abc" log`가 나타날 것입니다. **React는 항상 이전 렌더의 Effect를 다음 렌더의 Effect보다 먼저 정리합니다.** 따라서 빠르게 입력하더라도 한 번에 최대 하나의 타임아웃만 예약되는 것을 볼 수 있습니다. 입력을 몇 번 해보면서 Effect가 어떻게 정리되는지 느껴보세요.
-Type something into the input and then immediately press "Unmount the component". Notice how unmounting cleans up the last render's Effect. Here, it clears the last timeout before it has a chance to fire.
+입력란에 무언가를 입력한 다음 "컴포넌트 언마운트"를 눌러보세요. 언마운트가 마지막 렌더의 Effect를 정리함을 주목하세요. 여기서는 타임아웃이 실행되기 전에 마지막 타임아웃이 취소됩니다.
-Finally, edit the component above and comment out the cleanup function so that the timeouts don't get cancelled. Try typing `abcde` fast. What do you expect to happen in three seconds? Will `console.log(text)` inside the timeout print the *latest* `text` and produce five `abcde` logs? Give it a try to check your intuition!
+마지막으로 위 컴포넌트를 수정하고 정리 함수의 주석 처리를 해제하여 타임아웃이 취소되지 않도록 해보세요. `abcde`를 빠르게 입력해 보세요. 몇 초 후에 무엇이 기대되는지 생각해 보세요. 타임아웃 내부의 `console.log(text)`가 가장 최근의 `text`를 출력하고 다섯 번의 `abcde` 로그가 생성될까요? 직접 시도하여 확인해 보세요!
-Three seconds later, you should see a sequence of logs (`a`, `ab`, `abc`, `abcd`, and `abcde`) rather than five `abcde` logs. **Each Effect "captures" the `text` value from its corresponding render.** It doesn't matter that the `text` state changed: an Effect from the render with `text = 'ab'` will always see `'ab'`. In other words, Effects from each render are isolated from each other. If you're curious how this works, you can read about [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures).
+수 초 후에 `a`, `ab`, `abc`, `abcd`, 그리고 `abcde`라는 일련의 로그를 볼 수 있을 것입니다. **각 Effect는 해당 렌더의 `text` 값을 "캡처"합니다.** `text` 상태가 변경되었는지 여부는 중요하지 않습니다. `text = 'ab'` 렌더의 Effect에서는 항상 `'ab'`를 볼 것입니다. 다시 말해, 각 렌더의 Effect는 서로 격리되어 있습니다. 이 작동 방식에 대해서 궁금하다면 [클로저](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)에 대해 읽어볼 수 있습니다.
-#### Each render has its own Effects {/*each-render-has-its-own-effects*/}
+#### 각각의 렌더링은 각각의 고유한 Effect를 갖습니다. {/*each-render-has-its-own-effects*/}
-You can think of `useEffect` as "attaching" a piece of behavior to the render output. Consider this Effect:
+`useEffect`를 렌더링 결과물에 "부착"하는 것으로 생각할 수 있습니다. 다음과 같은 Effect를 고려해 보세요.
```js
export default function ChatRoom({ roomId }) {
@@ -855,119 +855,119 @@ export default function ChatRoom({ roomId }) {
}
```
-Let's see what exactly happens as the user navigates around the app.
+이제 사용자가 앱을 탐색하는 동안 정확히 어떤 일이 일어나는지 알아보겠습니다.
-#### Initial render {/*initial-render*/}
+#### 초기 렌더링 {/*initial-render*/}
-The user visits ` `. Let's [mentally substitute](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `roomId` with `'general'`:
+사용자가 ` `을 방문합니다. 이때, `roomId`를 `'general'`로 [멘탈모델 위에서 대체](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time)해보겠습니다.
```js
- // JSX for the first render (roomId = "general")
+ // 첫 번째 렌더링에 대한 JSX (roomId = "general")
return Welcome to general! ;
```
-**The Effect is *also* a part of the rendering output.** The first render's Effect becomes:
+**Effect 또한 렌더링 결과물의 일부입니다.** 첫 번째 렌더링의 Effect는 다음과 같습니다.
```js
- // Effect for the first render (roomId = "general")
+ // 첫 번째 렌더링에 대한 이펙트 (roomId = "general")
() => {
const connection = createConnection('general');
connection.connect();
return () => connection.disconnect();
},
- // Dependencies for the first render (roomId = "general")
+ // 첫 번째 렌더링의 의존성 (roomId = "general")
['general']
```
-React runs this Effect, which connects to the `'general'` chat room.
+React는 이 Effect를 실행하며, `'general'` 채팅방에 연결합니다.
-#### Re-render with same dependencies {/*re-render-with-same-dependencies*/}
+#### 같은 의존성 사이에서의 재랜더링 {/*re-render-with-same-dependencies*/}
-Let's say ` ` re-renders. The JSX output is the same:
+` `가 다시 렌더링된다고 가정해봅시다. JSX 결과물은 동일합니다.
```js
- // JSX for the second render (roomId = "general")
+ // 두 번째 렌더링에 대한 JSX (roomId = "general")
return Welcome to general! ;
```
-React sees that the rendering output has not changed, so it doesn't update the DOM.
+React는 렌더링 출력이 변경되지 않았기 때문에 DOM을 업데이트하지 않습니다.
-The Effect from the second render looks like this:
+두 번째 렌더링에서의 Effect는 다음과 같습니다.
```js
- // Effect for the second render (roomId = "general")
+ // 두 번째 렌더링에 대한 Effect (roomId = "general")
() => {
const connection = createConnection('general');
connection.connect();
return () => connection.disconnect();
},
- // Dependencies for the second render (roomId = "general")
+ // 두 번째 렌더링에 대한 의존성 (roomId = "general")
['general']
```
-React compares `['general']` from the second render with `['general']` from the first render. **Because all dependencies are the same, React *ignores* the Effect from the second render.** It never gets called.
+React는 두 번째 렌더링에서의 `['general']`를 첫 번째 렌더링에서의 `['general']`와 비교합니다. **모든 의존성이 동일하므로 React는 두 번째 렌더링에서의 Effect를 *무시*합니다.** 해당 Effect는 호출되지 않습니다.
-#### Re-render with different dependencies {/*re-render-with-different-dependencies*/}
+#### 다른 의존성으로 재렌더링 {/*re-render-with-different-dependencies*/}
-Then, the user visits ` `. This time, the component returns different JSX:
+그럼, 사용자가 ` `을 탐색합니다. 이번에는 컴포넌트가 다른 JSX를 반환합니다.
```js
- // JSX for the third render (roomId = "travel")
+ // 세 번째 렌더링에 대한 JSX (roomId = "travel")
return Welcome to travel! ;
```
-React updates the DOM to change `"Welcome to general"` into `"Welcome to travel"`.
+React는 DOM을 업데이트하여 `"Welcome to general"`을 `"Welcome to travel"`로 변경합니다.
-The Effect from the third render looks like this:
+세 번째 렌더링에서의 Effect는 다음과 같습니다:
```js
- // Effect for the third render (roomId = "travel")
+ // 세 번째 렌더링에 대한 Effect (roomId = "travel")
() => {
const connection = createConnection('travel');
connection.connect();
return () => connection.disconnect();
},
- // Dependencies for the third render (roomId = "travel")
+ // 세 번째 렌더링에 대한 의존성 (roomId = "travel")
['travel']
```
-React compares `['travel']` from the third render with `['general']` from the second render. One dependency is different: `Object.is('travel', 'general')` is `false`. The Effect can't be skipped.
+React는 세 번째 렌더링에서의 `['travel']`와 두 번째 렌더링에서의 `['general']`를 비교합니다. 하나의 의존성이 다릅니다: `Object.is('travel', 'general')`은 `false`입니다. Effect는 건너뛸 수 없습니다.
-**Before React can apply the Effect from the third render, it needs to clean up the last Effect that _did_ run.** The second render's Effect was skipped, so React needs to clean up the first render's Effect. If you scroll up to the first render, you'll see that its cleanup calls `disconnect()` on the connection that was created with `createConnection('general')`. This disconnects the app from the `'general'` chat room.
+**React는 세 번째 렌더링의 Effect를 적용하기 전에 먼저 실행된 Effect를 정리해야 합니다.** 두 번째 렌더링의 Effect가 건너뛰어졌기 때문에, React는 첫 번째 렌더링의 Effect를 정리해야 합니다. 처음 렌더링되었을 때 스크롤하면, `createConnection('general')`로 생성된 연결에 대해 `disconnect()`를 호출하는 것을 볼 수 있습니다. 이로써 앱은 `'general'` 채팅방과의 연결이 해제됩니다.
-After that, React runs the third render's Effect. It connects to the `'travel'` chat room.
+그 후에 React는 세 번째 렌더링의 Effect를 실행합니다. `'travel'` 채팅방에 연결합니다.
-#### Unmount {/*unmount*/}
+#### 언마운트 {/*unmount*/}
-Finally, let's say the user navigates away, and the `ChatRoom` component unmounts. React runs the last Effect's cleanup function. The last Effect was from the third render. The third render's cleanup destroys the `createConnection('travel')` connection. So the app disconnects from the `'travel'` room.
+마지막으로, 사용자가 다른 페이지로 이동하게 되어 `ChatRoom` 컴포넌트가 언마운트됩니다. React는 마지막 Effect의 클린업 함수를 실행합니다. 마지막 Effect는 세 번째 렌더링에서 온 것입니다. 세 번째 렌더링의 클린업은 `createConnection('travel')` 연결을 종료합니다. 그래서 앱은 `'travel'` 채팅방과의 연결을 해제하게 됩니다.
-#### Development-only behaviors {/*development-only-behaviors*/}
+#### 개발 환경에서만의 동작 {/*development-only-behaviors*/}
-When [Strict Mode](/reference/react/StrictMode) is on, React remounts every component once after mount (state and DOM are preserved). This [helps you find Effects that need cleanup](#step-3-add-cleanup-if-needed) and exposes bugs like race conditions early. Additionally, React will remount the Effects whenever you save a file in development. Both of these behaviors are development-only.
+[Strict Mode](/reference/react/StrictMode)가 활성화된 경우, React는 모든 컴포넌트를 한 번 마운트한 후에 다시 마운트합니다(state와 DOM은 보존됩니다). 이는 [클린업이 필요한 Effect를 찾는 데 도움이 되며](#step-3-add-cleanup-if-needed) 경쟁 조건과 같은 버그를 초기에 드러날 수 있게 합니다. 게다가 React는 개발 중 파일을 저장할 때마다 Effect를 다시 마운트합니다. 이러한 두 가지 동작은 개발 환경에서만 적용됩니다.
-- Unlike events, Effects are caused by rendering itself rather than a particular interaction.
-- Effects let you synchronize a component with some external system (third-party API, network, etc).
-- By default, Effects run after every render (including the initial one).
-- React will skip the Effect if all of its dependencies have the same values as during the last render.
-- You can't "choose" your dependencies. They are determined by the code inside the Effect.
-- Empty dependency array (`[]`) corresponds to the component "mounting", i.e. being added to the screen.
-- In Strict Mode, React mounts components twice (in development only!) to stress-test your Effects.
-- If your Effect breaks because of remounting, you need to implement a cleanup function.
-- React will call your cleanup function before the Effect runs next time, and during the unmount.
+- 이벤트와 달리 Effect는 특정 상호작용이 아닌 렌더링 자체에 의해 발생합니다.
+- Effect를 사용하면 컴포넌트를 외부 시스템(타사 API, 네트워크 등)과 동기화할 수 있습니다.
+- 기본적으로 Effect는 모든 렌더링(초기 렌더링 포함) 후에 실행됩니다.
+- React는 모든 의존성이 마지막 렌더링과 동일한 값을 가지면 Effect를 건너뜁니다.
+- 의존성을 "선택"할 수 없습니다. 의존성은 Effect 내부의 코드에 의해 결정됩니다.
+- 빈 의존성 배열(`[]`)은 컴포넌트 "마운팅"(화면에 추가됨)을 의미합니다.
+- Strict Mode에서 React는 컴포넌트를 두 번 마운트합니다(개발 환경에서만!) 이는 Effect의 스트레스 테스트를 위한 것입니다.
+- Effect가 다시 마운트로 인해 중단된 경우 클린업 함수를 구현해야 합니다.
+- React는 Effect가 다음에 실행되기 전에 정리 함수를 호출하며, 언마운트 중에도 호출합니다.
-#### Focus a field on mount {/*focus-a-field-on-mount*/}
+#### 마운트시 input 필드에 포커스하기 {/*focus-a-field-on-mount*/}
-In this example, the form renders a ` ` component.
+이 예시에서는 form이 ` ` 컴포넌트를 렌더링합니다.
-Use the input's [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) method to make `MyInput` automatically focus when it appears on the screen. There is already a commented out implementation, but it doesn't quite work. Figure out why it doesn't work, and fix it. (If you're familiar with the `autoFocus` attribute, pretend that it does not exist: we are reimplementing the same functionality from scratch.)
+화면에 나타날 때 `MyInput`이 자동으로 포커스되도록 입력의 [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) 메서드를 사용하세요. 이미 주석 처리된 구현이 있지만 제대로 작동하지 않습니다. 왜 작동하지 않는지 확인하고 수정해 보세요. (`autoFocus` 속성은 존재하지 않는 것으로 가정하세요. 우리는 처음부터 동일한 기능을 다시 구현하고 있습니다.)
@@ -977,7 +977,7 @@ import { useEffect, useRef } from 'react';
export default function MyInput({ value, onChange }) {
const ref = useRef(null);
- // TODO: This doesn't quite work. Fix it.
+ // TODO: 작동하지 않는다. 고쳐야함
// ref.current.focus()
return (
@@ -1000,13 +1000,13 @@ export default function Form() {
const [upper, setUpper] = useState(false);
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} form
+ setShow(s => !s)}>form {show ? '숨기기' : '보기'}
{show && (
<>
- Enter your name:
+ 이름을 입력하세요:
setName(e.target.value)}
@@ -1018,9 +1018,9 @@ export default function Form() {
checked={upper}
onChange={e => setUpper(e.target.checked)}
/>
- Make it uppercase
+ 대문자로 만들기
- Hello, {upper ? name.toUpperCase() : name}
+ 안녕하세요, {upper ? name.toUpperCase() : name}님
>
)}
>
@@ -1043,15 +1043,15 @@ body {
-To verify that your solution works, press "Show form" and verify that the input receives focus (becomes highlighted and the cursor is placed inside). Press "Hide form" and "Show form" again. Verify the input is highlighted again.
+솔루션이 제대로 작동하는지 확인하려면 "form 보기"를 누르고 입력란이 포커스되는지 확인하세요.(강조 표시, 커서가 내부에 배치됨). "form 숨기기"를 누르고 다시 "form 보기"를 눌러 입력란이 다시 강조 표시되는지 확인하세요.
-`MyInput` should only focus _on mount_ rather than after every render. To verify that the behavior is right, press "Show form" and then repeatedly press the "Make it uppercase" checkbox. Clicking the checkbox should _not_ focus the input above it.
+`MyInput`은 렌더링 후 매번 포커스되는 것이 아니라 _마운트 시에만_ 포커스되어야 합니다. 이 동작이 올바른지 확인하려면 "form 보기"를 누른 다음 "대문자로 만들기" 체크박스를 반복해서 클릭하세요. 체크박스를 클릭해도 상단의 입력란은 포커스가 _되지 않아야_ 합니다.
-Calling `ref.current.focus()` during render is wrong because it is a *side effect*. Side effects should either be placed inside an event handler or be declared with `useEffect`. In this case, the side effect is _caused_ by the component appearing rather than by any specific interaction, so it makes sense to put it in an Effect.
+렌더링 중에 `ref.current.focus()`를 호출하는 것은 적절하지 않습니다. *부수 효과*이기 때문입니다. 부수 효과는 이벤트 핸들러 내부에 배치하거나 `useEffect`로 선언해야 합니다. 이 경우에는 부작용이 특정 상호작용이 아니라 컴포넌트가 나타나는 것에 의해 _발생되기_ 때문에 Effect 내부에 넣는 것이 맞습니다.
-To fix the mistake, wrap the `ref.current.focus()` call into an Effect declaration. Then, to ensure that this Effect runs only on mount rather than after every render, add the empty `[]` dependencies to it.
+실수를 고치려면 `ref.current.focus()` 호출을 Effect 선언으로 감싸세요. 그런 다음, 이 Effect가 렌더링 후 매번 실행되는 것이 아니라 마운트 시에만 실행되도록 하려면 빈 `[]` 의존성을 추가하세요.
@@ -1085,13 +1085,13 @@ export default function Form() {
const [upper, setUpper] = useState(false);
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} form
+ setShow(s => !s)}>form {show ? '숨기기' : '보기'} form
{show && (
<>
- Enter your name:
+ 이름을 입력하세요:
setName(e.target.value)}
@@ -1103,9 +1103,9 @@ export default function Form() {
checked={upper}
onChange={e => setUpper(e.target.checked)}
/>
- Make it uppercase
+ 대문자로 만들기
- Hello, {upper ? name.toUpperCase() : name}
+ 안녕하세요, {upper ? name.toUpperCase() : name} 님
>
)}
>
@@ -1129,13 +1129,13 @@ body {
-#### Focus a field conditionally {/*focus-a-field-conditionally*/}
+#### 조건부로 input 필드에 포커스하기 {/*focus-a-field-conditionally*/}
-This form renders two ` ` components.
+이 form은 두 개의 ` ` 컴포넌트를 렌더링합니다.
-Press "Show form" and notice that the second field automatically gets focused. This is because both of the ` ` components try to focus the field inside. When you call `focus()` for two input fields in a row, the last one always "wins".
+"form 보기"를 누르면 두 번째 필드가 자동으로 포커스됩니다. 이는 두 ` ` 컴포넌트 모두 내부의 필드에 포커스를 주려고 하기 때문입니다. 두 개의 입력 필드에 연속해서 `focus()`를 호출하면 마지막 호출이 항상 "승리하게" 됩니다.
-Let's say you want to focus the first field. The first `MyInput` component now receives a boolean `shouldFocus` prop set to `true`. Change the logic so that `focus()` is only called if the `shouldFocus` prop received by `MyInput` is `true`.
+이제 첫 번째 필드에 포커스를 주려면 첫 번째 `MyInput` 컴포넌트가 `true`로 설정된 `shouldFocus` prop을 받도록 변경해야 합니다. 변경된 로직에 따라 `MyInput`이 받은 `shouldFocus` prop이 `true`일 때에만 `focus()`가 호출되도록 변경해 보세요.
@@ -1145,7 +1145,7 @@ import { useEffect, useRef } from 'react';
export default function MyInput({ shouldFocus, value, onChange }) {
const ref = useRef(null);
- // TODO: call focus() only if shouldFocus is true.
+ // TODO: shouldFocus가 true일때만 호출되도록
useEffect(() => {
ref.current.focus();
}, []);
@@ -1172,13 +1172,13 @@ export default function Form() {
const name = firstName + ' ' + lastName;
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} form
+ setShow(s => !s)}>form {show ? '숨기기' : '보기'}
{show && (
<>
- Enter your first name:
+ 이름을 입력하세요:
setFirstName(e.target.value)}
@@ -1186,14 +1186,14 @@ export default function Form() {
/>
- Enter your last name:
+ 성을 입력하세요:
setLastName(e.target.value)}
shouldFocus={false}
/>
- Hello, {upper ? name.toUpperCase() : name}
+ 안녕하세요, {upper ? name.toUpperCase() : name} 님
>
)}
>
@@ -1215,17 +1215,17 @@ body {
-To verify your solution, press "Show form" and "Hide form" repeatedly. When the form appears, only the *first* input should get focused. This is because the parent component renders the first input with `shouldFocus={true}` and the second input with `shouldFocus={false}`. Also check that both inputs still work and you can type into both of them.
+해당 코드를 실행하고 주어진 검증 방법을 따라 진행해 봅시다. "form 보기" 버튼을 반복적으로 누르고 "form 숨기기" 버튼을 클릭하여 결과를 확인할 수 있습니다. form이 나타날 때, *첫 번째* 입력 필드에만 포커스가 설정됩니다. 부모 컴포넌트가 첫 번째 입력 필드를 `shouldFocus={true}`로 렌더링하고 두 번째 입력 필드를 `shouldFocus={false}`로 렌더링하기 때문입니다. 또한 두 입력 필드 모두 정상적으로 작동하며, 둘 다 텍스트를 입력할 수 있습니다.
-You can't declare an Effect conditionally, but your Effect can include conditional logic.
+조건부로 `useEffect`를 선언할 수는 없지만, `useEffect` 내부에 조건부 로직을 포함시켜 원하는 동작을 구현할 수 있습니다.
-Put the conditional logic inside the Effect. You will need to specify `shouldFocus` as a dependency because you are using it inside the Effect. (This means that if some input's `shouldFocus` changes from `false` to `true`, it will focus after mount.)
+조건부 로직을 Effect 내부로 넣어주세요. `shouldFocus`를 Effect 내에서 사용하므로 이를 의존성으로 명시해야 합니다. (만약 어떤 input의 `shouldFocus`가 `false`에서 `true`로 변경된다면, 마운트 후에 포커스가 될 것입니다.)
@@ -1263,13 +1263,13 @@ export default function Form() {
const name = firstName + ' ' + lastName;
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} form
+ setShow(s => !s)}>form {show ? '숨기기' : '보기'}
{show && (
<>
- Enter your first name:
+ 이름을 입력하세요:
setFirstName(e.target.value)}
@@ -1277,14 +1277,14 @@ export default function Form() {
/>
- Enter your last name:
+ 성을 입력하세요:
setLastName(e.target.value)}
shouldFocus={false}
/>
- Hello, {upper ? name.toUpperCase() : name}
+ 안녕하세요, {upper ? name.toUpperCase() : name}님
>
)}
>
@@ -1308,15 +1308,15 @@ body {
-#### Fix an interval that fires twice {/*fix-an-interval-that-fires-twice*/}
+#### 두 번 실행되는 interval 고치기 {/*fix-an-interval-that-fires-twice*/}
-This `Counter` component displays a counter that should increment every second. On mount, it calls [`setInterval`.](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) This causes `onTick` to run every second. The `onTick` function increments the counter.
+아래 'Counter' 컴포넌트는 매 초마다 증가하는 카운터를 나타냅니다. 컴포넌트가 마운트될 때 [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval)을 호출합니다. 이로 인해 'onTick' 함수가 매 초마다 실행됩니다. 'onTick' 함수는 카운터를 증가시킵니다.
-However, instead of incrementing once per second, it increments twice. Why is that? Find the cause of the bug and fix it.
+하지만 1초마다 한 번씩 증가하는 대신 두 번씩 증가합니다. 왜 그럴까요? 버그의 원인을 찾아 수정하세요
-Keep in mind that `setInterval` returns an interval ID, which you can pass to [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) to stop the interval.
+`setInterval`은 interval ID를 반환하는데, 이를 [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) 함수에 전달하여 interval을 중지할 수 있습니다.
@@ -1348,7 +1348,7 @@ export default function Form() {
const [show, setShow] = useState(false);
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} counter
+ setShow(s => !s)}>카운터 {show ? '숨기기' : '보기'}
{show && }
@@ -1373,11 +1373,11 @@ body {
-When [Strict Mode](/reference/react/StrictMode) is on (like in the sandboxes on this site), React remounts each component once in development. This causes the interval to be set up twice, and this is why each second the counter increments twice.
+[Strict Mode](/reference/react/StrictMode)가 활성화된 경우 (이 사이트의 코드 예제 샌드박스처럼), React는 개발 중에 각 컴포넌트를 한 번씩 리마운트합니다. 이로 인해 간격이 두 번 설정되어 매 초마다 카운터가 두 번 증가합니다.
-However, React's behavior is not the *cause* of the bug: the bug already exists in the code. React's behavior makes the bug more noticeable. The real cause is that this Effect starts a process but doesn't provide a way to clean it up.
+그러나 React의 동작이 버그의 *원인*은 아닙니다. 버그는 코드에 있습니다. React의 동작은 버그를 더 눈에 띄게 만듭니다. 실제 문제는 이 Effect가 프로세스를 시작한 후에 클린업할 수 있는 방법을 제공하지 않는 것입니다.
-To fix this code, save the interval ID returned by `setInterval`, and implement a cleanup function with [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval):
+이 코드를 수정하려면 `setInterval`에 의해 반환된 interval ID를 저장하고, [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)을 사용하여 클린업 함수를 구현하세요.
@@ -1408,7 +1408,7 @@ export default function App() {
const [show, setShow] = useState(false);
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} counter
+ setShow(s => !s)}>카운터 {show ? '숨기기' : '보기'}
{show && }
@@ -1431,13 +1431,13 @@ body {
-In development, React will still remount your component once to verify that you've implemented cleanup well. So there will be a `setInterval` call, immediately followed by `clearInterval`, and `setInterval` again. In production, there will be only one `setInterval` call. The user-visible behavior in both cases is the same: the counter increments once per second.
+개발 중에 React는 여전히 컴포넌트를 한 번 리마운트하여 클린업이 잘 구현되었는지 확인합니다. 따라서 최초의 `setInterval` 호출 이후에, 바로 다음 `clearInterval`, 그리고 다시 `setInterval` 호출이 발생합니다. 프로덕션(운영 환경)에서는 `setInterval` 호출이 한 번만 있을 것입니다. 개발 환경과 운영 환경 모두 사용자가 볼 수 있는 동작은 동일합니다. 카운터가 1초마다 한 번씩 증가하는 것이죠.
-#### Fix fetching inside an Effect {/*fix-fetching-inside-an-effect*/}
+#### Effect 내부에서의 잘못된 데이터 페칭 고치기 {/*fix-fetching-inside-an-effect*/}
-This component shows the biography for the selected person. It loads the biography by calling an asynchronous function `fetchBio(person)` on mount and whenever `person` changes. That asynchronous function returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which eventually resolves to a string. When fetching is done, it calls `setBio` to display that string under the select box.
+이 컴포넌트는 select 태그로 선택한 사람의 일대기를 보여줍니다. 이 컴포넌트는 선택된 `person`이 변경될 때마다, 또한 마운트될 때마다 비동기 함수 `fetchBio(person)`를 호출하여 일대기를 불러옵니다. 이 비동기 함수는 [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)를 반환하며, 이 Promise는 결국 문자열로 resolve됩니다. 불러오기가 완료되면 `setBio`를 호출하여 해당 문자열을 select의 option으로 표시합니다.
@@ -1477,7 +1477,7 @@ export async function fetchBio(person) {
const delay = person === 'Bob' ? 2000 : 200;
return new Promise(resolve => {
setTimeout(() => {
- resolve('This is ' + person + '’s bio.');
+ resolve('이것은 ' + person + '의 일대기입니다.');
}, delay);
})
}
@@ -1487,30 +1487,30 @@ export async function fetchBio(person) {
-There is a bug in this code. Start by selecting "Alice". Then select "Bob" and then immediately after that select "Taylor". If you do this fast enough, you will notice that bug: Taylor is selected, but the paragraph below says "This is Bob's bio."
+이 코드에 버그가 있습니다. "Alice"를 선택한 다음 "Bob"을 선택한 다음 바로 "Taylor"을 선택하면 버그가 발생합니다. 충분히 빠르게 이 작업을 수행하면 버그를 확인할 수 있습니다. Taylor가 선택되었지만 아래 단락에는 "이것은 Bob의 전기입니다."라고 표시됩니다.
-Why does this happen? Fix the bug inside this Effect.
+이러한 현상이 발생하는 이유는 무엇일까요? 이 Effect 내부의 버그를 수정하세요.
-If an Effect fetches something asynchronously, it usually needs cleanup.
+Effect가 비동기로 무언가를 가져오는 경우 일반적으로 클린업이 필요합니다.
-To trigger the bug, things need to happen in this order:
+버그를 트리거하려면 다음 순서대로 진행되어야 합니다:
-- Selecting `'Bob'` triggers `fetchBio('Bob')`
-- Selecting `'Taylor'` triggers `fetchBio('Taylor')`
-- **Fetching `'Taylor'` completes *before* fetching `'Bob'`**
-- The Effect from the `'Taylor'` render calls `setBio('This is Taylor’s bio')`
-- Fetching `'Bob'` completes
-- The Effect from the `'Bob'` render calls `setBio('This is Bob’s bio')`
+- `'Bob'`을 선택하면 `fetchBio('Bob')`가 트리거됩니다.
+- `'Taylor'`을 선택하면 `fetchBio('Taylor')`가 트리거됩니다.
+- **`'Taylor'`의 일대기를 가져오는 작업이 `'Bob'`의 일대기를 가져오는 작업보다 *먼저* 완료됩니다.**
+- `'Taylor'` 렌더링의 Effect가 `setBio('This is Taylor’s bio')`를 호출합니다.
+- `'Bob'`의 일대기를 가져오는 작업이 완료됩니다.
+- `'Bob'` 렌더링의 Effect가 `setBio('This is Bob’s bio')`를 호출합니다.
-This is why you see Bob's bio even though Taylor is selected. Bugs like this are called [race conditions](https://en.wikipedia.org/wiki/Race_condition) because two asynchronous operations are "racing" with each other, and they might arrive in an unexpected order.
+이렇게 하면 Taylor가 선택되었음에도 불구하고 Bob의 일대기가 표시됩니다. 이와 같은 버그는 두 개의 비동기 작업이 "경쟁(race)"하며 작업 완료의 순서를 예상할 수 없는 [경쟁 조건(race condition)](https://en.wikipedia.org/wiki/Race_condition)이라고 합니다.
-To fix this race condition, add a cleanup function:
+이 경쟁 조건을 해결하려면 클린업 함수를 추가하세요.
@@ -1555,7 +1555,7 @@ export async function fetchBio(person) {
const delay = person === 'Bob' ? 2000 : 200;
return new Promise(resolve => {
setTimeout(() => {
- resolve('This is ' + person + '’s bio.');
+ resolve('이것은 ' + person + '의 일대기입니다.');
}, delay);
})
}
@@ -1564,16 +1564,16 @@ export async function fetchBio(person) {
-Each render's Effect has its own `ignore` variable. Initially, the `ignore` variable is set to `false`. However, if an Effect gets cleaned up (such as when you select a different person), its `ignore` variable becomes `true`. So now it doesn't matter in which order the requests complete. Only the last person's Effect will have `ignore` set to `false`, so it will call `setBio(result)`. Past Effects have been cleaned up, so the `if (!ignore)` check will prevent them from calling `setBio`:
+각 렌더링의 Effect는 자체 `ignore` 변수를 가지고 있습니다. 처음에 `ignore` 변수는 `false`로 설정됩니다. 그러나 Effect가 클린업되면(예: 다른 사람을 선택할 때), 해당 Effect의 `ignore` 변수는 `true`로 설정됩니다. 이제 어떤 순서로 요청이 완료되는지는 중요하지 않습니다. 마지막 사람의 Effect만 `ignore`가 `false`로 설정되므로 `setBio(result)`를 호출합니다. 이전 Effect는 정리되었으므로 `if (!ignore)` 검사가 `setBio` 호출을 방지합니다:
-- Selecting `'Bob'` triggers `fetchBio('Bob')`
-- Selecting `'Taylor'` triggers `fetchBio('Taylor')` **and cleans up the previous (Bob's) Effect**
-- Fetching `'Taylor'` completes *before* fetching `'Bob'`
-- The Effect from the `'Taylor'` render calls `setBio('This is Taylor’s bio')`
-- Fetching `'Bob'` completes
-- The Effect from the `'Bob'` render **does not do anything because its `ignore` flag was set to `true`**
+- `'Bob'`을 선택하면 `fetchBio('Bob')`가 트리거됩니다.
+- `'Taylor'`을 선택하면 `fetchBio('Taylor')`가 트리거되며 이전(Bob의) Effect가 **정리(cleaned up)**됩니다.
+- `'Taylor'`의 일대기를 가져오는 작업이 `'Bob'`의 일대기를 가져오는 작업보다 *먼저* 완료됩니다.
+- `'Taylor'` 렌더링의 Effect가 `setBio('This is Taylor’s bio')`를 호출합니다.
+- `'Bob'`의 일대기를 가져오는 작업이 완료됩니다.
+- `'Bob'` 렌더링의 Effect는 **`ignore` 플래그가 `true`로 설정되었기 때문에 아무 일도 수행하지 않습니다.**
-In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `ignore` is the most reliable way to fix this type of problems.
+오래된 API 호출의 결과를 무시하는 것 외에도 더 이상 필요하지 않은 요청을 취소하기 위해 [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)를 사용할 수도 있습니다. 그러나 이것만으로는 경쟁 조건에 대한 충분한 보호가 이뤄지지 않습니다. 피치 못할 상황에서는 추가적인 비동기 작업이 후행할 수 있으므로 `ignore`와 같은 명시적 플래그를 사용하는 것이 이러한 종류의 문제를 가장 안전하게 해결하는 가장 신뢰할 수 있는 방법입니다.
diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json
index e16d9355e..be8bd8755 100644
--- a/src/sidebarLearn.json
+++ b/src/sidebarLearn.json
@@ -169,7 +169,7 @@
"path": "/learn/manipulating-the-dom-with-refs"
},
{
- "title": "Effect로 값 동기화하기",
+ "title": "Effect로 동기화하기",
"path": "/learn/synchronizing-with-effects"
},
{