React đến tRPC
Nx Plugin for AWS cung cấp một generator để nhanh chóng tích hợp API tRPC của bạn với một trang web React. Nó thiết lập tất cả cấu hình cần thiết để kết nối với các backend tRPC của bạn, bao gồm hỗ trợ xác thực AWS IAM và Cognito cũng như xử lý lỗi phù hợp. Tích hợp này cung cấp type safety đầy đủ từ đầu đến cuối giữa frontend và (các) backend tRPC của bạn.
Điều kiện tiên quyết
Phần tiêu đề “Điều kiện tiên quyết”Trước khi sử dụng generator này, hãy đảm bảo ứng dụng React của bạn có:
- Một file
main.tsxrender ứng dụng của bạn - Một phần tử JSX
<App/>nơi tRPC provider sẽ được tự động inject - Một API tRPC hoạt động (được tạo bằng tRPC API generator)
- Cognito Auth được thêm qua
ts#website#authgenerator nếu kết nối một API sử dụng Cognito hoặc IAM auth
Ví dụ về cấu trúc main.tsx bắt buộc
import { StrictMode } from 'react';import * as ReactDOM from 'react-dom/client';import App from './app/app';
const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement,);root.render( <StrictMode> <App /> </StrictMode>,);Cách sử dụng
Phần tiêu đề “Cách sử dụng”Chạy Generator
Phần tiêu đề “Chạy Generator”pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connectionBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:connection --dry-runyarn nx g @aws/nx-plugin:connection --dry-runnpx nx g @aws/nx-plugin:connection --dry-runbunx nx g @aws/nx-plugin:connection --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - connection - Điền các tham số bắt buộc
- Nhấp
Generate
Tùy chọn
Phần tiêu đề “Tùy chọn”| Tham số | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
| sourceProject Bắt buộc | string | - | Dự án nguồn |
| targetProject Bắt buộc | string | - | Dự án đích để kết nối tới |
| sourceComponent | string | - | Component nguồn để kết nối từ đó (tên component, đường dẫn tương đối so với thư mục gốc của dự án nguồn, hoặc generator id). Sử dụng '.' để chọn rõ ràng dự án làm nguồn. |
| targetComponent | string | - | Component đích để kết nối tới (tên component, đường dẫn tương đối so với thư mục gốc của dự án đích, hoặc generator id). Sử dụng '.' để chọn rõ ràng dự án làm đích. |
| preferInstallDependencies | boolean | true | Có nên cài đặt các dependencies sau khi generator chạy hay không. Đặt thành false để trì hoãn việc cài đặt khi chạy nhiều generator liên tiếp (việc cài đặt vẫn sẽ chạy nếu cần thiết để các generator tiếp theo có thể tính toán Nx project graph); cài đặt một lần vào cuối. |
Kết quả của Generator
Phần tiêu đề “Kết quả của Generator”Generator tạo ra cấu trúc sau trong ứng dụng React của bạn:
Thư mụcsrc
Thư mụccomponents
- <ApiName>ClientProvider.tsx Thiết lập các tRPC client và binding đến (các) schema backend của bạn. ApiName sẽ được giải quyết thành tên của API
- QueryClientProvider.tsx TanStack React Query client provider
Thư mụchooks
- useSigV4.tsx Hook để ký các HTTP request với SigV4 (chỉ IAM)
- use<ApiName>.tsx Một hook trả về tRPC options proxy cho tích hợp TanStack Query
- use<ApiName>Client.tsx Một hook trả về vanilla tRPC client cho các lời gọi API trực tiếp
Ngoài ra, nó cài đặt các dependency cần thiết:
@trpc/client@trpc/tanstack-react-query@tanstack/react-queryaws4fetch(nếu sử dụng IAM auth)event-source-polyfill(nếu sử dụng REST API, để hỗ trợ subscription)
Sử dụng Code được tạo
Phần tiêu đề “Sử dụng Code được tạo”Sử dụng tRPC Options Proxy Hook
Phần tiêu đề “Sử dụng tRPC Options Proxy Hook”Generator cung cấp một hook use<ApiName> trả về một tRPC options proxy để sử dụng với các hook TanStack Query như useQuery và useMutation:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function MyComponent() { const trpc = useMyApi();
// Example query const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
// Example mutation const mutation = useMutation(trpc.users.create.mutationOptions());
const handleCreate = () => { mutation.mutate({ name: 'John Doe', email: 'john@example.com', }); };
if (isLoading) return <div>Loading...</div>;
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Sử dụng Vanilla tRPC Client
Phần tiêu đề “Sử dụng Vanilla tRPC Client”Hook use<ApiName>Client cung cấp quyền truy cập vào vanilla tRPC client, hữu ích cho các lời gọi API mệnh lệnh và subscription:
import { useState } from 'react';import { useMyApiClient } from './hooks/useMyApi';
function MyComponent() { const client = useMyApiClient();
const handleClick = async () => { const result = await client.echo.query({ message: 'Hello!' }); console.log(result);
const mutationResult = await client.users.create.mutate({ name: 'Jane' }); console.log(mutationResult); };
return <button onClick={handleClick}>Call API</button>;}Xử lý lỗi
Phần tiêu đề “Xử lý lỗi”Tích hợp này bao gồm xử lý lỗi tích hợp sẵn để xử lý đúng các lỗi tRPC:
function MyComponent() { const trpc = useMyApi();
const { data, error } = useQuery(trpc.users.list.queryOptions());
if (error) { return ( <div> <h2>Error occurred:</h2> <p>{error.message}</p> {error.data?.code && <p>Code: {error.data.code}</p>} </div> ); }
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Subscription (Streaming)
Phần tiêu đề “Subscription (Streaming)”Khi kết nối với một backend tRPC REST API, client được tạo tự động được cấu hình với một splitLink định tuyến các thao tác subscription thông qua httpSubscriptionLink (sử dụng SSE) và các query/mutation thông thường thông qua httpLink. Điều này có nghĩa là subscription hoạt động ngay lập tức mà không cần cấu hình bổ sung.
Để biết thông tin về cách định nghĩa các procedure subscription trong backend của bạn, xem hướng dẫn tRPC API generator.
Sử dụng useSubscription Hook
Phần tiêu đề “Sử dụng useSubscription Hook”Bạn có thể sử dụng subscription bằng hook useSubscription với subscriptionOptions từ options proxy:
import { useSubscription } from '@trpc/tanstack-react-query';import { useMyApi } from './hooks/useMyApi';
function StreamingComponent() { const trpc = useMyApi();
const subscription = useSubscription( trpc.myStream.subscriptionOptions( { query: 'hello' }, { enabled: true, onStarted: () => { console.log('Subscription started'); }, onData: (data) => { console.log('Received:', data.text); }, onError: (error) => { console.error('Subscription error:', error); }, }, ), );
return ( <div> <p>Status: {subscription.status}</p> {subscription.data && <p>Latest: {subscription.data.text}</p>} {subscription.error && <p>Error: {subscription.error.message}</p>} <button onClick={() => subscription.reset()}>Reset</button> </div> );}Đối tượng subscription cung cấp:
subscription.data— dữ liệu nhận được gần đây nhấtsubscription.error— lỗi nhận được gần đây nhấtsubscription.status— một trong'idle','connecting','pending', hoặc'error'subscription.reset()— reset subscription (hữu ích để khôi phục từ lỗi)
Sử dụng Vanilla Client
Phần tiêu đề “Sử dụng Vanilla Client”Ngoài ra, bạn có thể sử dụng vanilla tRPC client qua hook use<ApiName>Client để kiểm soát nhiều hơn vòng đời subscription:
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApi';
function StreamingComponent() { const client = useMyApiClient(); const [messages, setMessages] = useState<string[]>([]);
useEffect(() => { const subscription = client.myStream.subscribe( { query: 'hello' }, { onData: (data) => { setMessages((prev) => [...prev, data.text]); }, onComplete: () => { console.log('Stream complete'); }, onError: (error) => { console.error('Stream error:', error); }, }, );
// Clean up the subscription on unmount return () => subscription.unsubscribe(); }, [client]);
return ( <ul> {messages.map((msg, i) => ( <li key={i}>{msg}</li> ))} </ul> );}Thực hành tốt nhất
Phần tiêu đề “Thực hành tốt nhất”Xử lý trạng thái Loading
Phần tiêu đề “Xử lý trạng thái Loading”Luôn xử lý trạng thái loading và lỗi để có trải nghiệm người dùng tốt hơn:
function UserList() { const trpc = useMyApi();
const users = useQuery(trpc.users.list.queryOptions());
if (users.isLoading) { return <LoadingSpinner />; }
if (users.error) { return <ErrorMessage error={users.error} />; }
return ( <ul> {users.data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Optimistic Update
Phần tiêu đề “Optimistic Update”Sử dụng optimistic update để có trải nghiệm người dùng tốt hơn:
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
const deleteMutation = useMutation( trpc.users.delete.mutationOptions({ onMutate: async (userId) => { // Cancel outgoing fetches await queryClient.cancelQueries(trpc.users.list.queryFilter());
// Get snapshot of current data const previousUsers = queryClient.getQueryData( trpc.users.list.queryKey(), );
// Optimistically remove the user queryClient.setQueryData(trpc.users.list.queryKey(), (old) => old?.filter((user) => user.id !== userId), );
return { previousUsers }; }, onError: (err, userId, context) => { // Restore previous data on error queryClient.setQueryData( trpc.users.list.queryKey(), context?.previousUsers, ); }, }), );
return ( <ul> {users.map((user) => ( <li key={user.id}> {user.name} <button onClick={() => deleteMutation.mutate(user.id)}>Delete</button> </li> ))} </ul> );}Prefetch dữ liệu
Phần tiêu đề “Prefetch dữ liệu”Prefetch dữ liệu để có hiệu suất tốt hơn:
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
// Prefetch user details on hover const prefetchUser = async (userId: string) => { await queryClient.prefetchQuery(trpc.users.getById.queryOptions(userId)); };
return ( <ul> {users.map((user) => ( <li key={user.id} onMouseEnter={() => prefetchUser(user.id)}> <Link to={`/users/${user.id}`}>{user.name}</Link> </li> ))} </ul> );}Infinite Query
Phần tiêu đề “Infinite Query”Xử lý phân trang với infinite query:
function UserList() { const trpc = useMyApi();
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery( trpc.users.list.infiniteQueryOptions( { limit: 10 }, { getNextPageParam: (lastPage) => lastPage.nextCursor, }, ), );
return ( <div> {data?.pages.map((page) => page.users.map((user) => <UserCard key={user.id} user={user} />), )}
{hasNextPage && ( <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}> {isFetchingNextPage ? 'Loading...' : 'Load More'} </button> )} </div> );}Điều quan trọng cần lưu ý là infinite query chỉ có thể được sử dụng cho các procedure có thuộc tính input có tên cursor.
Type Safety
Phần tiêu đề “Type Safety”Tích hợp này cung cấp type safety hoàn chỉnh từ đầu đến cuối. IDE của bạn sẽ cung cấp autocompletion và type checking đầy đủ cho tất cả các lời gọi API của bạn:
function UserForm() { const trpc = useMyApi();
// ✅ Input is fully typed const createUser = trpc.users.create.useMutation();
const handleSubmit = (data: CreateUserInput) => { // ✅ Type error if input doesn't match schema createUser.mutate(data); };
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}Các type được tự động suy ra từ các định nghĩa router và schema của backend của bạn, đảm bảo rằng bất kỳ thay đổi nào đối với API của bạn đều được phản ánh ngay lập tức trong code frontend của bạn mà không cần build.
Custom Auth
Phần tiêu đề “Custom Auth”Nếu API tRPC của bạn sử dụng xác thực Custom (Lambda Authorizer), client provider được tạo bao gồm placeholder headers nơi bạn phải thêm các authorization header mà authorizer của bạn mong đợi. Tìm các comment // TODO: Add headers required by your custom authorizer trong file <ApiName>ClientProvider.tsx được tạo và thay thế chúng bằng logic token hoặc API key của bạn.
Thông tin thêm
Phần tiêu đề “Thông tin thêm”Để biết thêm thông tin, vui lòng tham khảo: