콘텐츠로 이동
import { Nested } from '@hurum/core'

Nested Store는 부모 Store가 자식 Store를 상태 트리의 일부로 구성할 수 있게 해줘요. 다양한 카디널리티를 위한 세 가지 변형이 있어요.

단일 Nested 자식 Store를 선언해요. 부모 Store가 생성될 때 정확히 하나의 자식 인스턴스가 생성돼요.

function Nested<TStore>(store: TStore): NestedMarker<TStore, 'single'>
매개변수타입설명
storeStoreDefinition자식 Store 정의.
const OrderStore = Store({
state: {
orderId: '',
transaction: Nested(TransactionStore),
},
})

Nested 자식 Store 배열을 선언해요. 각 항목은 id 필드로 식별돼요. 부모의 raw 상태 배열이 변경되면 항목이 동적으로 추가 및 제거돼요.

Nested.array<TStore>(store: TStore): NestedMarker<TStore, 'array'>
매개변수타입설명
storeStoreDefinition자식 Store 정의. 각 자식의 상태에 id 필드가 있어야 해요.
const TodoListStore = Store({
state: {
todos: Nested.array(TodoStore),
},
})

자식 Store는 id를 기준으로 raw 상태 배열과 조정돼요:

  • 배열의 새 항목은 새 자식 인스턴스를 생성해요.
  • 제거된 항목은 해당 자식 인스턴스를 해제해요.
  • 기존 항목은 자식 인스턴스를 유지해요 (재생성 없음).

키가 지정된 Nested 자식 Store 맵을 선언해요. 키는 문자열이에요. 부모의 raw 상태 레코드가 변경되면 항목이 동적으로 추가 및 제거돼요.

Nested.map<TStore>(store: TStore): NestedMarker<TStore, 'map'>
매개변수타입설명
storeStoreDefinition자식 Store 정의.
const DashboardStore = Store({
state: {
panels: Nested.map(PanelStore),
},
})

Nested 자식 상태는 getState()가 반환하는 부모의 결합된 상태에 포함돼요:

Nested 타입상태 형태
Nested(Child){ childKey: ChildState }
Nested.array(Child){ childKey: ChildState[] }
Nested.map(Child){ childKey: Record<string, ChildState> }

해석된 상태에는 자식의 raw 상태와 Computed 값이 포함돼요.


store.scope로 자식 Store 인스턴스에 접근해요:

const store = OrderStore.create()
// Single: direct instance
store.scope.transaction.send(TransactionIntents.start({ amount: 100 }))
// Array: array of instances
store.scope.todos.forEach((todo) => {
todo.send(TodoIntents.markDone())
})
// Map: Map<string, StoreInstance>
const panel = store.scope.panels.get('sidebar')
panel?.send(PanelIntents.toggle())
Nested 타입scope 타입
Nested(Child)StoreInstance
Nested.array(Child)StoreInstance[]
Nested.map(Child)Map<string, StoreInstance>

scope는 Executor에서도 context.scope로 사용할 수 있어요.


부모 Store가 Event를 적용하면 모든 자식 Store에 전달돼요. 자식에 해당 Event 타입에 대한 on 핸들러가 있으면 자식의 상태가 업데이트돼요.

자식 Store가 Event를 발행하면 부모로 버블링돼요. 부모의 Event 구독자 (subscribe('events', cb))가 이를 받고, 부모의 relay 핸들러가 반응할 수 있어요.

부모에서 .relay()를 사용해서 자식 Event를 부모 Event로 변환해요:

.relay(TodoEvent.completed, (event, state) => {
const allDone = state.todos.every((t) => t.completed)
return allDone ? [TodoListEvent.allCompleted()] : []
})

.childDeps()를 사용해서 부모 의존성을 자식 의존성으로 매핑해요:

const ParentStore = Store({ state: { child: Nested(ChildStore) } })
.deps<{ api: ApiClient }>()
.childDeps('child', (parentDeps) => ({
childApi: parentDeps.api.child,
}))

Nested.arrayNested.map의 경우 Store.create({ initialState })로 초기 데이터를 제공해요:

const store = TodoListStore.create({
initialState: {
todos: [
{ id: '1', title: 'Buy milk', completed: false },
{ id: '2', title: 'Write docs', completed: true },
],
},
})

배열의 각 항목은 해당 항목을 initialState로 사용해서 자식 Store 인스턴스를 생성해요.


  • Nested.array 자식에는 id 필드가 필수예요. 조정에 필요해요.
  • Nested.array의 자식 on 핸들러는 id로 가드해야 해요: if (state.id !== payload.id) return state.
  • 부모 해제 시 모든 Nested 자식에 연쇄적으로 전파돼요.
  • relay 깊이는 기본적으로 5로 제한돼요. 깊이 3을 초과하면 개발 경고가 트리거돼요.
  • Event 포워딩은 Event당 단방향이에요: 전달된 Event는 다시 버블링되지 않아요.