나는 오늘의 데이터가 2021 Summer를 얻었다고 생각한다🏄♂️【Next.js/Hasura】
REST API 사용, GraphiQL 사용, 클라이언트 캐시 사용, API 응답에서 어떻게 정형화되는지, 상태 관리를 어떻게 하는지 등 개발자들의 고민은 헤아릴 수 없지만 이 디자인에 대한 고려와 토론이 전방 개발의 즐거움이라고 생각합니다.
이 글의 후면은 하수라이고 앞면은 넥스트다.js를 사용할 때 가장 강한 도구의 조합과 사용 방법을 소개합니다.
모티프
as
하기 싫어요.전제 조건
전제를 재정리하다.
상태 관리
클라이언트의 상태 관리는 Recoil을 사용합니다.필자가 가장 익숙하기 때문이다.
Recoil을 사용하지 않아도 마찬가지지만 서버의 데이터 상태 관리는 ReactQuery에 의해 이루어지기 때문에 Recoil과 ReactQuery의 이중 관리가 되지 않도록 주의하십시오.
리콜은'진짜 고객 상태'라고 할 수 있는 값만 관리하려고 노력한다.예를 들어 UI의 어두운 테마 전환, 토스트 표시 상태 알림 등이다.서버의 상태와 무관한 "진정한 고객 상태"입니다.
인증
Auth0(auth0-react)을 사용합니다.하수라의 튜토리얼에서 사용했으니까요.
Type Script 유형에 Hasura의 반응을 원하는 대로 첨부
graphiql-codegen/cli와 플러그인을 조합해서 사용하면 GraphiQL 서버에서 모드를 가져와 TypeScript 형식 정의가 적힌 파일을 생성할 수 있습니다.
제 생각에는graphiql-codegen의 도구군이GraphiQL 개발을 사용하면 절대로 알아야 한다고 생각합니다. 그래서 아직 모르시는 분들은 아래 문서를 찾아보시는 것을 추천합니다.
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-operations
및 typescript-graphql-request
에 대해 설명합니다.응용 프로그램이 사용하는query와mutation을 설정합니다
codegen.js
의 document
에서 지정한 등급./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.js
generates
에서 지정한 ./src/generated/graphql.ts
에는 TypeScript 유형이 많이 적혀 있을 것이다.이렇게 토형 절차가 완성되었습니다.
생성된 형식을 사용하여 같은 인터페이스를 통해 클라이언트 서버에서 데이터를 가져옵니다
사실상 이때'고객 서버에서 인터페이스를 통해 데이터를 얻는 것'의 목적은 이미 달성되었다.
graphiql-codegen 플러그인
typescript-operations
과 typescript-graphql-request
에 따라 ./src/generated/graphql.ts
에 getSdk
라는 함수를 입력해야 한다.이것을 사용하여 클라이언트 실례를 생성합니다.
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 플러그인을 사용할 수 있습니다. 이렇게 하면 더욱 쉽습니다. (시도하지 않았습니다.)여기까지인 줄 알았는데 상술한 예가 아직 부족할 줄은 생각지도 못했다.
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.tsimport { 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 취득입니다.어때요?
후기
당신의 이의와 지적을 매우 환영합니다.기다리겠습니다.
여러분이 생각하는 최강의 데이터를 얻으라고 말씀해 주세요.
참고 자료
Reference
이 문제에 관하여(나는 오늘의 데이터가 2021 Summer를 얻었다고 생각한다🏄♂️【Next.js/Hasura】), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/eringiv3/articles/56f2b9f90a0632텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)