콘텐츠로 이동

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 모듈 경로>).QuadRoblox
local q = Quad:UseProvider(QuadRoblox)
local D = q.D

시그니처

-- 클래스마다 하나씩 생성된 별칭. 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.FrameFrame).

예제

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가 아니라 밖의 한 줄

동작 — 만들어지는 순서

  1. 엔진에 Instance를 만든다.
  2. quad가 그 Instance를 소유한다(생명주기 바인딩의 전제 — Claim과 D.Mapper의 “한 번만 claim한다”).
  3. 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 initialized

Parent는 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 }) -- OK
D.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 })
  • 값 자리에 StateTween이 그대로 들어갑니다(같은 값 대수).
  • UIPaddingUIPaddingOffset같은 관리 자식을 공유합니다 — 둘을 같이 쓰면 프로퍼티별로 나중에 쓴 쪽이 이깁니다.
  • 값이 nil/None이 되면 그 관리 자식을 파괴합니다.
  • 관리 자식은 quad가 만든 것이고, 여러분이 직접 넣은 UICorner 자식은 건드리지 않습니다. 다만 템플릿의 UICorner와 이 숏핸드를 같이 쓰면 둘이 생기니 하나만 쓰세요.

시그니처

New: <T>(className: string) -> (props: any) -> T

D에 별칭이 없는 클래스를 만드는 이스케이프 해치입니다. 커링이라 두 번 부릅니다.

local part = D.New<<Part>>("Part")({ Name = "loose", Anchored = true })
  • 타입 인자를 명시하세요. 생략하면 반환이 unknown으로 추론됩니다.
  • props는 any입니다 — 클래스별 프로퍼티 검사가 없습니다. 오타든 없는 프로퍼티든 런타임에서야 드러납니다. 별칭이 있는 클래스라면 별칭을 쓰세요.
  • 파이프라인은 별칭과 완전히 같습니다(생성 → 소유 → 드라이브). 에러 blame도 여러분의 호출 줄에 닿습니다.

q.D — 네임스페이스와 생성되는 클래스

섹션 제목: “q.D — 네임스페이스와 생성되는 클래스”

q.DUseProvider가 병합해 준 생성 네임스페이스입니다. 안에 있는 것은 넷입니다.

멤버 무엇
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)으로 만드세요.


관련