`의 `color`를 빨간색으로 설정할 수 있습니다. 마찬가지로 서로 다른 React context도 서로 재정의하지 않습니다. `createContext()`로 만드는 각 context는 다른 context와 완전히 분리되어 있으며, *특정* context를 사용하거나 제공하는 컴포넌트를 함께 묶습니다. 하나의 컴포넌트가 문제없이 다양한 context를 사용하거나 제공할 수 있습니다.
-## Before you use context {/*before-you-use-context*/}
+## Before you use context
context를 사용하기 전에 {/*before-you-use-context*/}
Context is very tempting to use! However, this also means it's too easy to overuse it. **Just because you need to pass some props several levels deep doesn't mean you should put that information into context.**
+
context는 사용하기 매우 유혹적입니다! 그러나 이는 또한 너무 쉽게 남용될 수 있다는 의미이기도 합니다.
+**props를 몇 단계 깊이 전달해야 한다고 해서 해당 정보를 context에 넣어야 한다는 의미는 아닙니다.**
Here's a few alternatives you should consider before using context:
+
다음은 context를 사용하기 전에 고려해야 할 몇 가지 대안입니다:
1. **Start by [passing props.](/learn/passing-props-to-a-component)** If your components are not trivial, it's not unusual to pass a dozen props down through a dozen components. It may feel like a slog, but it makes it very clear which components use which data! The person maintaining your code will be glad you've made the data flow explicit with props.
+
**[props 전달](/learn/passing-props-to-a-component)로 시작하세요.** 컴포넌트가 사소하지 않다면, 수십 개의 props를 수십 개의 컴포넌트에 전달해야 하는 경우가 드물지 않습니다. 지루하게 느껴질 수도 있지만, 어떤 컴포넌트가 어떤 데이터를 사용하는지 매우 명확해집니다! 코드를 유지 관리하는 사람은 props를 사용하여 데이터 흐름을 명확하게 만든 것에 만족할 것입니다.
2. **Extract components and [pass JSX as `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) to them.** If you pass some data through many layers of intermediate components that don't use that data (and only pass it further down), this often means that you forgot to extract some components along the way. For example, maybe you pass data props like `posts` to visual components that don't use them directly, like `
`. Instead, make `Layout` take `children` as a prop, and render `
`. This reduces the number of layers between the component specifying the data and the one that needs it.
+
컴포넌트를 추출하고 [JSX를 `children`](/learn/passing-props-to-a-component#passing-jsx-as-children)으로 전달하세요. 일부 데이터를 해당 데이터를 사용하지 않는 중간 컴포넌트의 여러 레이어를 거쳐 전달한다면(그리고 더 아래로만 전달한다면), 이는 종종 그 과정에서 일부 컴포넌트를 추출하는 것을 잊었다는 것을 의미합니다. 예를 들어, `posts`과 같은 데이터 props를 직접 사용하지 않는 시각적 컴포넌트에 `` 와 같은 방법 대신, Layout이 children을 prop으로 사용하도록 만들고 ``를 렌더링합니다. 이렇게 하면 데이터를 지정하는 컴포넌트와 데이터를 필요로 하는 컴포넌트 사이의 레이어 수가 줄어듭니다.
If neither of these approaches works well for you, consider context.
+
이 두 가지 접근 방식이 모두 적합하지 않은 경우 context를 고려하세요.
-## Use cases for context {/*use-cases-for-context*/}
+## Use cases for context
context 사용 사례 {/*use-cases-for-context*/}
* **Theming:** If your app lets the user change its appearance (e.g. dark mode), you can put a context provider at the top of your app, and use that context in components that need to adjust their visual look.
+
**테마:** 앱에서 사용자가 앱의 모양을 변경할 수 있는 경우(예: 다크 모드), 앱 상단에 context provider를 배치하고 시각적 모양을 조정해야 하는 컴포넌트에서 해당 context를 사용할 수 있습니다.
* **Current account:** Many components might need to know the currently logged in user. Putting it in context makes it convenient to read it anywhere in the tree. Some apps also let you operate multiple accounts at the same time (e.g. to leave a comment as a different user). In those cases, it can be convenient to wrap a part of the UI into a nested provider with a different current account value.
+
**현재 계정:** 많은 컴포넌트에서 현재 로그인한 사용자를 알아야 할 수 있습니다. 이 정보를 context에 넣으면 트리의 어느 곳에서나 편리하게 읽을 수 있습니다. 또한 일부 앱에서는 여러 계정을 동시에 조작할 수 있습니다(예: 다른 사용자로 댓글을 남기는 경우). 이러한 경우 UI의 일부를 다른 현재 계정 값으로 중첩된 provider로 감싸는 것이 편리할 수 있습니다.
* **Routing:** Most routing solutions use context internally to hold the current route. This is how every link "knows" whether it's active or not. If you build your own router, you might want to do it too.
+
**라우팅:** 대부분의 라우팅 솔루션은 내부적으로 context를 사용하여 현재 경로를 유지합니다. 이것이 모든 링크가 활성 상태인지 아닌지를 "아는" 방식입니다. 자체 라우터를 구축하는 경우에도 이러한 방식을 사용할 수 있습니다.
* **Managing state:** As your app grows, you might end up with a lot of state closer to the top of your app. Many distant components below may want to change it. It is common to [use a reducer together with context](/learn/scaling-up-with-reducer-and-context) to manage complex state and pass it down to distant components without too much hassle.
+
**state 관리**: 앱이 성장함에 따라 앱 상단에 많은 state가 가까워질 수 있습니다. 아래에 있는 많은 멀리 떨어진 컴포넌트에서 이를 변경하고 싶을 수 있습니다. [context와 함께 reducer를 사용](/learn/scaling-up-with-reducer-and-context)하여 복잡한 state를 관리하고 번거로움 없이 멀리 떨어진 컴포넌트에 전달하는 것이 일반적입니다.
Context is not limited to static values. If you pass a different value on the next render, React will update all the components reading it below! This is why context is often used in combination with state.
+
Context는 정적 값에만 국한되지 않습니다. 다음 렌더링에서 다른 값을 전달하면 React는 아래에서 이를 읽는 모든 컴포넌트를 업데이트합니다! 이것이 context가 state와 함께 자주 사용되는 이유입니다.
In general, if some information is needed by distant components in different parts of the tree, it's a good indication that context will help you.
+
일반적으로 트리의 다른 부분에 있는 멀리 떨어진 컴포넌트에서 일부 정보가 필요한 경우 context가 도움이 될 수 있다는 좋은 신호입니다.
-
* Context lets a component provide some information to the entire tree below it.
* To pass context:
1. Create and export it with `export const MyContext = createContext(defaultValue)`.
@@ -873,17 +945,30 @@ In general, if some information is needed by distant components in different par
* Context lets you write components that "adapt to their surroundings".
* Before you use context, try passing props or passing JSX as `children`.
+
+* Context는 컴포넌트가 그 아래 전체 트리에 일부 정보를 제공할 수 있도록 합니다.
+* context를 전달하려면:
+ 1. `export const MyContext = createContext(defaultValue)`를 사용하여 context를 생성하고 내보냅니다.
+ 2. `useContext(MyContext)` 훅에 전달하여 깊이에 상관없이 모든 하위 컴포넌트에서 읽을 수 있도록 합니다.
+ 3. 자식 컴포넌트를 ``로 감싸서 부모로부터 제공받습니다.
+* Context는 중간에 있는 모든 컴포넌트를 통과합니다.
+* Context를 사용하면 "주변 환경에 적응"하는 컴포넌트를 작성할 수 있습니다.
+* context를 사용하기 전에 props를 전달하거나 JSX를 `children`으로 전달해 보세요.
+
-#### Replace prop drilling with context {/*replace-prop-drilling-with-context*/}
+#### Replace prop drilling with contextcontext로 prop drilling 바꾸기 {/*replace-prop-drilling-with-context*/}
In this example, toggling the checkbox changes the `imageSize` prop passed to each ``. The checkbox state is held in the top-level `App` component, but each `` needs to be aware of it.
+이 예제에서 체크박스를 토글하면 각 ``에 전달된 `imageSize` prop이 변경됩니다. 체크박스 state는 최상위 App 컴포넌트에 유지되지만, 각 ``는 이를 인식해야 합니다.
Currently, `App` passes `imageSize` to `List`, which passes it to each `Place`, which passes it to the `PlaceImage`. Remove the `imageSize` prop, and instead pass it from the `App` component directly to `PlaceImage`.
+현재 `App`은 `imageSize`를 `List`에 전달하고, `List`는 이를 각 `Place`에 전달하며, 각 `Place`는 이를 `PlaceImage`에 전달합니다. `imageSize` prop을 제거하고 대신 App 컴포넌트에서 `PlaceImage`로 직접 전달하도록 변경해보세요.
You can declare context in `Context.js`.
+context는 `Context.js`에서 선언할 수 있습니다.
@@ -1021,8 +1106,10 @@ li {
Remove `imageSize` prop from all the components.
+모든 컴포넌트에서 `imageSize` props을 제거합니다.
Create and export `ImageSizeContext` from `Context.js`. Then wrap the List into `` to pass the value down, and `useContext(ImageSizeContext)` to read it in the `PlaceImage`:
+`Context.js`에서 `ImageSizeContext`를 생성하고 내보냅니다. 그런 다음 List 컴포넌트를 ``로 감싸서 값을 전달하고 `useContext(ImageSizeContext)`를 `PlaceImage`에서 읽어오도록 합니다:
@@ -1158,7 +1245,8 @@ li {
Note how components in the middle don't need to pass `imageSize` anymore.
+중간에 있는 컴포넌트에는 더이상 `imageSize`를 전달할 필요가 없다는점에 유의하세요.
-
+
\ No newline at end of file
diff --git a/src/content/learn/preserving-and-resetting-state.md b/src/content/learn/preserving-and-resetting-state.md
index 21468859735..4e167d2222e 100644
--- a/src/content/learn/preserving-and-resetting-state.md
+++ b/src/content/learn/preserving-and-resetting-state.md
@@ -1,11 +1,14 @@
---
title: Preserving and Resetting State
+translatedTitle: state 보존 및 재설정
---
State is isolated between components. React keeps track of which state belongs to which component based on their place in the UI tree. You can control when to preserve state and when to reset it between re-renders.
+state는 컴포넌트 간에 격리됩니다. React는 UI 트리에서 어떤 컴포넌트가 어떤 state에 속하는지를 추적합니다. state를 언제 보존하고 언제 초기화할지를 제어할 수 있습니다.
+
@@ -15,30 +18,41 @@ State is isolated between components. React keeps track of which state belongs t
* How to force React to reset component's state
* How keys and types affect whether the state is preserved
+
+- React가 컴포넌트 구조를 "보는" 방법
+- React가 state를 유지하거나 재설정하도록 선택할 때
+- React가 컴포넌트의 state를 재설정하도록 강제하는 방법
+- key와 type이 state 보존 여부에 영향을 미치는 방법
+
+
-## The UI tree {/*the-ui-tree*/}
+## The UI tree
UI 트리 {/*the-ui-tree*/}
Browsers use many tree structures to model UI. The [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) represents HTML elements, the [CSSOM](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model) does the same for CSS. There's even an [Accessibility tree](https://developer.mozilla.org/docs/Glossary/Accessibility_tree)!
+
브라우저는 UI를 모델링하기 위해 많은 트리 구조를 사용합니다. [DOM](https://developer.mozilla.org/ko/docs/Web/API/Document_Object_Model/Introduction)은 HTML 요소를 나타내고, [CSSOM](https://developer.mozilla.org/ko/docs/Web/API/CSS_Object_Model)은 CSS에 대해 동일한 역할을 합니다. 심지어 [접근성 트리](https://developer.mozilla.org/en-US/docs/Glossary/Accessibility_tree)도 있습니다!
React also uses tree structures to manage and model the UI you make. React makes **UI trees** from your JSX. Then React DOM updates the browser DOM elements to match that UI tree. (React Native translates these trees into elements specific to mobile platforms.)
+
React 또한 트리 구조를 사용하여 사용자가 만든 UI를 관리하고 모델링합니다. React는 JSX로부터 UI 트리를 만듭니다. 그런 다음 React DOM은 해당 UI 트리와 일치하도록 브라우저 DOM 엘리먼트를 업데이트합니다. (React Native는 이러한 트리를 모바일 플랫폼에 맞는 엘리먼트로 변환합니다.)
From components, React creates a UI tree which React DOM uses to render the DOM
-
+컴포넌트에서 React는 UI 트리를 생성하고, 이 트리는 React DOM이 DOM을 렌더링하는 데 사용됩니다.
-## State is tied to a position in the tree {/*state-is-tied-to-a-position-in-the-tree*/}
+## State is tied to a position in the tree
state는 트리의 한 위치에 묶입니다 {/*state-is-tied-to-a-position-in-the-tree*/}
When you give a component state, you might think the state "lives" inside the component. But the state is actually held inside React. React associates each piece of state it's holding with the correct component by where that component sits in the UI tree.
+
컴포넌트에 state를 부여할 때, state가 컴포넌트 내부에 "존재"한다고 생각할 수 있습니다. 하지만 state는 실제로 React 내부에서 유지됩니다. React는 UI 트리에서 해당 컴포넌트가 어디에 위치하는지에 따라 보유하고 있는 각 state를 올바른 컴포넌트와 연결합니다.
Here, there is only one `
` JSX tag, but it's rendered at two different positions:
+
여기에는 `` JSX 태그가 하나만 있지만 두 개의 다른 위치에서 렌더링됩니다:
@@ -102,23 +116,26 @@ label {
-Here's how these look as a tree:
+Here's how these look as a tree:
+
트리로 표시되는 모습은 다음과 같습니다:
React tree
-
**These are two separate counters because each is rendered at its own position in the tree.** You don't usually have to think about these positions to use React, but it can be useful to understand how it works.
+
**이 카운터는 각 트리에서 고유한 위치에 렌더링되기 때문에 두 개의 개별 카운터입니다.** 일반적으로 React를 사용하기 위해 이러한 위치에 대해 생각할 필요는 없지만, 작동 방식을 이해하는 것이 유용할 수 있습니다.
In React, each component on the screen has fully isolated state. For example, if you render two `Counter` components side by side, each of them will get its own, independent, `score` and `hover` states.
+
React에서 화면의 각 컴포넌트는 완전히 분리된 상태를 갖습니다. 예를 들어 두 개의 `Counter` 컴포넌트를 나란히 렌더링하면 각각 독립적인 `score` 및 `hover` 상태를 갖게 됩니다.
Try clicking both counters and notice they don't affect each other:
+
두 counter를 모두 클릭해 보면 서로 영향을 미치지 않는 것을 확인할 수 있습니다:
@@ -177,7 +194,7 @@ function Counter() {
As you can see, when one counter is updated, only the state for that component is updated:
-
+
보시다시피 counter 하나가 업데이트되면 해당 컴포넌트에 대한 상태만 업데이트됩니다:
@@ -191,6 +208,7 @@ Updating state
React will keep the state around for as long as you render the same component at the same position. To see this, increment both counters, then remove the second component by unchecking "Render the second counter" checkbox, and then add it back by ticking it again:
+React는 같은 컴포넌트를 같은 위치에 렌더링하는 한 그 state를 유지합니다. 이를 확인하려면 두 카운터를 모두 증가시킨 다음 "두 번째 counter 렌더링" 체크박스를 선택 해제하여 두 번째 컴포넌트를 제거한 다음 다시 선택하여 다시 추가합니다:
@@ -265,6 +283,7 @@ label {
Notice how the moment you stop rendering the second counter, its state disappears completely. That's because when React removes a component, it destroys its state.
+두 번째 counter 렌더링을 중지하는 순간 state가 완전히 사라지는 것에 주목하세요. React가 컴포넌트를 제거하면 그 state가 사라지기 때문입니다.
@@ -277,6 +296,7 @@ Deleting a component
When you tick "Render the second counter", a second `Counter` and its state are initialized from scratch (`score = 0`) and added to the DOM.
+"두 번째 counter 렌더링"을 선택하면 두 번째 `Counter`와 그 state가 처음부터 초기화되고(`score = 0`) DOM에 추가됩니다.
@@ -289,10 +309,12 @@ Adding a component
**React preserves a component's state for as long as it's being rendered at its position in the UI tree.** If it gets removed, or a different component gets rendered at the same position, React discards its state.
+React는 컴포넌트가 UI 트리의 해당 위치에서 렌더링되는 동안 컴포넌트의 state를 유지합니다. 컴포넌트가 제거되거나 같은 위치에 다른 컴포넌트가 렌더링되면 React는 해당 컴포넌트의 state를 삭제합니다.
-## Same component at the same position preserves state {/*same-component-at-the-same-position-preserves-state*/}
+## Same component at the same position preserves state동일한 위치의 동일한 컴포넌트는 state를 유지합니다 {/*same-component-at-the-same-position-preserves-state*/}
In this example, there are two different `` tags:
+다음 예제에는 두 개의 서로 다른 `` 태그가 있습니다:
@@ -378,23 +400,26 @@ label {
When you tick or clear the checkbox, the counter state does not get reset. Whether `isFancy` is `true` or `false`, you always have a `` as the first child of the `div` returned from the root `App` component:
+체크박스를 선택하거나 선택 취소해도 카운터 state는 재설정되지 않습니다. `isFancy`가 `true`이든 `false`이든, 루트 `App` 컴포넌트에서 반환된 `div`의 첫 번째 자식에는 항상 ``가 있습니다:
Updating the `App` state does not reset the `Counter` because `Counter` stays in the same position
+`Counter`가 동일 위치에 있으므로 `App`의 state를 업데이트해도 `Counter`는 재설정되지 않음
-
It's the same component at the same position, so from React's perspective, it's the same counter.
+같은 위치에 있는 같은 컴포넌트이므로 React의 관점에서 보면 같은 카운터입니다.
Remember that **it's the position in the UI tree--not in the JSX markup--that matters to React!** This component has two `return` clauses with different `` JSX tags inside and outside the `if`:
+**React에서 중요한 것은 JSX 마크업이 아니라 UI 트리에서의 위치라는 것을 기억하세요!** 이 컴포넌트에는 `if` 내부와 외부에 서로 다른 `` JSX 태그가 있는 두 개의 `return`절이 있습니다:
@@ -493,14 +518,17 @@ label {
You might expect the state to reset when you tick checkbox, but it doesn't! This is because **both of these `` tags are rendered at the same position.** React doesn't know where you place the conditions in your function. All it "sees" is the tree you return.
+checkbox를 선택하면 state가 재설정될 것으로 예상할 수 있지만 그렇지 않습니다! 이 **두 `` 태그가 모두 같은 위치에 렌더링되기 때문입니다.** React는 함수에서 조건을 어디에 배치했는지 알지 못합니다. 단지 여러분이 반환하는 트리만 볼 수 있을 뿐입니다.
In both cases, the `App` component returns a `` with `
` as a first child. To React, these two counters have the same "address": the first child of the first child of the root. This is how React matches them up between the previous and next renders, regardless of how you structure your logic.
+
두 경우 모두 `App` 컴포넌트는 ``를 첫 번째 자식으로 가진 ``를 반환합니다. 리액트에서 이 두 카운터는 루트의 첫 번째 자식의 첫 번째 자식이라는 동일한 "주소"를 갖습니다. React는 로직을 어떻게 구성하든 상관없이 이전 렌더링과 다음 렌더링 사이에서 이 방법으로 이들을 일치시킬 수 있습니다.
-## Different components at the same position reset state {/*different-components-at-the-same-position-reset-state*/}
+## Different components at the same position reset state
동일한 위치의 다른 컴포넌트는 state를 초기화합니다 {/*different-components-at-the-same-position-reset-state*/}
In this example, ticking the checkbox will replace `
` with a ``:
+아래 예제에서 확인란을 선택하면 ``가 ``로 바뀝니다:
@@ -578,13 +606,14 @@ label {
Here, you switch between _different_ component types at the same position. Initially, the first child of the `
` contained a `Counter`. But when you swapped in a `p`, React removed the `Counter` from the UI tree and destroyed its state.
+
여기서는 같은 위치에서 서로 다른 컴포넌트 유형 사이를 전환합니다. 처음에 ``의 첫 번째 자식에는 `Counter`가 있었습니다. 하지만 `p`를 넣었을 때 React는 UI 트리에서 `Counter`를 제거하고 그 상태를 소멸시켰습니다.
When `Counter` changes to `p`, the `Counter` is deleted and the `p` is added
-
+`Counter`가 `p`로 변경되면 `Counter`가 삭제되고 `p`가 추가됨
@@ -594,12 +623,13 @@ When `Counter` changes to `p`, the `Counter` is deleted and the `p` is added
When switching back, the `p` is deleted and the `Counter` is added
-
+다시 전환하면 `p`가 삭제되고 `Counter`가 추가됨
Also, **when you render a different component in the same position, it resets the state of its entire subtree.** To see how this works, increment the counter and then tick the checkbox:
+
또한 **같은 위치에 다른 컴포넌트를 렌더링하면 전체 하위 트리의 state가 재설정됩니다.** 어떻게 작동하는지 확인하려면 카운터를 증가시킨 다음 확인란을 선택합니다:
@@ -689,13 +719,14 @@ label {
The counter state gets reset when you click the checkbox. Although you render a `Counter`, the first child of the `div` changes from a `div` to a `section`. When the child `div` was removed from the DOM, the whole tree below it (including the `Counter` and its state) was destroyed as well.
+
확인란을 클릭하면 counter state가 재설정됩니다. `Counter`를 렌더링하더라도 `div`의 첫 번째 자식은 `div`에서 `section`으로 변경됩니다. 자식 `div`가 DOM에서 제거되면 그 아래의 전체 트리(카운터 및 해당 상태 포함)도 함께 제거됩니다.
When `section` changes to `div`, the `section` is deleted and the new `div` is added
-
+`section`이 `div`로 변경되면 `section`이 삭제되고 새 `div`가 추가됨
@@ -705,18 +736,21 @@ When `section` changes to `div`, the `section` is deleted and the new `div` is a
When switching back, the `div` is deleted and the new `section` is added
-
+다시 전환하면 `div`가 삭제되고 새 `section`이 추가됨
As a rule of thumb, **if you want to preserve the state between re-renders, the structure of your tree needs to "match up"** from one render to another. If the structure is different, the state gets destroyed because React destroys state when it removes a component from the tree.
+
경험칙상 **재랜더링 사이에 state를 유지하려면 트리의 구조가 "일치"해야 합니다**. 구조가 다르면 React는 트리에서 컴포넌트를 제거할 때 state를 파괴하기 때문입니다.
This is why you should not nest component function definitions.
+그렇기 때문에 컴포넌트 함수 정의를 중첩해서는 안 됩니다.
Here, the `MyTextField` component function is defined *inside* `MyComponent`:
+여기서는 `MyTextField` 컴포넌트 함수가 `MyComponent` 안에 정의되어 있습니다:
@@ -753,11 +787,14 @@ export default function MyComponent() {
Every time you click the button, the input state disappears! This is because a *different* `MyTextField` function is created for every render of `MyComponent`. You're rendering a *different* component in the same position, so React resets all state below. This leads to bugs and performance problems. To avoid this problem, **always declare component functions at the top level, and don't nest their definitions.**
+버튼을 클릭할 때마다 입력 state가 사라집니다! 이는 `MyComponent`를 렌더링할 때마다 다른 `MyTextField` 함수가 생성되기 때문입니다. 같은 위치에 다른 컴포넌트를 렌더링하기 때문에 React는 아래의 모든 state를 초기화합니다. 이로 인해 버그와 성능 문제가 발생합니다. 이 **문제를 방지하려면 항상 컴포넌트 함수를 최상위 수준에서 선언하고 정의를 중첩하지 마세요.**
+
-## Resetting state at the same position {/*resetting-state-at-the-same-position*/}
+## Resetting state at the same position
동일한 위치에서 state 재설정하기 {/*resetting-state-at-the-same-position*/}
By default, React preserves state of a component while it stays at the same position. Usually, this is exactly what you want, so it makes sense as the default behavior. But sometimes, you may want to reset a component's state. Consider this app that lets two players keep track of their scores during each turn:
+
기본적으로 React는 컴포넌트가 같은 위치에 있는 동안 컴포넌트의 state를 보존합니다. 일반적으로 이것은 사용자가 원하는 것이므로 기본 동작으로 적합합니다. 하지만 때로는 컴포넌트의 state를 리셋하고 싶을 때가 있습니다. 두 명의 플레이어가 각 턴 동안 점수를 추적할 수 있는 이 앱을 예로 들어보겠습니다:
@@ -828,18 +865,26 @@ h1 {
Currently, when you change the player, the score is preserved. The two `Counter`s appear in the same position, so React sees them as *the same* `Counter` whose `person` prop has changed.
+
현재 플레이어를 변경하면 점수가 보존됩니다. 두 `Counter`는 같은 위치에 표시되므로 React는 `person` prop이 변경된 동일한 `Counter`로 간주합니다.
But conceptually, in this app they should be two separate counters. They might appear in the same place in the UI, but one is a counter for Taylor, and another is a counter for Sarah.
+
하지만 개념적으로 이 앱에서는 두 개의 별도 카운터가 있어야 합니다. UI에서 같은 위치에 표시될 수도 있지만 하나는 Taylor의 카운터이고 다른 하나는 Sarah의 카운터입니다.
There are two ways to reset state when switching between them:
+
전환할 때 상태를 재설정하는 방법에는 두 가지가 있습니다:
1. Render components in different positions
2. Give each component an explicit identity with `key`
+
+ 1. 컴포넌트를 다른 위치에 렌더링하기
+ 2. 각 컴포넌트에 `key`로 명시적인 아이덴티티를 부여합니다.
+
-### Option 1: Rendering a component in different positions {/*option-1-rendering-a-component-in-different-positions*/}
+### Option 1: Rendering a component in different positions
컴포넌트를 다른 위치에 렌더링하기 {/*option-1-rendering-a-component-in-different-positions*/}
If you want these two `Counter`s to be independent, you can render them in two different positions:
+
이 두 `Counter`를 독립적으로 만들려면 두 개의 다른 위치에 렌더링하면 됩니다:
@@ -913,6 +958,11 @@ h1 {
* Initially, `isPlayerA` is `true`. So the first position contains `Counter` state, and the second one is empty.
* When you click the "Next player" button the first position clears but the second one now contains a `Counter`.
+
+- 처음에는 `isPlayerA`가 `true`입니다. 따라서 첫 번째 위치에는 `Counter` 상태가 포함되고 두 번째 위치는 비어 있습니다.
+- "Next player" 버튼을 클릭하면 첫 번째 위치는 지워지지만 두 번째 위치에는 이제 `Counter`가 포함됩니다.
+
+
@@ -936,16 +986,21 @@ Clicking "next" again
Each `Counter`'s state gets destroyed each time its removed from the DOM. This is why they reset every time you click the button.
+각 `Counter`의 state는 DOM에서 제거될 때마다 소멸됩니다. 그렇기 때문에 버튼을 클릭할 때마다 초기화됩니다.
This solution is convenient when you only have a few independent components rendered in the same place. In this example, you only have two, so it's not a hassle to render both separately in the JSX.
+이 솔루션은 같은 위치에 몇 개의 독립적인 컴포넌트만 렌더링할 때 편리합니다. 이 예시에서는 두 개만 있으므로 JSX에서 두 컴포넌트를 별도로 렌더링하는 것이 번거롭지 않습니다.
-### Option 2: Resetting state with a key {/*option-2-resetting-state-with-a-key*/}
+### Option 2: Resetting state with a keykey로 state 재설정하기 {/*option-2-resetting-state-with-a-key*/}
There is also another, more generic, way to reset a component's state.
+컴포넌트의 state를 재설정하는 더 일반적인 방법도 있습니다.
You might have seen `key`s when [rendering lists.](/learn/rendering-lists#keeping-list-items-in-order-with-key) Keys aren't just for lists! You can use keys to make React distinguish between any components. By default, React uses order within the parent ("first counter", "second counter") to discern between components. But keys let you tell React that this is not just a *first* counter, or a *second* counter, but a specific counter--for example, *Taylor's* counter. This way, React will know *Taylor's* counter wherever it appears in the tree!
+[목록을 렌더링](https://www.notion.so/1-7-Rendering-Lists-25246ef00d14407fb113ea66961946b3)할 때 `key`를 본 적이 있을 것입니다. key는 목록에만 사용되는 것이 아닙니다! key를 사용해 React가 모든 컴포넌트를 구분하도록 할 수 있습니다. 기본적으로 React는 부모 내의 순서("첫 번째 counter", "두 번째 counter")를 사용해 컴포넌트를 구분합니다. 하지만 key를 사용하면 이것이 첫 번째 counter나 두 번째 counter가 아니라 특정 counter(예: Taylor의 counter)임을 React에 알릴 수 있습니다. 이렇게 하면 React는 테일러의 counter가 트리에 어디에 나타나든 알 수 있습니다!
In this example, the two ``s don't share state even though they appear in the same place in JSX:
+다음 예제에서는 두 ``가 JSX에서 같은 위치에 표시되지만 state를 공유하지 않습니다:
@@ -1016,6 +1071,7 @@ h1 {
Switching between Taylor and Sarah does not preserve the state. This is because **you gave them different `key`s:**
+테일러와 사라 사이를 전환해도 state가 유지되지 않습니다. **서로 다른 `key`를 부여했기 때문입니다:**
```js
{isPlayerA ? (
@@ -1026,18 +1082,22 @@ Switching between Taylor and Sarah does not preserve the state. This is because
```
Specifying a `key` tells React to use the `key` itself as part of the position, instead of their order within the parent. This is why, even though you render them in the same place in JSX, React sees them as two different counters, and so they will never share state. Every time a counter appears on the screen, its state is created. Every time it is removed, its state is destroyed. Toggling between them resets their state over and over.
+`key`를 지정하면 React가 부모 내 순서가 아닌 `key` 자체를 위치의 일부로 사용하도록 지시합니다. 그렇기 때문에 JSX에서 같은 위치에 렌더링하더라도 React의 관점에서 보면 두 카운터는 서로 다른 카운터입니다. 결과적으로 state를 공유하지 않습니다. 카운터가 화면에 나타날 때마다 그 상태가 생성됩니다. 카운터가 제거될 때마다 그 state는 소멸됩니다. 두 카운터 사이를 토글하면 state가 계속 초기화됩니다.
Remember that keys are not globally unique. They only specify the position *within the parent*.
+키는 전역으로 고유하지는 않다는 점을 기억하세요. 키는 *부모 내에서의* 위치만 지정합니다.
-### Resetting a form with a key {/*resetting-a-form-with-a-key*/}
+### Resetting a form with a key키로 form 재설정하기 {/*resetting-a-form-with-a-key*/}
Resetting state with a key is particularly useful when dealing with forms.
+키로 state를 재설정하는 것은 form을 다룰 때 특히 유용합니다
In this chat app, the `` component contains the text input state:
+이 채팅 앱에서 `` 컴포넌트는 텍스트 입력 상태를 포함합니다:
@@ -1133,16 +1193,20 @@ textarea {
Try entering something into the input, and then press "Alice" or "Bob" to choose a different recipient. You will notice that the input state is preserved because the `` is rendered at the same position in the tree.
+입력란에 무언가를 입력한 다음 "앨리스" 또는 "밥"을 눌러 다른 수신자를 선택하세요. ``이 트리의 동일한 위치에 렌더링되므로 입력 state가 유지되는 것을 알 수 있습니다.
**In many apps, this may be the desired behavior, but not in a chat app!** You don't want to let the user send a message they already typed to a wrong person due to an accidental click. To fix it, add a `key`:
+**많은 앱에서 이러한 동작이 바람직할 수 있지만, 채팅 앱에서는 그렇지 않습니다!** 사용자가 실수로 클릭해서 이미 입력한 메시지를 엉뚱한 사람에게 보내는 것을 원치 않을 것입니다. 이 문제를 해결하려면 `key`를 추가하세요:
```js
```
This ensures that when you select a different recipient, the `Chat` component will be recreated from scratch, including any state in the tree below it. React will also re-create the DOM elements instead of reusing them.
+이렇게 하면 다른 수신자를 선택하면 `Chat` 컴포넌트가 그 아래 트리의 모든 state를 포함하여 처음부터 다시 생성됩니다. 또한 React는 DOM 엘리먼트를 재사용하는 대신 다시 생성합니다.
Now switching the recipient always clears the text field:
+이제 수신자를 전환하면 항상 텍스트 필드가 지워집니다:
@@ -1239,15 +1303,24 @@ textarea {
-#### Preserving state for removed components {/*preserving-state-for-removed-components*/}
+#### Preserving state for removed components제거된 컴포넌트에 대한 state 보존 {/*preserving-state-for-removed-components*/}
In a real chat app, you'd probably want to recover the input state when the user selects the previous recipient again. There are a few ways to keep the state "alive" for a component that's no longer visible:
+실제 채팅 앱에서는 사용자가 이전 수신자를 다시 선택할 때 입력 state를 복구하고 싶을 것입니다. 더 이상 표시되지 않는 컴포넌트의 state를 '살아있게' 유지하는 몇 가지 방법이 있습니다:
- You could render _all_ chats instead of just the current one, but hide all the others with CSS. The chats would not get removed from the tree, so their local state would be preserved. This solution works great for simple UIs. But it can get very slow if the hidden trees are large and contain a lot of DOM nodes.
- You could [lift the state up](/learn/sharing-state-between-components) and hold the pending message for each recipient in the parent component. This way, when the child components get removed, it doesn't matter, because it's the parent that keeps the important information. This is the most common solution.
- You might also use a different source in addition to React state. For example, you probably want a message draft to persist even if the user accidentally closes the page. To implement this, you could have the `Chat` component initialize its state by reading from the [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), and save the drafts there too.
+
+
+- 현재 채팅만 렌더링하는 것이 아니라 모든 채팅을 렌더링하되 다른 모든 채팅은 CSS로 숨길 수 있습니다. 채팅은 트리에서 제거되지 않으므로 로컬 state가 유지됩니다. 이 솔루션은 간단한 UI에 적합합니다. 하지만 숨겨진 트리가 크고 많은 DOM 노드를 포함하는 경우 속도가 매우 느려질 수 있습니다.
+- 부모 컴포넌트에서 각 수신자에 대한 보류 중인 메시지를 [state를 끌어올려서](https://www.notion.so/3-3-state-Sharing-State-Between-Components-84873ee0bb1a4a92bdc4c355d12d765c) 보관할 수 있습니다. 이렇게 하면 자식 컴포넌트가 제거되더라도 중요한 정보를 보관하는 것은 부모 컴포넌트이므로 문제가 되지 않습니다. 이것이 가장 일반적인 해결책입니다.
+- React state 외에 다른 소스를 사용할 수도 있습니다. 예를 들어 사용자가 실수로 페이지를 닫아도 메시지 초안이 유지되기를 원할 수 있습니다. 이를 구현하기 위해 `Chat` 컴포넌트가 `localStorage`에서 읽어서 state를 초기화하고 초안도 저장하도록 할 수 있습니다.
+
+
No matter which strategy you pick, a chat _with Alice_ is conceptually distinct from a chat _with Bob_, so it makes sense to give a `key` to the `` tree based on the current recipient.
+어떤 전략을 선택하든 앨리스와의 채팅은 밥과의 채팅과 개념적으로 구별되므로 현재 수신자를 기준으로 `` 트리에 `key`를 부여하는 것이 합리적입니다.
@@ -1258,15 +1331,23 @@ No matter which strategy you pick, a chat _with Alice_ is conceptually distinct
- You can force a subtree to reset its state by giving it a different key.
- Don't nest component definitions, or you'll reset state by accident.
+
+ - React는 동일한 컴포넌트가 동일한 위치에서 렌더링되는 한 state를 유지합니다.
+ - state는 JSX 태그에 보관되지 않습니다. JSX를 넣은 트리 위치와 연관되어 있습니다.
+ - 하위 트리에 다른 key를 지정하여 강제로 state를 재설정할 수 있습니다.
+ - 컴포넌트 정의를 중첩하지 마세요. 실수로 state가 초기화될 수 있습니다.
+
+
-#### Fix disappearing input text {/*fix-disappearing-input-text*/}
+#### Fix disappearing input text사라지는 입력 텍스트 수정하기 {/*fix-disappearing-input-text*/}
This example shows a message when you press the button. However, pressing the button also accidentally resets the input. Why does this happen? Fix it so that pressing the button does not reset the input text.
+이 예는 버튼을 누르면 메시지가 표시되는 것을 보여줍니다. 그러나 버튼을 누르면 실수로 입력이 재설정됩니다. 왜 이런 일이 발생하나요? 버튼을 눌러도 입력 텍스트가 재설정되지 않도록 수정하세요.
@@ -1316,8 +1397,10 @@ textarea { display: block; margin: 10px 0; }
The problem is that `Form` is rendered in different positions. In the `if` branch, it is the second child of the ``, but in the `else` branch, it is the first child. Therefore, the component type in each position changes. The first position changes between holding a `p` and a `Form`, while the second position changes between holding a `Form` and a `button`. React resets the state every time the component type changes.
+
문제는 `Form`이 다른 위치에서 렌더링된다는 것입니다. `if` 브랜치에서는 ``의 두 번째 자식이지만 `else` 브랜치에서는 첫 번째 자식입니다. 따라서 각 위치의 컴포넌트 유형이 변경됩니다. 첫 번째 위치는 `p`와 `Form`이 있을 때 바뀌고, 두 번째 위치는 `Form`과 `button`이 있을 때 바뀝니다. React는 컴포넌트 타입이 변경될 때마다 상태를 초기화합니다.
The easiest solution is to unify the branches so that `Form` always renders in the same position:
+
가장 쉬운 해결책은 `Form`이 항상 같은 위치에서 렌더링되도록 브랜치를 통합하는 것입니다:
@@ -1364,6 +1447,7 @@ textarea { display: block; margin: 10px 0; }
Technically, you could also add `null` before `` in the `else` branch to match the `if` branch structure:
+엄밀히 말하면, `else` 브랜치에서 `` 앞에 `null`을 추가하여 `if` 브랜치 구조와 일치시킬 수도 있습니다:
@@ -1412,19 +1496,22 @@ textarea { display: block; margin: 10px 0; }
This way, `Form` is always the second child, so it stays in the same position and keeps its state. But this approach is much less obvious and introduces a risk that someone else will remove that `null`.
+이렇게 하면 `Form`은 항상 두 번째 자식이므로 같은 위치에 머물며 상태를 유지합니다. 하지만 이 접근 방식은 훨씬 덜 명확하고 다른 사람이 `null`을 제거할 위험이 있습니다.
-#### Swap two form fields {/*swap-two-form-fields*/}
+#### Swap two form fields두 form 필드 교체하기 {/*swap-two-form-fields*/}
This form lets you enter first and last name. It also has a checkbox controlling which field goes first. When you tick the checkbox, the "Last name" field will appear before the "First name" field.
+이 양식에서는 이름과 성을 입력할 수 있습니다. 또한 어떤 필드가 먼저 표시되는지 제어하는 확인란도 있습니다. 확인란을 선택하면 '성' 필드가 '이름' 필드 앞에 표시됩니다.
It almost works, but there is a bug. If you fill in the "First name" input and tick the checkbox, the text will stay in the first input (which is now "Last name"). Fix it so that the input text *also* moves when you reverse the order.
+거의 작동하지만 버그가 있습니다. "이름" 입력란을 채우고 checkbox를 클릭하면 텍스트가 첫 번째 입력란(현재는 "성")에 그대로 유지됩니다. 순서를 바꿀 때 입력 텍스트도 이동하도록 수정하세요.
It seems like for these fields, their position within the parent is not enough. Is there some way to tell React how to match up the state between re-renders?
-
+이러한 필드의 경우 부모 내에서의 위치만으로는 충분하지 않은 것 같습니다. 리랜더링 간에 상태를 일치시키는 방법을 React에 알려줄 방법이 있을까요?
@@ -1459,8 +1546,8 @@ export default function App() {
{checkbox}
>
- );
- }
+ );
+ }
}
function Field({ label }) {
@@ -1488,7 +1575,7 @@ label { display: block; margin: 10px 0; }
Give a `key` to both `` components in both `if` and `else` branches. This tells React how to "match up" the correct state for either `` even if their order within the parent changes:
-
+`if`와 `else` 브랜치 모두에 있는 두 `` 컴포넌트에 `key`를 부여하세요. 이렇게 하면 부모 내 순서가 변경되더라도 두 ``의 올바른 상태를 "일치"시키는 방법을 React에 알려줍니다:
```js App.js
@@ -1521,8 +1608,8 @@ export default function App() {
{checkbox}
>
- );
- }
+ );
+ }
}
function Field({ label }) {
@@ -1549,11 +1636,13 @@ label { display: block; margin: 10px 0; }
-#### Reset a detail form {/*reset-a-detail-form*/}
+#### Reset a detail form상세 form 재설정하기 {/*reset-a-detail-form*/}
This is an editable contact list. You can edit the selected contact's details and then either press "Save" to update it, or "Reset" to undo your changes.
+수정 가능한 연락처 목록입니다. 선택한 연락처의 세부 정보를 수정한 다음 'Save'을 눌러 업데이트하거나 'Reset'을 눌러 변경 내용을 취소할 수 있습니다.
When you select a different contact (for example, Alice), the state updates but the form keeps showing the previous contact's details. Fix it so that the form gets reset when the selected contact changes.
+다른 연락처(예: Alice)를 선택하면 state는 업데이트되지만 양식에 이전 연락처의 세부 정보가 계속 표시됩니다. 선택한 연락처가 변경되면 양식이 재설정되도록 수정하세요.
@@ -1706,6 +1795,7 @@ button {
Give `key={selectedId}` to the `EditContact` component. This way, switching between different contacts will reset the form:
+`EditContact` 컴포넌트에 `key={selectedId}`를 지정합니다. 이렇게 하면 다른 연락처 사이를 전환할 때 form이 재설정됩니다:
@@ -1858,14 +1948,15 @@ button {
-#### Clear an image while it's loading {/*clear-an-image-while-its-loading*/}
+#### Clear an image while it's loading이미지 로딩중에 기존 이미지 지우기 {/*clear-an-image-while-its-loading*/}
When you press "Next", the browser starts loading the next image. However, because it's displayed in the same `
` tag, by default you would still see the previous image until the next one loads. This may be undesirable if it's important for the text to always match the image. Change it so that the moment you press "Next", the previous image immediately clears.
+"Next"을 누르면 브라우저에서 다음 이미지 로딩이 시작됩니다. 그러나 동일한 `
` 태그에 표시되기 때문에 기본적으로 다음 이미지가 로드될 때까지 이전 이미지가 계속 표시됩니다. 텍스트가 항상 이미지와 일치하는 것이 중요한 경우 이는 바람직하지 않을 수 있습니다. '다음'을 누르는 순간 이전 이미지가 즉시 지워지도록 변경하세요.
Is there a way to tell React to re-create the DOM instead of reusing it?
-
+React가 DOM을 재사용하는 대신 다시 생성하도록 지시하는 방법이 있을까요?
@@ -1935,7 +2026,7 @@ img { width: 150px; height: 150px; }
You can provide a `key` to the `
` tag. When that `key` changes, React will re-create the `
` DOM node from scratch. This causes a brief flash when each image loads, so it's not something you'd want to do for every image in your app. But it makes sense if you want to ensure the image always matches the text.
-
+`
` 태그에 `key`를 제공할 수 있습니다. 이 `key`가 변경되면 React는 `
` DOM 노드를 처음부터 다시 생성합니다. 이렇게 하면 각 이미지가 로드될 때 짧은 플래시가 발생하므로 앱의 모든 이미지에 대해 이 작업을 수행하는 것은 바람직하지 않습니다. 하지만 이미지가 항상 텍스트와 일치하도록 하려는 경우에는 의미가 있습니다.
```js
@@ -2002,11 +2093,13 @@ img { width: 150px; height: 150px; }
-#### Fix misplaced state in the list {/*fix-misplaced-state-in-the-list*/}
+#### Fix misplaced state in the list목록에서 잘못 배치된 상태 수정하기 {/*fix-misplaced-state-in-the-list*/}
In this list, each `Contact` has state that determines whether "Show email" has been pressed for it. Press "Show email" for Alice, and then tick the "Show in reverse order" checkbox. You will notice that it's _Taylor's_ email that is expanded now, but Alice's--which has moved to the bottom--appears collapsed.
+이 목록에서 각 `Contact`에는 "Show email"를 눌렀는지 여부를 결정하는 상태가 있습니다. Alice에 대해 'Show email'를 누른 다음 'Show in reverse order' 확인란을 선택합니다. 이제 Taylor의 이메일은 확장되어 있지만 하단으로 이동한 Alice의 이메일은 접혀 있는 것을 볼 수 있습니다.
Fix it so that the expanded state is associated with each contact, regardless of the chosen ordering.
+선택한 순서에 관계없이 expanded state가 각 연락처와 연결되도록 수정하세요.
@@ -2097,6 +2190,7 @@ button {
The problem is that this example was using index as a `key`:
+문제는 인덱스를 'key'로 사용하고 있다는 점입니다:
```js
{displayedContacts.map((contact, i) =>
@@ -2104,9 +2198,10 @@ The problem is that this example was using index as a `key`:
```
However, you want the state to be associated with _each particular contact_.
+상태가 _각 특정 연락처_와 연결되기를 원합니다.
Using the contact ID as a `key` instead fixes the issue:
-
+대신 연락처 ID를 `key`로 사용하면 문제가 해결됩니다:
```js App.js
@@ -2194,7 +2289,7 @@ button {
State is associated with the tree position. A `key` lets you specify a named position instead of relying on order.
-
+상태는 트리 위치와 연관됩니다. `key`를 사용하면 순서에 의존하지 않고 명명된 위치를 지정할 수 있습니다.
diff --git a/src/content/learn/referencing-values-with-refs.md b/src/content/learn/referencing-values-with-refs.md
index f395ab879b3..7dbedd73676 100644
--- a/src/content/learn/referencing-values-with-refs.md
+++ b/src/content/learn/referencing-values-with-refs.md
@@ -1,10 +1,12 @@
---
-title: 'Referencing Values with Refs'
+title: Referencing Values with Refs
+translatedTitle: Ref로 값 참조하기
---
When you want a component to "remember" some information, but you don't want that information to [trigger new renders](/learn/render-and-commit), you can use a *ref*.
+컴포넌트가 특정 정보를 '기억'하도록 하고 싶지만 해당 정보가 [새 렌더링을 트리거](/learn/render-and-commit)하지 않도록 하려는 경우 *ref*를 사용할 수 있습니다.
@@ -15,23 +17,33 @@ When you want a component to "remember" some information, but you don't want tha
- How refs are different from state
- How to use refs safely
+
+- 컴포넌트에 ref를 추가하는 방법
+- ref 값을 업데이트하는 방법
+- state와 ref의 차이점
+- ref를 안전하게 사용하는 방법
+
+
-## Adding a ref to your component {/*adding-a-ref-to-your-component*/}
+## Adding a ref to your component컴포넌트에 ref 추가하기 {/*adding-a-ref-to-your-component*/}
You can add a ref to your component by importing the `useRef` Hook from React:
+리액트에서 `useRef` 훅을 가져와서 컴포넌트에 ref를 추가할 수 있습니다:
```js
import { useRef } from 'react';
```
Inside your component, call the `useRef` Hook and pass the initial value that you want to reference as the only argument. For example, here is a ref to the value `0`:
+컴포넌트 내부에서 `useRef` 훅을 호출하고 참조할 초기값을 인자로 전달하십시오. 예를 들어,값 `0`은 ref에 대한 초기값입니다:
```js
const ref = useRef(0);
```
`useRef` returns an object like this:
+`useRef`는 다음과 같은 객체를 반환합니다:
```js
{
@@ -42,8 +54,10 @@ const ref = useRef(0);
You can access the current value of that ref through the `ref.current` property. This value is intentionally mutable, meaning you can both read and write to it. It's like a secret pocket of your component that React doesn't track. (This is what makes it an "escape hatch" from React's one-way data flow--more on that below!)
+`ref.current` 속성을 통해 해당 ref의 현재 값에 액세스할 수 있습니다. 이 값은 의도적으로 변경 가능하므로 읽기와 쓰기가 모두 가능합니다. React가 추적하지 않는 컴포넌트의 비밀 주머니와 같습니다. (이것이 바로 React의 단방향 데이터 흐름에서 "탈출구"가 되는 이유입니다. 아래에서 자세히 설명합니다!)
Here, a button will increment `ref.current` on every click:
+여기서 버튼은 클릭할 때마다 `ref.current`를 증가시킵니다:
@@ -69,12 +83,15 @@ export default function Counter() {
The ref points to a number, but, like [state](/learn/state-a-components-memory), you could point to anything: a string, an object, or even a function. Unlike state, ref is a plain JavaScript object with the `current` property that you can read and modify.
+여기서의 ref는 숫자를 가리키고 있지만, [state](/learn/state-a-components-memory)와 마찬가지로 문자열, 객체, 함수 등 무엇이든 가리킬 수 있습니다. state와 달리 ref는 `current` 속성을 읽고 수정할 수 있는 일반 JavaScript 객체입니다.
Note that **the component doesn't re-render with every increment.** Like state, refs are retained by React between re-renders. However, setting state re-renders a component. Changing a ref does not!
+**컴포넌트는 ref가 증가할 때마다 리렌더링되지 않는다**는 점에 유의하세요. state와 마찬가지로 ref는 리렌더링 사이에 React에 의해 유지됩니다. state를 설정하면 컴포넌트가 다시 렌더링됩니다. 반면 ref를 변경하면 그렇지 않습니다!
-## Example: building a stopwatch {/*example-building-a-stopwatch*/}
+## Example: building a stopwatch예제: 스톱워치 만들기 {/*example-building-a-stopwatch*/}
You can combine refs and state in a single component. For example, let's make a stopwatch that the user can start or stop by pressing a button. In order to display how much time has passed since the user pressed "Start", you will need to keep track of when the Start button was pressed and what the current time is. **This information is used for rendering, so you'll keep it in state:**
+ref와 state를 단일 컴포넌트로 결합할 수 있습니다. 예를 들어 사용자가 버튼을 눌러 시작하거나 중지할 수 있는 스톱워치를 만들어 봅시다. 사용자가 'Start'를 누른 후 얼마나 시간이 지났는지 표시하려면 시작 버튼을 누른 시점과 현재 시간을 추적해야 합니다. **이 정보는 렌더링에 사용되므로 state를 유지해야 합니다:**
```js
const [startTime, setStartTime] = useState(null);
@@ -82,6 +99,7 @@ const [now, setNow] = useState(null);
```
When the user presses "Start", you'll use [`setInterval`](https://developer.mozilla.org/docs/Web/API/setInterval) in order to update the time every 10 milliseconds:
+사용자가 'Start'를 누르면 10 milliseconds마다 시간을 업데이트하기 위해 [`setInterval`](https://developer.mozilla.org/docs/Web/API/setInterval)을 사용하게 됩니다:
@@ -122,6 +140,7 @@ export default function Stopwatch() {
When the "Stop" button is pressed, you need to cancel the existing interval so that it stops updating the `now` state variable. You can do this by calling [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval), but you need to give it the interval ID that was previously returned by the `setInterval` call when the user pressed Start. You need to keep the interval ID somewhere. **Since the interval ID is not used for rendering, you can keep it in a ref:**
+"Stop" 버튼을 누르면 `now` state 변수의 업데이트를 중지하도록 기존 간격을 취소해야 합니다. 이 작업은 [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)을 호출하여 수행할 수 있지만, 사용자가 시작을 눌렀을 때 이전에 `setInterval` 호출에서 반환한 interval ID를 제공해야 합니다. interval ID를 어딘가에 보관해야 합니다. **interval ID는 렌더링에 사용되지 않으므로 ref에 보관할 수 있습니다**
@@ -169,19 +188,31 @@ export default function Stopwatch() {
When a piece of information is used for rendering, keep it in state. When a piece of information is only needed by event handlers and changing it doesn't require a re-render, using a ref may be more efficient.
+렌더링에 정보가 사용되는 경우 해당 정보를 state로 유지하세요. 이벤트 핸들러만 정보를 필요로 하고 변경해도 다시 렌더링할 필요가 없는 경우, ref를 사용하는 것이 더 효율적일 수 있습니다.
-## Differences between refs and state {/*differences-between-refs-and-state*/}
+## Differences between refs and stateref와 state의 차이점 {/*differences-between-refs-and-state*/}
Perhaps you're thinking refs seem less "strict" than state—you can mutate them instead of always having to use a state setting function, for instance. But in most cases, you'll want to use state. Refs are an "escape hatch" you won't need often. Here's how state and refs compare:
+어쩌면 ref가 state보다 덜 "엄격"해 보인다고 생각할 수도 있습니다. 항상 state 설정자 함수를 사용하지 않고도 변이할 수 있기 때문입니다. 하지만 대부분의 경우 state를 사용하고 싶을 것입니다. ref는 자주 사용하지 않는 "탈출구"입니다. state와 ref를 비교하면 다음과 같습니다:
| refs | state |
| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `useRef(initialValue)` returns `{ current: initialValue }` | `useState(initialValue)` returns the current value of a state variable and a state setter function ( `[value, setValue]`) |
| Doesn't trigger re-render when you change it. | Triggers re-render when you change it. |
| Mutable—you can modify and update `current`'s value outside of the rendering process. | "Immutable"—you must use the state setting function to modify state variables to queue a re-render. |
-| You shouldn't read (or write) the `current` value during rendering. | You can read state at any time. However, each render has its own [snapshot](/learn/state-as-a-snapshot) of state which does not change.
+| You shouldn't read (or write) the `current` value during rendering. | You can read state at any time. However, each render has its own [snapshot](/learn/state-as-a-snapshot) of state which does not change. |
+
+
+| refs | state |
+| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
+| `useRef(initialValue)`는 `{ current: initialValue }`을 반환 | `useState(initialValue)`는 state 변수의 현재값과 state 설정자함수(`[value, setValue]`)를 반환 |
+| 변경 시 리렌더링이 트리거되지 않음 | 변경 시 리렌더링을 트리거함 |
+| Mutable— 렌더링 프로세스 외부에서 current 값을 수정하고 업데이트할 수 있음 | "Immutable"— state setting 함수를 사용하여 state 변수를 수정해 리렌더링을 대기열에 추가해야함 |
+| 렌더링 중에는 `current` 값을 읽거나 쓰지 않아야 함 | 언제든지 state를 읽을 수 있음. 각 렌더링에는 변경되지 않는 자체 state [snapshot](/learn/state-as-a-snapshot)이 있음 |
+
Here is a counter button that's implemented with state:
+다음은 state와 함께 구현된 카운터 버튼입니다:
@@ -206,8 +237,10 @@ export default function Counter() {
Because the `count` value is displayed, it makes sense to use a state value for it. When the counter's value is set with `setCount()`, React re-renders the component and the screen updates to reflect the new count.
+`count` 값이 표시되므로 state 값을 사용하는 것이 합리적입니다. 카운터 값이 `setCount()`로 설정되면 React는 컴포넌트를 다시 렌더링하고 화면이 새로운 카운트를 반영하도록 업데이트합니다.
If you tried to implement this with a ref, React would never re-render the component, so you'd never see the count change! See how clicking this button **does not update its text**:
+만약 이것을 ref로 구현하려고 한다면, React는 컴포넌트를 다시 렌더링하지 않으므로 카운트가 변경되는 것을 볼 수 없을 것입니다! 이 버튼을 클릭해도 **텍스트가 업데이트되지 않는 방법**을 확인하세요:
@@ -233,12 +266,14 @@ export default function Counter() {
This is why reading `ref.current` during render leads to unreliable code. If you need that, use state instead.
+이것이 렌더링 중에 `ref.current`를 읽으면 코드가 불안정해지는 이유입니다. 필요하다면 state를 대신 사용하세요.
-#### How does useRef work inside? {/*how-does-use-ref-work-inside*/}
+#### How does useRef work inside?ref는 내부에서 어떻게 작동하나요? {/*how-does-use-ref-work-inside*/}
Although both `useState` and `useRef` are provided by React, in principle `useRef` could be implemented _on top of_ `useState`. You can imagine that inside of React, `useRef` is implemented like this:
+useState와 useRef는 모두 React에서 제공하지만, 원칙적으로 useRef는 useState 위에 구현될 수 있습니다. React 내부에서 useRef는 다음과 같이 구현된다고 상상할 수 있습니다:
```js
// Inside of React
@@ -249,29 +284,46 @@ function useRef(initialValue) {
```
During the first render, `useRef` returns `{ current: initialValue }`. This object is stored by React, so during the next render the same object will be returned. Note how the state setter is unused in this example. It is unnecessary because `useRef` always needs to return the same object!
+첫 번째 렌더링 중에 `useRef` 는 `{ current: initialValue }`를 반환합니다. 이 객체는 React에 의해 저장되므로 다음 렌더링 중에 동일한 객체가 반환됩니다. 이 예제에서 state setter가 어떻게 사용되지 않는지 주목하세요. `useRef`는 항상 동일한 객체를 반환해야 하기 때문에 불필요합니다!
React provides a built-in version of `useRef` because it is common enough in practice. But you can think of it as a regular state variable without a setter. If you're familiar with object-oriented programming, refs might remind you of instance fields--but instead of `this.something` you write `somethingRef.current`.
+React는 충분히 일반적인 상황이라 판단하고 내장된 버전의 `useRef`를 제공합니다. ref를 설정자가 없는 일반 state 변수라고 생각하면 됩니다. 객체지향 프로그래밍에 익숙하다면 인스턴스 필드를 떠올릴 수 있는데, `this.something` 대신 `somethingRef.current`를 사용하면 됩니다.
-## When to use refs {/*when-to-use-refs*/}
+## When to use refsref를 사용해야 하는 경우 {/*when-to-use-refs*/}
Typically, you will use a ref when your component needs to "step outside" React and communicate with external APIs—often a browser API that won't impact the appearance of the component. Here are a few of these rare situations:
+일반적으로 ref는 컴포넌트가 React로부터 "외부로 나가서" 외부 API, 즉 컴포넌트의 형상에 영향을 주지 않는 브라우저 API 등과 통신해야 할 때 사용합니다. 다음은 이러한 드문 상황 중 몇가지입니다:
- Storing [timeout IDs](https://developer.mozilla.org/docs/Web/API/setTimeout)
- Storing and manipulating [DOM elements](https://developer.mozilla.org/docs/Web/API/Element), which we cover on [the next page](/learn/manipulating-the-dom-with-refs)
- Storing other objects that aren't necessary to calculate the JSX.
+
+- [timeout ID](https://developer.mozilla.org/docs/Web/API/setTimeout) 저장
+- [다음 페이지](/learn/manipulating-the-dom-with-refs)에서 다룰 [DOM elements](https://developer.mozilla.org/docs/Web/API/Element) 저장 및 조작
+- JSX를 계산하는 데 필요하지 않은 다른 객체 저장
+
+
If your component needs to store some value, but it doesn't impact the rendering logic, choose refs.
+컴포넌트에 일부 값을 저장해야 하지만 렌더링 로직에는 영향을 미치지 않는 경우 ref를 선택하세요.
-## Best practices for refs {/*best-practices-for-refs*/}
+## Best practices for refsref 모범 사례 {/*best-practices-for-refs*/}
Following these principles will make your components more predictable:
+다음 원칙을 따르면 컴포넌트의 예측 가능성을 높일 수 있습니다:
- **Treat refs as an escape hatch.** Refs are useful when you work with external systems or browser APIs. If much of your application logic and data flow relies on refs, you might want to rethink your approach.
- **Don't read or write `ref.current` during rendering.** If some information is needed during rendering, use [state](/learn/state-a-components-memory) instead. Since React doesn't know when `ref.current` changes, even reading it while rendering makes your component's behavior difficult to predict. (The only exception to this is code like `if (!ref.current) ref.current = new Thing()` which only sets the ref once during the first render.)
+
+
+- **ref를 탈출구로 취급하세요**. ref는 외부 시스템이나 브라우저 API로 작업할 때 유용합니다. 애플리케이션 로직과 데이터 흐름의 대부분이 ref에 의존하는 경우 접근 방식을 재고해봐야 할 수도 있습니다.
+- **렌더링 중에는 `ref.current`를 읽거나 쓰지 마세요.** 렌더링 중에 일부 정보가 필요한 경우, 대신 [state](/learn/state-a-components-memory)를 사용하세요. React는 `ref.current`가 언제 변경되는지 알지 못하기 때문에, 렌더링 중에 읽어도 컴포넌트의 동작을 예측하기 어렵습니다. (유일한 예외는 첫 번째 렌더링 중에 ref를 한 번만 설정하는 `if (!ref.current) ref.current = new Thing()`과 같은 코드입니다).
+
Limitations of React state don't apply to refs. For example, state acts like a [snapshot for every render](/learn/state-as-a-snapshot) and [doesn't update synchronously.](/learn/queueing-a-series-of-state-updates) But when you mutate the current value of a ref, it changes immediately:
+React state의 제한은 ref에는 적용되지 않습니다. 예를 들어 state는 [모든 렌더링에 대해 스냅샷](/learn/state-as-a-snapshot)처럼 작동하며 [동기적으로 업데이트](/learn/queueing-a-series-of-state-updates)되지 않습니다. 하지만 ref의 현재 값을 변이하면 즉시 변경됩니다:
```js
ref.current = 5;
@@ -279,12 +331,15 @@ console.log(ref.current); // 5
```
This is because **the ref itself is a regular JavaScript object,** and so it behaves like one.
+이는 **ref 자체가 일반 자바스크립트 객체이므로** 자바스크립트 객체처럼 동작하기 때문입니다.
You also don't need to worry about [avoiding mutation](/learn/updating-objects-in-state) when you work with a ref. As long as the object you're mutating isn't used for rendering, React doesn't care what you do with the ref or its contents.
+또한 ref로 작업할 때 [변이(mutation)를 피하는 것](/learn/updating-objects-in-state)에 대해 걱정할 필요가 없습니다. 변이하는 객체가 렌더링에 사용되지 않는 한, React는 ref나 그 콘텐츠로 무엇을 하든 상관하지 않습니다.
-## Refs and the DOM {/*refs-and-the-dom*/}
+## Refs and the DOMRef와 DOM {/*refs-and-the-dom*/}
You can point a ref to any value. However, the most common use case for a ref is to access a DOM element. For example, this is handy if you want to focus an input programmatically. When you pass a ref to a `ref` attribute in JSX, like ``, React will put the corresponding DOM element into `myRef.current`. You can read more about this in [Manipulating the DOM with Refs.](/learn/manipulating-the-dom-with-refs)
+
ref는 모든 값을 가리킬 수 있습니다. 그러나 ref의 가장 일반적인 사용 사례는 DOM 요소에 액세스하는 것입니다. 예를 들어 프로그래밍 방식으로 input에 focus를 맞추고자 할 때 유용합니다. ``와 같이 JSX의 `ref` 어트리뷰트에 ref를 전달하면 React는 해당 DOM 엘리먼트를 `myRef.current`에 넣습니다. 이에 대한 자세한 내용은 [ref로 DOM 조작하기](/learn/manipulating-the-dom-with-refs)에서 확인할 수 있습니다.
@@ -295,19 +350,28 @@ You can point a ref to any value. However, the most common use case for a ref is
- Unlike state, setting the ref's `current` value does not trigger a re-render.
- Don't read or write `ref.current` during rendering. This makes your component hard to predict.
-
-
+
+ - ref는 렌더링에 사용되지 않는 값을 유지하기 위한 탈출구입니다. 자주 필요하지 않습니다.
+ - ref는 `current`라는 단일 프로퍼티를 가진 일반 자바스크립트 객체로, 읽거나 설정할 수 있습니다.
+ - `useRef` 훅을 호출하여 React에 ref를 제공하도록 요청할 수 있습니다.
+ - state와 마찬가지로 ref를 사용하면 컴포넌트의 리렌더링 사이에 정보를 유지할 수 있습니다.
+ - state와 달리 ref의 `current` 값을 설정해도 리렌더링이 트리거되지 않습니다.
+ - 렌더링 중에는 `ref.current`를 읽거나 쓰지 마세요. 이렇게 하면 컴포넌트를 예측하기 어렵습니다.
+
+
-#### Fix a broken chat input {/*fix-a-broken-chat-input*/}
+#### Fix a broken chat input잘못된 chat input 고치기 {/*fix-a-broken-chat-input*/}
Type a message and click "Send". You will notice there is a three second delay before you see the "Sent!" alert. During this delay, you can see an "Undo" button. Click it. This "Undo" button is supposed to stop the "Sent!" message from appearing. It does this by calling [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) for the timeout ID saved during `handleSend`. However, even after "Undo" is clicked, the "Sent!" message still appears. Find why it doesn't work, and fix it.
+메시지를 입력하고 "Send"를 클릭하세요. "Sent!"라는 알림이 표시되기까지 3초 정도 지연되는 것을 확인할 수 있습니다. 이 지연 시간 동안 "Undo" 버튼이 표시됩니다. 이 버튼을 클릭하세요. 이 "Undo" 버튼은 "Sent!" 메시지가 표시되지 않도록 하기 위한 것입니다. 이 버튼은 `handleSend` 중에 저장된 timeout ID에 대해 [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout)을 호출하여 이를 수행합니다. 그러나 "Undo"를 클릭한 후에도 "Sent!" 메시지가 계속 표시됩니다. 작동하지 않는 이유를 찾아서 수정하세요.
Regular variables like `let timeoutID` don't "survive" between re-renders because every render runs your component (and initializes its variables) from scratch. Should you keep the timeout ID somewhere else?
+`let timeoutID`와 같은 일반 변수는 렌더링할 때마다 컴포넌트를 처음부터 실행하고 변수를 초기화하기 때문에 재렌더링 사이에 "살아남지" 못합니다. 타임아웃 ID를 다른 곳에 보관해야 할까요?
@@ -361,6 +425,7 @@ export default function Chat() {
Whenever your component re-renders (such as when you set state), all local variables get initialized from scratch. This is why you can't save the timeout ID in a local variable like `timeoutID` and then expect another event handler to "see" it in the future. Instead, store it in a ref, which React will preserve between renders.
+컴포넌트가 다시 렌더링할 때마다(예: state를 설정할 때) 모든 로컬 변수가 처음부터 초기화됩니다. 그렇기 때문에 `timeoutID`와 같은 로컬 변수에 timeout ID를 저장한 다음 다른 이벤트 핸들러가 나중에 이를 "볼" 것으로 기대할 수 없습니다. 대신, React가 렌더링 사이에 보존하는 ref에 저장하세요.
@@ -412,9 +477,10 @@ export default function Chat() {
-#### Fix a component failing to re-render {/*fix-a-component-failing-to-re-render*/}
+#### Fix a component failing to re-render재렌더링되지 않는 문제 해결하기 {/*fix-a-component-failing-to-re-render*/}
This button is supposed to toggle between showing "On" and "Off". However, it always shows "Off". What is wrong with this code? Fix it.
+이 버튼은 "On와 "Off" 표시 사이를 전환해야 합니다. 그러나 항상 "꺼짐"으로 표시됩니다. 이 코드에 어떤 문제가 있나요? 수정하세요.
@@ -439,6 +505,7 @@ export default function Toggle() {
In this example, the current value of a ref is used to calculate the rendering output: `{isOnRef.current ? 'On' : 'Off'}`. This is a sign that this information should not be in a ref, and should have instead been put in state. To fix it, remove the ref and use state instead:
+이 예제에서는 ref의 현재 값이 렌더링 출력을 계산하는 데 사용됩니다: `{isOnRef.current ? 'On' : 'Off'}`입니다. 이는 이 정보가 ref에 있어서는 안 되며 대신 state에 넣어야 한다는 신호입니다. 이 문제를 해결하려면 ref를 제거하고 대신 state를 사용하세요:
@@ -462,17 +529,21 @@ export default function Toggle() {
-#### Fix debouncing {/*fix-debouncing*/}
+#### Fix debouncing디바운싱 수정하기 {/*fix-debouncing*/}
In this example, all button click handlers are ["debounced".](https://redd.one/blog/debounce-vs-throttle) To see what this means, press one of the buttons. Notice how the message appears a second later. If you press the button while waiting for the message, the timer will reset. So if you keep clicking the same button fast many times, the message won't appear until a second *after* you stop clicking. Debouncing lets you delay some action until the user "stops doing things".
+이 예제에서는 모든 버튼 클릭 핸들러가 ["디바운스"](https://redd.one/blog/debounce-vs-throttle)되어 있습니다. 이것이 무엇을 의미하는지 확인하려면 버튼 중 하나를 누르세요. 1초 후에 메시지가 어떻게 표시되는지 확인하세요. 메시지를 기다리는 동안 버튼을 누르면 타이머가 재설정됩니다. 따라서 같은 버튼을 계속 빠르게 여러 번 클릭하면 클릭을 멈춘 후 1초가 지나야 메시지가 표시됩니다. 디바운싱을 사용하면 사용자가 "작업을 중단"할 때까지 일부 작업을 지연시킬 수 있습니다.
This example works, but not quite as intended. The buttons are not independent. To see the problem, click one of the buttons, and then immediately click another button. You'd expect that after a delay, you would see both button's messages. But only the last button's message shows up. The first button's message gets lost.
+이 예제는 작동하지만 의도한 대로 작동하지는 않습니다. 버튼은 독립적이지 않습니다. 문제를 확인하려면 버튼 중 하나를 클릭한 다음 즉시 다른 버튼을 클릭하세요. 잠시 후 두 버튼의 메시지가 모두 표시될 것으로 예상할 수 있습니다. 하지만 마지막 버튼의 메시지만 표시됩니다. 첫 번째 버튼의 메시지는 사라집니다.
Why are the buttons interfering with each other? Find and fix the issue.
+버튼이 서로 간섭하는 이유는 무엇인가요? 문제를 찾아서 해결하세요.
The last timeout ID variable is shared between all `DebouncedButton` components. This is why clicking one button resets another button's timeout. Can you store a separate timeout ID for each button?
+마지막 타임아웃 ID 변수는 모든 `DebouncedButton` 컴포넌트 간에 공유됩니다. 그렇기 때문에 한 버튼을 클릭하면 다른 버튼의 시간 초과가 재설정됩니다. 각 버튼에 대해 별도의 timeout ID를 저장할 수 있나요?
@@ -528,6 +599,7 @@ button { display: block; margin: 10px; }
A variable like `timeoutID` is shared between all components. This is why clicking on the second button resets the first button's pending timeout. To fix this, you can keep timeout in a ref. Each button will get its own ref, so they won't conflict with each other. Notice how clicking two buttons fast will show both messages.
+`timeoutID`와 같은 변수는 모든 컴포넌트 간에 공유됩니다. 그렇기 때문에 두 번째 버튼을 클릭하면 첫 번째 버튼의 보류 중인 타임아웃이 재설정됩니다. 이 문제를 해결하려면 타임아웃을 ref에 보관하면 됩니다. 각 버튼은 고유한 ref를 가지므로 서로 충돌하지 않습니다. 두 버튼을 빠르게 클릭하면 두 메시지가 모두 표시되는 것을 확인할 수 있습니다.
@@ -579,11 +651,13 @@ button { display: block; margin: 10px; }
-#### Read the latest state {/*read-the-latest-state*/}
+#### Read the latest state최신 state 읽기 {/*read-the-latest-state*/}
In this example, after you press "Send", there is a small delay before the message is shown. Type "hello", press Send, and then quickly edit the input again. Despite your edits, the alert would still show "hello" (which was the value of state [at the time](/learn/state-as-a-snapshot#state-over-time) the button was clicked).
+이 예에서는 "Send"를 누른 후 메시지가 표시되기까지 약간의 지연 시간이 있습니다. "hello"를 입력하고 보내기를 누른 다음 입력을 다시 빠르게 편집합니다. 편집한 내용에도 불구하고 알림에는 여전히 (버튼을 클릭할 [당시의](/learn/state-as-a-snapshot#state-over-time) state 값인) "hello"가 표시됩니다.
Usually, this behavior is what you want in an app. However, there may be occasional cases where you want some asynchronous code to read the *latest* version of some state. Can you think of a way to make the alert show the *current* input text rather than what it was at the time of the click?
+일반적으로 이 동작은 앱에서 원하는 것입니다. 그러나 일부 비동기 코드가 특정 state의 *최신* 버전을 읽기를 원하는 경우가 있을 수 있습니다. 알림에 클릭 당시의 텍스트가 아닌 *현재* 입력 텍스트를 표시하는 방법을 생각해낼 수 있나요?
@@ -619,6 +693,7 @@ export default function Chat() {
State works [like a snapshot](/learn/state-as-a-snapshot), so you can't read the latest state from an asynchronous operation like a timeout. However, you can keep the latest input text in a ref. A ref is mutable, so you can read the `current` property at any time. Since the current text is also used for rendering, in this example, you will need *both* a state variable (for rendering), *and* a ref (to read it in the timeout). You will need to update the current ref value manually.
+상태는 [스냅샷처럼](/learn/state-as-a-snapshot) 작동하므로, 타임아웃과 같은 비동기 작업에서 최신 상태를 읽을 수 없습니다. 그러나 최신 입력 텍스트는 ref에 보관할 수 있습니다. ref는 변경 가능하므로 언제든지 `current` 속성을 읽을 수 있습니다. 현재 텍스트는 렌더링에도 사용되므로 이 예제에서는 state 변수(렌더링용)*와* ref(타임아웃 읽기용)가 모두 필요합니다. 현재 ref 값을 수동으로 업데이트해야 합니다.
diff --git a/src/content/learn/reusing-logic-with-custom-hooks.md b/src/content/learn/reusing-logic-with-custom-hooks.md
index 679a9bac210..c133bfa191f 100644
--- a/src/content/learn/reusing-logic-with-custom-hooks.md
+++ b/src/content/learn/reusing-logic-with-custom-hooks.md
@@ -1,10 +1,12 @@
---
title: 'Reusing Logic with Custom Hooks'
+translatedTitle: 커스텀 훅으로 로직 재사용하기
---
React comes with several built-in Hooks like `useState`, `useContext`, and `useEffect`. Sometimes, you'll wish that there was a Hook for some more specific purpose: for example, to fetch data, to keep track of whether the user is online, or to connect to a chat room. You might not find these Hooks in React, but you can create your own Hooks for your application's needs.
+React에는 `useState`, `useContext`, `useEffect`와 같은 몇 가지 내장 훅이 있습니다. 때로는 데이터를 불러오거나, 사용자가 온라인 상태인지 추적하거나, 채팅방에 연결하는 등 좀 더 구체적인 목적을 위한 훅이 있었으면 좋겠다는 생각을 할 수 있습니다. React에서 이러한 훅을 찾지 못할 수도 있지만 애플리케이션의 필요에 따라 자신만의 훅을 만들 수 있습니다.
@@ -14,17 +16,29 @@ React comes with several built-in Hooks like `useState`, `useContext`, and `useE
- How to reuse logic between components
- How to name and structure your custom Hooks
- When and why to extract custom Hooks
+
+- 커스텀 훅이란 무엇이며, 직접 작성하는 방법
+- 컴포넌트 간에 로직을 재사용하는 방법
+- 커스텀 훅의 이름을 만들고 구조화하는 방법
+- 커스텀 훅을 추출해야 하는 시기와 이유
+
-## Custom Hooks: Sharing logic between components {/*custom-hooks-sharing-logic-between-components*/}
+## Custom Hooks: Sharing logic between components커스텀 훅: 컴포넌트간의 로직 공유 {/*custom-hooks-sharing-logic-between-components*/}
Imagine you're developing an app that heavily relies on the network (as most apps do). You want to warn the user if their network connection has accidentally gone off while they were using your app. How would you go about it? It seems like you'll need two things in your component:
+대부분의 앱이 그렇듯이 네트워크에 크게 의존하는 앱을 개발한다고 가정해 보겠습니다. 사용자가 앱을 사용하는 동안 실수로 네트워크 연결이 끊어진 경우 사용자에게 주의를 줄 경우 어떻게 하면 좋을까요? 이럴 경우에 컴포넌트에는 두 가지가 필요합니다.
1. A piece of state that tracks whether the network is online.
2. An Effect that subscribes to the global [`online`](https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event) and [`offline`](https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event) events, and updates that state.
+
+1. 네트워크가 온라인 상태인지 여부를 추적하는 state
+2. 전역 [`online`](https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event) 및 [`offline`](https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event) 이벤트를 구독하고, state를 업데이트하는 Effect
+
This will keep your component [synchronized](/learn/synchronizing-with-effects) with the network status. You might start with something like this:
+이렇게 하면 컴포넌트가 네트워크 상태와 [동기화된 상태](/learn/synchronizing-with-effects)로 유지됩니다. 다음과 같이 시작할 수 있습니다:
@@ -55,10 +69,13 @@ export default function StatusBar() {
Try turning your network on and off, and notice how this `StatusBar` updates in response to your actions.
+네트워크를 켜고 끄고, 동작에 따라 이 `StatusBar(상태 표시줄)`이 어떻게 업데이트되는지 확인해 보세요.
Now imagine you *also* want to use the same logic in a different component. You want to implement a Save button that will become disabled and show "Reconnecting..." instead of "Save" while the network is off.
+이제 *다른 컴포넌트에서도* 동일한 로직을 사용하고 싶다고 가정해 봅시다. 네트워크가 꺼져 있을 때 비활성화되고 "저장" 대신 "다시 연결 중..."이 표시되는 저장 버튼을 구현하고 싶다고 가정해 보겠습니다.
To start, you can copy and paste the `isOnline` state and the Effect into `SaveButton`:
+시작하려면 `isOnline` 상태와 Effect를 복사하여 `SaveButton`에 붙여넣으면 됩니다:
@@ -97,12 +114,15 @@ export default function SaveButton() {
Verify that, if you turn off the network, the button will change its appearance.
+네트워크를 끄면 버튼의 모양이 변경되는지 확인합니다.
These two components work fine, but the duplication in logic between them is unfortunate. It seems like even though they have different *visual appearance,* you want to reuse the logic between them.
+이 두 컴포넌트는 잘 작동하지만 두 컴포넌트 간의 로직이 중복되는 것은 안타까운 일입니다. 두 컴포넌트의 *시각적 모양*은 다르지만 당신은 두 컴포넌트 사이의 로직을 재사용하고 싶을 것입니다.
-### Extracting your own custom Hook from a component {/*extracting-your-own-custom-hook-from-a-component*/}
+### Extracting your own custom Hook from a component컴포넌트에서 커스텀 훅 추출하기 {/*extracting-your-own-custom-hook-from-a-component*/}
Imagine for a moment that, similar to [`useState`](/reference/react/useState) and [`useEffect`](/reference/react/useEffect), there was a built-in `useOnlineStatus` Hook. Then both of these components could be simplified and you could remove the duplication between them:
+[`useState`](/reference/react/useState) 와 [`useEffect`](/reference/react/useEffect)와 같은, 만들어진 `useOnlineStatus` 훅이 있다고 잠깐만 가정해봅시다. 이 두 컴포넌트들은 단순화 될 수 있고 두 컴포넌트 간의 중복을 제거할 수 있습니다
```js {2,7}
function StatusBar() {
@@ -126,6 +146,7 @@ function SaveButton() {
```
Although there is no such built-in Hook, you can write it yourself. Declare a function called `useOnlineStatus` and move all the duplicated code into it from the components you wrote earlier:
+이러한 내장 훅은 없지만, 직접 만들 수 있습니다.`useOnlineStatus` 이라는 함수를 선언하고 앞서 작성한 컴포넌트에서 중복된 코드를 모두 이 함수로 옮깁니다.
```js {2-16}
function useOnlineStatus() {
@@ -149,6 +170,7 @@ function useOnlineStatus() {
```
At the end of the function, return `isOnline`. This lets your components read that value:
+이 함수의 마지막에 `isOnline`의 값을 return하고, 컴포넌트들이 이 값을 읽을 수 있게 합니다.
@@ -210,55 +232,73 @@ export function useOnlineStatus() {
Verify that switching the network on and off updates both components.
+네트워크를 켜고 끄면서 두 컴포넌트가 모두 업데이트되는지 확인합니다.
Now your components don't have as much repetitive logic. **More importantly, the code inside them describes *what they want to do* (use the online status!) rather than *how to do it* (by subscribing to the browser events).**
+이제 컴포넌트에는 반복적인 로직이 많지 않습니다. **더 중요한 것은, 컴포넌트 내부의 코드가 (브라우저 이벤트에 가입하여) *어떻게 할 것인가*가 아니라 *무엇을 할 것인가*(온라인 상태 사용!)를 설명한다는 점입니다.**
When you extract logic into custom Hooks, you can hide the gnarly details of how you deal with some external system or a browser API. The code of your components expresses your intent, not the implementation.
+로직을 커스텀 훅으로 추출하면 외부 시스템이나 브라우저 API를 처리하는 방법에 대한 지저분한 세부 사항을 숨길 수 있습니다. 컴포넌트의 코드는 구현이 아니라 의도를 표현합니다.
-### Hook names always start with `use` {/*hook-names-always-start-with-use*/}
+### Hook names always start with `use`훅의 이름은 언제나 `use`로 시작됩니다. {/*hook-names-always-start-with-use*/}
React applications are built from components. Components are built from Hooks, whether built-in or custom. You'll likely often use custom Hooks created by others, but occasionally you might write one yourself!
+React 애플리케이션은 컴포넌트로 빌드됩니다. 컴포넌트는 내장된 것이든 커스텀이든 상관없이 훅으로 빌드됩니다. 다른 사람이 만든 커스텀 훅을 사용하는 경우가 많지만, 가끔은 직접 작성할 수도 있습니다!
You must follow these naming conventions:
+이때는 다음 명명 규칙을 따라야 합니다:
1. **React component names must start with a capital letter,** like `StatusBar` and `SaveButton`. React components also need to return something that React knows how to display, like a piece of JSX.
2. **Hook names must start with `use` followed by a capital letter,** like [`useState`](/reference/react/useState) (built-in) or `useOnlineStatus` (custom, like earlier on the page). Hooks may return arbitrary values.
+
+1. **React 컴포넌트 이름은** `StatusBar`나 `SaveButton`과 같이 **대문자로 시작해야 합니다.** 또한 React 컴포넌트는 JSX와 같이 React가 표시하는 방법을 알고 있는 것을 반환해야 합니다.
+2. **훅의 이름은** [`useState`](/reference/react/useState)(내장)이나 `useOnlineStatus`(커스텀)처럼 **`use`로 시작**해야 하고, 그 다음의 첫글자는 대문자여야 합니다. 훅은 임의의 값을 반환할 수 있습니다.
+
+
This convention guarantees that you can always look at a component and know where its state, Effects, and other React features might "hide". For example, if you see a `getColor()` function call inside your component, you can be sure that it can't possibly contain React state inside because its name doesn't start with `use`. However, a function call like `useOnlineStatus()` will most likely contain calls to other Hooks inside!
+이 규칙은 컴포넌트를 보고 상태, 효과 및 기타 React 기능이 어디에 "숨어 있는지" 항상 알 수 있도록 보장합니다. 예를 들어 컴포넌트 내부에 `getColor()` 함수 호출이 있다면, 그 이름이 `use`로 시작하지 않기 때문에 내부에 React state를 포함할 수 없다는 것을 확신할 수 있습니다. 하지만 `useOnlineStatus()`와 같은 함수 호출은 내부에 다른 훅에 대한 호출을 포함할 가능성이 높습니다!
If your linter is [configured for React,](/learn/editor-setup#linting) it will enforce this naming convention. Scroll up to the sandbox above and rename `useOnlineStatus` to `getOnlineStatus`. Notice that the linter won't allow you to call `useState` or `useEffect` inside of it anymore. Only Hooks and components can call other Hooks!
+Linter가 [React용으로 구성된 경우,](/learn/editor-setup#linting) 이 명명 규칙을 적용합니다. 위의 샌드박스로 스크롤하여 `useOnlineStatus` 를 `getOnlineStatus`로 변경합니다. 이제 더는 내부에서 `useState` 나 `useEffect` 를 호출할 수 없다는 것을 알 수 있습니다. 오직 훅과 컴포넌트만이 다른 훅을 호출할 수 있습니다!
-#### Should all functions called during rendering start with the use prefix? {/*should-all-functions-called-during-rendering-start-with-the-use-prefix*/}
+#### Should all functions called during rendering start with the use prefix?렌더링 시에 호출되는 모든 함수에 use 접두사를 써야 하나요? {/*should-all-functions-called-during-rendering-start-with-the-use-prefix*/}
No. Functions that don't *call* Hooks don't need to *be* Hooks.
+아니요. 훅을 *호출*하지 않는 함수는 훅이 될 필요가 없습니다.
If your function doesn't call any Hooks, avoid the `use` prefix. Instead, write it as a regular function *without* the `use` prefix. For example, `useSorted` below doesn't call Hooks, so call it `getSorted` instead:
+함수가 훅을 호출하지 않는다면 `use` 접두사를 사용하지 마세요. 대신 `use` 접두사가 없는 일반 함수로 작성하세요. 예를 들어, 아래의 `useSorted`는 Hook을 호출하지 않으므로 대신 `getSorted`로 호출하세요:
```js
// 🔴 Avoid: A Hook that doesn't use Hooks
+// 🔴 이러지 마세요: 훅을 사용하지 않는 훅
function useSorted(items) {
return items.slice().sort();
}
// ✅ Good: A regular function that doesn't use Hooks
+// ✅ 좋습니다: 훅을 사용하지 않는 일반 함수
function getSorted(items) {
return items.slice().sort();
}
```
This ensures that your code can call this regular function anywhere, including conditions:
+이렇게 하면 코드가 조건을 포함하여 어디서나 이 일반 함수를 호출할 수 있습니다:
```js
function List({ items, shouldSort }) {
let displayedItems = items;
if (shouldSort) {
// ✅ It's ok to call getSorted() conditionally because it's not a Hook
+ // ✅ getSorted()는 훅이 아니므로 조건부로 호출해도 괜찮음
displayedItems = getSorted(items);
}
// ...
@@ -266,32 +306,39 @@ function List({ items, shouldSort }) {
```
You should give `use` prefix to a function (and thus make it a Hook) if it uses at least one Hook inside of it:
+함수가 내부에 하나 이상의 훅을 사용하는 경우 함수에 `use` 접두사를 지정해야 합니다(따라서 훅으로 만들어야 합니다):
```js
// ✅ Good: A Hook that uses other Hooks
+// ✅ 좋습니다: 다른 훅을 사용하는 훅
function useAuth() {
return useContext(Auth);
}
```
Technically, this isn't enforced by React. In principle, you could make a Hook that doesn't call other Hooks. This is often confusing and limiting so it's best to avoid that pattern. However, there may be rare cases where it is helpful. For example, maybe your function doesn't use any Hooks right now, but you plan to add some Hook calls to it in the future. Then it makes sense to name it with the `use` prefix:
+엄밀히 말하자면 이것은 React에 의해 강제되지 않습니다. 원칙적으로 다른 훅을 호출하지 않는 훅을 만들 수 있습니다. 이는 종종 혼란스럽고 제한적이므로 이 패턴은 피하는 것이 가장 좋습니다. 하지만 드물게 도움이 되는 경우가 있을 수 있습니다. 예를 들어, 함수에 지금은 훅을 사용하지 않지만 나중에 훅 호출을 추가할 계획이 있을 수 있습니다. 이 경우 `use`접두사를 사용하여 이름을 지정하는 것이 좋습니다:
-```js {3-4}
+```js {4-5}
// ✅ Good: A Hook that will likely use some other Hooks later
+// ✅ 좋습니다: 나중에 다른 훅을 사용할 가능성이 있는 훅
function useAuth() {
// TODO: Replace with this line when authentication is implemented:
+ // TODO: 인증 기능이 구현되면 다음 줄로 바꿀 것:
// return useContext(Auth);
return TEST_USER;
}
```
Then components won't be able to call it conditionally. This will become important when you actually add Hook calls inside. If you don't plan to use Hooks inside it (now or later), don't make it a Hook.
+그러면 컴포넌트가 조건부로 호출할 수 없게 됩니다. 이것은 실제로 내부에 Hook 호출을 추가할 때 중요해질 것입니다. 내부에서 Hook을 사용할 계획이 없다면(지금 또는 나중에) Hook으로 만들지 마세요.
-### Custom Hooks let you share stateful logic, not state itself {/*custom-hooks-let-you-share-stateful-logic-not-state-itself*/}
+### Custom Hooks let you share stateful logic, not state itself커스텀 훅은 state 자체가 아닌 상태적인 로직(stateful logic)을 공유합니다. {/*custom-hooks-let-you-share-stateful-logic-not-state-itself*/}
In the earlier example, when you turned the network on and off, both components updated together. However, it's wrong to think that a single `isOnline` state variable is shared between them. Look at this code:
+앞의 예제에서는 네트워크를 켜고 끌 때 두 컴포넌트가 함께 업데이트되었습니다. 그러나 하나의 `isOnline` state 변수가 두 컴포넌트 간에 공유된다고 생각하는 것은 잘못된 생각입니다. 이 코드를 보세요:
```js {2,7}
function StatusBar() {
@@ -306,6 +353,7 @@ function SaveButton() {
```
It works the same way as before you extracted the duplication:
+중복을 제거하기 전과 같은 방식으로 동작하고 있습니다:
```js {2-5,10-13}
function StatusBar() {
@@ -326,8 +374,10 @@ function SaveButton() {
```
These are two completely independent state variables and Effects! They happened to have the same value at the same time because you synchronized them with the same external value (whether the network is on).
+완전히 독립적인 두 개의 state variables(상태 변수)와 Effects입니다! 단지 네트워크가 켜져 있는지 여부에 관계없이 동일한 외부 값과 동기화했기 때문에 동시에 동일한 값을 갖게 된 것입니다.
To better illustrate this, we'll need a different example. Consider this `Form` component:
+이를 더 잘 설명하기 위해 다른 예시가 필요합니다. 이 `Form` 컴포넌트를 생각해 봅시다:
@@ -370,12 +420,19 @@ input { margin-left: 10px; }
There's some repetitive logic for each form field:
+각 양식 필드에는 몇 가지 반복되는 로직이 있습니다:
1. There's a piece of state (`firstName` and `lastName`).
1. There's a change handler (`handleFirstNameChange` and `handleLastNameChange`).
1. There's a piece of JSX that specifies the `value` and `onChange` attributes for that input.
+
+1. state(`firstName` 및 `lastName`)가 있습니다.
+2. 변경 핸들러(`handleFirstNameChange` 및 `handleLastNameChange`)가 있습니다.
+3. 해당 입력에 대한 `value` 및 `onChange` 속성을 지정하는 JSX 조각이 있습니다.
+
You can extract the repetitive logic into this `useFormInput` custom Hook:
+반복 로직을 이 useFormInput 커스텀 훅으로 추출할 수 있습니다:
@@ -429,8 +486,10 @@ input { margin-left: 10px; }
Notice that it only declares *one* state variable called `value`.
+`value`라는 state variable(state 변수)를 하나만 선언하는 것을 주목하세요.
However, the `Form` component calls `useFormInput` *two times:*
+하지만 `Form` 컴포넌트는 `useFormInput`을 두 번 호출합니다:
```js
function Form() {
@@ -440,16 +499,21 @@ function Form() {
```
This is why it works like declaring two separate state variables!
+이것이 바로 두 개의 state 변수를 선언하는 것처럼 작동하는 이유입니다!
**Custom Hooks let you share *stateful logic* but not *state itself.* Each call to a Hook is completely independent from every other call to the same Hook.** This is why the two sandboxes above are completely equivalent. If you'd like, scroll back up and compare them. The behavior before and after extracting a custom Hook is identical.
+**커스텀 훅을 사용하면 *상태 로직(stateful logic)*은 공유할 수 있지만 *state 자체*는 공유할 수 없습니다.** 각 훅 호출은 동일한 훅에 대한 다른 모든 호출과 완전히 독립적입니다.** 이것이 바로 위의 두 샌드박스가 완전히 동일한 이유입니다. 원하신다면 스크롤을 위로 올려서 비교해 보세요. 커스텀 훅을 추출하기 전과 후의 동작은 동일합니다.
When you need to share the state itself between multiple components, [lift it up and pass it down](/learn/sharing-state-between-components) instead.
+여러 컴포넌트 간에 상태 자체를 공유해야 하는 경우, 대신 [끌어올려 전달하기](/learn/sharing-state-between-components)를 사용하세요.
-## Passing reactive values between Hooks {/*passing-reactive-values-between-hooks*/}
+## Passing reactive values between Hooks훅 사이에 반응형 값 전달하기 {/*passing-reactive-values-between-hooks*/}
The code inside your custom Hooks will re-run during every re-render of your component. This is why, like components, custom Hooks [need to be pure.](/learn/keeping-components-pure) Think of custom Hooks' code as part of your component's body!
+컴포넌트를 다시 렌더링할 때마다 커스텀 훅 내부의 코드가 다시 실행됩니다. 이것이 컴포넌트와 마찬가지로 커스텀 훅도 [순수해야 하는](/learn/keeping-components-pure) 이유입니다. 커스텀 Hook의 코드를 컴포넌트 본문의 일부로 생각하세요!
Because custom Hooks re-render together with your component, they always receive the latest props and state. To see what this means, consider this chat room example. Change the server URL or the chat room:
+커스텀 훅은 컴포넌트와 함께 다시 렌더링되기 때문에 항상 최신 프로퍼티와 상태를 받습니다. 이것이 무엇을 의미하는지 이 채팅방 예시를 통해 알아보세요. 서버 URL 또는 선택한 채팅방을 변경합니다:
@@ -517,6 +581,7 @@ export default function ChatRoom({ roomId }) {
```js chat.js
export function createConnection({ serverUrl, roomId }) {
// A real implementation would actually connect to the server
+ // 실제 구현은 진짜 서버로 연결됩니다
if (typeof serverUrl !== 'string') {
throw Error('Expected serverUrl to be a string. Received: ' + serverUrl);
}
@@ -600,8 +665,10 @@ button { margin-left: 10px; }
When you change `serverUrl` or `roomId`, the Effect ["reacts" to your changes](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) and re-synchronizes. You can tell by the console messages that the chat re-connects every time that you change your Effect's dependencies.
+`serverUrl` 혹은 `roomId` 를 변경할 때마다 Effect는 [변화에 “반응"](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values)하고 재동기화 됩니다. Effect의 종속성을 변경할 때마다 채팅이 다시 연결된다는 것은 콘솔 메시지를 통해 알 수 있습니다.
Now move the Effect's code into a custom Hook:
+이제 Effect 코드를 커스텀 훅으로 옮깁니다:
```js {2-13}
export function useChatRoom({ serverUrl, roomId }) {
@@ -621,6 +688,7 @@ export function useChatRoom({ serverUrl, roomId }) {
```
This lets your `ChatRoom` component call your custom Hook without worrying about how it works inside:
+이것은 `ChatRoom` 컴포넌트가 내부에서 어떻게 작동하는지 걱정할 필요 없이 사용자 지정 훅을 호출할 수 있습니다:
```js {4-7}
export default function ChatRoom({ roomId }) {
@@ -644,8 +712,10 @@ export default function ChatRoom({ roomId }) {
```
This looks much simpler! (But it does the same thing.)
+이렇게 하면 더 간단해 보입니다! (하지만 기능상 동일합니다.)
Notice that the logic *still responds* to prop and state changes. Try editing the server URL or the selected room:
+이 로직이 *여전히 prop과 state 변화에 반응*한다는 것을 주목하세요. 서버 URL과 선택한 room을 편집해보세요:
@@ -725,6 +795,7 @@ export function useChatRoom({ serverUrl, roomId }) {
```js chat.js
export function createConnection({ serverUrl, roomId }) {
// A real implementation would actually connect to the server
+ // 실제 구현은 진짜 서버로 연결됩니다
if (typeof serverUrl !== 'string') {
throw Error('Expected serverUrl to be a string. Received: ' + serverUrl);
}
@@ -808,6 +879,7 @@ button { margin-left: 10px; }
Notice how you're taking the return value of one Hook:
+하나의 훅의 값을 어떻게 리턴했는지 주목하세요:
```js {2}
export default function ChatRoom({ roomId }) {
@@ -821,6 +893,7 @@ export default function ChatRoom({ roomId }) {
```
and pass it as an input to another Hook:
+그리고 다른 훅에 인풋으로 전달합니다:
```js {6}
export default function ChatRoom({ roomId }) {
@@ -834,16 +907,19 @@ export default function ChatRoom({ roomId }) {
```
Every time your `ChatRoom` component re-renders, it passes the latest `roomId` and `serverUrl` to your Hook. This is why your Effect re-connects to the chat whenever their values are different after a re-render. (If you ever worked with audio or video processing software, chaining Hooks like this might remind you of chaining visual or audio effects. It's as if the output of `useState` "feeds into" the input of the `useChatRoom`.)
+`ChatRoom` 컴포넌트가 다시 렌더링할 때마다 최신 `roomId`와`serverUrl`을 Hook에 전달합니다. 이것이 바로 재렌더링 후 값이 달라질 때마다 Effect가 채팅에 다시 연결되는 이유입니다. (음악 처리 소프트웨어로 작업해 본 적이 있다면 이런 식으로 Hook을 연결하면 리버브나 코러스 추가와 같이 여러 오디오 효과를 연결하는 것을 떠올릴 수 있습니다. 마치 `useState`의 출력이 `useChatRoom`의 입력에 '피드' 되는 것과 같습니다.)
-### Passing event handlers to custom Hooks {/*passing-event-handlers-to-custom-hooks*/}
+### Passing event handlers to custom Hooks커스텀훅에게 이벤트 핸들러 전달하기 {/*passing-event-handlers-to-custom-hooks*/}
This section describes an **experimental API that has not yet been released** in a stable version of React.
+이 섹션에서는 **아직 React에 추가되지 않은 실험적인 API**에 대해 설명하며, 아직 사용할 수 없습니다.
As you start using `useChatRoom` in more components, you might want to let components customize its behavior. For example, currently, the logic for what to do when a message arrives is hardcoded inside the Hook:
+더 많은 컴포넌트에서 `useChatRoom`을 사용하기 시작하면 다른 컴포넌트에서 그 동작을 사용자 정의할 수 있을 것입니다. 예를 들어, 현재 메시지가 도착했을 때 무엇을 해야 하는지에 대한 로직은 Hook 내부에 하드코딩되어 있습니다:
```js {9-11}
export function useChatRoom({ serverUrl, roomId }) {
@@ -863,6 +939,7 @@ export function useChatRoom({ serverUrl, roomId }) {
```
Let's say you want to move this logic back to your component:
+만약에 이 로직을 다시 컴포넌트 안으로 이동하고 싶다고 가정해 봅시다.
```js {7-9}
export default function ChatRoom({ roomId }) {
@@ -879,6 +956,7 @@ export default function ChatRoom({ roomId }) {
```
To make this work, change your custom Hook to take `onReceiveMessage` as one of its named options:
+이 기능을 사용하려면 커스텀 훅을 변경하여 `onReceiveMessage` 를 이름 옵션 중 하나로 사용하세요.
```js {1,10,13}
export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
@@ -894,12 +972,15 @@ export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
});
return () => connection.disconnect();
}, [roomId, serverUrl, onReceiveMessage]); // ✅ All dependencies declared
+ // ✅ 모든 의존성이 선언됨
}
```
This will work, but there's one more improvement you can do when your custom Hook accepts event handlers.
+이 방법은 작동하지만 커스텀 Hook이 이벤트 핸들러를 수락할 때 한 가지 더 개선할 수 있습니다.
Adding a dependency on `onReceiveMessage` is not ideal because it will cause the chat to re-connect every time the component re-renders. [Wrap this event handler into an Effect Event to remove it from the dependencies:](/learn/removing-effect-dependencies#wrapping-an-event-handler-from-the-props)
+`onReceiveMessage`에 종속성을 추가하면 컴포넌트가 다시 렌더링될 때마다 채팅이 다시 연결되므로 이상적이지 않습니다. [이 이벤트 핸들러를 Effect Event로 래핑하여 종속성에서 제거하세요:](/learn/removing-effect-dependencies#wrapping-an-event-handler-from-the-props)
```js {1,4,5,15,18}
import { useEffect, useEffectEvent } from 'react';
@@ -920,10 +1001,12 @@ export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
});
return () => connection.disconnect();
}, [roomId, serverUrl]); // ✅ All dependencies declared
+ // ✅ 모든 의존성이 선언됨
}
```
Now the chat won't re-connect every time that the `ChatRoom` component re-renders. Here is a fully working demo of passing an event handler to a custom Hook that you can play with:
+이제 `ChatRoom` 컴포넌트가 다시 렌더링할 때마다 채팅이 다시 연결되지 않습니다. 다음은 이벤트 핸들러를 커스텀 Hook에 전달하는 데모입니다:
@@ -1009,6 +1092,7 @@ export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
```js chat.js
export function createConnection({ serverUrl, roomId }) {
// A real implementation would actually connect to the server
+ // 실제 구현은 진짜 서버로 연결됩니다
if (typeof serverUrl !== 'string') {
throw Error('Expected serverUrl to be a string. Received: ' + serverUrl);
}
@@ -1092,19 +1176,24 @@ button { margin-left: 10px; }
Notice how you no longer need to know *how* `useChatRoom` works in order to use it. You could add it to any other component, pass any other options, and it would work the same way. That's the power of custom Hooks.
+이제 더 이상 `useChatRoom`이 *어떻게* 작동하는지 알 필요 없이 사용할 수 있습니다. 다른 컴포넌트에 추가하고 다른 옵션을 전달해도 동일한 방식으로 작동합니다. 이것이 바로 커스텀 Hook의 힘입니다.
-## When to use custom Hooks {/*when-to-use-custom-hooks*/}
+## When to use custom Hooks언제 커스텀 훅을 사용할 것인가 {/*when-to-use-custom-hooks*/}
You don't need to extract a custom Hook for every little duplicated bit of code. Some duplication is fine. For example, extracting a `useFormInput` Hook to wrap a single `useState` call like earlier is probably unnecessary.
+중복되는 모든 코드에 대해 커스텀 훅을 추출할 필요는 없습니다. 약간의 중복은 괜찮습니다. 예를 들어, 앞서처럼 단일 `useState` 호출을 감싸기 위해 `useFormInput` 훅을 추출하는 것은 불필요할 수 있습니다.
However, whenever you write an Effect, consider whether it would be clearer to also wrap it in a custom Hook. [You shouldn't need Effects very often,](/learn/you-might-not-need-an-effect) so if you're writing one, it means that you need to "step outside React" to synchronize with some external system or to do something that React doesn't have a built-in API for. Wrapping it into a custom Hook lets you precisely communicate your intent and how the data flows through it.
+하지만 Effect를 작성할 때마다 커스텀 훅으로 감싸는 것이 더 명확할지 고려하세요. [Effect는 자주 필요하지 않으므로,](/learn/you-might-not-need-an-effect) 만약 Effect를 작성한다면 외부 시스템과 동기화하거나 React에 내장된 API가 없는 작업을 수행하기 위해 "React 외부로 나가야 한다"는 뜻입니다. Effect를 커스텀 훅으로 감싸면 의도와 데이터 흐름 방식을 정확하게 전달할 수 있습니다.
For example, consider a `ShippingForm` component that displays two dropdowns: one shows the list of cities, and another shows the list of areas in the selected city. You might start with some code that looks like this:
+예를 들어, 도시 목록을 표시하는 드롭다운과 선택한 도시의 지역 목록을 표시하는 드롭다운 두 개를 표시하는 `ShippingForm` 컴포넌트를 생각해 봅시다. 다음과 같은 코드로 시작할 수 있습니다:
```js {3-16,20-35}
function ShippingForm({ country }) {
const [cities, setCities] = useState(null);
// This Effect fetches cities for a country
+ // 이 Effect는 국가의 도시들을 페치합니다
useEffect(() => {
let ignore = false;
fetch(`/api/cities?country=${country}`)
@@ -1122,6 +1211,7 @@ function ShippingForm({ country }) {
const [city, setCity] = useState(null);
const [areas, setAreas] = useState(null);
// This Effect fetches areas for the selected city
+ // 이 Effect는 선택된 도시의 장소들을 페치합니다
useEffect(() => {
if (city) {
let ignore = false;
@@ -1142,6 +1232,7 @@ function ShippingForm({ country }) {
```
Although this code is quite repetitive, [it's correct to keep these Effects separate from each other.](/learn/removing-effect-dependencies#is-your-effect-doing-several-unrelated-things) They synchronize two different things, so you shouldn't merge them into one Effect. Instead, you can simplify the `ShippingForm` component above by extracting the common logic between them into your own `useData` Hook:
+이 코드는 상당히 반복적이지만 이러한 효과는 [서로 분리하여 유지하는 것이 맞습니다.](/learn/removing-effect-dependencies#is-your-effect-doing-several-unrelated-things) 서로 다른 두 가지를 동기화하므로 하나의 Effect로 병합해서는 안 됩니다. 대신, 위의 `ShippingForm` 컴포넌트 사이의 공통 로직을 자체 `useData` 훅으로 추출하여 단순화할 수 있습니다:
```js {2-18}
function useData(url) {
@@ -1166,6 +1257,7 @@ function useData(url) {
```
Now you can replace both Effects in the `ShippingForm` components with calls to `useData`:
+이제 `ShippingForm` 구성 요소의 두 효과를 모두 `useData` 호출로 바꿀 수 있습니다:
```js {2,4}
function ShippingForm({ country }) {
@@ -1176,38 +1268,45 @@ function ShippingForm({ country }) {
```
Extracting a custom Hook makes the data flow explicit. You feed the `url` in and you get the `data` out. By "hiding" your Effect inside `useData`, you also prevent someone working on the `ShippingForm` component from adding [unnecessary dependencies](/learn/removing-effect-dependencies) to it. With time, most of your app's Effects will be in custom Hooks.
+커스텀 훅을 추출하면 데이터 흐름을 명시적으로 만들 수 있습니다. `url`을 입력하면 `data`를 가져올 수 있습니다. `useData` 안에 효과를 "숨기면" `ShippingForm` 컴포넌트에서 작업하는 사람이 [불필요한 종속성](/learn/removing-effect-dependencies)을 추가하는 것을 방지할 수 있습니다. 이상적으로는 시간이 지나면 앱의 효과 대부분이 커스텀 훅에 포함될 것입니다.
-#### Keep your custom Hooks focused on concrete high-level use cases {/*keep-your-custom-hooks-focused-on-concrete-high-level-use-cases*/}
+#### Keep your custom Hooks focused on concrete high-level use cases커스텀 훅은 구체적인 고수준 사용 사례에 집중하세요 {/*keep-your-custom-hooks-focused-on-concrete-high-level-use-cases*/}
Start by choosing your custom Hook's name. If you struggle to pick a clear name, it might mean that your Effect is too coupled to the rest of your component's logic, and is not yet ready to be extracted.
+먼저 커스텀 훅의 이름을 선택하세요. 명확한 이름을 고르는 데 어려움을 겪는다면 Effect가 컴포넌트의 나머지 로직과 너무 결합되어 있어 아직 추출할 준비가 되지 않았다는 의미일 수 있습니다.
Ideally, your custom Hook's name should be clear enough that even a person who doesn't write code often could have a good guess about what your custom Hook does, what it takes, and what it returns:
+커스텀 훅의 이름은 코드를 자주 작성하지 않는 사람이라도 커스텀 훅이 무엇을 하고, 무엇을 취하고, 무엇을 반환하는지 짐작할 수 있을 정도로 명확해야 합니다:
* ✅ `useData(url)`
* ✅ `useImpressionLog(eventName, extraData)`
* ✅ `useChatRoom(options)`
When you synchronize with an external system, your custom Hook name may be more technical and use jargon specific to that system. It's good as long as it would be clear to a person familiar with that system:
+외부 시스템과 동기화할 때 커스텀 훅의 이름은 좀 더 기술적이고 해당 시스템과 관련된 전문 용어를 사용할 수 있습니다. 해당 시스템에 익숙한 사람이 이해할 수 있는 이름이라면 괜찮습니다:
* ✅ `useMediaQuery(query)`
* ✅ `useSocket(url)`
* ✅ `useIntersectionObserver(ref, options)`
**Keep custom Hooks focused on concrete high-level use cases.** Avoid creating and using custom "lifecycle" Hooks that act as alternatives and convenience wrappers for the `useEffect` API itself:
+**커스텀 훅은 구체적인 고수준 사용 사례에 집중하세요.** `useEffect` API 자체에 대한 대안 및 편의 래퍼 역할을 하는 커스텀 "라이프사이클" 훅을 생성하거나 사용하지 마세요:
* 🔴 `useMount(fn)`
* 🔴 `useEffectOnce(fn)`
* 🔴 `useUpdateEffect(fn)`
For example, this `useMount` Hook tries to ensure some code only runs "on mount":
+예를 들어`useMount` 훅은 일부코드가 “마운트 할 때”에만 실행됩니다.
```js {4-5,14-15}
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
// 🔴 Avoid: using custom "lifecycle" Hooks
+ // 🔴 이러지 마세요: 커스텀 "라이프사이클" 훅 사용
useMount(() => {
const connection = createConnection({ roomId, serverUrl });
connection.connect();
@@ -1218,22 +1317,27 @@ function ChatRoom({ roomId }) {
}
// 🔴 Avoid: creating custom "lifecycle" Hooks
+// 🔴 이러지 마세요: 커스텀 "라이브사이클" 훅 생성
function useMount(fn) {
useEffect(() => {
fn();
}, []); // 🔴 React Hook useEffect has a missing dependency: 'fn'
+ // 🔴 React 훅 useEffect에 의존성 누락: 'fn'
}
```
**Custom "lifecycle" Hooks like `useMount` don't fit well into the React paradigm.** For example, this code example has a mistake (it doesn't "react" to `roomId` or `serverUrl` changes), but the linter won't warn you about it because the linter only checks direct `useEffect` calls. It won't know about your Hook.
+**`useMount`와 같은 커스텀 "라이프사이클" 훅은 React 패러다임에 잘 맞지 않습니다.** 예를 들어, 이 코드 예시에는 실수가 있지만(`roomId` 및 `serverUrl`변경에 "반응"하지 않음), linter는 직접적인 `useEffect` 호출만 확인하기 때문에 경고하지 않습니다. 당신의 훅에 대해서 알지 못합니다.
If you're writing an Effect, start by using the React API directly:
+Effect를 사용할 것이라면 React API를 직접 사용하세요:
```js
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
// ✅ Good: two raw Effects separated by purpose
+ // ✅ 좋습니다: 목적별로 분리된 두 원시 Effect
useEffect(() => {
const connection = createConnection({ serverUrl, roomId });
@@ -1250,12 +1354,14 @@ function ChatRoom({ roomId }) {
```
Then, you can (but don't have to) extract custom Hooks for different high-level use cases:
+그러면 다른 고수준 사용 사례에 대한 커스텀 훅을 추출할 수 있습니다(반드시 그럴 필요는 없습니다):
```js
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
// ✅ Great: custom Hooks named after their purpose
+ // ✅ 매우 좋습니다: 용도에 따라 이름을 지정한 커스텀 훅
useChatRoom({ serverUrl, roomId });
useImpressionLog('visit_chat', { roomId });
// ...
@@ -1263,14 +1369,17 @@ function ChatRoom({ roomId }) {
```
**A good custom Hook makes the calling code more declarative by constraining what it does.** For example, `useChatRoom(options)` can only connect to the chat room, while `useImpressionLog(eventName, extraData)` can only send an impression log to the analytics. If your custom Hook API doesn't constrain the use cases and is very abstract, in the long run it's likely to introduce more problems than it solves.
+**좋은 커스텀 훅은 호출 코드가 수행하는 작업을 제한하여 보다 선언적으로 만듭니다**. 예를 들어, `useChatRoom(options)`은 채팅방에만 연결할 수 있고, `useImpressionLog(eventName, extraData)`는 애널리틱스에 노출 로그만 전송할 수 있습니다. 커스텀 훅 API가 사용 사례를 제한하지 않고 매우 추상적일 경우, 장기적으로는 해결하는 것보다 더 많은 문제를 야기할 가능성이 높습니다.
-### Custom Hooks help you migrate to better patterns {/*custom-hooks-help-you-migrate-to-better-patterns*/}
+### Custom Hooks help you migrate to better patterns커스텀 훅은 더 나은 패턴으로 마이그레이션하는데 도움을 줍니다. {/*custom-hooks-help-you-migrate-to-better-patterns*/}
Effects are an ["escape hatch"](/learn/escape-hatches): you use them when you need to "step outside React" and when there is no better built-in solution for your use case. With time, the React team's goal is to reduce the number of the Effects in your app to the minimum by providing more specific solutions to more specific problems. Wrapping your Effects in custom Hooks makes it easier to upgrade your code when these solutions become available.
+Effect는 ["탈출구"](/learn/escape-hatches):입니다. "React를 벗어나야 할 때", 그리고 사용 사례에 더 나은 내장 솔루션이 없을 때 사용합니다. 시간이 지남에 따라 React 팀의 목표는 더 구체적인 문제에 대한 더 구체적인 솔루션을 제공함으로써 앱에서 Effect의 수를 최소한으로 줄이는 것입니다. 효과를 커스텀 훅으로 감싸면 이러한 솔루션이 제공될 때 코드를 더 쉽게 업그레이드할 수 있습니다.
Let's return to this example:
+이 예제로 돌아가 보겠습니다:
@@ -1332,8 +1441,10 @@ export function useOnlineStatus() {
In the above example, `useOnlineStatus` is implemented with a pair of [`useState`](/reference/react/useState) and [`useEffect`.](/reference/react/useEffect) However, this isn't the best possible solution. There is a number of edge cases it doesn't consider. For example, it assumes that when the component mounts, `isOnline` is already `true`, but this may be wrong if the network already went offline. You can use the browser [`navigator.onLine`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine) API to check for that, but using it directly would not work on the server for generating the initial HTML. In short, this code could be improved.
+위의 예제에서는, [`useState`](/reference/react/useState)와 [`useEffect`](/reference/react/useEffect)의 페어로 `useOnlineStatus`를 구성했습니다. 하지만 이것은 최적의 방법은 아닙니다. 고려하지 않은 여러 케이스들이 있습니다.예를 들어, 컴포넌트가 마운트될 때 `isOnline`이 이미 `true`라고 가정하지만, 네트워크가 이미 오프라인 상태였다면 이는 틀릴 수 있습니다. 브라우저 [`navigator.onLine`](https://developer.mozilla.org/ko/docs/Web/API/Navigator/onLine) API를 사용하여 이를 확인할 수 있지만, 서버에서 React 앱을 실행하여 초기 HTML을 생성하는 경우 이를 직접 사용하면 코드가 깨질 수 있습니다. 요컨대, 이 코드는 개선될 수 있습니다.
Luckily, React 18 includes a dedicated API called [`useSyncExternalStore`](/reference/react/useSyncExternalStore) which takes care of all of these problems for you. Here is how your `useOnlineStatus` Hook, rewritten to take advantage of this new API:
+다행히 React 18에는 이 모든 문제를 해결해 주는 [`useSyncExternalStore`](/reference/react/useSyncExternalStore)라는 전용 API가 포함되어 있습니다. 이 새로운 API를 활용하기 위해 재작성된 `useOnlineStatus` 훅은 다음과 같습니다:
@@ -1385,15 +1496,17 @@ export function useOnlineStatus() {
return useSyncExternalStore(
subscribe,
() => navigator.onLine, // How to get the value on the client
+ // 클라이언트에서 값을 가져오는 방법
() => true // How to get the value on the server
+ // 서버에서 값을 가져오는 방법
);
}
-
```
Notice how **you didn't need to change any of the components** to make this migration:
+이 마이그레이션을 위해 **구성 요소를 변경할 필요가 없다는 점**을 주목하세요:
```js {2,7}
function StatusBar() {
@@ -1408,22 +1521,30 @@ function SaveButton() {
```
This is another reason for why wrapping Effects in custom Hooks is often beneficial:
+이것이 커스텀 훅으로 효과를 래핑하는 것이 종종 유익한 또 다른 이유입니다:
1. You make the data flow to and from your Effects very explicit.
2. You let your components focus on the intent rather than on the exact implementation of your Effects.
3. When React adds new features, you can remove those Effects without changing any of your components.
+
+1. Effect와의 데이터 흐름을 매우 명확하게 만들 수 있습니다.
+2. 컴포넌트가 효과의 정확한 구현보다는 의도에 집중할 수 있습니다.
+3. React가 새로운 기능을 추가할 때 컴포넌트를 변경하지 않고도 해당 효과를 제거할 수 있습니다.
+
Similar to a [design system,](https://uxdesign.cc/everything-you-need-to-know-about-design-systems-54b109851969) you might find it helpful to start extracting common idioms from your app's components into custom Hooks. This will keep your components' code focused on the intent, and let you avoid writing raw Effects very often. Many excellent custom Hooks are maintained by the React community.
+[디자인 시스템](https://uxdesign.cc/everything-you-need-to-know-about-design-systems-54b109851969)과 유사하게 앱의 컴포넌트에서 공통된 관용구를 추출하여 커스텀 훅으로 만드는 것이 도움이 될 수 있습니다. 이렇게 하면 컴포넌트의 코드가 의도에 집중할 수 있고, 원시 효과를 자주 작성하는 것을 피할 수 있습니다. React 커뮤니티에서 관리하고 있는 훌륭한 커스텀 훅도 많이 있습니다.
-#### Will React provide any built-in solution for data fetching? {/*will-react-provide-any-built-in-solution-for-data-fetching*/}
+#### Will React provide any built-in solution for data fetching?React는 데이터 페칭을 위해 내장 솔루션을 제공할건가요? {/*will-react-provide-any-built-in-solution-for-data-fetching*/}
We're still working out the details, but we expect that in the future, you'll write data fetching like this:
+아직 세부 사항을 작업 중이지만, 앞으로는 다음과 같이 데이터 페칭을 할 수 있을 것으로 예상합니다:
```js {1,4,6}
-import { use } from 'react'; // Not available yet!
-
+import { use } from 'react'; // Not available yet!
+ // 아직 동작하지 않습니다!
function ShippingForm({ country }) {
const cities = use(fetch(`/api/cities?country=${country}`));
const [city, setCity] = useState(null);
@@ -1432,12 +1553,14 @@ function ShippingForm({ country }) {
```
If you use custom Hooks like `useData` above in your app, it will require fewer changes to migrate to the eventually recommended approach than if you write raw Effects in every component manually. However, the old approach will still work fine, so if you feel happy writing raw Effects, you can continue to do that.
+앱에서 위의 `useData`와 같은 커스텀 훅을 사용하면 모든 컴포넌트에 원시 Effect를 수동으로 작성하는 것보다 최종적으로 권장되는 접근 방식으로 마이그레이션하는 데 더 적은 변경이 필요할 것입니다. 다만 이전 접근 방식도 여전히 잘 작동하므로 원시 Effect를 작성하는 것이 만족스럽다면 계속 사용할 수 있습니다.
-### There is more than one way to do it {/*there-is-more-than-one-way-to-do-it*/}
+### There is more than one way to do it여러가지 방법이 있습니다 {/*there-is-more-than-one-way-to-do-it*/}
Let's say you want to implement a fade-in animation *from scratch* using the browser [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) API. You might start with an Effect that sets up an animation loop. During each frame of the animation, you could change the opacity of the DOM node you [hold in a ref](/learn/manipulating-the-dom-with-refs) until it reaches `1`. Your code might start like this:
+브라우저 요청 [`requestAnimationFrame`](https://developer.mozilla.org/ko/docs/Web/API/window/requestAnimationFrame) API를 사용하여 페이드인 애니메이션을 *처음부터* 구현한다고 가정해 보겠습니다. 애니메이션 루프를 설정하는 효과로 시작할 수 있습니다. 애니메이션의 각 프레임 동안 [ref로 유지하는](/learn/manipulating-the-dom-with-refs) DOM 노드의 불투명도를 `1`에 도달할 때까지 변경할 수 있습니다. 코드는 다음과 같이 시작할 수 있습니다:
@@ -1460,6 +1583,7 @@ function Welcome() {
onProgress(progress);
if (progress < 1) {
// We still have more frames to paint
+ // 아직 칠해야 할 프레임이 남아있습니다
frameId = requestAnimationFrame(onFrame);
}
}
@@ -1521,6 +1645,7 @@ html, body { min-height: 300px; }
To make the component more readable, you might extract the logic into a `useFadeIn` custom Hook:
+컴포넌트의 가독성을 높이기 위해 로직을 `useFadeIn` 커스텀 훅으로 추출할 수 있습니다:
@@ -1570,6 +1695,7 @@ export function useFadeIn(ref, duration) {
onProgress(progress);
if (progress < 1) {
// We still have more frames to paint
+ // 아직 칠해야 할 프레임이 남아있습니다
frameId = requestAnimationFrame(onFrame);
}
}
@@ -1612,6 +1738,7 @@ html, body { min-height: 300px; }
You could keep the `useFadeIn` code as is, but you could also refactor it more. For example, you could extract the logic for setting up the animation loop out of `useFadeIn` into a custom `useAnimationLoop` Hook:
+`useFadeIn` 코드를 그대로 유지할 수도 있지만 더 리팩토링할 수도 있습니다. 예를 들어, 애니메이션 루프를 설정하는 로직을 `useFadeIn`에서 추출하여 `useAnimationLoop`라는 새로운 커스텀 훅으로 만들 수 있습니다:
@@ -1716,6 +1843,7 @@ html, body { min-height: 300px; }
However, you didn't *have to* do that. As with regular functions, ultimately you decide where to draw the boundaries between different parts of your code. You could also take a very different approach. Instead of keeping the logic in the Effect, you could move most of the imperative logic inside a JavaScript [class:](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)
+하지만 꼭 그렇게 할 필요는 없습니다. 일반 함수와 마찬가지로 궁극적으로 코드의 여러 부분 사이의 경계를 어디에 그릴지는 사용자가 결정합니다. 예를 들어 매우 다른 접근 방식을 취할 수도 있습니다. Effect에 로직을 유지하는 대신 대부분의 명령형 로직을 JavaScript [클래스](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes) 내부로 옮길 수 있습니다:
@@ -1783,6 +1911,7 @@ export class FadeInAnimation {
this.stop();
} else {
// We still have more frames to paint
+ // 아직 칠해야 할 프레임이 남아있습니다
this.frameId = requestAnimationFrame(() => this.onFrame());
}
}
@@ -1814,8 +1943,10 @@ html, body { min-height: 300px; }
Effects let you connect React to external systems. The more coordination between Effects is needed (for example, to chain multiple animations), the more it makes sense to extract that logic out of Effects and Hooks *completely* like in the sandbox above. Then, the code you extracted *becomes* the "external system". This lets your Effects stay simple because they only need to send messages to the system you've moved outside React.
+Effects를 사용하면 React를 외부 시스템에 연결할 수 있습니다. 예를 들어 여러 애니메이션을 체인으로 연결하기 위해 효과 간의 조정이 더 많이 필요할수록 위의 샌드박스에서처럼 효과와 훅에서 해당 로직을 완전히 추출하는 것이 더 합리적입니다. 그러면 추출한 코드가 "외부 시스템"이 됩니다. 이렇게 하면 React 외부로 이동한 시스템으로 메시지를 보내기만 하면 되기 때문에 Effects를 단순하게 유지할 수 있습니다.
The examples above assume that the fade-in logic needs to be written in JavaScript. However, this particular fade-in animation is both simpler and much more efficient to implement with a plain [CSS Animation:](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations)
+위의 예시에서는 페이드인 로직이 자바스크립트로 작성되어야 한다고 가정했습니다. 하지만 이 특정 페이드인 애니메이션은 일반 [CSS 애니메이션](https://developer.mozilla.org/ko/docs/Web/CSS/CSS_Animations/Using_CSS_animations)으로 구현하는 것이 더 간단하고 훨씬 더 효율적입니다:
@@ -1871,6 +2002,7 @@ html, body { min-height: 300px; }
Sometimes, you don't even need a Hook!
+때로는 훅이 전혀 필요 없을 때도 있습니다!
@@ -1883,14 +2015,26 @@ Sometimes, you don't even need a Hook!
- Wrap event handlers received by custom Hooks into Effect Events.
- Don't create custom Hooks like `useMount`. Keep their purpose specific.
- It's up to you how and where to choose the boundaries of your code.
+
+- 커스텀 훅을 사용하면 컴포넌트 간에 로직을 공유할 수 있습니다.
+- 커스텀 훅의 이름은 `use`로 시작하고 대문자로 끝나야 합니다.
+- 커스텀 훅은 state 저장 로직만 공유하며 상태 자체는 공유하지 않습니다.
+- 반응형 값을 한 훅에서 다른 훅으로 전달할 수 있으며 최신 상태로 유지됩니다.
+- 컴포넌트가 다시 렌더링될 때마다 모든 훅이 다시 실행됩니다.
+- 커스텀 훅의 코드는 컴포넌트의 코드와 같이 순수해야 합니다.
+- 커스텀 훅이 수신한 이벤트 핸들러를 Effect Event로 래핑하세요.
+- `useMount`와 같은 커스텀 훅을 만들지 마세요. 용도를 명확히 하세요.
+- 코드의 경계를 어디에서 어떻게 선택할지는 여러분이 결정할 수 있습니다.
+
-#### Extract a `useCounter` Hook {/*extract-a-usecounter-hook*/}
+#### Extract a `useCounter` Hook`useCounter` 훅 추출하기 {/*extract-a-usecounter-hook*/}
This component uses a state variable and an Effect to display a number that increments every second. Extract this logic into a custom Hook called `useCounter`. Your goal is to make the `Counter` component implementation look exactly like this:
+이 컴포넌트는 state 변수와 Effect를 사용해 매초마다 증가하는 숫자를 표시합니다. 이 로직을 `useCounter`라는 커스텀 훅으로 추출합니다. 목표는 `Counter` 컴포넌트 구현을 다음과 같이 만드는 것입니다:
```js
export default function Counter() {
@@ -1900,6 +2044,7 @@ export default function Counter() {
```
You'll need to write your custom Hook in `useCounter.js` and import it into the `Counter.js` file.
+`useCounter.js`에 커스텀 훅을 작성하고 이를 `Counter.js` 파일로 가져와야 합니다.
@@ -1927,6 +2072,7 @@ export default function Counter() {
Your code should look like this:
+아래의 코드처럼 작성되어야 합니다:
@@ -1957,12 +2103,14 @@ export function useCounter() {
Notice that `App.js` doesn't need to import `useState` or `useEffect` anymore.
+`App.js`는 더 이상 `useState`나 `useEffect`를 import 할 필요가 없습니다.
-#### Make the counter delay configurable {/*make-the-counter-delay-configurable*/}
+#### Make the counter delay configurable카운터 지연을 구성 가능하게 만들기 {/*make-the-counter-delay-configurable*/}
In this example, there is a `delay` state variable controlled by a slider, but its value is not used. Pass the `delay` value to your custom `useCounter` Hook, and change the `useCounter` Hook to use the passed `delay` instead of hardcoding `1000` ms.
+이 예시에서는 슬라이더로 제어되는 `delay` 되는 state 변수가 있지만 그 값은 사용되지 않습니다. `delay`값을 커스텀 훅인 `useCounter`에 전달하고, 1000ms를 하드코딩하는 대신 전달된 `delay`을 사용하도록 `useCounter` 훅을 변경하세요.
@@ -2013,6 +2161,7 @@ export function useCounter() {
Pass the `delay` to your Hook with `useCounter(delay)`. Then, inside the Hook, use `delay` instead of the hardcoded `1000` value. You'll need to add `delay` to your Effect's dependencies. This ensures that a change in `delay` will reset the interval.
+`useCounter(delay)`로 `delay`를 훅에 전달합니다. 그런 다음 훅 내부에서 하드코딩된 `1000` 값 대신 `delay`를 사용합니다. Effect의 종속성에 `delay`를 추가해야 합니다. 이렇게 하면 `delay`가 변경되면 간격이 재설정됩니다.
@@ -2062,9 +2211,10 @@ export function useCounter(delay) {
-#### Extract `useInterval` out of `useCounter` {/*extract-useinterval-out-of-usecounter*/}
+#### Extract `useInterval` out of `useCounter``useCounter`에서 `useInterval` 추출하기 {/*extract-useinterval-out-of-usecounter*/}
Currently, your `useCounter` Hook does two things. It sets up an interval, and it also increments a state variable on every interval tick. Split out the logic that sets up the interval into a separate Hook called `useInterval`. It should take two arguments: the `onTick` callback, and the `delay`. After this change, your `useCounter` implementation should look like this:
+현재 사용 중인 `useCounter` 훅은 두 가지 작업을 수행합니다. 간격(interval)을 설정하고 간격이 틱 될 때마다 state 변수를 증가시킵니다. 간격을 설정하는 로직을 `useInterval`이라는 별도의 훅으로 분리하세요. 이 훅은 `onTick` 과`delay` 라는 두 개의 인수를 받아야 합니다. 이렇게 변경하면 `useCounter`구현은 다음과 같이 보일 것입니다:
```js
export function useCounter(delay) {
@@ -2077,6 +2227,7 @@ export function useCounter(delay) {
```
Write `useInterval` in the `useInterval.js` file and import it into the `useCounter.js` file.
+`useInterval`와 `useInterval.js`의 파일을 작성하고 `useCounter.js` 파일에 import 하세요.
@@ -2114,6 +2265,7 @@ export function useCounter(delay) {
The logic inside `useInterval` should set up and clear the interval. It doesn't need to do anything else.
+`useInterval` 내부의 로직에서 간격을 설정하고 지워야 합니다. 다른 작업은 할 필요가 없습니다.
@@ -2153,16 +2305,20 @@ export function useInterval(onTick, delay) {
Note that there is a bit of a problem with this solution, which you'll solve in the next challenge.
+이 정답에는 약간의 문제가 있는데, 다음 도전 과제에서 해결할 것입니다.
-#### Fix a resetting interval {/*fix-a-resetting-interval*/}
+#### Fix a resetting interval인터벌 리셋 고치기 {/*fix-a-resetting-interval*/}
In this example, there are *two* separate intervals.
+이 예시에서는 두 개의 별도의 인터벌이 있습니다.
The `App` component calls `useCounter`, which calls `useInterval` to update the counter every second. But the `App` component *also* calls `useInterval` to randomly update the page background color every two seconds.
+`App`컴포넌트는`useCounter`를 호출하고, 이 컴포넌트는 `useInterval`을 호출하여 매 초마다 카운터를 업데이트합니다. 그러나 `App`컴포넌트는 2초마다 페이지 배경색을 임의로 업데이트하기 위해 `useInterval`도 호출합니다.
For some reason, the callback that updates the page background never runs. Add some logs inside `useInterval`:
+어떤 이유에서인지 페이지 배경을 업데이트하는 콜백이 실행되지 않습니다. `useInterval`안에 몇 가지 로그를 추가하세요.
```js {2,5}
useEffect(() => {
@@ -2176,12 +2332,15 @@ For some reason, the callback that updates the page background never runs. Add s
```
Do the logs match what you expect to happen? If some of your Effects seem to re-synchronize unnecessarily, can you guess which dependency is causing that to happen? Is there some way to [remove that dependency](/learn/removing-effect-dependencies) from your Effect?
+로그가 예상한 것과 일치하나요? 일부 Effect가 불필요하게 재동기화되는 것 같다면 어떤 종속성 때문에 그런 일이 발생하는지 짐작할 수 있나요? Effect에서 해당 [종속성을 제거](/learn/removing-effect-dependencies)할 수 있는 방법이 있나요?
After you fix the issue, you should expect the page background to update every two seconds.
+문제를 해결한 후에는 페이지 배경이 2초마다 업데이트되어야 합니다.
It looks like your `useInterval` Hook accepts an event listener as an argument. Can you think of some way to wrap that event listener so that it doesn't need to be a dependency of your Effect?
+`useInterval` 훅이 이벤트 리스너를 인수로 받아들이는 것 같습니다. 이벤트 리스너가 Effect의 종속성이 될 필요가 없도록 이벤트 리스너를 감싸는 방법을 생각해낼 수 있을까요?
@@ -2251,10 +2410,13 @@ export function useInterval(onTick, delay) {
Inside `useInterval`, wrap the tick callback into an Effect Event, as you did [earlier on this page.](/learn/reusing-logic-with-custom-hooks#passing-event-handlers-to-custom-hooks)
+`useInterval` 내에서 [이 페이지의 앞부분에서 한 것처럼](/learn/reusing-logic-with-custom-hook#passing-event-handlers-to-custom-hook) 틱 콜백을 효과 이벤트에 래핑합니다.
This will allow you to omit `onTick` from dependencies of your Effect. The Effect won't re-synchronize on every re-render of the component, so the page background color change interval won't get reset every second before it has a chance to fire.
+이렇게 하면 Effect의 종속성에서 `onTick`을 생략할 수 있습니다. 컴포넌트를 다시 렌더링할 때마다 Effect가 다시 동기화되지 않으므로 페이지 배경색 변경 간격이 매초마다 재설정되지 않고 실행될 수 있습니다.
With this change, both intervals work as expected and don't interfere with each other:
+해당 변경으로 두 간격이 모두 예상대로 작동하며 서로 간섭하지 않습니다:
@@ -2321,21 +2483,27 @@ export function useInterval(callback, delay) {
-#### Implement a staggering movement {/*implement-a-staggering-movement*/}
+#### Implement a staggering movement비틀거리는 움직임 구현하기 {/*implement-a-staggering-movement*/}
In this example, the `usePointerPosition()` Hook tracks the current pointer position. Try moving your cursor or your finger over the preview area and see the red dot follow your movement. Its position is saved in the `pos1` variable.
+이 예시에서는 `usePointerPosition()`훅이 현재 포인터 위치를 추적합니다. 미리보기 영역 위로 커서나 손가락을 움직이면 빨간색 점이 움직임을 따라가는 것을 확인하세요. 그 위치는 `pos1` 변수에 저장됩니다.
In fact, there are five (!) different red dots being rendered. You don't see them because currently they all appear at the same position. This is what you need to fix. What you want to implement instead is a "staggered" movement: each dot should "follow" the previous dot's path. For example, if you quickly move your cursor, the first dot should follow it immediately, the second dot should follow the first dot with a small delay, the third dot should follow the second dot, and so on.
+실제로는 다섯 개(!)의 다른 빨간색 점이 렌더링되고 있습니다. 현재는 모두 같은 위치에 나타나기 때문에 보이지 않습니다. 이 부분을 수정해야 합니다. 대신 구현하려는 것은 "엇갈린" 움직임입니다. 각 점이 이전 점의 경로를 "따라야" 합니다. 예를 들어 커서를 빠르게 이동하면 첫 번째 점은 즉시 따라가고, 두 번째 점은 약간의 지연을 두고 첫 번째 점을 따라가고, 세 번째 점은 두 번째 점을 따라가는 등의 방식으로 커서를 이동해야 합니다.
You need to implement the `useDelayedValue` custom Hook. Its current implementation returns the `value` provided to it. Instead, you want to return the value back from `delay` milliseconds ago. You might need some state and an Effect to do this.
+`useDelayedValue` 커스텀 훅을 구현해야 합니다. 현재 구현은 제공된 값을 반환합니다. 대신 밀리초 전 `delay`에서 값을 다시 반환하고 싶습니다. 이를 위해서는 state와 Effect가 필요할 수 있습니다.
After you implement `useDelayedValue`, you should see the dots move following one another.
+`useDelayedValue`을 구현하고 나면 점들이 서로 따라 움직이는 것을 볼 수 있어야합니다
You'll need to store the `delayedValue` as a state variable inside your custom Hook. When the `value` changes, you'll want to run an Effect. This Effect should update `delayedValue` after the `delay`. You might find it helpful to call `setTimeout`.
+`delayedValue`을 커스텀 훅 안에 state 변수로 저장해야 합니다. `value`가 변경되면 Effect를 실행하고 싶을 것입니다. 이 Effect는 `delay` 이후에 `delayedValue`를 업데이트해야 합니다. `setTimeout`을 호출하는 것이 도움이 될 수 있습니다.
Does this Effect need cleanup? Why or why not?
+Effect를 정리해야 하나요? 왜 또는 왜 하지 말아야 하나요?
@@ -2409,6 +2577,7 @@ body { min-height: 300px; }
Here is a working version. You keep the `delayedValue` as a state variable. When `value` updates, your Effect schedules a timeout to update the `delayedValue`. This is why the `delayedValue` always "lags behind" the actual `value`.
+다음은 작동하는 버전입니다. `delayedValue`을 state 변수로 유지합니다. `value`가 업데이트될 때 Effect는 timeout을 예약하여 `delayedValue`를 업데이트합니다. 이것이 바로 `delayedValue`가 항상 실제 `value`보다 "뒤처지는" 이유입니다.
@@ -2486,6 +2655,7 @@ body { min-height: 300px; }
Note that this Effect *does not* need cleanup. If you called `clearTimeout` in the cleanup function, then each time the `value` changes, it would reset the already scheduled timeout. To keep the movement continuous, you want all the timeouts to fire.
+Effect는 정리가 *필요하지 않다*는 점에 유의하세요. cleanup 함수에서 `clearTimeout`을 호출하면 `value`가 변경될 때마다 이미 예약된 timeout이 재설정됩니다. 동작을 계속 유지하려면 모든 timeout이 실행되기를 원합니다.
diff --git a/src/content/learn/scaling-up-with-reducer-and-context.md b/src/content/learn/scaling-up-with-reducer-and-context.md
index 0281afcec17..16ec478a77e 100644
--- a/src/content/learn/scaling-up-with-reducer-and-context.md
+++ b/src/content/learn/scaling-up-with-reducer-and-context.md
@@ -1,10 +1,12 @@
---
title: Scaling Up with Reducer and Context
+translatedTitle: Reducer와 Context로 확장하기
---
Reducers let you consolidate a component's state update logic. Context lets you pass information deep down to other components. You can combine reducers and context together to manage state of a complex screen.
+Reducer를 사용하면 컴포넌트의 state 업데이트 로직을 통합할 수 있습니다. Context를 사용하면 다른 컴포넌트들에 정보를 전달할 수 있습니다. Reducer와 context를 함께 사용하여 복잡한 화면의 state를 관리할 수 있습니다.
@@ -14,11 +16,18 @@ Reducers let you consolidate a component's state update logic. Context lets you
* How to avoid passing state and dispatch through props
* How to keep context and state logic in a separate file
+
+* reducer와 context를 결합하는 방법
+* state와 dispatch 함수를 prop으로 전달하지 않는 방법
+* context와 state 로직을 별도의 파일에서 관리하는 방법
+
+
-## Combining a reducer with context {/*combining-a-reducer-with-context*/}
+## Combining a reducer with contextreducer와 context를 결합하기 {/*combining-a-reducer-with-context*/}
In this example from [the introduction to reducers](/learn/extracting-state-logic-into-a-reducer), the state is managed by a reducer. The reducer function contains all of the state update logic and is declared at the bottom of this file:
+[Reducer의 개요](/learn/extracting-state-logic-into-a-reducer)에서 reducer로 state를 관리하는 방법에 대해 알아보았습니다. 해당 예시에서 state 업데이트 로직을 모두 포함하는 reducer 함수를 App.js 파일의 맨 아래에 선언했습니다:
@@ -208,8 +217,10 @@ ul, li { margin: 0; padding: 0; }
A reducer helps keep the event handlers short and concise. However, as your app grows, you might run into another difficulty. **Currently, the `tasks` state and the `dispatch` function are only available in the top-level `TaskApp` component.** To let other components read the list of tasks or change it, you have to explicitly [pass down](/learn/passing-props-to-a-component) the current state and the event handlers that change it as props.
+Reducer를 사용하면 이벤트 핸들러를 간결하고 명확하게 만들 수 있습니다. 그러나 앱이 커질수록 다른 어려움에 부딪힐 수 있습니다. **현재 `tasks` state와 `dispatch` 함수는 최상위 컴포넌트인 `TaskBoard`에서만 사용할 수 있습니다.** 다른 컴포넌트들에서 tasks의 리스트를 읽고 변경하려면 prop을 통해 현재 state와 state를 변경할 수 있는 이벤트 핸들러를 명시적으로 [전달](/learn/passing-props-to-a-component)해야 합니다.
For example, `TaskApp` passes a list of tasks and the event handlers to `TaskList`:
+예를 들어, 아래 `TaskApp` 컴포넌트에서 `TaskList` 컴포넌트로 task 리스트와 이벤트 핸들러를 전달합니다:
```js
그리고 `TaskList` 컴포넌트에서 `Task` 컴포넌트로 이벤트 핸들러를 전달합니다:
+
```js
지금처럼 간단한 예시에서는 잘 동작하지만, 수십 수백 개의 컴포넌트를 거쳐 state나 함수를 전달하기는 쉽지 않습니다!
This is why, as an alternative to passing them through props, you might want to put both the `tasks` state and the `dispatch` function [into context.](/learn/passing-data-deeply-with-context) **This way, any component below `TaskApp` in the tree can read the tasks and dispatch actions without the repetitive "prop drilling".**
+그래서 `tasks` state와 `dispatch` 함수를 props를 통해 전달하는 대신 [context에 넣어서 사용](/learn/passing-data-deeply-with-context)하고 싶을 겁니다. **그러면 반복적인 "prop drilling" 없이 `TaskBoard` 아래의 모든 컴포넌트 트리에서 tasks를 읽고 dispatch 함수를 실행할 수 있습니다.**
Here is how you can combine a reducer with context:
+Reducer와 context를 결합하는 방법은 아래와 같습니다:
+
+1. **Create** the context.
+2. **Put** state and dispatch into context.
+3. **Use** context anywhere in the tree.
-1. **Create** the context.
-2. **Put** state and dispatch into context.
-3. **Use** context anywhere in the tree.
+
+1. Context를 **생성한다**.
+2. State과 dispatch 함수를 context에 **넣는다**.
+3. 트리 안에서 context를 **사용한다**.
+
-### Step 1: Create the context {/*step-1-create-the-context*/}
+### Step 1: Create the contextContext 생성하기 {/*step-1-create-the-context*/}
The `useReducer` Hook returns the current `tasks` and the `dispatch` function that lets you update them:
+`useReducer`훅은 현재 `tasks`와 업데이트할 수 있는 `dispatch` 함수를 반환합니다.
```js
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
```
To pass them down the tree, you will [create](/learn/passing-data-deeply-with-context#step-2-use-the-context) two separate contexts:
+트리를 통해 전달하려면, 두 개의 별개의 context를 생성해야 합니다.
-- `TasksContext` provides the current list of tasks.
+- `TasksContext` provides the current list of tasks.
- `TasksDispatchContext` provides the function that lets components dispatch actions.
+
+- TasksContext는 현재 tasks 리스트를 제공합니다.
+- `TasksDispatchContext`는 컴포넌트에서 action을 dispatch 하는 함수를 제공합니다.
+
+
Export them from a separate file so that you can later import them from other files:
+두 context는 나중에 다른 파일에서 가져올 수 있도록 별도의 파일에서 내보냅니다.
@@ -449,10 +479,12 @@ ul, li { margin: 0; padding: 0; }
Here, you're passing `null` as the default value to both contexts. The actual values will be provided by the `TaskApp` component.
+두 개의 context에 모두 기본값을 `null`로 전달하고 있습니다. 실제 값은 `TaskBoard` 컴포넌트에서 제공합니다.
-### Step 2: Put state and dispatch into context {/*step-2-put-state-and-dispatch-into-context*/}
+### Step 2: Put state and dispatch into contextState와 dispatch 함수를 context에 넣기 {/*step-2-put-state-and-dispatch-into-context*/}
Now you can import both contexts in your `TaskApp` component. Take the `tasks` and `dispatch` returned by `useReducer()` and [provide them](/learn/passing-data-deeply-with-context#step-3-provide-the-context) to the entire tree below:
+이제 `TaskBoard` 컴포넌트에서 두 context를 모두 불러올 수 있습니다. `useReducer()`를 통해 반환된 `tasks`와 `dispatch`를 받고 [아래 트리 전체에 전달](/learn/passing-data-deeply-with-context#step-3-provide-the-context)합니다:
```js {4,7-8}
import { TasksContext, TasksDispatchContext } from './TasksContext.js';
@@ -471,6 +503,7 @@ export default function TaskApp() {
```
For now, you pass the information both via props and in context:
+지금은 props와 context를 모두 이용하여 정보를 전달하고 있습니다:
@@ -670,10 +703,12 @@ ul, li { margin: 0; padding: 0; }
In the next step, you will remove prop passing.
+다음 단계에서 이제 prop을 통한 전달을 제거합니다.
-### Step 3: Use context anywhere in the tree {/*step-3-use-context-anywhere-in-the-tree*/}
+### Step 3: Use context anywhere in the tree트리 안에서 context 사용하기 {/*step-3-use-context-anywhere-in-the-tree*/}
Now you don't need to pass the list of tasks or the event handlers down the tree:
+이제 tasks 리스트나 이벤트 핸들러를 트리 아래로 전달할 필요가 없습니다:
```js {4-5}
@@ -686,6 +721,7 @@ Now you don't need to pass the list of tasks or the event handlers down the tree
```
Instead, any component that needs the task list can read it from the `TaskContext`:
+대신 필요한 컴포넌트에서는 `TaskContext`에서 task 리스트를 읽을 수 있습니다:
```js {2}
export default function TaskList() {
@@ -694,6 +730,7 @@ export default function TaskList() {
```
To update the task list, any component can read the `dispatch` function from context and call it:
+task 리스트를 업데이트하기 위해서 컴포넌트에서 context의 `dispatch` 함수를 읽고 호출할 수 있습니다:
```js {3,9-13}
export default function AddTask() {
@@ -714,6 +751,7 @@ export default function AddTask() {
```
**The `TaskApp` component does not pass any event handlers down, and the `TaskList` does not pass any event handlers to the `Task` component either.** Each component reads the context that it needs:
+**`TaskBoard` 컴포넌트는 자식 컴포넌트에, `TaskList`는 `Task` 컴포넌트에 이벤트 핸들러를 전달하지 않습니다.** 각 컴포넌트에서 필요한 context를 읽을 수 있습니다:
@@ -898,10 +936,12 @@ ul, li { margin: 0; padding: 0; }
**The state still "lives" in the top-level `TaskApp` component, managed with `useReducer`.** But its `tasks` and `dispatch` are now available to every component below in the tree by importing and using these contexts.
+**State와 state를 관리하는 `useReducer`는 여전히 최상위 컴포넌트인 `TaskBoard`에 있습니다.** 그러나 `tasks`와 `dispatch`는 하위 트리 컴포넌트 어디서나 context를 불러와서 사용할 수 있습니다.
-## Moving all wiring into a single file {/*moving-all-wiring-into-a-single-file*/}
+## Moving all wiring into a single file하나의 파일로 합치기 {/*moving-all-wiring-into-a-single-file*/}
You don't have to do this, but you could further declutter the components by moving both reducer and context into a single file. Currently, `TasksContext.js` contains only two context declarations:
+반드시 이런 방식으로 작성하지 않아도 되지만, reducer와 context를 모두 하나의 파일에 작성하면 컴포넌트들을 조금 더 정리할 수 있습니다. 현재, `TasksContext.js`는 두 개의 context만을 선언하고 있습니다:
```js
import { createContext } from 'react';
@@ -911,11 +951,18 @@ export const TasksDispatchContext = createContext(null);
```
This file is about to get crowded! You'll move the reducer into that same file. Then you'll declare a new `TasksProvider` component in the same file. This component will tie all the pieces together:
+이제 이 파일이 좀 더 복잡해질 예정입니다. Reducer를 같은 파일로 옮기고 `TasksProvider` 컴포넌트를 새로 선언합니다. 이 컴포넌트는 모든 것을 하나로 묶는 역할을 하게 됩니다:
1. It will manage the state with a reducer.
2. It will provide both contexts to components below.
3. It will [take `children` as a prop](/learn/passing-props-to-a-component#passing-jsx-as-children) so you can pass JSX to it.
+
+1. Reducer로 state를 관리합니다.
+2. 두 context를 모두 자식 컴포넌트에 제공합니다.
+3. [`children`을 prop으로 받기 때문에](/learn/passing-props-to-a-component#passing-jsx-as-children) JSX를 전달할 수 있습니다.
+
+
```js
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
@@ -931,6 +978,7 @@ export function TasksProvider({ children }) {
```
**This removes all the complexity and wiring from your `TaskApp` component:**
+**이렇게 하면 `TaskBoard` 컴포넌트에서 복잡하게 얽혀있던 부분을 깔끔하게 정리할 수 있습니다.**
@@ -1122,6 +1170,7 @@ ul, li { margin: 0; padding: 0; }
You can also export functions that _use_ the context from `TasksContext.js`:
+`TasksContext.js`에서 context를 사용하기 위한 _use_ 함수들도 내보낼 수 있습니다.
```js
export function useTasks() {
@@ -1134,6 +1183,7 @@ export function useTasksDispatch() {
```
When a component needs to read context, it can do it through these functions:
+그렇게 만들어진 함수를 사용하여 컴포넌트에서 context를 읽을 수 있습니다.
```js
const tasks = useTasks();
@@ -1141,6 +1191,7 @@ const dispatch = useTasksDispatch();
```
This doesn't change the behavior in any way, but it lets you later split these contexts further or add some logic to these functions. **Now all of the context and reducer wiring is in `TasksContext.js`. This keeps the components clean and uncluttered, focused on what they display rather than where they get the data:**
+이렇게 하면 동작이 바뀌는 건 아니지만, 다음에 context를 더 분리하거나 함수들에 로직을 추가하기 쉬워집니다. **이제 모든 context와 reducer는 `TasksContext.js`에 있습니다. 이렇게 컴포넌트들이 데이터를 어디서 가져오는지가 아닌 무엇을 보여줄 것인지에 집중할 수 있도록 깨끗하게 정리할 수 있습니다.**
@@ -1341,14 +1392,17 @@ ul, li { margin: 0; padding: 0; }
You can think of `TasksProvider` as a part of the screen that knows how to deal with tasks, `useTasks` as a way to read them, and `useTasksDispatch` as a way to update them from any component below in the tree.
+`TasksProvider`는 tasks를 화면의 한 부분으로 tasks를 관리합니다. `useTasks`로 tasks를 읽을 수 있고, `useTasksDispatch`로 컴포넌트들에서 tasks를 업데이트 할 수 있습니다.
Functions like `useTasks` and `useTasksDispatch` are called *[Custom Hooks.](/learn/reusing-logic-with-custom-hooks)* Your function is considered a custom Hook if its name starts with `use`. This lets you use other Hooks, like `useContext`, inside it.
+`useTasks`와 `useTasksDispatch` 같은 함수들을 *[사용자 정의 훅](/learn/reusing-logic-with-custom-hooks)* 이라고 합니다. 이름이 `use`로 시작되는 함수들은 사용자 정의 훅입니다. 사용자 정의 훅 안에서도 `useContext` 등 다른 Hook을 사용할 수 있습니다.
As your app grows, you may have many context-reducer pairs like this. This is a powerful way to scale your app and [lift state up](/learn/sharing-state-between-components) without too much work whenever you want to access the data deep in the tree.
+앱이 커질수록 context-reducer 조합이 더 많아질 겁니다. 앱을 확장하고 큰 노력 없이 트리 아래에서 데이터에 접근할 수 있도록 [state를 끌어올리기 위한](/learn/sharing-state-between-components) 강력한 방법이기 때문입니다.
@@ -1362,5 +1416,17 @@ As your app grows, you may have many context-reducer pairs like this. This is a
- You can also export custom Hooks like `useTasks` and `useTasksDispatch` to read it.
- You can have many context-reducer pairs like this in your app.
+
+- Reducer와 context를 결합해서 컴포넌트가 상위 state를 읽고 수정할 수 있도록 할 수 있습니다.
+- State와 dispatch 함수를 자식 컴포넌트들에 제공하는 방법
+ 1. 두 개의 context를 만듭니다. (각각 state와 dispatch 함수를 위한 것).
+ 2. 하위 컴포넌트들에서 필요한 context를 사용합니다.
+ 3. 하위 컴포넌트들에서 필요한 context를 사용합니다.
+- 더 나아가 하나의 파일로 합쳐서 컴포넌트들을 정리할 수 있습니다.
+ - Context를 제공하는 `TasksProvider` 같은 컴포넌트를 내보낼 수 있습니다.
+ - 바로 사용할 수 있도록 `useTasks`와 `useTasksDispatch` 같은 사용자 Hook을 내보낼 수 있습니다.
+- context-reducer 조합을 앱에 여러 개 만들 수 있습니다.
+
+
diff --git a/src/content/learn/writing-markup-with-jsx.md b/src/content/learn/writing-markup-with-jsx.md
index 335f192af7d..621b5a8add4 100644
--- a/src/content/learn/writing-markup-with-jsx.md
+++ b/src/content/learn/writing-markup-with-jsx.md
@@ -1,10 +1,12 @@
---
title: Writing Markup with JSX
+translatedTitle: JSX로 마크업 작성하기
---
*JSX* is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file. Although there are other ways to write components, most React developers prefer the conciseness of JSX, and most codebases use it.
+*JSX*는 JavaScript를 확장한 문법으로, JavaScript 파일 안에 HTML과 유사한 마크업을 작성할 수 있도록 해줍니다. 컴포넌트를 작성하는 다른 방법도 있지만, 대부분의 React개발자는 JSX의 간결함을 선호하며 대부분의 코드베이스에서 JSX를 사용합니다.
@@ -14,11 +16,18 @@ title: Writing Markup with JSX
* How JSX is different from HTML
* How to display information with JSX
+
+- React가 마크업과 렌더링 로직을 같이 사용하는 이유
+- JSX와 HTML의 차이점
+- JSX로 정보를 보여주는 방법
+
+
-## JSX: Putting markup into JavaScript {/*jsx-putting-markup-into-javascript*/}
+## JSX: Putting markup into JavaScriptJSX: JavaScript에 마크업 넣기 {/*jsx-putting-markup-into-javascript*/}
The Web has been built on HTML, CSS, and JavaScript. For many years, web developers kept content in HTML, design in CSS, and logic in JavaScript—often in separate files! Content was marked up inside HTML while the page's logic lived separately in JavaScript:
+웹은 HTML, CSS, JavaScript를 기반으로 만들어져왔습니다. 수년 동안 웹 개발자들은 HTML로 컨텐츠를, CSS로 디자인을, 로직은 JavaScript로 작성해왔습니다. 보통은 각각 분리된 파일로 관리를 합니다! 페이지의 로직이 JavaScript안에서 분리되어 동작하는 동안, HTML 안에서는 컨텐츠가 마크업 되었습니다.
@@ -37,6 +46,7 @@ JavaScript
But as the Web became more interactive, logic increasingly determined content. JavaScript was in charge of the HTML! This is why **in React, rendering logic and markup live together in the same place—components.**
+하지만 웹이 더욱 인터랙티브해지면서 로직이 컨텐츠를 결정하는 경우가 많아졌습니다. 그래서 JavaScript가 HTML을 담당하게 되었죠! **이것이 바로 React에서 렌더링 로직과 마크업이 같은 위치의 컴포넌트에 함께 있는 이유입니다.**
@@ -55,18 +65,22 @@ But as the Web became more interactive, logic increasingly determined content. J
Keeping a button's rendering logic and markup together ensures that they stay in sync with each other on every edit. Conversely, details that are unrelated, such as the button's markup and a sidebar's markup, are isolated from each other, making it safer to change either of them on their own.
+버튼의 렌더링 로직과 마크업이 함께 존재한다면 모든 편집에서 서로 동기화 상태를 유지할 수 있습니다. 반대로 버튼의 마크업과 사이드바의 마크업처럼 서로 관련이 없는 항목들은 서로 분리되어 있으므로 각각 개별적으로 변경하는 것이 더 안전합니다.
Each React component is a JavaScript function that may contain some markup that React renders into the browser. React components use a syntax extension called JSX to represent that markup. JSX looks a lot like HTML, but it is a bit stricter and can display dynamic information. The best way to understand this is to convert some HTML markup to JSX markup.
+각 React 컴포넌트는 React가 브라우저에 마크업을 렌더링할 수 있는 JavaScript 함수입니다. React 컴포넌트는 JSX라는 구문 확장자를 사용하여 해당되는 마크업을 표현합니다. JSX는 HTML과 비슷해보이지만 조금 더 엄격하며 동적으로 정보를 표시할 수 있습니다. JSX를 이해하는 가장 좋은 방법은 일부의 HTML마크업을 JSX마크업으로 변환해보는 것입니다.
JSX and React are two separate things. They're often used together, but you *can* [use them independently](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#whats-a-jsx-transform) of each other. JSX is a syntax extension, while React is a JavaScript library.
+JSX와 React는 서로 다른 별개의 개념입니다. 종종 함께 사용되기도 하지만 [독립적으로](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#whats-a-jsx-transform) 사용할 수도 있습니다. JSX는 구문 확장이고, React는 자바스크립트 라이브러리입니다.
-## Converting HTML to JSX {/*converting-html-to-jsx*/}
+## Converting HTML to JSXHTML을 JSX로 변환하기 {/*converting-html-to-jsx*/}
Suppose that you have some (perfectly valid) HTML:
+다음과 같은 (완벽하게 유효한) HTML이 있다고 가정해봅시다:
```html
Hedy Lamarr's Todos
@@ -83,6 +97,7 @@ Suppose that you have some (perfectly valid) HTML:
```
And you want to put it into your component:
+이제 이것을 컴포넌트로 만들어볼 것입니다:
```js
export default function TodoList() {
@@ -93,7 +108,7 @@ export default function TodoList() {
```
If you copy and paste it as is, it will not work:
-
+이 코드를 그대로 복사하여 붙여넣는다면 동작하지 않을 것입니다:
@@ -123,20 +138,24 @@ img { height: 90px }
This is because JSX is stricter and has a few more rules than HTML! If you read the error messages above, they'll guide you to fix the markup, or you can follow the guide below.
+왜냐하면 JSX는 HTML보다 더 엄격하며 몇 가지 규칙이 더 있기 때문입니다! 위의 오류메세지를 읽으면 마크업을 수정하도록 안내하거나 아래의 가이드를 따를 수 있습니다.
Most of the times, React's on-screen error messages will help you find where the problem is. Give them a read if you get stuck!
+대부분의 경우 React의 화면 오류 메세지는 문제가 있는 곳을 찾는 데 도움이 됩니다. 막혔을 때 읽어주세요!
-## The Rules of JSX {/*the-rules-of-jsx*/}
+## The Rules of JSXJSX 규칙 {/*the-rules-of-jsx*/}
-### 1. Return a single root element {/*1-return-a-single-root-element*/}
+### 1. Return a single root element단일 루트 엘리먼트를 반환하세요 {/*1-return-a-single-root-element*/}
To return multiple elements from a component, **wrap them with a single parent tag.**
+컴포넌트에서 여러 엘리먼트를 반환하려면, **하나의 부모 태그로 감싸주세요.**
For example, you can use a ``:
+
예를 들면 ``를 사용할 수 있습니다:
```js {1,11}
@@ -154,6 +173,7 @@ For example, you can use a `
`:
If you don't want to add an extra `
` to your markup, you can write `<>` and `>` instead:
+
마크업에 ``를 추가하고 싶지 않다면 `<>`와 `>`를 사용하면 됩니다:
```js {1,11}
<>
@@ -169,21 +189,25 @@ If you don't want to add an extra `
` to your markup, you can write `<>` and
>
```
-This empty tag is called a *[Fragment.](/reference/react/Fragment)* Fragments let you group things without leaving any trace in the browser HTML tree.
+This empty tag is called a [*Fragment*](/reference/react/Fragment). Fragments let you group things without leaving any trace in the browser HTML tree.
+
이런 빈 태그를 [*Fragment*](/reference/react/Fragment)라고 합니다. Fragment는 브라우저상의 HTML 트리 구조에서 흔적을 남기지 않고 그룹화해줍니다.
-#### Why do multiple JSX tags need to be wrapped? {/*why-do-multiple-jsx-tags-need-to-be-wrapped*/}
+#### Why do multiple JSX tags need to be wrapped?왜 여러 JSX태그를 하나로 감싸줘야 할까요? {/*why-do-multiple-jsx-tags-need-to-be-wrapped*/}
JSX looks like HTML, but under the hood it is transformed into plain JavaScript objects. You can't return two objects from a function without wrapping them into an array. This explains why you also can't return two JSX tags without wrapping them into another tag or a Fragment.
+JSX는 HTML처럼 보이지만 내부적으로는 JavaScript 객체로 변환됩니다. 하나의 배열로 감싸지 않은 하나의 함수에서는 두 개의 객체를 반환할 수 없습니다. 따라서 또 다른 태그나 Fragment로 감싸지 않으면 두 개의 JSX태그를 반환할 수 없습니다.
-### 2. Close all the tags {/*2-close-all-the-tags*/}
+### 2. Close all the tags
모든 태그를 닫으세요 {/*2-close-all-the-tags*/}
JSX requires tags to be explicitly closed: self-closing tags like `
![]()
` must become `
![]()
`, and wrapping tags like `
oranges` must be written as `oranges`.
+
JSX에서는 태그를 명시적으로 닫아야 합니다. `
`태그처럼 자체적으로 닫는 태그도 반드시 `
`로 작성해야하며, ``oranges와 같은 래핑 태그 역시 `oranges`형태로 작성해야 합니다.
This is how Hedy Lamarr's image and list items look closed:
+
다음과 같이 Hedy Lamarr의 이미지와 리스트의 항목들을 닫아줍니다:
```js {2-6,8-10}
<>
@@ -200,11 +224,13 @@ This is how Hedy Lamarr's image and list items look closed:
>
```
-### 3. camelCase
all most of the things! {/*3-camelcase-salls-most-of-the-things*/}
+### 3. camelCase
all most of the things!
거의 대부분이 캐멀 케이스입니다! {/*3-camelcase-salls-most-of-the-things*/}
JSX turns into JavaScript and attributes written in JSX become keys of JavaScript objects. In your own components, you will often want to read those attributes into variables. But JavaScript has limitations on variable names. For example, their names can't contain dashes or be reserved words like `class`.
+
JSX는 JavaScript로 바뀌고 JSX로 작성된 어트리뷰트는 JavaScript 객체의 키가 됩니다. 종종 컴포넌트 안에서 어트리뷰트를 변수로 읽고 싶은 경우가 있을 것입니다. 하지만 JavaScript에는 변수명에 제한이 있습니다. 예를 들어 변수명에는 대시를 포함하거나 `class`처럼 예약어를 사용할 수 없습니다.
This is why, in React, many HTML and SVG attributes are written in camelCase. For example, instead of `stroke-width` you use `strokeWidth`. Since `class` is a reserved word, in React you write `className` instead, named after the [corresponding DOM property](https://developer.mozilla.org/en-US/docs/Web/API/Element/className):
+
이것이 React에서 많은 HTML과 SVG 어트리뷰트가 캐멀 케이스로 작성되는 이유입니다. 예를 들어 `stroke-width` 대신 `strokeWidth`을 사용합니다. `class`는 예약어이므로, React에서는 대신 해당 [DOM 속성](https://developer.mozilla.org/en-US/docs/Web/API/Element/className)의 이름을 따서 `className`을 씁니다:
```js {4}
![]()
이런 [모든 어트리뷰트는 React DOM엘리먼트에서](/reference/react-dom/components/common) 찾을 수 있습니다. 틀려도 걱정하지 마세요. React는 [브라우저 콘솔](https://developer.mozilla.org/docs/Tools/Browser_Console)에서 수정 가능한 부분을 메세지로 알려줍니다.
For historical reasons, [`aria-*`](https://developer.mozilla.org/docs/Web/Accessibility/ARIA) and [`data-*`](https://developer.mozilla.org/docs/Learn/HTML/Howto/Use_data_attributes) attributes are written as in HTML with dashes.
+역사적인 이유로 [`aria-*`](https://developer.mozilla.org/docs/Web/Accessibility/ARIA) 과 [`data-*`](https://developer.mozilla.org/docs/Learn/HTML/Howto/Use_data_attributes)의 어트리뷰트는 HTML에서와 동일하게 대시를 사용하여 작성합니다.
+
+
+#### 역사적인 이유? -@정재남 {/*역사적인-이유--정재남*/}
+
+https://stackoverflow.com/a/52489695
+
+Speculation: Perhaps this has something to do with a change which took place between React versions 15 and 16. The blog post ["DOM Attributes in React 16"](https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html) explains that custom attributes are now allowed in React 16, which were previously stripped out. It describes some concerns that an internal whitelist of attributes had become a maintenance burden, which needed to be simplified. Now arbitrary attributes can be included in JSX. I don't know how this works internally, but I suppose the `aria-*` attributes play some part in the story of the internal whitelist. For instance, WAI-ARIA 1.1 recently introduced several new `aria-*` attributes, and the WAI [Personalization Semantics Content Module](https://www.w3.org/TR/personalization-semantics-content-1.0/) working draft introduces a lot of `aui-*` attributes. Both of these would have needed to be whitelisted.
+추측: 아마도 이것은 React 버전 15와 16 사이에 일어난 변경과 관련이 있을 것입니다. 블로그 포스트 ["React 16의 DOM 어트리뷰트"](https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html) 에서는 이전에는 제거되었던 사용자 정의 어트리뷰트가 이제 React 16에서 허용된다고 설명합니다. 이 글에서는 어트리뷰트의 내부 화이트리스트가 유지보수 부담이 되어 단순화할 필요가 있다는 우려를 표합니다. 이제 임의의 어트리뷰트를 JSX에 포함할 수 있습니다. 내부적으로 어떻게 작동하는지는 모르겠지만, 내부 화이트리스트의 이야기에서 `aria-*` 속성이 어느 정도 역할을 하는 것으로 생각됩니다. 예를 들어, WAI-ARIA 1.1은 최근 몇 가지 새로운 `aria-*` 어트리뷰트를 도입했고, WAI [개인화 시맨틱 콘텐츠 모듈](https://www.w3.org/TR/personalization-semantics-content-1.0/) 작업 초안에는 `aui-*` 어트리뷰트가 많이 도입되었습니다. 이 두 가지 모두 화이트리스트에 추가해야 했습니다.
+
+
-### Pro-tip: Use a JSX Converter {/*pro-tip-use-a-jsx-converter*/}
+### Pro-tip: Use a JSX Converter
전문가 팁: JSX 변환기 사용 {/*pro-tip-use-a-jsx-converter*/}
Converting all these attributes in existing markup can be tedious! We recommend using a [converter](https://transform.tools/html-to-jsx) to translate your existing HTML and SVG to JSX. Converters are very useful in practice, but it's still worth understanding what is going on so that you can comfortably write JSX on your own.
+
+기존 마크업에서 모든 어트리뷰트를 변환하는 것은 지루할 수 있습니다. [변환기](https://transform.tools/html-to-jsx)를 사용하여 기존 HTML과 SVG를 JSX로 변환하는 것을 추천합니다. 변환기는 매우 유용하지만 그래도 JSX를 편안하게 작성할 수 있도록 어트리뷰트를 어떻게 쓰는지 이해하는 것도 중요합니다.
+
Here is your final result:
+
최종 결과는 다음과 같습니다:
@@ -257,22 +299,30 @@ img { height: 90px }
-
Now you know why JSX exists and how to use it in components:
* React components group rendering logic together with markup because they are related.
* JSX is similar to HTML, with a few differences. You can use a [converter](https://transform.tools/html-to-jsx) if you need to.
* Error messages will often point you in the right direction to fixing your markup.
+
+지금까지 JSX가 존재하는 이유와 컴포넌트에서 JSX를 쓰는 방법에 대해 알아보았습니다.
+* React 컴포넌트는 서로 관련이 있는 마크업과 렌더링 로직을 함께 그룹화합니다.
+* JSX는 HTML과 비슷하지만 몇 가지 차이점이 있습니다. 필요한 경우 [변환기](https://transform.tools/html-to-jsx)를 사용할 수 있습니다.
+* 오류 메세지는 종종 마크업을 수정할 수 있도록 올바른 방향을 알려줍니다.
+
+
+
-#### Convert some HTML to JSX {/*convert-some-html-to-jsx*/}
+#### Convert some HTML to JSXHTML을 JSX로 변환해 보세요. {/*convert-some-html-to-jsx*/}
This HTML was pasted into a component, but it's not valid JSX. Fix it:
+컴포넌트에 HTML을 붙여넣었지만 올바른 JSX가 아닙니다. 수정해보세요:
@@ -309,6 +359,7 @@ export default function Bio() {
Whether to do it by hand or using the converter is up to you!
+직접 수정할지 변환기를 사용할지는 여러분에게 달려있습니다!
diff --git a/src/content/learn/you-might-not-need-an-effect.md b/src/content/learn/you-might-not-need-an-effect.md
index 05f053be264..fc0e668505f 100644
--- a/src/content/learn/you-might-not-need-an-effect.md
+++ b/src/content/learn/you-might-not-need-an-effect.md
@@ -1,10 +1,12 @@
---
-title: 'You Might Not Need an Effect'
+title: You Might Not Need an Effect
+translatedTitle: Effect가 필요하지 않을 수도 있습니다
---
Effects are an escape hatch from the React paradigm. They let you "step outside" of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM. If there is no external system involved (for example, if you want to update a component's state when some props or state change), you shouldn't need an Effect. Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone.
+Effect는 React 패러다임에서 벗어날 수 있는 탈출구입니다. Effect를 사용하면 React의 "외부로 나가서" 컴포넌트를 React가 아닌 위젯, 네트워크 또는 브라우저 DOM과 같은 외부 시스템과 동기화할 수 있습니다. 외부 시스템이 관여하지 않는 경우(예: 일부 props나 state가 변경될 때 컴포넌트의 state를 업데이트하려는 경우)에는 Effect가 필요하지 않습니다. 불필요한 Effect를 제거하면 코드를 더 쉽게 따라갈 수 있고, 실행 속도가 빨라지며, 오류 발생 가능성이 줄어듭니다.
@@ -17,29 +19,45 @@ Effects are an escape hatch from the React paradigm. They let you "step outside"
* Which logic should be moved to event handlers
* How to notify parent components about changes
+
+- 컴포넌트에서 불필요한 Effect를 제거하는 이유와 방법
+- Effect 없이 값비싼 계산을 캐시하는 방법
+- Effect 없이 컴포넌트 state를 리셋하고 조정하는 방법
+- 이벤트 핸들러 간에 로직을 공유하는 방법
+- 이벤트 핸들러로 이동되어야 하는 로직
+- 부모 컴포넌트에 변경 사항을 알리는 방법
+
-## How to remove unnecessary Effects {/*how-to-remove-unnecessary-effects*/}
+## How to remove unnecessary Effects불필요한 Effect를 제거하는 방법 {/*how-to-remove-unnecessary-effects*/}
There are two common cases in which you don't need Effects:
+Effect가 필요하지 않은 흔한 경우는 두 가지가 있습니다:
* **You don't need Effects to transform data for rendering.** For example, let's say you want to filter a list before displaying it. You might feel tempted to write an Effect that updates a state variable when the list changes. However, this is inefficient. When you update the state, React will first call your component functions to calculate what should be on the screen. Then React will ["commit"](/learn/render-and-commit) these changes to the DOM, updating the screen. Then React will run your Effects. If your Effect *also* immediately updates the state, this restarts the whole process from scratch! To avoid the unnecessary render passes, transform all the data at the top level of your components. That code will automatically re-run whenever your props or state change.
+**렌더링을 위해 데이터를 변환하는 경우 Effect는 필요하지 않습니다.** 예를 들어 목록을 표시하기 전에 필터링하고 싶다고 가정해 봅시다. 목록이 변경될 때 state 변수를 업데이트하는 Effect를 작성하고 싶을 수 있습니다. 하지만 이는 비효율적입니다. 컴포넌트의 state를 업데이트할 때 React는 먼저 컴포넌트 함수를 호출해 화면에 표시될 내용을 계산합니다. 다음으로 이러한 변경 사항을 DOM에 ["commit"](/learn/render-and-commit)하여 화면을 업데이트하고, 그 후에 Effect를 실행합니다. 만약 Effect "역시" state를 즉시 업데이트한다면, 이로 인해 전체 프로세스가 처음부터 다시 시작될 것입니다! 불필요한 렌더링을 피하려면 모든 데이터 변환을 컴포넌트의 최상위 레벨에서 하세요. 그러면 props나 state가 변경될 때마다 해당 코드가 자동으로 다시 실행될 것입니다.
+
* **You don't need Effects to handle user events.** For example, let's say you want to send an `/api/buy` POST request and show a notification when the user buys a product. In the Buy button click event handler, you know exactly what happened. By the time an Effect runs, you don't know *what* the user did (for example, which button was clicked). This is why you'll usually handle user events in the corresponding event handlers.
+**사용자 이벤트를 처리하는 데에 Effect는 필요하지 않습니다.** 예를 들어 사용자가 제품을 구매할 때 `/api/buy` POST 요청을 전송하고 알림을 표시하고 싶다고 합시다. 구매 버튼 클릭 이벤트 핸들러에서는 정확히 어떤 일이 일어났는지 알 수 있습니다. 반면 Effect는 사용자가 무엇을 했는지(예: 어떤 버튼을 클릭했는지)를 알 수 없습니다. 그렇기 때문에 일반적으로 사용자 이벤트를 해당 이벤트 핸들러에서 처리합니다.
You *do* need Effects to [synchronize](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events) with external systems. For example, you can write an Effect that keeps a jQuery widget synchronized with the React state. You can also fetch data with Effects: for example, you can synchronize the search results with the current search query. Keep in mind that modern [frameworks](/learn/start-a-new-react-project#production-grade-react-frameworks) provide more efficient built-in data fetching mechanisms than writing Effects directly in your components.
+한편 외부 시스템과 [동기화](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events)하려면 Effect가 *필요*합니다. 예를 들어 jQuery 위젯을 React state와 동기화하는 Effect를 작성할 수 있습니다. 또한 검색 결과를 현재의 검색 쿼리와 동기화하기 위해 데이터 요청을 Effect로 처리할 수 있습니다. 최신 [프레임워크](/learn/start-a-new-react-project#building-with-a-full-featured-framework)는 컴포넌트에 직접 Effects를 작성하는 것보다 더 효율적인 내장 데이터 페칭 메커니즘을 제공한다는 점을 명심하세요.
To help you gain the right intuition, let's look at some common concrete examples!
+올바른 직관을 얻기 위해 몇 가지 일반적인 구체적인 예를 살펴봅시다!
-### Updating state based on props or state {/*updating-state-based-on-props-or-state*/}
+### Updating state based on props or stateprops 또는 state에 따라 state 업데이트하기 {/*updating-state-based-on-props-or-state*/}
Suppose you have a component with two state variables: `firstName` and `lastName`. You want to calculate a `fullName` from them by concatenating them. Moreover, you'd like `fullName` to update whenever `firstName` or `lastName` change. Your first instinct might be to add a `fullName` state variable and update it in an Effect:
+`firstName`과 `lastName`이라는 두 개의 state 변수가 있는 컴포넌트를 가정해 봅시다. 이 두 변수를 연결하여 `fullName`을 계산하고 싶습니다. 또한, `firstName` 또는 `lastName`이 변경될 때마다 `fullName`이 업데이트되기를 원합니다. 가장 먼저 생각나는 것은 `fullName` state 변수를 추가하고 Effect에서 업데이트하는 것일 수 있습니다:
-```js {5-9}
+```js {5-10}
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 🔴 Avoid: redundant state and unnecessary Effect
+ // 🔴 이러지 마세요: 중복 state 및 불필요한 Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
@@ -49,28 +67,33 @@ function Form() {
```
This is more complicated than necessary. It is inefficient too: it does an entire render pass with a stale value for `fullName`, then immediately re-renders with the updated value. Remove the state variable and the Effect:
+이는 필요 이상으로 복잡하고 비효율적입니다: 전체 렌더링 과정에서 `fullName`에 대한 오래된 값을 사용한 다음, 즉시 업데이트된 값으로 다시 렌더링합니다. state 변수와 Effect를 모두 제거하세요:
-```js {4-5}
+```js {4-6}
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Good: calculated during rendering
+ // ✅ 좋습니다: 렌더링 과정 중에 계산
const fullName = firstName + ' ' + lastName;
// ...
}
```
**When something can be calculated from the existing props or state, [don't put it in state.](/learn/choosing-the-state-structure#avoid-redundant-state) Instead, calculate it during rendering.** This makes your code faster (you avoid the extra "cascading" updates), simpler (you remove some code), and less error-prone (you avoid bugs caused by different state variables getting out of sync with each other). If this approach feels new to you, [Thinking in React](/learn/thinking-in-react#step-3-find-the-minimal-but-complete-representation-of-ui-state) explains what should go into state.
+**기존 props나 state에서 계산할 수 있는 것이 있으면 [state에 넣지 마세요.](/learn/choosing-the-state-structure#avoid-redundant-state) 대신 렌더링 중에 계산하세요.** 이렇게 하면 코드가 더 빨라지고(추가적인 "계단식" 업데이트를 피함), 더 간단해지고(일부 코드 제거), 오류가 덜 발생합니다(서로 다른 state 변수가 서로 동기화되지 않아 발생하는 버그를 피함). 이 접근 방식이 생소하게 느껴진다면, [React로 사고하기](/learn/thinking-in-react#step-3-find-the-minimal-but-complete-representation-of-ui-state)에서 state에 들어가야할 내용이 무엇인지 확인하세요.
-### Caching expensive calculations {/*caching-expensive-calculations*/}
+### Caching expensive calculations고비용 계산 캐싱하기 {/*caching-expensive-calculations*/}
This component computes `visibleTodos` by taking the `todos` it receives by props and filtering them according to the `filter` prop. You might feel tempted to store the result in state and update it from an Effect:
+아래 컴포넌트는 props로 받은 `todos`를 `filter` prop에 따라 필터링하여 `visibleTodos`를 계산합니다. 이 결과를 state변수에 저장하고 Effect에서 업데이트하고 싶을 수도 있을 것입니다:
-```js {4-8}
+```js {4-9}
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// 🔴 Avoid: redundant state and unnecessary Effect
+ // 🔴 이러지 마세요: 중복 state 및 불필요한 Effect
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
@@ -81,19 +104,23 @@ function TodoList({ todos, filter }) {
```
Like in the earlier example, this is both unnecessary and inefficient. First, remove the state and the Effect:
+앞의 예시에서와 마찬가지로 이것은 불필요하고 비효율적입니다. state와 Effect를 제거합시다:
-```js {3-4}
+```js {3-5}
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// ✅ This is fine if getFilteredTodos() is not slow.
+ // ✅ getFilteredTodos()가 느리지 않다면 괜찮습니다.
const visibleTodos = getFilteredTodos(todos, filter);
// ...
}
```
Usually, this code is fine! But maybe `getFilteredTodos()` is slow or you have a lot of `todos`. In that case you don't want to recalculate `getFilteredTodos()` if some unrelated state variable like `newTodo` has changed.
+일반적으로 위 코드는 괜찮습니다! 하지만 `getFilteredTodos()`가 느리거나 `todos`가 많을 경우, `newTodo`와 같이 관련 없는 state 변수가 변경되더라도 `getFilteredTodos()`를 다시 계산하고 싶지 않을 수 있습니다.
You can cache (or ["memoize"](https://en.wikipedia.org/wiki/Memoization)) an expensive calculation by wrapping it in a [`useMemo`](/reference/react/useMemo) Hook:
+이럴 땐 값비싼 계산을 [`useMemo`](/reference/react/useMemo) 훅으로 감싸서 캐시(또는 ["메모화 (memoize)"](https://en.wikipedia.org/wiki/Memoization))할 수 있습니다:
```js {5-8}
import { useMemo, useState } from 'react';
@@ -102,6 +129,7 @@ function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const visibleTodos = useMemo(() => {
// ✅ Does not re-run unless todos or filter change
+ // ✅ todos나 filter가 변하지 않는 한 재실행되지 않음
return getFilteredTodos(todos, filter);
}, [todos, filter]);
// ...
@@ -109,27 +137,32 @@ function TodoList({ todos, filter }) {
```
Or, written as a single line:
+또는 한 줄로 작성할 수도 있습니다:
-```js {5-6}
+```js {5-7}
import { useMemo, useState } from 'react';
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// ✅ Does not re-run getFilteredTodos() unless todos or filter change
+ // ✅ todos나 filter가 변하지 않는 한 getFilteredTodos()가 재실행되지 않음
const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
// ...
}
```
**This tells React that you don't want the inner function to re-run unless either `todos` or `filter` have changed.** React will remember the return value of `getFilteredTodos()` during the initial render. During the next renders, it will check if `todos` or `filter` are different. If they're the same as last time, `useMemo` will return the last result it has stored. But if they are different, React will call the inner function again (and store its result).
+**이렇게 하면 `todos`나 `filter`가 변경되지 않는 한 내부 함수가 다시 실행되지 않기를 원한다는 것을 React에 알립니다.** 그러면 React는 초기 렌더링 중에 `getFilteredTodos()`의 반환값을 기억합니다. 그 다음부터는 렌더링 중에 할 일이나 필터가 다른지 확인합니다. 지난번과 동일하다면 `useMemo`는 마지막으로 저장한 결과를 반환합니다. 같지 않다면, React는 내부 함수를 다시 호출하고 그 결과를 저장합니다.
The function you wrap in [`useMemo`](/reference/react/useMemo) runs during rendering, so this only works for [pure calculations.](/learn/keeping-components-pure)
+[`useMemo`](/reference/react/useMemo)로 래핑하는 함수는 렌더링 중에 실행되므로, [순수 계산](/learn/keeping-components-pure)에만 작동합니다.
-#### How to tell if a calculation is expensive? {/*how-to-tell-if-a-calculation-is-expensive*/}
+#### How to tell if a calculation is expensive?계산이 비싼지는 어떻게 알 수 있나요? {/*how-to-tell-if-a-calculation-is-expensive*/}
In general, unless you're creating or looping over thousands of objects, it's probably not expensive. If you want to get more confidence, you can add a console log to measure the time spent in a piece of code:
+일반적으로 수천 개의 객체를 만들거나 반복하는 경우가 아니라면 비용이 많이 든다고 보지 않을 것입니다. 좀 더 확신을 얻고 싶다면 콘솔 로그를 추가하여 코드에 소요된 시간을 측정할 수 있습니다:
```js {1,3}
console.time('filter array');
@@ -138,6 +171,7 @@ console.timeEnd('filter array');
```
Perform the interaction you're measuring (for example, typing into the input). You will then see logs like `filter array: 0.15ms` in your console. If the overall logged time adds up to a significant amount (say, `1ms` or more), it might make sense to memoize that calculation. As an experiment, you can then wrap the calculation in `useMemo` to verify whether the total logged time has decreased for that interaction or not:
+측정하려는 상호작용을 수행하세요(예: input에 입력). 그러면 `filter array : 0.15ms` 라는 로그가 콘솔에 표시되는 것을 보게될 것 입니다. 기록된 전체 시간이 상당하다면(예: 1ms 이상) 해당 계산은 메모해 두는 것이 좋을 수 있습니다. 실험삼아 해당 계산을 `useMemo`로 감싸서 해당 상호작용에 대해 총 로그된 시간이 감소했는지 여부를 확인할 수 있습니다:
```js
console.time('filter array');
@@ -148,22 +182,27 @@ console.timeEnd('filter array');
```
`useMemo` won't make the *first* render faster. It only helps you skip unnecessary work on updates.
+`useMemo`는 *첫 번째* 렌더링을 더 빠르게 만들지는 않습니다. 업데이트 시 불필요한 작업을 건너뛰는 데에만 도움이 될 뿐입니다.
Keep in mind that your machine is probably faster than your users' so it's a good idea to test the performance with an artificial slowdown. For example, Chrome offers a [CPU Throttling](https://developer.chrome.com/blog/new-in-devtools-61/#throttling) option for this.
+컴퓨터가 사용자보다 빠를 수 있으므로 인위적으로 속도 저하를 일으켜서 성능을 테스트하는 것도 좋은 생각입니다. 예를 들어 Chrome에서는 [CPU 스로틀링](https://developer.chrome.com/blog/new-in-devtools-61/#throttling) 옵션을 제공합니다.
Also note that measuring performance in development will not give you the most accurate results. (For example, when [Strict Mode](/reference/react/StrictMode) is on, you will see each component render twice rather than once.) To get the most accurate timings, build your app for production and test it on a device like your users have.
+또한 개발 중에 성능을 측정하는 것은 정확한 결과를 제공하지는 않는다는 점에 유의하세요. (예를 들어 [Strict Mode](/reference/react/StrictMode)를 켜면, 각 컴포넌트가 한 번이 아닌 두 번씩 렌더링되는 것을 볼 수 있습니다.) 가장 정확한 타이밍을 얻으려면 프로덕션용 앱을 빌드하고 사용자가 사용하는 것과 동일한 기기에서 테스트하세요.
-### Resetting all state when a prop changes {/*resetting-all-state-when-a-prop-changes*/}
+### Resetting all state when a prop changesprop이 변경되면 모든 state 재설정하기 {/*resetting-all-state-when-a-prop-changes*/}
This `ProfilePage` component receives a `userId` prop. The page contains a comment input, and you use a `comment` state variable to hold its value. One day, you notice a problem: when you navigate from one profile to another, the `comment` state does not get reset. As a result, it's easy to accidentally post a comment on a wrong user's profile. To fix the issue, you want to clear out the `comment` state variable whenever the `userId` changes:
+다음 `ProfilePage` 컴포넌트는 `userId` prop을 받습니다. 이 페이지에는 코멘트 input이 포함되어 있으며, `comment` state 변수를 사용하여 그 값을 보관합니다. 어느 날, 한 프로필에서 다른 프로필로 이동할 때 `comment` state가 재설정되지 않는 문제를 발견했습니다. 그 결과 의도치 않게 잘못된 사용자의 프로필에 댓글을 게시하기가 쉬운 상황입니다. 이 문제를 해결하려면 `userId`가 변경될 때마다 `comment` state 변수를 지워줘야 합니다:
-```js {4-7}
+```js {4-8}
export default function ProfilePage({ userId }) {
const [comment, setComment] = useState('');
// 🔴 Avoid: Resetting state on prop change in an Effect
+ // 🔴 이러지 마세요: prop 변경시 Effect에서 state 재설정 수행
useEffect(() => {
setComment('');
}, [userId]);
@@ -172,10 +211,12 @@ export default function ProfilePage({ userId }) {
```
This is inefficient because `ProfilePage` and its children will first render with the stale value, and then render again. It is also complicated because you'd need to do this in *every* component that has some state inside `ProfilePage`. For example, if the comment UI is nested, you'd want to clear out nested comment state too.
+이것은 `ProfilePage`와 그 자식들이 먼저 오래된 값으로 렌더링한 다음 새로운 값으로 다시 렌더링하기 때문에 비효율적입니다. 또한 `ProfilePage` 내부에 어떤 state가 있는 *모든* 컴포넌트에서 이 작업을 수행해야 하므로 복잡합니다. 예를 들어 댓글 UI가 중첩되어 있는 경우 중첩된 하위 댓글 state들도 모두 지워야 할 것입니다.
Instead, you can tell React that each user's profile is conceptually a _different_ profile by giving it an explicit key. Split your component in two and pass a `key` attribute from the outer component to the inner one:
+그 대신 명시적인 키를 전달해 각 사용자의 프로필이 개념적으로 *다른* 프로필이라는 것을 React에 알릴 수 있습니다. 컴포넌트를 둘로 나누고 바깥쪽 컴포넌트에서 안쪽 컴포넌트로 `key` 속성을 전달하세요:
-```js {5,11-12}
+```js {5,11-13}
export default function ProfilePage({ userId }) {
return (
일반적으로 React는 같은 컴포넌트가 같은 위치에서 렌더링될 때 state를 유지합니다. **`userId`를 `key`로 `Profile` 컴포넌트에 전달하는 것은 곧, `userId`가 다른 두 `Profile` 컴포넌트를 state를 공유하지 않는 별개의 컴포넌트들로 취급하도록 React에게 요청하는 것입니다.** React는 (`userId`로 설정한) key가 변경될 때마다 DOM을 다시 생성하고 state를 재설정하며, `Profile` 컴포넌트 및 모든 자식들의 [state를 재설정](/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key)할 것입니다. 그 결과 `comment` 필드는 프로필들을 탐색할 때마다 자동으로 지워집니다.
Note that in this example, only the outer `ProfilePage` component is exported and visible to other files in the project. Components rendering `ProfilePage` don't need to pass the key to it: they pass `userId` as a regular prop. The fact `ProfilePage` passes it as a `key` to the inner `Profile` component is an implementation detail.
+위 예제에서는 외부의 `ProfilePage` 컴포넌트만 export하였으므로 프로젝트의 다른 파일에서는 오직 `ProfilePage` 컴포넌트에만 접근 가능한 상태입니다. `ProfilePage`를 렌더링하는 컴포넌트는 key를 전달할 필요 없이 일반적인 prop으로 `userId`만 전달하고 있습니다. `ProfilePage`가 내부의 `Profile` 컴포넌트에 `key`로 전달한다는 사실은 내부에서만 알고 있는 구현 세부 사항입니다.
-### Adjusting some state when a prop changes {/*adjusting-some-state-when-a-prop-changes*/}
+### Adjusting some state when a prop changesprops가 변경될 때 일부 state 조정하기 {/*adjusting-some-state-when-a-prop-changes*/}
Sometimes, you might want to reset or adjust a part of the state on a prop change, but not all of it.
+때론 prop이 변경될 때 state의 전체가 아닌 일부만 재설정하거나 조정하고 싶을 수 있습니다.
This `List` component receives a list of `items` as a prop, and maintains the selected item in the `selection` state variable. You want to reset the `selection` to `null` whenever the `items` prop receives a different array:
+다음의 `List` 컴포넌트는 `items` 목록을 prop으로 받고, `selection` state 변수에 선택된 항목을 유지합니다. `items` prop이 다른 배열을 받을 때마다 `selection`을 `null`로 재설정하고 싶습니다:
-```js {5-8}
+```js {5-10}
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
// 🔴 Avoid: Adjusting state on prop change in an Effect
+ // 🔴 이러지 마세요: prop 변경시 Effect에서 state 조정
useEffect(() => {
setSelection(null);
}, [items]);
@@ -216,15 +263,18 @@ function List({ items }) {
```
This, too, is not ideal. Every time the `items` change, the `List` and its child components will render with a stale `selection` value at first. Then React will update the DOM and run the Effects. Finally, the `setSelection(null)` call will cause another re-render of the `List` and its child components, restarting this whole process again.
+이것 역시 이상적이지 않습니다. items가 `변경될 때마다` List`와 그 하위 컴포넌트는 처음에는 오래된` selection`값으로 렌더링됩니다. 그런 다음 React는 DOM을 업데이트하고 Effects를 실행합니다. 마지막으로`setSelection(null)`호출은`List와 그 자식 컴포넌트를 다시 렌더링하여 이 전체 과정을 재시작하게 됩니다.
Start by deleting the Effect. Instead, adjust the state directly during rendering:
+Effect를 삭제하는 것으로 시작하세요. 대신 렌더링 중에 직접 state를 조정합니다:
-```js {5-11}
+```js {5-12}
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
// Better: Adjust the state while rendering
+ // 더 나음: 렌더링 중에 state 조정
const [prevItems, setPrevItems] = useState(items);
if (items !== prevItems) {
setPrevItems(items);
@@ -235,30 +285,36 @@ function List({ items }) {
```
[Storing information from previous renders](/reference/react/useState#storing-information-from-previous-renders) like this can be hard to understand, but it’s better than updating the same state in an Effect. In the above example, `setSelection` is called directly during a render. React will re-render the `List` *immediately* after it exits with a `return` statement. React has not rendered the `List` children or updated the DOM yet, so this lets the `List` children skip rendering the stale `selection` value.
+이렇게 [이전 렌더링의 정보를 저장하는 것](/reference/react/useState#storing-information-from-previous-renders)은 이해하기 어려울 수 있지만, Effect에서 동일한 state를 업데이트하는 것보다는 낫습니다. 위 예시에서는 렌더링 도중 `setSelection`이 직접 호출됩니다. React는 `return`문과 함께 종료된 _직후에_ `List`를 다시 렌더링합니다. 이 시점에서 React는 아직 `List`의 자식들을 렌더링하거나 DOM을 업데이트하지 않았기 때문에, `List`의 자식들은 기존의 `selection` 값에 대한 렌더링을 건너뛰게 됩니다.
When you update a component during rendering, React throws away the returned JSX and immediately retries rendering. To avoid very slow cascading retries, React only lets you update the *same* component's state during a render. If you update another component's state during a render, you'll see an error. A condition like `items !== prevItems` is necessary to avoid loops. You may adjust state like this, but any other side effects (like changing the DOM or setting timeouts) should stay in event handlers or Effects to [keep components pure.](/learn/keeping-components-pure)
+렌더링 도중 컴포넌트를 업데이트하면, React는 반환된 JSX를 버리고 즉시 렌더링을 다시 시도합니다. React는 계단식으로 전파되는 매우 느린 재시도를 피하기 위해, 렌더링 중에 *동일한* 컴포넌트의 state만 업데이트할 수 있도록 허용합니다. 렌더링 도중 다른 컴포넌트의 state를 업데이트하면 오류가 발생합니다. 동일 컴포넌트가 무한으로 재렌더링을 반복 시도하는 상황을 피하기 위해 `items !== prevItems`와 같은 조건이 필요한 것입니다. 이런 식으로 state를 조정할 수 있긴 하지만, 다른 side effect(DOM 변경이나 timeout 설정 등)은 이벤트 핸들러나 Effect에서만 처리함으로써 [컴포넌트의 순수성을 유지](/learn/keeping-components-pure)해야 합니다.
**Although this pattern is more efficient than an Effect, most components shouldn't need it either.** No matter how you do it, adjusting state based on props or other state makes your data flow more difficult to understand and debug. Always check whether you can [reset all state with a key](#resetting-all-state-when-a-prop-changes) or [calculate everything during rendering](#updating-state-based-on-props-or-state) instead. For example, instead of storing (and resetting) the selected *item*, you can store the selected *item ID:*
+**이 패턴은 Effect보다 효율적이지만, 대부분의 컴포넌트에는 필요하지 않습니다.** 어떻게 하든 props나 다른 state들을 바탕으로 state를 조정하면 데이터 흐름을 이해하고 디버깅하기 어려워질 것입니다. 항상 [key로 모든 state를 재설정](#resetting-all-state-when-a-prop-changes)하거나 [렌더링 중에 모두 계산](#updating-state-based-on-props-or-state)할 수 있는지를 확인하세요. 예를 들어 선택한 *item*을 저장(및 재설정)하는 대신, 선택한 item의 *ID*를 저장할 수 있습니다:
-```js {3-5}
+```js {3-6}
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selectedId, setSelectedId] = useState(null);
// ✅ Best: Calculate everything during rendering
+ // ✅ 가장 좋음: 렌더링 중에 모든 값을 계산
const selection = items.find(item => item.id === selectedId) ?? null;
// ...
}
```
Now there is no need to "adjust" the state at all. If the item with the selected ID is in the list, it remains selected. If it's not, the `selection` calculated during rendering will be `null` because no matching item was found. This behavior is different, but arguably better because most changes to `items` preserve the selection.
-
-### Sharing logic between event handlers {/*sharing-logic-between-event-handlers*/}
+이제 state를 "조정"할 필요가 전혀 없습니다. 선택한 ID를 가진 항목이 목록에 있으면 선택된 state로 유지됩니다. 그렇지 않은 경우 렌더링 중에 계산된 `selection` 항목은 일치하는 항목을 찾지 못하므로 `null`이 됩니다. 이 방식은 `items`에 대한 대부분의 변경과 무관하게 'selection' 항목은 그대로 유지되므로 대체로 더 나은 방법입니다.
+### Sharing logic between event handlers이벤트 핸들러 간 로직 공유 {/*sharing-logic-between-event-handlers*/}
Let's say you have a product page with two buttons (Buy and Checkout) that both let you buy that product. You want to show a notification whenever the user puts the product in the cart. Calling `showNotification()` in both buttons' click handlers feels repetitive so you might be tempted to place this logic in an Effect:
+해당 제품을 구매할 수 있는 두 개의 버튼(구매 및 결제)이 있는 제품 페이지가 있다고 합시다. 사용자가 제품을 장바구니에 넣을 때마다 알림을 표시하고 싶습니다. 두 버튼의 클릭 핸들러에 모두 `showNotification()` 호출을 추가하는 것은 반복적으로 느껴지므로 이 로직을 효과에 배치하고 싶을 수 있습니다:
-```js {2-7}
+```js {2-8}
function ProductPage({ product, addToCart }) {
// 🔴 Avoid: Event-specific logic inside an Effect
+ // 🔴 이러지 마세요: Effect 내부에 특정 이벤트에 대한 로직 존재
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the shopping cart!`);
@@ -278,12 +334,15 @@ function ProductPage({ product, addToCart }) {
```
This Effect is unnecessary. It will also most likely cause bugs. For example, let's say that your app "remembers" the shopping cart between the page reloads. If you add a product to the cart once and refresh the page, the notification will appear again. It will keep appearing every time you refresh that product's page. This is because `product.isInCart` will already be `true` on the page load, so the Effect above will call `showNotification()`.
+이 효과는 불필요합니다. 또한 버그를 유발할 가능성이 높습니다. 예를 들어 페이지가 새로고침 될 때마다 앱이 장바구니를 "기억"한다고 가정해 봅시다. 카트에 제품을 한 번 추가하고 페이지를 새로고침 하면 알림이 다시 표시됩니다. 또한 해당 제품 페이지를 새로고침할 때에도 여전히 알림이 계속 등장합니다. 이는 페이지를 불러올 때 `product.isInCart`가 이미 `true`이므로 위의 Effect가 `showNotification()`을 호출하기 때문입니다.
**When you're not sure whether some code should be in an Effect or in an event handler, ask yourself *why* this code needs to run. Use Effects only for code that should run *because* the component was displayed to the user.** In this example, the notification should appear because the user *pressed the button*, not because the page was displayed! Delete the Effect and put the shared logic into a function called from both event handlers:
+**어떤 코드가 Effect에 있어야 하는지 이벤트 핸들러에 있어야 하는지 확실치 않은 경우, 이 코드가 실행되어야 하는 *이유*를 자문해 보세요. 컴포넌트가 사용자에게 표시되었기 *때문에* 실행되어야 하는 코드에만 Effect를 사용하세요.** 이 예제에서는 페이지가 표시되었기 때문이 아니라, 사용자가 버튼을 눌렀기 때문에 알림이 표시되어야 합니다! Effect를 삭제하고 공유 로직을 두 이벤트 핸들러에서 호출하는 함수에 넣으세요:
-```js {2-6,9,13}
+```js {2-7,10,14}
function ProductPage({ product, addToCart }) {
// ✅ Good: Event-specific logic is called from event handlers
+ // ✅ 좋습니다: 이벤트 핸들러 안에서 각 이벤트별 로직 호출
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to the shopping cart!`);
@@ -302,22 +361,26 @@ function ProductPage({ product, addToCart }) {
```
This both removes the unnecessary Effect and fixes the bug.
+이렇게 하면 불필요한 효과가 제거되고 버그가 수정됩니다.
-### Sending a POST request {/*sending-a-post-request*/}
+### Sending a POST requestPOST요청 보내기 {/*sending-a-post-request*/}
This `Form` component sends two kinds of POST requests. It sends an analytics event when it mounts. When you fill in the form and click the Submit button, it will send a POST request to the `/api/register` endpoint:
+이 `Form` 컴포넌트는 두 종류의 POST 요청을 전송합니다. 마운트될 때에는 분석 이벤트를 보냅니다. 양식을 작성하고 제출 버튼을 클릭하면 `/api/register` 엔드포인트로 POST 요청을 보냅니다:
-```js {5-8,10-16}
+```js {5-9,11-18}
function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// ✅ Good: This logic should run because the component was displayed
+ // ✅ 좋습니다: '컴포넌트가 표시되었기 때문에 로직이 실행되어야 하는 경우'에 해당
useEffect(() => {
post('/analytics/event', { eventName: 'visit_form' });
}, []);
// 🔴 Avoid: Event-specific logic inside an Effect
+ // 🔴 이러지 마세요: Effect 내부에 특정 이벤트에 대한 로직 존재
const [jsonToSubmit, setJsonToSubmit] = useState(null);
useEffect(() => {
if (jsonToSubmit !== null) {
@@ -334,17 +397,21 @@ function Form() {
```
Let's apply the same criteria as in the example before.
+이전 예제와 동일한 기준을 적용해 봅시다.
The analytics POST request should remain in an Effect. This is because the _reason_ to send the analytics event is that the form was displayed. (It would fire twice in development, but [see here](/learn/synchronizing-with-effects#sending-analytics) for how to deal with that.)
+분석 POST 요청은 Effect에 남아 있어야 합니다. 분석 이벤트를 전송하는 *이유*는 form이 표시되었기 때문입니다. (개발 모드에서는 두 번 발생하는데, 이를 처리하는 방법은 [여기](/learn/synchronizing-with-effects#sending-analytics)를 참조하세요).
However, the `/api/register` POST request is not caused by the form being _displayed_. You only want to send the request at one specific moment in time: when the user presses the button. It should only ever happen _on that particular interaction_. Delete the second Effect and move that POST request into the event handler:
+그러나 `/api/register` POST 요청은 form이 *표시*되어서 발생하는 것이 아닙니다. 특정 시점, 즉 사용자가 버튼을 누를 때만 요청을 보내고 싶을 것입니다. 이 요청은 *해당 상호작용에서만 발생*해야 합니다. 두 번째 효과를 삭제하고 해당 POST 요청을 이벤트 핸들러로 이동합니다:
-```js {12-13}
+```js {13-15}
function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// ✅ Good: This logic runs because the component was displayed
+ // ✅ 좋습니다: '컴포넌트가 표시되었기 때문에 로직이 실행되어야 하는 경우'에 해당
useEffect(() => {
post('/analytics/event', { eventName: 'visit_form' });
}, []);
@@ -352,6 +419,7 @@ function Form() {
function handleSubmit(e) {
e.preventDefault();
// ✅ Good: Event-specific logic is in the event handler
+ // ✅ 좋습니다: 이벤트 핸들러 안에서 특정 이벤트 로직 호출
post('/api/register', { firstName, lastName });
}
// ...
@@ -359,12 +427,14 @@ function Form() {
```
When you choose whether to put some logic into an event handler or an Effect, the main question you need to answer is _what kind of logic_ it is from the user's perspective. If this logic is caused by a particular interaction, keep it in the event handler. If it's caused by the user _seeing_ the component on the screen, keep it in the Effect.
+어떤 로직을 이벤트 핸들러에 넣을지 Effect에 넣을지 선택할 때, 사용자 관점에서 *어떤 종류의 로직*인지에 대한 답을 찾아야 합니다. 이 로직이 특정 상호작용으로 인해 발생하는 것이라면 이벤트 핸들러에 넣으세요. 사용자가 화면에서 컴포넌트를 *보는* 것이 원인이라면 Effect에 넣으세요.
-### Chains of computations {/*chains-of-computations*/}
+### Chains of computations연쇄 계산 {/*chains-of-computations*/}
Sometimes you might feel tempted to chain Effects that each adjust a piece of state based on other state:
+때로는 다른 state를 바탕으로 또다른 state를 조정하는 Effect를 연쇄적으로 사용하고 싶을 수도 있습니다:
-```js {7-29}
+```js {7-30}
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
@@ -372,6 +442,7 @@ function Game() {
const [isGameOver, setIsGameOver] = useState(false);
// 🔴 Avoid: Chains of Effects that adjust the state solely to trigger each other
+ // 🔴 이러지 마세요: 오직 서로를 트리거하기 위해서만 state를 조정하는 Effect 체인
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
@@ -407,20 +478,25 @@ function Game() {
```
There are two problems with this code.
+이 코드에는 두 가지 문제가 있습니다.
One problem is that it is very inefficient: the component (and its children) have to re-render between each `set` call in the chain. In the example above, in the worst case (`setCard` → render → `setGoldCardCount` → render → `setRound` → render → `setIsGameOver` → render) there are three unnecessary re-renders of the tree below.
+한 가지 문제는 매우 비효율적이라는 점입니다. 컴포넌트(및 그 자식들)은 체인의 각 `set` 호출 사이에 다시 렌더링해야 합니다. 위의 예시에서 최악의 경우(`setCard` → 렌더링 → `setGoldCardCount` → 렌더링 → `setRound` → 렌더링 → `setIsGameOver` → 렌더링)에는 하위 트리의 불필요한 재랜더링이 세 번이나 발생합니다.
Even if it weren't slow, as your code evolves, you will run into cases where the "chain" you wrote doesn't fit the new requirements. Imagine you are adding a way to step through the history of the game moves. You'd do it by updating each state variable to a value from the past. However, setting the `card` state to a value from the past would trigger the Effect chain again and change the data you're showing. Such code is often rigid and fragile.
+속도가 느리지는 않더라도, 코드가 발전함에 따라 작성한 '체인'이 새로운 요구사항에 맞지 않는 경우가 발생할 수 있습니다. 게임 이동의 기록을 단계별로 살펴볼 수 있는 방법을 추가한다고 가정해 보겠습니다. 각 state 변수를 과거의 값으로 업데이트하여 이를 수행할 수 있습니다. 하지만 '카드' state를 과거의 값으로 설정하면 Effect 체인이 다시 트리거되고 표시되는 데이터가 변경됩니다. 이와 같은 코드는 종종 경직되고 취약합니다.
In this case, it's better to calculate what you can during rendering, and adjust the state in the event handler:
+이 경우 렌더링 중에 가능한 것을 계산하고 이벤트 핸들러에서 state를 조정하는 것이 좋습니다:
-```js {6-7,14-26}
+```js {6-8,15-28}
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
// ✅ Calculate what you can during rendering
+ // ✅ 가능한 것을 렌더링 중에 계산
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
@@ -429,6 +505,7 @@ function Game() {
}
// ✅ Calculate all the next state in the event handler
+ // ✅ 이벤트 핸들러에서 다음 state를 모두 계산
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount <= 3) {
@@ -447,20 +524,26 @@ function Game() {
```
This is a lot more efficient. Also, if you implement a way to view game history, now you will be able to set each state variable to a move from the past without triggering the Effect chain that adjusts every other value. If you need to reuse logic between several event handlers, you can [extract a function](#sharing-logic-between-event-handlers) and call it from those handlers.
+훨씬 더 효율적입니다. 또한 게임 기록을 볼 수 있는 방법을 구현하면 이제 다른 모든 값을 조정하는 Effect 체인을 트리거하지 않고도 각 state 변수를 과거의 움직임으로 설정할 수 있습니다. 여러 이벤트 핸들러 간에 로직을 재사용해야 하는 경우 [함수를 추출](#sharing-logic-between-event-handlers)하여 해당 핸들러에서 함수를 호출할 수 있습니다.
Remember that inside event handlers, [state behaves like a snapshot.](/learn/state-as-a-snapshot) For example, even after you call `setRound(round + 1)`, the `round` variable will reflect the value at the time the user clicked the button. If you need to use the next value for calculations, define it manually like `const nextRound = round + 1`.
+이벤트 핸들러 내부에서 [state는 스냅샷처럼 동작](/learn/state-as-a-snapshot)함을 기억하세요. 예를 들어 `setRound(round + 1)`를 호출한 후에도 `round` 변수는 사용자가 버튼을 클릭한 시점의 값을 반영합니다. 계산에 다음 값을 사용해야 하는 경우 `const nextRound = round + 1`과 같이 수동으로 정의하세요.
In some cases, you *can't* calculate the next state directly in the event handler. For example, imagine a form with multiple dropdowns where the options of the next dropdown depend on the selected value of the previous dropdown. Then, a chain of Effects is appropriate because you are synchronizing with network.
+이벤트 핸들러에서 직접 다음 state를 계산할 수 없는 경우도 있습니다. 예를 들어 이전 드롭다운의 선택 값에 따라 다음 드롭다운의 옵션이 달라지는 form을 상상해 봅시다. 이를 네트워크와 동기화해야 한다면 Effect 체인이 적절할 것입니다.
-### Initializing the application {/*initializing-the-application*/}
+### Initializing the application애플리케이션 초기화하기 {/*initializing-the-application*/}
Some logic should only run once when the app loads.
+일부 로직은 앱이 로드될 때 한 번만 실행되어야 합니다.
You might be tempted to place it in an Effect in the top-level component:
+최상위 컴포넌트의 Effect에 배치하고 싶을 수도 있습니다:
```js {2-6}
function App() {
// 🔴 Avoid: Effects with logic that should only ever run once
+ // 🔴 이러지 마세요: 한 번만 실행되어야 하는 로직이 포함된 Effect
useEffect(() => {
loadDataFromLocalStorage();
checkAuthToken();
@@ -470,10 +553,12 @@ function App() {
```
However, you'll quickly discover that it [runs twice in development.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) This can cause issues--for example, maybe it invalidates the authentication token because the function wasn't designed to be called twice. In general, your components should be resilient to being remounted. This includes your top-level `App` component.
+그러나 이 함수가 [개발 중에 두 번 실행된다는 것](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development)을 금방 알게 될 것입니다. 이 함수는 두 번 호출되도록 설계되지 않았기 때문에 인증 토큰이 무효화되는 등의 문제가 발생할 수 있습니다. 일반적으로 컴포넌트는 다시 마운트할 때 복원력이 있어야 합니다. 여기에는 최상위 `App` 컴포넌트도 포함됩니다.
Although it may not ever get remounted in practice in production, following the same constraints in all components makes it easier to move and reuse code. If some logic must run *once per app load* rather than *once per component mount*, add a top-level variable to track whether it has already executed:
+프로덕션 환경에서는 실제로 다시 마운트되지 않을 수 있지만, 모든 컴포넌트에서 동일한 제약 조건을 따르면 코드를 이동하고 재사용하기가 더 쉬워집니다. 일부 로직이 *컴포넌트 마운트당 한 번*이 아니라 *앱 로드당 한 번* 실행되어야 하는 경우, 최상위 변수를 추가하여 이미 실행되었는지 여부를 추적하세요:
-```js {1,5-6,10}
+```js {1,5-6,11}
let didInit = false;
function App() {
@@ -481,6 +566,7 @@ function App() {
if (!didInit) {
didInit = true;
// ✅ Only runs once per app load
+ // ✅ 앱 로드당 한 번만 실행됨
loadDataFromLocalStorage();
checkAuthToken();
}
@@ -490,10 +576,13 @@ function App() {
```
You can also run it during module initialization and before the app renders:
+모듈 초기화 중이나 앱 렌더링 전에 실행할 수도 있습니다:
-```js {1,5}
+```js {1-2,7}
if (typeof window !== 'undefined') { // Check if we're running in the browser.
- // ✅ Only runs once per app load
+ // 브라우저에서 실행중인지 확인
+ // ✅ Only runs once per app load
+ // ✅ 앱 로드당 한 번만 실행됨
checkAuthToken();
loadDataFromLocalStorage();
}
@@ -504,16 +593,19 @@ function App() {
```
Code at the top level runs once when your component is imported--even if it doesn't end up being rendered. To avoid slowdown or surprising behavior when importing arbitrary components, don't overuse this pattern. Keep app-wide initialization logic to root component modules like `App.js` or in your application's entry point.
+컴포넌트를 import할 때 최상위 레벨의 코드는 렌더링되지 않더라도 일단 한 번 실행됩니다. 임의의 컴포넌트를 임포트할 때 속도 저하나 예상치 못한 동작을 방지하려면 이 패턴을 과도하게 사용하지 마세요. 앱 전체 초기화 로직은 `App.js`와 같은 루트 컴포넌트 모듈이나 애플리케이션의 엔트리 포인트에 유지하세요.
-### Notifying parent components about state changes {/*notifying-parent-components-about-state-changes*/}
+### Notifying parent components about state changesstate변경을 부모 컴포넌트에 알리기 {/*notifying-parent-components-about-state-changes*/}
Let's say you're writing a `Toggle` component with an internal `isOn` state which can be either `true` or `false`. There are a few different ways to toggle it (by clicking or dragging). You want to notify the parent component whenever the `Toggle` internal state changes, so you expose an `onChange` event and call it from an Effect:
+`참` 또는 `거짓`일 수 있는 내부 `isOn` state를 가진 `Toggle` 컴포넌트를 작성하고 있다고 가정해 봅시다. 토글하는 방법에는 몇 가지가 있습니다(클릭 또는 드래그). `Toggle` 내부 state가 변경될 때마다 부모 컴포넌트에 알리고 싶어서 `onChange` 이벤트를 prop으로 받고 Effect에서 이를 호출하도록 했습니다:
-```js {4-7}
+```js {4-8}
function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);
// 🔴 Avoid: The onChange handler runs too late
+ // 🔴 이러지 마세요: onChange 핸들러가 너무 늦게 실행됨
useEffect(() => {
onChange(isOn);
}, [isOn, onChange])
@@ -535,15 +627,18 @@ function Toggle({ onChange }) {
```
Like earlier, this is not ideal. The `Toggle` updates its state first, and React updates the screen. Then React runs the Effect, which calls the `onChange` function passed from a parent component. Now the parent component will update its own state, starting another render pass. It would be better to do everything in a single pass.
+앞서와 마찬가지로 이것은 이상적이지 않습니다. 먼저 `Toggle`이 state를 업데이트하고, React가 화면을 업데이트합니다. 그런 다음 React는 부모 컴포넌트로부터 전달받은 `onChange` 함수를 호출하는 Effect를 실행합니다. 이제 부모 컴포넌트가 자신의 state를 업데이트하고, 다른 렌더 패스를 실행합니다. 이보다는, 모든 것을 단일 명령 안에서 처리하는 것이 더 좋습니다.
Delete the Effect and instead update the state of *both* components within the same event handler:
+Effect를 삭제하고, 대신 동일한 이벤트 핸들러 내에서 *두* 컴포넌트의 state를 업데이트 합시다:
-```js {5-7,11,16,18}
+```js {5-8,12,17,19}
function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);
function updateToggle(nextIsOn) {
// ✅ Good: Perform all updates during the event that caused them
+ // ✅ 좋습니다: 이벤트 발생시 모든 업데이트를 수행
setIsOn(nextIsOn);
onChange(nextIsOn);
}
@@ -565,11 +660,14 @@ function Toggle({ onChange }) {
```
With this approach, both the `Toggle` component and its parent component update their state during the event. React [batches updates](/learn/queueing-a-series-of-state-updates) from different components together, so there will only be one render pass.
+이 접근에 따르면 `Toggle` 컴포넌트 및 부모 컴포넌트 모두 이벤트가 발생하는 동안 state를 업데이트합니다. React는 서로 다른 컴포넌트에서 [일괄 업데이트](/learn/queueing-a-series-of-state-updates)를 함께 처리하므로, 렌더링 과정은 한 번만 발생합니다.
You might also be able to remove the state altogether, and instead receive `isOn` from the parent component:
+state를 완전히 제거하고 그 대신 부모 컴포넌트로부터 `isOn`을 받을 수도 있습니다:
-```js {1,2}
+```js {1-3}
// ✅ Also good: the component is fully controlled by its parent
+// ✅ 좋습니다: 부모 컴포넌트에 의해 완전히 제어됨
function Toggle({ isOn, onChange }) {
function handleClick() {
onChange(!isOn);
@@ -588,12 +686,14 @@ function Toggle({ isOn, onChange }) {
```
["Lifting state up"](/learn/sharing-state-between-components) lets the parent component fully control the `Toggle` by toggling the parent's own state. This means the parent component will have to contain more logic, but there will be less state overall to worry about. Whenever you try to keep two different state variables synchronized, try lifting state up instead!
+["state 끌어올리기"](/learn/sharing-state-between-components)는 부모 컴포넌트가 부모 자체의 state를 토글하여 `Toggle`을 완전히 제어할 수 있게 해줍니다. 이는 부모 컴포넌트가 더 많은 로직을 포함해야 하지만, 전체적으로 걱정해야 할 state가 줄어든다는 것을 의미합니다. 두 개의 서로 다른 state 변수를 동기화하려고 할 때마다, 대신 state를 끌어올려 보세요!
-### Passing data to the parent {/*passing-data-to-the-parent*/}
+### Passing data to the parent부모에게 데이터 전달하기 {/*passing-data-to-the-parent*/}
This `Child` component fetches some data and then passes it to the `Parent` component in an Effect:
+다음 `Child` 컴포넌트는 일부 데이터를 가져온 다음 Effect의 `Parent` 컴포넌트에 전달합니다:
-```js {9-14}
+```js {9-15}
function Parent() {
const [data, setData] = useState(null);
// ...
@@ -603,6 +703,7 @@ function Parent() {
function Child({ onFetched }) {
const data = useSomeAPI();
// 🔴 Avoid: Passing data to the parent in an Effect
+ // 🔴 이러지 마세요: Effect에서 부모에게 데이터 전달
useEffect(() => {
if (data) {
onFetched(data);
@@ -613,12 +714,14 @@ function Child({ onFetched }) {
```
In React, data flows from the parent components to their children. When you see something wrong on the screen, you can trace where the information comes from by going up the component chain until you find which component passes the wrong prop or has the wrong state. When child components update the state of their parent components in Effects, the data flow becomes very difficult to trace. Since both the child and the parent need the same data, let the parent component fetch that data, and *pass it down* to the child instead:
+React에서 데이터는 부모 컴포넌트에서 자식 컴포넌트로 흐릅니다. 화면에 뭔가 잘못된 것이 보이면, 컴포넌트 체인을 따라 올라가서 어떤 컴포넌트가 잘못된 prop을 전달하거나 잘못된 state를 가지고 있는지 찾아냄으로써 정보의 출처를 추적할 수 있습니다. 자식 컴포넌트가 Effect에서 부모 컴포넌트의 state를 업데이트하면, 데이터 흐름을 추적하기가 매우 어려워집니다. 자식과 부모 컴포넌트 모두 동일한 데이터가 필요하므로, 대신 부모 컴포넌트가 해당 데이터를 fetch해서 자식에게 *전달*하도록 하세요:
-```js {4-5}
+```js {4-6}
function Parent() {
const data = useSomeAPI();
// ...
// ✅ Good: Passing data down to the child
+ // ✅ 좋습니다: 자식에게 데이터 전달
return ;
}
@@ -628,14 +731,17 @@ function Child({ data }) {
```
This is simpler and keeps the data flow predictable: the data flows down from the parent to the child.
+이렇게 하면 데이터가 부모에서 자식으로 내려오기 때문에 데이터 흐름이 더 간단하고 예측 가능하게 유지됩니다.
-### Subscribing to an external store {/*subscribing-to-an-external-store*/}
+### Subscribing to an external store외부 스토어 구독하기 {/*subscribing-to-an-external-store*/}
Sometimes, your components may need to subscribe to some data outside of the React state. This data could be from a third-party library or a built-in browser API. Since this data can change without React's knowledge, you need to manually subscribe your components to it. This is often done with an Effect, for example:
+때로는 컴포넌트가 React state 외부의 일부 데이터를 구독해야 할 수도 있습니다. 써드파티 라이브러리나 내장 브라우저 API에서 데이터를 가져와야 할 수도 있습니다. 이 데이터는 React가 모르는 사이에 변경될 수도 있는데, 그럴 땐 수동으로 컴포넌트가 해당 데이터를 구독하도록 해야 합니다. 이 작업은 종종 Effect에서 수행합니다. 예를 들어:
-```js {2-17}
+```js {2-18}
function useOnlineStatus() {
// Not ideal: Manual store subscription in an Effect
+ // 이상적이지 않음: Effect에서 수동으로 store 구독
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function updateState() {
@@ -661,10 +767,12 @@ function ChatIndicator() {
```
Here, the component subscribes to an external data store (in this case, the browser `navigator.onLine` API). Since this API does not exist on the server (so it can't be used for the initial HTML), initially the state is set to `true`. Whenever the value of that data store changes in the browser, the component updates its state.
+여기서 컴포넌트는 외부 데이터 저장소(이 경우 브라우저 `navigator.onLine` API)에 구독합니다. 이 API는 서버에 존재하지 않으므로(따라서 초기 HTML을 생성하는 데 사용할 수 없으므로) 처음에는 state가 `true`로 설정됩니다. 브라우저에서 해당 데이터 저장소의 값이 변경될 때마다 컴포넌트는 해당 state를 업데이트합니다.
Although it's common to use Effects for this, React has a purpose-built Hook for subscribing to an external store that is preferred instead. Delete the Effect and replace it with a call to [`useSyncExternalStore`](/reference/react/useSyncExternalStore):
+이를 위해 Effect를 사용하는 것이 일반적이지만, React에는 외부 저장소를 구독하기 위해 특별히 제작된 훅이 있습니다. Effect를 삭제하고 [`useSyncExternalStore`](/reference/react/useSyncExternalStore)호출로 대체하세요:
-```js {11-16}
+```js {11-20}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
@@ -676,10 +784,14 @@ function subscribe(callback) {
function useOnlineStatus() {
// ✅ Good: Subscribing to an external store with a built-in Hook
+ // ✅ 좋습니다: 내장 훅에서 외부 store 구독
return useSyncExternalStore(
subscribe, // React won't resubscribe for as long as you pass the same function
+ // React는 동일한 함수를 전달하는 한 다시 구독하지 않음
() => navigator.onLine, // How to get the value on the client
+ // 클라이언트에서 값을 가져오는 방법
() => true // How to get the value on the server
+ // 서버에서 값을 가져오는 방법
);
}
@@ -690,18 +802,21 @@ function ChatIndicator() {
```
This approach is less error-prone than manually syncing mutable data to React state with an Effect. Typically, you'll write a custom Hook like `useOnlineStatus()` above so that you don't need to repeat this code in the individual components. [Read more about subscribing to external stores from React components.](/reference/react/useSyncExternalStore)
+이 접근 방식은 변경 가능한 데이터를 Effect를 사용해 React state에 수동으로 동기화하는 것보다 오류 가능성이 적습니다. 일반적으로 위의 `useOnlineStatus()`와 같은 커스텀 훅을 작성하여 개별 컴포넌트에서 이 코드를 반복할 필요가 없도록 합니다. [React 컴포넌트에서 외부 store를 구독하는 방법에 대해 자세히 읽어보세요](/reference/react/useSyncExternalStore).
-### Fetching data {/*fetching-data*/}
+### Fetching data데이터 가져오기(fetch) {/*fetching-data*/}
Many apps use Effects to kick off data fetching. It is quite common to write a data fetching Effect like this:
+많은 앱이 데이터 fetch를 시작하기 위해 Effect를 사용합니다. 이와 같은 데이터 fetching Effect를 작성하는 것은 매우 일반적입니다:
-```js {5-10}
+```js {5-11}
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [page, setPage] = useState(1);
useEffect(() => {
// 🔴 Avoid: Fetching without cleanup logic
+ // 🔴 이러지 마세요: 정리 로직 없이 fetch 수행
fetchResults(query, page).then(json => {
setResults(json);
});
@@ -715,14 +830,19 @@ function SearchResults({ query }) {
```
You *don't* need to move this fetch to an event handler.
+이 페치를 이벤트 핸들러로 옮길 필요는 *없습니다*.
This might seem like a contradiction with the earlier examples where you needed to put the logic into the event handlers! However, consider that it's not *the typing event* that's the main reason to fetch. Search inputs are often prepopulated from the URL, and the user might navigate Back and Forward without touching the input.
+이벤트 핸들러에 로직을 넣어야 했던 앞선 예제와 모순되는 것처럼 보일 수 있습니다! 하지만 가져와야 하는 주된 이유가 *타이핑 이벤트*가 아니라는 점을 생각해 보세요. 검색 입력은 URL에 미리 채워져 있는 경우가 많으며, 사용자는 input을 건드리지 않고도 앞뒤로 탐색할 수 있습니다.
It doesn't matter where `page` and `query` come from. While this component is visible, you want to keep `results` [synchronized](/learn/synchronizing-with-effects) with data from the network for the current `page` and `query`. This is why it's an Effect.
+`page`와 `query`가 어디에서 오는지는 중요하지 않습니다. 이 컴포넌트가 표시되는 동안 현재의 `page` 및 `query`에 대한 네트워크의 데이터와 `results`의 [동기화](/learn/synchronizing-with-effects)가 유지되면 됩니다. 이것이 Effect인 이유입니다.
However, the code above has a bug. Imagine you type `"hello"` fast. Then the `query` will change from `"h"`, to `"he"`, `"hel"`, `"hell"`, and `"hello"`. This will kick off separate fetches, but there is no guarantee about which order the responses will arrive in. For example, the `"hell"` response may arrive *after* the `"hello"` response. Since it will call `setResults()` last, you will be displaying the wrong search results. This is called a ["race condition"](https://en.wikipedia.org/wiki/Race_condition): two different requests "raced" against each other and came in a different order than you expected.
+다만 위 코드에는 버그가 있습니다. `"hello"`를 빠르게 입력한다고 합시다. 그러면 `query`가 `"h"`에서 `"he"`, `"hel"`, `"hell"`, `"hello"`로 변경됩니다. 이렇게 하면 각각 fetching을 수행하지만, 어떤 순서로 응답이 도착할지는 보장할 수 없습니다. 예를 들어 `"hell"` 응답은 `"hello"` 응답 *이후*에 도착할 수 있습니다. 이에 따라 마지막에 호출된 `setResults()`로부터 잘못된 검색 결과가 표시될 수 있습니다. 이를 ["경쟁 조건"](https://en.wikipedia.org/wiki/Race_condition)이라고 합니다. 서로 다른 두 요청이 서로 "경쟁"하여 예상과 다른 순서로 도착한 경우입니다.
**To fix the race condition, you need to [add a cleanup function](/learn/synchronizing-with-effects#fetching-data) to ignore stale responses:**
+**경쟁 조건을 수정하기 위해서는 오래된 응답을 무시하도록 [정리 함수를 추가](/learn/synchronizing-with-effects#fetching-data)해야 합니다.**
```js {5,7,9,11-13}
function SearchResults({ query }) {
@@ -748,12 +868,16 @@ function SearchResults({ query }) {
```
This ensures that when your Effect fetches data, all responses except the last requested one will be ignored.
+이렇게 하면 Effect가 데이터를 가져올 때 마지막으로 요청된 응답을 제외한 모든 응답이 무시됩니다.
Handling race conditions is not the only difficulty with implementing data fetching. You might also want to think about caching responses (so that the user can click Back and see the previous screen instantly), how to fetch data on the server (so that the initial server-rendered HTML contains the fetched content instead of a spinner), and how to avoid network waterfalls (so that a child can fetch data without waiting for every parent).
+데이터 불러오기를 구현할 때 경합 조건을 처리하는 것만 어려운 것은 아닙니다. 응답을 캐시하는 방법(사용자가 Back을 클릭하고면 스피너 대신 이전 화면을 즉시 볼 수 있도록), 서버에서 가져오는 방법(초기 서버 렌더링 HTML에 스피너 대신 가져온 콘텐츠가 포함되도록), 네트워크 워터폴을 피하는 방법(데이터를 가져와야 하는 하위 컴포넌트가 시작하기 전에 위의 모든 부모가 데이터 가져오기를 완료할 때까지 기다릴 필요가 없도록) 등도 고려해볼 사항입니다.
**These issues apply to any UI library, not just React. Solving them is not trivial, which is why modern [frameworks](/learn/start-a-new-react-project#production-grade-react-frameworks) provide more efficient built-in data fetching mechanisms than fetching data in Effects.**
+**이런 문제는 React뿐만 아니라 모든 UI 라이브러리에 적용됩니다. 이러한 문제를 해결하는 것은 간단하지 않기 때문에 최신 [프레임워크](/learn/start-a-new-react-project#building-with-a-full-featured-framework)들은 컴포넌트에서 직접 Effect를 작성하는 것보다 더 효율적인 내장 데이터 페칭 메커니즘을 제공합니다.**
If you don't use a framework (and don't want to build your own) but would like to make data fetching from Effects more ergonomic, consider extracting your fetching logic into a custom Hook like in this example:
+프레임워크를 사용하지 않고(또한 직접 만들고 싶지 않고) Effect에서 데이터 페칭을 보다 인체공학적으로 만들고 싶다면, 다음 예시처럼 페칭 로직을 커스텀 훅으로 추출하는 것을 고려해 보세요:
```js {4}
function SearchResults({ query }) {
@@ -787,8 +911,10 @@ function useData(url) {
```
You'll likely also want to add some logic for error handling and to track whether the content is loading. You can build a Hook like this yourself or use one of the many solutions already available in the React ecosystem. **Although this alone won't be as efficient as using a framework's built-in data fetching mechanism, moving the data fetching logic into a custom Hook will make it easier to adopt an efficient data fetching strategy later.**
+또한 오류 처리와 콘텐츠 로딩 여부를 추적하기 위한 로직을 추가하고 싶을 것입니다. 이와 같은 훅을 직접 빌드하거나 React 에코시스템에서 이미 사용 가능한 많은 솔루션 중 하나를 사용할 수 있습니다. **이 방법만으로는 프레임워크에 내장된 데이터 페칭 메커니즘을 사용하는 것만큼 효율적이지는 않겠지만, 데이터 페칭 로직을 커스텀 훅으로 옮기면 나중에 효율적인 데이터 페칭 전략을 채택하기가 더 쉬워집니다.**
In general, whenever you have to resort to writing Effects, keep an eye out for when you can extract a piece of functionality into a custom Hook with a more declarative and purpose-built API like `useData` above. The fewer raw `useEffect` calls you have in your components, the easier you will find to maintain your application.
+일반적으로 Effects를 작성해야 할 때마다 위의 `useData`와 같이 좀 더 선언적이고 목적에 맞게 만들어진 API를 사용하여 기능을 커스텀 훅으로 추출할 수 있는지 잘 살펴보세요. 컴포넌트에서 원시 `useEffect` 호출이 적을수록 애플리케이션을 유지 관리하기가 더 쉬워집니다.
@@ -801,16 +927,27 @@ In general, whenever you have to resort to writing Effects, keep an eye out for
- Whenever you try to synchronize state variables in different components, consider lifting state up.
- You can fetch data with Effects, but you need to implement cleanup to avoid race conditions.
+
+- 렌더링 중에 무언가를 계산할 수 있다면 Effect가 필요하지 않습니다.
+- 비용이 많이 드는 계산을 캐시하려면 `useEffect` 대신 `useMemo`를 추가하세요.
+- 전체 컴포넌트 트리의 state를 재설정하려면 다른 `key`를 전달하세요.
+- prop 변경에 대한 응답으로 특정 state 일부를 조정하려면 렌더링 중에 설정하세요.
+- 컴포넌트가 *표시*되었기 때문에 실행해야 하는 코드는 Effect에 있어야 하고, 나머지는 이벤트에 있어야 합니다.
+- 여러 컴포넌트의 state를 업데이트해야 하는 경우 단일 이벤트에서 처리하는 것이 좋습니다.
+- 여러 컴포넌트에서 state 변수를 동기화하려고 할 때마다 state 끌어올리기를 고려하세요.
+- Effect로 데이터를 가져올 수 있지만, 경쟁 조건을 피하기 위해 정리 로직을 구현해야 합니다.
+
-#### Transform data without Effects {/*transform-data-without-effects*/}
+#### Transform data without EffectsEffect 없이 데이터 변환하기 {/*transform-data-without-effects*/}
The `TodoList` below displays a list of todos. When the "Show only active todos" checkbox is ticked, completed todos are not displayed in the list. Regardless of which todos are visible, the footer displays the count of todos that are not yet completed.
+아래의 `TodoList`는 할일 목록을 표시합니다. "Show only active todos" 체크박스를 선택하면 완료된 할 일은 목록에 표시되지 않습니다. 표시되는 할 일과 관계없이 바닥글에는 아직 완료되지 않은 할 일의 수가 표시됩니다.
Simplify this component by removing all the unnecessary state and Effects.
-
+불필요한 state와 Effect를 모두 제거하여 이 컴포넌트를 단순화하세요.
```js
@@ -910,14 +1047,17 @@ input { margin-top: 10px; }
If you can calculate something during rendering, you don't need state or an Effect that updates it.
+렌더링 중에 무언가를 계산할 수 있다면 state나 이를 업데이트하는 Effect가 필요하지 않습니다.
There are only two essential pieces of state in this example: the list of `todos` and the `showActive` state variable which represents whether the checkbox is ticked. All of the other state variables are [redundant](/learn/choosing-the-state-structure#avoid-redundant-state) and can be calculated during rendering instead. This includes the `footer` which you can move directly into the surrounding JSX.
+이 예제에서는 `todos` 목록과 체크박스의 체크 여부를 나타내는 `showActive`의 두 필수 state만 있습니다. 다른 모든 state 변수는 [중복](/learn/choosing-the-state-structure#avoid-redundant-state)이며 대신 렌더링 중에 계산할 수 있습니다. 여기에는 주변 JSX로 직접 이동할 수 있는 `footer`가 포함됩니다.
Your result should end up looking like this:
+결과는 다음과 같이 표시되어야 합니다:
@@ -1002,15 +1142,18 @@ input { margin-top: 10px; }
-#### Cache a calculation without Effects {/*cache-a-calculation-without-effects*/}
+#### Cache a calculation without EffectsEffect 없이 계산 캐시하기 {/*cache-a-calculation-without-effects*/}
In this example, filtering the todos was extracted into a separate function called `getVisibleTodos()`. This function contains a `console.log()` call inside of it which helps you notice when it's being called. Toggle "Show only active todos" and notice that it causes `getVisibleTodos()` to re-run. This is expected because visible todos change when you toggle which ones to display.
+이 예제에서는 할 일 필터링이 `getVisibleTodos()`라는 별도의 함수로 추출되었습니다. 이 함수 안에는 `console.log()` 호출이 포함되어 있어 언제 호출되는지 알 수 있습니다. "활성 할 일만 표시"를 토글하면 `getVisibleTodos()`가 다시 실행되는 것을 확인할 수 있습니다. 이는 표시할 할 일을 토글하면 표시되는 할 일이 변경되기 때문에 예상되는 현상입니다.
Your task is to remove the Effect that recomputes the `visibleTodos` list in the `TodoList` component. However, you need to make sure that `getVisibleTodos()` does *not* re-run (and so does not print any logs) when you type into the input.
+여러분의 임무는 `TodoList` 컴포넌트에서 `visibleTodos` 목록을 다시 계산하는 Effect를 제거하는 것입니다. 하지만 입력을 입력할 때 `getVisibleTodos()`가 다시 실행되지 않도록(따라서 로그를 인쇄하지 않도록) 해야 합니다.
One solution is to add a `useMemo` call to cache the visible todos. There is also another, less obvious solution.
+한 가지 해결책은 `useMemo` 호출을 추가하여 표시된 할일을 캐시하는 것입니다. 덜 분명한 또 다른 해결책도 있습니다.
@@ -1097,6 +1240,7 @@ input { margin-top: 10px; }
Remove the state variable and the Effect, and instead add a `useMemo` call to cache the result of calling `getVisibleTodos()`:
+state 변수와 Effect를 제거하고 대신 `getVisibleTodos()` 호출 결과를 캐시하는 `useMemo` 호출을 추가합니다:
@@ -1178,8 +1322,10 @@ input { margin-top: 10px; }
With this change, `getVisibleTodos()` will be called only if `todos` or `showActive` change. Typing into the input only changes the `text` state variable, so it does not trigger a call to `getVisibleTodos()`.
+이 변경으로 `todos` 또는 `showActive`가 변경된 경우에만 `getVisibleTodos()`가 호출됩니다. 입력을 입력하면 `text` state 변수만 변경되므로 `getVisibleTodos()` 호출을 트리거하지 않습니다.
There is also another solution which does not need `useMemo`. Since the `text` state variable can't possibly affect the list of todos, you can extract the `NewTodo` form into a separate component, and move the `text` state variable inside of it:
+`useMemo`가 필요 없는 또 다른 해결책도 있습니다. `text` state 변수가 할 일 목록에 영향을 줄 수 없기 때문에 `NewTodo` form을 별도의 컴포넌트로 추출하고 그 안에 `text` state 변수를 옮길 수 있습니다:
@@ -1267,15 +1413,17 @@ input { margin-top: 10px; }
This approach satisfies the requirements too. When you type into the input, only the `text` state variable updates. Since the `text` state variable is in the child `NewTodo` component, the parent `TodoList` component won't get re-rendered. This is why `getVisibleTodos()` doesn't get called when you type. (It would still be called if the `TodoList` re-renders for another reason.)
+이 접근 방식도 요구 사항을 충족합니다. input에 타이핑하면 `text` state 변수만 업데이트됩니다. `text` state 변수가 자식인 `NewTodo` 컴포넌트에 있기 때문에 부모인 `TodoList` 컴포넌트는 다시 렌더링되지 않습니다. 그래서 사용자가 입력할 때 `getVisibleTodos()`가 호출되지 않습니다. (다른 이유로 `TodoList`가 다시 렌더링되는 경우에는 여전히 호출됩니다.)
-#### Reset state without Effects {/*reset-state-without-effects*/}
+#### Reset state without EffectsEffect 없이 state 재설정하기 {/*reset-state-without-effects*/}
This `EditContact` component receives a contact object shaped like `{ id, name, email }` as the `savedContact` prop. Try editing the name and email input fields. When you press Save, the contact's button above the form updates to the edited name. When you press Reset, any pending changes in the form are discarded. Play around with this UI to get a feel for it.
+이 `EditContact` 컴포넌트는 `{ id, name, email }` 모양의 연락처 객체를 `savedContact` prop으로 받습니다. name과 email 입력 필드를 편집해 보세요. Save를 누르면 양식 위의 연락처 버튼이 편집된 이름으로 업데이트됩니다. Reset을 누르면 양식의 보류 중인 변경 사항이 모두 삭제됩니다. 이 UI를 사용해 보면서 사용법을 익혀 보세요.
When you select a contact with the buttons at the top, the form resets to reflect that contact's details. This is done with an Effect inside `EditContact.js`. Remove this Effect. Find another way to reset the form when `savedContact.id` changes.
-
+상단의 버튼으로 연락처를 선택하면 해당 연락처의 세부 정보를 반영하도록 양식이 재설정됩니다. 이 작업은 `EditContact.js` 내의 Effect로 수행됩니다. 이 Effect를 제거하세요. `savedContact.id`가 변경될 때 양식을 재설정하는 다른 방법을 찾아보세요.
```js App.js hidden
@@ -1433,12 +1581,13 @@ button {
It would be nice if there was a way to tell React that when `savedContact.id` is different, the `EditContact` form is conceptually a _different contact's form_ and should not preserve state. Do you recall any such way?
-
+`savedContact.id`가 달라졌을 때, `EditContact` form은 개념적으로 *다른 연락처의 폼*이므로, React에게 state를 보존해서는 안 된다고 알리는 방법이 있다면 좋을 것 같습니다. 그런 방법을 기억하시나요?
Split the `EditContact` component in two. Move all the form state into the inner `EditForm` component. Export the outer `EditContact` component, and make it pass `savedContact.id` as the `key` to the inner `EditContact` component. As a result, the inner `EditForm` component resets all of the form state and recreates the DOM whenever you select a different contact.
+`EditContact` 컴포넌트를 둘로 분할합니다. 모든 양식 state를 내부의 `EditForm` 컴포넌트로 이동합니다. 외부 `EditContact` 컴포넌트를 내보내고 `savedContact.id`를 `key`로 내부 `EditContact` 컴포넌트에 전달하도록 합니다. 그 결과, 내부 `EditForm` 컴포넌트는 다른 연락처를 선택할 때마다 모든 양식 state를 재설정하고 DOM을 다시 생성합니다.
@@ -1600,17 +1749,21 @@ button {
-#### Submit a form without Effects {/*submit-a-form-without-effects*/}
+#### Submit a form without EffectsEffect 없이 양식 제출하기 {/*submit-a-form-without-effects*/}
This `Form` component lets you send a message to a friend. When you submit the form, the `showForm` state variable is set to `false`. This triggers an Effect calling `sendMessage(message)`, which sends the message (you can see it in the console). After the message is sent, you see a "Thank you" dialog with an "Open chat" button that lets you get back to the form.
+이 `Form` 컴포넌트를 사용하면 친구에게 메시지를 보낼 수 있습니다. 양식을 제출하면 `showForm` state 변수가 `false`로 설정됩니다. 그러면 메시지를 전송하는 `sendMessage(message)`라는 Effect가 트리거됩니다(콘솔에서 확인할 수 있음). 메시지가 전송되면 'Open Chat' 버튼이 있는 "Thank you" 대화 상자가 표시되어 양식으로 돌아갈 수 있습니다.
Your app's users are sending way too many messages. To make chatting a little bit more difficult, you've decided to show the "Thank you" dialog *first* rather than the form. Change the `showForm` state variable to initialize to `false` instead of `true`. As soon as you make that change, the console will show that an empty message was sent. Something in this logic is wrong!
+앱 사용자가 너무 많은 메시지를 보내고 있습니다. 채팅을 조금 더 어렵게 만들기 위해 양식 대신 "감사합니다" 대화 상자를 *먼저* 표시하기로 결정했습니다. `showForm` state 변수를 `true` 대신 `false`로 초기화하도록 변경해 보세요. 이렇게 변경하자마자 콘솔에 빈 메시지가 전송된 것으로 표시됩니다. 이 로직에서 뭔가 잘못되었습니다!
What's the root cause of this problem? And how can you fix it?
+이 문제의 근본 원인은 무엇일까요? 그리고 어떻게 해결할 수 있을까요?
Should the message be sent _because_ the user saw the "Thank you" dialog? Or is it the other way around?
+사용자가 "감사합니다" 대화 상자를 보았기 *때문에* 메시지가 전송되어야 하나요? 아니면 그 반대인가요?
@@ -1676,7 +1829,7 @@ label, textarea { margin-bottom: 10px; display: block; }
The `showForm` state variable determines whether to show the form or the "Thank you" dialog. However, you aren't sending the message because the "Thank you" dialog was _displayed_. You want to send the message because the user has _submitted the form._ Delete the misleading Effect and move the `sendMessage` call inside the `handleSubmit` event handler:
-
+`showForm` state 변수는 양식을 표시할지, 아니면 "감사합니다" 대화 상자를 표시할지를 결정합니다. 그러나 "감사합니다" 대화 상자가 *표시되었기 때문에* 메시지를 보내지는 않습니다. 사용자가 *양식을 제출했기 때문에* 메시지를 보내려는 것입니다. 오해의 소지가 있는 Effect를 삭제하고 `handleSubmit` 이벤트 핸들러 내부로 `sendMessage` 호출을 이동합니다:
```js
@@ -1732,6 +1885,7 @@ label, textarea { margin-bottom: 10px; display: block; }
Notice how in this version, only _submitting the form_ (which is an event) causes the message to be sent. It works equally well regardless of whether `showForm` is initially set to `true` or `false`. (Set it to `false` and notice no extra console messages.)
+이 버전에서는 *양식을 제출할 때만* (이벤트인) 메시지가 전송되는 것을 주목하세요. `showForm`이 처음에 `true`로 설정되었는지, `false`로 설정되었는지에 관계없이 동일하게 작동합니다. (`false`로 설정해도 추가 콘솔 메시지가 표시되지 않습니다.)
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 7173759688b..378077256d8 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -11,8 +11,9 @@ import '@docsearch/css';
import '../styles/algolia.css';
import '../styles/index.css';
import '../styles/sandpack.css';
+import '../styles/translate.css';
-if (typeof window !== 'undefined') {
+/* if (typeof window !== 'undefined') {
if (process.env.NODE_ENV === 'production') {
ga('create', process.env.NEXT_PUBLIC_GA_TRACKING_ID, 'auto');
ga('send', 'pageview');
@@ -21,7 +22,7 @@ if (typeof window !== 'undefined') {
window.addEventListener(terminationEvent, function () {
ga('send', 'timing', 'JS Dependencies', 'unload');
});
-}
+} */
export default function MyApp({Component, pageProps}: AppProps) {
const router = useRouter();
@@ -41,7 +42,7 @@ export default function MyApp({Component, pageProps}: AppProps) {
}
}, []);
- useEffect(() => {
+ /* useEffect(() => {
const handleRouteChange = (url: string) => {
const cleanedUrl = url.split(/[\?\#]/)[0];
ga('set', 'page', cleanedUrl);
@@ -51,7 +52,7 @@ export default function MyApp({Component, pageProps}: AppProps) {
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
- }, [router.events]);
+ }, [router.events]); */
return ;
}
diff --git a/src/sidebarHome.json b/src/sidebarHome.json
index 4509c26fc3c..113d9df39ac 100644
--- a/src/sidebarHome.json
+++ b/src/sidebarHome.json
@@ -8,10 +8,12 @@
},
{
"title": "Quick Start",
+ "translatedTitle": "빠른 시작",
"path": "/learn"
},
{
"title": "Installation",
+ "translatedTitle": "설치",
"path": "/learn/installation"
},
{
@@ -20,18 +22,22 @@
},
{
"title": "Describing the UI",
+ "translatedTitle": "UI 구성하기",
"path": "/learn/describing-the-ui"
},
{
"title": "Adding Interactivity",
+ "translatedTitle": "상호작용 추가하기",
"path": "/learn/adding-interactivity"
},
{
"title": "Managing State",
+ "translatedTitle": "state 관리",
"path": "/learn/managing-state"
},
{
"title": "Escape Hatches",
+ "translatedTitle": "탈출구",
"path": "/learn/escape-hatches"
},
{
diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json
index 89d5cffca0c..ec2fc25bcbf 100644
--- a/src/sidebarLearn.json
+++ b/src/sidebarLearn.json
@@ -8,36 +8,44 @@
},
{
"title": "Quick Start",
+ "translatedTitle": "빠른 시작",
"path": "/learn",
"routes": [
{
"title": "Tutorial: Tic-Tac-Toe",
+ "translatedTitle": "자습서: 틱택토 게임",
"path": "/learn/tutorial-tic-tac-toe"
},
{
"title": "Thinking in React",
+ "translatedTitle": "React로 사고하기",
"path": "/learn/thinking-in-react"
}
]
},
{
"title": "Installation",
+ "translatedTitle": "설치",
"path": "/learn/installation",
"routes": [
{
"title": "Start a New React Project",
+ "translatedTitle": "새 React 프로젝트 시작하기",
"path": "/learn/start-a-new-react-project"
},
{
"title": "Add React to an Existing Project",
+ "translatedTitle": "기존 프로젝트에 React 추가하기",
"path": "/learn/add-react-to-an-existing-project"
},
{
"title": "Editor Setup",
+ "translatedTitle": "편집기 설정",
"path": "/learn/editor-setup"
},
{
"title": "React Developer Tools",
+ "translatedTitle": "React 개발자 도구",
"path": "/learn/react-developer-tools"
}
]
@@ -48,148 +56,182 @@
},
{
"title": "Describing the UI",
+ "translatedTitle": "UI 구성하기",
"tags": [],
"path": "/learn/describing-the-ui",
"routes": [
{
"title": "Your First Component",
+ "translatedTitle": "첫번째 컴포넌트",
"path": "/learn/your-first-component"
},
{
"title": "Importing and Exporting Components",
+ "translatedTitle": "컴포넌트 import 및 export",
"path": "/learn/importing-and-exporting-components"
},
{
"title": "Writing Markup with JSX",
+ "translatedTitle": "JSX로 마크업 작성하기",
"path": "/learn/writing-markup-with-jsx"
},
{
"title": "JavaScript in JSX with Curly Braces",
+ "translatedTitle": "JSX에서 JavaScript 사용하기",
"path": "/learn/javascript-in-jsx-with-curly-braces"
},
{
"title": "Passing Props to a Component",
+ "translatedTitle": "컴포넌트에 props 전달하기",
"path": "/learn/passing-props-to-a-component"
},
{
"title": "Conditional Rendering",
+ "translatedTitle": "조건부 렌더링",
"path": "/learn/conditional-rendering"
},
{
"title": "Rendering Lists",
+ "translatedTitle": "목록 렌더링",
"path": "/learn/rendering-lists"
},
{
"title": "Keeping Components Pure",
+ "translatedTitle": "컴포넌트 순수성 유지",
"path": "/learn/keeping-components-pure"
}
]
},
{
"title": "Adding Interactivity",
+ "translatedTitle": "상호작용 추가하기",
"path": "/learn/adding-interactivity",
"tags": [],
"routes": [
{
"title": "Responding to Events",
+ "translatedTitle": "이벤트에 응답하기",
"path": "/learn/responding-to-events"
},
{
"title": "State: A Component's Memory",
+ "translatedTitle": "state: 컴포넌트의 메모리",
"path": "/learn/state-a-components-memory"
},
{
"title": "Render and Commit",
+ "translatedTitle": "렌더링하고 커밋하기",
"path": "/learn/render-and-commit"
},
{
"title": "State as a Snapshot",
+ "translatedTitle": "스냅샷으로서의 state",
"path": "/learn/state-as-a-snapshot"
},
{
"title": "Queueing a Series of State Updates",
+ "translatedTitle": "state 업데이트 큐",
"path": "/learn/queueing-a-series-of-state-updates"
},
{
"title": "Updating Objects in State",
+ "translatedTitle": "객체 state 업데이트",
"path": "/learn/updating-objects-in-state"
},
{
"title": "Updating Arrays in State",
+ "translatedTitle": "배열 state 업데이트",
"path": "/learn/updating-arrays-in-state"
}
]
},
{
"title": "Managing State",
+ "translatedTitle": "state 관리",
"path": "/learn/managing-state",
"tags": ["intermediate"],
"routes": [
{
"title": "Reacting to Input with State",
+ "translatedTitle": "state로 입력에 반응하기",
"path": "/learn/reacting-to-input-with-state"
},
{
"title": "Choosing the State Structure",
+ "translatedTitle": "state 구조 선택",
"path": "/learn/choosing-the-state-structure"
},
{
"title": "Sharing State Between Components",
+ "translatedTitle": "컴포넌트 간 state 공유",
"path": "/learn/sharing-state-between-components"
},
{
"title": "Preserving and Resetting State",
+ "translatedTitle": "state 보존 및 재설정",
"path": "/learn/preserving-and-resetting-state"
},
{
"title": "Extracting State Logic into a Reducer",
+ "translatedTitle": "state 로직을 리듀서로 추출하기",
"path": "/learn/extracting-state-logic-into-a-reducer"
},
{
"title": "Passing Data Deeply with Context",
+ "translatedTitle": "컨텍스트로 데이터를 깊게 전달하기",
"path": "/learn/passing-data-deeply-with-context"
},
{
"title": "Scaling Up with Reducer and Context",
+ "translatedTitle": "리듀서와 컨텍스트로 확장하기",
"path": "/learn/scaling-up-with-reducer-and-context"
}
]
},
{
"title": "Escape Hatches",
+ "translatedTitle": "탈출구",
"path": "/learn/escape-hatches",
"tags": ["advanced"],
"routes": [
{
"title": "Referencing Values with Refs",
+ "translatedTitle": "ref로 값 참조하기",
"path": "/learn/referencing-values-with-refs"
},
{
"title": "Manipulating the DOM with Refs",
+ "translatedTitle": "ref로 DOM 조작하기",
"path": "/learn/manipulating-the-dom-with-refs"
},
{
"title": "Synchronizing with Effects",
+ "translatedTitle": "Effect와 동기화하기",
"path": "/learn/synchronizing-with-effects"
},
{
"title": "You Might Not Need an Effect",
+ "translatedTitle": "Effect가 필요하지 않을 수도 있습니다",
"path": "/learn/you-might-not-need-an-effect"
},
{
"title": "Lifecycle of Reactive Effects",
+ "translatedTitle": "반응형 Effect의 생명주기",
"path": "/learn/lifecycle-of-reactive-effects"
},
{
"title": "Separating Events from Effects",
+ "translatedTitle": "이벤트와 Effect 분리하기",
"path": "/learn/separating-events-from-effects"
},
{
"title": "Removing Effect Dependencies",
- "path": "/learn/removing-effect-dependencies"
+ "translatedTitle": "Effect 종속성 제거하기",
+ "path": "/learn/removing-effect-dependencies"
},
{
"title": "Reusing Logic with Custom Hooks",
+ "translatedTitle": "커스텀훅으로 로직 재사용하기",
"path": "/learn/reusing-logic-with-custom-hooks"
}
]
diff --git a/src/styles/translate.css b/src/styles/translate.css
new file mode 100644
index 00000000000..581a6d80d01
--- /dev/null
+++ b/src/styles/translate.css
@@ -0,0 +1,83 @@
+.translate {
+ display: inline-block;
+ position: relative;
+ padding-left: 14px;
+}
+.translate::before {
+ content: '';
+ position: absolute;
+ left: 2px;
+ top: 0.2em;
+ bottom: 0.2em;
+ border-left: 4px solid var(--docsearch-primary-color);
+}
+
+h1 > strong,
+h2 > strong,
+h3 > strong,
+h4 > strong,
+h5 > strong,
+h6 > strong {
+ display: block;
+}
+
+h1 > .translate {
+ padding-left: 20px;
+}
+h1 > .translate::before {
+ left: 4px;
+ border-left-width: 6px;
+}
+
+h2 > .translate {
+ padding-left: 19px;
+}
+h2 > .translate::before {
+ left: 3px;
+ border-left-width: 5px;
+}
+
+nav[role='navigation'] {
+ line-height: 1.4;
+}
+nav[role='navigation'] .translate {
+ display: block;
+ padding-left: 11px;
+}
+nav[role='navigation'] .translate::before {
+ left: 3px;
+ border-left-width: 3px;
+}
+.challenge-tab .translate {
+ display: block;
+ margin-top: -7px;
+ margin-left: 1px;
+}
+.challenge-tab .translate::before {
+ border-left-width: 3px;
+ top: 5px;
+ bottom: 5px;
+}
+
+.translate-block {
+ position: relative;
+ padding-left: 14px;
+ margin-top: -0.5rem;
+}
+.translate-block > ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+.translate-block > *:first-of-type {
+ margin-top: 0;
+}
+.translate-block::before {
+ content: '';
+ position: absolute;
+ display: inline-block;
+ vertical-align: middle;
+ left: 2px;
+ top: 5px;
+ bottom: 5px;
+ border-left: 4px solid var(--docsearch-primary-color);
+}