중간에 useContext훅을 끼워넣으면 Consumer가 사라지고, 컴포넌트는 Context의 존재에 대해 몰라도 됨.
같이 보면 좋을 글
https://itchallenger.tistory.com/646
https://itchallenger.tistory.com/644
리액트에 IOC 컨테이너가 필요할까?
위의 영상에서도 그렇고 프론트는 백엔드에서 쓰는 것들이 없어서 쉽다는 의견이 좀 있는것 같습니다.
프론트의 DI나 IoC에 대한 논의를 아무리 찾아봐도 볼 수가 없다고 하셨는데, 미디엄에 치니까 관련된 지료가 수두룩합니다.
- 사실 위 글을 다 보진 않았습니다. 어차피 IoC 컨테이너 쓰는 내용일 텐데
아시다시피 리액트 프로젝트에서 다같이 IoC 컨테이너 쓰자! 라고 주장하는건 요즘 트렌드에 맞지 않는것 같습니다. - 프론트의 DI나 IOC를 제대로 만나시려면 사실 앵귤러를 하는게 맞습니다. 거기는 위 두가지를 빼놓고 논의가 안되는 세계이기 때문입니다.
- 추가로 리액트 같은 경우는 스프링이나 앵귤러처럼 IoC 컨테이너를 자체적으로 임베딩하고 있지 않아,
이걸 사용하려면 내가 바닥부터 셋업을 해야합니다.
그런데 해당 글에 달린 댓글이 인상깊은데요
React.Context는 Redux나 Mobx의 사용을 배제하지 않습니다. 컨텍스트 API의 상태 관리를 위해서요
(주 : context 는 상태관리 도구가 아님)
React.Context의 좋은 점은 개발자가 종속성 측면에서 생각하도록 강제한다는 것입니다.
따라서 개발자는 DI 라이브러리를 사용하는 대신 자연스럽게 Context를 종속성으로 생각합니다.
React.Context는 몇 가지 종속성을 요청한 다음
새 컨텍스트를 구성하는 팩토리 메서드로 생각할 수 있습니다.
컨텍스트 provider의 순서는 위상 정렬된 종속성 순서에 불과합니다.
따라서 모든 React.Context와 해당 컨텍스트에 다른 컨텍스트 종속성이 있는 경우
위상 정렬하면 컨텍스트 API가 알아서 의존성 관련 설정을 자동으로 수행할 수 있습니다.
(Context.Provider를 트리에서 올바른 순서로 마운트).
아마도 추가 50-100줄의 코드일 것이고
여전히 코드베이스를 바닐라 React로 유지하며
객체를 얻기 위해 사용하는 매직 싱글톤 인젝터 객체에 의존할 필요가 없습니다.
사실 위 댓글의 내용을 완벽하게 이해한 것은 아닙니다만
지금까지 여러 상태관리 도구들을 공부하며 의존성 주입 관련 게시물 들을 읽어본 뒤의 제 생각은 다음과 같습니다.
- 의존성 주입을 제대로 하려면 분명 IoC 컨테이너가 필요합니다.
- 아니면 지금처럼 컨테이너 / 프레젠데이션 컴포넌트 패턴을 사용하는 정도가 최선입니다.
- 하지만 개인적으로 컴포넌트는 렌더링 관심사를 보여주는게 좋다고 생각합니다.
- 컨테이너 컴포넌트가 순수한 컴포넌트 사이에 끼어있으면, 프러덕션에서 사용하는 컴포넌트를 다른 기술에 의존 없이 테스트할 수 없습니다.
- jest.jn을 이용한 몽키패치, 가짜 리액트쿼리 객체, msw등의 방법론을 사용해야 합니다.
- context API는 렌더링 관련 성능적인 이슈가 분명히 있습니다.
- 마이크로한 최적화와 반응성을 원한다면, 리코일이나 jotai, zustand를 사용해야 합니다.
- 아니면 직접 만들어보세요! : https://blog.saeloun.com/2021/12/30/react-18-usesyncexternalstore-api
- context API는 의존성 주입 도구로 충분히 사용할 수 있습니다.
- 함수로 따지면 고차함수 클로저에 불과합니다.
- 함수형 프로그래밍 입장에서도 자연스럽고, 리액트 관점에서도 자유롭습니다.
- jest.jn을 이용한 몽키패치, 가짜 리액트쿼리 객체, msw등의 방법론이 필요 없습니다.
- 가짜 함수, 가짜 Provider만 사용하면 됩니다.
- keep it simple, stupid!
그래서 어떻게 하나요?
데이터의 의존성을 제공하는 방법은 아래 글에서 한번 설명한 적이 있습니다.
https://itchallenger.tistory.com/286?category=1064448
그럼 함수 의존성은 어떻게 제공할까요? 훅을 내려줍니다
// 실제로 이렇게 하는것보단 커스텀 훅에서 리턴값을 뷰 프렌들리한 인터페이스로 한번 매핑해주는게 좋습니다.
// (DTO)
const MyContext = React.createContext<{
usePosts: () => {
data: PostProps[] | never[];
isLoading: boolean;
};
usePost: (
id: number
) => {
data: PostProps | never[];
isLoading: boolean;
};
}>(null);
커스텀 훅의 타입 추론 결과는 다음과 같이 정말 간단합니다.
dip를 이용하면 리액트 쿼리에서 데이터를 가져온다는 사실을 숨길 수 있습니다.
{
data: PostProps | never[];
isLoading: boolean;
};
컴포넌트에선 아래와 같이 사용합니다.
export const PostArticle = ({
match
}: {
match: {
params: {
postId: string;
};
};
}) => {
const { usePost } = useMyContext();
const { data, isLoading } = usePost(match.params.postId);
if (isLoading) {
return null;
}
return (
<div>
<div>{data?.body}</div>
</div>
);
};
테스트 시에는는 아래와 같이 가짜 함수를 컨텍스트에 제공할 수 있습니다.
아래와 같이 전부 가짜함수를 제공하면 상단의 리액트 쿼리 컨텍스트 프로바이더가 없어도 동작하겠네요.
export const TestMyContextProvider = ({
children
}: {
children: React.ReactNode;
}) => {
return (
<MyContext.Provider
value={{ usePosts: () => ({ data: [], isLoading: false }), usePost }}
>
{children}
</MyContext.Provider>
);
};
동작하는 예제는 다음과 같습니다.
남이 만든 예제를 fork해서 PoC 개념으로 작업한거라,
(사실 제 머릿속의 생각을 코드로 옮긴 것 뿐)
프로덕션 레벨로 정돈되어있거나 sw공학적으로 완벽하지는 않습니다만,
아래의 Takeaways를 보여주기엔 충분한 것 같습니다.
Takeaways
context API를 잘 활용하면 스프링 IOC 컨테이너와 유사한 효과를 발휘할 수 있다.
- 테스트 시 프로바이더를 교체하는것 만으로 컴포넌트를 그대로 테스트에 활용할 수 있다.
- 가짜 클래스를 제공해서 가짜 함수를 의존성 주입하는 효과를 발휘할 수 있다.
- 리액트 쿼리라는 구체적인 기술(클래스)에 대한 의존성을 인터페이스를 이용해 숨길 수 있다.
참고 :
https://kentcdodds.com/blog/how-to-use-react-context-effectively
'FrontEnd' 카테고리의 다른 글
자바로 알아보는 결합도(커플링) (0) | 2022.08.01 |
---|---|
React 18의 useSyncExternalStore, Tearing 현상은 무엇인가? (0) | 2022.08.01 |
리액트 프로젝트의 결합도를 관리하는 방법 (3) | 2022.07.31 |
리액트 컴포넌트의 응집도를 관리하는 방법 (0) | 2022.07.30 |
리액트 Concurrent UI Pattern - Scheduling in React (0) | 2022.07.30 |