From 9aaa8064537a5a3a66e20ab19019ce97139eaca3 Mon Sep 17 00:00:00 2001 From: devwoong <66172061+wjlee611@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:17:56 +0900 Subject: [PATCH] docs: add `ko` translations for introduction sections (#4160) Co-authored-by: Felix Angelov --- docs/astro.config.mjs | 2 +- docs/src/content/docs/ko/architecture.mdx | 115 +++++ docs/src/content/docs/ko/bloc-concepts.mdx | 441 ++++++++++++++++++ docs/src/content/docs/ko/faqs.mdx | 194 ++++++++ .../content/docs/ko/flutter-bloc-concepts.mdx | 397 ++++++++++++++++ docs/src/content/docs/ko/getting-started.mdx | 35 ++ docs/src/content/docs/ko/index.mdx | 81 ++++ .../content/docs/ko/naming-conventions.mdx | 82 ++++ docs/src/content/docs/ko/testing.mdx | 64 +++ docs/src/content/docs/ko/why-bloc.mdx | 31 ++ 10 files changed, 1441 insertions(+), 1 deletion(-) create mode 100644 docs/src/content/docs/ko/architecture.mdx create mode 100644 docs/src/content/docs/ko/bloc-concepts.mdx create mode 100644 docs/src/content/docs/ko/faqs.mdx create mode 100644 docs/src/content/docs/ko/flutter-bloc-concepts.mdx create mode 100644 docs/src/content/docs/ko/getting-started.mdx create mode 100644 docs/src/content/docs/ko/index.mdx create mode 100644 docs/src/content/docs/ko/naming-conventions.mdx create mode 100644 docs/src/content/docs/ko/testing.mdx create mode 100644 docs/src/content/docs/ko/why-bloc.mdx diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 40dc29bfb11..155a044e211 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -40,7 +40,7 @@ const locales = { lang: 'ja', }, ko: { - label: '한국인', + label: '한국어', lang: 'ko', }, 'pt-br': { diff --git a/docs/src/content/docs/ko/architecture.mdx b/docs/src/content/docs/ko/architecture.mdx new file mode 100644 index 00000000000..efdecbc77c5 --- /dev/null +++ b/docs/src/content/docs/ko/architecture.mdx @@ -0,0 +1,115 @@ +--- +title: 아키텍쳐 +description: Bloc을 사용할 때 권장되는 아키텍쳐(디자인) 패턴에 대한 개요입니다. +--- + +import DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro'; +import RepositorySnippet from '~/components/architecture/RepositorySnippet.astro'; +import BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro'; +import BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro'; +import BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro'; +import AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro'; +import AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro'; +import PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro'; + +![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png) + +Bloc 라이브러리를 사용하면 애플리케이션을 세 가지 레이어로 분리할 수 있습니다: + +- Presentation +- Business Logic +- Data + - Repository + - Data Provider + +이제 가장 아래 수준(사용자 인터페이스로부터 가장 멀리 떨어진)의 레이어 부터 시작하여 Presentation 레이어까지 작업해 보겠습니다. + +## Data Layer + +Data 레이어는 하나 이상의 소스에서 데이터를 검색/조작하는 책임을 맡습니다. + +따라서 Data 레이어는 두 부분으로 나뉠 수 있습니다: + +- Repository +- Data Provider + +이 레이어는 애플리케이션의 가장 아래 수준이며 데이터베이스, 네트워크 요청 및 기타 비동기 데이터 소스와 상호 작용합니다. + +### Data Provider + +Data Provider는 원시(raw) 데이터를 제공하는 것입니다. Data Provider는 일반적이고 가변적이어야 합니다. + +일반적으로 Data Provider는 간단한 API를 노출하여 [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 작업을 수행합니다. +따라서 Data 레이어의 일부로 `createData`, `readData`, `updateData`, 그리고 `deleteData` 메서드가 있을 수 있습니다. + + + +### Repository + +Repository는 Bloc 계층이 통신하는 하나 이상의 Data Provider를 감싸는 래퍼(Wrapper) 입니다. + + + +보이시는 바와 같이, Repository 계층은 여러 Data Provider와 상호 작용하고, 데이터에 대한 변환을 수행한 후 그 결과를 Business Logic 레이어로 전달할 수 있습니다. + +## Business Logic Layer + +Business Logic 레이어는 Presentation 레이어의 입력에 대해 새로운 상태로 응답하는 책임을 갖습니다. + +Business Logic 레이어를 사용자 인터페이스(Presentation 레이어)와 Data 레이어 사이의 다리라고 생각해도 좋습니다. Business Logic 레이어는 Presentation 레이어로부터 events/actions에 대한 알림을 받은 다음, Repository와 통신하여 Presentation 레이어가 사용할 새 state를 구축합니다. + + + +### Bloc간 통신 + +Bloc은 스트림(Stream)을 노출하기 때문에 다른 bloc을 수신하는 bloc을 만들고 싶은 경우가 있을 수 있겠습니다만, **절대로** 이렇게 하면 안 됩니다. 아래에 소개되는 코드보다 더 나은 대안이 있습니다. + + + +위에 소개된 코드에 오류는 없지만(심지어 스트림 구독 해체 로직도 있지만) 더 큰 문제가 존재합니다: 두 bloc간 종속성을 생성하는 문제 + +일반적으로, 동일한 아키텍쳐 레이어이 있는 두 엔티티(Entity)간 남매 종속성(Sibling dependencies)은 유지보수하기 어려운 긴밀한 결합을 생성하기 때문에 어떤 대가를 치르더라도 반드시 피해야 합니다. Bloc은 Business Logic 아키텍쳐 레이어에 존재하기 때문에 어떠한 bloc도 다른 bloc에 대해 알면 안 됩니다. + +![Application Architecture Layers](~/assets/architecture/architecture.png) + +Bloc은 events와 종속성 주입된 Repository(즉, 생성자에서 bloc에 주입된 repository)를 통해서만 정보를 수신해야 합니다. + +한 bloc이 다른 bloc에 응답해야 하는 상황에 처한 경우 두 가지 다른 대안이 있습니다. 연결 문제를 한 레이어 위로(Presentation 레이어로) 올리거나, 한 레이어 아래로(Domain 레이어로) 내릴 수 있습니다. + +#### Presentation를 통한 bloc 연결 + +`BlocListener`를 사용하여 한 bloc(FirstBloc)을 수신하고, 이 bloc이 변경될 때 마다 다른 bloc(SecondBloc)에 event를 추가할 수 있습니다. + + + +위의 코드는 `SecondBloc`이 `FirstBloc`에 대해 알 필요가 없도록 하여 느슨한 결합을 유도합니다. [flutter_weather](/ko/tutorials/flutter-weather) 애플리케이션은 [이 기법을 사용하여](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42) 수신된 날씨 정보에 따라 앱 테마를 변경합니다. + +어떤 상황에서는 Presentation 레이어에서 두 bloc을 연결하고 싶지 않을 수 있습니다. 이런 경우에는 두 bloc이 동일한 데이터 소스를 공유하고 데이터가 변경될 때 마다 업데이트하는 것이 합리적일 수 있습니다. + +#### Domain을 통한 bloc 연결 + +두 bloc은 Repository에서 Stream을 수신하고 Repository 데이터가 변경될 때 마다 서로 독립적으로 상태를 업데이트 할 수 있습니다. Reactive repository를 사용하여 state를 동기화 하는 것은 대규모 기업체 애플리케이션에서 흔히 볼 수 있습니다. + +우선, 데이터 `Stream`을 제공하는 Repository를 만들거나 사용합니다. 예를 들어, 아래에 소개되는 Repository는 몇 가지 앱 아이디어에 대한 무한히 반복되는 Stream을 노출합니다: + + + +새로운 앱 아이디어에 반응해야 하는 각 bloc에 동일한 Repository를 종속성 주입할 수 있습니다. 아래의 코드는 위의 Repository에서 들어오는 각 앱 아이디어에 대한 state를 yield하는 `AppIdeaRankingBloc`입니다: + + + +Bloc에서 Stream을 사용하는 방법에 대한 자세한 내용은 [Streams 그리고 Concurrency에서 Bloc을 사용하는 방법](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency)을 참조하세요. + +## Presentation Layer + +Presentation 레이어는 하나 이상의 bloc state를 기반하여 렌더링하는 방법을 알아내야 하는 책임을 갖습니다. + +대부분의 애플리케이션 흐름은 애플리케이션이 사용자에게 표시할 일부 데이터를 가져오도록 촉발하는 `AppStart` event로부터 시작합니다. + +이 시나리오에서 Presentation 레이어는 `AppStart` event를 추가합니다. + +또한, Presentation 레이어는 bloc 레이어의 state를 기반으로 화면에 렌더링할 내용을 파악해야 합니다. + + + +지금까지 몇 가지 코드 스니펫을 살펴봤지만, 이 모든것은 상당히 높은 수준이었습니다. 튜토리얼 섹션에서는 여러 가지 예제 앱을 빌드하면서 이 모든 것을 종합해 보겠습니다. diff --git a/docs/src/content/docs/ko/bloc-concepts.mdx b/docs/src/content/docs/ko/bloc-concepts.mdx new file mode 100644 index 00000000000..cbff8f9f022 --- /dev/null +++ b/docs/src/content/docs/ko/bloc-concepts.mdx @@ -0,0 +1,441 @@ +--- +title: 핵심 컨셉 +description: package:bloc의 핵심 개념에 대한 개요입니다. +sidebar: + order: 1 +--- + +import CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro'; +import SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro'; +import StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro'; +import CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro'; +import CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro'; +import CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro'; +import CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro'; +import CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro'; +import CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro'; +import CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro'; +import CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro'; +import CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro'; +import SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro'; +import SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro'; +import SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro'; +import CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro'; +import SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro'; +import CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro'; +import CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro'; +import CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro'; +import CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro'; +import CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro'; +import CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro'; +import CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro'; +import CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro'; +import CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro'; +import CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro'; +import CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro'; +import SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro'; +import SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro'; +import SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro'; +import CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro'; +import SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro'; +import SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro'; +import CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro'; +import CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro'; +import CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro'; +import CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro'; +import AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro'; +import AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro'; +import AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro'; +import DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro'; + +:::note +[`package:bloc`](https://pub.dev/packages/bloc)으로 작업하기 전에 다음 섹션을 주의 깊게 읽어주세요. +::: + +Bloc 패키지 사용 방법을 이해하는 데 중요한 몇 가지 핵심 개념이 있습니다. + +다음 섹션에서는 각 항목에 대해 자세하게 살펴보고 카운터 앱에 적용하는 방법을 살펴보겠습니다. + +## Streams + +:::note +`Streams`에 대한 자세한 내용은 [Dart 문서](https://dart.dev/tutorials/language/streams)를 참조하세요. +::: + +Stream은 연속적인 비동기 데이터입니다. + +Bloc 라이브러리를 사용하려면 `Streams`과 그 작동 방식에 대한 기본적인 이해가 필요합니다. + +만약 당신이 `Streams`이 익숙하지 않다면, 물이 흐르는 파이프를 생각하면 됩니다. 파이프는 `Streams`이고 물은 비동기 데이터 입니다. + +`async*` (비동기 생성기) 함수를 작성하여 Dart에서 `Stream`을 생성할 수 있습니다. + + + +함수를 `async*`로 표시하면 `yield` 키워드를 사용하여 데이터의 `Stream`을 반환할 수 있습니다. 위 예시에서는 `max` 정수 파라미터까지의 정수 `Stream`을 반환하고 있습니다. + +`async*` 함수에서 `yield` 할 때 마다 해당 데이터를 `Stream`을 통해 푸쉬합니다. + +위의 `Stream`을 여러 가지 방법으로 사용할 수 있습니다. 만약 정수로 이루어진 `Stream`의 합계를 반환하는 함수를 작성하고 싶다면 다음과 같이 작성할 수 있습니다: + + + +위의 함수를 `async`로 작성하면 `await` 키워드를 사용하여 정수의 `Future`를 반환할 수 있습니다. 이 예제에서는 Stream의 각 값을 기다렸다가 Stream에 있는 모든 정수의 합을 반환합니다. + +위 모든 코드를 다음과 같이 사용할 수 있습니다: + + + +이제 Dart에서 `Streams`이 어떻게 작동하는지 기본적인 이해를 했으니, Bloc 패키지의 핵심 구성 요소: `Cubit`에 대해 알아볼 준비가 되었습니다. + +## Cubit + +`Cubit`은 `BlocBase`를 extends한 클래스로, 모든 유형의 state를 관리하도록 확장할 수 있습니다. + +![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png) + +`Cubit`은 state의 변경을 촉발하기 위해 호출할 수 있는 함수를 외부로 노출시킬 수 있습니다. + +State는 `Cubit`의 출력이며 애플리케이션 state의 일부를 나타냅니다. UI 컴포넌트는 state에 대한 notify를 받고, 현재 state에 따라 일부를 다시 그릴 수 있습니다. + +:::note +`Cubit`의 기원에 대한 자세한 내용은 [해당 Issue](https://github.com/felangel/cubit/issues/69)에서 확인하세요. +::: + +### Cubit 만들기 + +다음과 같은 `CounterCubit`을 만들 수 있습니다: + + + +`Cubit`을 생성할 때, `Cubit`이 관리할 상태의 타입을 정의해야 합니다. 위의 `CounterCubit`의 경우 state 타입은 `int`로 표현할 수 있지만, 더 복잡한 경우에는 Primitive type 대신 `class`를 사용해야 할 수도 있습니다. + +`Cubit`을 생성할 때 두 번째로 해야 할 일은 초기 상태를 지정하는 것입니다. 초기 상태의 값으로 `super`를 호출하여 이를 수행할 수 있습니다. 위의 예시 코드는 내부적으로 초기 상태를 `0`으로 설정하고 있지만, 다음과 같이 외부의 값을 허용하여 `Cubit`이 더 유연하게 작동하도록 할 수도 있습니다. + + + +이렇게 하면 다음과 같이 다양한 초기 상태를 가진 `CounterCubit` 인스턴스를 만들 수 있습니다. + + + +### Cubit의 state변화 + +각 `Cubit`은 `emit`을 통해 새로운 state를 출력할 수 있습니다. + + + +위의 예시 코드에서 `CounterCubit`은 외부에서 호출하여 `CounterCubit`의 state를 증가시킬 수 있는 `increment` 라는 public 메서드를 노출하고 있습니다. `increment`가 호출되면 `state` getter를 통해 `Cubit`의 현재 state에 접근하고, 현재 상태에 1을 더하여 새로운 state를 `emit`할 수 있습니다. + +:::caution +`emit` 메서드는 protected 이므로 `Cubit` 내부에서만 사용해야 합니다. +::: + +### Cubit 사용하기 + +이제 우리가 구현한 `CounterCubit`을 실제로 사용할 수 있습니다! + +#### 기본 사용법 + + + +위의 예시 코드에서는 먼저 `CounterCubit`의 인스턴스를 생성합니다. 그런 다음 초기 state인 Cubit의 현재 state를 출력합니다 (아직 새로운 state가 emit되지 않았으므로). 다음으로 `increment` 함수를 호출하여 state의 변경을 촉발합니다. 마지막으로 `0`에서 `1`로 바뀐 `Cubit`의 state를 다시 출력하고 `Cubit`의 `close`를 호출하여 내부 state stream을 닫습니다. + +#### Stream 사용법 + +`Cubit`은 실시간 state 업데이트를 받을 수 있는 `Stream`을 노출합니다: + + + +위의 예시 코드에서는 `CounterCubit`을 구독하고 각 state 변경 시마다 print를 호출하고 있습니다. 그런 다음 새로운 state를 출력하는 `increment` 함수를 호출하고 있습니다. 마지막으로, 더 이상 업데이트를 받고 싶지 않을 때 `subscription`의 `cancel`을 호출하고 `Cubit`을 닫습니다. + +:::note +해당 예제에서는 구독이 즉시 취소되지 않도록 `await Future.delayed(Duration.zero)`을 추가했습니다. +::: + +:::caution +`Cubit`에서 `listen`을 호출할 때는 후속 상태 변경 사항만을 수신합니다. +::: + +### Cubit 관찰하기 + +`Cubit`이 새로운 state를 emit하면 `Change`가 발생합니다. `onChange`를 override하여 주어진 `Cubit`에 대한 모든 변경 사항을 관찰할 수 있습니다. + + + +그런 다음 `Cubit`과 상호 작용하고, 콘솔로 출력되는 모든 변경 사항을 관찰해봅시다. + + + +위 예시는 다음과 같이 출력됩니다: + + + +:::note +`Change`는 `Cubit`의 state가 업데이트되기 직전에 발생합니다. `Change`는 `currentState`와 `nextState`로 구성됩니다. +::: + +#### BlocObserver + +Bloc 라이브러리를 사용하면 한 곳에서 모든 `Change`에 접근할 수 있다는 장점이 있습니다. 이 애플리케이션은 하나의 `Cubit`만 있지만, 대규모 애플리케이션에서는 애플리케이션 state의 여러 부분을 관리하는 많은 `Cubit`을 사용하는 것이 일반적입니다. + +모든 `Change`에 대응하여 무언가를 할 수 있도록 하여면 자체적으로 `BlocObserver`를 만들면 됩니다. + + + +:::note +`BlocObserver`를 extends 하고 `onChange`메서드를 override 하기만 하면 됩니다. +::: + +`SimpleBlocObserver`를 사용하려면 `main`함수를 조금만 수정하면 됩니다: + + + +위 예시 코드에 대한 출력입니다: + + + +:::note +내부의 `onChange` override가 먼저 호출되어 `BlocObserver`의 `onChange`를 알리는 `super.onChange`를 호출합니다. +::: + +:::tip +`BlocObserver`에서는 `Change` 그 자체 외에도 `Cubit` 인스턴스에 접근살 수 있습니다. +::: + +### Cubit의 에러 처리 + +모든 `Cubit`에는 에러가 발생했음을 나타내는데 사용할 수 있는 `addError` 메서드가 있습니다. + + + +:::note +특정 `Cubit`에 대한 모든 에러를 처리하기 위해 `onError`를 `Cubit` 내에서 override할 수 있습니다. +::: + +`BlocObserver`에서 `onError`를 override하여 보고된 모든 에러를 전역적으로 처리할 수도 있습니다. + + + +동일한 프로그램을 다시 실행하면 다음과 같은 출력을 볼 수 있습니다: + + + +:::note +`onChange`와 마찬가지로, 내부 `onError` override는 전역 `BlocObserver` override보다 먼저 호출됩니다. +::: + +## Bloc + +`Bloc`은 함수가 아닌 `event`에 의존하여 `state` 변경을 촉발하는 고급 클래스 입니다. `Bloc`은 또한 `BlocBase`를 extends하여 `Cubit`과 유사한 공용 API를 갖고 있습니다. 그러나, `Bloc`에서 `함수`를 호출하여 새로운 `state`를 직접 emit하는 대신, `Bloc`은 `event`를 수신하고 수신된 `event`를 나가는 `state`로 변환합니다. + +![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png) + +### Bloc 만들기 + +`Bloc`을 생성하는 것은 `Cubit`를 생성하는 것과 비슷하지만, 관라할 state를 정의하는 것 외에 `Bloc`이 처리할 event도 정의해야 한다는 점이 다릅니다. + +Event는 Bloc에 대한 입력입니다. 일반적으로 버튼을 누름과 같은 사용자 상호 작용이나, 페이지 로드와 같은 생명 주기 이벤트에 대한 응답으로 추가됩니다. + + + +`CounterCubit`을 생성할 때와 마찬가지로 `super`를 통해 부모 클래스에 전달하여 초기 state를 지정해야 합니다. + +### Bloc의 state변화 + +`Bloc`은 `Cubit`의 함수가 아닌 `on` API를 통해 이벤트 핸들러를 등록해야 합니다. 이벤트 핸들러는 들어오는 모든 event를 0개 이상의 나가는 state로 변환하는 역할을 수행합니다. + + + +:::tip +`EventHandler`는 추가된 event뿐 만 아니라 수신되는 event에 대한 응답으로 0개 이상의 상태를 emit하는데 사용할 수 있는 `Emitter`에 접근할 수 있습니다. +::: + +그런 다음 `EventHandler`를 업데이트하여 `CounterIncrementPressed` event를 처리할 수 있습니다: + + + +위의 예시 코드에서는 모든 `CounterIncrementPressed` event를 관리하기 위해 `EventHandler`를 등록했습니다. 들어오는 각 `CounterIncrementPressed` event에 대해 `state` getter와 `emit(state + 1)`를 통해 bloc의 현재 상태에 접근할 수 있습니다. + +:::note +`Bloc` 클래스는 `BlocBase`를 extends 하기 떄문에 `Cubit`와 마찬가지로 `state` getter를 통해 언제든지 bloc의 현재 state에 접근할 수 있습니다. +::: + +:::caution +Bloc은 새로운 state를 직접 `emit`하면 안 됩니다. 대신 모든 state 변경은 `EventHandler` 내에서 들어오는 event에 대한 응답으로 출력되어야 합니다. +::: + +:::caution +Bloc과 Cubit ahen 복제된 상태를 무시합니다. `state == nextState`에서 `State nextState`를 emit하면 state 변경이 발생하지 않습니다. +::: + +### Bloc 사용하기 + +이 시점에서 `CounterBloc`의 인스턴스를 생성하여 사용할 수 있습니다! + +#### 기본 사용법 + + + +위의 예시 코드에서는 먼저 `CounterBloc`의 인스턴스를 생성합니다. 그런 다음 초기 state인 `Bloc`의 현재 state를 출력합니다 (아직 새로운 state가 emit되지 않았으므로). 다음으로 state 변경을 촉발하기 위해 `CounterIncrementPressed` event를 추가합니다. 마지막으로 `0`애서 `1`로 변경된 `Bloc`의 state를 다시 출력하고 `Bloc`의 `close`룰 호출하여 내부 state stream을 닫습니다. + +:::note +다음 event-loop를 기다리기 위해 `await Future.delayed(Duration.zero)`가 추가됩니다 (`EventHandler`가 event를 처리할 수 있도록 허용). +::: + +#### Stream 사용법 + +`Cubit`과 마찬가지로 `Bloc`은 `Stream`의 특수한 유형으로, `Bloc`을 구독하여 state를 실시간으로 업데이트 할 수도 있습니다: + + + +위의 예시 코드에서는 `CounterBloc`을 구독하고 각 state 변경 시마다 print를 호출하고 있습니다. 그런 다음 `on` `EvnetHandler`를 촉발하고 새 state를 emit하는 `CounterIncrementPressed` event를 추가하고 있습니다. 마지막으로, 더 이상 업데이트를 받지 않으려면 `subscription`의 `cancel`을 호출하고 `Bloc`을 닫습니다. + +:::note +해당 예제에서는 구독이 즉시 취소되지 않도록 `await Future.delayed(Duration.zero)`을 추가했습니다. +::: + +### Bloc 관찰하기 + +`Bloc`은 `BlocBase`를 extends 하기 떄문에 `onChange`를 사용하여 `Bloc`의 모든 state 변화를 관찰할 수 있습니다. + + + +그런 다음 `main.dart`를 다음과 같이 업데이트 합니다: + + + +위 예시 코드에 대한 출력입니다: + + + +`Bloc`과 `Cubit`의 주요 차별화 요소 중 하나는 `Bloc`이 event 기반이기 때문에 state 변화를 유발한 원인에 대한 정보도 캡처할 수 있다는 점입니다. + +이 작업은 `onTransition`을 override하여 수행할 수 있습니다. + +한 state에서 다른 state로 변경되는 것을 `Transition`이라고 합니다. `Transition`은 현재 state, event, 다음 state로 구성됩니다. + + + +그런 다음 이전과 동일한 `main.dart` 예시 코드를 다시 실행하면 다음과 같은 출력이 표시됩니다. + + + +:::note +`onTransition`은 `onChange` 이전에 호출되며 `currentState`에서 `nextState`로 변경을 촉발한 event를 포함합니다. +::: + +#### BlocObserver + +이전과 마찬가지로 커스텀 `BlocObserver`에서 `onTransition`을 override하여 단일 위치에서 발생하는 모든 Transition을 관찰할 수 있습니다. + + + +이전과 마찬가지로 `SimpleBlocObserver`를 초기화 할 수 있습니다: + + + +이제 위의 예시 코드를 실행하면 다음과 같은 출력을 얻을 수 있습니다: + + + +:::note +`onTransition` 이 먼저 호출되고(global보다 local이 먼저) 그 다음에 `onChange`가 호출됩니다. +::: + +`Bloc` 인스턴스의 또 다른 독특한 특징은 `Bloc`에 새 event가 추가될 때마다 호출되는 `onEvnet`를 override할 수 있다는 점입니다. `onChange` 및 `onTransition`과 마찬가지로 `onEvent`는 전역뿐만 아니라 로컬에서도 override할 수 있습니다. + + + + + +이전과 동일한 `main.dart`를 실행하면 다음과 같은 출력을 볼 수 있습니다: + + + +:::note +`onEvent`는 event가 추가되는 즉시 호출됩니다. local `onEvent`는 `BlocObserver`의 global `onEvent`보다 먼저 호출됩니다. +::: + +### Bloc의 에러 처리 + +`Cubit`과 마찬가지로 각 `Bloc`에는 `addError`와 `onError` 메서드가 있습니다. `Bloc` 내부 어디에서나 `addError`를 호출하여 에러가 발생했음을 알릴 수 있습니다. 그런 다음 `Cubit`과 마찬가지로 `onError`를 override 하여 모든 에러에 대응할 수 있습니다. + + + +이전과 동일한 `main.dart`를 다시 실행하면 에러가 보고될 때 어떤 모습인지 확인할 수 있습니다: + + + +:::note +local `onError`가 먼저 호출된 후 `BlocObserver`의 global `onError`가 호출됩니다. +::: + +:::note +`onError`와 `onChange`는 `Bloc`과 `Cubit` 인스턴스 모두에 대해 똑같은 방식으로 작동합니다. +::: + +:::caution +`EventHandler` 내에서 발생하는 처리되지 않은 exception도 `onError`에서 보고됩니다. +::: + +## Cubit vs. Bloc + +이제 `Cubit`과 `Bloc` 클래스의 기본 사항을 살펴봤으니 언제 `Cubit`을 사용해야 하는지, 언제 `Bloc`을 사용해야 하는지 궁금하실 것입니다. + +### Cubit 장점 + +#### 단순성 + +`Cubit` 사용의 가장 큰 장점 중 하나는 단순성입니다. `Cubit`을 생성할 때는 state와 state를 변경하기 위해 노출할 함수만 정의하면 됩니다. 반면에 `Bloc`을 생성할 때는 state, event, `EventHandler` 구현을 정의해야 합니다. 따라서 `Cubit`을 더 쉽게 이해할 수 있고 관련된 코드도 더 적습니다. + +이제 두 가지 카운터 구현을 살펴보겠습니다: + +##### CounterCubit + + + +##### CounterBloc + + + +`Cubit` 구현은 더 간결하며 event를 별도로 정의하는 대신 함수가 event처럼 작동합니다. 또한, `Cubit`을 사용할 때는 어디서든 `emit`을 호출하여 state 변경을 촉발할 수 있습니다. + +### Bloc 장점 + +#### 추적가능성 + +`Bloc` 사용의 가장 큰 장점 중 하나는 state 변경의 순서와 그 변경을 유발한 원인을 정확히 파악할 수 있다는 점입니다. 애플리케이션의 기능에 중요한 state의 경우 state 변경 외에도 모든 event를 캡처하기 위해, 보다 event 중심적인 접근 방식을 사용하는 것이 매우 유용할 수 있습니다. + +일반적인 사용 사례는 `AuthenticationState`를 관리하는 것입니다. 간단히 설명하기 위해 `enum`을 통해 `AuthenticationState`를 표현할 수 있다고 가정해 보겠습니다: + + + +애플리케이션의 state가 `authenticated`에서 `unauthenticated`로 변경되는 이유는 여러 가지가 있을 수 있습니다. 예로 들어 사용자가 로그아웃 버튼을 탭하고 애플리케이션에서 로그아웃을 요청했을 수 있습니다. 반면에 사용자의 access token이 해지되어 강제로 로그아웃되었을 수도 있습니다. `Bloc`을 사용하면 애플리케이션 state가 특정 state에 도달한 경로를 명확하게 추적할 수 있습니다. + + + +위의 `Transition`은 state가 변경된 이유를 이해하는데 필요한 모든 정보를 제공합니다. 만약 `Cubit`을 사용해 `AuthenticationState`를 관리했다면, 로그는 다음과 같이 보일 것입니다: + + + +이는 사용자가 로그아웃되었다는 사실을 알려주지만, 시간이 지남에 따라 상태가 어떻게 변하는지 디버깅하고 이해하는데 무엇이 중요한지 설명하지 않습니다. + +#### 고급 Event Transformations + +`Bloc`이 `Cubit`보다 뛰어난 또 다른 영역은 `buffer`, `debounceTime`, `throttle` 등과 같은 반응형 연산자를 활용해야 하는 경우입니다. + +`Bloc`에는 들어오는 event의 흐름을 제어하고 변환할 수 있는 event sink가 있습니다. + +예를 들어, 실시간 검색을 구축하는 경우 속도 제한을 피하고 백엔드의 비용/부하를 줄이기 위해 백엔드에 대한 요청을 debouncing하고 싶을 것입니다. + +`Bloc`을 사용하면 `Bloc`이 수신 event를 처리하는 방식을 변경하는 커스텀 `EventTransformer`를 제공할 수 있습니다. + + + +위의 코드를 사용하면 추가 코드를 거의 추가하지 않고도 수신 event를 쉽게 debounce할 수 있습니다. + +:::tip +EventTransformer에 대한 자세한 내용은 [`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency)를 확인하세요. +::: + +어떤 것을 사용해야 할지 잘 모르겠다면 `Cubit`으로 시작하고 나중에 필요에 따라 `Bloc`으로 리펙토링하거나 스케일업할 수 있습니다. diff --git a/docs/src/content/docs/ko/faqs.mdx b/docs/src/content/docs/ko/faqs.mdx new file mode 100644 index 00000000000..a3d8dc23b91 --- /dev/null +++ b/docs/src/content/docs/ko/faqs.mdx @@ -0,0 +1,194 @@ +--- +title: 자주 묻는 질문 +description: Bloc 라이브러리와 관련하여 자주 묻는 질문에 대한 답변입니다. +--- + +import StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro'; +import StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro'; +import StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro'; +import StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro'; +import StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro'; +import StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro'; +import EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro'; +import EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro'; +import NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro'; +import SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro'; +import SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro'; +import BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro'; +import BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro'; +import BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro'; +import BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro'; +import BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro'; +import BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro'; + +## State가 업데이트되지 않아요 + +❔ **Question**: Bloc에서 state를 emit하는데 UI가 업데이트되지 않아요. 무엇이 문제인가요? + +💡 **Answer**: 만약 Equatable을 사용하는 경우 모든 프로퍼티를 props getter에 전달해야 합니다. + +✅ **GOOD** + + + +❌ **BAD** + + + + + +또한, Bloc에서 state의 새 인스턴스를 emit하고 있는지 확인하세요. + +✅ **GOOD** + + + + + +❌ **BAD** + + + +:::caution +`Equatable` 프로퍼티는 항상 수정하지 말고 복사해야 합니다. `Equatable` 클래스에 `List` 또는 `Map`이 프로퍼티로 있는 경우, 참조가 아닌 프로퍼티 값을 기준으로 동등성이 평가되도록 `List.from` 또는 `Map.from`을 각각 사용해야 합니다. +::: + +## 언제 Equatable를 사용해야 하나요 + +❔**Question**: Equatable은 언제 사용해야 하나요? + +💡**Answer**: + + + +위의 시나리오에서 `StateA`가 `Equatable`을 extends 한다면 하나의 state 변경만 발생합니다 (두 번째 emit은 무시됩니다). 일반적으로 코드를 최적화하여 리빌드 횟수를 줄이려면 `Equatable`을 사용해야 합니다. 동일한 state가 연속적으로 여러 Transition을 촉발하려면 `Equatable`을 사용하면 안 됩니다. + +또한, `Equatable`을 사용하면 `Matchers`나 `Predicates`를 사용하는 것보다 Bloc state의 특정 인스턴스를 예상할 수 있으므로 Bloc을 훨씬 쉽게 테스트할 수 있습니다. + + + +`Equatable`이 없다면 위의 테스트는 실패할 것이고, 다음과 같이 다시 작성해야 합니다: + + + +## 에러 처리 + +❔ **Question**: 이전 데이터를 계속 표시하면서 에러를 처리하려면 어떻게 해야 하나요? + +💡 **Answer**: + +이는 Bloc의 state가 어떻게 모델링되었는지에 따라 크게 달라집니다. 에러가 발생하더라도 데이터를 계속 유지해야 하는 경우에는 단일 state 클래스를 사용하는 것이 좋습니다. + + + +이렇게 하면 위젯이 `데이터` 및 `에러` 프로퍼티에 동시에 접근할 수 있으며, Bloc은 `state.copyWith`을 사용하여 에러가 발생한 경우에도 이전 데이터를 유지할 수 있습니다. + + + +## Bloc vs. Redux + +❔ **Question**: Bloc과 Redux의 차이점은 무엇인가요? + +💡 **Answer**: + +BLoC 은 다음 규칙에 의해 정의되는 디자인 패턴입니다: + +1. BLoC의 입력과 출력은 간단한 Streams과 Sinks 입니다. +2. 종속성은 주입이 가능하고 플렛폼에 구애받지 않아야 합니다. +3. 플랫폼별 분기은 허용되지 않습니다. +4. 위의 규칙을 따르는 한 원하는 대로 구현할 수 있습니다. + +UI 가이드라인은 다음과 같습니다: + +1. "충분히 복잡한" 각 컴포넌트에는 해당하는 BLoC이 있습니다. +2. 컴포넌트는 입력을 "있는 그대로" 보내야 합니다. +3. 컴포넌트는 가능한 "있는 그대로"에 가까운 출력을 표시해야 합니다. +4. 모든 분기는 간단한 BLoC boolean 출력을 기반으로 해야 합니다. + +Bloc 라이브러리는 BLoC 디자인 패턴을 구현하며 개발자 경험을 단순화하기 위해 RxDart를 추상화하는 것을 목표로 합니다. + +Redux의 세 원칙은 다음과 같습니다: + +1. 신뢰할 수 있는 단일 소스 +2. State는 읽기 전용 +3. 순수 함수로 Change가 이루어짐 + +Bloc 라이브러리는 bloc state가 여러 bloc에 분산되어 있기 때문에 첫 번째 원칙을 위반합니다. 또한 bloc에는 미들웨어라는 개념이 없으며 bloc은 비동기 상태 변경을 매우 쉽게 할 수 있도록 설계되어 단일 event에 대해 여러 state를 emit할 수 있습니다. + +## Bloc vs. Provider + +❔ **Question**: Bloc과 Provider의 차이점은 무엇인가요? + +💡 **Answer**: `provider`는 종속성 주입을 위해 설계되었습니다 (`InheritedWidget`을 래핑합니다). 여전히 state를 관리하는 방법을 알아내야 합니다 (`ChangeNotifier`, `Bloc`, `Mobx` 등을 통해). Bloc 라이브러리는 내부적으로 `provider`를 사용하여 위젯 트리 전체에서 bloc을 쉽게 제공하고 접근할 수 있도록 합니다. + +## BlocProvider.of()가 Bloc을 못 찾아요 + +❔ **Question**: `BlocProvider.of(context)`을 사용할 때 bloc을 찾을 수 없어요. 어떻게 고치면 될까요? + +💡 **Answer**: Bloc이 제공한 context와 동일한 context에서는 bloc에 접근할 수 없으므로, 하위 `BuildContext` 내에서 `BlocProvider.of()`가 호출되는지 확인해야 합니다. + +✅ **GOOD** + + + + + +❌ **BAD** + + + +## 프로젝트 구조 + +❔ **Question**: 프로젝트를 어떻게 구조화하는게 좋을까요? + +💡 **Answer**: 이 질문에 대한 정답은 없지만, 몇 가지 권장되는 참고 자료는 다음과 같습니다: + +- [I/O Photobooth](https://github.com/flutter/photobooth) +- [I/O Pinball](https://github.com/flutter/pinball) +- [Flutter News Toolkit](https://github.com/flutter/news_toolkit) + +가장 중요한 것은 **일관성**있고 **의도적인** 프로젝트 구조를 갖는 것입니다. + +## Bloc 내에서 Event 추가하기 + +❔ **Question**: Bloc 내에서 event를 추가해도 괜찮은가요? + +💡 **Answer**: 대부분의 경우, event는 외부에서 추가해야 하지만 일부 경우에는 event를 내부적으로 추가하는 것이 합리적일 수 있습니다. + +내부 event가 사용되는 가장 일반적인 상황은 Repository의 실시간 업데이트에 대한 응답으로 state의 변경이 발생해야 하는 경우입니다. 이러한 상황에서 Repository는 버튼 탭과 같은 외부 event 대신 state 변경에 대한 자극이 됩니다. + +다음 예시에서 `MyBloc`의 state는 `UserRepository`의 `Stream`를 통해 노출되는 현재 사용자에 따라 달라집니다. `MyBloc`은 현재 사용자의 변경 사항을 수신하고, 사용자가 사용자 stream에서 방출될 때 마다 내부 `_UserChanged` event를 추가합니다. + + + +내부 event를 추가함으로써 event에 대한 커스텀 `transformer`를 지정하여 여러 `_UserChanged` event가 처리되는 방식을 결정할 수도 있습니다. 기본적으로 event는 동시에 처리됩니다. + +내부 event는 private로 정의되는 것을 강력히 권장합니다. 이는 특정 event가 bloc 자체 내에서만 사용한다는 것을 명시적으로 알리는 방법이며, 외부 컴포넌트가 event에 대해 아는 것을 방지합니다. + + + +또한 외부 `Started` event를 정의하고 `emit.forEach` API를 사용하여 실시간 사용자 업데이트에 대한 반응을 처리할 수 있습니다. + + + +위 접근 방식의 장점은 다음과 같습니다: + +- 내부 `_UserChanged` event가 필요하지 않습니다. +- `StreamSubscription`을 수동으로 관리할 필요가 없습니다. +- Bloc이 사용자 업데이트 steram을 구독하는 시기를 완전히 제어할 수 있습니다. + +위 접근 방식의 단점은 다음과 같습니다: + +- 구독을 쉽게 `pause` 하거나 `resume`할 수 없습니다. +- 외부적으로 추가해야 하는 공개 `Started` event를 노출해야 합니다. +- 사용자 업데이트에 반응하는 방식을 조정하기 위해 커스텀 `transformer`를 사용할 수 없습니다. + +## Public 메서드 노출 + +❔ **Question**: Bloc 및 Cubit 인스턴스에 public 메서드를 노출해도 괜찮을까요? + +💡 **Answer** + +Cubit을 생성할 때 state 변경을 촉발할 목적으로만 public 메서드를 노출하는 것이 좋습니다. 결과적으로, 일반적인 cubit 인스턴스의 모든 public 메서드는 `void` 또는 `Future`를 반환해야 합니다. + +Bloc을 생성할 때 커스텀 public 메서드를 노출하지 않고, 대신 `add`를 호출하여 event를 bloc에 알리는 것이 좋습니다. diff --git a/docs/src/content/docs/ko/flutter-bloc-concepts.mdx b/docs/src/content/docs/ko/flutter-bloc-concepts.mdx new file mode 100644 index 00000000000..f220c09c38d --- /dev/null +++ b/docs/src/content/docs/ko/flutter-bloc-concepts.mdx @@ -0,0 +1,397 @@ +--- +title: Flutter Bloc 핵심 컨셉 +description: package:flutter_bloc의 핵심 개념에 대한 개요입니다. +sidebar: + order: 2 +--- + +import BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro'; +import BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro'; +import BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro'; +import BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro'; +import BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro'; +import BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro'; +import BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro'; +import BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro'; +import NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro'; +import MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro'; +import BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro'; +import BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro'; +import BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro'; +import NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro'; +import MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro'; +import BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro'; +import BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro'; +import RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro'; +import RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro'; +import NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro'; +import MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro'; +import CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro'; +import CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro'; +import CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro'; +import WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro'; +import WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro'; +import WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro'; +import WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro'; + +:::note +[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc)을 사용하기 전에 다음 섹션을 주의 깊게 읽어주세요. +::: + +:::note +`flutter_bloc` 패키지에서 export된 모든 위젯은 `Cubit` 및 `Bloc` 인스턴스와 모두 통합됩니다. +::: + +## Bloc Widgets + +### BlocBuilder + +**BlocBuilder**는 `Bloc` 및 `builder` 함수 기능이 필요한 Flutter 위젯입니다. `BlocBuilder`는 새로운 state에 대한 응답으로 위젯 빌드를 처리합니다. `BlocBuilder`는 `StreamBuilder`와 매우 유사하지만 필요한 보일러플레이트 코드의 양을 줄이기 위해 더 간단한 API를 갖고 있습니다. `builder` 함수는 잠재적으로 여러 번 호출될 수 있으며 state에 응답하여 위젯을 반환하는 [순수 함수](https://en.wikipedia.org/wiki/Pure_function)여야 합니다. + +Navigation, Dialog 표시 등과 같은 state 변경에 대한 응답으로 무엇이든 간에 "수행"하려면 `BlocListener`를 참조하세요. + +만약 `bloc` 파라미터가 생략되면 `BlocBuilder`는 `BlocProvider`와 현재 `BuildContext`를 사용하여 자동으로 bloc을 조회합니다. + + + +단일 위젯으로 범위가 지정되고, 상위 `BlocProvider` 및 현재 `BuildContext`를 통해 접근할 수 없는 bloc을 제공하려는 경우에만 bloc 파라미터를 지정하세요. + + + +`builder` 함수가 호출되는 시점을 세밀하게 제어하기 위해 선택적 `buildWhen` 파라미터가 제공됩니다. `buildWhen`은 이전 bloc state와 현재 bloc state를 가져온 후 boolean을 반환합니다. `buildWhen`이 true를 반환하면 `builder`가 `state`와 함께 호출되고 위젯이 다시 빌드됩니다. `buildWhen`이 false를 반환하면 `builder`는 `state`와 함께 호출되지 않으며 리빌드는 일어나지 않습니다. + + + +### BlocSelector + +**BlocSelector**는 `BlocBuilder`와 유사하지만 개발자가 현재 bloc state에 따라 새 값을 선택하여 업데이트를 필터링할 수 있는 Flutter 위젯입니다. 선택한 값이 변경되지 않으면 불필요한 빌드가 방지됩니다. `BlocSelector`가 `builder`를 다시 호출해야 하는지 여부를 정확하게 결정하려면 선택한 값을 변경할 수 없어야(immutable) 합니다. + +만약 `bloc` 파라미터가 생략되면 `BlocBuilder`는 `BlocProvider`와 현재 `BuildContext`를 사용하여 자동으로 bloc을 조회합니다. + + + +### BlocProvider + +**BlocProvider**는 `BlocProvider.of(context)`를 통해 child에게 bloc을 제공하는 Flutter 위젯입니다. 이는 bloc의 단일 인스턴스가 하위 트리 내의 여러 위젯에 제공될 수 있도록 종속성 주입(DI) 위젯으로 사용됩니다. + +대부분의 경우, `BlocProvider`를 사용하여 나머지 하위 트리에서 사용할 수 있는 새 bloc을 생성해야 합니다. 이 경우 `BlocProvider`가 bloc 생성을 담당하기 때문에 자동으로 bloc을 close하는 것도 처리합니다. + + + +기본적으로, `BlocProvider`는 bloc을 lazy하게 생성합니다. 즉, `BlocProvider.of(context)`을 통해 bloc을 조회할 때 `create`가 실행된다는 의미입니다. + +이 동작을 무시하고 `create`가 즉시 실행되도록 하려면 `lazy`를 `false`로 설정하면 됩니다. + + + +어떤 경우에는 `BlocProvider`를 사용하여 위젯 트리의 새 부분에 기존 bloc을 제공할 수 잇습니다. 이는 기존 bloc을 새 route에서 사용할 수 있도록 해야 할 때 가장 일반적으로 사용됩니다. 이 경우 `BlocProvider`는 bloc을 생성하지 않았으므로 자동으로 bloc을 close하지 않습니다. + + + +그런 다음 `ChildA` 또는 `ScreenA`애서 다음을 사용하여 `BlocA`를 찾을 수 있습니다: + + + +### MultiBlocProvider + +**MultiBlocProvider**는 여러 `BlocProvider` 위젯을 하나로 병합하는 Flutter 위젯입니다. `MultiBlocProvider`는 가독성을 향상시키고 여러 `BlocProvider`를 중첩할 필요성을 제거합니다. `MultiBlocProvider`를 사용하면 다음과 같던 코드를: + + + +다음과 같이 변경할 수 있습니다: + + + +### BlocListener + +**BlocListener**는 필수 `BlocWidgetListener`와 선택적 `Bloc` 파라미터를 사용하고 bloc의 state 변경에 대한 응답으로 `listener`를 호출하는 Flutter 위젯입니다. Navigation, `Shackbar` 표시, `Dialog` 표시 등과 같이 state 변경당 한 번 발생해야 하는 기능에 사용해야 합니다. + +`listener`는 `BlocBuilder`의 `builder`와 달리 각 state 변경 (초기 state를 포함하지 **않음**)에 대해 한 번만 호출되며 `void` 함수입니다. + +만약 `bloc` 파라미터가 생략되면 `BlocBuilder`는 `BlocProvider`와 현재 `BuildContext`를 사용하여 자동으로 bloc을 조회합니다. + + + +`BlocProvider` 및 현재 `BuildContext`를 통해 접근할 수 없는 bloc을 제공하려는 경우에만 bloc 파라미터를 지정하세요. + + + +`listner` 함수가 호출되는 시점을 세밀하게 제어하기 위해 선택적 `listenWhen` 파라미터가 제공됩니다. `listenWhen`은 이전 bloc state와 현재 bloc state를 가져온 후 boolean을 반환합니다. `listenWhen`이 true를 반환하면 `listener`는 `state`와 함께 호출됩니다. `listenWhen`이 false를 반환하면 `listener`는 `state`와 함께 호출되지 않습니다. + + + +### MultiBlocListener + +**MultiBlocListener**는 여러 `BlocListener` 위젯을 하나로 병합하는 Flutter 위젯입니다. `MultiBlocListener`는 가독성을 향상시키고 여러 `BlocListener`를 중첩할 필요성을 제거합니다. `MultiBlocListener`를 사용하면 다음과 같던 코드를: + + + +다음과 같이 변경할 수 있습니다: + + + +### BlocConsumer + +**BlocConsumer**는 새로운 state에 반응하기 위해 `builder`와 `listener`를 노출합니다. `BlocConsumer`는 중첩된 `BlocListener` 및 `BlocBuilder`와 유사하지만, 필요한 보일러플레이트 코드의 양을 줄입니다. `BlocConsumer`는 UI를 다시 빌드하고 `bloc`의 상태 변경에 대한 다른 반응을 실행해야 하는 경우에만 사용해야 합니다. `BlocConsumer`는 필수 `BlocWidgetBuilder` 및 `BlocWidgetListener`와 선택적인 `bloc`, `BlocBuilderCondition`, `BlocListenerCondition` 파라미터를 사용합니다. + +만약 `bloc` 파라미터가 생략되면 `BlocBuilder`는 `BlocProvider`와 현재 `BuildContext`를 사용하여 자동으로 bloc을 조회합니다. + + + +선택적 파라미터인 `listenWhen` 및 `buildWhen`을 구현하면 `listener` 및 `builder`가 호출되는 시점을 더욱 세밀하게 제어할 수 있습니다. `listenWhen` 및 `buildWhen`은 각 `bloc` `state` 변경 시 호출됩니다. 이들은 각각 이전 `state`와 현재 `state`를 취하고 `builder` 및/또는 `listener` 함수가 호출되는지 여부를 결정하는 `bool`을 반환해야 합니다. `BlocConsumer`가 초기화되면 이전 `state`는 `bloc`의 `state`로 초기화됩니다. `listenWhen` 및 `buildWhen`은 선택사항이며 구현되지 않은 경우 기본값은 `true` 입니다. + + + +### RepositoryProvider + +**RepositoryProvider**는 `RepositoryProvider.of(context)`를 통해 child에게 repository을 제공하는 Flutter 위젯입니다. 이는 repository의 단일 인스턴스가 하위 트리 내의 여러 위젯에 제공될 수 있도록 종속성 주입(DI) 위젯으로 사용됩니다. `BlocProvider`는 bloc을 제공하는 데 사용해야 하는 반면, `RepositoryProvider`는 repository를 제공하는 데에만 사용해야 합니다. + + + +그런 다음 `ChildA`에서 다음을 사용하여 `Repository` 인스턴스를 찾을 수 있습니다: + + + +### MultiRepositoryProvider + +**MultiRepositoryProvider**는 여러 `RepositoryProvider` 위젯을 하나로 병합하는 Flutter 위젯입니다. `MultiRepositoryProvider`는 가독성을 향상시키고 여러 `RepositoryProvider`를 중첩할 필요성을 제거합니다. `MultiRepositoryProvider`를 사용하면 다음과 같던 코드를: + + + +다음과 같이 변경할 수 있습니다: + + + +## BlocProvider 사용법 + +`BlocProvider`를 사용하여 `CounterPage`에 `CounterBloc`을 제공하고 `BlocBuilder`를 사용하여 state 변경에 대한 반응을 살펴보겠습니다. + + + + + + + +이 시점에서 우리는 Presentation 레이어를 Business Logic 레이어에서 성공적으로 분리했습니다. `CounterPage` 위젯은 사용자가 버튼을 탭할 때 어떤 일이 발생하는지 전혀 모릅니다. 위젯은 단순히 사용자가 증가 또는 감소 버튼을 눌렀음을 `CounterBloc`에 알려줄 뿐 입니다. + +## RepositoryProvider 사용법 + +[`flutter_weather`][flutter_weather_link] 예시의 맥락에서 `RepositoryProvider`를 사용하는 방법을 살펴보겠습니다. + + + +앱이 `WeatherRepository`에 명시적으로 종속되어 있으므로 생성자를 통해 인스턴스를 주입합니다. 이를 통해 빌드 Flavor나 환경에 따라 `WeatherRepository`의 다양한 인스턴스를 주입할 수 있습니다. + + + +우리 앱에는 하나의 Repository만 있으므로 `RepositoryProvider.value`를 통해 이를 위젯 트리에 삽입합니다. Repository가 두 개 이상인 경우 `MultiRepositoryProvider`를 사용하여 하위 트리에 여러 repository 인스턴스를 제공할 수 있습니다. + + + +대부분의 경우, Root 앱 위젯은 `RepositoryProvider`를 통해 하위 트리에 하나 이상의 repository를 노출합니다. + + + +이제 bloc을 인스턴스화 할 때, `context.read`를 통해 repository의 인스턴스에 접근하고 생성자를 통해 repository를 bloc에 주입할 수 있습니다. + +[flutter_weather_link]: https://github.com/felangel/bloc/blob/master/examples/flutter_weather + +## Extension Methods + +Dart 2.7에 도입된 [Extension methods](https://dart.dev/guides/language/extension-methods)는 기존 라이브러리에 기능을 추가하는 방법입니다. 이번 섹션에서는 `package:flutter_bloc`에 포함된 확장 메서드와 이를 사용하는 방법을 살펴보겠습니다. + +`flutter_bloc`은 [`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)의 사용을 단순화하는 [package:provider](https://pub.dev/packages/provider)에 대한 종속성이 있습니다. + +내부적으로, `package:flutter_bloc`은 `package:provider`를 사용하여 `BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` 그리고 `MultiRepositoryProvider` 위젯을 구현합니다. `package:flutter_bloc`은 `package:provider`의 확장인 `ReadContext`, `WatchContext` 그리고 `SelectContext`를 export 합니다. + +:::note +[`package:provider`](https://pub.dev/packages/provider)에 대해 자세히 알아보세요. +::: + +### context.read + +`context.read()`는 `T`타입에 가장 가까운 상위 인스턴스를 조회하며 기능적으로 `BlocProvider.of(context)`와 동일합니다. `context.read`는 `onPressed` 콜백 내에 event를 추가하기 위해 bloc 인스턴스를 검색하는 데 가장 일반적으로 사용됩니다. + +:::note +`context.read()`는 `T`를 listen하지 않습니다. 제공된 `T` 타입의 `Object`가 변경되면 `context.read`는 위젯 리빌드를 촉발하지 않습니다. +::: + +#### 사용법 + +✅ **DO** 콜백에 event를 추가하려면 `context.read`를 사용하세요. + +```dart +onPressed() { + context.read().add(CounterIncrementPressed()), +} +``` + +❌ **AVOID** `context.read`를 사용하여 `build` 메서드 내에서 상태를 찾지 마세요. + +```dart +@override +Widget build(BuildContext context) { + final state = context.read().state; + return Text('$state'); +} +``` + +위의 사용법은 bloc의 state가 변경되어도 `Text` 위젯이 다시 리빌드되지 않기 때문에 오류가 발생하기 쉽습니다. + +:::caution +State 변경에 따라 다시 빌드하려면 `BlocBuilder` 또는 `context.watch` 를 대신 사용하세요. +::: + +### context.watch + +`context.read()`와 마찬가지로, `context.watch()`는 `T`타입에 가장 가까운 상위 인스턴스를 조회하며 인스턴스의 변경 사항도 listen 합니다. 기능적으로는 `BlocProvider.of(context, listening: true)`와 동일합니다. + +제공된 `T` 타입의 `Object`가 변경되면 `context.watch`는 위젯 리빌드를 촉발합니다. + +:::caution +`context.watch`는 `StatelessWidget` 또는 `State` 클래스의 `build` 메서드 내에서만 접근할 수 있습니다. +::: + +#### 사용법 + +✅ **DO** 명시적으로 리빌드 scope를 지정하려면 `context.watch` 대신 `BlocBuilder`를 사용하세요. + +```dart +Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: BlocBuilder( + builder: (context, state) { + // Whenever the state changes, only the Text is rebuilt. + return Text(state.value); + }, + ), + ), + ); +} +``` + +또는, `Builder`를 사용하여 리빌드 scope를 제한하세요. + +```dart +@override +Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Builder( + builder: (context) { + // Whenever the state changes, only the Text is rebuilt. + final state = context.watch().state; + return Text(state.value); + }, + ), + ), + ); +} +``` + +✅ **DO** `Builder`와 `context.watch`를 `MultiBlocBuilder`처럼 사용하세요. + +```dart +Builder( + builder: (context) { + final stateA = context.watch().state; + final stateB = context.watch().state; + final stateC = context.watch().state; + + // return a Widget which depends on the state of BlocA, BlocB, and BlocC + } +); +``` + +❌ **AVOID** `build` 메서드 내의 상위 위젯이 state에 의존하지 않는 경우 `context.watch`를 사용하지 마세요. + +```dart +@override +Widget build(BuildContext context) { + // Whenever the state changes, the MaterialApp is rebuilt + // even though it is only used in the Text widget. + final state = context.watch().state; + return MaterialApp( + home: Scaffold( + body: Text(state.value), + ), + ); +} +``` + +:::caution +`build` 메서드의 root에서 `context.watch`를 사용하면 bloc state가 변경될 때 전체 위젯이 다시 빌드됩니다. +::: + +### context.select + +`context.watch()`와 마찬가지로, `context.select(R function(T value))`는 `T`타입에 가장 가까운 상위 인스턴스를 조회하며 인스턴스의 변경 사항도 listen 합니다. `context.watch`와 달리 `context.select`를 사용하면 state의 작은 부분(일부)에서의 변경 사항을 listen할 수 있습니다. + +```dart +Widget build(BuildContext context) { + final name = context.select((ProfileBloc bloc) => bloc.state.name); + return Text(name); +} +``` + +위의 내용은 `ProfileBloc` state의 `name` 프로퍼티가 변경될 때만 위젯을 다시 빌드합니다. + +#### 사용법 + +✅ **DO** 명시적으로 리빌드 scope를 지정하려면 `context.select` 대신 `BlocSelector`를 사용하세요. + +```dart +Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: BlocSelector( + selector: (state) => state.name, + builder: (context, name) { + // Whenever the state.name changes, only the Text is rebuilt. + return Text(name); + }, + ), + ), + ); +} +``` + +또는, `Builder`를 사용하여 리빌드 scope를 제한하세요. + +```dart +@override +Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Builder( + builder: (context) { + // Whenever state.name changes, only the Text is rebuilt. + final name = context.select((ProfileBloc bloc) => bloc.state.name); + return Text(name); + }, + ), + ), + ); +} +``` + +❌ **AVOID** `build` 메서드 내의 상위 위젯이 state에 의존하지 않는 경우 `context.select`를 사용하지 마세요. + +```dart +@override +Widget build(BuildContext context) { + // Whenever the state.value changes, the MaterialApp is rebuilt + // even though it is only used in the Text widget. + final name = context.select((ProfileBloc bloc) => bloc.state.name); + return MaterialApp( + home: Scaffold( + body: Text(name), + ), + ); +} +``` + +:::caution +`build` 메서드의 root에서 `context.select`를 사용하면 bloc state가 변경될 때 전체 위젯이 다시 빌드됩니다. +::: diff --git a/docs/src/content/docs/ko/getting-started.mdx b/docs/src/content/docs/ko/getting-started.mdx new file mode 100644 index 00000000000..c2c3825d95b --- /dev/null +++ b/docs/src/content/docs/ko/getting-started.mdx @@ -0,0 +1,35 @@ +--- +title: 시작하기 가이드 +description: Bloc으로 구축을 시작하는 데 필요한 모든 것. +--- + +import InstallationTabs from '~/components/getting-started/InstallationTabs.astro'; +import ImportTabs from '~/components/getting-started/ImportTabs.astro'; + +## 패키지 + +Bloc 생태계는 아래에 나열된 여러 패키지들로 구성됩니다: + +| 패키지 | Description | Link | +| ------------------------------------------------------------------------------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------- | +| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc) | AngularDart Components | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc) | +| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc) | Core Dart APIs | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc) | +| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Event Transformers | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) | +| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test) | Testing APIs | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test) | +| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc) | Flutter Widgets | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc) | +| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc) | Caching/Persistence Support | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc) | +| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc) | Undo/Redo Support | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc) | + +## 설치 + + + +:::note +Bloc 사용을 시작하려면 장치에 [Dart SDK](https://dart.dev/get-dart)가 설치되어 있어야 합니다. +::: + +## Imports + +이제 bloc을 성공적으로 설치했으므로 `main.dart`를 만들고 해당 `bloc` 패키지를 가져올 수 있습니다. + + diff --git a/docs/src/content/docs/ko/index.mdx b/docs/src/content/docs/ko/index.mdx new file mode 100644 index 00000000000..4b512f3a284 --- /dev/null +++ b/docs/src/content/docs/ko/index.mdx @@ -0,0 +1,81 @@ +--- +template: splash +title: Bloc 상태 관리 라이브러리 +description: Bloc 상태 관리 라이브러리에 대한 공식 문서입니다. Dart, Flutter, 그리고 AngularDart를 지원합니다. 예제 및 튜토리얼이 포함되어 있습니다. +banner: + content: | + ✨ Bloc Shop + 을 방문해보세요✨ +editUrl: false +lastUpdated: false +hero: + title: Bloc v8.1.4 + tagline: Dart를 위한 예측 가능한 상태관리 라이브러리. + image: + alt: Bloc logo + file: ~/assets/bloc.svg + actions: + - text: 시작하기 + link: /ko/getting-started/ + variant: primary + icon: rocket + - text: GitHub에서 보기 + link: https://github.com/felangel/bloc + icon: github + variant: secondary +--- + +import { CardGrid } from '@astrojs/starlight/components'; +import SponsorsGrid from '~/components/landing/SponsorsGrid.astro'; +import Card from '~/components/landing/Card.astro'; +import ListCard from '~/components/landing/ListCard.astro'; +import SplitCard from '~/components/landing/SplitCard.astro'; +import Discord from '~/components/landing/Discord.astro'; + + + +
+ + + + + ```sh + # 프로젝트에 bloc을 추가하세요. + dart pub add bloc + ``` + +여기 [시작하기 가이드](/ko/getting-started) 에는 빠르게 Bloc을 사용하는 방법에 대한 단계별 지침이 나와 있습니다. + + + + + [공식 튜토리얼](/ko/tutorials/flutter-counter) 을 완료하여 모범 사례를 배우고, + Bloc을 사용하는 다양한 앱을 빌드하세요. + + + + 카운터, 타이머, 무한 스크롤, 날씨, todo 등과 같은 고품질의, 완벽한 테스트를 + 거친 [예시 앱](https://github.com/felangel/bloc/tree/master/examples)들을 + 살펴보세요! + + + + + - [왜 Bloc인가?](/ko/why-bloc) + - [핵심 컨셉](/ko/bloc-concepts) + - [아키텍쳐](/ko/architecture) + - [테스팅](/ko/testing) + - [작명 규칙](/ko/naming-conventions) + + + + + - [VSCode Integration](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc) + - [IntelliJ Integration](https://plugins.jetbrains.com/plugin/12129-bloc) + - [Mason CLI Integration](https://github.com/felangel/bloc/blob/master/bricks/README.md) + - [Custom Templates](https://brickhub.dev/search?q=bloc) + - [Developer Tools](https://github.com/felangel/bloc/issues/2748) + + + + diff --git a/docs/src/content/docs/ko/naming-conventions.mdx b/docs/src/content/docs/ko/naming-conventions.mdx new file mode 100644 index 00000000000..e960bce3822 --- /dev/null +++ b/docs/src/content/docs/ko/naming-conventions.mdx @@ -0,0 +1,82 @@ +--- +title: 작명 규칙 +description: Bloc 사용 시 권장되는 작명 규칙 개요입니다. +--- + +import EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro'; +import EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro'; +import StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro'; +import SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro'; +import StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro'; + +다음의 작명 규칙은 단순히 권장 사항일 뿐이며 완진한 선택 사항입니다. 원하는 작명 규칙을 자유롭게 사용하세요. 일부 예제/문서는 주로 단순성/간결성을 위해 작명 규칙을 따르지 않을 수 있습니다. 이러한 규칙은 개발자가 여러 명인 대규모 프로젝트에 강력히 권장됩니다. + +## Event 규칙 + +이벤트는 bloc의 관점에서 이미 발생한 일이므로 **과거형**으로 이름을 지정해야 합니다. + +### 해부 + +`BlocSubject` + `명사 (선택)` + `동사 (event)` + +초기 로드 event는 다음의 규칙을 따라야 합니다: `BlocSubject` + `Started` + +:::note +기본이 되는 클래스 이름은 다음과 같아야 합니다: `BlocSubject` + `Event`. +::: + +### 예시 + +✅ **Good** + + + +❌ **Bad** + + + +## State 규칙 + +State는 특정 시점의 스냅샷일 뿐이므로 state는 명사여야 합니다. State를 나타내는 두 가지 일반적인 방법은 하위 클래스를 사용하거나 단일 클래스를 사용하는 것입니다. + +### 해부 + +#### 하위 클래스 + +`BlocSubject` + `동사 (동작)` + `State` + +State를 여러 하위 클래스로 표현하는 경우 `State`는 다음 중 하나여야 합니다: + +`Initial` | `Success` | `Failure` | `InProgress` + +:::note +초기 state는 다음의 규칙을 따라야 합니다: `BlocSubject` + `Initial`. +::: + +#### 단일 클래스 + +`BlocSubject` + `State` + +State를 단일 기본 클래스로 표시할 때 `BlocSubject` + `Status`라는 enum을 사용하여 다음과 같은 상태를 표시해야 합니다: + +`initial` | `success` | `failure` | `loading`. + +:::note +기본이 되는 클래스 이름은 항상 다음과 같아야 합니다: `BlocSubject` + `State`. +::: + +### 예시 + +✅ **Good** + +##### 하위 클래스 + + + +##### 단일 클래스 + + + +❌ **Bad** + + diff --git a/docs/src/content/docs/ko/testing.mdx b/docs/src/content/docs/ko/testing.mdx new file mode 100644 index 00000000000..f4ee1b05c6c --- /dev/null +++ b/docs/src/content/docs/ko/testing.mdx @@ -0,0 +1,64 @@ +--- +title: 테스팅 +description: Bloc에 대한 테스트 작성 방법에 대한 기본 사항입니다. +--- + +import CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro'; +import AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro'; +import CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro'; +import CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro'; +import CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro'; +import CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro'; +import CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro'; + +Bloc은 테스트하기가 매우 쉽도록 설계되었습니다. 이 섹션에서는 bloc을 unit test 하는 방법을 살펴보겠습니다. + +단순화를 위해, [Core Concepts](/ko/bloc-concepts)에서 만든 `CounterBloc`에 대한 테스트를 작성해보겠습니다. + +복습을 위해 요약하면, `CounterBloc` 구현은 다음과 같습니다: + + + +## 준비 + +테스트 작성을 시작하기 전에 종속성에 테스트 프레임워크를 추가해야 합니다. + +프로젝트에 [test](https://pub.dev/packages/test) 및 [bloc_test](https://pub.dev/packages/bloc_test)를 추가합니다. + + + +## 테스팅 + +`CounterBloc` 테스트용 파일인 `counter_bloc_test.dart`를 만들고 테스트 패키지를 가져오는 것부터 시작해 보겠습니다. + + + +다음으로, `main`와 테스트 그룹을 만들어야 합니다. + + + +:::note +그룹은 개별 테스트를 구성하고 모든 개별 테스트에서 공통 `setUp` 및 `tearDown`을 공유할 수 있는 context를 생성하기 위한 것입니다. +::: + +모든 테스트에서 사용될 `CounterBloc`의 인스턴스를 생성하는 것부터 시작해 보겠습니다. + + + +이제 개별 테스트 작성을 시작하겠습니다. + + + +:::note +`dart test` 명령어를 사용하여 모든 테스트를 실행할 수 있습니다. +::: + +이 시점에서 우리는 첫 번째 통과된 테스트를 받아야 합니다! 이제 [bloc_test](https://pub.dev/packages/bloc_test) 패키지를 사용하여 좀 더 복잡한 테스트를 작성해 보겠습니다. + + + +이제 우리는 테스트를 실행하고 모든 테스트가 통과되는지 확인할 수 있어야 합니다. + +이게 전부입니다. 테스트는 쉬워야 하며 코드를 변경하고 리펙토링할 때 자신감을 가질 수 있어야 합니다. + +완전히 테스트된 애플리케이션의 예제는 [Weather App](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)을 참조 바랍니다. diff --git a/docs/src/content/docs/ko/why-bloc.mdx b/docs/src/content/docs/ko/why-bloc.mdx new file mode 100644 index 00000000000..69d6b40f7d3 --- /dev/null +++ b/docs/src/content/docs/ko/why-bloc.mdx @@ -0,0 +1,31 @@ +--- +title: 왜 Bloc인가? +description: 어떤 요소가 Bloc을 견고한 상태 관리 솔루션으로 만드는 지에 대한 개요입니다. +sidebar: + order: 1 +--- + +Bloc을 사용하면 Business Logic에서 Presentation을 쉽게 분리하여 코드를 _빠르게_, _테스트하기 쉽게_, *재사용 가능*하게 만들 수 있습니다. + +프로덕션 품질의 애플리케이션을 구축할 때 상태 관리가 중요해집니다. + +개발자로서 우리는 다음과 같은 요구사항을 가질 수 있습니다: + +- 언제든지 애플리케이션의 상태를 알 수 있어야 합니다. +- 모든 케이스를 쉽게 테스트하여 앱이 적절하게 응답하는지 확인해야 합니다. +- 데이터 기반 결정을 내릴 수 있도록 애플리케이션의 모든 단일 사용자 상호 작용을 기록해야 합니다. +- 애플리케이션 내부와 다른 애플리케이션 전반에서 최대한 효율적으로 작업하고, 구성 요소를 재사용해야 합니다. +- 많은 개발자가 동일한 패턴과 규칙에 따라 단일 코드 기반 내에서 원활하게 작업할 수 있어야 합니다. +- 빠르고 반응성이 뛰어난 앱을 개발해야 합니다. + +Bloc은 이러한 모든 요구 사항과 그 이상을 충족하도록 설계되었습니다. + +상태 관리 솔루션은 다양하며 어떤 솔루션을 사용할지 결정하는 것은 어려운 작업일 수 있습니다. 완벽한 상태 관리 솔루션은 없습니다! 하지만 중요한 것은 팀과 프로젝트에 가장 적합한 것을 선택하는 것입니다. + +Bloc은 다음 세 가지 핵심 가치를 염두에 두고 설계되었습니다: + +- **간단함:** 이해하기 쉽고 다양한 스킬 수준을 가진 개발자가 사용할 수 있습니다. +- **강력함:** 더 작은 구성 요소로 구성하여 놀랍고 복잡한 애플리케이션을 만드는 데 도움을 줍니다. +- **테스트 가능:** 애플리케이션의 모든 측면을 쉽게 테스트할 수 있으므로 자신있게 테스트를 반복할 수 있습니다. + +전반적으로, Bloc은 상태 변경이 발생할 수 있는 시기를 규제하고 애플리케이션 전체에 걸쳐 단일한 상태를 변경하는 방법을 시행함으로써 상태 변경을 예측 가능하게 만들려고 시도합니다.