- state:Observer(fn)는 등록 즉시 1회 실행되는 것으로 확정 — 초기화 순서 디버깅 문제를 피하고, store-bind 프로퍼티 핸들러가 "초기값 적용"과 "이후 변경 반영"을 같은 코드 경로로 통일할 수 있게 됨. - Effect(fn, state?)로 확정 — state 생략 시 기존 스펙(설치 1회 + 확정 정리) 유지, state 지정 시 내부적으로 state:Observer(...)를 조합해 재실행 + 자동 cleanup 배선(React useEffect와 동형). 다수 의존성은 :With(...)로 묶어서 넘김. 여전히 자유 함수(메소드 아님) — leaf 생명주기 바인딩을 state가 소유하지 않아서. - fn 커링 스타일을 Effect/Observer 공통 모듈화 관용구로 권장, state:Apply 커링 조합기 아이디어는 백로그로만 기록. - question.md 0번의 Effect/Observer 열린 질문 해소. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
83 lines
5.9 KiB
Markdown
83 lines
5.9 KiB
Markdown
# Effect — 설치 + 확정 정리, `state` 있으면 Observer를 감싸 재실행도 지원
|
|
|
|
**상태**: base — `research/additional-primitives-plan.md`(다른 프레임워크
|
|
대비 갭 분석)에서 갈라져 나온 확정 프리미티브. `base/blocker-plan.md`(같은
|
|
조사에서 나온 다른 확정 프리미티브)와는 서로 무관 — Store/State 작업이나
|
|
Ref/PreRef와 파생 관계는 아니라 별도 파일로 둔다(2026-08-07 문서 정리에서
|
|
한 파일로 합쳤던 걸 다시 분리). 단 `state` 인자를 받는 형태는 내부적으로
|
|
Observer를 조합해서 만들어짐(아래 참고, 2026-08-07 여섯 번째 세션 확정) —
|
|
"Observer와 무관한 완전 독립 프리미티브"였던 이전 서술은 정정됨.
|
|
|
|
**Effect와 Observer의 관계 확정(2026-08-07 여섯 번째 세션)**: 별개의
|
|
독립 프리미티브이되, `state`를 받는 형태의 Effect는 내부적으로 Observer를
|
|
**조합(compose)**해서 만들어짐 — Ref/PreRef처럼 브랜드 태그만 다른 재사용이
|
|
아니라, Observer(재실행 신호) 위에 자동 cleanup 배선을 얹은 한 단계 위
|
|
계층. **자유 함수인 이유는 여전히 유효**: `state` 없이도 성립하는
|
|
mount/unmount 전용 유스케이스가 있고, 실제 leaf 생명주기 바인딩은 (Observer와
|
|
마찬가지로) children 배열 위치에 거는 것이라 `state`가 그 바인딩을 소유하지
|
|
않음 — Roblox엔 `task.spawn`으로 코루틴에 반복문/타이머를 돌리는 패턴이
|
|
흔하고, Luau 테이블엔 `__gc` 같은 GC 시점 훅이 없어서 "이게 진짜 사라지는
|
|
순간"을 아는 유일한 방법은 `Instance.Destroying`류 명시적 신호뿐 — 이런
|
|
케이스(타이머 시작 → leaf가 죽을 때 반드시 정지)를 위한 별도 primitive로
|
|
합의됨.
|
|
|
|
```
|
|
Effect(fn, state?) -> EffectHandle
|
|
```
|
|
|
|
**`state` 생략 시**: `fn()`을 즉시 1회 실행, 리턴값(`nil | () -> ()`)은
|
|
이 Effect가 바인드된 leaf가 죽을 때 정확히 1회 호출. 재실행 없음
|
|
(mount/unmount 전용, React `useEffect(fn, [])`와 동형).
|
|
|
|
**`state` 지정 시(2026-08-07 여섯 번째 세션 확정)**: Effect는 내부적으로
|
|
`state:Observer(...)`를 감싸는 걸로 구현 — `fn`은 포지셔널 인자로 `state`를
|
|
받고(`fn(state)`, `:Compute`의 `fn(self)` 포지셔널-self 패턴 재사용,
|
|
모듈화 목적 — 클로저 캡처 없이 `fn`을 독립적으로 정의/재사용 가능),
|
|
Observer가 이제 등록 즉시 1회 실행되므로(아래 Observer 절 참고) 그 첫
|
|
실행이 "설치"를 겸함. 이후 `state`가 무효화될 때마다 **직전 `fn` 호출이
|
|
리턴한 cleanup을 먼저 호출한 뒤 `fn`을 재호출**, 그리고 Effect가 바인드된
|
|
leaf가 죽을 때 **마지막 cleanup을 한 번 더 호출**. 결과적으로 React
|
|
`useEffect(fn, [dep])`와 동형(설치+재실행 사이/최종 cleanup 전부 같은
|
|
반환 계약 하나로 처리).
|
|
|
|
- **다수 의존성은 `:With(...)`로 먼저 하나의 State로 묶어서 넘길 것** —
|
|
React식 별도 deps 배열을 새로 만들지 않음, quad가 이미 가진 다중 의존성
|
|
결합 관용구(`base/bind-system-plan.md` "`:With` + `:Compute`" 절)를
|
|
그대로 재사용해 같은 일 하는 두 번째 경로를 안 만듦.
|
|
- **`fn`은 커링 스타일도 권장(2026-08-07 여섯 번째 세션, 사용자 제안)** —
|
|
`Effect(makeLogger("mount"), state)`처럼 팩토리 함수가 실제 `fn(state)`를
|
|
만들어 반환하는 패턴, `Modifier`의 `Boldify(10)` 커링 관용구(`modifier-plan.md`
|
|
8번)와 같은 결. `state:Observer(fn)`도 동일하게 커링 스타일을 권장 대상으로
|
|
같이 문서화(아래 Observer 절 참고) — 모듈화가 필요하면 둘 다 이 패턴을 쓸 것.
|
|
- **재실행이 필요 없는 케이스와 혼동하지 말 것**: 값 변화와 무관하게 설치+최종
|
|
정리만 필요하면 `state` 없이 `Effect(fn)`을 씀 — `state`를 굳이 넘겨서
|
|
재실행을 유발할 필요 없음.
|
|
|
|
children 배열에 leaf로 놓는 기존 Observer 바인딩 패턴을 그대로 재사용(그
|
|
leaf가 살아있는 동안만 유효, leaf가 죽으면 최종 정리 콜백 호출). 비용은
|
|
leaf당 실제 Destroying 바인딩 하나(공유 weak table로 되는 Observer보다
|
|
비쌈) — 필요할 때만 쓰는 걸로 충분.
|
|
|
|
**Observer 자체에 cleanup 반환 계약을 추가하는 안은 여전히 기각** — React
|
|
`useEffect`류로 `fn`이 `nil | () -> ()`를 반환하면 다음 재실행 직전에 그걸
|
|
불러주는 안을 검토했으나, 클로저 업밸류로 이미 쉽게 되고 잘 작동해서(`local
|
|
lastConn; state:Observer(function() if lastConn then lastConn:Disconnect()
|
|
end; lastConn = ... end)`) **Observer 자체**가 이걸 대신해줄 이유는 여전히
|
|
약함. **이 기각과 위 Effect 설계는 상충하지 않는다** — 그때 기각한 건
|
|
"Observer 자체에 이 복잡도를 넣지 말자"였지 "이 패턴 자체가 무용하다"가
|
|
아니었음. 자동 cleanup 배선이 필요한 사람만 opt-in으로 쓰는 별도 계층
|
|
(Effect)으로 분리해 얹었을 뿐, Observer의 기본 계약(재실행 신호만, cleanup은
|
|
클로저로 직접)은 그대로 가볍게 유지됨.
|
|
|
|
## 해결됨 — Effect/Observer 관계 (2026-08-07 여섯 번째 세션, 이전 미해결 절 대체)
|
|
|
|
**과거 미해결이었던 두 질문 모두 확정**:
|
|
1. Effect는 자유 함수로 확정(`state:Effect(fn)` 메소드 아님) — 위 "Effect와
|
|
Observer의 관계 확정" 절 참고. `state` 인자가 있어도 실제 leaf 생명주기
|
|
바인딩을 `state`가 소유하지 않아서 메소드로 만들 필연성이 없었음.
|
|
2. `state:Observer(fn)`는 등록 즉시 1회 실행되는 것으로 확정(`base/
|
|
bind-system-plan.md`의 Observer 절 참고) — 이 덕에 Effect가 `state`를
|
|
받을 때 Observer를 그대로 조합해 재사용할 수 있게 됨(별도 "설치 시
|
|
1회 실행" 로직을 Effect가 따로 만들 필요 없음).
|
|
|
|
`.claude/question.md` 0번의 관련 항목도 해소됨으로 갱신 완료.
|