Relay 찍어먹기

koreanhole·2021년 4월 27일
1

GraphQL

목록 보기
2/2

Relay

relay는 리액트 어플리케이션에서 graphql을 사용한 데이터 fetching과 managing 기능을 제공하는 자바스크립트 프레임워크이다. 단순하게 api client기능만 수행하는것이 아니라 컴포넌트의 데이터 독립성을 위한 아키텍쳐도 제시한다.

relay는 컴포넌트에서 정의된 데이터의 가장 효율적인 fetching을 보장한다. 또한 불필요한 리렌더링을 방지하기 위해 정말 필요할때만 데이터의 업데이트를 수행한다. 그리고 GraphQL의 Mutation후 변경된 데이터에 대한 로컬 데이터의 업데이트를 보장하며 항상 최신의 데이터를 보장해준다.

기존 Data Fetching방식의 문제점

알다시피 React는 재사용 가능한 컴포넌트의 조합으로 이루어져 있다. 특정 컴포넌트는 아래로 여러 개의 컴포넌트의 합성으로 이루어져 있다. 기존의 data fetching방식은 크게 두가지로 이루어져 있다.


  1. 상위 컴포넌트에서 하위 컴포넌트에서 필요한 모든 데이터를 fetching한 후 하위 컴포넌트로 넘겨준다.
  2. 각각의 하위 컴포넌트에서 필요한 데이터를 알아서 fetching한다.

첫번째 use case의 경우는 상위 컴포넌트와 하위 컴포넌트의 데이터 커플링이 발생하는 문제가 있다. 하위 컴포넌트의 변경이 있을 경우 데이터 fetching을 담당하는 상위 컴포넌트에서 다시 fetching을 해야하고 그 결과 하위 컴포넌트의 리렌더링이 일어나게 된다.

두번째 use case의 경우는 데이터 fetching의 staging이 발생한다는 문제가 있다. 상위 컴포넌트에서 하위 컴포넌트로 내려가면서 단계적으로 데이터 fetching이 발생하고 이에 따라 반복적으로 하위 컴포넌트의 리렌더링이 발생하면서 성능에 안좋은 영향을 끼친다.

위와 같은 기존 data fetching방식의 문제점을 graphql과 relay를 사용하면서 해결할 수 있다. relay는 단 하나의 graphql query으로 하위 컴포넌트에서 필요한 모든 데이터를 가져올 수 있다. 또한 각 하위 컴포넌트는 graphql fragment라는 것을 통해 필요한 데이터요소를 선언한다. 이로 인해 graphql runtime은 단 한번의 네트워크 요청으로 view에서 필요한 모든 데이터를 가져올 수 있다.

컴포넌트에서 필요한 데이터의 선언

relay를 사용하면 단 한번의 네트워크 요청으로 view에서 필요한 모든 데이터를 가져올 수 있다고 했는데 구체적으로 어떤 방식으로 가능한건지 알아보자.

relay에서는 fragment 를 사용해 필요한 데이터를 표현할 수 있다. 특정 타입 객체에서 필요한 데이터 필드를 나타낸다.

const authorDetailsFragment = graphql`
  fragment AuthorDetails_author on Author {
    name
    photo {
      url
    }
  }
`;

위와 같은 fragment는 useFragment 훅을 통해 스토어에 있는 데이터를 가져올 수 있다.

export default function AuthorDetails(props) {
  const data = useFragment(authorDetailsFragment, props.author);
  // ...
}

useFragment 훅의 두번째 파라미터인 props.author는 fragment의 참조값이다. fragment의 참조값은 해당fragment를 다른 fragment나 query에서 ... 를 통해 얻을 수 있다. fragment 자체로는 실제 서버에 있는 데이터를 가져올 수 없기 때문에 spread syntax를 통해 query내에서 사용되어야 한다.

fragment를 사용해 실제 데이터를 가져오는 쿼리는 다음과 같다.

const storyQuery = graphql`
  query StoryQuery($storyID: ID!) {
    story(id: $storyID) {
      title
      author {
        ...AuthorDetails_author
      }
    }
  }
`;

위의 쿼리를 사용하는 컴포넌트는 다음과 같다.

function Story(props) {
  const data = useLazyLoadQuery(storyQuery, props.storyId);

  return (
		<>
	    <Heading>{data?.story.title}</Heading>
	    {data?.story?.author && <AuthorDetails author={data.story.author} />}
	  </>
	);
}

AuthorDetails_author fragment를 통해 한 번의 네트워크 요청을 통해 Story , AuthorDetails 컴포넌트에서 필요한 데이터를 가져왔다.

useFragment

const data = useFragment(fragment, fragmentReference);

  • useFragment의 첫번째 인수인 fragment는 graphql template literal로 나타낸 GraphQL fragment를 나타낸다.
  • fragmentReference는 Relay 객체의 참조값을 나타낸다. 이 참조값을 통해 스토어에서 데이터를 가져온다.
    • graphql lint rule 을 통해 fragment reference가 적절히 선언되었는지 확인할 수 있다.
  • 반환값
    • fragment에서 선언한 데이터 필드와 그 모양대로 스토어에서 가져온 데이터를 나타낸다.
  • 해당 fragment를 사용한 컴포넌트는 자동적으로 fragment data의 업데이트를 구독하게 된다. 따라서 앱의 어딘가에서 데이터가 업데이트 된다면 컴포넌트는 자동적으로 최신의 데이터로 렌더링된다.

Data Masking

Relay에서는 컴포넌트로 하여금 해당 컴포넌트에서 명시한 데이터에만 접근할 수 있게 한다. 명시하지 않은 데이터에 대해서는 알 수도 없으며 알 필요도 없다. 자식 컴포넌트에서 어떤 데이터를 필요로 하는지도 몰라도 된다.

결론

Relay는 선언적인 data-fetching이 가능하게 한다. 컴포넌트에서는 fragment를 통해 필요한 데이터를 선언하기만 한다. 데이터를 가져오는 효율적인 방법은 Relay가 책임진다. 이러한 방식은 선언적인 방식으로 UI를 개발할 수 있는 React와 결합되면 효과가 극대화된다. 필요한 데이터를 선언하기만 하는 방법은 컴포넌트 간 커플링이 감소된 굳건한 컴포넌트를 만들 수 있다.

0개의 댓글