Published on

[번역] Relay - Graphql Queries

Authors
  • avatar
    Name
    Kim, Dong-Wook
    Twitter

GraphQL Queries

GraphQL Query란 GraphQL 서버로부터 쿼리하고 싶은 데이터의 설명서 입니다. 이것은 여러 필드들의 집합으로 구성되어 있습니다(잠재적으로 Fragment가 될 수 있습니다). 우리가 쿼리할 수 있는 것은 서버로부터 노출된 GraphQL Schema에 의존합니다.

쿼리는 네트워크 요청을 통해 전달될 수 있으며, 쿼리가 사용하는 변수를 가지고 데이터를 불러올 수 있습니다. 서버는 우리가 보낸 query의 형태에 맞춰서 JSON 객체를 응답할 것 입니다.

query UserQuery($id: ID!) {
  user(id: $id) {
    id
    name
    ...UserFragment
  }
  viewer {
    actor {
      name
    }
  }
}

fragment UserFragment on User {
  username
}

예시 응답


query UserQuery($id: ID!) {
  user(id: $id) {
    id
    name
    ...UserFragment
  }
  viewer {
    actor {
      name
    }
  }
}

fragment UserFragment on User {
  username
}

Rendering Queries

Relay에서 query를 렌더링하기 위해서 usePreloadedQuery Hook을 사용할 수 있습니다. usePreloadedQuery Hook은 query definition과 query reference를 가지며, 이것에 부합하는 데이터를 반환합니다

import type { HomeTabQuery } from 'HomeTabQuery.graphql'
import type { PreloadedQuery } from 'react-relay'

const React = require('React')
const { graphql, usePreloadedQuery } = require('react-relay')

type Props = {
  queryRef: PreloadedQuery<HomeTabQuery>
}

function HomeTab(props: Props) {
  const data = usePreloadedQuery<HomeTabQuery>(
    graphql`
      query HomeTabQuery($id: ID!) {
        user(id: $id) {
          name
        }
      }
    `,
    props.queryRef
  )

  return <h1>{data.user?.name}</h1>
}

코드를 자세히 살펴봅시다

  • usePreloadedQuery는 graphql 쿼리와 PreloadedQuery reference를 받습니다. 그리고 쿼리를 통해 받아온 데이터를 반환합니다
    • PreloadedQuery(여기서는 queryRef)는 우리가 데이터를 불러온 query 인스턴스에 대한 describe와 reference를 가진 객체입니다.
      • 실제로 어떻게 데이터를 불러오고, 데이터를 불러오는 과정에서 로딩 상태를 어떻게 렌더링하는지 Loading States with Suspense 섹션에서 설명합니다
  • fragments와 유사하게, 컴포넌트는 자동으로 query data에 대해 구독되어집니다. 만약 해당 쿼리에 대한 데이터가 어플리케이션 어디선가 업데이트 된다면, 컴포넌트는 가장 최신의 데이터를 가지고 자동적으로 re-render 됩니다.
  • usePreloadedQuery 또한 Flow type parameter를 가집니다.(해당 type은 쿼리의 Flow type과 부합합니다. 이 경우 HomeTabQuery를 보면 됩니다)
    • Relay 컴파일러는 자동으로 선언된 쿼리들에 대한 Flow type을 생성하며 이것은 generated 폴더안에 <query_name>``.graphql.js 파일을 import해서 사용할 수 있습니다
    • 주의해야할 점은, data는 명시적인 어노테이션 없이도 GraphQL Schema를 기반으로 적절한 Flow-type 되었다는 것 입니다. 예를 들면, 위의 예제에서 data의 타입은 { user: ?{ name: ?string } }입니다
  • Relay Environment Provider를 이용해서 Relay environment를 제공하고 있다는 점을 잊지 마세요.

Fetching Queries for Render

query를 렌더링하는 것을 떠나서, 우리는 서버로부터 query의 결과를 받아와야합니다. 보통 우리는 쿼리를 어플리케이션의 root에서 불러오려고 합니다. 그리고 화면을 그리기 위해 필요로하는 모든 데이터를 축적하는 하나 또는 소수의 쿼리들을 가지고 있습니다. 이상적으로, 우리는 어플리케이션을 렌더링 하기 이전에 가능한 빨리 데이터를 불러올 것 입니다.

후속적으로 데이터를 불러와서 렌더링하기 위해서, useQueryLoader Hookd을 사용할 수 있습니다


import type {HomeTabQuery as HomeTabQueryType} from 'HomeTabQuery.graphql';
import type {PreloadedQuery} from 'react-relay';

const HomeTabQuery = require('HomeTabQuery.graphql')
const {useQueryLoader} = require('react-relay');


type Props = {
  initialQueryRef: PreloadedQuery<HomeTabQueryType>,
};

function AppTabs(props) {
  const [
    homeTabQueryRef,
    loadHomeTabQuery,
  ] = useQueryLoader<HomeTabQueryType>(
    HomeTabQuery,
    props.initialQueryRef, /* e.g. provided by router */
  );

  const onSelectHomeTab = () => {
    // Start loading query for HomeTab immediately in the event handler
    // that triggers navigation to that tab, *before* we even start
    // rendering the target tab.
    // Calling this function will update the value of homeTabQueryRef.
    loadHomeTabQuery({id: '4'});

    // ...
  }

  // ...

  return (
    screen === 'HomeTab' && homeTabQueryRef != null ?
      // Pass to component that uses usePreloadedQuery
      <HomeTab queryRef={homeTabQueryRef} /> :
      // ...
  );
}

위의 예제를 살펴봅시다

  • AppTabs 컴포넌트에서 useQueryLoader를 호출하고 있습니다
    • 해당 훅은 query를 인자로 갖고 있는데, 이 경우 HomeTabQuery입니다(이전 예제에서 선언한 query 입니다). 그리고 우리는 자동으로 생성된 'HomeTabQuery.graphql'이 생성됩니다.
    • 해당 훅은 옵셔널한 초기 PreloadedQuery를 가지는데, 이는 homeTabQueryRef(useQueryLoader에 의해 반환된)의 초기 값으로 사용됩니다.
    • 그리고 Flow type parameter를 부가적으로 받습니다. 해당 타입은 'HomeTabQuery.graphql'에 자동생성될 것 입니다.
  • useQueryLoader를 호출함으로써 2가지 것을 얻을 수 있습니다
    • homeTabQueryRef: ?PreloadedQuery이며, 데이터를 불러온 query 인스턴스를 describe & reference하는 객체입니다. 해당 값은 쿼리를 불러오지 않았다면 null값을 가집니다. 위의 예시에서는 loadHomeTabQuery를 호출하지 않을 경우 입니다.
    • loadHomeTabQuery: 해당 쿼리에 대한 데이터를 서버로부터 불러오는 함수입니다(caching되지 않았다면). 그리고 쿼리가 예상하는 객체(변수와 함께!)를 주며, 이 경우 {id: '4'}입니다.(어떻게 Relay가 캐쉬된 데이터를 사용하는지 알고 싶다면 Reusing Cached Data For Render를 읽어보세요) 해당 함수를 호출하는 것은 homeTabQueryRef(PreloadedQuery의 인스턴스인)의 값 또한 업데이트 할 것 입니다.
      • 우리가 함수로 넘긴 variables는 GraphQL 쿼리가 예상하는 값이 맞는지 체크하기 위해서 Flow Type Check를 할 것 입니다.
      • 우리는 해당 함수를 event handler 안에서 호출함으로써 HomeTab이 렌더링 되도록 합니다. 이로써 우리는 새로운 Tab이 렌더링 되기도 전에 데이터가 가능한 빨리 화면에 그려지도록 할 수 있습니다.
        • 실제로, loadQuery는 React 렌더링 phase에서 호출되면 에러를 반환할 것 입니다.
  • useQueryLoader는 자동으로 컴포넌트가 언마운트되면 load된 모든 쿼리들을 처분(또는 해제)할 것 입니다. 이것이 의미하는 것은 Relay가 더 이상 특정 쿼리 인스턴스에 대한 캐시를 더 이상 가지고 있지 않을 것이라는 것 입니다.(Resuing Cached Data For Render에서 query data lifecycle에 대해서 알 수 있습니다). 추가적으로, 만약 쿼리 인스턴스 삭제가 이루어질때 query 요청이 진행중이면, 요청이 자동적으로 취소될 것 입니다.
  • AppTabs 컴포넌트는 이전 예제에서 HomeTab 컴포넌트를 렌더링합니다. 그리고 적절한 query reference를 넘깁니다. 즉, 부모 컴포넌트가 해당 쿼리에 대한 데이터의 lifetime을 가지고 있으며, 이 컴포넌트가 언마운트 될 때 query 인스턴스를 분해할 것 입니다.
  • 마지막으로, useQueryLoader 훅을 사용하기 전에 Relay Environment Provider를 이용해서 Relay 환경을 제공해야 합니다.

때때로, 부모 컴포넌트 바깥의 context에서 데이터 fetching을 시작하고 싶을 때가 있습니다. 예를 들면 어플리케이션의 초기 로드를 위한 데이터를 불러올때 말입니다. 이런 경우, useQueryLoader를 사용할 필요 없이 loadQuery API를 직접적으로 사용할 수 있습니다.


import type {HomeTabQuery as HomeTabQueryType} from 'HomeTabQuery.graphql';

const HomeTabQuery = require('HomeTabQuery.graphql')
const {loadQuery} = require('react-relay');


const environment = createEnvironment(...);

// At some point during app initialization
const initialQueryRef = loadQuery<HomeTabQueryType>(
  environment,
  HomeTabQuery,
  {id: '4'},
);

// ...

// E.g. passing the initialQueryRef to the root component
render(<AppTabs initialQueryRef={initialQueryRef} initialTab={...} />)

  • 위의 경우, PreloadedQuery 인스턴스를 직접적으로 얻기 위해서 loadQuery 함수를 호출하고 있습니다. 해당 인스턴스는 usePreloadedQuery를 사용하는 컴포넌트에게 후추 넘길 수 있습니다.
  • 이 경우, AppTabs 컴포넌트가 query reference의 lifetime을 가지고 있다고 예상할 수 있습니다.
  • 이 예제에서 "app initialization"의 세부사항에 대해서 단순하게 넘겼는데, 이는 어플리케이션 마다 매우 다를 것이기 때문입니다. 여기에서 중요하게 보고 넘어가야할 것은 root 컴포넌트 렌더링을 시작하기 전에 query reference를 획득해야한다는 것 입니다. 실제로, loadQuery는 리엑트 렌더링 phase에서 호출되면 에러를 반환할 것 입니다.

Render as you Fetch

위의 예제들은 데이터를 가능한 빨리 가져와서 유저에게 컨텐츠를 보여주기 위해서 렌더링으로 부터 데이터 fetching을 어떻게 분리해야할지 보여줍니다. 이를 통해 waterfalling round trip을 방지할 수 있고, 더 많은 제어권과 fetch가 발생했을때 예측가능성을 우리에게 줍니다. 반면 우리 렌더링 중 fetch가 발생하면, 어느 시점에 fetch가 발생할지 결정하기 힘들어집니다. 이 부분은 React suspense의 render-as-you-fetch 패턴과 매우 유사합니다.

이 패턴은 Relay가 데이터 fetching을 할때 선호하는 패턴입니다. 그리고 이 패턴은 여러 상황에서 적용되는데, 예를 들면 어플리케이션 초기 로딩할때, subsequent navigation이 되는 동안, 또는 일반적으로 UI 요소가 초기에 숨겨져있거나 추후 인터렉션에 의해 드러내어 질때(menu, popover, dialog, 등등), 그리고 추가적인 데이터를 불러올때 잘 적용될 수 있습니다.

Lazily Fetching Queries during Render

쿼리를 불러올때 다른 대안은 컴포넌트가 렌더링 될 때 게으르게(lazily) 쿼리를 fetch하는 것 입니다. 그렇지만, 이전에 언급했다싶이, 선호되는 패턴은 rendering 되기 전에 쿼리 fetching을 시작하는 것 입니다. 만약 게으른 fetching이 경고 없이 사용되어졌다면, nested 또는 waterfalling round trip을 발생시킬 수 있으며, 이는 성능 하락으로 이어질 수 있습니다

쿼리를 게으르게 fetch하기 위해서, useLazyLoadQuery Hook을 사용할 수 있습니다

const React = require('React')
const { graphql, useLazyLoadQuery } = require('react-relay')

function App() {
  const data = useLazyLoadQuery(
    graphql`
      query AppQuery($id: ID!) {
        user(id: $id) {
          name
        }
      }
    `,
    { id: '4' }
  )

  return <h1>{data.user?.name}</h1>
}

어떤 일이 일어나는지 봅시다

  • useLazyLoadQuery는 graphql query와 해당 쿼리를 위한 몇개의 변수를 취합니다. 그리고 해당 쿼리를 통해 얻어진 데이터를 반환합니다. 변수들은 GraphQL 쿼리 안에서 추론된 변수들의 값을 가진 객체입니다.
  • fragment들과 유사하게, 컴포넌트는 자동으로 query data 업데이트에 대해 구독 되어집니다. 만약 해당 쿼리에 대한 데이터가 어플리케이션 어디선가 업데이트가 된다면, 컴포넌트는 가장 최근에 업데이트 된 데이터로 자동적으로 re-render될 것 입니다.
  • useLazyLoadQuery는 추가적으로 Flow type parameter를 받습니다. 해당 parameter는 query에 대한 flow type과 부합하며, 예제의 경우 AppQuery입니다.
    • Relay가 선언된 쿼리들에 대한 Flow type을 자동으로 생성한다는 것을 명심하세요. 자동 생성된 타입들은 import해서 useLazyLoadQuery에서 사용할 수 있습니다. 해당 타입들은 <query_name>.graphql.js 형식으로된 name format에 따라서 생성된 파일을 사용할 수 있습니다.
    • 넘겨진 variable들은 Graphql query가 예상하는 타입과 부합하는지 Flow type 체크가 될 것 입니다.
    • 명시적인 인용구 없이도 데이터는 이미 적절하게 Flow-type이 되었다는 것에 주의하세요. 그리고 해당 타입은 GraphQL 스키마를 기반으로 타입이 생성됩니다. 예를 들면, 위의 data{ user: ?{ name: ?string } }의 타입을 가지게 될 것 입니다.
  • 기본적으로, 컴포넌트가 렌더링 될 때, Relay는 query에 대한 데이터를 가져올 것 입니다(캐싱되지 않았다면요). 그리고 useLazyLoadQuery를 호출함으로써 결과값을 가져올 것 입니다. 우리는 Loading States with Suspense에서 더 자세한 내용을 살펴볼 것 이며, 그리고 어떻게 Relay가 데이터를 캐싱하는지 Reusing Cached Data For Rendering문서에서 살펴볼 것 입니다.
  • 만약 당신의 컴포넌트를 re-render하면서 다른 query variable들을 query에 넣었다면, 새로운 변수들에 대한 새로운 데이터를 가져올 것 이며, 이는 잠재적으로 다른 데이터로 re-render될 것 입니다.
  • 마지막으로, 꼭 query를 렌더링 하기 전에 Relay Environment Provider를 이용해서 Relay environment를 제공하세요.