Skip to content

Context

This content is not available in your language yet.

명시적 타입드 값 가방입니다. 트리를 거슬러 올라가는 암묵적 컨텍스트가 아닙니다 — 아무것도 조회하지 않고, 컴포넌트는 이름 붙은 파라미터로 가방을 받아 자기 키만 읽습니다. 노리는 자리는 중간 계층입니다: 앱의 스토어 스키마를 모르는 셸이나 레이아웃 컴포넌트가, 스토어 타입을 하나하나 적지 않고 Context 하나만 아래로 넘길 수 있게 합니다.

이 페이지의 심볼: q.Context() · q.Context.Provider(name?) · ctx:Set(provider, value) · ctx:Get(provider) · ctx:Peek(provider) · provider.Name · 술어 둘

quad-base에 있으므로 백엔드와 무관하게 존재합니다.

-- 01장의 설정 모듈: quad_base에 quad_roblox를 설치하고 타입을 다시 내보낸다(시작하기 01 참고)
local q = require("@game/ReplicatedStorage/Client/UI/Quad")

시그니처

Context: ContextConstructor
-- export type ContextConstructor =
-- setmetatable<{ Provider: <T>(name: string?) -> Provider<T> }, { __call: (self: any) -> Context }>

q.Context는 호출 가능한 네임스페이스입니다 — q.Context()로 부르면 가방을 만들고, q.Context.Provider로 키를 만듭니다.

인자 없음 — 인자를 주면(q.Context({ [P] = v })처럼 초기값을 넘기면) Quad0255 Context: takes no arguments (got 1 argument) - create an empty Context and fill it with ctx:Set(provider, value)로 거부합니다. Tag·Attr·Store와 달리 초기값을 받지 않습니다 — 가방은 변경 가능하니 만든 뒤 :Set으로 채우세요. strict 타입은 이미 인자 수 불일치로 잡습니다. 반환 — 빈 Context.

동작 — 가방은 자기가 담은 Provider와 값을 강하게 붙듭니다(약한 변형은 없습니다). 열거 표면은 일부러 없습니다 — 가방은 하나의 진실이지, 서브트리마다 갈라 쓰는 물건이 아닙니다. tostring은 담긴 개수를 보여줍니다(Context(2)).


시그니처

Provider: <T>(name: string?) -> Provider<T>
-- export type Provider<T> = { read __quadProvider: true, read __quadProviderValue: T, read __quadProviderAccepts: (T) -> (), read Name: string? }

인자

이름 타입 설명
name string? 선택. 에러 메시지와 tostring에만 쓰인다

반환 — 새 Provider<T>. 매번 서로 다른 frozen 테이블입니다.

동작 — 키는 테이블 신원입니다. 문자열이 아니므로 모듈 사이에서 충돌하지 않고, 같은 name을 두 번 줘도 서로 다른 키입니다. 신원은 quad-base 사본마다입니다 — 다른 사본에서 만든 Provider는 여기서 q.isProvider가 아닙니다(브랜드와 같은 규칙).

name이 문자열이 아니면 만드는 그 줄에서 던집니다.

Quad0035 Context.Provider: name must be a string (got number)

타입은 만드는 자리에서 한 번 붙입니다. T는 값에 없는 팬텀이라, 캐스트든 명시적 타입 인자든 한쪽으로 적어주면 그 뒤로는 Get이 알아서 추론됩니다. 둘 다 luau-analyze와 luau-lsp --flag:LuauSolverV2=true(둘 다 신 솔버) 양쪽에서 통과합니다.

type Theme = { ButtonBg: Color3 }
-- (1) 캐스트
local ThemeProvider = q.Context.Provider("Theme") :: q.Provider<Theme>
-- (2) 명시적 타입 인자
local UserProvider = q.Context.Provider<<{ Name: string }>>("User")

Provider<T>는 T에 불변입니다. 값을 넣고(:Set) 꺼내는(:Get) 열쇠라, Provider<Frame>은 Provider<Instance> 자리에 들어가지 않고 Provider<number>에 문자열을 :Set하면 타입 에러입니다. TypeScript React의 RefObject<T>가 불변인 것과 같은 이유입니다 — 넓은 열쇠로 대입을 허용하면 다른 쪽이 좁은 타입을 기대하는 가방에 넓은 값을 넣을 수 있게 됩니다. __quadProviderAccepts와 __quadProviderValue는 그 불변성을 만드는 타입 전용 팬텀 필드이니 읽지 마세요.

시그니처

Set: <T>(self: Context, provider: Provider<T>, value: T) -> Context

인자 — provider(이 가방의 키), value(붙일 값, nil 불가). 반환 — self(체이닝).

동작 — 부르는 그 가방을 변경합니다. 이미 있는 키면 덮어씁니다. 불변 복제 API도, 가방을 열거하는 API도 없습니다 — 부모 가방을 서브트리용으로 확장하려면 만드는 쪽이 q.Context()로 새 가방을 만들어 필요한 키를 다시 :Set 해 넘기세요.

왜 복제 API가 없나요?

가방이 서브트리마다 갈라지면 디버깅할 때 봐야 할 사본이 늘어나고, 자식이 바꾼 사본을 부모 쪽에 반영하는 동작을 따로 만들어야 합니다. 가방을 하나의 진실로 두면 어디서 찍어 봐도 전부 보이고 그런 장치가 필요 없습니다.

false는 값입니다. 부재로 취급되는 것은 nil뿐이고, nil을 넣으려 하면 막힙니다. q.None은 막지 않고 값으로 보관돼 :Get이 None을 돌려줍니다 — “없음”을 넣고 싶으면 :Set을 부르지 마세요.

Quad0037 Context:Set: value for Provider(Theme) must not be nil (absence is "not set")

키 자리에 Provider가 아닌 값이 오면 세 메소드가 모두 같은 모양으로 막습니다(Set/Get/Peek 자리에 따라 앞의 이름만 바뀝니다).

Quad0036 Context:Set: key must be a Provider from Context.Provider() (got string)

시그니처

Get: <T>(self: Context, provider: Provider<T>) -> T

반환 — 등록된 값. 타입은 Provider<T>의 팬텀 T에서 나옵니다.

동작 — 값을 그 자리에서 한 번 돌려줍니다. 가방은 반응형이 아니라서 나중에 :Set을 다시 해도 이미 읽어 간 곳은 모릅니다 — 실행 중에 바뀌어 따라와야 하는 값은 State/Store를 담아 넘기세요. 없으면 에러입니다. 필요한 Provider를 안 준 것은 프로그래밍 실수라는 판단이고, blame은 :Get을 부른 줄로 갑니다. 있는지부터 확인하고 싶으면 :Peek을 쓰십시오. 메시지의 Provider(...) 자리는 그 Provider의 tostring이라 이름 없는 Provider면 Provider만 찍힙니다.

Quad0038 Context:Get: no value for Provider(Locale) - the creator of this Context did not :Set it (use :Peek to test)

시그니처

Peek: <T>(self: Context, provider: Provider<T>) -> T?

반환 — 값 또는 nil. 키 게이트만 있고 부재 에러는 없습니다 — “있는지” 묻는 자리입니다.


시그니처

read Name: string?

생성 때 준 이름 그대로이고 읽기 전용입니다(Provider 테이블 자체가 frozen). 안 줬으면 nil이며, 에러 메시지와 tostring이 이걸 씁니다 — tostring(named) == "Provider(Theme)", 이름이 없으면 "Provider".


호출 뜻
q.isContext(x) x가 이 quad 사본의 Context 가방인가
q.isProvider(x) x가 이 quad 사본의 Provider 키인가

예제 — 중간 계층이 스키마를 모른 채 넘긴다

섹션 제목: “예제 — 중간 계층이 스키마를 모른 채 넘긴다”

컴포넌트는 플레인 함수이므로, 중간 계층은 그냥 파라미터를 하나 더 받는 함수입니다.

type Theme = { ButtonBg: Color3 }
local ThemeProvider = q.Context.Provider<<Theme>>("Theme")
local UserProvider = q.Context.Provider<<{ Name: string }>>("User")
-- 리프 컴포넌트 — 필요한 키만 꺼내 쓴다
local function ThemedButton(props: { Context: q.Context }): TextButton
local theme = props.Context:Get(ThemeProvider)
return q.Elem "TextButton" { BackgroundColor3 = theme.ButtonBg }
end
-- 중간 셸 — Theme/User가 뭔지 모른 채 가방만 내려보낸다
local function AppShell(props: { Context: q.Context, Content: Instance }): Frame
return q.Elem "Frame" {
ThemedButton({ Context = props.Context }),
props.Content,
}
end
local rootCtx = q.Context()
:Set(ThemeProvider, { ButtonBg = Color3.fromRGB(40, 40, 40) })
:Set(UserProvider, { Name = "q" })
local maybeUser: { Name: string }? = rootCtx:Peek(UserProvider) -- 없으면 nil
local shell = AppShell({ Context = rootCtx, Content = q.Elem "Frame" {} })