D — Instance 생성
D는 Roblox Instance를 만드는 네임스페이스입니다. 클래스마다 별칭이 하나씩 있고, 별칭은
props 테이블 하나를 받아 만들어진 Instance를 돌려줍니다.
이 페이지의 심볼: D.<Class>(props) · 해시 부분 ·
배열 부분 · 숏핸드 키 넷 ·
D.New<<T>>(className)(props) · q.D
-- 설치 경로는 프로젝트 구성에 따라 다르다(00-installation 참고)local Quad = require(<quad-base 모듈 경로>)local QuadTypes = require(<quad-types 모듈 경로>)local QuadRoblox = require(<quad-roblox 모듈 경로>).QuadRobloxlocal q = Quad:UseProvider(QuadRoblox)local D = q.DD.<Class>(props)
섹션 제목: “D.<Class>(props)”시그니처
-- 클래스마다 하나씩 생성된 별칭. Frame이라면:D.Frame: (FrameParam<FrameElem>) -> Frame
-- props 테이블의 모양(생성 타입 — 클래스마다 프로퍼티·이벤트 목록이 다르다)export type FrameParam<E> = { [number]: E, -- 배열 부분: 자식과 디스크립터 Size: PV8?, -- 해시 부분: 프로퍼티 Visible: PV0?, -- … MouseEnter: (((x: number, y: number) -> ()) | StateMarker<(x: number, y: number) -> ()> | None)?, -- …}인자 — props 테이블 하나. 해시 부분과 배열 부분이 한 테이블에 섞여 들어갑니다.
반환 — 만들어진 Instance. 타입은 그 클래스 그대로(D.Frame은 Frame).
예제
local hovered = q.Source(false)local label = q.Source("확인")local ref = q.PreRef(nil :: TextButton?)
local button = D.TextButton({ -- 해시 부분 — 프로퍼티와 이벤트 Text = label, -- State: 값이 바뀌면 프로퍼티가 따라간다 Size = UDim2.fromOffset(120, 36), -- 평범한 값 BackgroundColor3 = hovered:Compute(function(self: QuadTypes.StateData<boolean>): Color3 return if self:Get() then Color3.fromRGB(60, 130, 255) else Color3.fromRGB(30, 30, 30) end), TextTransparency = q.Tween({ Value = 0, Time = 0.2 }), -- Tween 값 MouseEnter = function() -- 이벤트: 엔진 인자만 받는다 hovered:Set(true) end, MouseLeave = function() hovered:Set(false) end, UICorner = 6, -- 숏핸드 키
-- 배열 부분 — 자식·Modifier·Ref·디스크립터 D.UIPadding({ PaddingLeft = UDim.new(0, 8), PaddingRight = UDim.new(0, 8) }), D.Modifier.TextButton():TextSize(18), ref, q.Tag("primary"), q.OnChange("Text", function(v: string) print(v) end),})
button.Parent = script.Parent -- 붙이는 것은 props가 아니라 밖의 한 줄동작 — 만들어지는 순서
- 엔진에 Instance를 만든다.
- quad가 그 Instance를 소유한다(생명주기 바인딩의 전제 — Claim과 D.Mapper의 “한 번만 claim한다”).
- props를 드라이브한다. 첫 패스에서 배열 부분의
Modifier가 소진되고(아래), 그다음 배열 부분이 해시 부분보다 먼저 처리됩니다.OnChange가 같은 props의 프로퍼티 쓰기를 받아볼 수 있는 것이 이 순서 덕입니다(q.OnChange의 초기값 발화).
D.Frame 같은 별칭은 사실 D.New("Frame")을 한 번 적용해 둔 것이고, 둘은 같은 파이프라인을 탑니다.
해시 부분 — 프로퍼티와 이벤트
섹션 제목: “해시 부분 — 프로퍼티와 이벤트”프로퍼티 값의 대수
| 넣는 것 | 뜻 |
|---|---|
| 값 그대로 | 한 번 쓴다 |
State(= Source/파생 State) |
발행될 때마다 다시 쓴다 |
q.Tween{ … } |
그 값으로 애니메이션한다(Tween과 Animate) |
q.None |
그 자리를 nil로 쓴다 — 객체 참조(Adornee/NextSelection* 등)를 놓는 방법 |
Tween 팔이 있는 타입은 정해져 있습니다 — number · boolean · UDim · UDim2 · Vector2 ·
Vector3 · Color3 · CFrame · Rect. 그 밖의 타입(Enum.*, string, Instance 참조 등)의
프로퍼티는 값 / State / None 셋뿐이고, 거기에 Tween을 넣으면 타입 에러입니다.
None으로 쓴 nil을 받아들이지 못하는 프로퍼티라면 엔진이 자기 에러를 던지고, 그 에러는 여러분이
봐야 할 에러입니다 — quad는 삼키지 않습니다.
이벤트
이벤트 이름을 키로 쓰면 그 신호에 연결됩니다. 콜백은 엔진 인자만 받습니다 — self도, Instance도
앞에 붙지 않습니다.
D.TextButton({ Activated = function(inputObject: InputObject, clickCount: number) end })값 자리에는 콜백 / State<콜백> / None이 올 수 있습니다. None(또는 State가 계산한 nil)이면 그
자리의 연결을 끊고 새로 연결하지 않습니다. 같은 자리에 다른 콜백을 다시 발행하면 옛 연결을 끊고
하나만 남깁니다(Connect는 멱등이 아니라서 같은 값 dedup이 없습니다 — 재발행마다 Disconnect+Connect 한 번).
함수가 아닌 값이 오면 quad가 먼저 거부합니다.
Event: handler for "{k}" must be a function (got {typeof(v)})(엔진은 이 자리에서 던지지 않고 출력 창에만 남기기 때문에, 게이트가 quad 쪽에 있습니다.)
어떤 키가 매치되는가
프로퍼티인지 이벤트인지는 엔진 리플렉션으로 판정합니다 — 그 클래스(상속 포함)의 쓰기 가능한
프로퍼티면 프로퍼티 핸들러가, 이벤트면 이벤트 핸들러가 받습니다. 두 집합은 서로 겹치지 않습니다.
읽기 전용 프로퍼티(AbsoluteSize 등)는 쓰기 표면이 아니라 여기서 매치되지 않습니다 — 읽고 싶으면
q.OnChange를 쓰세요.
둘 다 아닌 문자열 키는 일반 no-match 에러입니다.
Dispatch: no handler matched key {tostring(k)} (value: {typeof(v)}{, brand: …}) — check that the provider for this value (e.g. quad-roblox) is initializedParent는 props가 아닙니다
Parent 키는 어떤 핸들러에도 매치되지 않도록 프로퍼티 핸들러의 매치 규칙에서 빠져 있습니다.
그래서 Parent를 넣으면 특별한 문구가 아니라 위의 일반 no-match 에러가 납니다. 부모는 만들어진
뒤 밖에서 inst.Parent = … 한 줄로 붙이세요.
각 클래스의 프로퍼티·이벤트 목록
quad는 Roblox 클래스를 설명하지 않습니다 — React가 <div>의 속성을 설명하지 않는 것과 같습니다.
Frame에 어떤 프로퍼티가 있고 TextButton에 어떤 이벤트가 있는지는 Roblox 공식 레퍼런스가 소스입니다.
https://create.roblox.com/docs/reference/engine/classes/<Class>(예:.../classes/Frame,.../classes/TextButton)
quad가 더하는 것은 그 위의 값 대수(State/Tween/None)와 배열 부분뿐입니다. 이름과 타입은 생성 타입이 그대로 실어 나르므로 에디터 자동완성에서도 보입니다.
배열 부분 — 자식과 디스크립터
섹션 제목: “배열 부분 — 자식과 디스크립터”배열 부분(숫자 키)에는 자식 Instance와 디스크립터들이 들어갑니다. 이 자리에 올 수 있는 값은 이만큼입니다.
| 값 | 무엇을 하나 |
|---|---|
| Instance | 정적 자식으로 붙는다 |
Slot |
동적 자식 구간 — 목록/단일 교체 |
Ref / PreRef / PostRef |
만들어진 Instance를 그 핸들에 채운다 |
Observer / EffectHandle |
그 Instance의 생애에 묶인다(leaf 종료 관용구) |
Modifier |
프로퍼티 묶음을 이 자리에 펼친다(아래) |
q.Tag(...) / q.Attr{...} / q.StringAttr·NumberAttr·BooleanAttr |
CollectionService 태그·Attribute |
q.OnChange(name, fn) |
프로퍼티 변경 신호 바인딩 |
q.None |
아무것도 아닌 자리(구멍 메꾸기) |
위의 것들을 담은 State |
발행될 때마다 그 자리를 갈아 끼운다 |
Modifier·Ref류·Observer·Slot 같은 핸들러 층 값은 해시 키가 아니라 배열 부분에 놓습니다.
nil 구멍을 만들지 마세요 — or q.None
배열 리터럴 중간에 nil이 들어가면 배열 순회가 끊겨 부기(bookkeeping)가 깨집니다. 조건부 자리는
q.None으로 메꾸는 것이 관용구입니다.
D.Frame({ props.Modifier or q.None, props.Child or q.None,})Modifier는 첫 패스에서 소진됩니다
배열 부분의 Modifier는 드라이브 첫 패스에서 자기 필드를 해시 부분으로 펼치고 그 자리를
비웁니다. 규칙 둘:
- 인라인 해시 키가 이깁니다 — 같은 이름이 props에 직접 적혀 있으면(그 값이
None이어도) 그쪽이 남습니다. - 뒤에 오는 Modifier가 이깁니다 — 배열의 나중 자리가 먼저 씁니다.
해시 키의 값으로 Modifier를 놓는 것은 오용이라 바로 거부합니다.
Modifier: a Modifier cannot be a value of key "{tostring(k)}" — place it in the array part클래스별로 좁혀집니다
배열 자리의 타입은 클래스마다 다릅니다. Ref는 그 클래스(또는 조상)의 Ref만 받고, Modifier도
자기 클래스와 조상 클래스의 것만 받습니다.
local frameRef = q.Ref(nil :: Frame?)local labelRef = q.Ref(nil :: TextLabel?)
D.Frame({ frameRef }) -- OKD.Frame({ labelRef }) -- 타입 에러: 형제 클래스의 Ref는 거부된다D.Mapper 디스크립터는 Claim 전용이라 여기 오면 no-match입니다.
숏핸드 키 넷
섹션 제목: “숏핸드 키 넷”GuiObject 계열 클래스에는 자주 쓰는 UI 자식을 해시 키로 줄여 쓰는 편의 키가 넷 있습니다.
| 키 | 값 | 하는 일 |
|---|---|---|
UICorner |
number 또는 UDim |
관리되는 UICorner 자식의 CornerRadius. number는 offset UDim으로 바뀐다 |
UIPadding |
UDim |
관리되는 UIPadding 자식의 네 방향 패딩 |
UIPaddingOffset |
number |
같은 자식의 네 방향 패딩을 offset UDim으로 |
UIScale |
number |
관리되는 UIScale 자식의 Scale |
D.Frame({ UICorner = 8, UIPaddingOffset = 12 })- 값 자리에
State와Tween이 그대로 들어갑니다(같은 값 대수). UIPadding과UIPaddingOffset은 같은 관리 자식을 공유합니다 — 둘을 같이 쓰면 프로퍼티별로 나중에 쓴 쪽이 이깁니다.- 값이
nil/None이 되면 그 관리 자식을 파괴합니다. - 관리 자식은 quad가 만든 것이고, 여러분이 직접 넣은
UICorner자식은 건드리지 않습니다. 다만 템플릿의UICorner와 이 숏핸드를 같이 쓰면 둘이 생기니 하나만 쓰세요.
D.New<<T>>(className)(props)
섹션 제목: “D.New<<T>>(className)(props)”시그니처
New: <T>(className: string) -> (props: any) -> TD에 별칭이 없는 클래스를 만드는 이스케이프 해치입니다. 커링이라 두 번 부릅니다.
local part = D.New<<Part>>("Part")({ Name = "loose", Anchored = true })- 타입 인자를 명시하세요. 생략하면 반환이
unknown으로 추론됩니다. - props는
any입니다 — 클래스별 프로퍼티 검사가 없습니다. 오타든 없는 프로퍼티든 런타임에서야 드러납니다. 별칭이 있는 클래스라면 별칭을 쓰세요. - 파이프라인은 별칭과 완전히 같습니다(생성 → 소유 → 드라이브). 에러 blame도 여러분의 호출 줄에 닿습니다.
q.D — 네임스페이스와 생성되는 클래스
섹션 제목: “q.D — 네임스페이스와 생성되는 클래스”q.D는 UseProvider가 병합해 준 생성 네임스페이스입니다. 안에 있는 것은 넷입니다.
| 멤버 | 무엇 |
|---|---|
D.<Class> |
클래스별 생성 별칭 — 아래 31개 |
D.New |
범위 밖 클래스용 이스케이프 |
D.Mapper |
이미 있는 트리를 매핑하는 디스크립터 생성기 |
D.Modifier |
클래스별 타입드 Modifier 생성자 |
클래스 별칭은 31개입니다.
BillboardGui · Camera · CanvasGroup · Folder · Frame · ImageButton · ImageLabel ·
ScreenGui · ScrollingFrame · SurfaceGui · TextBox · TextButton · TextLabel ·
UIAspectRatioConstraint · UICorner · UIDragDetector · UIFlexItem · UIGradient ·
UIGridLayout · UIListLayout · UIPadding · UIPageLayout · UIScale · UIShadow ·
UISizeConstraint · UIStroke · UITableLayout · UITextSizeConstraint · VideoFrame ·
ViewportFrame · WorldModel
서비스와 추상 클래스(GuiObject 등)는 범위 밖이고, 엔진이 Deprecated/NotBrowsable/Internal로 표시한
클래스도 제외됩니다. 목록에 없는 클래스는 D.New<<T>>(className)으로 만드세요.
관련
- 02. 첫 화면 만들기
- 03. 컴포넌트 합성 —
Modifier/Ref를 props로 넘기기 - 05. 테마와 동적 스타일링
- Quadnomicon Vol. 8 — 확장 가능한 디스패치 엔진