Gitsunmin

TIL

TIL
(=Today I Learned)

Relay

Relay는 GraphQL로 데이터를 Query하고, Mutation하는 데 사용되는 React 전용 라이브러리입니다. 기본적으로 뛰어난 성능을 제공하며, 스키마 기반으로 GraphQL 코드를 안정적으로 관리하기 위해 사용됩니다.

Relay의 특징

  • Declarative data: 각 컴포넌트에 필요한 데이터를 선언하기만 하면, Relay가 로딩 상태를 처리합니다.

  • Co-location and composability: 각 컴포넌트는 자체적으로 필요한 데이터를 선언하고, Relay는 이를 효율적인 쿼리로 결합합니다. 다른 화면에서 컴포넌트를 재사용하면 쿼리가 자동으로 업데이트됩니다.

    • Co-location: Co-location은 프로그래밍에서 데이터와 이를 사용하는 코드를 같은 위치에 두는 것을 의미합니다. 이는 코드의 가독성을 높이고, 관련 로직을 더 쉽게 추적할 수 있게 해줍니다.
  • Pre-fetching: Relay는 코드를 분석하여 코드를 다운로드하거나 실행하기 전에 쿼리를 가져올 수 있습니다.

  • UI patterns: Relay는 로딩 상태, 페이지네이션, refetching, 낙관적 업데이트, 롤백 및 기타 일반적인 UI 동작을 구현합니다.

    • 낙관적 업데이트(optimistic updates): 서버에 변경 사항을 보내기 전에 먼저 UI를 업데이트하여 사용자에게 즉각적인 반응을 제공하는 방식을 의미합니다.
  • Consistent updates: Relay는 정규화된 데이터 저장소를 유지하므로 동일한 데이터를 관찰하는 컴포넌트는 다른 쿼리로 도달하더라도 동기화됩니다.

  • Streaming and deferred data: 쿼리의 일부를 선언적으로 지연시키고, Relay는 데이터가 스트리밍됨에 따라 UI를 점진적으로 다시 렌더링합니다.

  • Great developer experience: Relay는 GraphQL 스키마에 대한 자동 완성 및 정의로 이동 기능을 제공합니다.

  • Type safety: Relay는 오류를 런타임이 아닌 정적으로 잡아내도록 타입 정의를 생성합니다.

  • Manage local data: 서버 데이터와 로컬 클라이언트 상태에 동일한 API를 사용합니다.

  • Hyper-optimized runtime: Relay는 끊임없이 최적화됩니다. JIT 친화적인 런타임은 어떤 페이로드를 기대할지 정적으로 결정하여 들어오는 데이터를 더 빠르게 처리합니다.

    • JIT (Just In Time): 프로그램이 실행되는 도중에 필요한 부분만을 즉석에서 컴파일하는 방식을 의미합니다. 프로그램이 실행되는 도중에 필요한 부분만을 즉석에서 컴파일하는 방식을 의미합니다.
    • AOT (Ahead Of Time): 프로그램이 실행되기 전에 필요한 부분을 미리 컴파일하는 방식을 의미합니다.

Relay Compiler

Relay Compiler는 GraphQL 쿼리를 JavaScript 코드로 변환합니다. 또한 한 페이지에서 독립적으로 사용중인 Query가 있을 경우에 이를 하나의 쿼리로 결합합니다.

직접 실행해볼수도 있다. 여기

Fragment

Relay는 GraphQL의 Fragment를 사용하여 컴포넌트에 필요한 데이터를 선언합니다. Fragment는 GraphQL의 Query와 유사하지만, 이름이 있고, 재사용 가능하며, 컴포넌트에 필요한 데이터만을 포함합니다.

Fragment 사용법

  1. 정의하기
import { graphql } from 'relay-runtime';

const ImageFragment = graphql`
  fragment ImageFragment on Image {
    url
  }
`;
  1. 사용하기
const StoryFragment = graphql`
  fragment StoryFragment on Story {
    title
    summary
    postedAt
    poster {
      ...PosterBylineFragment
    }
    thumbnail {
      ...ImageFragment
    }
  }
`;
  1. useFragment로 사용하기
import { useFragment } from 'react-relay';
import type { ImageFragment$key } from "./__generated__/ImageFragment.graphql";

type Props = {
  image: ImageFragment$key;
  ...
};

function Image({image}: Props) {
  const data = useFragment(ImageFragment, image);
  return <img key={data.url} src={data.url} ... />
}

Glossary

Directive

릴레이 컴파일러나 서버에 특수 명령을 제공하는 graphql 리터럴 또는 graphql 파일에 포함되어 있습니다.

@arguments

fragment spread에 인자를 전달할 때 사용합니다. 인자는 fragment 정의에서 @argument로 선언되어야 합니다.

const StoryFragment = graphql`
  fragment StoryFragment on Story {
    title
    summary
    postedAt
    poster {
      ...PosterBylineFragment @arguments(line: "22")
    }
    thumbnail {
      ...ImageFragment
    }
  }
`;

@argumentDefinitions

fragment 정의를 수정하고 프래그먼트가 사용할 수 있는 로컬 인수의 이름과 타입을 정의하는 지시문입니다.

const StoryFragment = graphql`
  fragment StoryFragment on Story @argumentDefinitions(line: {type: "Int"}) {
    title
    summary
    postedAt
    poster {
      ...PosterBylineFragment @arguments(line: $line)
    }
    thumbnail {
      ...ImageFragment
    }
  }
`;

@connection

이 @connection은 커서 기반의 페이지네이션을 구현하는 데 사용되며, Relay의 쿼리에서 페이징을 관리하기 위한 메커니즘을 제공합니다.

graphql`
  query ExampleQuery {
    user(id: "123") {
      friends(first: 10, after: $cursor) @connection(key: "ExampleQuery_friends") {
        edges {
          node {
            id
            name
          }
        }
      }
    }
  }
`

이 예시에서는 user 오브젝트의 friends 필드에 대해 페이징 처리를 하기 위해 @connection 지시어를 사용합니다. key 인자는 “ExampleQuery_friends”로 설정되어 Relay가 이 커넥션을 식별할 수 있도록 합니다. first와 after 인자는 페이지네이션을 제어하는 데 사용됩니다.

@include

@include는 주어진 조건이 true일 때 쿼리의 특정 부분을 포함시키는 데 사용됩니다. 이는 동적인 쿼리 작성에 유용하며, 필요에 따라 서버로부터 받아오는 데이터를 조절할 수 있게 해줍니다.

graphql`
  query ExampleQuery($isDetailed: Boolean!, $skipUser: Boolean!) {
    user(id: "123") {
      id
      name
      ...UserDetails @include(if: $isDetailed)
    }
  }
`

@skip

@skip은 주어진 조건이 true일 때 쿼리의 특정 부분을 제외시키는 데 사용됩니다. 이를 통해 불필요한 데이터의 요청을 줄이고, 네트워크 트래픽과 서버 부하를 감소시킬 수 있습니다.

graphql`
  query ExampleQuery($isDetailed: Boolean!, $skipUser: Boolean!) {
    user(id: "123") {
      id
      name
      ...UserContacts @skip(if: $skipUser)
    }
  }
`

Store

here Relay는 GraphQL 데이터를 캐싱하는 저장소가 존재합니다. 이 저장소에 접근하는 방법에 대해서 알아보겠습니다.

RecordSourceSelectorProxy

RecordSourceSelectorProxy는 업데이터 함수가 인수로 받는 저장소 유형입니다. 다음은 RecordSourceSelectorProxy 인터페이스입니다:

interface RecordSourceSelectorProxy {
  create(dataID: string, typeName: string): RecordProxy;
  delete(dataID: string): void;
  get(dataID: string): ?RecordProxy;
  getRoot(): RecordProxy;
  getRootField(fieldName: string): ?RecordProxy;
  getPluralRootField(fieldName: string): ?Array<?RecordProxy>;
  invalidateStore(): void;
}

create(dataID: string, typeName: string): RecordProxy

GraphQL 스키마에 정의된 데이터ID와 유형 이름이 주어지면 스토어에 새 레코드를 생성합니다. 새로 생성된 레코드를 변경하는 인터페이스 역할을 하는 RecordProxy를 반환합니다.

const record = store.create(dataID, 'Todo');

delete(dataID: string): void

주어진 데이터ID를 가진 레코드를 스토어에서 삭제합니다.

store.delete(dataID);

get(dataID: string): ?RecordProxy

주어진 데이터ID를 가진 레코드를 반환합니다. 레코드가 존재하지 않으면 null을 반환합니다.

const record = store.get(dataID);

getRoot(): RecordProxy

스토어의 루트 레코드를 반환합니다. 루트 레코드는 스키마의 Query 타입에 해당합니다.

아래와 같이 정의되어 있을 떄,

viewer {
  id
}

아래와 같이 사용할 수 있습니다.

const root = store.getRoot();

getPluralRootField(fieldName: string)

GraphQL 문서에 정의된 대로 fieldName이 주어진 스토어에서 컬렉션을 나타내는 루트 필드를 검색합니다. 레코드 프록시 배열을 반환합니다.

nodes(first: 10) {
  # ...
}
const nodes = store.getPluralRootField('nodes');

invalidateStore(): void

릴레이 스토어를 전역적으로 무효화합니다. 이렇게 하면 무효화가 발생하기 전에 스토어에 기록된 모든 데이터가 부실 데이터로 간주되어 다음에 environment.check()로 쿼리를 검사할 때 다시 가져와야 하는 것으로 간주됩니다.

store.invalidateStore();

environment.check(query) === 'stale'

1