@@ -26,10 +26,9 @@ type Result = {
2626 preferences: Preferences ;
2727};
2828
29- // 폼 컴포넌트들
3029const ProfileForm = (props : MozardStepProps <Profile >) => {
3130 const { register, handleSubmit } = useForm <Profile >();
32-
31+
3332 return (
3433 <form onSubmit = { handleSubmit (props .onSubmit )} >
3534 <h2 >기본 정보</h2 >
@@ -43,14 +42,14 @@ const ProfileForm = (props: MozardStepProps<Profile>) => {
4342
4443const ParentConsentForm = (props : MozardStepProps <ParentConsent >) => {
4544 const { register, handleSubmit } = useForm <ParentConsent >();
46-
45+
4746 return (
4847 <form onSubmit = { handleSubmit (props .onSubmit )} >
49- <h2 >보호자 동의 (미성년자 )</h2 >
48+ <h2 >보호자 동의 (18세 미만 )</h2 >
5049 <input { ... register (" parentEmail" , { required: true })} placeholder = " 보호자 이메일" />
5150 <label >
5251 <input { ... register (" agreed" , { required: true })} type = " checkbox" />
53- 보호자가 가입에 동의합니다
52+ 보호자가 가입에 동의했습니다
5453 </label >
5554 <button type = " submit" >다음</button >
5655 </form >
@@ -59,14 +58,14 @@ const ParentConsentForm = (props: MozardStepProps<ParentConsent>) => {
5958
6059const PreferencesForm = (props : MozardStepProps <Preferences > & { isMinor: boolean }) => {
6160 const { register, handleSubmit } = useForm <Preferences >();
62-
61+
6362 return (
6463 <form onSubmit = { handleSubmit (props .onSubmit )} >
6564 <h2 >환경설정</h2 >
6665 { ! props .isMinor && (
6766 <label >
6867 <input { ... register (" newsletter" )} type = " checkbox" />
69- 뉴스레터 수신 동의
68+ 뉴스레터 구독
7069 </label >
7170 )}
7271 <select { ... register (" theme" )} >
@@ -78,40 +77,36 @@ const PreferencesForm = (props: MozardStepProps<Preferences> & { isMinor: boolea
7877 );
7978};
8079
81- // 메인 앱
82- export function SignUpApp() {
80+ export function RegistrationApp() {
8381 const [values, setValue] = useState <Entry <Schema >[]>([]);
84-
82+
8583 const { elements, done, value, get } = useMozard <Schema , Result >({
8684 values ,
8785 onNext: setValue ,
8886 * do(step ) {
89- // 1. 기본 정보 수집
9087 const profile = yield * step (" profile" , ProfileForm , {});
91-
92- // 2. 미성년자인 경우 보호자 동의 필요
88+
9389 let parentConsent: ParentConsent | undefined ;
9490 if (profile .age < 18 ) {
9591 parentConsent = yield * step (" parentConsent" , ParentConsentForm , {});
9692 }
97-
98- // 3. 환경설정 (미성년자는 뉴스레터 옵션 제외)
99- const preferences = yield * step (" preferences" , PreferencesForm , {
100- isMinor: profile .age < 18
93+
94+ const preferences = yield * step (" preferences" , PreferencesForm , {
95+ isMinor: profile .age < 18
10196 });
102-
97+
10398 return { profile , parentConsent , preferences };
10499 }
105100 }, []);
106-
101+
107102 const profile = get (" profile" );
108103 const currentStep = values .length + 1 ;
109104 const totalSteps = profile ?.age < 18 ? 3 : 2 ;
110-
105+
111106 return (
112107 <div >
113108 <div >진행률: { currentStep } /{ totalSteps } </div >
114-
109+
115110 { done ? (
116111 <div >
117112 <h1 >가입 완료!</h1 >
@@ -122,7 +117,7 @@ export function SignUpApp() {
122117 <div >
123118 { values .length > 0 && (
124119 <button onClick = { () => setValue (values .slice (0 , - 1 ))} >
125- 이전 단계
120+ 이전
126121 </button >
127122 )}
128123 { elements .at (- 1 )}
@@ -133,24 +128,18 @@ export function SignUpApp() {
133128}
134129```
135130
136- ## 2. 동적 아이템 추가 플로우
131+ ## 2. 동적 아이템 수집
137132
138- 사용자가 원하는 만큼 아이템을 추가할 수 있는 동적 폼입니다.
133+ 동적 폼 루프를 구현하는 방법을 보여주는 예제입니다:
139134
140135``` tsx
141136type User = { name: string ; role: ' user' | ' admin' };
142137type Item = { title: string ; description: string };
143138type ContinueChoice = { continue: boolean };
144139
145- type DynamicSchema = {
146- user: User ;
147- [key : ` item-${number } ` ]: Item ;
148- [key : ` continue-${number } ` ]: ContinueChoice ;
149- };
150-
151140const ItemForm = (props : MozardStepProps <Item > & { index: number }) => {
152141 const { register, handleSubmit } = useForm <Item >();
153-
142+
154143 return (
155144 <form onSubmit = { handleSubmit (props .onSubmit )} >
156145 <h2 >아이템 #{ props .index + 1 } </h2 >
@@ -163,65 +152,63 @@ const ItemForm = (props: MozardStepProps<Item> & { index: number }) => {
163152
164153const ContinueForm = (props : MozardStepProps <ContinueChoice > & { itemCount: number }) => {
165154 const { register, handleSubmit } = useForm <ContinueChoice >();
166-
155+
167156 return (
168157 <form onSubmit = { handleSubmit (props .onSubmit )} >
169158 <h2 >현재 { props .itemCount } 개 아이템이 추가되었습니다</h2 >
170159 <label >
171160 <input { ... register (" continue" )} type = " checkbox" />
172- 아이템을 더 추가하시겠습니까?
161+ 더 추가하시겠습니까?
173162 </label >
174- <button type = " submit" >결정 </button >
163+ <button type = " submit" >계속 </button >
175164 </form >
176165 );
177166};
178167
179168export function DynamicItemApp() {
180- const [values, setValue] = useState <Entry <DynamicSchema >[]>([]);
181-
182- const { elements, done, value } = useMozard < DynamicSchema , { user : User ; items : Item [] }> ({
169+ const [values, setValue] = useState <Entry <any >[]>([]);
170+
171+ const { elements, done, value } = useMozard ({
183172 values ,
184173 onNext: setValue ,
185174 * do(step ) {
186175 const user = yield * step (" user" , UserForm , {});
187-
176+
188177 const items: Item [] = [];
189178 let shouldContinue = true ;
190-
191- // 첫 번째 아이템은 필수
179+
192180 const firstItem = yield * step (" item-0" , ItemForm , { index: 0 });
193181 items .push (firstItem );
194-
195- // 추가 아이템들은 사용자 선택에 따라
182+
196183 while (shouldContinue ) {
197184 const { continue : wantMore } = yield * step (
198- ` continue-${items .length } ` ,
199- ContinueForm ,
185+ ` continue-${items .length } ` ,
186+ ContinueForm ,
200187 { itemCount: items .length }
201188 );
202-
189+
203190 if (wantMore ) {
204191 const nextItem = yield * step (
205- ` item-${items .length } ` ,
206- ItemForm ,
192+ ` item-${items .length } ` ,
193+ ItemForm ,
207194 { index: items .length }
208195 );
209196 items .push (nextItem );
210197 } else {
211198 shouldContinue = false ;
212199 }
213200 }
214-
201+
215202 return { user , items };
216203 }
217204 }, []);
218-
205+
219206 return (
220207 <div >
221208 { done ? (
222209 <div >
223210 <h1 >완료!</h1 >
224- <h2 >{ value .user .name } 님의 아이템 목록 :</h2 >
211+ <h2 >{ value .user .name } 님의 아이템들 :</h2 >
225212 <ul >
226213 { value .items .map ((item , i ) => (
227214 <li key = { i } >{ item .title } : { item .description } </li >
@@ -236,9 +223,9 @@ export function DynamicItemApp() {
236223}
237224```
238225
239- ## 3. 복잡한 조건부 분기
226+ ## 3. 복잡한 조건부 플로우
240227
241- 관리자와 일반 사용자에 따라 완전히 다른 플로우를 가지는 예제입니다.
228+ 사용자 타입에 따른 다중 분기 경로를 보여주는 예제입니다:
242229
243230``` tsx
244231type UserType = { type: ' admin' | ' user' ; email: string };
@@ -248,29 +235,26 @@ type Verification = { code: string };
248235
249236export function ConditionalFlowApp() {
250237 const [values, setValue] = useState <Entry <any >[]>([]);
251-
238+
252239 const { elements, done, value } = useMozard ({
253240 values ,
254241 onNext: setValue ,
255242 * do(step ) {
256243 const userType = yield * step (" userType" , UserTypeForm , {});
257-
244+
258245 if (userType .type === " admin" ) {
259- // 관리자 플로우
260246 const verification = yield * step (" verification" , VerificationForm , {});
261247 const adminSettings = yield * step (" adminSettings" , AdminSettingsForm , {});
262-
248+
263249 return {
264250 type: " admin" as const ,
265251 email: userType .email ,
266252 verification ,
267253 settings: adminSettings
268254 };
269255 } else {
270- // 일반 사용자 플로우
271256 const profile = yield * step (" userProfile" , UserProfileForm , {});
272-
273- // 관심사가 3개 이상이면 추가 설문
257+
274258 if (profile .interests .length >= 3 ) {
275259 const survey = yield * step (" survey" , SurveyForm , { interests: profile .interests });
276260 return {
@@ -280,7 +264,7 @@ export function ConditionalFlowApp() {
280264 survey
281265 };
282266 }
283-
267+
284268 return {
285269 type: " user" as const ,
286270 email: userType .email ,
@@ -289,7 +273,7 @@ export function ConditionalFlowApp() {
289273 }
290274 }
291275 }, []);
292-
276+
293277 return (
294278 <div >
295279 { done ? (
@@ -324,11 +308,15 @@ yield* step("item", ItemForm, {}); // 반복문에서 중복 가능
324308``` typescript
325309* do (step ) {
326310 const user = yield * step (" user" , UserForm , {});
327-
311+
328312 if (user .type === " admin" ) {
329313 const settings = yield * step (" adminSettings" , AdminSettingsForm , {});
330314 return { user , adminSettings: settings }; // 명시적 키 이름
331315 }
332-
316+
333317 const profile = yield * step (" userProfile" , UserProfileForm , {});
334- return { user , profile };
318+ return { user , userProfile: profile };
319+ }
320+ ```
321+
322+ 이러한 예제들은 Mozard의 모나딕 합성이 어떻게 복잡한 폼 플로우를 간결하고 타입 안전하게 표현할 수 있는지 보여줍니다. Generator의 ` yield* ` 구문을 통해 각 단계를 자연스럽게 연결하고, JavaScript의 제어 구조(` if ` , ` while ` , ` for ` )를 그대로 사용할 수 있습니다.
0 commit comments