[TS]Apollo Client에서 Reactive Variables로 상태 관리하기

peppermint100
8 min readSep 3, 2020

--

서론

React로 SPA 웹 어플리케이션을 만들 때 가장 고민이 되는 부분이 상태 관리입니다. 일반적으로 React Hooks의 useState를 통해 간단한 상태를 관리하고 만약 클래스형 컴포넌트를 사용한다면 setState를 통해 state를 변화시켜 관리합니다.

간단한 기능을 가진 어플리케이션이라면 useState 정도에서 상태 관리를 해도 되지만 대부분의 경우 그렇지 않습니다. 정말 간단한 todo list를 만들더라도 처음부터 props가 내려갈 구조를 제대로 생각해 놓지 않는다면 상태 관리가 꼬이게 되죠.

그래서 등장한게 Redux입니다.( Context API나 MobX도 있습니다.) Redux는 전역 상태 관리를 하게 만들어줍니다.

하지만 Redux는 굉장히 복잡합니다. Redux는 간단한 문제에 대한 복잡한 솔루션이라는 말도 있죠. 최근에 GraphQL과 Express로 서버를 만들고 프론트단에서는 Apollo Client를 공부해보고 있는데 여기서 상태 관리를 어떻게 하면 좋은지 공식 문서를 읽고 배운 점에 대해 적어보겠습니다.

동작 방식

리액트에서 GraphQL 통신 방식은 2가지 입니다. 서버와의 통신이 있고 웹 어플리케이션 자체(캐시)에 저장하는 로컬 통신이 있습니다. 서버와의 통신은 아래 링크를 참고하여 아폴로 서버의 기본적인 방법을 사용하면 됩니다.

여기서 우리가 다룰 건 Local State입니다. 이 상태는 아폴로 서버의 캐시에 저장되어 클라이언트 단에서만 변화하고 React의 state와 같이 변화하면 컴포넌트를 다시 렌더링하게 됩니다.

그리고 심지어 이 Local State를 서버와 함께 한 번의 GraphQL 쿼리로 Local State와 서버의 응답을 하나의 결과로 받을 수 도 있습니다. 그리고 그 이후에는 그 결과 정보를 캐시에 저장하여 서버와의 통신 없이 아폴로 서버가 계속 정보를 보여줄 수 있게 되죠.(아폴로 서버의 장점 중 하나입니다! 이 점에 대해선 다음 포스트로 작성하도록 하겠습니다.)

아폴로 서버 설정

기본적인 아폴로 서버 설정은 위 링크 공식 문서를 참고하시면 됩니다. 그리고 저희는 여기서 cache 부분을 바꿔줄 겁니다.

cache: new InMemoryCache({
typePolicies: {
Query:{
fields:{
// 여기에 Reactive Variables을 지정해줍니다.
}
},
},
}),
})

아폴로 서버 설정에 위 처럼 cache를 수정해줍니다.

그리고

import { makeVar } from "@apollo/client";export const userVar = makeVar<UserProps | null>(null)
//cache.ts

위와 같이 makeVar를 import해줍니다. makeVar 안에 이제 저희가 상태를 집어 넣을 겁니다. 여기서 저희는 유저의 정보를 넣는다고 가정하고 그 유저의 정보는 UserProps라는 타입을 가지게 제네릭으로 설정해줍니다.

import { userVar } from "../local/cache"export const user  = {
read(){
//Any logic you want
return userVar()
}
}
// field.ts

그리고 위와 같이 field.ts라는 파일에는 user의 필드를 설정해줍니다. user 오브젝트안에 함수이름은 꼭 read여야합니다. 그리고 read가 실행되면 위에서 작성한 userVar를 돌려주도록 합니다. 여기서 모든 비즈니스 로직이 가능합니다. user 쿼리가 실행되었을 때 필요한 작업을 하고 userVar를 돌려줄 수 도 있습니다.

그리고 마지막으로 아폴로 서버 설정에 가서

cache: new InMemoryCache({
typePolicies: {
Query:{
fields:{
user
}
},
},
}),
})

user 필드를 위와 같이 추가해줍니다.

사용

이제 tsx 혹은 jsx 컴포넌트에서 상태를 읽어오고 변화를 시켜보겠습니다.

import { userVar } from '../../local/cache'const YourComponent: React.FC = () => {const user = userVar()
console.log(user)
return () => <h1>Component</h1>}export default YourComponent

위처럼 userVar를 리터럴로 지정해주고 console에서 확인하면 null을 확인할 수 있습니다. 이제 user라는 변수를 통해 상태 값에 접근할 수 있게 되었습니다.

상태를 변화하는 방법도 간단합니다.

import { userVar } from '../../local/cache'const user = { name: "pepper", age: 26 }
userVar(user)
// anywhere

어디서든지 userVar를 가져오고 userVar안에 지정했던 제네릭과 같은 타입의 데이터를 집어 넣어주기만 하면 됩니다.

const user = userVar()

라는 리터럴을 통해 상태 값을 전달해 줄 수 있습니다.

하지만 이렇게 reactive variable을 통해 userVar안의 user 값을 변화시키더라도 리액트 컴포넌트가 변화 감지를 못하는지 다시 렌더링을 하지 않습니다.

공식문서를 살펴보면

Most importantly, modifying a reactive variable automatically triggers an update of every active query that depends on that variable.

이렇게 적혀있습니다. 즉 reactive variables의 변화를 감지하면 컴포넌트를 직접적으로 재 렌더링하는 것이 아닌 쿼리를 다시 trigger시켜 컴포넌트가 변화를 감지하는 것입니다.

따라서 다음과 같이 쿼리문을 작성해줍니다.

import { gql } from "@apollo/client";export const GET_USER_STATE = gql`
query GetUserInfo{
user @client
}
`
// query.ts

여기서 굵은 표시로 된 user는 아폴로 서버 설정 부분에서 fields내의 user와 같아야 합니다. 그리고 user는 클라이언트 단에 작성된 필드이므로 @client를 추가해줍니다.

그리고

const { loading, data: { user } } = useQuery<{ user: User} >(GET_USER_STATE)<button onClick={ () => {userVar(YourAnotherUser}>
state change
</button>//YourComponent.tsx에 위 내용을 추가해줍니다.

위처럼 useQuery를 통하여 userVar안의 값에 접근해주고 버튼을 통해 userVar를 변화시키면 컴포넌트가 변화를 감지하고 재 렌더링을 하는 모습을 보실수 있습니다.

결론

Apollo Client의 Reactive Variables를 사용하면 리덕스보다 쉽게 전역 변수를 GraphQL 문으로 관리할 수 있었습니다. 물론 서버 통신을 GraphQL로 하고 전역 변수는 리덕스로 관리 할 수도 있습니다. 하지만 리덕스는 간단한 문제에 대한 복잡한 솔루션이라는 말이 있고 GraphQL와 Apollo를 통해 웹 어플리케이션을 만든다면 상태 관리 역시 GraphQL의 쿼리문을 통해 관리하는 것이 코드 작성에 있어서 통일성을 준다고 생각합니다. 최근에 GraphQL을 공부하고 있는데 express와 graphql을 통해 서버를 생성하고 resolver와 query를 어느정도 작성해놓으면 그 이후에 또 다른 API가 필요할 때 백엔드 코드를 건들지 않고 클라이언트 단에서 쿼리문으로 필요한 정보를 가져올 수 있다는 점을 직접 경험하니 GraphQL이 참 편리하다는 점을 느꼈습니다.

--

--

peppermint100
peppermint100

Written by peppermint100

기억하기 위해 또는 잊어버리기 위해 작성하는 블로그입니다.

No responses yet