Skip to content

14. 층을 건너 값 넘기기 — q.Context

This content is not available in your language yet.

대상 독자: 13. 목록 만들기를 끝낸 개발자 목표: 진입점에서 만든 값을 중간 층이 모르는 채로 말단 컴포넌트까지 내려보내기

값은 props로 내리는 게 기본입니다. 다만 층이 깊어지면 중간 컴포넌트들이 자기가 쓰지도 않는 값을 계속 받아 넘겨야 합니다. 지금 우리 화면이 딱 그 모양입니다 — 진입점 → CounterBoardCounter. 강조 색을 Counter가 쓰게 하려고 CounterBoardAccent를 받게 하고 싶지는 않습니다.

q.Context값 여러 개를 가방 하나로 묶어 넘기는 도구입니다. 중간 층은 가방만 넘기고, 안에 뭐가 들었는지는 몰라도 됩니다.


Provider는 가방 안에서 값을 찾을 열쇠입니다. 담는 쪽과 꺼내는 쪽이 같은 열쇠를 봐야 하니, 열쇠는 둘 다 require할 수 있는 모듈에 둡니다.

-- 새 파일: ReplicatedStorage/Client/UI/Theme
const q = require("./Quad")
return {
Provider = q.Context.Provider("Theme"),
}

진입점에서 가방을 만들어 값을 담고, 화면에 같이 넘깁니다.

-- (Main.client.luau 계속 — 13장의 board 두 줄은 아래 두 줄로 갈아 끼웁니다)
const Theme = require("@game/ReplicatedStorage/Client/UI/Theme")
const ctx = q.Context():Set(Theme.Provider, {
Accent = Color3.fromRGB(0, 162, 255),
})
const board = CounterBoard { Rows = rows, Ctx = ctx } -- ← 가방을 같이 넘긴다
board.Parent = screen

CounterBoard가 바꿀 곳은 한 줄입니다. props.Ctx를 읽지도, 열지도 않고 Counter에 그대로 건네기만 합니다.

-- … (CounterBoard.luau) updateFn의 마지막 갈래를 이렇게 고칩니다
return Counter {
Label = item.Label,
Start = item.Start,
Ctx = props.Ctx, -- ← 이 한 줄만 추가. 안에 뭐가 들었는지는 모른다
}, ud

CounterBoardTheme을 require하지도, 그 열쇠 이름을 알지도 못합니다. 강조 색이 하나 더 늘든 폰트가 들어오든 이 파일은 그대로입니다 — 그것이 이 도구가 줄여 주는 비용입니다.


Counter에서 색을 꺼내 씁니다. 여기서 바뀌는 것은 세 줄입니다 — 열쇠를 가져오는 require 한 줄, 가방을 여는 한 줄, 그리고 색을 쓰는 한 줄. 12장에서 얹은 강조 파이프(highlightColor)는 여기서 테마 색에 자리를 내줍니다.

-- … (Counter.luau) 파일 머리에 한 줄
const Theme = require("./Theme")
return function(props)
const theme = props.Ctx:Get(Theme.Provider) -- ← 내 열쇠로 내 값만 꺼낸다
const count = q.Source(props.Start or 0)
-- …중간 생략…
D.TextButton {
BackgroundColor3 = theme.Accent, -- ← 12장의 highlightColor(count, 10) 대신 가방에서 온 색
Text = "+ 1",
-- …나머지 생략…
},
-- …생략…
end

실행하면 목록의 카운터 전부가 가방에 담아 둔 강조 색으로 칠해집니다. 진입점에서 Accent 하나만 바꾸면 전부 따라 바뀌고, 그 사이의 CounterBoard는 아무것도 하지 않았습니다.

여기서 중요한 것은 가방도 손으로 넘긴다는 점입니다. React의 Context처럼 트리를 거슬러 올라가 값을 찾아 주는 조회는 quad에 없습니다 — 줄어드는 것은 “중간 층이 알아야 할 이름의 수”이지, 넘기는 행위 자체가 아닙니다.

그래서 넘기는 걸 빠뜨리면 그 자리에서 드러납니다. props.Ctx가 아예 nil이면 nil:Get(...)이 되어 quad의 에러가 아니라 평범한 Lua 에러(attempt to index nil with 'Get')가 납니다.


  • q.Context.Provider(name?)가 돌려주는 것은 테이블 신원 키입니다 — 모듈 사이에 문자열 충돌이 없고, 같은 이름을 두 번 줘도 서로 다른 키입니다. 이름은 에러 메시지용 선택 인자입니다.
  • :Set가방 자신을 바꾸고 자기를 돌려줍니다(체이닝). 불변 복제 API는 없으니, 서브트리용으로 확장하려면 만드는 쪽이 먼저 복사해야 합니다.
  • :Get은 없는 키에 에러를 내고, :Peeknil을 돌려줍니다.
Context:Get: no value for Provider(Locale) — the creator of this Context did not :Set it (use :Peek to test)
ctx:Get(...)의 타입은 어디서 오나요?

Provider<T>T는 값에 없는 팬텀이라, 키를 만드는 자리에서 한 번 적어 주면 그 뒤로는 :Get이 알아서 추론됩니다. 위 Theme 모듈에 타입까지 같이 두면 됩니다.

-- (§1의 Theme 모듈 — 타입까지 넣은 모양, --!strict)
const q = require("./Quad")
export type Theme = { Accent: Color3 }
return {
Provider = q.Context.Provider("Theme") :: q.Provider<Theme>,
-- 캐스트 대신 명시적 타입 인자도 된다
-- Provider = q.Context.Provider<<Theme>>("Theme"),
}

q.Provider<Theme>01장 설정 모듈의 export type Provider<T> = QuadTypes.Provider<T> 줄에서 옵니다.