나는 오늘의 데이터가 2021 Summer를 얻었다고 생각한다🏄‍♂️【Next.js/Hasura】

전방 응용 프로그램을 개발할 때 피할 수 없는 데이터 취득을 설치했다.
REST API 사용, GraphiQL 사용, 클라이언트 캐시 사용, API 응답에서 어떻게 정형화되는지, 상태 관리를 어떻게 하는지 등 개발자들의 고민은 헤아릴 수 없지만 이 디자인에 대한 고려와 토론이 전방 개발의 즐거움이라고 생각합니다.
이 글의 후면은 하수라이고 앞면은 넥스트다.js를 사용할 때 가장 강한 도구의 조합과 사용 방법을 소개합니다.

모티프

  • API의 응답에 TypeScript 유형을 원하는 경우스타일리시as하기 싫어요.
  • 클라이언트든 서버(SSR)든 모두 같은 인터페이스에서 데이터 추출 방법을 제공하기를 원한다.
  • 불필요한 데이터 추출을 방지하기 위해 클라이언트 캐시 API 응답을 원합니다.
  • 전제 조건


    전제를 재정리하다.
  • 백엔드에서 Hasura의 GraphiQL 서버를 사용합니다.
  • 전면에 있습니다.js를 사용합니다.SSM/SSG가 필요한 경우를 가정합니다.
  • Type Script를 사용합니다.
  • 로그인 기능이 있는 응용을 가정한다.
  • 또한 이번 설명에 사용된 코드는 다음과 같은 도구를 사용합니다.모든 선정은 깊은 의미가 없고 이 문장의 본질과 큰 관계가 없다.

    상태 관리


    클라이언트의 상태 관리는 Recoil을 사용합니다.필자가 가장 익숙하기 때문이다.
    Recoil을 사용하지 않아도 마찬가지지만 서버의 데이터 상태 관리는 ReactQuery에 의해 이루어지기 때문에 Recoil과 ReactQuery의 이중 관리가 되지 않도록 주의하십시오.
    리콜은'진짜 고객 상태'라고 할 수 있는 값만 관리하려고 노력한다.예를 들어 UI의 어두운 테마 전환, 토스트 표시 상태 알림 등이다.서버의 상태와 무관한 "진정한 고객 상태"입니다.

    인증


    Auth0(auth0-react)을 사용합니다.하수라의 튜토리얼에서 사용했으니까요.

    Type Script 유형에 Hasura의 반응을 원하는 대로 첨부


    graphiql-codegen/cli와 플러그인을 조합해서 사용하면 GraphiQL 서버에서 모드를 가져와 TypeScript 형식 정의가 적힌 파일을 생성할 수 있습니다.
    제 생각에는graphiql-codegen의 도구군이GraphiQL 개발을 사용하면 절대로 알아야 한다고 생각합니다. 그래서 아직 모르시는 분들은 아래 문서를 찾아보시는 것을 추천합니다.
    https://www.graphql-code-generator.com/docs/getting-started/installation

    codegen 설정


    이번에는 다음 코드젠의 설정 파일을 준비했습니다.
    codegen.js
    const secrets = require('./secrets.json')
    
    module.exports = {
      schema: [
        {
          'https://xxxxx-xxx-xx.hasura.app/v1/graphql': {
            headers: {
              'x-hasura-admin-secret': secrets.HASURA_ADMIN_SECRET,
              'x-hasura-role': 'user',
            },
          },
        },
      ],
      documents: ['./src/graphql/**/*.graphql'],
      overwrite: true,
      generates: {
        './src/generated/graphql.ts': {
          plugins: [
            'typescript',
            'typescript-operations',
            'typescript-graphql-request',
          ],
          config: {
            skipTypename: false,
            withHooks: true,
            withHOC: false,
            withComponent: false,
          },
        },
        './graphql.schema.json': {
          plugins: ['introspection'],
        },
      },
    }
    
    점으로 모드를 가져올 때 사용하는 HTTP 요청 제목x-hasura-role에 응용 프로그램에 사용할 역할을 지정하시겠습니까?
    하수라는 팟캐스트 때 캐릭터에 따라 볼 수 있는 패턴이 다르다.
    패턴의 변화는 생성된 유형 정의도 캐릭터의 변화에 따라 달라지는 것이다.
    여기에 적합하지 않은 캐릭터를 지정하면 프로그램 사용자가 조작할 수 없는query와mutation이 생성되기 때문에 주의해야 합니다.적합한 배역을 지정하십시오.
    그리고 하수라ADMIN_SECRET는 다른 파일에 json으로 저장되어 원격 창고에 넣으면 좋지 않기 때문에gitignore에도 기록됩니다.
    나중에 플러그 인typescript-operationstypescript-graphql-request에 대해 설명합니다.

    응용 프로그램이 사용하는query와mutation을 설정합니다

    codegen.jsdocument에서 지정한 등급./src/graphql에서query와mutation이 적힌 파일이 발생합니다.
    getUserByName.graphql
    query GetUserByName($name: String!) {
      users(where: { name: { _eq: $name } }) {
        name
      }
    }
    
    실제 개발을 할 때는 브라우저에서 하수라의 콘솔을 열고 그래픽QL에서 응답을 확인하는 동시에 작업을 해야 한다.
    하수라 콘솔의GraphiQL에서 UI에 필요한 속성을 선택하면query는 임의로 기술되기 때문에 복사./src/graphql는 아래에서 1개 조회 단위로 파일을 만드는 것이 좋다.
    여기까지. 패키지.json의scripts에서codegen의 스크립트를 미리 설정하십시오.
    "generate": "graphql-codegen --config codegen.js"
    
    yarn generate
    
    에서 실행하고 형식 정의 파일을 생성합니다!codegen.jsgenerates에서 지정한 ./src/generated/graphql.ts에는 TypeScript 유형이 많이 적혀 있을 것이다.

    이렇게 토형 절차가 완성되었습니다.

    생성된 형식을 사용하여 같은 인터페이스를 통해 클라이언트 서버에서 데이터를 가져옵니다


    사실상 이때'고객 서버에서 인터페이스를 통해 데이터를 얻는 것'의 목적은 이미 달성되었다.
    graphiql-codegen 플러그인typescript-operationstypescript-graphql-request에 따라 ./src/generated/graphql.tsgetSdk라는 함수를 입력해야 한다.
    이것을 사용하여 클라이언트 실례를 생성합니다.
    src/lib/hasuraClient.ts
    import { GraphQLClient } from 'graphql-request'
    import { HASURA_GRAPHQL_END_POINT } from '../config/constants'
    import { getSdk } from '../generated/graphql'
    
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    export const createHasuraClient = (token: string | null) => {
      const headers =
        token !== null
          ? {
              authorization: `Bearer ${token}`,
            }
          : undefined
      const client = new GraphQLClient(HASURA_GRAPHQL_END_POINT, {
        headers,
      })
      return getSdk(client)
    }
    
    export type HasuraClient = ReturnType<typeof createHasuraClient>
    
    이런 느낌으로 데이터를 얻을 수 있다.
    const hasuraClient = createHasuraClient(null)
    hasuraClient.GetUserByName({ name: 'eringiv3' }).then((data) => {
      // dataにはGetUserByNameQueryという型がついている
      console.log({ data })
    })
    
    Hasura 클라이언트 실례는 클라이언트 서버에 적용되었고 클라이언트에서만 사용하는 hook 등에 의존하지 않습니다.
    애초 제기된 동기 중 해결할 수 없는 과제는'클라이언트 캐시 API 응답으로 불필요한 데이터 취득을 막으려는 것'이었다.오직

    클라이언트 캐시 API 응답을 통해 불필요한 데이터 취득 방지


    ReactQuery 를 사용합니다.아마도 SWR과 Apollo Client도 대용할 수 있을 것이다.
    ReactQuery에 대한 자세한 설명은 없지만 대체적으로 클라이언트에서 API 요청의 응답을 캐시하고 캐시가 유효하면 요청을 하지 않고 캐시를 반환합니다.
    캐시 열쇠를 고려할 필요가 있다.
    캐시 키는 실행할 GraphiQL 조회, 매개 변수 variables, 인증 상태마다 유일한 값입니다.
    인증 상태가 필요한 이유는 앞서 설명한 Hasura의 역할에 따라 실행 가능한 질의가 변경되기 때문입니다.
    쿼리, 파라미터를 생성하는variables와 파라미터의 유일한 캐시 키를 만드는 도구 함수를 준비합니다.
    src/utils/stringHelpers.ts
    import hash from 'hash.js'
    
    // eslint-disable-next-line @typescript-eslint/ban-types
    export const getQueryKey = (fn: Function, variables: object): string => {
      return `${fn.name}::${sha256(JSON.stringify(variables))}`
    }
    
    export const sha256 = (str: string): string => {
      return hash.sha256().update(str).digest('hex')
    }
    
    이런 느낌의 현금 키로 할 수 있어요.요청한 캐시 키를 누르면 ReactQuery의 devol에서 열람할 수 있습니다.

    이것을 사용하면 고객으로부터 아래의 데이터를 얻을 수 있습니다!
    src/pages/index.tsx
    import { useAuth0 } from '@auth0/auth0-react'
    import { useQuery } from 'react-query'
    
    const SomeComponent: React.FC = () => {
        const { isAuthenticated } = useAuth0()
        const hasuraClient = createHasuraClient(null)
      const variables = { name: 'eringiv3' }
      const { data, error, isLoading } = useQuery(
        getQueryKey(hasuraClient.GetUserByName, {
          variables,
          isAuthenticated,
        }),
        () => hasuraClient.GetUserByName(variables)
      )
      
      if (isLoading) return <div>Loading...</div>
      
      return <div>{data.users[0].name}</div>
    }
    export default SomeComponent
    
    데이터 추출에 사용되는 줄 수가 아직 10줄에 이르지 않았다.
    그리고 ReactQuery 덕분에 응답할 수 있는 고속 캐시, 선언적인 데이터를 얻었다.
    이번 서버 클라이언트는 같은 인터페이스에서 조회를 실행하고자 하는 동력이 생겨서 이런 방법을 채택하였다서버에서 데이터 추출typescript-react-query이 진행되지 않으면graphiql-codegen 플러그인을 사용할 수 있습니다. 이렇게 하면 더욱 쉽습니다. (시도하지 않았습니다.)
    https://www.graphql-code-generator.com/docs/plugins/typescript-react-query
    여기까지인 줄 알았는데 상술한 예가 아직 부족할 줄은 생각지도 못했다.
    Hasura의 역할을 사용하려면 로그인 상태에서 요청 페이지의 헤더에 영패를 지정해야 합니다.
    모든 구성 요소는 다음과 같은 느낌을 가지고 있으며, 매번 단독 눈썹을 지정하는 것은 좀 번거롭다.
      const { isAuthenticated, getAccessTokenSilently } = useAuth0()
      const { data, error, isLoading } = useQuery(
        getQueryKey(hasuraClient.GetUserByName, {
          variables,
          isAuthenticated,
        }),
        () =>
          isAuthenticated
            ? getAccessTokenSilently().then((token) =>
                hasuraClient.GetUserByName(variables, {
                  authorization: `Bearer ${token}`,
                })
              )
            : hasuraClient.GetUserByName(variables)
    
    첫 번째 함수createHasuraClient를 사용하여 클라이언트 실례를 만들 때 기본 머리글을 지정할 수 있습니다.
    이 옵션을 사용하면 인증 상태에 따라hasuraClient 처리가 바뀝니다.
    src/pages/_app.tsx
    import { Auth0Provider, useAuth0 } from '@auth0/auth0-react'
    import type { AppProps } from 'next/app'
    import { useEffect } from 'react'
    import { QueryClient, QueryClientProvider } from 'react-query'
    import { ReactQueryDevtools } from 'react-query/devtools'
    import { RecoilRoot, useSetRecoilState } from 'recoil'
    import {
      AUTH0_API_AUDIENCE,
      AUTH0_CLIENT_ID,
      AUTH0_DOMAIN,
      AUTH0_REDIRECT_URI,
    } from '../config/constants'
    import { createHasuraClient } from '../lib/hasuraClient'
    import { hasuraClientState } from '../states/hasuraClient'
    const queryClient = new QueryClient()
    
    const AppInit = () => {
      const { isAuthenticated, getAccessTokenSilently } = useAuth0()
      const setHasuraClient = useSetRecoilState(hasuraClientState)
    
      useEffect(() => {
        if (isAuthenticated) {
          getAccessTokenSilently().then((token) => {
            const client = createHasuraClient(token)
            setHasuraClient(client)
          })
        } else {
          const client = createHasuraClient(null)
          setHasuraClient(client)
        }
      }, [isAuthenticated])
    
      return null
    }
    
    const App: React.FC<AppProps> = ({ Component, pageProps }) => {
      return (
        <Auth0Provider
          domain={AUTH0_DOMAIN}
          clientId={AUTH0_CLIENT_ID}
          redirectUri={AUTH0_REDIRECT_URI}
          audience={AUTH0_API_AUDIENCE}
        >
          <QueryClientProvider client={queryClient}>
            <RecoilRoot>
              <AppInit />
              <Component {...pageProps} />
              <ReactQueryDevtools initialIsOpen={false} />
            </RecoilRoot>
          </QueryClientProvider>
        </Auth0Provider>
      )
    }
    
    export default App
    
    src/states/hasuraClient.ts
    import { atom } from 'recoil'
    import type { HasuraClient } from '../lib/hasuraClient'
    import { createHasuraClient } from '../lib/hasuraClient'
    
    export const hasuraClientState = atom<HasuraClient | undefined>({
      key: 'hasuraClient',
      default: createHasuraClient(null),
    })
    
    이렇게 하면 모든 구성 요소를 써서 단독으로 지폐 처리를 받을 필요가 없다.
    최종적으로 클라이언트 서버에서 다음과 같은 느낌으로 데이터를 추출할 수 있다.
    물론 모두 스타일리시한 상태.
    서버
    import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'
    import { GetUserByNameQuery } from '../../generated/graphql'
    import { createHasuraClient } from '../../lib/hasuraClient'
    
    const MyPage: React.VFC<
      InferGetServerSidePropsType<typeof getServerSideProps>
    > = ({ data }) => {
      const user = data.users[0]
      return <div>{user.name}</div>
    }
    
    export default MyPage
    
    type ServerSideProps = {
      data: GetUserByNameQuery
    }
    export const getServerSideProps: GetServerSideProps<ServerSideProps> = async () => {
      const hasuraClient = createHasuraClient(null)
      const data = await hasuraClient.GetUserByName({ name: 'eringiv3' })
      if (data.users.length === 0) {
        return {
          notFound: true,
        }
      }
      return {
        props: {
          data,
        },
      }
    }
    
    클라이언트
    import { useAuth0 } from '@auth0/auth0-react'
    import type { NextPage } from 'next'
    import { useQuery } from 'react-query'
    import { useRecoilValue } from 'recoil'
    import LoginButton from '../components/auth/LoginButton'
    import LogoutButton from '../components/auth/LogoutButton'
    import { hasuraClientState } from '../states/hasuraClient'
    import { getQueryKey } from '../utils/stringHelpers'
    
    const IndexPage: NextPage = () => {
      const hasuraClient = useRecoilValue(hasuraClientState)
      const { user, isAuthenticated } = useAuth0()
      const variables = { name: 'eringiv3' }
    
      const { data, isLoading } = useQuery(
        getQueryKey(hasuraClient.GetUserByName, {
          variables,
          isAuthenticated,
        }),
        () => hasuraClient.GetUserByName(variables)
      )
    
      if (isLoading) return <div>Loading...</div>
    
      return isAuthenticated ? (
        <div>
          <div>
            <img src={user.picture} alt={user.name} />
            <h2>{data.users[0].name}</h2>
          </div>
          <div>
            <LogoutButton />
          </div>
        </div>
      ) : (
        <LoginButton />
      )
    }
    
    export default IndexPage
    
    이상은 제가 생각하는 최강의 데이터인 2021 Summer 취득입니다.
    어때요?

    후기


    당신의 이의와 지적을 매우 환영합니다.기다리겠습니다.
    여러분이 생각하는 최강의 데이터를 얻으라고 말씀해 주세요.

    참고 자료


    https://techlife.cookpad.com/entry/2021/03/24/123214

    좋은 웹페이지 즐겨찾기