graphql-hooks
🎣 Thư viện client GraphQL dựa trên hooks với độ phức tạp tối thiểu.
Tính năng
- 🥇 API hooks ưu việt
- ⚖️ Gói nhẹ: chỉ 7.6kB (nén gzipped 2.8)
- 📄 Hỗ trợ SSR đầy đủ: xem graphql-hooks-ssr
- 🔌 Plugin Caching: xem graphql-hooks-memcache
- 🔥 Không còn bị rối trong render props
- ⏳ Xử lý trạng thái loading và lỗi dễ dàng
Cài đặt
npm install graphql-hooks
hoặc
yarn add graphql-hooks
Hỗ trợ
- Node LTS
- Trình duyệt > 1%, not dead
Xem xét việc polyfilling:
- FormData
- Promise
- fetch. LƯU Ý: Bạn cũng có thể cung cấp một cài đặt tùy chỉnh thay vì polyfilling, xem
GraphQLClient
Bắt đầu Nhanh
Đầu tiên, bạn cần tạo một client và bọc ứng dụng của bạn bằng provider:
import { GraphQLClient, ClientContext } from 'graphql-hooks'
const client = new GraphQLClient({
url: '/graphql'
})
function App() {
return (
<ClientContext.Provider value={client}>
{/* children */}
</ClientContext.Provider>
)
}
Bây giờ trong các thành phần con của bạn, bạn có thể sử dụng useQuery
import { useQuery } from 'graphql-hooks'
const HOMEPAGE_QUERY = `query HomePage($limit: Int) {
users(limit: $limit) {
id
name
}
}`
function MyComponent() {
const { loading, error, data } = useQuery(HOMEPAGE_QUERY, {
variables: {
limit: 10
}
})
if (loading) return 'Loading...'
if (error) return 'Something Bad Happened'
return (
<ul>
{data.users.map(({ id, name }) => (
<li key={id}>{name}</li>
))}
</ul>
)
}
Tại sao sử dụng graphql-hooks
?
Điều đầu tiên bạn có thể hỏi khi thấy graphql-hooks
là “Tại sao không sử dụng Apollo hooks?”. Đó là so sánh mà hầu hết mọi người sẽ đưa ra. Trên thực tế, có một article comparing the two trên LogRocket.
Chúng tôi tin rằng graphql-hooks
là lựa chọn tuyệt vời làm client GraphQL dựa trên hooks vì API ngắn gọn và kích thước gói nhỏ gọn của nó.
Về hiệu suất, điều này có phần mơ hồ hơn vì chúng ta chưa có bất kỳ bài đo chính thức nào.
Nếu bạn cần một client cung cấp nhiều tùy chỉnh hơn như cấu hình cache tiên tiến, thì apollo-hooks
có thể phù hợp với dự án của bạn nếu kích thước gói không phải là một vấn đề.
Ưu điểm | Nhược điểm |
---|---|
Nhỏ gọn về kích thước | Cấu hình cache ít “nâng cao” hơn |
API ngắn gọn | |
Nhanh chóng để bắt đầu và chạy |
API
GraphQLClient
Sử dụng :
import { GraphQLClient } from 'graphql-hooks'
const client = new GraphQLClient(config)
**config**
: Đối tượng chứa các thuộc tính cấu hình
url
: Địa chỉ URL của máy chủ GraphQL HTTP của bạn. Nếu không được chỉ định, bạn phải bậtfullWsTransport
và cung cấp mộtsubscriptionClient
hợp lệ; nếu không, nó là bắt buộc.fullWsTransport
: Boolean – đặt thànhtrue
nếu bạn muốn sử dụngsubscriptionClient
để gửi cả truy vấn và mutations qua WebSocket; mặc định làfalse
ssrMode
: Boolean – đặt thànhtrue
khi sử dụng trên máy chủ cho việc render trên phía máy chủ; mặc định làfalse
useGETForQueries
: Boolean – đặt thànhtrue
để sử dụng phương thức HTTP GET cho tất cả các truy vấn; mặc định là false. Xem Hỗ trợ HTTP Get để biết thêm thông tinsubscriptionClient
: Cấu hình client WebSocket. Chấp nhận một trong các phiên bản củaSubscriptionClient
từ subscriptions-transport-ws hoặcClient
từ graphql-ws. Cũng chấp nhận một hàm factory ví dụ để tránh tạo client trong môi trường SSR.cache
( Bắt buộc nếussrMode
làtrue
, nếu không là tùy chọn): Đối tượng với các phương thức sau:cache.get(key)
cache.set(key, data)
cache.delete(key)
cache.clear()
cache.keys()
getInitialState()
- Xem graphql-hooks-memcache như một ví dụ tham chiếu
fetch(url, options)
: Triển khai Fetch – mặc định là APIfetch
toàn cầu. Kiểm tra Request interceptors để biết thêm chi tiết về cách quản lýfetch
.FormData
: Triển khai FormData – mặc định là APIFormData
toàn cầu. Polyfill trong môi trường node.js. Xem file-uploads-nodejs để biết thêm thông tin.fetchOptions
: Xem MDN để biết thông tin về các tùy chọn có thể được truyềnheaders
: Đối tượng, ví dụ:{ 'My-Header': 'hello' }
logErrors
: Boolean – mặc định làtrue
middleware
: Chấp nhận một mảng các hàm middleware, mặc định: không có, xem thêm trong tài liệu về middlewaresonError({ operation, result })
: Bộ xử lý lỗi tùy chỉnhoperation
: Đối tượng vớiquery
,variables
vàoperationName
result
: Đối tượng chứadata
và đối tượngerror
chứafetchError
,httpError
vàgraphqlErrors
Các phương thức của client
client.setHeader(key, value)
: Cập nhậtclient.headers
bằng cách thêm header mới vào các header hiện cóclient.setHeaders(headers)
: Thay thếclient.headers
client.removeHeader(key)
: Cập nhậtclient.headers
bằng cách xóa header nếu nó tồn tạiclient.logErrorResult({ operation, result })
: Bộ ghi lỗi mặc định; hữu ích nếu bạn muốn sử dụng nó trong trình xử lýonError
tùy chỉnh của bạnrequest(operation, options)
: Tạo một yêu cầu đến máy chủ GraphQL của bạn; trả về một Promiseoperation
: Đối tượng vớiquery
,variables
vàoperationName
options.fetchOptionsOverrides
: Đối tượng chứa các tùy chọn fetch bổ sung để thêm vào các tùy chọn mặc định được truyền vàonew GraphQLClient(config)
options.responseReducer
: Hàm Reducer để chọn giá trị từ đối tượng Fetch Response ban đầu. Giá trị được hợp nhất vào phản hồirequest
dưới khóadata
. Ví dụ sử dụng:{responseReducer: (data, response) => ({...data, myKey: response.headers.get('content-length)})
client.invalidateQuery(query)
: Sẽ xóa bộ nhớ cache cũ, truy xuất dữ liệu mới bằng cùng một truy vấn và lưu trữ nó trong bộ nhớ cache như một giá trị mớiquery
: Truy vấn GraphQL dưới dạng chuỗi thô để truy xuất lại hoặc đối tượng Operation (vớiquery
,variables
vàoperationName
)client.setQueryData(query, (oldState) => [...oldState, newState]])
: Sẽ ghi đè trạng thái cache cũ bằng trạng thái mới được cung cấp bởi hàm trả vềquery
: Truy vấn GraphQL dưới dạng chuỗi thô hoặc đối tượng Operation (vớiquery
,variables
vàoperationName
)(oldState) => [...oldState, newState]]
: Hàm gọi lại với giá trị trả về sẽ là trạng thái mới được lưu trữ trong bộ nhớ cache.oldState
: Giá trị cũ được lưu trữ trong bộ nhớ cache
ClientContext
ClientContext
là kết quả của React.createContext()
– có nghĩa là nó có thể được sử dụng trực tiếp với API ngữ cảnh mới của React:
Ví dụ :
import { ClientContext } from 'graphql-hooks'
function App() {
return (
<ClientContext.Provider value={client}>
{/* children can now consume the client context */}
</ClientContext.Provider>
)
}
Để truy cập đối tượng GraphQLClient
, gọi React.useContext(ClientContext)
:
import React, { useContext } from 'react'
import { ClientContext } from 'graphql-hooks'
function MyComponent() {
const client = useContext(ClientContext)
}
useQuery
Sử dụng :
const state = useQuery(query, [options])
Ví dụ:
import { useQuery } from 'graphql-hooks'
function MyComponent() {
const { loading, error, data } = useQuery(query)
if (loading) return 'Loading...'
if (error) return 'Something bad happened'
return <div>{data.thing}</div>
}
Đây là một hook tùy chỉnh để thực hiện truy vấn của bạn và lưu kết quả vào bộ nhớ cache. Nó sẽ không thực hiện lại truy vấn trừ khi query
hoặc options.variables
thay đổi.
query
: Truy vấn GraphQL của bạn dưới dạng chuỗi thôoptions
: Đối tượng với các thuộc tính tùy chọn sau đâyvariables
: Đối tượng ví dụ:{ limit: 10 }
operationName
: Nếu truy vấn của bạn có nhiều thao tác, hãy chuyển tên của thao tác bạn muốn thực hiện.persisted
: Boolean – mặc định làfalse
; Chuyểntrue
nếu máy chủ GraphQL của bạn hỗ trợ cờpersisted
để phục vụ các truy vấn đã lưu.useCache
: Boolean – mặc định làtrue
; Lưu kết quả truy vấn vào cacheskip
: Boolean – mặc định làfalse
; Không thực hiện truy vấn nếu được đặt thànhtrue
skipCache
: Boolean – mặc định làfalse
; Nếutrue
, nó sẽ bỏ qua bộ nhớ cache và truy xuất, nhưng kết quả sau đó sẽ được lưu trữ cho các lời gọi sau. Lưu ý rằng hàmrefetch
sẽ thực hiện điều này tự độngssr
: Boolean – mặc định làtrue
. Đặt thànhfalse
nếu bạn muốn bỏ qua truy vấn này trong quá trình SSRfetchOptionsOverrides
: Đối tượng – Ghi đè cụ thể cho truy vấn này. Xem MDN để biết thông tin về các tùy chọn có thể được truyềnupdateData(previousData, data)
: Hàm – Bộ xử lý tùy chỉnh để hợp nhất kết quả truy vấn trước và sau; giá trị trả về sẽ thay thếdata
trong giá trị trả vềuseQuery
previousData
: Kết quả trước đó của truy vấn GraphQL hoặc kết quả từupdateData
data
: Kết quả mới của truy vấn GraphQLclient
: GraphQLClient – Nếu một GraphQLClient được truyền một cách rõ ràng như một tùy chọn, thì nó sẽ được sử dụng thay vì client từClientContext
.refetchAfterMutations
: Chuỗi | Đối tượng | (Chuỗi | Đối tượng)[] – Bạn có thể chỉ định khi nào một thay đổi (mutation) nên gây ra việc truy vấn lại.- Nếu nó là một chuỗi, đó là chuỗi thay đổi (mutation)
- Nếu nó là một đối tượng thì nó có các thuộc tính thay đổi (mutation) và bộ lọc (filter)
mutation
: Chuỗi – Chuỗi thay đổi (mutation)refetchOnMutationError
: boolean (tùy chọn, mặc định làtrue
) – Nó cho biết liệu truy vấn có cần phải được truy vấn lại nếu thay đổi (mutation) trả về lỗi hay khôngfilter
: Hàm (tùy chọn) – Nó nhận biến của thay đổi (mutation) làm tham số và chặn việc truy vấn lại nếu nó trả vềfalse
- Nếu nó là một mảng, các phần tử có thể thuộc loại trên
Giá trị trả về của useQuery
const { loading, error, data, refetch, cacheHit } = useQuery(QUERY)
loading
: Boolean –true
nếu truy vấn đang được thực hiệndata
: Đối tượng – kết quả của truy vấn GraphQL của bạnrefetch(options)
: Hàm – hữu ích khi bạn muốn truy vấn lại cùng một truy vấn sau một thay đổi (mutation); LƯU Ý điều này đặtskipCache=true
và sẽ bỏ qua hàmoptions.updateData
đã được truyền vàouseQuery
. Bạn có thể truyền một hàmupdateData
mới vàorefetch
nếu cần.options
: Đối tượng – các tùy chọn sẽ được hợp nhất vào các tùy chọn đã được truyền vàouseQuery
(xem phía trên).cacheHit
: Boolean –true
nếu kết quả truy vấn đến từ bộ nhớ cache, hữu ích cho mục đích gỡ lỗierror
: Đối tượng – Được đặt nếu ít nhất một trong các lỗi sau đã xảy ra và bao gồm:fetchError
: Đối tượng – Được đặt nếu có lỗi xảy ra trong cuộc gọifetch
httpError
: Đối tượng – Được đặt nếu có phản hồi lỗi từ máy chủgraphQLErrors
: Mảng – Được điền nếu có bất kỳ lỗi nào xảy ra trong quá trình giải quyết truy vấn
useManualQuery
Sử dụng điều này khi bạn không muốn truy vấn tự động được truy xuất hoặc muốn gọi một truy vấn theo cách lập trình.
Sử dụng :
const [queryFn, state] = useManualQuery(query, [options])
Ví dụ :
import { useManualQuery } from 'graphql-hooks'
function MyComponent(props) {
const [fetchUser, { loading, error, data }] = useManualQuery(GET_USER_QUERY, {
variables: { id: props.userId }
})
return (
<div>
<button onClick={fetchUser}>Get User!</button>
{error && <div>Failed to fetch user<div>}
{loading && <div>Loading...</div>}
{data && <div>Hello ${data.user.name}</div>}
</div>
)
}
Nếu bạn không biết các tùy chọn cụ thể khi khai báo useManualQuery
, bạn cũng có thể truyền các tùy chọn giống nhau vào chính hàm truy vấn khi gọi nó:
import { useManualQuery } from 'graphql-hooks'
function MyComponent(props) {
const [fetchUser] = useManualQuery(GET_USER_QUERY)
const fetchUserThenSomething = async () => {
const user = await fetchUser({
variables: { id: props.userId }
})
return somethingElse()
}
return (
<div>
<button onClick={fetchUserThenSomething}>Get User!</button>
</div>
)
}
useQueryClient
Sẽ trả về đối tượng graphql client được cung cấp cho ClientContext.Provider
dưới dạng value
Sử dụng :
const client = useQueryClient()
Ví dụ :
import { useQueryClient } from 'graphql-hooks'
function MyComponent() {
const client = useQueryClient()
return <div>...</div>
}
useMutation
Khác với Truy vấn (Queries), Thay đổi (Mutations) không được lưu vào cache.
Sử dụng :
const [mutationFn, state, resetFn] = useMutation(mutation, [options])
Ví dụ :
import { useMutation } from 'graphql-hooks'
const UPDATE_USER_MUTATION = `mutation UpdateUser(id: String!, name: String!) {
updateUser(id: $id, name: $name) {
name
}
}`
function MyComponent({ id, name }) {
const [updateUser] = useMutation(UPDATE_USER_MUTATION)
const [newName, setNewName] = useState(name)
return (
<div>
<input
type="text"
value={newName}
onChange={e => setNewName(e.target.value)}
/>
<button
onClick={() => updateUser({ variables: { id, name: newName } })}
/>
</div>
)
}
Đối tượng options
có thể được truyền vào useMutation(mutation, options)
hoặc mutationFn(options)
và có các thuộc tính sau:
variables
: Đối tượng ví dụ:{ limit: 10 }
operationName
: Nếu truy vấn của bạn có nhiều thao tác, hãy chuyển tên của thao tác bạn muốn thực hiện.fetchOptionsOverrides
: Đối tượng – Ghi đè cụ thể cho truy vấn này. Xem MDN để biết thông tin về các tùy chọn có thể được truyềnclient
: GraphQLClient – Nếu một GraphQLClient được truyền một cách rõ ràng như một tùy chọn, thì nó sẽ được sử dụng thay vì client từClientContext
.onSuccess
: Một hàm sẽ được gọi sau khi thay đổi (mutation) hoàn thành thành công mà không gây ra lỗi nào
Ngoài ra, có một tùy chọn để đặt lại trạng thái hiện tại trước khi gọi thay đổi (mutation) lại bằng cách gọi resetFn(desiredState)
trong đó desiredState
là tùy chọn và nếu truyền vào, nó sẽ ghi đè trạng thái ban đầu với:
data
: Đối tượng – dữ liệuerror
: Lỗi – lỗiloading
: Boolean – true nếu vẫn đang tảicacheHit
: Boolean – true nếu kết quả đã được lưu vào bộ nhớ cache
useSubscription
Để sử dụng đăng ký (subscription), bạn có thể sử dụng cả subscriptions-transport-ws hoặc graphql-ws
API
useSubscription(operation, callback)
operation
: Đối tượng – Thao tác GraphQL với các thuộc tính sau:query
: Chuỗi (bắt buộc) – truy vấn GraphQLvariables
: Đối tượng (tùy chọn) – Bất kỳ biến nào mà truy vấn có thể cầnoperationName
: Chuỗi (tùy chọn) – Nếu truy vấn của bạn có nhiều thao tác, bạn có thể chọn thao tác nào bạn muốn gọi.client
: GraphQLClient – Nếu một GraphQLClient được truyền một cách rõ ràng như một tùy chọn, thì nó sẽ được sử dụng thay vì client từClientContext
.callback
: Hàm – Hàm này sẽ được gọi khi đăng ký nhận một sự kiện từ máy chủ GraphQL của bạn – nó sẽ nhận đối tượng với phản hồi GraphQL điển hình như{ data: <kết quả của bạn>, errors?: [Error] }
Sử dụng :
Đầu tiên, làm theo hướng dẫn nhanh để tạo client và provider. Sau đó, chúng ta cần cập nhật cấu hình cho GraphQLClient
của chúng tôi bằng cách truyền vào subscriptionClient
:
import { GraphQLClient } from 'graphql-hooks'
import { SubscriptionClient } from 'subscriptions-transport-ws'
// or
import { createClient } from 'graphql-ws'
const client = new GraphQLClient({
url: 'http://localhost:8000/graphql',
subscriptionClient: () =>
new SubscriptionClient('ws://localhost:8000/graphql', {
/* additional config options */
}),
// or
subscriptionClient: () =>
createClient({
url: 'ws://localhost:8000/graphql'
/* additional config options */
})
})
Tiếp theo, trong ứng dụng React của chúng ta, chúng ta có thể sử dụng hook useSubscription
ngay bây giờ.
import React, { useState } from 'react'
import { useSubscription } from 'graphql-hooks'
const TOTAL_COUNT_SUBSCRIPTION = `
subscription TotalCount {
totalCount {
count
}
}
`
function TotalCountComponent() {
const [count, setCount] = useState(0)
const [error, setError] = useState(null)
useSubscription({ query: TOTAL_COUNT_SUBSCRIPTION }, ({ data, errors }) => {
if (errors && errors.length > 0) {
// handle your errors
setError(errors[0])
return
}
// all good, handle the gql result
setCount(data.totalCount.count)
})
if (error) {
return <span>An error occurred {error.message}</span>
}
return <div>Current count: {count}</div>
}
Ví dụ làm việc :
Xem ví dụ về đăng ký của chúng tôi có cả mã client và mã máy chủ để tích hợp đăng ký vào ứng dụng của bạn.
Xem thêm ví dụ về giao thức WS hoàn chỉnh nếu bạn muốn xem cách gửi mọi thao tác thông qua WebSocket.
Hướng dẫn
SSR
Xem graphql-hooks-ssr để biết hướng dẫn chi tiết.
Phân trang
GraphQL Pagination có thể được thực hiện theo nhiều cách khác nhau và người tiêu dùng có quyền quyết định cách xử lý dữ liệu kết quả từ các truy vấn được phân trang. Hãy xem truy vấn sau đây là một ví dụ về phân trang theo offset:
export const allPostsQuery = `
query allPosts($first: Int!, $skip: Int!) {
allPosts(first: $first, skip: $skip) {
id
title
url
}
_allPostsMeta {
count
}
}
`
Trong truy vấn này, biến $first
được sử dụng để giới hạn số bài viết được trả về và biến $skip
được sử dụng để xác định vị trí bắt đầu. Chúng ta có thể sử dụng các biến này để chia dữ liệu lớn thành các khối nhỏ hơn, hoặc “trang”. Sau đó, chúng ta có thể chọn hiển thị các khối này như các trang riêng biệt cho người dùng hoặc sử dụng phương pháp tải vô hạn và thêm mỗi khối mới vào danh sách bài viết hiện có.
Các trang riêng biệt
Dưới đây là một ví dụ về việc hiển thị các truy vấn phân trang trên các trang riêng biệt:
import { React, useState } from 'react'
import { useQuery } from 'graphql-hooks'
export default function PostList() {
// set a default offset of 0 to load the first page
const [skipCount, setSkipCount] = useState(0)
const { loading, error, data } = useQuery(allPostsQuery, {
variables: { skip: skipCount, first: 10 }
})
if (error) return <div>There was an error!</div>
if (loading && !data) return <div>Loading</div>
const { allPosts, _allPostsMeta } = data
const areMorePosts = allPosts.length < _allPostsMeta.count
return (
<section>
<ul>
{allPosts.map(post => (
<li key={post.id}>
<a href={post.url}>{post.title}</a>
</li>
))}
</ul>
<button
// reduce the offset by 10 to fetch the previous page
onClick={() => setSkipCount(skipCount - 10)}
disabled={skipCount === 0}
>
Previous page
</button>
<button
// increase the offset by 10 to fetch the next page
onClick={() => setSkipCount(skipCount + 10)}
disabled={!areMorePosts}
>
Next page
</button>
</section>
)
}
Tải vô hạn
Dưới đây là một ví dụ về việc thêm mỗi truy vấn phân trang vào cuối danh sách hiện tại:
import { React, useState } from 'react'
import { useQuery } from 'graphql-hooks'
// use options.updateData to append the new page of posts to our current list of posts
const updateData = (prevData, data) => ({
...data,
allPosts: [...prevData.allPosts, ...data.allPosts]
})
export default function PostList() {
const [skipCount, setSkipCount] = useState(0)
const { loading, error, data } = useQuery(allPostsQuery, {
variables: { skip: skipCount, first: 10 },
updateData
})
if (error) return <div>There was an error!</div>
if (loading && !data) return <div>Loading</div>
const { allPosts, _allPostsMeta } = data
const areMorePosts = allPosts.length < _allPostsMeta.count
return (
<section>
<ul>
{allPosts.map(post => (
<li key={post.id}>
<a href={post.url}>{post.title}</a>
</li>
))}
</ul>
{areMorePosts && (
<button
// set the offset to the current number of posts to fetch the next page
onClick={() => setSkipCount(allPosts.length)}
>
Show more
</button>
)}
</section>
)
}
Truy vấn lại với các thay đổi từ đăng ký thay đổi
Chúng ta có thể có một truy vấn để tự động truy vấn lại khi bất kỳ thay đổi (mutation) nào từ danh sách được cung cấp thực hiện. Trong ví dụ sau, chúng tôi đang truy vấn lại một danh sách các bài viết cho một người dùng cụ thể.
Ví dụ
export const allPostsByUserIdQuery = `
query allPosts($userId: Int!) {
allPosts(userId: $userId) {
id
title
url
}
}
`
export const createPostMutation = `
mutation createPost($userId: Int!, $text: String!) {
createPost(userId: $userId, text: $text) {
id
title
url
}
}
`
const myUserId = 5
useQuery(allPostsByUserIdQuery, {
variables: {
userId: myUserId
},
refetchAfterMutations: [
{
mutation: createPostMutation,
filter: variables => variables.userId === myUserId
}
]
})
Cập nhật cache thủ công sau một số thay đổi (mutation)
Có hai cách để thực hiện điều đó:
Bằng cách truy vấn lại truy vấn
import { useMutation, useQueryClient } from 'graphql-hooks'
import React from 'react'
const MY_MUTATION = `...`
const MY_QUERY = `...`
export default function MyComponent() {
const client = useQueryClient()
const [applyMutation, { ... }] = useMutation(MY_MUTATION, {
onSuccess: () => client.invalidateQuery(MY_QUERY)
})
return (
...
)
}
Bằng cách ghi đè trạng thái cũ trong cache mà không cần truy vấn lại dữ liệu
import { useMutation, useQueryClient } from 'graphql-hooks'
import React from 'react'
const MY_MUTATION = `...`
const MY_QUERY = `...`
export default function MyComponent() {
const client = useQueryClient()
const [applyMutation, { ... }] = useMutation(MY_MUTATION, {
onSuccess: (result) => {
client.setQueryData(MY_QUERY, oldState => [
...oldState,
result,
])
}
})
return (
...
)
}
Tải lên tệp
graphql-hooks
tuân theo GraphQL multipart request spec, cho phép tệp được sử dụng làm đối số truy vấn hoặc biến đổi. Cùng một đặc tả này cũng được hỗ trợ bởi các máy chủ GraphQL phổ biến, bao gồm Apollo Server (xem danh sách các máy chủ được hỗ trợ tại here).
Nếu có tệp để tải lên, cơ thể của yêu cầu sẽ là một phiên bản FormData tuân theo đặc tả yêu cầu đa phần GraphQL.
import React, { useRef } from 'react'
import { useMutation } from 'graphql-hooks'
const uploadPostPictureMutation = `
mutation UploadPostPicture($picture: Upload!) {
uploadPostPicture(picture: $picture) {
id
pictureUrl
}
}
`
export default function PostForm() {
// File input is always uncontrolled in React.
// See: https://reactjs.org/docs/uncontrolled-components.html#the-file-input-tag.
const fileInputRef = useRef(null)
const [uploadPostPicture] = useMutation(uploadPostPictureMutation)
const handleSubmit = event => {
event.preventDefault()
uploadPostPicture({
variables: {
picture: fileInputRef.current.files[0]
}
})
}
return (
<form onSubmit={handleSubmit}>
<input accept="image/*" ref={fileInputRef} type="file" />
<button>Upload</button>
</form>
)
}
Tải lên tệp Node.js
import { FormData } from 'formdata-node'
import { fileFromPath } from 'formdata-node/file-from-path'
const client = new GraphQLClient({
url: 'https://domain.com/graphql',
fetch: require('node-fetch'),
FormData
})
const uploadPostPictureMutation = `
mutation UploadPostPicture($picture: Upload!) {
uploadPostPicture(picture: $picture) {
id
pictureUrl
}
}
`
const { data, error } = await client.request({
query: uploadPostPictureMutation,
variables: { picture: await fileFromPath('some-file.txt') }
})
Hỗ trợ HTTP Get
Sử dụng GET
cho các truy vấn có thể hữu ích, đặc biệt là khi triển khai bất kỳ chiến lược lưu trữ HTTP nào. Có hai cách bạn có thể làm điều này:
Theo truy vấn
const { loading, error, data } = useQuery(MY_QUERY, {
fetchOptionsOverrides: { method: 'GET' }
})
// same goes for useManualQuery
const [fetchSomething] = useManualQuery(MY_QUERY, {
fetchOptionsOverrides: { method: 'GET' }
})
Cho Tất cả Các Truy Vấn
Khi bạn tạo máy khách của mình, đặt tùy chọn useGETForQueries
thành true
:
const client = new GraphQLClient({
url: '/graphql',
useGETForQueries: true
})
Xác thực
Bạn có thể truy cập vào ngữ cảnh máy khách graphql-hooks bằng cách sử dụng API ngữ cảnh mới của React. ClientContext
thực sự là kết quả của React.createContext()
.
Ví dụ Đăng nhập
import React, { useState, useContext } from 'react'
import { useMutation, ClientContext } from 'graphql-hooks'
const LOGIN_MUTATION = `mutation LoginUser (name: String!, password: String!) {
loginUser(name: $name, password: $password) {
token
}
}`
const Login = () => {
const client = useContext(ClientContext)
const [loginUserMutation] = useMutation(LOGIN_MUTATION)
const [userName, setUserName] = useState()
const [password, setPassword] = useState()
const handleLogin = async e => {
e.preventDefault()
const { data, error } = await loginUserMutation({
variables: { userName, password }
})
if (error) {
// your code to handle login error
} else {
const { token } = data.loginUser
client.setHeader('Authorization', `Bearer ${token}`)
// your code to handle token in browser and login redirection
}
}
return (
<form onSubmit={handleLogin}>
User Name:{' '}
<input
type={'text'}
value={userName}
onChange={e => setUserName(e.target.value)}
/>
PassWord: <input
type={'password'}
value={password}
onChange={e => setPassword(e.target.value)}
/>
<input type={'submit'} value={'Login'} />
</form>
)
}
export default Login
Trong ví dụ trên, chúng tôi sử dụng useContext()
hook để truy cập vào ngữ cảnh máy khách graphql-hooks. Sau đó, chúng tôi yêu cầu mã thông báo từ máy chủ bằng cách thực hiện biến đổi loginUser
. Trong trường hợp đăng nhập thành công, chúng tôi đặt mã thông báo vào tiêu đề của máy khách (client.setHeader
), nếu không, chúng tôi cần xử lý lỗi. Để biết thêm thông tin về ngữ cảnh máy khách graphql-hooks, hãy tham khảo phần GraphQLClient.
Fragment
Sắp có!
Di chuyển từ Apollo
Để có một ví dụ thực tế, so sánh with-apollo của next.js vs with-graphql-hooks. Chúng tôi có tính năng tương đương và bản gói main-*.js
nhỏ đến 93% (7.9KB so với 116KB).
ApolloClient ➡️ GraphQLClient
- import { ApolloClient } from 'apollo-client'
- import { InMemoryCache } from 'apollo-cache-inmemory'
+ import { GraphQLClient } from 'graphql-hooks'
+ import memCache from 'graphql-hooks-memcache'
- const client = new ApolloClient({
- uri: '/graphql',
- cache: new InMemoryCache()
- })
+ const client = new GraphQLClient({
+ url: '/graphql',
+ cache: memCache()
+ })
Rất nhiều tùy chọn bạn sẽ truyền vào ApolloClient
giống như GraphQLClient
:
uri
➡️url
fetchOptions
onError
– chữ ký hàm có chút khác biệtheaders
fetch
cache
ApolloProvider ➡️ ClientContext.Provider
- import { ApolloProvider } from 'react-apollo'
+ import { ClientContext } from 'graphql-hooks'
function App({ client }) {
return (
- <ApolloProvider client={client}>
+ <ClientContext.Provider value={client}>
{/* children */}
+ </ClientContext.Provider>
- </ApolloProvider>
)
}
Query Component ➡️ useQuery
- import { Query } from 'react-apollo'
- import gql from 'graphql-tag'
+ import { useQuery } from 'graphql-hooks'
function MyComponent() {
+ const { loading, error, data } = useQuery('...')
- return (
- <Query query={gql`...`}>
- {({ loading, error, data}) => {
if (loading) return 'Loading...'
if (error) return 'Error :('
return <div>{data}</div>
- }}
- </Query>
- )
}
Thuộc tính Query Component
Rất nhiều tùy chọn có thể được giữ nguyên hoặc có các tùy chọn thay thế trực tiếp:
query
➡️useQuery(query)
: Loại bỏ bất kỳ sử dụng củagql
và truyền truy vấn của bạn dưới dạng chuỗi.variables
➡️useQuery(query, { variables })
ssr
➡️useQuery(query, { ssr })
- Chính sách Truy xuất : Xem #75 để biết thêm thông tin
cache-first
: Đây là hành vi mặc định củagraphql-hooks
cache-and-network
: Hành vi này được cung cấp bởi hàm refetch, nó sẽ đặt loading: true, nhưng dữ liệu cũ vẫn được đặt cho đến khi lấy dữ liệu hoàn thành.network-only
➡️useQuery(QUERY, { skipCache: true })
cache-only
: Không được hỗ trợno-cache
➡️useQuery(QUERY, { useCache: false })
Chưa được hỗ trợ
errorPolicy
: Bất kỳ lỗi nào sẽ đặterror
thành giá trị true. Xem useQuery để biết thêm chi tiết.pollInterval
notifyOnNetworkStatusChange
skip
onCompleted
: Khả năng tương tự nếu sử dụnguseManualQuery
onError
: Khả năng tương tự nếu sử dụnguseManualQuery
partialRefetch
Thuộc tính Render Props của Query Component
- <Query query={gql`...`}>
- {(props) => {}}
- </Query>
+ const state = useQuery(`...`)
props.loading
➡️const { loading } = useQuery('...')
props.error
➡️const { error } = useQuery('...')
: Giá trị lỗi từuseQuery
là Boolean, thông tin chi tiết về lỗi có thể được tìm thấy trong một trong các mục sau:state.fetchError
state.httpError
state.graphQLErrors
props.refetch
➡️const { refetch } = useQuery('...')
props.updateData(prevResult, options)
➡️state.updateData(prevResult, newResult)
Chưa được hỗ trợ
props.networkStatus
props.startPolling
props.stopPolling
props.subscribeToMore
Component Mutation ➡️ useMutation
- import { Mutation } from 'react-apollo'
- import gql from 'graphql-tag'
+ import { useMutation } from 'graphql-hooks'
function MyComponent() {
+ const [mutateFn, { loading, error, data }] = useMutation('...')
- return (
- <Mutation mutation={gql`...`}>
- {(mutateFn, { loading, error }) => {
if (error) return 'Error :('
return <button disabled={loading} onClick={() => mutateFn()}>Submit</button>
- }}
- </Mutation>
- )
}
Props Mutation
mutation
➡️useMutation(mutation)
– không cần bọc nó tronggql
variables
➡️useMutation(mutation, { variables })
hoặcmutateFn({ variables })
ignoreResults
➡️const [mutateFn] = useMutation(mutation)
onCompleted
➡️mutateFn().then(onCompleted)
onError
➡️mutateFn().then(({ error }) => {...})
Chưa được hỗ trợ
update
: Sắp có #52optimisticResponse
refetchQueries
awaitRefetchQueries
context
Component Mutation Render Props
- <Mutation mutation={gql`...`}>
- {(mutateFn, props) => {}}
- </Mutation>
+ const [mutateFn, state] = useMutation(`...`)
props.data
➡️const [mutateFn, { data }] = useMutation()
props.loading
➡️const [mutateFn, { loading }] = useMutation()
props.error
➡️const [mutateFn, { error }] = useMutation()
: Chi tiết lỗi có thể được tìm thấy trong một trong các mục sau:state.fetchError
state.httpError
state.graphQLErrors
client
➡️const client = useContext(ClientContext)
xem ClientContext
Chưa được hỗ trợ
called
Kiểm tra và giả lập
Có một lớp LocalGraphQLClient
bạn có thể sử dụng để giả lập các yêu cầu mà không cần máy chủ cho mục đích kiểm tra hoặc phát triển.
Khách hàng này kế thừa từ GraphQLClient
và cung cấp cùng một API, nhưng không kết nối đến bất kỳ máy chủ nào và thay vào đó đáp ứng các truy vấn đã được định nghĩa trước đó.
Nó cần được cung cấp khi tạo với một đối tượng localQueries
, đó là một đối tượng trong đó:
- các khóa là các truy vấn được định nghĩa trong ứng dụng;
-
các giá trị là các hàm truy vấn trả về dữ liệu giả.
// src/components/Post.js
export const allPostsQuery =query {
allPosts {
id
title
url
}
}// test/Post.test.tsx
import { allPostsQuery, createPostMutation } from ‘../src/components/Post’const localQueries = {
[allPostsQuery]: () => ({
allPosts: [
{
id: 1,
title: ‘Test’,
url: ‘https://example.com’
}
]
}),
[createPostMutation]: () => ({ createPost: { id: 1 } })
}
const client = new LocalGraphQLClient({ localQueries })
const { data, error } = await client.request({
query: allPostsQuery
})
LocalGraphQLClient
sẽ trả về các thuộc tính data
và error
trong cùng định dạng như GraphQLClient
Biến
Biến có thể được sử dụng trong các truy vấn giả định cục bộ được đưa cho LocalGraphQLClient
, sau đó có thể được cung cấp cho hàm request
:
const localQueries = {
AddNumbersQuery: ({ a, b }) => ({
addedNumber: a + b
})
}
const client = new LocalGraphQLClient({ localQueries })
const result = await client.request({
query: 'AddNumbersQuery',
variables: {
a: 2,
b: 3
}
})
console.log(result.data.addedNumber) // Will be 5
Mô phỏng lỗi
Lỗi có thể được mô phỏng đơn giản trong các truy vấn LocalGraphQLClient
bằng cách sử dụng lớp LocalGraphQLError
:
// test/Post.test.tsx
import { allPostsQuery } from '../src/components/Post'
const localQueries = {
[allPostsQuery]: () =>
new LocalGraphQLError({
httpError: {
status: 404,
statusText: 'Not found',
body: 'Not found'
}
})
}
const client = new LocalGraphQLClient({ localQueries })
const result = await client.request({
query: allPostsQuery
})
console.log(result.error) // The `error` object will have an `httpError`
Cũng có khả năng mô phỏng một phản hồi lỗi một phần (ví dụ: khi một bộ giải quyết gặp lỗi nhưng các bộ giải quyết khác trả về thành công). Để làm điều này, hãy bao gồm các đối tượng Error
trong bộ giải quyết truy vấn giả:
import { allPostsQuery } from '../src/components/Post'
const localQueries = {
[allPostsQuery]: () => ({
field1: 'foo',
field2: new Error('something went wrong'),
nested: {
field3: new Error('a nested error')
}
})
}
const client = new LocalGraphQLClient({ localQueries })
const result = await client.request({
query: allPostsQuery
})
console.log(result.data) // The `data` object will have the correct value for `field1` and `null` for any fields returning `Error` objects
console.log(result.error) // The `error` object will have a `graphQLErrors` array containing each of the `Error` objects created above
Kiểm tra với React
Các bài kiểm tra ví dụ sử dụng LocalGraphQLClient
được cung cấp tại the examples/create-react-app/test folder.
test-utils.js là một ví dụ tốt về cách tạo một hàm render tùy chỉnh bằng cách sử dụng @testing-library/react có thể bao gói quá trình render của một thành phần React trong một thiết lập ClientContext
để sử dụng LocalGraphQLClient
với các truy vấn cục bộ được cung cấp:
const customRender = (ui, options) => {
const client = new LocalGraphQLClient({
localQueries: options.localQueries
})
const Wrapper = ({ children }) => {
return (
<ClientContext.Provider value={client}>{children}</ClientContext.Provider>
)
}
Wrapper.propTypes = {
children: T.node.isRequired
}
return render(ui, {
wrapper: Wrapper,
...options
})
}
export * from '@testing-library/react'
export { customRender as render }
Sử dụng điều này cho phép dễ dàng render một thành phần bằng cách sử dụng LocalGraphQLClient
với các truy vấn cục bộ khi viết các bài kiểm tra:
// Comes from the above code
import { render, screen } from './test-utils'
const localQueries = {
[allPostsQuery]: () => ({
allPosts: [
{
id: 1,
title: 'Test',
url: 'https://example.com'
}
]
})
}
describe('Posts', () => {
it('should render successfully', async () => {
render(<Posts />, {
localQueries
})
expect(
await screen.findByRole('link', {
name: /Test/i
})
).toBeTruthy()
})
})
Thay đổi các truy vấn giả trong quá trình kiểm tra
Bởi vì LocalGraphQLClient
chỉ sử dụng đối tượng localQueries
được cung cấp cho nó, nên có thể thay đổi hoặc theo dõi các truy vấn cục bộ trong quá trình kiểm tra. Ví dụ:
it('shows "No posts" if 0 posts are returned', async () => {
jest.spyOn(localQueries, allPostsQuery).mockImplementation(() => ({
allPosts: []
}))
render(<Posts />, {
localQueries
})
expect(await screen.findByText('No posts')).toBeTruthy()
})
Khác
Intercepters yêu cầu
Cảm ơn bạn!
Có thể cung cấp một thư viện tùy chỉnh để xử lý các yêu cầu mạng. Điều này cho phép bạn có nhiều kiểm soát hơn về cách xử lý các yêu cầu. Ví dụ dưới đây cho thấy cách cung cấp axios HTTP client với interceptors. Điều này có thể hữu ích trong các tình huống khi mã thông báo JWT đã hết hạn, cần phải làm mới và thử lại yêu cầu.
import axios from 'axios'
import { buildAxiosFetch } from '@lifeomic/axios-fetch'
import { GraphQLClient } from 'graphql-hooks'
const gqlAxios = axios.create()
gqlAxios.interceptors.response.use(
function (response) {
return response
},
function (error) {
// Handle expired JWT and refresh token
}
)
const client = new GraphQLClient({
url: '/graphql',
fetch: buildAxiosFetch(gqlAxios)
})
AbortController
Nếu bạn muốn hủy một lệnh fetch, bạn có thể truyền tín hiệu AbortController vào tùy chọn fetchOptionsOverrides
của hàm fetch. Điều này không phải là chức năng cụ thể của graphql-hooks
, mà chỉ là một ví dụ về cách sử dụng nó với thư viện.
import { useManualQuery } from 'graphql-hooks'
function AbortControllerExample() {
const abortControllerRef = useRef()
const [fetchData, { loading }] = useManualQuery(`...`)
const handleFetch = () => {
abortControllerRef.current = new AbortController()
const { signal } = abortControllerRef.current
fetchData({
fetchOptionsOverrides: {
signal
}
})
}
const handleAbort = () => {
abortControllerRef.current?.abort()
}
return (
<>
<button onClick={handleFetch}>Fetch Data</button>
{loading && <button onClick={handleAbort}>Abort</button>}
)
}
Cộng đồng
Chúng tôi hiện sử dụng GitHub Discussions cho cộng đồng của mình. Để tham gia, hãy nhấp vào “Discussions”. Chúng tôi khuyến khích bạn bắt đầu một cuộc thảo luận mới, chia sẻ ý tưởng hoặc đặt câu hỏi từ cộng đồng. Nếu bạn muốn xem các bài viết cộng đồng cũ (trên Spectrum), bạn có thể truy cập chúng tại here.
Người đóng góp
Cảm ơn những người tuyệt vời này (emoji key):
Dự án này tuân theo thông số kỹ thuật all-contributors. Chúng tôi hoan nghênh mọi đóng góp!
Chi tiết tải xuống:
Tác giả: nearform
Nguồn: https://github.com/nearform/graphql-hooks
Giấy phép: View license
Cảm ơn bạn!