GraphQL 실행 결과를 쉽게 입력하는 방법 단계별 가이드

최근 TypeScript와 GraphQL의 혼합은 현대 웹 개발의 실제 표준이 되고 있다.그러나 그것을 어떻게 쉽게 조합할 수 있는지에 대한 정보는 많지 않다.
한 마디로 하면 Fragment 첫 번째 방법은 TypeScript를 쉽게 처리할 수 있다는 것을 알게 되었다.왜?그것은 유형 정의의 중용성을 가속화시켰다.그것이 어떻게 일을 하는지 우리에게 보여 주시오.
[편집]
나는 현실 세계의 전단 프로젝트(프리랜서로서)를 실현하려고 시도할 때Colocating Fragment가GraphQL+TypeScript에 가장 적합하다는 것을 깨달았다.이런 모델은 좀 지루하지만 성명성을 가지고 있어 확장하기 쉽다.그러므로 본문을 읽는 것보다 자세히 읽는 것이 낫다. https://www.apollographql.com/docs/react/data/fragments/#colocating-fragments
[/편집]

1단계 - 유형 없음
the example from react-apollo에서 아래 코드를 볼 수 있습니다.
import { useQuery, gql } from "@apollo/client"

const EXCHANGE_RATES = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      currency
      rate
    }
  }
`

function ExchangeRates() {
  const { loading, error, data } = useQuery(EXCHANGE_RATES)

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error :(</p>

  return data.rates.map(({ currency, rate }) => (
    <div key={currency}>
      <p>
        {currency}: {rate}
      </p>
    </div>
  ))
}
그것은 보기에는 괜찮지만 data의 유형을 상상해 보세요.네, 네.그것은 유형의 안전을 파괴했다. 너는 미칠 것이다.

Note: technically not any but Record, although they are almost same in this context



2단계 - 수동 입력anydata로 바뀌는 것을 피하기 위해서 우리는 TypeScript의 일반 기능을 사용하여 조회 결과를 입력할 수 있다.
import { useQuery, gql } from "@apollo/client"

interface GetExchangeRates {
  rates: {
    currency: string
    rate: number
  }[]
}

const EXCHANGE_RATES = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      currency
      rate
    }
  }
`

function ExchangeRates() {
  const { loading, error, data } = useQuery<GetExchangeRates>(EXCHANGE_RATES)

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error :(</p>

  // Type signature of `data` is:
  // {
  //   rates: {
  //     currency: string
  //     rate: number
  //   }[]
  // }

  return data.rates.map(({ currency, rate }) => (
    <div key={currency}>
      <p>
        {currency}: {rate}
      </p>
    </div>
  ))
}
보시다시피 이것은 너무 고통스럽다!질의를 업데이트할 때마다 인터페이스를 수동으로 업데이트해야 합니다.

3단계 - codegen 을 입력합니다.
다행히도, GraphQL 조회에서TypeScript의 형식 정의를 생성할 수 있습니다.
https://github.com/apollographql/apollo-tooling#apollo-clientcodegen-output

Note: there are some tools other than apollo, but I prefer apollo because it is the most minimal one.


형식 정의를 만들기 위해 명령을 실행합시다.
npx apollo client:codegen \
  --localSchemaFile schema.gql \
  --target typescript \
  --includes 'src/**/*.{ts,tsx}'

Note: If you run the above command it will fail, because you won't have schema.gql in local.


네가 있는지 확인해라 any.GraphQL 서버는 GraphQL 모드를 파일로 보내는 기능을 갖추어야 합니다.
명령이 실행되면 다음 코드가 포함된 출력 파일이 표시됩니다.
// __generated__/GetExchangeRates.ts

export interface GetExchangeRates_rate {
  currency: string
  rate: number
}

export interface GetExchangeRates {
  rates: GetExchangeRates_rate[]
}
그래서 우리는 생성된 형식으로 마지막 코드를 바꿀 수 있다.
import { useQuery, gql } from "@apollo/client"
import { GetExchangeRates } from "./__generated__/GetExchangeRates"

const EXCHANGE_RATES = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      currency
      rate
    }
  }
`

function ExchangeRates() {
  const { loading, error, data } = useQuery<GetExchangeRates>(EXCHANGE_RATES)

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error :(</p>

  return data.rates.map(({ currency, rate }) => (
    <div key={currency}>
      <p>
        {currency}: {rate}
      </p>
    </div>
  ))
}
이것은 훨씬 쉽다!
단점은 GraphQL 코드를 편집할 때마다 명령을 실행해서 형식 정의를 만들어야 하는데, 이것은 수동으로 입력하는 것보다 훨씬 쉽다는 것이다.
나는 이것이 비교적 작은 프로젝트에 있어서 이미 충분하다고 생각한다.그러나 프로젝트가 계속 늘어나면 유형의 중용성에 문제가 생길 수 있다.

단계 4 - 유형 정의 재사용apollo-tooling 덕분에 우리는 유형 정의를 생성할 수 있다.그러나 어떻게 이런 유형의 정의를 다시 사용합니까?
상상해 보세요. 우리는 이렇게 우리의 구성 요소를 분리하고 싶습니다.
// ExchangeRates.tsx

import { useQuery, gql } from "@apollo/client"
import { GetExchangeRates } from "./__generated__/GetExchangeRates"
import { ExchangeRateItem } from "./ExchangeRateItem"

const EXCHANGE_RATES = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      currency
      rate
    }
  }
`

function ExchangeRates() {
  const { loading, error, data } = useQuery<GetExchangeRates>(EXCHANGE_RATES)

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error :(</p>

  return data.rates.map((rate) => (
    <ExchangeRateItem rate={rate} key={rate.currency} />
  ))
}
// ExchangeRateItem.tsx

import { GetExchangeRates_rate } from "./__generated__/GetExchangeRates"

interface ExchangeRateItemProps {
  rate: GetExchangeRates_rate
}

export function ExchangeRateItem({ rate }: ExchangeRateItemProps) {
  const { currency, rate } = rate
  return (
    <div>
      <p>
        {currency}: {rate}
      </p>
    </div>
  )
}
보시다시피 GraphQL 형식 정의는 생성된 코드에서 가져올 수 있습니다.그러나 그것은 혼란스러워야 한다. 왜냐하면:
  • 서브어셈블리는 모 어셈블리 질의에 의존합니다.
  • 는 두 개의 특정한 조회가 있기 때문에 중복 사용하기 어렵다schema.gql.
  • 의존류는 선형이 아니다.apollo -> ExchangeRateItem -> ExchangeRateItem -> __generated__
  • Note: Technically __generated__ does not depends on ExchangeRates, but conceptually it depends, as type definitions are generated from it.


    나는 아직 이 문제를 어떻게 처리하는지 완전히 이해하지 못했지만, 두 가지 해결 방안이 있는데, 사용 ExchangeRates 이다.

    단계 4.1 - 공용 쿼리 및 세션 작성
    첫 번째는 영역 분리에 기반한 것이다.GraphQL과 관련된 공용 파일을 만들고 구성 요소가 아닌 로직을 작성하는 것이 좋습니다.
    // graphql/Rate.ts
    
    import { useQuery, gql } from "@apollo/client"
    import {
      GetExchangeRates,
      GetExchangeRates_rate,
    } from "./__generated__/GetExchangeRates"
    
    // Re-export fragment type because of reusability
    export type { RateFragment } from "./ExchangeRateItem"
    
    const RATE_FRAGMENT = gql`
      fragment RateFragment on Rate {
        currency
        rate
        # ...And other props in the future
      }
    `
    
    const EXCHANGE_RATES = gql`
      query GetExchangeRates {
        rates(currency: "USD") {
          ...RateFragment
        }
      }
      ${RATE_FRAGMENT}
    `
    
    export const useRates = () => useQuery<GetExchangeRates>(EXCHANGE_RATES)
    
    // Other fragments, hooks, queries will follow
    
    // ExchangeRates.tsx
    
    import { useRates } from "./graphql/Rate"
    import { ExchangeRateItem } from "./ExchangeRateItem"
    
    function ExchangeRates() {
      const { loading, error, data } = useRates()
    
      if (loading) return <p>Loading...</p>
      if (error) return <p>Error :(</p>
    
      return data.rates.map((rate) => (
        <ExchangeRateItem rate={rate} key={rate.currency} />
      ))
    }
    
    // ExchangeRateItem.tsx
    
    import { RateFragment } from "./graphql/Rate"
    
    interface ExchangeRateItemProps {
      rate: RateFragment
    }
    
    export function ExchangeRateItem({ rate }: ExchangeRateItemProps) {
      const { currency, rate } = rate
      return (
        <div>
          <p>
            {currency}: {rate}
          </p>
        </div>
      )
    }
    
    GraphQL 코드를ExchangeRateItem로 이동한 후에 의존 관계는 선형으로 바뀌었다.
  • Fragment -> ./graphql/Rate -> ExchangeRates
  • graphql/Rate -> __generated__ -> ExchangeRates -> ExchangeRateItem
  • Fragment를 사용하면 GraphQL의 코드가 더 길고 지루해진다.그러나 관심과 분리되는 장점이 있다.
  • graphql/Rate 데이터를 얻는 방법을 알고 있습니다.
  • __generated__ 인터페이스를 공개했다.
  • graphql/Rategraphql/Rate는 데이터를 어떻게 얻는지 모른다.그것들은 실현에 의존하지 않고 데이터 원본과 유형의 인터페이스에 의존한다.
  • 우리 구성 요소의 코드가 더욱 작아져서, 이것은 전방 개발자에게도 매우 좋다.

    단계 4.2 - 총 세그먼트 배치
    또 다른 해결 방안은 '공통 포지셔닝 세션' 이라는 모델을 사용해서 중성자 구성 요소가 어떤 데이터를 필요로 하는지 설명하는 것이다.
    // ExchangeRates.tsx
    
    import { useQuery, gql } from "@apollo/client"
    import { ExchangeRateItem, RATE_ITEM_FRAGMENT } from "./ExchangeRateItem"
    
    const EXCHANGE_RATES = gql`
      query GetExchangeRates {
        rates(currency: "USD") {
          ...RateItemFragment
        }
      }
      ${RATE_ITEM_FRAGMENT}
    `
    
    function ExchangeRates() {
      const { loading, error, data } = useQuery<GetExchangeRates>(EXCHANGE_RATES)
    
      if (loading) return <p>Loading...</p>
      if (error) return <p>Error :(</p>
    
      return data.rates.map((rate) => (
        <ExchangeRateItem rate={rate} key={rate.currency} />
      ))
    }
    
    // ExchangeRateItem.tsx
    
    import { gql } from "@apollo/client"
    import { RateItemFragment } from "./__generated__/RateItemFragment"
    
    export const RATE_ITEM_FRAGMENT = gql`
      fragment RateItemFragment on Rate {
        currency
        rate
        # ...And other props in the future
      }
    `
    
    interface ExchangeRateItemProps {
      rate: RateItemFragment
    }
    
    export function ExchangeRateItem({ rate }: ExchangeRateItemProps) {
      const { currency, rate } = rate
      return (
        <div>
          <p>
            {currency}: {rate}
          </p>
        </div>
      )
    }
    
    이를 통해 다음과 같은 이점을 얻을 수 있습니다.
  • GraphQL 코드를 필요로 하는 구성 요소와 분리할 필요가 없습니다
  • 데이터 변경이 필요한 경우 필드를 수동으로 업데이트할 필요가 없음
  • 읽기 쉬운 코드
  • 자세한 내용은 https://www.apollographql.com/docs/react/data/fragments/#colocating-fragments를 참조하십시오.

    아폴로 이외의 Codegen 도구

    유형 문서 노드
    추천ExchangeRates.나는 이 라이브러리를 시도해 본 적이 없지만, 그는 가장 똑똑한GraphQL 개발자 중의 하나이기 때문에 네가 가서 보아야 한다!
    https://the-guild.dev/blog/typed-document-node

    @graphql-codegen/cli
    이것은 길드의 수석 기술관이 제작한 것으로 광범위하게 사용된다.나는 아직 내 프로젝트에서 시도해 본 적이 없지만, 그것은 거의 모든 주요 도구를 포함한다.
    https://github.com/dotansimha/graphql-code-generator

    결론
  • 사용 ExchangeRateItem 또는 기타 도구로 GraphQL 결과 입력
  • GraphQL과 관련된 코드를 디렉터리로 분리합니다
  • 사용TypedDocumentNode으로 유니버설 리셋 가능 유형 만들기
  • 만약 당신에게 어떤 생각이 있다면, 평론을 발표하세요.

    좋은 웹페이지 즐겨찾기