Store
Store는 이름 붙은 Source들의 가방입니다. 값을 담는 새 반응형 노드가 아니라, 이미 만든 Source들을 한 레코드로 묶어 타입이 붙은 점 접근(store.hp)을 주는 것이 전부입니다.
이 페이지의 심볼: q.Store(defaults) · store.key · store:Of<<U>>(name) · store:Names() · 예약 키
-- 설치 경로는 프로젝트 구성에 따라 다르다(00-installation 참고)local Quad = require(<quad-base 모듈 경로>)local QuadRoblox = require(<quad-roblox 모듈 경로>).QuadRobloxlocal q = Quad:UseProvider(QuadRoblox) -- quad-roblox 백엔드 설치: D/Tween/Animate/OnChange가 생긴다q.Store(defaults)
섹션 제목: “q.Store(defaults)”시그니처
Store: (() -> Store<{}>) & (<T>(defaults: T) -> Store<T>)
export type Store<T> = T & { Of: <U>(self: any, name: string) -> Source<U>, Names: (self: any) -> { string }, __reservedCheck: CheckReservedKeys<keyof<T>>,}인자
| 이름 | 타입 | 설명 |
|---|---|---|
defaults |
T |
초기 필드. 넘긴 레코드 타입이 그대로 T가 됩니다. 런타임 계약은 메타테이블 없는 평범한 테이블이고 값은 **전부 Source**여야 한다는 것입니다 — 생략하면 빈 Store입니다. |
반환 — Store<T>. T는 넘긴 레코드 타입 그대로입니다(타입 함수 없이 평범한 레코드).
동작
-
초기화는 명시적입니다. 넘긴 테이블이 그대로 store가 되고, 메소드는 메타테이블 쪽에 있습니다. 그래서
store.hp는 넣은 그Source객체 자신이고,:Names()에 메소드 이름이 섞이지 않습니다. -
값 검증은 화이트리스트입니다 —
Source가 아닌 값은 그 자리에서 거부합니다. 통과시켰다면 첫:Get()에서야 죽었을 것이기 때문입니다. -
검증 에러 문구:
상황 문구 값이 Source가 아님Store: default for "{tostring(name)}" is not a Source (got {typeof(value)})테이블이 아님 Store: defaults must be a table of Sources (got {typeof(defaults)})메타테이블이 달림( Source하나를 그대로 넘긴 경우 등)Store: defaults must be a plain table without a metatable (a bare Source instead of { name = Source }?)브랜드 값(AttrKey·Mapper 디스크립터 등) Store: defaults must be a plain { name = Source } table (got an AttrKey/Mapper descriptor)키가 문자열이 아니거나 빈 문자열 Store: {what} must be a non-empty string (got {if name == "" then '""' else typeof(name)})({what}은 생성자에서key,:Of에서Of name)예약된 이름 Store: "{name}" is a reserved store key -
Source의 값 제약이 그대로 따라옵니다 — Modifier는 담을 수 없습니다. 이 검사는Source생성자가 하므로:Of로 만든 필드에도 적용됩니다. -
print(store)는 정렬된 키 목록으로Store{hp, mp}모양입니다.
예제
local store = q.Store({ hp = q.Source(100), name = q.Source("player"),})
print(store.hp:Get()) --> 100store.hp:Set(80)관련 — How-To 02 폼 검증 · 02-source
store.key (선언된 필드)
섹션 제목: “store.key (선언된 필드)”동작
- 선언한 키는 평범한 레코드 필드입니다 — 넣은
Source가 그 자리에 그대로 있고, 타입도 넘긴 레코드 타입 그대로 붙습니다.store.hp는Source<number>이지 래퍼가 아닙니다. - 선언하지 않은 키를 점으로 읽으면
nil입니다. 점 접근에는 지연 생성이 없습니다 — 없는 이름을 그 자리에서 만들어주는 문은:Of하나뿐입니다.
local store = q.Store({ hp = q.Source(100) })print(store.hp:Get()) --> 100print((store :: any).mp) --> nil (선언 안 한 이름 — 만들어지지 않는다)store:Of<<U>>(name)
섹션 제목: “store:Of<<U>>(name)”시그니처
Of: <U>(self: any, name: string) -> Source<U>인자
| 이름 | 타입 | 설명 |
|---|---|---|
name |
string |
필드 이름. 빈 문자열이 아닌 문자열이어야 하고, 예약 이름이면 안 됩니다. |
반환 — 그 이름의 Source<U>. 없으면 그 자리에서 만들어 저장하고 돌려줍니다.
동작
- 계산된 이름을 쓰는 유일한 문이자 지연 생성이 남아 있는 유일한 자리입니다. 타입이 이름을 알 수 없으므로 결과 타입은 호출자가 타입 인자로 직접 줍니다 — 그만큼 타입 안전은 여기서 포기하는 것입니다.
- 새로 만들어진
Source의 값은 **nil**입니다.store:Of<<number>>("mp"):Get()은 타입이number여도 첫 호출에선nil입니다 — 필요하면 바로:Set하세요. - 같은 이름으로 다시 부르면 같은 객체가 옵니다.
- 이름 검증:
Store: {what} must be a non-empty string(여기서{what}은Of name) /Store: "{name}" is a reserved store key
예제
local store = q.Store({ hp = q.Source(100) })
local mp = store:Of<<number>>("mp") -- 결과 타입은 호출자가 직접 준다mp:Set(50) -- 갓 만들어진 Source의 값은 nil이므로 먼저 채운다print(store:Of<<number>>("mp") == mp) --> true (같은 객체)store:Names()
섹션 제목: “store:Names()”시그니처
Names: (self: any) -> { string }반환 — 지금 이 store에 있는 필드 이름들.
동작
- 생성자에 선언한 키와
:Of가 만든 키가 둘 다 들어갑니다. - 메소드(
Of/Names)는 메타테이블 쪽에 있어 세지 않고, 타입에만 있는 팬텀 필드도 들어가지 않습니다. - 순서는 보장되지 않습니다 — 필요하면 직접 정렬하세요.
예제
local store = q.Store({ hp = q.Source(100) })store:Of("mp")
local names = store:Names()table.sort(names)print(table.concat(names, ",")) --> hp,mp예약 키
섹션 제목: “예약 키”Of, Names, __reservedCheck 셋은 store 필드 이름으로 쓸 수 없습니다. 앞의 둘은 메소드 이름이고, 마지막은 타입 검사용 팬텀 필드입니다.
- 런타임: 생성자와
:Of둘 다 같은 문으로 막습니다 —Store: "{name}" is a reserved store key - 타입 검사 시점:
Store<T>에 얹힌 타입 함수가 주석 자리에서 먼저 경고를 냅니다. 타입 함수는 에러를 던질 수 없어 출력으로만 알립니다 —quad.Store: "{v}" is a reserved key