@ehfuse/forma 2.0.10 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +333 -185
- package/dist/core/FieldStore.d.ts +41 -0
- package/dist/core/FieldStore.d.ts.map +1 -1
- package/dist/core/FieldStore.js +121 -7
- package/dist/core/FieldStore.js.map +1 -1
- package/dist/esm/core/FieldStore.d.ts +41 -0
- package/dist/esm/core/FieldStore.d.ts.map +1 -1
- package/dist/esm/core/FieldStore.js +121 -7
- package/dist/esm/core/FieldStore.js.map +1 -1
- package/dist/esm/hooks/useForm.js +6 -3
- package/dist/esm/hooks/useForm.js.map +1 -1
- package/dist/esm/hooks/useFormaState.d.ts +3 -1
- package/dist/esm/hooks/useFormaState.d.ts.map +1 -1
- package/dist/esm/hooks/useFormaState.js +31 -3
- package/dist/esm/hooks/useFormaState.js.map +1 -1
- package/dist/esm/hooks/useGlobalForm.d.ts +8 -12
- package/dist/esm/hooks/useGlobalForm.d.ts.map +1 -1
- package/dist/esm/hooks/useGlobalForm.js +36 -2
- package/dist/esm/hooks/useGlobalForm.js.map +1 -1
- package/dist/esm/hooks/useGlobalFormaState.d.ts +8 -72
- package/dist/esm/hooks/useGlobalFormaState.d.ts.map +1 -1
- package/dist/esm/hooks/useGlobalFormaState.js +8 -2
- package/dist/esm/hooks/useGlobalFormaState.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -44
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/form.d.ts +18 -0
- package/dist/esm/types/form.d.ts.map +1 -1
- package/dist/esm/types/globalForm.d.ts +23 -2
- package/dist/esm/types/globalForm.d.ts.map +1 -1
- package/dist/esm/types/globalForm.js.map +1 -1
- package/dist/hooks/useForm.js +6 -3
- package/dist/hooks/useForm.js.map +1 -1
- package/dist/hooks/useFormaState.d.ts +3 -1
- package/dist/hooks/useFormaState.d.ts.map +1 -1
- package/dist/hooks/useFormaState.js +31 -3
- package/dist/hooks/useFormaState.js.map +1 -1
- package/dist/hooks/useGlobalForm.d.ts +8 -12
- package/dist/hooks/useGlobalForm.d.ts.map +1 -1
- package/dist/hooks/useGlobalForm.js +36 -2
- package/dist/hooks/useGlobalForm.js.map +1 -1
- package/dist/hooks/useGlobalFormaState.d.ts +8 -72
- package/dist/hooks/useGlobalFormaState.d.ts.map +1 -1
- package/dist/hooks/useGlobalFormaState.js +8 -2
- package/dist/hooks/useGlobalFormaState.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -67
- package/dist/index.js.map +1 -1
- package/dist/types/form.d.ts +18 -0
- package/dist/types/form.d.ts.map +1 -1
- package/dist/types/globalForm.d.ts +23 -2
- package/dist/types/globalForm.d.ts.map +1 -1
- package/dist/types/globalForm.js.map +1 -1
- package/package.json +4 -10
- package/dist/esm/index.min.js +0 -28
- package/dist/index.min.js +0 -28
package/README.md
CHANGED
|
@@ -11,61 +11,192 @@ Forma is a high-performance library that makes form and state management in Reac
|
|
|
11
11
|
|
|
12
12
|
Forma는 React 애플리케이션에서 폼과 상태를 **간편하면서도 강력하게** 관리할 수 있는 고성능 라이브러리입니다. **Zero-Config로 바로 시작**할 수 있으며, 개별 필드 구독을 통한 **선택적 리렌더링**으로 최적의 성능을 제공합니다. 복잡한 설정 없이도 **글로벌 폼 상태 공유**, **Dot Notation 중첩 객체 접근**, **MUI 완전 호환** 등 프로덕션 레벨의 고급 기능들을 손쉽게 사용할 수 있습니다.
|
|
13
13
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
14
|
+
## Why Forma? | 왜 Forma인가?
|
|
15
|
+
|
|
16
|
+
Forma는 단순한 폼 라이브러리가 아닙니다. **React 상태 관리의 패러다임을 바꾸는** 혁신적인 솔루션입니다.
|
|
17
|
+
|
|
18
|
+
### 🚀 The Ultimate State Management Solution | 최강의 상태 관리 솔루션
|
|
19
|
+
|
|
20
|
+
#### 1. **Watch + Actions = No More useEffect & useState & Context**
|
|
21
|
+
|
|
22
|
+
**useEffect, useState, Context 지옥에서 탈출하세요**
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// ❌ Traditional: useEffect + useState + Context + Props Drilling
|
|
26
|
+
const AuthContext = createContext(null);
|
|
27
|
+
|
|
28
|
+
function App() {
|
|
29
|
+
const [logined, setLogined] = useState(false);
|
|
30
|
+
const [syncInterval, setSyncInterval] = useState(null);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (logined) {
|
|
34
|
+
const interval = setInterval(() => syncData(), 5000);
|
|
35
|
+
setSyncInterval(interval);
|
|
36
|
+
} else {
|
|
37
|
+
if (syncInterval) clearInterval(syncInterval);
|
|
38
|
+
setSyncInterval(null);
|
|
39
|
+
}
|
|
40
|
+
}, [logined]);
|
|
41
|
+
|
|
42
|
+
// Props drilling or Context Provider needed
|
|
43
|
+
return (
|
|
44
|
+
<AuthContext.Provider value={{ logined, setLogined }}>
|
|
45
|
+
<Header />
|
|
46
|
+
<Main />
|
|
47
|
+
<Footer />
|
|
48
|
+
</AuthContext.Provider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ✅ Forma: Clean and declarative
|
|
53
|
+
const state = useGlobalFormaState({
|
|
54
|
+
stateId: "auth",
|
|
55
|
+
initialValues: {
|
|
56
|
+
logined: false,
|
|
57
|
+
user: { name: "", email: "" },
|
|
58
|
+
syncInterval: null,
|
|
59
|
+
},
|
|
60
|
+
actions: {
|
|
61
|
+
startSync: (ctx) => {
|
|
62
|
+
const interval = setInterval(() => syncData(), 5000);
|
|
63
|
+
ctx.setValue("syncInterval", interval);
|
|
64
|
+
},
|
|
65
|
+
stopSync: (ctx) => {
|
|
66
|
+
const interval = ctx.getValue("syncInterval");
|
|
67
|
+
if (interval) clearInterval(interval);
|
|
68
|
+
ctx.setValue("syncInterval", null);
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
watch: {
|
|
72
|
+
logined: (ctx, value) => {
|
|
73
|
+
value ? ctx.actions.startSync(ctx) : ctx.actions.stopSync(ctx);
|
|
74
|
+
},
|
|
75
|
+
"user.email": (ctx, value) => {
|
|
76
|
+
console.log("Email changed:", value);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Benefits | 이점:**
|
|
83
|
+
|
|
84
|
+
- 🧹 **No useEffect clutter** | useEffect 없이 깔끔한 코드
|
|
85
|
+
- � **No Context needed** | Context API 불필요
|
|
86
|
+
- 🎯 **No Props Drilling** | Props 전달 지옥 탈출
|
|
87
|
+
- �📦 **Modular logic** | 로직을 별도 파일로 분리 가능
|
|
88
|
+
- 🧪 **Easy testing** | actions/watch 단독 테스트 용이
|
|
89
|
+
- 🎯 **Better code cohesion** | 높은 코드 응집도
|
|
90
|
+
|
|
91
|
+
#### 2. **Surgical Re-rendering**
|
|
92
|
+
|
|
93
|
+
**수술적 정밀도의 리렌더링**
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// ❌ Redux/Context: Entire component re-renders
|
|
97
|
+
const { user, todos, settings } = useStore(); // or useContext(AppContext)
|
|
98
|
+
// All fields change = entire component re-renders
|
|
99
|
+
|
|
100
|
+
// ✅ Forma: Only what you need
|
|
101
|
+
const userName = state.useValue("user.name"); // Only this field
|
|
102
|
+
const todoCount = state.useValue("todos.length"); // Only array length
|
|
103
|
+
const theme = state.useValue("settings.theme"); // Only theme
|
|
104
|
+
// Each component subscribes to ONLY what it needs
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Performance | 성능:**
|
|
108
|
+
|
|
109
|
+
- ⚡ **10-100x faster** than Redux for large forms | 대규모 폼에서 Redux 대비 10-100배 빠름
|
|
110
|
+
- 🎯 **Field-level optimization** | 필드 단위 최적화
|
|
111
|
+
- 📊 **No selectors needed** | 셀렉터 불필요
|
|
112
|
+
- 🔥 **Zero wasted renders** | 불필요한 렌더링 제로
|
|
113
|
+
|
|
114
|
+
#### 3. **Form + State + Global Access in One**
|
|
115
|
+
|
|
116
|
+
**폼, 상태, 글로벌 접근을 하나로**
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// ❌ Traditional: Multiple libraries + Context boilerplate
|
|
120
|
+
import { useForm } from "react-hook-form";
|
|
121
|
+
import { create } from "zustand";
|
|
122
|
+
import { createContext, useContext } from "react";
|
|
123
|
+
|
|
124
|
+
// Context setup, Provider wrapping, Props drilling...
|
|
125
|
+
const FormContext = createContext(null);
|
|
126
|
+
|
|
127
|
+
function App() {
|
|
128
|
+
const form = useForm();
|
|
129
|
+
return (
|
|
130
|
+
<FormContext.Provider value={form}>
|
|
131
|
+
<Header />
|
|
132
|
+
<MainContent />
|
|
133
|
+
</FormContext.Provider>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ✅ Forma: One library, zero boilerplate
|
|
138
|
+
import { useGlobalForm, useGlobalFormaState } from "@ehfuse/forma";
|
|
139
|
+
|
|
140
|
+
function Header() {
|
|
141
|
+
// Access anywhere, no Provider needed!
|
|
142
|
+
const state = useGlobalFormaState<AuthState>({ stateId: "auth" });
|
|
143
|
+
const userName = state.useValue("user.name");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function MainContent() {
|
|
147
|
+
// Same state, no props drilling
|
|
148
|
+
const state = useGlobalFormaState<AuthState>({ stateId: "auth" });
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**All-in-One | 올인원:**
|
|
153
|
+
|
|
154
|
+
- 📝 **Form management** | 폼 관리
|
|
155
|
+
- 🌐 **Global state (no Context!)** | 전역 상태 (Context 불필요!)
|
|
156
|
+
- 🚫 **No Props Drilling** | Props 전달 불필요
|
|
157
|
+
- 👀 **Reactive watch** | 반응형 감시
|
|
158
|
+
- 🎬 **Actions system** | 액션 시스템
|
|
159
|
+
- 🎭 **Modal management** | 모달 관리
|
|
160
|
+
- 📱 **Breakpoint detection** | 반응형 감지
|
|
161
|
+
|
|
162
|
+
## Key Features | 주요 특징
|
|
163
|
+
|
|
164
|
+
- 🎯 **Zero-Config**: Start immediately | 설정 없이 즉시 시작
|
|
165
|
+
- 👀 **Watch System**: Replace useEffect with declarative watchers | useEffect를 선언적 watcher로 대체
|
|
166
|
+
- � **Actions Pattern**: Modular business logic | 모듈화된 비즈니스 로직
|
|
167
|
+
- ✅ **Individual Field Subscription**: Surgical re-rendering | 수술적 리렌더링
|
|
168
|
+
- 🌟 **Dot Notation**: Deep nested access `user.profile.name` | 깊은 중첩 접근
|
|
169
|
+
- 🌐 **Global State Sharing**: Share across components | 컴포넌트 간 공유
|
|
170
|
+
- 🎭 **Modal Stack**: Mobile-friendly with back button | 뒤로가기 지원 모달
|
|
171
|
+
- 📱 **Breakpoint Management**: Responsive UI made easy | 반응형 UI 간편화
|
|
172
|
+
- ✅ **Full MUI Compatibility**: Perfect Material-UI integration | MUI 완벽 통합
|
|
173
|
+
- ✅ **TypeScript Native**: Full type safety | 완전한 타입 안전성
|
|
43
174
|
|
|
44
175
|
## Documentation | 문서
|
|
45
176
|
|
|
46
177
|
### English
|
|
47
178
|
|
|
48
|
-
- **[Getting Started Guide](./docs/getting-started
|
|
49
|
-
- **[API Reference](./docs/API
|
|
50
|
-
- **[Examples Collection](./docs/examples
|
|
51
|
-
- **[Performance Guide](./docs/performance-guide
|
|
52
|
-
- **[Performance Warnings](./docs/performance-warnings
|
|
53
|
-
- **[Migration Guide](./docs/migration
|
|
54
|
-
- **[useGlobalForm Guide](./docs/useGlobalForm-guide
|
|
55
|
-
- **[Global Hooks Comparison](./docs/global-hooks-comparison
|
|
56
|
-
- **[Library Comparison](./docs/library-comparison
|
|
179
|
+
- **[Getting Started Guide](./docs/en/getting-started.md)** - Step-by-step tutorial and examples
|
|
180
|
+
- **[API Reference](./docs/en/API.md)** - Complete API documentation with examples
|
|
181
|
+
- **[Examples Collection](./docs/en/examples.md)** - Practical usage examples and patterns
|
|
182
|
+
- **[Performance Guide](./docs/en/performance-guide.md)** - Performance optimization techniques
|
|
183
|
+
- **[Performance Warnings](./docs/en/performance-warnings.md)** - Anti-patterns and common pitfalls
|
|
184
|
+
- **[Migration Guide](./docs/en/migration.md)** - Migrate from other form libraries
|
|
185
|
+
- **[useGlobalForm Guide](./docs/en/useGlobalForm-guide.md)** - Global form state management
|
|
186
|
+
- **[Global Hooks Comparison](./docs/en/global-hooks-comparison.md)** - useGlobalForm vs useGlobalFormaState
|
|
187
|
+
- **[Library Comparison](./docs/en/library-comparison.md)** - Forma vs other libraries
|
|
57
188
|
|
|
58
189
|
### 한국어 (Korean)
|
|
59
190
|
|
|
60
|
-
- **[시작 가이드](./docs/getting-started
|
|
61
|
-
- **[API 레퍼런스](./docs/API
|
|
62
|
-
- **[예제 모음](./docs/examples
|
|
63
|
-
- **[성능 최적화 가이드](./docs/performance-guide
|
|
64
|
-
- **[성능 최적화 주의사항](./docs/performance-warnings
|
|
65
|
-
- **[마이그레이션 가이드](./docs/migration
|
|
66
|
-
- **[useGlobalForm 가이드](./docs/useGlobalForm-guide
|
|
67
|
-
- **[글로벌 훅 비교](./docs/global-hooks-comparison
|
|
68
|
-
- **[라이브러리 비교](./docs/library-comparison
|
|
191
|
+
- **[시작 가이드](./docs/ko/getting-started.md)** - 단계별 튜토리얼과 예제
|
|
192
|
+
- **[API 레퍼런스](./docs/ko/API.md)** - 완전한 API 문서와 예제
|
|
193
|
+
- **[예제 모음](./docs/ko/examples.md)** - 실용적인 사용 예제와 패턴
|
|
194
|
+
- **[성능 최적화 가이드](./docs/ko/performance-guide.md)** - 성능 최적화 기법
|
|
195
|
+
- **[성능 최적화 주의사항](./docs/ko/performance-warnings.md)** - 안티패턴과 일반적인 함정
|
|
196
|
+
- **[마이그레이션 가이드](./docs/ko/migration.md)** - 다른 폼 라이브러리에서 이전
|
|
197
|
+
- **[useGlobalForm 가이드](./docs/ko/useGlobalForm-guide.md)** - 글로벌 폼 상태 관리
|
|
198
|
+
- **[글로벌 훅 비교](./docs/ko/global-hooks-comparison.md)** - useGlobalForm vs useGlobalFormaState
|
|
199
|
+
- **[라이브러리 비교](./docs/ko/library-comparison.md)** - Forma vs 다른 라이브러리
|
|
69
200
|
|
|
70
201
|
### Links | 링크
|
|
71
202
|
|
|
@@ -90,154 +221,102 @@ yarn add @ehfuse/forma
|
|
|
90
221
|
|
|
91
222
|
## Quick Start | 빠른 시작
|
|
92
223
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
**Start immediately without any configuration!**
|
|
96
|
-
**설정 없이 바로 시작하세요!**
|
|
97
|
-
|
|
98
|
-
```tsx
|
|
99
|
-
import { useForm, useFormaState } from "@ehfuse/forma";
|
|
100
|
-
|
|
101
|
-
function ZeroConfigForm() {
|
|
102
|
-
// Zero-Config: Start without any parameters
|
|
103
|
-
// Zero-Config: 매개변수 없이 바로 사용
|
|
104
|
-
const form = useForm<{ name: string; email: string }>();
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<div>
|
|
108
|
-
<input
|
|
109
|
-
placeholder="Name"
|
|
110
|
-
value={form.useFormValue("name")}
|
|
111
|
-
onChange={(e) => form.setFormValue("name", e.target.value)}
|
|
112
|
-
/>
|
|
113
|
-
<input
|
|
114
|
-
placeholder="Email"
|
|
115
|
-
value={form.useFormValue("email")}
|
|
116
|
-
onChange={(e) => form.setFormValue("email", e.target.value)}
|
|
117
|
-
/>
|
|
118
|
-
<button onClick={() => console.log(form.getFormValues())}>
|
|
119
|
-
Log Values
|
|
120
|
-
</button>
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function ZeroConfigState() {
|
|
126
|
-
// Zero-Config: General state without configuration
|
|
127
|
-
// Zero-Config: 일반 상태도 설정 없이 사용
|
|
128
|
-
const state = useFormaState<{ count: number }>();
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<div>
|
|
132
|
-
<p>Count: {state.useValue("count") || 0}</p>
|
|
133
|
-
<button
|
|
134
|
-
onClick={() =>
|
|
135
|
-
state.setValue("count", (state.getValue("count") || 0) + 1)
|
|
136
|
-
}
|
|
137
|
-
>
|
|
138
|
-
Increment
|
|
139
|
-
</button>
|
|
140
|
-
</div>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
224
|
+
```bash
|
|
225
|
+
npm install @ehfuse/forma
|
|
143
226
|
```
|
|
144
227
|
|
|
145
|
-
###
|
|
228
|
+
### Real-World Example: Todo App with Watch | 실전 예제: Watch를 활용한 Todo 앱
|
|
146
229
|
|
|
147
230
|
```tsx
|
|
148
|
-
import {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
231
|
+
import { useGlobalFormaState } from "@ehfuse/forma";
|
|
232
|
+
|
|
233
|
+
// 🎯 Separate actions file for better organization
|
|
234
|
+
// 액션을 별도 파일로 분리하여 코드 응집도 향상
|
|
235
|
+
const todoActions = {
|
|
236
|
+
addTodo: (ctx, text: string) => {
|
|
237
|
+
const todos = ctx.values.todos;
|
|
238
|
+
ctx.setValue("todos", [
|
|
239
|
+
...todos,
|
|
240
|
+
{
|
|
241
|
+
id: Date.now(),
|
|
242
|
+
text,
|
|
243
|
+
completed: false,
|
|
244
|
+
},
|
|
245
|
+
]);
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
toggleTodo: (ctx, id: number) => {
|
|
249
|
+
const todos = ctx.values.todos.map((t) =>
|
|
250
|
+
t.id === id ? { ...t, completed: !t.completed } : t
|
|
251
|
+
);
|
|
252
|
+
ctx.setValue("todos", todos);
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
// Auto-save to localStorage
|
|
256
|
+
saveToStorage: (ctx) => {
|
|
257
|
+
localStorage.setItem("todos", JSON.stringify(ctx.values.todos));
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
function TodoApp() {
|
|
262
|
+
const state = useGlobalFormaState({
|
|
263
|
+
stateId: "todo-app",
|
|
264
|
+
initialValues: {
|
|
265
|
+
todos: [],
|
|
266
|
+
filter: "all",
|
|
267
|
+
lastSync: null,
|
|
169
268
|
},
|
|
170
|
-
|
|
171
|
-
|
|
269
|
+
actions: todoActions,
|
|
270
|
+
watch: {
|
|
271
|
+
// 👀 Auto-save when todos change (replaces useEffect!)
|
|
272
|
+
// todos 변경 시 자동 저장 (useEffect 불필요!)
|
|
273
|
+
todos: (ctx, value) => {
|
|
274
|
+
ctx.actions.saveToStorage(ctx);
|
|
275
|
+
ctx.setValue("lastSync", new Date().toISOString());
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// 🎯 Log filter changes
|
|
279
|
+
filter: (ctx, value, prevValue) => {
|
|
280
|
+
console.log(`Filter changed: ${prevValue} → ${value}`);
|
|
281
|
+
},
|
|
172
282
|
},
|
|
173
283
|
});
|
|
174
284
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<input
|
|
178
|
-
name="name"
|
|
179
|
-
value={form.useFormValue("name")}
|
|
180
|
-
onChange={form.handleFormChange}
|
|
181
|
-
/>
|
|
182
|
-
<input
|
|
183
|
-
name="email"
|
|
184
|
-
value={form.useFormValue("email")}
|
|
185
|
-
onChange={form.handleFormChange}
|
|
186
|
-
/>
|
|
187
|
-
<button type="submit">Submit</button>
|
|
188
|
-
</form>
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### General State Management | 일반 상태 관리
|
|
194
|
-
|
|
195
|
-
```tsx
|
|
196
|
-
import { useFormaState } from "@ehfuse/forma";
|
|
197
|
-
|
|
198
|
-
function UserDashboard() {
|
|
199
|
-
const state = useFormaState({
|
|
200
|
-
todos: [
|
|
201
|
-
{ id: 1, text: "Learn React", completed: false },
|
|
202
|
-
{ id: 2, text: "Build app", completed: false },
|
|
203
|
-
],
|
|
204
|
-
filter: "all",
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Individual field subscription - re-renders only when that field changes
|
|
208
|
-
// 개별 필드 구독 - 해당 필드가 변경될 때만 리렌더링
|
|
209
|
-
const filter = state.useValue("filter");
|
|
210
|
-
|
|
211
|
-
// ✅ Subscribe only to array length (re-renders only when items are added/removed)
|
|
212
|
-
// ✅ 배열 길이만 구독 (항목 추가/삭제 시에만 리렌더링)
|
|
285
|
+
// ✅ Surgical re-rendering: Only subscribes to what's needed
|
|
286
|
+
// 수술적 리렌더링: 필요한 것만 구독
|
|
213
287
|
const todosLength = state.useValue("todos.length");
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// ✅ 특정 할 일의 텍스트만 구독 (dot notation 활용)
|
|
217
|
-
const firstTodoText = state.useValue("todos.0.text");
|
|
218
|
-
|
|
219
|
-
const addTodo = () => {
|
|
220
|
-
const todos = state.getValues().todos;
|
|
221
|
-
state.setValue("todos", [
|
|
222
|
-
...todos,
|
|
223
|
-
{ id: Date.now(), text: "New todo", completed: false },
|
|
224
|
-
]);
|
|
225
|
-
};
|
|
288
|
+
const filter = state.useValue("filter");
|
|
289
|
+
const lastSync = state.useValue("lastSync");
|
|
226
290
|
|
|
227
291
|
return (
|
|
228
292
|
<div>
|
|
229
|
-
<
|
|
230
|
-
<p>
|
|
231
|
-
|
|
232
|
-
<button onClick={addTodo}>
|
|
233
|
-
|
|
234
|
-
Show Completed
|
|
293
|
+
<h1>Todos ({todosLength})</h1>
|
|
294
|
+
<p>Last synced: {lastSync}</p>
|
|
295
|
+
|
|
296
|
+
<button onClick={() => state.actions.addTodo(state, "New Task")}>
|
|
297
|
+
Add Todo
|
|
235
298
|
</button>
|
|
299
|
+
|
|
300
|
+
<select
|
|
301
|
+
value={filter}
|
|
302
|
+
onChange={(e) => state.setValue("filter", e.target.value)}
|
|
303
|
+
>
|
|
304
|
+
<option value="all">All</option>
|
|
305
|
+
<option value="active">Active</option>
|
|
306
|
+
<option value="completed">Completed</option>
|
|
307
|
+
</select>
|
|
236
308
|
</div>
|
|
237
309
|
);
|
|
238
310
|
}
|
|
239
311
|
```
|
|
240
312
|
|
|
313
|
+
**What you gain | 얻는 것:**
|
|
314
|
+
|
|
315
|
+
- 🧹 **No useEffect** - Watch handles all side effects | useEffect 제거 - Watch가 모든 부수효과 처리
|
|
316
|
+
- 📦 **Modular actions** - Easy to test and maintain | 모듈화된 액션 - 테스트와 유지보수 용이
|
|
317
|
+
- ⚡ **Optimized rendering** - Only `todosLength`, `filter`, `lastSync` trigger re-renders | 최적화된 렌더링
|
|
318
|
+
- 🔄 **Automatic persistence** - Watch auto-saves changes | 자동 저장 - Watch가 변경사항 자동 저장
|
|
319
|
+
|
|
241
320
|
---
|
|
242
321
|
|
|
243
322
|
## When to choose Forma?
|
|
@@ -314,19 +393,88 @@ Forma는 **폼 상태 관리에 특화**된 라이브러리로 특정 시나리
|
|
|
314
393
|
|
|
315
394
|
---
|
|
316
395
|
|
|
317
|
-
##
|
|
396
|
+
## Architecture Benefits | 아키텍처 이점
|
|
397
|
+
|
|
398
|
+
### 📁 Clean Separation of Concerns | 관심사의 명확한 분리
|
|
318
399
|
|
|
319
400
|
```tsx
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
const
|
|
323
|
-
|
|
401
|
+
// actions.ts - Business logic isolated
|
|
402
|
+
// actions.ts - 비즈니스 로직 분리
|
|
403
|
+
export const authActions = {
|
|
404
|
+
login: async (ctx, credentials) => {
|
|
405
|
+
const user = await api.login(credentials);
|
|
406
|
+
ctx.setValues({ logined: true, user, token: user.token });
|
|
407
|
+
},
|
|
408
|
+
logout: (ctx) => {
|
|
409
|
+
ctx.setValues({ logined: false, user: null, token: null });
|
|
410
|
+
},
|
|
411
|
+
startSync: (ctx) => {
|
|
412
|
+
/* ... */
|
|
413
|
+
},
|
|
414
|
+
stopSync: (ctx) => {
|
|
415
|
+
/* ... */
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// watch.ts - Side effects isolated
|
|
420
|
+
// watch.ts - 부수효과 분리
|
|
421
|
+
export const authWatch = {
|
|
422
|
+
logined: (ctx, value) => {
|
|
423
|
+
value ? ctx.actions.startSync(ctx) : ctx.actions.stopSync(ctx);
|
|
424
|
+
},
|
|
425
|
+
"user.preferences": (ctx, value) => {
|
|
426
|
+
localStorage.setItem("prefs", JSON.stringify(value));
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// component.tsx - Pure UI
|
|
431
|
+
// component.tsx - 순수 UI
|
|
432
|
+
function AuthApp() {
|
|
433
|
+
const state = useGlobalFormaState({
|
|
434
|
+
stateId: "auth",
|
|
435
|
+
actions: authActions,
|
|
436
|
+
watch: authWatch,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Clean, declarative UI
|
|
440
|
+
// 깔끔한 선언적 UI
|
|
441
|
+
return <LoginForm onSubmit={state.actions.login} />;
|
|
442
|
+
}
|
|
443
|
+
```
|
|
324
444
|
|
|
325
|
-
|
|
326
|
-
|
|
445
|
+
**Benefits | 이점:**
|
|
446
|
+
|
|
447
|
+
- 🧪 **Testable**: Test actions/watch independently | 독립적 테스트 가능
|
|
448
|
+
- 📦 **Reusable**: Share logic across projects | 프로젝트 간 로직 공유
|
|
449
|
+
- 🔍 **Maintainable**: Easy to locate and update logic | 로직 위치 파악 및 수정 용이
|
|
450
|
+
- 👥 **Team-friendly**: Clear code organization | 명확한 코드 구조
|
|
451
|
+
|
|
452
|
+
### ⚡ Performance Comparison | 성능 비교
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
// ❌ Redux: Entire component re-renders
|
|
456
|
+
const state = useSelector((state) => state); // Everything triggers re-render
|
|
457
|
+
// 전체 컴포넌트 리렌더링
|
|
458
|
+
|
|
459
|
+
// ❌ Context: All consumers re-render
|
|
460
|
+
const { user, todos, settings } = useContext(AppContext);
|
|
461
|
+
// 모든 컨슈머 리렌더링
|
|
462
|
+
|
|
463
|
+
// ✅ Forma: Surgical precision
|
|
464
|
+
const userName = state.useValue("user.name"); // Only this
|
|
465
|
+
const todoCount = state.useValue("todos.length"); // Only this
|
|
466
|
+
const theme = state.useValue("settings.theme"); // Only this
|
|
467
|
+
// 수술적 정밀도
|
|
327
468
|
```
|
|
328
469
|
|
|
329
|
-
**
|
|
470
|
+
**Real-world impact | 실제 영향:**
|
|
471
|
+
|
|
472
|
+
- 📊 **50+ fields**: 10-100x faster than Redux | Redux 대비 10-100배 빠름
|
|
473
|
+
- ⚡ **Real-time forms**: Smooth 60fps performance | 부드러운 60fps 성능
|
|
474
|
+
- 📱 **Mobile**: Better battery life | 배터리 수명 향상
|
|
475
|
+
- 🎯 **Zero wasted renders**: Every render is intentional | 모든 렌더링이 의도적
|
|
476
|
+
|
|
477
|
+
**[View Detailed Performance Guide](./docs/en/performance-guide.md)** | **[성능 가이드 보기](./docs/ko/performance-guide.md)**
|
|
330
478
|
|
|
331
479
|
---
|
|
332
480
|
|
|
@@ -33,11 +33,16 @@
|
|
|
33
33
|
*
|
|
34
34
|
* @template T 폼 데이터의 타입 / Form data type
|
|
35
35
|
*/
|
|
36
|
+
/**
|
|
37
|
+
* Watch 콜백 타입 / Watch callback type
|
|
38
|
+
*/
|
|
39
|
+
type WatchCallback = (value: any, prevValue: any) => void;
|
|
36
40
|
export declare class FieldStore<T extends Record<string, any>> {
|
|
37
41
|
private fields;
|
|
38
42
|
private dotNotationListeners;
|
|
39
43
|
private initialValues;
|
|
40
44
|
private globalListeners;
|
|
45
|
+
private watchers;
|
|
41
46
|
constructor(initialValues: T);
|
|
42
47
|
/**
|
|
43
48
|
* 특정 필드 값 가져오기 / Get specific field value
|
|
@@ -130,9 +135,45 @@ export declare class FieldStore<T extends Record<string, any>> {
|
|
|
130
135
|
* 초기값으로 리셋 / Reset to initial values
|
|
131
136
|
*/
|
|
132
137
|
reset(): void;
|
|
138
|
+
/**
|
|
139
|
+
* 필드 변경 감시 / Watch field changes
|
|
140
|
+
* @param path 감시할 필드 경로 (dot notation 지원) / Field path to watch (supports dot notation)
|
|
141
|
+
* @param callback 변경 시 실행할 콜백 / Callback to execute on change
|
|
142
|
+
* @param options 옵션 / Options
|
|
143
|
+
* @returns cleanup 함수 / Cleanup function
|
|
144
|
+
*/
|
|
145
|
+
watch(path: string, callback: WatchCallback, options?: {
|
|
146
|
+
immediate?: boolean;
|
|
147
|
+
}): () => void;
|
|
148
|
+
/**
|
|
149
|
+
* Watcher 알림 실행 / Notify watchers
|
|
150
|
+
* @param path 변경된 필드 경로 / Changed field path
|
|
151
|
+
* @param value 새 값 / New value
|
|
152
|
+
* @param prevValue 이전 값 / Previous value
|
|
153
|
+
*/
|
|
154
|
+
private notifyWatchers;
|
|
155
|
+
/**
|
|
156
|
+
* 와일드카드 패턴 매칭 / Wildcard pattern matching
|
|
157
|
+
* @param path 실제 경로 / Actual path (e.g., "todos.0.completed")
|
|
158
|
+
* @param pattern 와일드카드 패턴 / Wildcard pattern (e.g., "todos.*.completed")
|
|
159
|
+
* @returns 매칭 여부 / Whether path matches pattern
|
|
160
|
+
*/
|
|
161
|
+
private matchesWildcard;
|
|
162
|
+
/**
|
|
163
|
+
* 특정 path에 watcher가 등록되어 있는지 확인 / Check if watcher is registered for specific path
|
|
164
|
+
* @param path 확인할 경로 / Path to check
|
|
165
|
+
* @returns watcher 등록 여부 / Whether watcher is registered
|
|
166
|
+
*/
|
|
167
|
+
hasWatcher(path: string): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* 등록된 모든 watcher path 목록 반환 (디버깅용) / Return all registered watcher paths (for debugging)
|
|
170
|
+
* @returns watcher path 배열 / Array of watcher paths
|
|
171
|
+
*/
|
|
172
|
+
getWatchedPaths(): string[];
|
|
133
173
|
/**
|
|
134
174
|
* 리소스 정리 / Clean up resources
|
|
135
175
|
*/
|
|
136
176
|
destroy(): void;
|
|
137
177
|
}
|
|
178
|
+
export {};
|
|
138
179
|
//# sourceMappingURL=FieldStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldStore.d.ts","sourceRoot":"","sources":["../../core/FieldStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH;;;;;;GAMG;AACH,qBAAa,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACjD,OAAO,CAAC,MAAM,CACA;IACd,OAAO,CAAC,oBAAoB,CAA2C;IACvE,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,eAAe,CAAyB;
|
|
1
|
+
{"version":3,"file":"FieldStore.d.ts","sourceRoot":"","sources":["../../core/FieldStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH;;;;;;GAMG;AACH;;GAEG;AACH,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,KAAK,IAAI,CAAC;AAE1D,qBAAa,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACjD,OAAO,CAAC,MAAM,CACA;IACd,OAAO,CAAC,oBAAoB,CAA2C;IACvE,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAA8C;gBAElD,aAAa,EAAE,CAAC;IAW5B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,GAAG;IA8B1C;;;;;;OAMG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI;IAuD3D;;;;;OAKG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI;IAOpC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG;IA0KhD;;;OAGG;IACH,SAAS,IAAI,CAAC;IAUd;;;OAGG;IACH,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IA4C/B;;;OAGG;IACH,gBAAgB,CAAC,gBAAgB,EAAE,CAAC;IAyBpC;;;OAGG;IACH,UAAU,IAAI,OAAO;IAkBrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuBzB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAU/B;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAyC/B;;;;OAIG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAYzD;;;;OAIG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IA2CnC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IA4B5C;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAsF7B;;OAEG;IACH,KAAK;IA6DL;;;;;;OAMG;IACH,KAAK,CACD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAClC,MAAM,IAAI;IAuBb;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAoCtB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAoBvB;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC;;;OAGG;IACH,eAAe,IAAI,MAAM,EAAE;IAI3B;;OAEG;IACH,OAAO;CAMV"}
|