라이브러리&프레임워크/React

[React] ContextAPI

youngble 2022. 9. 13. 00:46

contextAPI는 왜 쓰는가?

위의 그림을 예시로 최상위 컴포넌트 App을 중심으로 하위 컴포넌트들의 트리구조를 확인할 수있는데, 리액트를 사용하면 state관리하면서 해당 컴포넌트에서도 사용을위해 props로 하위 컴포넌트에 state를 전달해주게 되는데

이렇게되면 위의 사진처럼 Login이라는 stateLoginForm 컴포넌트에서 사용하고 setState로 변화시켜 이 stateShop, Cart 컴포넌트에도 영향을 미치는데 전달할수있는 connection이 없다. 또한 Product컴포넌트에서 Add to Cart 라는 장바구니 기능역시 Cart라는 컴포넌트에 영향을 주게 되지만 서로 연결점이없기 때문에 이를 최상위 컴포넌트인 App 컴포넌트에서 state를 생성하여 전달해줘야하는데 이렇게 되면 불필요하게 props drilling이 발생하게되고 유지보수, 코드이해, 수정 등이 어려워지게 된다. 이러한 단점을 보완하기위해 사용하게 되는게 ContextAPI 인데 이렇게 연결점이없는 컴포넌트 간의 state를 direct로 전달하여 이용할 수 있게 해줌으로써 props drilling을 줄임과 동시에 필요한 부분에서만 사용하기때문에 코드이해와 유지보수, 수정이 더 효율적이고 쉬워진다.

 

사용 예시

먼저 contextAPI를 관리할 store 폴더를 만들고 파스칼형식이 아닌(컴포넌트가 아니기에) 케밥케이스로 만들어주었다.

auth-context.js 파일안에 코드는 위와 같은데 contextAPI를 쓰기위한 기본 세팅은 React 내장함수createContext를 사용하여 기본 default state를 설정해준다. 이때 해당 값을 담을 변수를 const AuthContext로 만들어 담아주었다.

로그인여부를 위한 state값이 필요함으로 isLoggedIn 이라는 키에 기본적으로 false를 주었다.

이제 이 컨텍스트를 사용하기위해서 최상위 컴포넌트인 App.js에 넣어준다.

App.js

위의 코드 처럼 MainHeader컴포넌트와 main태그에 포함된 Login, Home 컴포넌트에서 사용하기위해서 AuthContext컴포넌트로 묶어주었는데 이때 .Provider를 사용하여야한다 이렇게 해야 공급자로써 해당 Authcontext 컨텍스트에 접근하여 사용할 수 있게 된다. 이때 해당 컨텍스트값에 접근할려면 리스닝을 사용해야하는데 이때 두가지 방법이 있는데 1. AuthContext에 포함된 Consumer 를 사용하거나 2. React 훅인 useContext를 사용한다.

 

Consumer를 사용할수도 있지만 이것을 활용한 예시는 스킵하고 좀더 Syntax(문법,구문)적으로 더 깔끔한 2번 useContext 예시만 설명 작성하려고 한다.

 

먼저 context를 사용하기위해서 AuthContext.Providervalue값을 정해줘야한다. 이는 공급자(Provider)를 쓰기때문에 넣어주는데 만약 Provider를 쓰지않는다면 따로 value 값을 지정해주지 않아도 기본 isLoggedIn의 default값인 false를 사용 할수 있다. 하지만 여기서는 공급자(Provider)가 있고 변할수있는 값의 컨텍스를 사용하기위해서 value 를 기본 세팅해준다. 

여기서 나온 isLoggedIn은 로그인여부에 사용할 useState값이다

이제 contextAPI를 사용하기 때문에 위에처럼 MainHeader 컴포넌트로 전달해주는 props중 하나인 isAuthenticated를 지워주었고 MainHeader에서도 prop으로 사용했던 isLoggedIn = {props.isAuthenticated} 를 지워주었다

 

그다음 위에처럼 useContext훅을 불러와 사용할 컨텍스트인 AuthContext 를 인자로 넘겨주고 ctx 라는 변수에 담아주었다.

이렇게 하면 ctx안에 있는 isLoggedIn 이라는 객체를 사용할수있고 이는 App.js 에서 value로 넘겨준 state이기 때문에 그에 해당하는 true/false 값이 된다.

 

또한 함수를 props로 넘겨주는 것 또한 컨텍스트로 사용할 수 있기 때문에 MainHeader에 넘겨주는 onLogout={logoutHandler} 도 지워주고 이를 props로 사용하여 Navigation 컴포넌트에 넘겨주는 onLogout ={props.onLogout}도 지워주었다.

그리고나서 ProvideronLogout이라는 키에 logoutHandler 함수를 가리키게 하였고

이제 컨텍스트에 접근하여 사용할수 있기때문에 Navigation컴포넌트에 ctx.onLogout로 바꿔주었다.

이런식으로 다른 컴포넌트에서도 props 로 전달하는 drilling을 없애고 다이렉트로 쓸수있게 해주었다.

 

하지만 이렇게 했을때 보기 안좋은 점은 프로젝트가 커지면 최상위컴포넌트 App.js에서 사용하는 state와 함수들이 늘어나게 되고 기존에도 사용하는 컨텍스트를 위해 App.js에 써놔야하는데 이렇게되면 App.js 자체코드가 방대하게 커지게 되고 각 다른 기능을 가진 컨텍스트들이라면 이들을 분리 나눠서 관리하기 힘들다.

auth-context.js

따라서 App.js에서 모든 state와 함수를 관리하지말고 auth-context.jsProvider컴포넌트를 하나 만들어주고 그컴포넌트안에 {props.children}을통해 묶어주기로한다. 그러고나서 App.js에서 사용하고 싶지않은 모든 state와 함수를 옮겨준다.

index.js

위와같이  index.js 에서 App컴포넌트를 AuthContextProvider 컴포넌트로 묶어주고 App.js에서 useContext를 사용하여 코드량을 줄였다. 이렇게 하면 최상위 App컴포넌트가 가볍고 따로 컨텍스트를 관리할수 있게 되었다.

 

하지만 이러한 이유라고 해도 만약 props drilling도 있지않고 짧은 props chain이라면 context를 사용할 필요가 없기때문에 기존의 useState와 props를 이용하는게 좋다.

 

그렇다면 리덕스와 contextAPI 둘다 전역상태관리를 할수 있는데 어떤것을 골라야하는가?

이에 대해선 여러가지가 있겠고 개인선호차이, 프로젝트 컨셉에 따라 다르겠지만 먼저 contextAPI 같은경우는 자주 바뀌는 state에 사용하면 좋지않다고한다.

예를들어 1분에 수십번 수백번 빠르게 바뀌어야하는 state일 경우는 context를 사용하지말고 더 나은 툴인 리덕스를 사용하라고 하고있다.

또한 개인적인 의견이지만 위와같이 state를 관리할때 보통 context를 사용하면 가벼운 세팅으로도 사용할수 있지만 리덕스 경우는 세팅도 많아 소요시간이 많기때문에 이경우는 개인적으로 리덕스를 사용하지 않지만, 백엔드와의 api통신을 통해 방대한 값들을 사용하기위해서는 리듀서를 통해 리덕스로 사용하는 것을 애용하고 있다. api를 통해 넘어오는 값들은 보통 json 파일로 방대한 양의 키값을 가지고 있고 state를 변경한다기보단 기존의 순수 json 값을 사용하기 때문에  contextAPI로 관리하기에는 개인적으로 좋지 않다고 생각한다.