diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md index 60fe0d201..7fb1819ca 100644 --- a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md @@ -12,11 +12,11 @@ sidebar_position: 1 ## 사용자 로그인 정보 수집 방법 -앱에서 사용자로부터 로그인 정보를 수집하는 방법을 알아보겠습니다. 만약에 OAuth를 사용하는 경우, OAuth 제공자의 로그인 페이지를 사용하여 [3단계](#how-to-store-the-token-for-authenticated-requests)로 바로 넘어갈 수 있습니다. +앱에서 사용자로부터 로그인 정보를 수집하는 방법을 알아보겠습니다. 만약에 OAuth를 사용하는 경우, OAuth 제공자의 로그인 page를 사용하여 [3단계](#how-to-store-the-token-for-authenticated-requests)로 바로 넘어갈 수 있습니다. -### 전용 로그인 페이지 만들기 +### 전용 로그인 page 만들기 -웹사이트에서 사용자 이름과 비밀번호를 입력하는 로그인 페이지를 제공하는 것이 일반적입니다. 이러한 페이지들은 구조가 단순하여 별도의 복잡한 분해 작업이 필요하지 않습니다. 다만, 로그인과 회원가입 양식은 외형이 비슷하기 때문에, 경우에 따라 두 양식을 하나의 페이지에서 통합하여 제공하기도 합니다. +웹사이트에서 사용자 이름과 비밀번호를 입력하는 로그인 page를 제공하는 것이 일반적입니다. 이러한 page들은 구조가 단순하여 별도의 복잡한 분해 작업이 필요하지 않습니다. 다만, 로그인과 회원가입 양식은 외형이 비슷하기 때문에, 경우에 따라 두 양식을 하나의 page에서 통합하여 제공하기도 합니다. - 📂 pages - 📂 login @@ -28,9 +28,9 @@ sidebar_position: 1 로그인과 회원가입 컴포넌트를 별도로 만들고, 필요에 따라 index 파일에서 export 할 수 있습니다. 이 컴포넌트들은 사용자로부터 로그인 정보을 입력받는 폼을 포함합니다. -### 로그인 다이얼로그 만들기 +### 로그인 widget 만들기 -앱의 어디서나 사용할 수 있는 로그인 다이얼로그가 필요하다면, 이 다이얼로그를 재사용 가능한 위젯으로 만드는 것이 좋습니다. 이렇게 하면 불필요한 세분화를 피하면서도 어떤 페이지에서나 쉽게 로그인 다이얼로그를 띄울 수 있습니다. +앱의 어디서나 사용할 수 있는 로그인 다이얼로그가 필요하다면, 이 다이얼로그를 재사용 가능한 widget으로 만드는 것이 좋습니다. 이렇게 하면 불필요한 세분화를 피하면서도 어떤 page에서나 쉽게 로그인 다이얼로그를 띄울 수 있습니다. - 📂 widgets - 📂 login-dialog @@ -39,11 +39,11 @@ sidebar_position: 1 - 📄 index.ts - other widgets… -가이드 나머지 부분은 전용 페이지 방식에 대해 설명하고 있지만, 동일한 원칙을 로그인 다이얼로그에도 적용할 수 있습니다. +가이드 나머지 부분은 전용 page 방식에 대해 설명하고 있지만, 동일한 원칙을 로그인 다이얼로그에도 적용할 수 있습니다. ### 클라이언트 측 검증 -특히 회원가입의 경우, 사용자가 입력한 내용에 문제가 있을 때 빠르게 피드백을 제공하기 위해 클라이언트 측 검증을 수행하는 것이 좋습니다. 이를 위해 로그인 페이지의 `model` 세그먼트에서 검증 로직을 구현할 수 있습니다. 예를 들어 JS/TS에서는 [Zod][ext-zod]와 같은 스키마 검증 라이브러리를 사용할 수 있습니다: +특히 회원가입의 경우, 사용자가 입력한 내용에 문제가 있을 때 빠르게 피드백을 제공하기 위해 클라이언트 측 검증을 수행하는 것이 좋습니다. 이를 위해 로그인 page의 `model` segment에서 검증 로직을 구현할 수 있습니다. 예를 들어 JS/TS에서는 [Zod][ext-zod]와 같은 스키마 검증 라이브러리를 사용할 수 있습니다: ```ts title="pages/login/model/registration-schema.ts" import { z } from "zod"; @@ -58,7 +58,7 @@ export const registrationData = z.object({ }); ``` -그런 다음, ui 세그먼트에서 이 스키마를 사용하여 사용자 입력을 검증할 수 있습니다: +그런 다음, ui segment에서 이 스키마를 사용하여 사용자 입력을 검증할 수 있습니다: ```tsx title="pages/login/ui/RegisterPage.tsx" import { registrationData } from "../model/registration-schema"; @@ -90,11 +90,11 @@ export function RegisterPage() { ## 로그인 정보 전송 방법 -로그인 정보를 백엔드 서버로 전송하기 위한 요청 함수를 작성하세요. 이 함수는 상태 관리 라이브러리나 뮤테이션 라이브러리(예: TanStack Query)를 사용하여 호출할 수 있습니다. +로그인 정보를 백엔드 서버로 전송하기 위한 요청 함수를 작성하세요. 이 함수는 상태 관리 라이브러리나 mutation 라이브러리(예: TanStack Query)를 사용하여 호출할 수 있습니다. ### 요청 함수 저장 위치 -이 요청 함수를 저장할 수 있는 위치는 크게 두 가지입니다: `shared/api` 또는 페이지의 `api` 세그먼트입니다. +이 요청 함수를 저장할 수 있는 위치는 크게 두 가지입니다: `shared/api` 또는 page의 `api` segment입니다. #### `shared/api`에 저장하기 @@ -108,7 +108,7 @@ export function RegisterPage() { - 📄 client.ts - 📄 index.ts -`📄 client.ts` 파일은 요청을 수행하는 원시 함수(예: `fetch()`)에 대한 래퍼를 포함합니다. 이 래퍼는 백엔드의 기본 URL 설정, 헤더 설정, 데이터 직렬화 등을 처리합니다. +`📄 client.ts` 파일은 요청을 수행하는 원시 함수(예: `fetch()`)에 대한 wrapper를 포함합니다. 이 wrapper는 백엔드의 기본 URL 설정, 헤더 설정, 데이터 직렬화 등을 처리합니다. ```ts title="shared/api/endpoints/login.ts" import { POST } from "../client"; @@ -122,9 +122,9 @@ export function login({ email, password }: { email: string, password: string }) export { login } from "./endpoints/login"; ``` -#### 페이지의 `api` 세그먼트에 저장하기 +#### page의 `api` segment에 저장하기 -로그인 요청이 특정 페이지에만 필요한 경우, 로그인 페이지의 `api` 세그먼트에 함수를 저장할 수 있습니다: +로그인 요청이 특정 page에만 필요한 경우, 로그인 page의 `api` segment에 함수를 저장할 수 있습니다: - 📂 pages - 📂 login @@ -143,15 +143,15 @@ export function login({ email, password }: { email: string, password: string }) } ``` -이 함수는 페이지의 공개 API에서 내보낼 필요가 없습니다. 로그인 요청이 다른 곳에서 필요할 가능성이 낮기 때문입니다. +이 함수는 page의 공개 API에서 내보낼 필요가 없습니다. 로그인 요청이 다른 곳에서 필요할 가능성이 낮기 때문입니다. ### 이중 인증(2FA) -앱이 이중 인증(2FA)을 지원하는 경우, 사용자가 일회용 비밀번호(OTP)를 입력할 수 있는 별도의 페이지로 이동해야 할 수 있습니다. 일반적으로 `POST /login` 요청은 사용자가 2FA를 활성화했음을 나타내는 플래그가 포함된 사용자 객체를 반환합니다. 이 플래그가 설정되면 사용자를 2FA 페이지로 리디렉션해야 합니다. +앱이 이중 인증(2FA)을 지원하는 경우, 사용자가 일회용 비밀번호(OTP)를 입력할 수 있는 별도의 page로 이동해야 할 수 있습니다. 일반적으로 `POST /login` 요청은 사용자가 2FA를 활성화했음을 나타내는 플래그가 포함된 사용자 객체를 반환합니다. 이 플래그가 설정되면 사용자를 2FA page로 리디렉션해야 합니다. -2FA 페이지는 로그인과 밀접하게 연관되어 있으므로 Pages 레이어의 `login` 슬라이스에 함께 저장하는 것이 좋습니다.
+2FA page는 로그인과 밀접하게 연관되어 있으므로 Pages layer의 `login` slice에 함께 저장하는 것이 좋습니다.
-이중 인증을 처리하기 위해서는 `login()` 함수와 유사한 또 다른 요청 함수가 필요할 것입니다. 이러한 함수들은 `Shared`나 로그인 페이지의 `api` 세그먼트에 함께 배치할 수 있습니다. +이중 인증을 처리하기 위해서는 `login()` 함수와 유사한 또 다른 요청 함수가 필요할 것입니다. 이러한 함수들은 `Shared`나 로그인 page의 `api` segment에 함께 배치할 수 있습니다. ## 인증된 요청의 토큰 저장 방법 {#how-to-store-the-token-for-authenticated-requests} @@ -163,7 +163,7 @@ export function login({ email, password }: { email: string, password: string }) ### Shared에 저장하기 -`shared/api`에 저장하는 접근 방식은 API 클라이언트와 잘 맞아떨어집니다. 인증이 필요한 다른 요청 함수에서 이 토큰을 쉽게 사용할 수 있기 때문입니다. API 클라이언트에서 반응형 스토어나 모듈 수준 변수를 사용해 토큰을 저장하고, `login()/logout()` 함수에서 해당 상태를 업데이트할 수 있습니다. +`shared/api`에 저장하는 접근 방식은 API 클라이언트와 잘 맞아떨어집니다. 인증이 필요한 다른 요청 함수에서 이 토큰을 쉽게 사용할 수 있기 때문입니다. API 클라이언트에서 반응형 store나 module 수준 변수를 사용해 토큰을 저장하고, `login()/logout()` 함수에서 해당 상태를 업데이트할 수 있습니다. 토큰 자동 갱신은 API 클라이언트에서 미들웨어 형태로 구현할 수 있습니다. 모든 요청마다 실행되며, 아래와 같은 방식으로 동작합니다: @@ -177,7 +177,7 @@ export function login({ email, password }: { email: string, password: string }) ### Entities에 저장하기 -FSD 프로젝트에서는 사용자 엔티티 또는 현재 사용자 엔티티를 사용하는 것이 일반적입니다. 두 엔티티는 같은 것을 가리킬 수도 있습니다. +FSD 프로젝트에서는 user entity 또는 current user entity를 사용하는 것이 일반적입니다. 두 entity는 같은 것을 가리킬 수도 있습니다. :::note @@ -185,41 +185,41 @@ FSD 프로젝트에서는 사용자 엔티티 또는 현재 사용자 엔티티 ::: -User 엔티티에 토큰을 저장하려면 `model` 세그먼트에 반응형 스토어를 생성해야 합니다. 이 스토어는 토큰과 사용자 객체를 모두 포함할 수 있습니다. +User entity에 토큰을 저장하려면 `model` segment에 reactive store를 생성해야 합니다. 이 store는 토큰과 user 객체를 모두 포함할 수 있습니다. -API 클라이언트는 일반적으로 `shared/api` 정의되거나 엔티티 전체에 분산되어 있습니다. 따라서 주요 과제는 레이어의 임포트 규칙([import rule on layers][import-rule-on-layers])을 위반하지 않으면서 다른 요청에서도 토큰을 사용할 수 있도록 하는 것입니다. +API 클라이언트는 일반적으로 `shared/api` 정의되거나 entity 전체에 분산되어 있습니다. 따라서 주요 과제는 layer의 임포트 규칙([import rule on layers][import-rule-on-layers])을 위반하지 않으면서 다른 요청에서도 토큰을 사용할 수 있도록 하는 것입니다. -> 레이어 규칙: 슬라이스의 모듈은 자기보다 낮은 레이어에 위치한 다른 슬라이스만 임포트할 수 있습니다. +> Layer 규칙: slice의 module은 자기보다 낮은 layer에 위치한 다른 slice만 import할 수 있습니다. 이 문제를 해결하기 위한 몇 가지 방법은 다음과 같습니다: 1. **요청 시마다 토큰 수동 전달** 이 방법은 가장 간단하지만, 번거롭고 타입 안전성이 보장되지 않으면 실수가 발생할 가능성이 큽니다. 또한 Shared의 API 클라이언트에 미들웨어 패턴을 적용하기 어렵습니다. -2. **앱 전역에서 글로벌 스토어로 토큰 관리** - 토큰을 context나 `localStorage`에 저장하고, `shared/api`에 토큰 접근 키를 보관합니다. 토큰의 반응형 저장소는 User 엔터티에서 내보내며, 필요한 경우 context Provider는 App 레이어에서 설정합니다. 이 방법은 API 클라이언트 설계를 유연하게 만들지만, 상위 레이어에 context 제공이 필요하다는 암묵적인 의존성을 발생시킵니다. 따라서 context나 `localStorage`가 제대로 설정되지 않았을 경우, 유용한 오류 메시지를 제공하는 것이 좋습니다. +2. **앱 전역에서 글로벌 store로 토큰 관리** + 토큰을 context나 `localStorage`에 저장하고, `shared/api`에 토큰 접근 키를 보관합니다. 토큰의 reactive store는 User entity에서 내보내며, 필요한 경우 context Provider는 App layer에서 설정합니다. 이 방법은 API 클라이언트 설계를 유연하게 만들지만, 상위 layer에 context 제공이 필요하다는 암묵적인 의존성을 발생시킵니다. 따라서 context나 `localStorage`가 제대로 설정되지 않았을 경우, 유용한 오류 메시지를 제공하는 것이 좋습니다. 3. **토큰 변경 시 API 클라이언트 업데이트** - 반응형 스토어를 활용해 엔티티의 스토어가 변경될 때마다 API 클라이언트의 토큰 스토어를 업데이트하는 구독(subscribe)을 생성할 수 있습니다. 이 방법은 상위 계층에 암묵적인 의존성을 만든다는 점에서는 이전 해결책과 비슷하지만, 이 방법은 더 "명령형(push)" 접근이고, 이전 방법은 더 "선언형(pull)" 접근입니다. + reactive store를 활용해 entity의 store가 변경될 때마다 API 클라이언트의 토큰 store를 업데이트하는 구독(subscribe)을 생성할 수 있습니다. 이 방법은 상위 layer에 암묵적인 의존성을 만든다는 점에서는 이전 해결책과 비슷하지만, 이 방법은 더 "명령형(push)" 접근이고, 이전 방법은 더 "선언형(pull)" 접근입니다. -엔티티의 `model`에 토큰을 저장하여 문제를 해결하면, 토큰 관리와 관련된 더 많은 비즈니스 로직을 추가할 수 있습니다. 예를 들어, `model` 세그먼트에 토큰 만료 시 갱신하는 로직을 추가하거나, 일정 시간이 지나면 토큰을 무효화하는 로직을 포함할 수 있습니다. -백엔드에 요청을 보내야 하는 경우에는 User 엔티티의 api 세그먼트나 `shared/api`를 사용할 수 있습니다. +entity의 `model` segment에 토큰을 저장하여 문제를 해결하면, 토큰 관리와 관련된 더 많은 비즈니스 로직을 추가할 수 있습니다. 예를 들어, `model` segment에 토큰 만료 시 갱신하는 로직을 추가하거나, 일정 시간이 지나면 토큰을 무효화하는 로직을 포함할 수 있습니다. +백엔드에 요청을 보내야 하는 경우에는 User entity의 api segment나 `shared/api`를 사용할 수 있습니다. ### Pages/Widgets에 저장하기 (권장하지 않음) -애플리케이션 전역에 적용되는 상태(예: 액세스 토큰)를 페이지나 위젯에 저장하는 것은 권장되지 않습니다. 예를 들어, 로그인 페이지의 `model` 세그먼트에 토큰 스토어를 배치하는 대신, 이 아티클에서 제시한 처음 두 해결책인 Shared나 Entities를 사용하는 것이 권장됩니다. +애플리케이션 전역에 적용되는 상태(예: 액세스 토큰)를 page나 widget에 저장하는 것은 권장되지 않습니다. 예를 들어, 로그인 page의 `model` segment에 토큰 store를 배치하는 대신, 이 아티클에서 제시한 처음 두 해결책인 Shared나 Entities를 사용하는 것이 권장됩니다. ## 로그아웃 및 토큰 무효화 -로그아웃 기능은 애플리케이션에서 중요한 기능이지만, 이를 위한 별도의 페이지는 없는 경우가 많습니다. 이 기능은 백엔드에 인증된 요청을 보내고, 토큰 스토어를 업데이트하는 작업으로 구성됩니다. +로그아웃 기능은 애플리케이션에서 중요한 기능이지만, 이를 위한 별도의 page는 없는 경우가 많습니다. 이 기능은 백엔드에 인증된 요청을 보내고, 토큰 store를 업데이트하는 작업으로 구성됩니다. -모든 요청을 `shared/api`에 보관했다면, 로그인 함수 근처에 로그아웃 요청 함수를 두는 것이 좋습니다. 그렇지 않은 경우, 로그아웃 버튼이 있는 위치 근처에 로그아웃 요청 함수를 배치할 수 있습니다. 예를 들어, 모든 페이지에 나타나는 헤더 위젯에 로그아웃 링크가 있다면, 해당 요청을 그 위젯의 `api` 세그먼트에 배치하는 것이 좋습니다. +모든 요청을 `shared/api`에 보관했다면, 로그인 함수 근처에 로그아웃 요청 함수를 두는 것이 좋습니다. 그렇지 않은 경우, 로그아웃 버튼이 있는 위치 근처에 로그아웃 요청 함수를 배치할 수 있습니다. 예를 들어, 모든 page에 나타나는 header widget에 로그아웃 링크가 있다면, 해당 요청을 그 widget의 `api` segment에 배치하는 것이 좋습니다. -토큰 스토어에 대한 업데이트는 로그아웃 버튼이 위치한 곳(예: 헤더 위젯)에서 트리거되어야 합니다. 이 요청과 스토어 업데이트를 해당 위젯의 `model` 세그먼트에서 결합할 수 있습니다. +토큰 store에 대한 업데이트는 로그아웃 버튼이 위치한 곳(예: header widget)에서 트리거되어야 합니다. 이 요청과 store 업데이트를 해당 widget의 `model` segment에서 결합할 수 있습니다. ### 자동 로그아웃 -로그아웃 요청 실패나 로그인 토큰 갱신 실패 시를 대비해 안전장치를 마련하는 것도 중요합니다. 이 두 경우 모두 토큰 스토어를 비워야 합니다. 토큰을 Entities에 저장하는 경우, 이 로직은 `model` 세그먼트에 배치할 수 있습니다. 토큰을 Shared에 저장하는 경우, 이 로직을 `shared/api`에 포함하면 세그먼트가 너무 복잡해질 수 있습니다. 따라서 토큰 관리 로직을 별도의 세그먼트(예: `shared/auth`)로 분리하는 것도 고려해볼 만합니다. +로그아웃 요청 실패나 로그인 토큰 갱신 실패 시를 대비해 안전장치를 마련하는 것도 중요합니다. 이 두 경우 모두 토큰 store를 비워야 합니다. 토큰을 Entities에 저장하는 경우, 이 로직은 `model` segment에 배치할 수 있습니다. 토큰을 Shared에 저장하는 경우, 이 로직을 `shared/api`에 포함하면 segment가 너무 복잡해질 수 있습니다. 따라서 토큰 관리 로직을 별도의 segment(예: `shared/auth`)로 분리하는 것도 고려해볼 만합니다. [tutorial-authentication]: /docs/get-started/tutorial#authentication [import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers [ext-remix]: https://remix.run -[ext-zod]: https://zod.dev +[ext-zod]: https://zod.dev \ No newline at end of file diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md index df57fa83f..fc23d4340 100644 --- a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md @@ -4,7 +4,7 @@ sidebar_position: 3 # Page layouts -이 가이드는 여러 페이지가 같은 기본 구조를 공유하고, 주요 내용만 다른 경우 사용할 수 있는 _페이지 레이아웃_ 에 대해 설명합니다. +이 가이드는 여러 page가 같은 기본 구조를 공유하고, 주요 내용만 다른 경우 사용할 수 있는 _page layout_ 에 대해 설명합니다. :::info @@ -14,7 +14,7 @@ sidebar_position: 3 ## 간단한 레이아웃 -간단한 레이아웃 예시로 설명 해 보겠습니다. 이 페이지는 사이트 내비게이션이 포함된 헤더, 두 개의 사이드바, 외부 링크가 포함된 푸터로 구성되어 있습니다. 복잡한 비즈니스 로직은 없으며, 동적인 부분은 사이드바와 헤더 오른쪽에 있는 테마 전환 버튼뿐입니다. 이러한 레이아웃은 shared/ui 또는 app/layouts에 포함시킬 수 있으며, props를 통해 전달받은 사이드바 콘텐츠를 표시합니다. +간단한 layout 예시로 설명 해 보겠습니다. 이 page는 사이트 내비게이션이 포함된 헤더, 두 개의 사이드바, 외부 링크가 포함된 푸터로 구성되어 있습니다. 복잡한 비즈니스 로직은 없으며, 동적인 부분은 사이드바와 헤더 오른쪽에 있는 테마 전환 버튼뿐입니다. 이러한 layout은 shared/ui 또는 app/layouts에 포함시킬 수 있으며, props를 통해 전달받은 사이드바 콘텐츠를 표시합니다. ```tsx title="shared/ui/layout/Layout.tsx" import { Link, Outlet } from "react-router-dom"; @@ -74,26 +74,26 @@ export function useThemeSwitcher() { 상황에 따라 layout에 특정 비즈니스 로직을 추가하고 싶을 때가 있습니다. 특히 [React Router][ext-react-router]와 같은 라우터를 사용해 깊이 중첩된 경로를 다룰 때 이러한 요구가 발생합니다. 이러한 경우 layout을 shared나 widgets 폴더에 두는 것이 어려울 수 있습니다. 이는 [layer에 대한 import 규칙][import-rule-on-layers] 때문입니다: -> slice의 module은 자신보다 하위 layers에 위치한 다른 slice만 import할 수 있습니다. +> slice의 module은 자신보다 하위 layer에 위치한 다른 slice만 import할 수 있습니다. -이 문제가 정말 중요한지 먼저 고려해 봐야 합니다. 레이아웃이 _정말로 필요한가요?_ 그리고 그 레이아웃이 _정말로 widget이어야 할까요?_ 만약 해당 비즈니스 로직이 2-3개의 페이지에서만 사용되고, 레이아웃이 그 widget을 감싸는 역할이라면, 다음 두 가지 방법을 고려해 보세요: +이 문제가 정말 중요한지 먼저 고려해 봐야 합니다. layout이 _정말로 필요한가요?_ 그리고 그 layout이 _정말로 widget이어야 할까요?_ 만약 해당 비즈니스 로직이 2-3개의 page에서만 사용되고, layout이 그 widget을 감싸는 역할이라면, 다음 두 가지 방법을 고려해 보세요: -1. **App 레이어에서 인라인으로 레이아웃 작성하기** - App 레이어에서 직접 레이아웃을 정의하는 것이 좋습니다. 이렇게 하면 중첩된 라우터를 사용할 때 특정 경로 그룹에만 해당 레이아웃을 적용할 수 있어 유연하게 사용할 수 있습니다. +1. **App layer에서 인라인으로 layout 작성하기** + App layer에서 직접 layout을 정의하는 것이 좋습니다. 이렇게 하면 중첩된 라우터를 사용할 때 특정 경로 그룹에만 해당 layout을 적용할 수 있어 유연하게 사용할 수 있습니다. 2. **복사하여 붙여넣기** - 코드 추상화는 항상 좋은 선택은 아닙니다. 특히 레이아웃은 자주 변경되지 않기 때문에, 필요한 경우 해당 페이지만 수정하는 것이 더 효율적일 수 있습니다. 이렇게 하면 다른 페이지에 영향을 주지 않고 수정할 수 있습니다. 팀원들이 다른 페이지를 수정하는 걸 잊을까 봐 걱정된다면, 페이지 간의 관계를 주석으로 남겨보세요. 큰 프로젝트에서도 협업이 더 편해질 거예요. + 코드 추상화는 항상 좋은 선택은 아닙니다. 특히 layout은 자주 변경되지 않기 때문에, 필요한 경우 해당 page만 수정하는 것이 더 효율적일 수 있습니다. 이렇게 하면 다른 page에 영향을 주지 않고 수정할 수 있습니다. 팀원들이 다른 page를 수정하는 걸 잊을까 봐 걱정된다면, page 간의 관계를 주석으로 남겨보세요. 큰 프로젝트에서도 협업이 더 편해질 거예요. -위의 내용이 적절하지 않은 경우, 레이아웃에 widget을 포함하는 두 가지 해결책이 있습니다: +위의 내용이 적절하지 않은 경우, layout에 widget을 포함하는 두 가지 해결책이 있습니다: 1. **render props나 slots 사용하기** 대부분의 프레임워크에서는 컴포넌트 내부에 표시될 UI 요소를 외부에서 전달할 수 있는 기능을 제공합니다. React에서는 [render props][ext-render-props]라고 하며, Vue에서는 [slots][ext-vue-slots]이라고 부릅니다. -2. **레이아웃을 App 레이어로 이동하기** - 레이아웃을 `app/layouts` 등 App 레이어에 저장하고 원하는 widget을 구성할 수도 있습니다. +2. **layout을 App layer로 이동하기** + layout을 `app/layouts` 등 App layer에 저장하고 원하는 widget을 구성할 수도 있습니다. ## 추가 자료 -React 및 Remix(React Router와 유사)의 인증 레이아웃 구축에 대한 예시는 [튜토리얼][tutorial]에서 확인하실 수 있습니다. +React 및 Remix(React Router와 유사)의 인증 layout 구축에 대한 예시는 [튜토리얼][tutorial]에서 확인하실 수 있습니다. [tutorial]: /docs/get-started/tutorial diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md index 5b07e9106..12519e26c 100644 --- a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md +++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md @@ -40,18 +40,18 @@ type ArrayValues = T[number]; :::warning -`shared/types` 폴더를 생성하거나 각 슬라이스에 `types`라는 세그먼트를 추가하고 싶은 마음이 들 수 있지만, 그렇게 하지 않는 것이 좋습니다.
-`types`라는 카테고리는 `components`나 `hooks`와 마찬가지로 내용이 무엇인지를 설명할 뿐, 코드의 목적을 명확히 설명하지 않습니다. 슬라이스는 해당 코드의 목적을 정확히 설명할 수 있어야 합니다. +`shared/types` 폴더를 생성하거나 각 slice에 `types`라는 segment를 추가하고 싶은 마음이 들 수 있지만, 그렇게 하지 않는 것이 좋습니다.
+`types`라는 카테고리는 `components`나 `hooks`와 마찬가지로 내용이 무엇인지를 설명할 뿐, 코드의 목적을 명확히 설명하지 않습니다. slice는 해당 코드의 목적을 정확히 설명할 수 있어야 합니다. ::: -## 비즈니스 엔티티 및 상호 참조 관계 +## 비즈니스 entity 및 상호 참조 관계 -앱에서 가장 중요한 타입 중 하나는 비즈니스 엔티티, 즉 앱에서 다루는 객체들 입니다. -예를 들어, 음악 스트리밍 앱에서는 _Song_, _Album_ 등이 비즈니스 엔티티가 될 수 있습니다. +앱에서 가장 중요한 타입 중 하나는 비즈니스 entity, 즉 앱에서 다루는 객체들 입니다. +예를 들어, 음악 스트리밍 앱에서는 _Song_, _Album_ 등이 비즈니스 entity가 될 수 있습니다. -비즈니스 엔티티는 주로 백엔드 바탕이기 떄문에, 백엔드 응답을 타입으로 정의하는 것이 첫 번째 단계입니다. -각 엔드포인트에 대한 요청 함수와 그 응답을 타입으로 지정하는 것이 좋습니다, 추가적인 타입 안정성을 위해 [Zod][ext-zod]와 같은 스키마 검증 라이브러리를 사용해 응답을 검증할 수도 있습니다. +비즈니스 entity는 주로 백엔드 바탕이기 떄문에, 백엔드 응답을 타입으로 정의하는 것이 첫 번째 단계입니다. +각 endpoint에 대한 요청 함수와 그 응답을 타입으로 지정하는 것이 좋습니다, 추가적인 타입 안정성을 위해 [Zod][ext-zod]와 같은 스키마 검증 라이브러리를 사용해 응답을 검증할 수도 있습니다. 예를 들어, 모든 요청을 Shared에 보관하는 경우 이렇게 작성할 수 있습니다. @@ -69,14 +69,14 @@ export function listSongs() { } ``` -`Song` 타입은 다른 엔티티인 `Artist`를 참조합니다. 이와 같이 요청 관련 코드들을 Shared에 관리하면, 타입들의 서로 얽혀 있을 떄 관리가 용이해집니다. 만약 이 함수를 `entities/song/api`에 보관했다면, `entities/artist`에서 간단히 가져오는 것이 어려웠을 것 입니다. FSD 구조에서는 [레이어별 import 규칙][import-rule-on-layers]을 통해 슬라이스 간의 교차 import를 제한하고 있기 떄문입니다: +`Song` 타입은 다른 entity인 `Artist`를 참조합니다. 이와 같이 요청 관련 코드들을 Shared에 관리하면, 타입들의 서로 얽혀 있을 떄 관리가 용이해집니다. 만약 이 함수를 `entities/song/api`에 보관했다면, `entities/artist`에서 간단히 가져오는 것이 어려웠을 것 입니다. FSD 구조에서는 [layer별 import 규칙][import-rule-on-layers]을 통해 slice 간의 교차 import를 제한하고 있기 떄문입니다: -> 슬라이스 안에 있는 모듈은 계층적으로 더 낮은 레이어에 위치한 슬라이스만 가져올 수 있습니다. +> slice 안에 있는 module은 계층적으로 더 낮은 layer에 위치한 slice만 가져올 수 있습니다. 이 문제를 해결하기 위한 두 가지 방법은 다음과 같습니다: 1. **타입 매개변수화** - 타입이 다른 엔티티와 연결될 때, 타입 매개변수를 통해 처리할 수 있습니다. 예를 들어, Song 타입에 ArtistType이라는 제약 조건을 설정할 수 있습니다. + 타입이 다른 entity와 연결될 때, 타입 매개변수를 통해 처리할 수 있습니다. 예를 들어, Song 타입에 ArtistType이라는 제약 조건을 설정할 수 있습니다. ```ts title="entities/song/model/song.ts" interface Song { @@ -89,7 +89,7 @@ export function listSongs() { 이 방법은 일부 타입에 더 적합합니다. 예를 들어, `Cart = { items: Array }`처럼 간단한 타입은 다양한 제품 타입을 지원하기 쉽게 할 수 있습니다. 하지만 `Country`와 `City`처럼 더 밀접하게 연결된 타입은 분리하기 어렵습니다. 2. **Cross-import (공개 API를 사용해 관리하기)** - FSD에서 엔티티 간 cross-imports를 허용하기 위해서는 공개 API를 사용할 수 있습니다. 예를 들어, `song`, `artist`, `playlist`라는 엔티티가 있고, 후자의 두 엔티티가 `song`을 참조해야 한다고 가정합니다. 이 경우, `song` 엔티티 내에 `artist`와 `playlist`용 공개 API를 따로 `@x` 표기를 만들어 사용할 수 있습니다. + FSD에서 entity 간 cross-imports를 허용하기 위해서는 공개 API를 사용할 수 있습니다. 예를 들어, `song`, `artist`, `playlist`라는 entity가 있고, 후자의 두 entity가 `song`을 참조해야 한다고 가정합니다. 이 경우, `song` entity 내에 `artist`와 `playlist`용 공개 API를 따로 `@x` 표기를 만들어 사용할 수 있습니다. - 📂 entities - 📂 song @@ -115,11 +115,11 @@ export function listSongs() { } ``` - 이렇게 엔티티 간 명시적으로 연결을 해두면 의존 관계를 파악하고 도메인 분리 수준을 유지하기 쉬워집니다. + 이렇게 entity 간 명시적으로 연결을 해두면 의존 관계를 파악하고 도메인 분리 수준을 유지하기 쉬워집니다. ## 데이터 전송 객체와 mappers {#data-transfer-objects-and-mappers} -데이터 전송 객체(Data Transfer Object, DTO)는 백엔드에서 오는 데이터의 구조를 나타내는 용어입니다. 떄로는 DTO를 그대로 사용하는 것이 편리할 수 있지만, 경우에 따라 프론트엔드에서는 불편할 수 있습니다. 이때 매퍼를 사용해 DTO를 더 편리한 형태로 변환합니다. +데이터 전송 객체(Data Transfer Object, DTO)는 백엔드에서 오는 데이터의 구조를 나타내는 용어입니다. 떄로는 DTO를 그대로 사용하는 것이 편리할 수 있지만, 경우에 따라 프론트엔드에서는 불편할 수 있습니다. 이때 mapper를 사용해 DTO를 더 편리한 형태로 변환합니다. ### DTO의 위치 @@ -145,7 +145,7 @@ export function listSongs() { ### Mappers의 위치 -Mappers는 DTO를 받아 변환하는 역할을 하므로, DTO 정의와 가까운 위치에 두는 것이 좋습니다. 만약 요청과 DTO가 `shared/api`에 정의되어 있다면, mappers도 그곳에 위치하는 것이 적절합니다. +Mapper는 DTO를 받아 변환하는 역할을 하므로, DTO 정의와 가까운 위치에 두는 것이 좋습니다. 만약 요청과 DTO가 `shared/api`에 정의되어 있다면, mapper도 그곳에 위치하는 것이 적절합니다. ```ts title="shared/api/songs.ts" import type { ArtistDTO } from "./artists"; @@ -179,7 +179,7 @@ export function listSongs() { } ``` -요청과 상태 관리 코드가 엔티티 슬라이스에 정의되어 있는 경우, mappers 역시 해당 슬라이스 내에 두는 것이 좋습니다. 이때 슬라이스 간 교차 참조가 발생하지 않도록 주의해야 합니다. +요청과 상태 관리 코드가 entity slice에 정의되어 있는 경우, mapper 역시 해당 slice 내에 두는 것이 좋습니다. 이때 slice 간 교차 참조가 발생하지 않도록 주의해야 합니다. ```ts title="entities/song/api/dto.ts" import type { ArtistDTO } from "entities/artist/@x/song"; @@ -243,7 +243,7 @@ const songsSlice = createSlice({ ### 중첩된 DTO 처리 방법 -백엔드 응답에 여러 엔티티가 포함된 경우 문제가 될 수 있습니다. 예를 들어, 곡 정보에 저자의 ID뿐만 아니라 저자 객체 전체가 포함된 경우가 있을 수 있습니다. 이런 상황에서는 엔티티 간의 상호 참조를 피하기 어렵습니다. 데이터를 지우거나 백엔드 팀과 협의하지 않는 한, 이러한 경우에는 슬라이스 간 간접적인 연결 대신 명시적인 교차 참조를 사용하는 것이 좋습니다. 이를 위해 `@x` 표기법을 활용할 수 있으며, 다음은 Redux Toolkit을 사용한 예시입니다: +백엔드 응답에 여러 entity가 포함된 경우 문제가 될 수 있습니다. 예를 들어, 곡 정보에 저자의 ID뿐만 아니라 저자 객체 전체가 포함된 경우가 있을 수 있습니다. 이런 상황에서는 entity 간의 상호 참조를 피하기 어렵습니다. 데이터를 지우거나 백엔드 팀과 협의하지 않는 한, 이러한 경우에는 slice 간 간접적인 연결 대신 명시적인 교차 참조를 사용하는 것이 좋습니다. 이를 위해 `@x` 표기법을 활용할 수 있으며, 다음은 Redux Toolkit을 사용한 예시입니다: ```ts title="entities/song/model/songs.ts" import { @@ -317,7 +317,7 @@ const reducer = slice.reducer export default reducer ``` -이 방법은 슬라이스 분리의 이점을 다소 제한할 수 있지만, 우리가 제어할 수 없는 두 엔티티 간의 관계를 명확하게 나타냅니다. 만약 이러한 엔티티가 리팩토링되어야 한다면, 함께 리팩토링해야 할 것입니다. +이 방법은 slice 분리의 이점을 다소 제한할 수 있지만, 우리가 제어할 수 없는 두 entity 간의 관계를 명확하게 나타냅니다. 만약 이러한 entity가 리팩토링되어야 한다면, 함께 리팩토링해야 할 것입니다. ## 전역 타입과 Redux @@ -325,7 +325,7 @@ export default reducer 1. 애플리케이션 특성이 없는 제너릭 타입 2. 애플리케이션 전체에 알고 있어야 하는 타입 -첫 번째 경우에는 관련 타입을 Shared 폴더 안에 적절한 세그먼트로 배치하면 됩니다. 예를 들어, 분석 전역 변수를 위한 인터페이스가 있다면 `shared/analytics`에 두는 것이 좋습니다. +첫 번째 경우에는 관련 타입을 Shared 폴더 안에 적절한 segment로 배치하면 됩니다. 예를 들어, 분석 전역 변수를 위한 인터페이스가 있다면 `shared/analytics`에 두는 것이 좋습니다. :::warning @@ -333,7 +333,7 @@ export default reducer ::: -두 번째 경우는 Redux를 사용하지만 RTK가 없는 프로젝트에서 자주 발생합니다. 최종 스토어 타입은 모든 리듀서를 추가한 후에만 사용 가능하지만, 이 스토어 타입은 앱 전체에서 사용하는 셀렉터에 필요합니다. 예를 들어, 일반적인 스토어 정의는 다음과 같습니다: +두 번째 경우는 Redux를 사용하지만 RTK가 없는 프로젝트에서 자주 발생합니다. 최종 store 타입은 모든 reducer를 추가한 후에만 사용 가능하지만, 이 store 타입은 앱 전체에서 사용하는 selector에 필요합니다. 예를 들어, 일반적인 store 정의는 다음과 같습니다: ```ts title="app/store/index.ts" import { combineReducers, rootReducer } from "redux"; @@ -349,11 +349,11 @@ type RootState = ReturnType; type AppDispatch = typeof store.dispatch; ``` -`shared/store`에서 `useAppDispatch`와 `useAppSelector`와 같은 타입이 지정된 Redux 훅을 사용하는 것이 좋지만, [레이어에 대한 import 규칙][import-rule-on-layers] 떄문에 App 레이어에서 `RootState`와 `AppDispatch`를 import 할 수 없습니다. +`shared/store`에서 `useAppDispatch`와 `useAppSelector`와 같은 타입이 지정된 Redux 훅을 사용하는 것이 좋지만, [layer에 대한 import 규칙][import-rule-on-layers] 때문에 App layer에서 `RootState`와 `AppDispatch`를 import 할 수 없습니다. -> 슬라이스의 모듈은 더 낮은 레이어에 위치한 다른 슬라이스만 import 할 수 있습니다. +> slice의 module은 더 낮은 layer에 위치한 다른 slice만 import 할 수 있습니다. -이 경우 권장되는 해결책은 Shared와 App 레이어 간에 암묵적인 의존성을 만드는 것입니다. `RootState`와 `AppDispatch` 두 타입은 유지보수 필요성이 적고 Redux를 사용하는 개발자들에게 익숙하므로 큰 문제 없이 사용할 수 있습니다. +이 경우 권장되는 해결책은 Shared와 App layer 간에 암묵적인 의존성을 만드는 것입니다. `RootState`와 `AppDispatch` 두 타입은 유지보수 필요성이 적고 Redux를 사용하는 개발자들에게 익숙하므로 큰 문제 없이 사용할 수 있습니다. TypeScript에서는 다음과 같이 타입을 전역으로 선언할 수 있습니다: @@ -375,21 +375,21 @@ export const useAppSelector: TypedUseSelectorHook = useSelector; **일반적으로 열거형(enum)은 사용되는 위치와 최대한 가까운 곳에 정의하는 것이 좋습니다**. 열거형이 특정 기능과 관련된 값을 나타낸다면, 해당 기능 내에 정의해야 합니다. -세그먼트 선택도 사용 위치에 따라 달라져야 합니다. 예를 들어, 화면에서 토스트 위치를 나타내는 열거형이라면 ui 세그먼트에 두는 것이 좋고, 백엔드 응답 상태 등을 나타낸다면 api 세그먼트에 두는 것이 적합합니다. +segment 선택도 사용 위치에 따라 달라져야 합니다. 예를 들어, 화면에서 토스트 위치를 나타내는 열거형이라면 ui segment에 두는 것이 좋고, 백엔드 응답 상태 등을 나타낸다면 api segment에 두는 것이 적합합니다. -프로젝트 전반에서 공통으로 사용되는 열거형도 있습니다. 예를 들어, 일반적인 백엔드 응답 상태나 디자인 시스템 토큰 등이 있습니다. 이 경우 Shared에 두되, 열거형이 나타내는 것을 기준으로 세그먼트를 선택하면 됩니다 (`api`는 응답 상태, `ui`는 디자인 토큰 등). +프로젝트 전반에서 공통으로 사용되는 열거형도 있습니다. 예를 들어, 일반적인 백엔드 응답 상태나 디자인 시스템 토큰 등이 있습니다. 이 경우 Shared에 두되, 열거형이 나타내는 것을 기준으로 segment를 선택하면 됩니다 (`api`는 응답 상태, `ui`는 디자인 토큰 등). ## 타입 검증 스키마와 Zod 데이터가 특정 형태나 제약 조건을 충족하는지 검증하려면 검증 스키마를 정의할 수 있습니다. TypeScript에서는 [Zod][ext-zod]와 같은 라이브러리를 많이 사용합니다. 검증 스키마는 가능하면 사용하는 코드와 같은 위치에 두는 것이 좋습니다. -검증 스키마는 데이터를 파싱하며, 파싱에 실패하면 오류를 발생시킵니다.([Data transfoer objects and mappers](#data-transfer-objects-and-mappers) 토론을 참조하세요.) 가장 일반적인 검증 사례 중 하나는 백엔드에서 오는 데이터에 대한 것입니다. 데이터가 스키마와 일치하지 않는 경우 요청을 실패시키기를 원하기 때문에, 보통 `api` 세그먼트에 스키마를 두는 것이 좋습니다. +검증 스키마는 데이터를 파싱하며, 파싱에 실패하면 오류를 발생시킵니다.([Data transfoer objects and mappers](#data-transfer-objects-and-mappers) 토론을 참조하세요.) 가장 일반적인 검증 사례 중 하나는 백엔드에서 오는 데이터에 대한 것입니다. 데이터가 스키마와 일치하지 않는 경우 요청을 실패시키기를 원하기 때문에, 보통 `api` segment에 스키마를 두는 것이 좋습니다. -사용자 입력(예: 폼)으로 데이터를 받을 경우, 입력된 데이터에 대해 바로 검증이 이루어져야 합니다. 이 경우 스키마를 `ui` 세그먼트 내 폼 컴포넌트 옆에 두거나, `ui` 세그먼트가 너무 복잡하다면 `model` 세그먼트에 둘 수 있습니다. +사용자 입력(예: 폼)으로 데이터를 받을 경우, 입력된 데이터에 대해 바로 검증이 이루어져야 합니다. 이 경우 스키마를 `ui` segment 내 폼 컴포넌트 옆에 두거나, `ui` segment가 너무 복잡하다면 `model` segment에 둘 수 있습니다. ## 컴포넌트 props와 context의 타입 정의 -보통 props나 context 인터페이스는 이를 사용하는 컴포넌트나 컨텍스트와 같은 파일에 두는 것이 가장 좋습니다. 만약 Vue나 Svelte처럼 단일 파일 컴포넌트를 사용하는 프레임워크에서 여러 컴포넌트 간에 해당 인터페이스를 공유해야 한다면, `ui` 세그먼트 내 동일 폴더에 별도의 파일을 만들어 정의할 수 있습니다. +보통 props나 context 인터페이스는 이를 사용하는 컴포넌트나 컨텍스트와 같은 파일에 두는 것이 가장 좋습니다. 만약 Vue나 Svelte처럼 단일 파일 컴포넌트를 사용하는 프레임워크에서 여러 컴포넌트 간에 해당 인터페이스를 공유해야 한다면, `ui` segment 내 동일 폴더에 별도의 파일을 만들어 정의할 수 있습니다. 예를 들어, React의 JSX에서는 다음과 같이 정의합니다: @@ -423,7 +423,7 @@ export interface RecentActionsProps { [Vite][ext-vite]나 [ts-reset][ext-ts-reset] 같은 일부 패키지는 앱 전반에서 작동하기 위해 Ambient 선언 파일을 필요로 합니다. 이러한 파일들은 보통 크거나 복잡하지 않기 때문에 `src/` 폴더에 두어도 괜찮습니다. 더 정리된 구조를 위해 `app/ambient/` 폴더에 두는 것도 좋은 방법입니다. -타이핑이 없는 패키지인 경우, 해당 패키지를 미타입으로 선언하거나 직접 타이핑을 작성할 수 있습니다. 이러한 타이핑을 위한 좋은 위치는 `shared/lib` 폴더 내의 `shared/lib/untyped-packages` 폴더입니다. 이 폴더에 `%LIBRARY_NAME%.d.ts` 파일을 생성하고 필요한 타입을 선언합니다 +타이핑이 없는 패키지인 경우, 해당 패키지를 미타입으로 선언하거나 직접 타이핑을 작성할 수 있습니다. 이러한 타이핑을 위한 좋은 위치는 `shared/lib` 폴더 내의 `shared/lib/untyped-packages` 폴더입니다. 이 폴더에 `%LIBRARY_NAME%.d.ts` 파일을 생성하고 필요한 타입을 선언합니다 ```ts title="shared/lib/untyped-packages/use-react-screenshot.d.ts" // 이 라이브러리는 타입 정의가 없으며 작성하는 것을 생략했습니다.