-
Notifications
You must be signed in to change notification settings - Fork 0
Kong/method etc #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Kong/method etc #17
Changes from all commits
68568b7
4e403d9
193a905
bcc63f8
3c0a03d
2b13a5a
f6de4e7
9f7eb83
1d3f9ae
957d454
1bff9ac
1762b75
f51a26e
902333b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,238 @@ | ||
| # 콘-텍스트 | ||
|
|
||
| > [!Info] | ||
| > 연관된 파일로는 `provider.md`가 있으니 시간이 난다면 한 번 보는 것도 좋을 것 같다. | ||
|
|
||
| 어떤 라이브러리를 만들기 위해 AI에게 코드 작성과 에러 수정 후 코드 분석을 하게 된 나... 시작부터 난관에 맞닥뜨리는데... | ||
|
|
||
| (대충 놀랍고 엄청나다는 이미지) | ||
|
|
||
| --- | ||
|
|
||
| ## 서론 | ||
|
|
||
| 코드를 분석하기 위해 `import` 부분부터 봤다. 뭐든 처음부터 보는 게 좋지 않은가... 그래서 코드를 봤는데 바로 모르는 것이 나와버렸다... `createContext`, `useContext`. 이게 대체 뭐지? | ||
|
|
||
| --- | ||
|
|
||
| ## Props | ||
|
|
||
| 들어가기 전에 Context를 이해하려면 먼저 props를 알면 도움이 된다. | ||
|
|
||
| 간단하게 설명하자면, 부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달하는 방식이 props다. props는 간단하게 데이터를 전달할 수 있지만, 자식 컴포넌트 이상으로 데이터를 전달하려면 중간 단계의 컴포넌트에서 불필요하게 데이터를 처리해야 하는 문제가 발생한다. | ||
|
|
||
| <img width="640" height="382" alt="image" src="https://github.com/user-attachments/assets/a610d0a1-7621-4d82-b889-08746b7b2c8e" /> | ||
|
|
||
| 이런 상황을 속성이 여러 컴포넌트를 관통하는 것 같다고 해서 **Prop Drilling**이라고 한다. | ||
|
|
||
| <img width="1440" height="890" alt="image" src="https://github.com/user-attachments/assets/0cfbd9b6-991f-40e5-a95a-2dd663b7bff1" /> | ||
|
|
||
| --- | ||
|
|
||
| ## 그래서 진짜로, Context. 그게 뭔데 | ||
|
|
||
| 해당 props drilling을 해결하기 위해 나온 게 바로 Context API다. | ||
|
|
||
| Context API는 앱에서 컴포넌트로 **props를 사용하지 않고 필요한 데이터를 쉽게 공유**할 수 있게 해준다. 특정 컴포넌트에서 제공하는 데이터를 하위 컴포넌트에서 사용할 수 있게 하는 것 — 중간 컴포넌트들을 거치지 않고, 필요한 컴포넌트가 직접 값을 꺼내 사용한다. | ||
|
|
||
| ```tsx | ||
| import { createContext, useContext } from "react"; | ||
|
|
||
| // 1. createContext 메서드로 context 생성 | ||
| const MyContext = createContext(데이터의초기값); | ||
|
|
||
| // 2. Provider로 대상 컴포넌트를 감싸고, value에 전달할 데이터를 넣는다 | ||
| <MyContext.Provider value={전달할데이터}>{children}</MyContext.Provider>; | ||
|
|
||
| // 3. 필요한 컴포넌트에서 useContext로 꺼내 쓴다 (공식 문서 권장 방식) | ||
| const 데이터 = useContext(MyContext); | ||
|
|
||
| // 참고: Consumer 컴포넌트 방식도 있지만, useContext 사용을 권장한다 | ||
| <MyContext.Consumer>{(데이터) => <div>{데이터}</div>}</MyContext.Consumer>; | ||
| ``` | ||
|
|
||
| 실제 예제로 보면: | ||
|
|
||
| ```tsx | ||
| import { createContext, useContext } from "react"; | ||
|
|
||
| const ThemeContext = createContext(null); | ||
|
|
||
| export default function MyApp() { | ||
| return ( | ||
| <ThemeContext value="dark"> | ||
| <Form /> | ||
| </ThemeContext> | ||
| ); | ||
| } | ||
|
|
||
| function Panel({ title, children }) { | ||
| const theme = useContext(ThemeContext); // "dark"를 받아옴 | ||
| return ( | ||
| <section className={"panel-" + theme}> | ||
| <h1>{title}</h1> | ||
| {children} | ||
| </section> | ||
| ); | ||
| } | ||
|
|
||
| function Button({ children }) { | ||
| const theme = useContext(ThemeContext); // 여기서도 "dark"를 받아옴 | ||
| return <button className={"button-" + theme}>{children}</button>; | ||
| } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 주로 어디에 사용할까? | ||
|
|
||
| Context가 적합한 데이터 종류가 있다. | ||
|
|
||
| - **라이트 모드 / 다크 모드** 설정 | ||
| - **사용자 데이터** (현재 인증된 유저 정보) | ||
| - **언어 혹은 지역 데이터** | ||
|
|
||
| 공통점이 보이는가? 모두 **자주 업데이트할 필요가 없는 데이터**다. Context에서 `value`가 바뀌면 Provider로 감싼 모든 자식 컴포넌트들이 리렌더링되므로, 자주 바뀌는 값에는 적합하지 않다. | ||
|
|
||
| 즉 Context는 *컴포넌트를 위한 전역 변수*의 개념이라고 볼 수 있다. | ||
|
|
||
| --- | ||
|
|
||
| ## Context를 사용하기 전에 고려할 것 | ||
|
|
||
| Context는 사용하기에 꽤 유혹적이다. 그러나 이는 또한 남용하기 쉽다는 의미이기도 하다. **어떤 props를 여러 레벨 깊이로 전달해야 한다고 해서 해당 정보를 context에 넣어야 하는 것은 아니다.** | ||
|
|
||
| 먼저 이 두 가지를 시도해보자. | ||
|
|
||
| **1. Props 전달하기로 시작하기.** 여러 컴포넌트를 거쳐 props가 흘러가는 것은 그리 이상한 일이 아니다. 힘든 일처럼 느껴질 수 있지만, 어떤 컴포넌트가 어떤 데이터를 사용하는지 매우 명확히 해줘서 유지보수하기 좋다. | ||
|
|
||
| **2. 컴포넌트를 추출하고 `children`으로 전달하기.** 이게 바로 Composition(합성)이라는 기법이다. 데이터를 쓰지도 않는 컴포넌트가 짐꾼 역할을 하고 있다면, 구조를 바꾸는 것이 먼저다. | ||
|
|
||
| ```tsx | ||
| // Bad: Layout이 user를 쓰지도 않는데 받아야 함 | ||
| function App() { | ||
| const [user] = useState({ name: "홍길동" }); | ||
| return <Layout user={user} />; | ||
| } | ||
| function Layout({ user }) { | ||
| return ( | ||
| <div> | ||
| <Header user={user} /> | ||
| </div> | ||
| ); // 그냥 전달만... | ||
| } | ||
|
|
||
| // Good: App에서 Header를 직접 렌더링해서 넣어버린다 | ||
| function App() { | ||
| const [user] = useState({ name: "홍길동" }); | ||
| return ( | ||
| <Layout> | ||
| <Header user={user} /> {/* Layout은 user가 뭔지 몰라도 됨 */} | ||
| </Layout> | ||
| ); | ||
| } | ||
| function Layout({ children }) { | ||
| return <div>{children}</div>; // 그냥 구멍(Slot)만 뚫어놓으면 끝 | ||
| } | ||
| ``` | ||
|
|
||
| `App` → `Layout` → `Header` 3단계였던 게, `App` → `Header`로 줄어든다. Layout은 껍데기가 되어 리렌더링 범위에서도 제외된다. | ||
|
|
||
| 이래도 해결이 안 될 만큼 깊거나 광범위할 때 비로소 Context를 꺼내 드는 것이 React의 권장 순서다. | ||
|
|
||
| --- | ||
|
|
||
| ## Context랑 전역 상태 관리 라이브러리, 같은 거 아냐? | ||
|
|
||
| 겉보기에는 Context API도 전역적으로 데이터를 뿌려주니까 Redux나 Zustand 같은 라이브러리의 대체제처럼 보일 수 있다. 허나 다르다. | ||
|
|
||
| **Context API의 핵심 설계 목적은 Dependency Injection(의존성 주입)이다.** 깊게 중첩된 컴포넌트 트리에서 Prop Drilling 없이 데이터를 하단으로 꽂아주는 **통로** 역할. | ||
|
|
||
| 반면 상태 관리 도구는 단순히 값을 전달하는 것 외에 **효율적인 업데이트**가 가능해야 한다. Context의 치명적인 약점이 여기서 드러난다. | ||
|
|
||
| ``` | ||
| 전역 상태 객체 { user, theme, posts }가 Context에 있다고 가정. | ||
| → theme만 바뀌어도 user 정보만 쓰는 컴포넌트까지 전부 재렌더링된다. | ||
| ``` | ||
|
|
||
| Redux, Zustand, Recoil 같은 라이브러리는 내부적으로 최적화되어 있어서, `user`가 바뀌어도 `theme`을 구독 중인 컴포넌트는 눈 하나 깜짝 안 한다. 이게 바로 **전달**과 **관리**의 차이다. | ||
|
|
||
| | 특징 | Context API | 전역 상태 관리 라이브러리 | | ||
| | ------------- | ------------------------------ | ------------------------------------ | | ||
| | 주 목적 | Props 전달 생략, 결합도 낮추기 | 효율적인 상태 업데이트 및 로직 분리 | | ||
| | 렌더링 최적화 | 어려움 (구독자 전체 렌더링) | 뛰어남 (변경된 부분만 선택적 렌더링) | | ||
| | 비동기 처리 | 직접 구현해야 함 | Middleware 등 내장/지원 | | ||
| | 디버깅 툴 | 기본 DevTools | 전용 DevTools (타임머신 디버깅 등) | | ||
|
|
||
| 요약하면: | ||
|
|
||
| - **Context** → 데이터를 어디로 보낼지 결정하는 **전송 수단** | ||
| - **Redux·Zustand** → 데이터를 어떻게 관리하고 효율적으로 변화시킬지 결정하는 **시스템** | ||
|
|
||
| --- | ||
|
|
||
| ## 그래서 언제 뭘 써야 할까? | ||
|
|
||
| **Context가 적합한 경우:** | ||
|
|
||
| - 값이 자주 바뀌지 않을 때 (테마, 언어, 로그인 유저 정보) | ||
| - 앱 전체보다는 특정 범위 내에서만 공유가 필요할 때. | ||
|
|
||
| **상태 관리 라이브러리가 적합한 경우:** | ||
|
|
||
| - 업데이트가 빈번하게 일어나는 복잡한 데이터 | ||
| - 컴포넌트 수백 개 중 특정 컴포넌트만 정밀하게 업데이트해야 할 때 | ||
| - 상태 관리 로직을 컴포넌트 외부로 완전히 분리하고 싶을 때. | ||
|
|
||
| --- | ||
|
|
||
| ## 고민 | ||
|
|
||
| 1. context가 props drilling 해결을 위해 사용하는 건 맞는데, 렌더링 측면에서 보면 context가 리렌더링을 많이 일으켜서 그렇게 좋다고는 생각을 안 함… 근데 어차피 자식의 자식의 자식으로 보내서 뭘 해도 리렌더링 일으키는 거, 그냥 편하게 최상단에서 context로 쏴버리자인가? | ||
| 2. 근데 생각해보니 리렌더링은 값이 변화할 때만 일어나는 것이고… context가 변화하는 값에만 사용되는 게 아니라 그냥 변화하지 않는 일반적인 값을 내려줄 때도 사용되니… 양측에서 생각을… | ||
| 3. 근데 거기서 더 생각해보니 안에 들어 있는 모든 컴포넌트가 리렌더링 되는 게 아니라 `useContext`를 사용하고 있는 애들만 리렌더링 되니까… | ||
|
|
||
| --- | ||
|
|
||
| ## 별록 | ||
|
|
||
| `useContext`는 전달한 Context에 대한 Context Value를 반환한다. Context 값을 결정하기 위해 React는 컴포넌트 트리를 탐색하고, 특정 Context에 대해 상위에서 가장 가까운 Context Provider를 찾는다. | ||
|
|
||
| `value`에 `useState` 값을 넣으면 Context를 업데이트할 수 있다. | ||
|
|
||
| ```tsx | ||
| const AppContext = createContext(); | ||
|
|
||
| export const AppProvider = ({ children }) => { | ||
| const [user, setUser] = useState({ name: "Guest" }); | ||
|
|
||
| return <AppContext value={{ user, setUser }}>{children}</AppContext>; | ||
| }; | ||
| ``` | ||
|
|
||
| +) 변하는 값에는 전역 상태 관리 라이브러리, 그저 props에 값을 전달할 때는 context도 괜찮을 것 같다. | ||
|
|
||
| --- | ||
|
|
||
| ## 추가 | ||
|
|
||
| 해당 글을 작성하면서 context는 언제 사용하고 왜 전역 상태 관리 라이브러리로 대체하지 않는지 궁금점들이 생겼다. | ||
| 왜냐하면 context는 리렌더링 측면에서만 봤을 때는 그리 좋지 않기 때문이다. 그래서 리렌더링 최적화가 잘 되어있는 라이브러리를 사용하면 되지 않을까? 생각했다. | ||
| 그런데 그게 아니었다. | ||
|
|
||
| 라이브러리와 context를 나누는 기준은 범위로 생각을 해야한다. context는 A라는 컴포넌트 트리 안에서만 공유되어야 하는 값일 때 사용한다. | ||
| 따라서 Provider로만 감싼 해당 트리 안에서만 유효한 범위를 만들 수 있다. | ||
|
|
||
| 반면 라이브러리는 A라는 트리 컴포넌트 뿐만이 아니라 B, C같이 여러 곳에서 읽고 쓰는 값에 사용한다. | ||
| 여기서 또 나처럼 질문이 생길 수도 있다. 엥 그냥 값 관리면 context로 관리하는 것도 라이브러리로 하면 되는 거 아닌감? | ||
| 모달로 예시를 들어보자. 나는 a라는 모달의 오픈 여부를 담아가고 싶다. 그걸 라이브러리로 관리하면 Modal이 닫혀도 상태가 남아있고, 앱 어디서든 접근 가능해져서 관심사 분리가 깨지는 것이다. | ||
| -> 왜? 라이브러리는 storge에 저장이 되는데, 그건 페이지를 닫거나 아예 값을 날려버리기 전까지는 남아있기 때문이다. (앱 생명주기 동안 살아있다는 뜻) | ||
| -> 근데 context는 Provider가 unmount되면 같이 사라진다. (모달이 닫히면 사라진다는 뜻) | ||
|
|
||
| props drilling이 일어났을 때 유용한 것은 맞지만 context의 주 목적이 props drilling이 아니라는 것에 초점을 맞춰야 한다. | ||
| 나처럼 props drilling에 초점을 맞췄다가 어? 그렴 라이브러리가 더 나은 거 아닌가? ㅇㅅㅇa 가 될 수도 있기 때문이다. | ||
| props drilling > 부수 효과. 목적이 아님!! | ||
|
|
||
| --- | ||
|
|
||
| \_참고: [heropy.dev](https://heropy.dev), [React 공식 문서](https://ko.react.dev/learn/passing-data-deeply-with-context) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| # 프로-바이더 | ||
|
|
||
| ## 서론 | ||
|
|
||
| 하,,,, | ||
|
|
||
| 분명? 나는 리액트 컴포넌트의 성능을 런타임에도 적은 오버헤드로 telemetry 할 수 있는 프로파일링 라이브러리를 만들고 있었는데, 해당 라이브러리를 그냥 쌩으로 만들기에는 지식이 너무 없었다. | ||
|
|
||
| 그래서 claude에게 설계를 보여주며 짜달라고 했고, 해당 코드들을 분석 후 다시 처음부터 내가 만들려고 했다. | ||
|
|
||
| 그런데 코드 분석 중 `createContext`과 `useContext`을 맞닥뜨리게 되었다… 그게 뭔데? `useState`나 `useRef`같은 것들만 사용해오던 나에게는…. 너무 어려웠다….n | ||
|
|
||
| 공식 문서를 자세하게 읽어봐도 잘 모르겠어서 아는 프론트엔드 개발자분들께 여쭤보니 context가 최상단에서 상태를 저장해두고 상단에서 context provider를 설정해두고 쏴주는거라고,,, 약간 전역 변수처럼 쓰이는 친구인가보다... | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 직접 Context와 Provider를 구현해서 여기 branch에 남기면 좋을거 같아요. 그러면 다른 분들이 릴레이로 작성해서 커밋하고 등등하는 행위가 일어나도 좋을거 같아요. |
||
|
|
||
| > Redux, recoil, tq의 조상격(?),,,? 전역으로 변수 설정이 가능한 친구... | ||
| > useContext with Providers 참고.... | ||
| > 전역 라이브러리와 context API 차이... | ||
| > provider 컴파운드 컴포넌트도 따로 분리... | ||
|
|
||
| 예...? provider가 뭔데요...? | ||
|
|
||
| --- | ||
|
|
||
| ## 그래서 provider는 또 뭔데? (context.provider) | ||
|
|
||
| ```js | ||
| <MyContext.Provider value={/* 어떤 값 */}> | ||
| ``` | ||
|
|
||
| 리액트로 컴포넌트를 만들 때 상태값 관리는 보통 props나 state로 관리한다. | ||
| 여기서 context가 나오는데, context는 간단하게 props drilling 문제를 해결하기 위한 React의 내장 기능이라고 보면 될 것 같다. | ||
|
|
||
| context에 포함된 react provider는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다. | ||
| provider 컴포넌트는 value prop을 받아서 이 값을 하위에 있는 컴포넌트에게 전달한다. 값을 전달받을 수 있는 컴포넌트 수에 제한은 없다. | ||
| provider 하위에 또 다른 provider를 배치하는 것도 가능하며, 이 경우 하위 provider의 값이 우선시된다. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 어떤 문서에서 참고했을까요? 이중 provider는 처음 들어봤고 그런 패턴을 본적이 없어서 매우 흥미롭네요! 한번 구현해 주셔도 좋을거 같아요. 약간 React Provider와 Recoil은 동시에 사용하는 방법일까요? |
||
| Redux도 내부적으로 같은 Provider 패턴을 사용한다. | ||
|
|
||
| 그러니까 provider는 HOC로 context를 제공하고, react가 제공하는 createContext 메서드를 활용하여 context 객체를 만들어낼 수 있다. | ||
| Provider 컴포넌트는 value라는 prop으로 하위 컴포넌트들에게 내려줄 데이터를 받는다. 이 컴포넌트의 모든 자식 컴포넌트들은 해당 provider를 통해 value prop에 접근할 수 있다. | ||
|
|
||
| 요약: provider 안에 있는 자식 컴포넌트들은 provider 라인에서 선언한 context를 value와 useContext를 통해 받을 수 있다. | ||
|
|
||
| ```js | ||
| function App() { | ||
| const data = { ... } | ||
|
|
||
| return ( | ||
| <div> | ||
| <DataContext value={data}> | ||
| <SideBar /> | ||
| <Content /> | ||
| </DataContext> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| function SideBar() { | ||
| const { data } = React.useContext(DataContext); | ||
| return <h1>{data.text}</h1>; | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| 가만히 보면 그냥 context랑 별반 다를 게 없는 친구 아니야? 싶겠지만, 그건 내가 이해를 잘못 해서 그런 것이었다. | ||
za0012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| provider와 context는 역할이 다르다... provider는 값을 넣는 쪽, context는 값을 꺼내는 쪽이라고 보면 될 것 같다. | ||
| Provider 없이 useContext만 쓰면 값이 null이고, useContext 없이 Provider만 쓰면 값을 꺼내지 못한다... | ||
| 그리고 Provider가 감싼 범위만 전역변수로 사용할 수 있으니 참고할 것. | ||
|
|
||
| ```js | ||
| const UserContext = createContext(null); | ||
| // Provider가 없을 때 사용되는 기본값 | ||
| // Provider 안에 있으면 이 값은 무시됨 | ||
|
|
||
| // Provider → 값을 "넣는" 쪽 | ||
| <UserContext value={{ name: "홍길동" }}> | ||
| <App /> | ||
| </UserContext>; | ||
|
|
||
| // useContext → 값을 "꺼내는" 쪽 | ||
| function GrandChild() { | ||
| const user = useContext(UserContext); // 꺼냄 | ||
| } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| 기존에 provider를 사용할 때는 ~context.provider를 했어야 했는데, React 19로 올라오면서 `<SomeContext.Provider>` 대신 `<SomeContext>`를 바로 Provider로 렌더링할 수 있다. | ||
|
|
||
| ```js | ||
| // React 18 이하 (구버전) | ||
| <ThemeContext.Provider value={theme}> | ||
| <Page /> | ||
| </ThemeContext.Provider> | ||
|
|
||
| // React 19 이상 (신버전) — 완전히 동일한 동작 | ||
| <ThemeContext value={theme}> | ||
| <Page /> | ||
| </ThemeContext> | ||
| ``` | ||
|
|
||
| ## 번외. 훅으로 만들기! | ||
|
|
||
| 이런 식으로 각 컴포넌트에서 useContext를 import 하는 대신 필요로 하는 컨텍스트를 직접 반환하는 훅을 구현할 수 있다. | ||
| 커스텀 훅으로 감싸면 에러 메시지도 넣을 수 있고, 매번 import를 두 번 안 해도 된다...! | ||
|
|
||
| ```js | ||
| function useThemeContext() { | ||
| const theme = useContext(ThemeContext); | ||
| if (!theme) { | ||
| throw new Error("useThemeContext must be used within ThemeProvider"); | ||
| } | ||
| return theme; | ||
| } | ||
| ``` | ||
|
|
||
| ## 번외2. 검색 | ||
|
|
||
| 검색할 때 react provider 라고 하면 나오는 게 많이 없다... provider 패턴이라고 검색해야 잘 나온다. 참고하기! | ||
|
|
||
| ## 참고 | ||
|
|
||
| [Provider 패턴](https://patterns-dev-kr.github.io/design-patterns/provider-pattern/) | ||
| -> 괜찮은 기술?블로그입니다. 참고하시면 좋을 것 같아요. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분도 너무 궁금한... 정확힌 어떤 개발과 시도를 하시려 했던건지 알고 싶어요. 지난번에 공유해주신 내용같은데 추후에 시간이 되면 이 부분도 추가해서 작업하면 좋을거 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 작업 완료 후 배포까지 하게 된다면 추가해놓겠습니다...
지금은 공부하다가 context에서 막힌 상태라... 둘 다 어느 정도 이해는 햇습니다