🔍 搜尋結果:onclick

🔍 搜尋結果:onclick

使用 NextJS 建立電子商務商店

在本教程中,您將學習如何建立電子商務商店,客戶可以在其中透過 Stripe 購買產品並付款。成功付款後,將向客戶發送電子郵件通知,並向管理員用戶發送應用程式內通知。管理員用戶也可以在應用程式中建立和刪除產品。 為了建立這個應用程式,我們將使用以下工具: - [Appwrite](https://appwrite.io/) - 用於驗證使用者身份,以及保存和檢索產品詳細資訊。 - [Next.js](https://nextjs.org/) - 用於建立應用程式的使用者介面和後端。 - [Novu](https://docs.novu.co/getting-started/introduction) - 用於發送電子郵件和應用程式內通知。 - [React Email](https://react.email/docs/introduction) - 用於建立電子郵件範本。 - [Stripe](https://docs.stripe.com/) - 用於將付款結帳整合到應用程式中。 ![應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t02iyysrqjfqw8imuqxn.png) --- 使用 Next.js 建立應用程式介面 ------------------- 應用程式頁面根據指派給使用者的角色分為兩部分。客戶可以在付款前存取主頁並登入應用程式。管理員使用者可以存取所有頁面,包括登入頁面和儀表板頁面,他們可以在其中新增和刪除產品。 現在,讓我們建立應用程式。 ![https://media1.giphy.com/media/iopxsZtW2QVRs4poEC/giphy.gif?cid=7941fdc6aot3qt7vvq4voh5c1iagyusdpuga713m8ljqcqmd&ep=v1_gifs_searchiagyusdpuga713m8ljqcqmd&ep=v1_gifs_searchiagyusdpugagif&ct](https://media1.giphy.com/media/iopxsZtW2QVRs4poEC/giphy.gif?cid=7941fdc6aot3qt7vvq4voh5c1iagyusdpuga713m8ljqcqmd&ep=v1_gifs_search&rid=giphy.gif&ct=g) 透過執行以下程式碼片段來建立一個新的 Next.js Typescript 專案: ``` npx create-next-app novu-store ``` 接下來,安裝[React Icons](https://react-icons.github.io/react-icons)和[Headless UI](https://headlessui.com/)包。 React Icons 允許我們在應用程式中使用各種圖標,而 Headless UI 則提供易於使用的現代 UI 元件。 ``` npm install react-icons @headlessui/react ``` 將此程式碼片段從[GitHub 儲存庫](https://github.com/dha-stix/ecom-store-with-nextjs-appwrite-novu-and-stripe/blob/main/src/app/page.tsx)複製到`app/page.tsx`檔案中。它在螢幕上呈現產品列表,並允許用戶選擇購物車中的商品,類似於下圖。 ![1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dj69givzhqfapgsg12rk.gif) 建立登入路由,使用戶能夠使用其 GitHub 帳戶進行簽署。將下面的程式碼片段複製到`app/login/page.tsx`檔案中。 ``` //👉🏻 create a login folder containing a page.tsx file export default function Home() { const handleGoogleSignIn = async () => {}; return ( <main className='w-full min-h-screen flex flex-col items-center justify-center'> <h2 className='font-semibold text-3xl mb-2'>Customer Sign in</h2> <p className='mb-4 text-sm text-red-500'> You need to sign in before you can make a purchase </p> <button className='p-4 border-[2px] border-gray-500 rounded-md hover:bg-black hover:text-white w-2/3' onClick={() => handleGoogleSignIn()} > Sign in with GitHub </button> </main> ); } ``` ![客戶登入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nh2rowpfg4hgksj5diy.png) 當使用者點擊「登入」按鈕時,會將他們重新導向到 GitHub 驗證頁面並提示他們登入應用程式。您很快就會了解如何使用[Appwrite](https://appwrite.io/)執行此操作。 接下來,讓我們建立管理頁面。在`app`資料夾中新增包含`login`和`dashboard`路由的`admin`資料夾。 ``` cd app mkdir admin && cd admin mkdir dashboard login ``` 在`dashboard`和`login`資料夾中新增`page.tsx`文件,並將下面的程式碼片段複製到`login/page.tsx`檔案中。 ``` "use client"; import Link from "next/link"; import { useState } from "react"; export default function Login() { const [email, setEmail] = useState<string>(""); const [password, setPassword] = useState<string>(""); const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); console.log({ email, password }); }; return ( <main className='w-full min-h-screen flex flex-col items-center justify-center'> <h2 className='font-semibold text-3xl mb-4'> Admin Sign in</h2> <form className='w-2/3' onSubmit={handleLogin}> <label htmlFor='email' className='block'> Email </label> <input type='email' id='email' className='w-full px-4 py-3 border border-gray-400 rounded-sm mb-4' required value={email} placeholder='[email protected]' onChange={(e) => setEmail(e.target.value)} /> <label htmlFor='password' className='block'> Password </label> <input type='password' id='password' className='w-full px-4 py-3 border border-gray-400 rounded-sm mb-4' required value={password} placeholder='admin123' onChange={(e) => setPassword(e.target.value)} /> <button className='p-4 text-lg mb-3 bg-blue-600 text-white w-full rounded-md'> Sign in </button> <p className='text-sm text-center'> Not an Admin?{" "} <Link href='/login' className='text-blue-500'> Sign in as a Customer </Link> </p> </form> </main> ); } ``` 上面的程式碼片段呈現一個表單,該表單接受管理員的電子郵件和密碼,驗證憑證,然後將使用者登入應用程式中。 ![管理員登入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjd9wsi63t96d5cls9om.png) 管理儀表板頁面呈現可用的產品,並允許管理員使用者在應用程式中新增和刪除產品。將此[程式碼片段複製](https://github.com/dha-stix/ecom-store-with-nextjs-appwrite-novu-and-stripe/blob/main/src/app/admin/dashboard/page.tsx)到`dashboard/page.tsx`檔案中以建立使用者介面。 ![2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1gd1uq1eq6n76fesjxu.gif) 恭喜!您已經建立了應用程式介面。在接下來的部分中,您將了解如何將應用程式連接到 Appwrite 後端並在客戶端和伺服器之間發送資料。 --- 如何將 Appwrite 新增到 Next.js 應用程式 ----------------------------- Appwrite 是一項開源後端服務,可讓您建立安全且可擴展的軟體應用程式。它提供多種身份驗證方法、安全性資料庫、文件儲存、雲端訊息傳遞等功能,這些對於建立全端應用程式至關重要。 在本部分中,您將了解如何設定 Appwrite 專案,包括身份驗證、資料庫和檔案儲存等功能。 首先,請造訪[Appwrite Cloud](https://cloud.appwrite.io/register) ,並為您的專案建立一個帳戶和組織。 接下來,建立一個新專案並選擇您的首選區域來託管該專案。 ![應用程式寫入1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/as6302olk60oklfo70x5.png) 選擇`Web`作為應用程式的平台 SDK。 ![應用程式編寫2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bb5ae82i9fyoyrowsy96.png) 請依照螢幕上顯示的步驟進行操作。由於您目前正在開發模式下建置,因此您可以使用通配符 ( `*` ) 作為主機名,並在部署應用程式後將其變更為您的網域名稱。 ![應用程式寫入3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5ccs0hzgs9ujf5lzh83.png) 在 Next.js 專案中安裝 Appwrite 用戶端 SDK。 ``` npm install appwrite ``` 最後,在 Next.js 應用程式資料夾中建立一個`appwrite.ts`文件,並將下面的程式碼片段複製到該文件中以初始化 Appwrite。 ``` import { Client, Account, Databases, Storage } from "appwrite"; const client = new Client(); client .setEndpoint("https://cloud.appwrite.io/v1") .setProject(<YOUR_PROJECT_ID>); export const account = new Account(client); export const db = new Databases(client); export const storage = new Storage(client); ``` ### 使用 Appwrite 設定 GitHub 身份驗證 在這裡,您將了解如何使用 Appwrite 設定 GitHub 和電子郵件/密碼驗證。預設已配置電子郵件/密碼身份驗證,因此我們專注於設定 GitHub 身份驗證。 在繼續之前,您需要使用您的 GitHub 帳戶建立[GitHub OAuth 應用程式](https://github.com/settings/developers)。 Appwrite 將需要客戶端 ID 和金鑰來設定 GitHub 身份驗證。 ![GitHub 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9znk1yr7tffus7soitq2.png) 透過從側邊欄選單中選擇`Auth`並導覽至`Settings`選項卡,啟用 Appwrite 的 GitHub 驗證方法。 ![應用程式編寫4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43uo6nho1bz9su14zsno.png) 將您的 GitHub 用戶端 ID 和金鑰複製到 Appwrite 的 GitHub OAuth 設定中。 最後,確保將 Appwrite 產生的 URI 複製到 GitHub 應用程式設定中。 ![GitHub 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g75q5r5hc6l5pi09k88m.png) ### 設定 Appwrite 資料庫 從側邊欄選單中選擇資料庫並建立新資料庫。您可以將其命名為`novu store` 。 ![應用程式寫入5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7kn1llmu7olqirfcrpa.png) 接下來,建立`products`集合。它將包含應用程式中的產品清單。 ![應用程式寫入 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7p8laty6z37x0q1g6az4.png) 將名稱、價格和圖像屬性新增至集合。 ![應用程式寫入 7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzom3ptlz8t1rh9dtt1k.png) 在「設定」標籤下,更新權限以允許每個使用者執行 CRUD 操作。但是,您可以在部署應用程式後變更此設置,以確保只有經過身份驗證的使用者才能執行各種操作。 ![應用程式寫入 8](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/37cqr8s0crtcttocjagk.png) 最後,將專案、資料庫和集合 ID 複製到**`.env.local`**檔案中。這可以確保您的憑證安全,並允許您引用其環境變數中的每個值。 ``` NEXT_PUBLIC_PROJECT_ID=<YOUR_PROJECT_ID> NEXT_PUBLIC_DB_ID=<YOUR_DATABASE_ID> NEXT_PUBLIC_PRODUCTS_COLLECTION_ID=<YOUR_DB_COLLECTION_ID> ``` ### 設定應用程式寫入存儲 從側邊欄選單中選擇`Storage` ,然後建立新儲存桶來儲存所有產品影像。 ![應用程式編寫 9](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b84t9mk3k0wrkgiy4uca.png) 在`Settings`標籤下,更新「權限」以暫時允許任何使用者。 ![應用程式寫入 10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zi3iozkaera7fohkwanm.png) 設定可接受的文件格式。由於我們上傳的是圖像,因此您可以選擇**`.jpg`**和**`.png`**檔案格式。 ![應用寫入 11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzxqpoq5dcpokkvdcsce.png) 最後,將您的儲存桶 ID 複製到`.env.local`檔案中。 ``` NEXT_PUBLIC_BUCKET_ID=<YOUR_BUCKET_ID> ``` 恭喜!您已成功配置 Appwrite。我們現在可以開始與其各種功能進行互動。 --- 如何使用Appwrite執行CRUD操作 -------------------- 在本部分中,您將了解如何從 Appwrite 建立、檢索和刪除產品。用戶需要能夠在購買前查看現有產品,而管理員用戶應有權在應用程式中新增和刪除產品。 首先,在 Next.js **`app`**資料夾中建立一個**`utils.ts`**檔案。該文件將包含所有 Appwrite 資料庫交互,然後您可以將其導入到必要的頁面中。 ``` cd app touch utils.ts ``` ### 將產品儲存到 Appwrite 回想一下, `products`集合有三個屬性:名稱、圖像和價格。因此,在將產品新增至資料庫時,您需要先上傳產品的圖像,從回應中檢索其 URL 和 ID,然後將 URL 作為產品的圖像屬性上傳,使用圖像的儲存 ID 作為產品資料。 這是解釋這一點的程式碼片段: ``` import { db, storage } from "@/app/appwrite"; import { ID } from "appwrite"; export const createProduct = async ( productTitle: string, productPrice: number, productImage: any ) => { try { //👇🏻 upload the image const response = await storage.createFile( process.env.NEXT_PUBLIC_BUCKET_ID!, ID.unique(), productImage ); //👇🏻 get the image's URL const file_url = `https://cloud.appwrite.io/v1/storage/buckets/${process.env.NEXT_PUBLIC_BUCKET_ID}/files/${response.$id}/view?project=${process.env.NEXT_PUBLIC_PROJECT_ID}&mode=admin`; //👇🏻 add the product to the database await db.createDocument( process.env.NEXT_PUBLIC_DB_ID!, process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID!, response.$id, //👉🏻 use the image's ID { name: productTitle, price: productPrice, image: file_url, } ); alert("Product created successfully"); } catch (err) { console.error(err); } }; ``` 上面的程式碼片段將圖像上傳到 Appwrite 的雲端存儲,並使用儲存桶 ID、圖像 ID 和專案 ID 檢索準確的圖像 URL。圖片成功上傳後,其 ID 將用於產品資料中,以便輕鬆檢索和參考。 ### 從 Appwrite 檢索產品 若要從 Appwrite 取得產品,您可以在頁面載入時在 React **`useEffect`**掛鉤中執行下列函數。 ``` export const fetchProducts = async () => { try { const products = await db.listDocuments( process.env.NEXT_PUBLIC_DB_ID!, process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID! ); if (products.documents) { return products.documents; } } catch (err) { console.error(err); } }; ``` `fetchProducts`函數傳回`products`集合中的所有資料。 ### 從 Appwrite 中刪除產品 管理員使用者也可以透過產品 ID 刪除產品。 **`deleteProduct`**函數接受產品的 ID 作為參數,並從資料庫中刪除所選產品(包括其圖像),因為它們使用相同的 ID 屬性。 ``` export const deleteProduct = async (id: string) => { try { await db.deleteDocument( process.env.NEXT_PUBLIC_DB_ID!, process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID!, id ); await storage.deleteFile(process.env.NEXT_PUBLIC_BUCKET_ID!, id); alert("Product deleted successfully"); } catch (err) { console.error(err); } }; ``` --- 如何使用 Appwrite 驗證使用者身份 --------------------- 在前面的部分中,我們已經設定了 GitHub 身份驗證方法。在這裡,您將了解如何處理使用者登入應用程式。 若要使客戶能夠使用其 GitHub 帳戶登入應用程式,請在按一下`Sign in`按鈕時執行以下功能。該函數將使用者重定向到 GitHub,在那裡他們可以向應用程式授權或授予權限,然後登入應用程式: ``` import { account } from "../appwrite"; import { OAuthProvider } from "appwrite"; const handleGoogleSignIn = async () => { try { account.createOAuth2Session( OAuthProvider.Github, "http://localhost:3000", "http://localhost:3000/login" ); } catch (err) { console.error(err); } }; ``` 管理員使用者可以使用電子郵件和密碼登入應用程式。 Appwrite 在授予對應用程式儀表板的存取權之前會驗證憑證。 ``` import { account } from "@/app/appwrite"; const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); try { await account.createEmailPasswordSession(email, password); alert(`Welcome back 🎉`); router.push("/admin/dashboard"); } catch (err) { console.error(err); alert("Invalid credentials ❌"); } }; ``` Appwrite 還允許您取得目前使用者的資料。例如,如果只有經過身份驗證的使用者才能付款,您可以透過執行下面的程式碼片段來完成此操作。它會檢索目前使用者的資料,如果使用者未登錄,則傳回 null。 ``` import { account } from "@/app/appwrite"; useEffect(() => { const checkAuthStatus = async () => { try { const request = await account.get(); setUser(request); } catch (err) { console.log(err); } }; checkAuthStatus(); }, []); ``` --- 如何將 Stripe 付款結帳新增至 Next.js -------------------------- 在本節中,您將了解如何在應用程式中實現 Stripe 付款結帳。 Stripe 是一種流行的線上支付處理平台,可讓您建立產品並將一次性和定期支付方式整合到您的應用程式中。 首先,您需要[建立一個 Stripe 帳戶](https://dashboard.stripe.com/login)。您可以在本教學中使用測試模式帳戶。 ![條紋1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nibs7bxb09i167mxm918.png) 點擊頂部選單中的`Developers` ,然後從 API 金鑰選單中複製您的金鑰。 ![條紋2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/up8757knbquc3k0577ps.png) 將您的 Stripe 金鑰貼到`.env.local`檔案中。 ``` STRIPE_SECRET_KEY=<your_secret_key> ``` 安裝[Stripe Node.js SDK](https://docs.stripe.com/libraries) 。 ``` npm install stripe ``` 接下來,在 Next.js `app`資料夾中建立一個`api`資料夾。 `api`資料夾將包含應用程式的所有 API 路由和端點。 ``` cd app mkdir api ``` 透過在`api`資料夾中新增`checkout`資料夾來建立`checkout`端點。 ``` cd api mkdir checkout && cd checkout touch route.ts ``` 將下面的程式碼片段複製到`route.ts`檔中。 ``` import { NextRequest, NextResponse } from "next/server"; import Stripe from "stripe"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(req: NextRequest) { //👇🏻 accepts the customer's cart const cart = await req.json(); try { //👇🏻 creates a checkout session const session = await stripe.checkout.sessions.create({ payment_method_types: ["card"], line_items: cart.map((product: Product) => ({ price_data: { currency: "usd", product_data: { name: product.name, }, unit_amount: product.price * 100, }, quantity: 1, })), mode: "payment", cancel_url: `http://localhost:3000/?canceled=true`, success_url: `http://localhost:3000?success=true&session_id={CHECKOUT_SESSION_ID}`, }); //👇🏻 return the session URL return NextResponse.json({ session: session.url }, { status: 200 }); } catch (err) { return NextResponse.json({ err }, { status: 500 }); } } ``` 上面的程式碼片段建立了一個接受 POST 請求的結帳端點。它為客戶建立結帳會話並傳回會話 URL。 **`cancel_url`**和**`success_url`**確定完成或取消付款後將用戶重新導向到何處。 最後,當用戶決定為產品付款時,您可以透過執行以下程式碼片段將客戶的購物車發送到`/checkout`端點: ``` const processPayment = async (cart: Product[]) => { try { if (user !== null) { //👇🏻 saves cart to local storage localStorage.setItem("cart", JSON.stringify(cart)); //👇🏻 sends cart to /checkout route const request = await fetch("/api/checkout", { method: "POST", body: JSON.stringify(cart), headers: { "Content-Type": "application/json" }, }); //👇🏻 retrieves the session URL const { session } = await request.json(); //👇🏻 redirects the user to the checkout page window.location.assign(session); } else { //👇🏻 redirects unauthenticated users router.push("/login"); } } catch (err) { console.error(err); } }; ``` 上面的程式碼片段將購物車儲存到瀏覽器的本機儲存體並將其傳送到 API 端點,然後從後端伺服器檢索回應(會話 URL)並將使用者重新導向至 Stripe 結帳頁面。 ![條紋3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i5hokf2qyqyey3kwsg9x.gif) --- 使用 Novu 發送應用程式內通知和電子郵件通知 ------------------------ [Novu](https://github.com/novuhq/novu)是第一個提供統一 API 的通知基礎架構,用於透過多種管道(包括應用程式內、推播、電子郵件、簡訊和聊天)發送通知。 在本部分中,您將了解如何將 Novu 加入到您的應用程式,以便您能夠發送電子郵件和應用程式內訊息。 首先,安裝所需的 Novu 軟體包: ``` npm install @novu/node @novu/echo @novu/notification-center ``` 當用戶進行購買時,他們將收到一封付款確認電子郵件,管理員用戶也會收到一條應用程式內通知。 為此,您需要[在 Novu 上建立帳戶](https://web.novu.co/auth/login)並設定主要電子郵件提供者。在本教程中,我們將使用[“重新發送”](https://resend.com/docs/introduction) 。 在 Novu 上建立帳戶後,建立一個[重新傳送帳戶](https://resend.com/docs/introduction),然後從儀表板上的側邊欄選單中選擇`API Keys`來建立帳戶。 ![重新發送 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jhehx7s45x180zpir1ti.png) 接下來,回到 Novu 儀表板,從側邊欄選單中選擇`Integrations Store` ,然後新增 Resend 作為電子郵件提供者。您需要將重新傳送 API 金鑰和電子郵件地址貼到必填欄位中。 ![新 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f03vb6nftyi8g790vg7m.png) 從側邊欄選單中選擇**「設定」** ,然後將您的`Novu API`金鑰和`App ID`複製到**`.env.local`**檔案中,如下所示。另外,將您的`subscriber ID`複製到其欄位中 - 您可以從`Subscribers`部分獲取此資訊。 ``` NOVU_API_KEY=<YOUR_API_FOR_NEXT_SERVER> NEXT_PUBLIC_NOVU_API_KEY=<YOUR_API_FOR_NEXT_CLIENT> NEXT_PUBLIC_NOVU_APP_ID=<YOUR_API_ID> NOVU_SUBSCRIBER_ID=<YOUR_API_FOR_NEXT_SERVER> NEXT_PUBLIC_NOVU_SUBSCRIBER_ID=<YOUR_API_FOR_CLIENT> ``` ![新2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/voeofvvtv88pex9rpr1s.png) 最後,將 Novu 通知鈴新增至管理儀表板,以使管理員使用者能夠在應用程式內接收通知。 ``` import { NovuProvider, PopoverNotificationCenter, NotificationBell, } from "@novu/notification-center"; export default function AdminNav() { return ( <NovuProvider subscriberId={process.env.NEXT_PUBLIC_NOVU_SUBSCRIBER_ID!} applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!} > <PopoverNotificationCenter colorScheme='light'> {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />} </PopoverNotificationCenter> </NovuProvider> ); } ``` ![儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m62ft87ue9orse2yww9z.png) --- 如何使用 Novu Echo 建立通知工作流程 ----------------------- [Novu](https://docs.novu.co/echo/quickstart)提供程式碼優先的工作流程引擎,讓您能夠在程式碼庫中建立通知工作流程。它允許您將電子郵件、簡訊、聊天範本和內容產生器(例如[React Email](https://react.email/docs/introduction)和[MJML](https://mjml.io/) )整合到 Novu 中,以建立高級且強大的通知。 在本部分中,您將了解如何在應用程式中建立通知工作流程、如何使用 Novu 的電子郵件通知範本以及如何使用 Novu 發送應用程式內通知和電子郵件通知。 透過執行以下命令安裝[React Email](https://react.email/docs/introduction) : ``` npm install react-email @react-email/components -E ``` 將以下腳本包含在您的 package.json 檔案中。 `--dir`標誌使 React Email 能夠存取位於專案內的電子郵件範本。在本例中,電子郵件範本位於`src/emails`資料夾中。 ``` { "scripts": { "email": "email dev --dir src/emails" } } ``` 接下來,在 Next.js `app`資料夾中建立一個包含`email.tsx`的`emails`資料夾,並將以下程式碼片段複製到該檔案中: ``` import { Body, Column, Container, Head, Heading, Hr, Html, Link, Preview, Section, Text, Row, render, } from "@react-email/components"; import * as React from "react"; const EmailTemplate = ({ message, subject, name, }: { message: string; subject: string; name: string; }) => ( <Html> <Head /> <Preview>{subject}</Preview> <Body style={main}> <Container style={container}> <Section style={header}> <Row> <Column style={headerContent}> <Heading style={headerContentTitle}>{subject}</Heading> </Column> </Row> </Section> <Section style={content}> <Text style={paragraph}>Hey {name},</Text> <Text style={paragraph}>{message}</Text> </Section> </Container> <Section style={footer}> <Text style={footerText}> You&apos;re receiving this email because your subscribed to Newsletter App </Text> <Hr style={footerDivider} /> <Text style={footerAddress}> <strong>Novu Store</strong>, &copy;{" "} <Link href='https://novu.co'>Novu</Link> </Text> </Section> </Body> </Html> ); export function renderEmail(inputs: { message: string; subject: string; name: string; }) { return render(<EmailTemplate {...inputs} />); } const main = { backgroundColor: "#f3f3f5", fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif", }; const headerContent = { padding: "20px 30px 15px" }; const headerContentTitle = { color: "#fff", fontSize: "27px", fontWeight: "bold", lineHeight: "27px", }; const paragraph = { fontSize: "15px", lineHeight: "21px", color: "#3c3f44", }; const divider = { margin: "30px 0", }; const container = { width: "680px", maxWidth: "100%", margin: "0 auto", backgroundColor: "#ffffff", }; const footer = { width: "680px", maxWidth: "100%", margin: "32px auto 0 auto", padding: "0 30px", }; const content = { padding: "30px 30px 40px 30px", }; const header = { borderRadius: "5px 5px 0 0", display: "flex", flexDireciont: "column", backgroundColor: "#2b2d6e", }; const footerDivider = { ...divider, borderColor: "#d6d8db", }; const footerText = { fontSize: "12px", lineHeight: "15px", color: "#9199a1", margin: "0", }; const footerLink = { display: "inline-block", color: "#9199a1", textDecoration: "underline", fontSize: "12px", marginRight: "10px", marginBottom: "0", marginTop: "8px", }; const footerAddress = { margin: "4px 0", fontSize: "12px", lineHeight: "15px", color: "#9199a1", }; ``` 上面的程式碼片段使用 React Email 建立了一個可自訂的電子郵件範本。您可以找到更多[易於編輯的靈感或模板](https://demo.react.email/preview/notifications/vercel-invite-user)。該元件還接受訊息、主題和名稱作為屬性,並將它們填入元素中。 最後,您可以在終端機中執行`npm run email`來預覽範本。 接下來,讓我們將電子郵件範本整合到 Novu Echo 中。首先,關閉 React Email 伺服器,然後執行下面的程式碼片段。它會在瀏覽器中開啟[Novu Dev Studio](https://docs.novu.co/echo/concepts/studio) 。 ``` npx novu-labs@latest echo ``` 在 Next.js 應用程式資料夾中建立一個包含`client.ts`檔案的`echo`資料夾,並將此程式碼片段複製到該檔案中。 ``` import { Echo } from "@novu/echo"; import { renderEmail } from "@/app/emails/email"; interface EchoProps { step: any; payload: { subject: string; message: string; name: string; totalAmount: string; }; } export const echo = new Echo({ apiKey: process.env.NEXT_PUBLIC_NOVU_API_KEY!, devModeBypassAuthentication: process.env.NODE_ENV === "development", }); echo.workflow( "novu-store", async ({ step, payload }: EchoProps) => { //👇🏻 in-app notification step await step.inApp("notify-admin", async () => { return { body: `${payload.name} just made a new purchase of ${payload.totalAmount} 🎉`, }; }); //👇🏻 email notification step await step.email( "email-customer", async () => { return { subject: `${payload ? payload?.subject : "No Subject"}`, body: renderEmail(payload), }; }, { inputSchema: { type: "object", properties: {}, }, } ); }, { payloadSchema: { type: "object", properties: { message: { type: "string", default: "Congratulations! Your purchase was successful! 🎉", }, subject: { type: "string", default: "Message from Novu Store" }, name: { type: "string", default: "User" }, totalAmount: { type: "string", default: "0" }, }, required: ["message", "subject", "name", "totalAmount"], additionalProperties: false, }, } ); ``` 此程式碼片段定義了一個名為`novu-store` Novu 通知工作流程,該工作流程接受包含電子郵件主題、訊息、客戶姓名和總金額的有效負載。 此工作流程有兩個步驟:應用程式內通知和電子郵件通知。應用程式內通知使用通知鈴聲向管理員發送訊息,而電子郵件則向客戶的電子郵件發送訊息。 接下來,您需要為 Novu Echo 建立 API 路由。在`api`資料夾中,建立一個包含`route.ts`檔案的`email`資料夾,並將下面提供的程式碼片段複製到該檔案中。 ``` import { serve } from "@novu/echo/next"; import { echo } from "@/app/echo/client"; export const { GET, POST, PUT } = serve({ client: echo }); ``` 在終端機中執行`npx novu-labs@latest echo` 。它將自動開啟 Novu Dev Studio,您可以在其中預覽工作流程並將其與雲端同步。 ![新3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ed2sl38m7zrlgjoj4a6y.gif) `Sync to Cloud`按鈕會觸發一個彈出窗口,其中提供有關如何將工作流程推送到 Novu 雲端的說明。 ![新4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ch8ba7y9klyudmmv9jz.png) 若要繼續,請在終端機中執行以下程式碼片段。這將產生一個唯一的 URL,表示您的開發環境和雲端環境之間的本機隧道。 ``` npx localtunnel --port 3000 ``` 將產生的連結與 Echo API 端點一起複製到 Echo Endpoint 欄位中,按一下`Create Diff`按鈕,然後部署變更。 ``` https://<LOCAL_TUNNEL_URL>/<ECHO_API_ENDPOINT (/api/email)> ``` 恭喜!您剛剛從程式碼庫建立了 Novu 工作流程。 ![新5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6bdugs6g15y1e7xeixux.png) 最後,讓我們建立在用戶付款時發送電子郵件和應用程式內通知的端點。建立一個`api/send`路由並將下面的程式碼片段複製到檔案中: ``` import { NextRequest, NextResponse } from "next/server"; import { Novu } from "@novu/node"; const novu = new Novu(process.env.NOVU_API_KEY!); export async function POST(req: NextRequest) { const { email, name, totalAmount } = await req.json(); const { data } = await novu.trigger("novu-store", { to: { subscriberId: process.env.NOVU_SUBSCRIBER_ID!, email, firstName: name, }, payload: { name, totalAmount, subject: `Purchase Notification from Novu Store`, message: `Your purchase of ${totalAmount} was successful! 🎉`, }, }); console.log(data.data); return NextResponse.json( { message: "Purchase Completed!", data: { novu: data.data }, success: true, }, { status: 200 } ); } ``` 端點接受客戶的電子郵件、姓名和支付總額,並在付款成功後觸發 Novu 通知工作流程發送所需的通知。 --- 結論 -- 到目前為止,您已經學會如何執行以下操作: - 實施多種身份驗證方法,從 Appwrite 儲存和檢索資料和檔案。 - 使用 React Email 建立電子郵件模板,並使用 Novu 發送應用程式內和電子郵件通知。 如果您希望在應用程式中發送通知,Novu 是您的最佳選擇。使用 Novu,您可以為應用程式加入多個通知管道,包括聊天、簡訊、電子郵件、推播和應用程式內通知。 本教學的源程式碼可在此處取得: <https://github.com/novuhq/ecom-store-with-nextjs-appwrite-novu-and-stripe> 感謝您的閱讀! --- 原文出處:https://dev.to/novu/building-an-e-commerce-store-with-nextjs-49m

如何建置:具有嵌入式 AI copilot 的待辦事項清單應用程式(Next.js、GPT4 和 CopilotKit)

**長話短說** -------- 待辦事項清單是每個開發人員的經典專案。在當今世界,學習如何使用人工智慧進行建構並在你的投資組合中加入一些人工智慧專案是很棒的。 今天,我將逐步介紹如何使用嵌入式 AI 副駕駛來建立待辦事項列表,以實現一些 AI 魔法🪄。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nw0jituk3y5tosz6f34u.gif) 我們將介紹如何: - 使用 Next.js、TypeScript 和 Tailwind CSS 建立待辦事項清單產生器 Web 應用。 - 使用 CopilotKit 將 AI 功能整合到待辦事項清單產生器中。 - 使用 AI 聊天機器人新增清單、將清單分配給某人、將清單標記為已完成以及刪除清單。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/guf0l2fiq1g1jv86o7bg.png) --- CopilotKit:建構應用內人工智慧副駕駛的框架 -------------------------- CopilotKit是一個[開源的AI副駕駛框架](https://github.com/CopilotKit/CopilotKit)。我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。 建造: - ChatBot:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 💬 - CopilotTextArea:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能📝 - 聯合代理:應用程式內人工智慧代理,可以與您的應用程式和使用者互動🤖 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myp7zashy99m33ya8kaf.gif) {% cta https://go.copilotkit.ai/bonnie %} 明星 CopilotKit ⭐️ {% endcta %} --- 先決條件 ---- 要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。 以下是建立人工智慧驅動的待辦事項清單產生器所需的工具: - [Nanoid](https://github.com/ai/nanoid) - 一個小型、安全、URL 友善、唯一的 JavaScript 字串 ID 產生器。 - [OpenAI API](https://platform.openai.com/api-keys) - 提供 API 金鑰,讓您能夠使用 ChatGPT 模型執行各種任務。 - [CopilotKit](https://github.com/CopilotKit) - 一個開源副駕駛框架,用於建立自訂 AI 聊天機器人、應用程式內 AI 代理程式和文字區域。 專案設定和套件安裝 --------- 首先,透過在終端機中執行以下程式碼片段來建立 Next.js 應用程式: ``` npx create-next-app@latest todolistgenerator ``` 選擇您首選的配置設定。在本教學中,我們將使用 TypeScript 和 Next.js App Router。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tc6he9eivkt3hhxnj3p.png) 接下來,安裝 Nanoid 套件及其相依性。 ``` npm i nanoid ``` 最後,安裝 CopilotKit 軟體套件。這些套件使我們能夠從 React 狀態檢索資料並將 AI copilot 新增至應用程式。 ``` npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend @copilotkit/shared ``` 恭喜!您現在已準備好建立由人工智慧驅動的待辦事項清單產生器。 **建立待辦事項清單產生器前端** ----------------- 在本節中,我將引導您完成使用靜態內容建立待辦事項清單產生器前端的過程,以定義生成器的使用者介面。 首先,請在程式碼編輯器中前往`/[root]/src/app`並建立一個名為`types`資料夾。在 types 資料夾中,建立一個名為`todo.ts`的文件,並新增以下程式碼來定義名為**`Todo`的 TypeScript 介面。** **`Todo`**介面定義了一個物件結構,其中每個待辦事項都必須具有**`id`** 、 **`text`**和**`isCompleted`**狀態,同時也可以選擇具有**`assignedTo`**屬性。 ``` export interface Todo { id: string; text: string; isCompleted: boolean; assignedTo?: string; } ``` 然後轉到程式碼編輯器中的`/[root]/src/app`並建立一個名為`components`的資料夾。在 Components 資料夾中,建立三個名為`Header.tsx` 、 `TodoList.tsx`和`TodoItem.tsx`的檔案。 在`Header.tsx`檔案中,新增以下程式碼,定義一個名為`Header`的功能元件,該元件將呈現生成器的導覽列。 ``` import Link from "next/link"; export default function Header() { return ( <> <header className="flex flex-wrap sm:justify-start sm:flex-nowrap z-50 w-full bg-gray-800 border-b border-gray-200 text-sm py-3 sm:py-0 "> <nav className="relative max-w-7xl w-full mx-auto px-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8" aria-label="Global"> <div className="flex items-center justify-between"> <Link className="w-full flex-none text-xl text-white font-semibold p-6" href="/" aria-label="Brand"> To-Do List Generator </Link> </div> </nav> </header> </> ); } ``` 在`TodoItem.tsx`檔案中,新增以下程式碼來定義名為**`TodoItem`**的 React 功能元件。它使用 TypeScript 來確保類型安全性並定義元件接受的 props。 ``` import { Todo } from "../types/todo"; // Importing the Todo type from a types file // Defining the interface for the props that the TodoItem component will receive interface TodoItemProps { todo: Todo; // A single todo item toggleComplete: (id: string) => void; // Function to toggle the completion status of a todo deleteTodo: (id: string) => void; // Function to delete a todo assignPerson: (id: string, person: string | null) => void; // Function to assign a person to a todo hasBorder?: boolean; // Optional prop to determine if the item should have a border } // Defining the TodoItem component as a functional component with the specified props export const TodoItem: React.FC<TodoItemProps> = ({ todo, toggleComplete, deleteTodo, assignPerson, hasBorder, }) => { return ( <div className={ "flex items-center justify-between px-4 py-2 group" + (hasBorder ? " border-b" : "") // Conditionally adding a border class if hasBorder is true }> <div className="flex items-center"> <input className="h-5 w-5 text-blue-500" type="checkbox" checked={todo.isCompleted} // Checkbox is checked if the todo is completed onChange={() => toggleComplete(todo.id)} // Toggle completion status on change /> <span className={`ml-2 text-sm text-white ${ todo.isCompleted ? "text-gray-500 line-through" : "text-gray-900" // Apply different styles if the todo is completed }`}> {todo.assignedTo && ( <span className="border rounded-md text-xs py-[2px] px-1 mr-2 border-purple-700 uppercase bg-purple-400 text-black font-medium"> {todo.assignedTo} {/* Display the assigned person's name if available */} </span> )} {todo.text} {/* Display the todo text */} </span> </div> <div> <button onClick={() => deleteTodo(todo.id)} // Delete the todo on button click className="text-red-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5"> <path strokeLinecap="round" strokeLinejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> </svg> </button> <button onClick={() => { const name = prompt("Assign person to this task:"); assignPerson(todo.id, name); }} className="ml-2 text-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5"> <path strokeLinecap="round" strokeLinejoin="round" d="M18 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0ZM3 19.235v-.11a6.375 6.375 0 0 1 12.75 0v.109A12.318 12.318 0 0 1 9.374 21c-2.331 0-4.512-.645-6.374-1.766Z" /> </svg> </button> </div> </div> ); }; ``` 在`TodoList.tsx`檔案中,加入以下程式碼來定義名為**`TodoList`**的 React 功能元件。此元件用於管理和顯示待辦事項清單。 ``` "use client"; import { TodoItem } from "./TodoItem"; // Importing the TodoItem component import { nanoid } from "nanoid"; // Importing the nanoid library for generating unique IDs import { useState } from "react"; // Importing the useState hook from React import { Todo } from "../types/todo"; // Importing the Todo type // Defining the TodoList component as a functional component export const TodoList: React.FC = () => { // State to hold the list of todos const [todos, setTodos] = useState<Todo[]>([]); // State to hold the current input value const [input, setInput] = useState(""); // Function to add a new todo const addTodo = () => { if (input.trim() !== "") { // Check if the input is not empty const newTodo: Todo = { id: nanoid(), // Generate a unique ID for the new todo text: input.trim(), // Trim the input text isCompleted: false, // Set the initial completion status to false }; setTodos([...todos, newTodo]); // Add the new todo to the list setInput(""); // Clear the input field } }; // Function to handle key press events const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter") { // Check if the Enter key was pressed addTodo(); // Add the todo } }; // Function to toggle the completion status of a todo const toggleComplete = (id: string) => { setTodos( todos.map((todo) => todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo ) ); }; // Function to delete a todo const deleteTodo = (id: string) => { setTodos(todos.filter((todo) => todo.id !== id)); }; // Function to assign a person to a todo const assignPerson = (id: string, person: string | null) => { setTodos( todos.map((todo) => todo.id === id ? { ...todo, assignedTo: person ? person : undefined } : todo ) ); }; return ( <div> <div className="flex mb-4"> <input className="border rounded-md p-2 flex-1 mr-2" value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={handleKeyPress} // Add this to handle the Enter key press /> <button className="bg-blue-500 rounded-md p-2 text-white" onClick={addTodo}> Add Todo </button> </div> {todos.length > 0 && ( // Check if there are any todos <div className="border rounded-lg"> {todos.map((todo, index) => ( <TodoItem key={todo.id} // Unique key for each todo item todo={todo} // Pass the todo object as a prop toggleComplete={toggleComplete} // Pass the toggleComplete function as a prop deleteTodo={deleteTodo} // Pass the deleteTodo function as a prop assignPerson={assignPerson} // Pass the assignPerson function as a prop hasBorder={index !== todos.length - 1} // Conditionally add a border to all but the last item /> ))} </div> )} </div> ); }; ``` 接下來,前往`/[root]/src/page.tsx`文件,新增以下程式碼,匯入`TodoList`和`Header`元件並定義名為`Home`的功能元件。 ``` import Header from "./components/Header"; import { TodoList } from "./components/TodoList"; export default function Home() { return ( <> <Header /> <div className="border rounded-md max-w-2xl mx-auto p-4 mt-4"> <h1 className="text-2xl text-white font-bold mb-4"> Create a to-do list </h1> <TodoList /> </div> </> ); } ``` 接下來,刪除`globals.css`檔案中的 CSS 程式碼並新增以下 CSS 程式碼。 ``` @tailwind base; @tailwind components; @tailwind utilities; body { height: 100vh; background-color: rgb(16, 23, 42); } ``` 最後,在命令列上執行命令`npm run dev` ,然後導航到 http://localhost:3000/。 現在您應該在瀏覽器上查看待辦事項清單產生器前端,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmd8j3brrtiqackalul1.png) **使用 CopilotKit 將 AI 功能整合到待辦事項清單產生器** ------------------------------------- 在本節中,您將學習如何將 AI 副駕駛新增至待辦事項清單產生器,以使用 CopilotKit 產生清單。 CopilotKit 提供前端和[後端](https://docs.copilotkit.ai/getting-started/quickstart-backend)套件。它們使您能夠插入 React 狀態並使用 AI 代理在後端處理應用程式資料。 首先,我們將 CopilotKit React 元件新增至待辦事項清單產生器前端。 ### **將 CopilotKit 新增至待辦事項清單產生器前端** 在這裡,我將引導您完成將待辦事項清單產生器與 CopilotKit 前端整合以促進清單產生的過程。 首先,使用下面的程式碼片段匯入`/[root]/src/app/components/TodoList.tsx`檔案頂部的`useCopilotReadable`和`useCopilotAction`自訂掛鉤。 ``` import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core"; ``` 在`TodoList`函數內的狀態變數下方,新增以下程式碼,該程式碼使用`useCopilotReadable`掛鉤來新增將作為應用程式內聊天機器人的上下文產生的待辦事項清單。該鉤子使副駕駛可以讀取待辦事項清單。 ``` useCopilotReadable({ description: "The user's todo list.", value: todos, }); ``` 在上面的程式碼下方,新增以下程式碼,該程式碼使用`useCopilotAction`掛鉤來設定名為`updateTodoList`的操作,該操作將啟用待辦事項清單的產生。 此操作採用一個名為 items 的參數,該參數可以產生待辦事項列表,並包含一個根據給定提示產生待辦事項列表的處理程序函數。 在處理函數內部, `todos`狀態會使用新產生的 todo 清單進行更新,如下所示。 ``` // Define the "updateTodoList" action using the useCopilotAction function useCopilotAction({ // Name of the action name: "updateTodoList", // Description of what the action does description: "Update the users todo list", // Define the parameters that the action accepts parameters: [ { // The name of the parameter name: "items", // The type of the parameter, an array of objects type: "object[]", // Description of the parameter description: "The new and updated todo list items.", // Define the attributes of each object in the items array attributes: [ { // The id of the todo item name: "id", type: "string", description: "The id of the todo item. When creating a new todo item, just make up a new id.", }, { // The text of the todo item name: "text", type: "string", description: "The text of the todo item.", }, { // The completion status of the todo item name: "isCompleted", type: "boolean", description: "The completion status of the todo item.", }, { // The person assigned to the todo item name: "assignedTo", type: "string", description: "The person assigned to the todo item. If you don't know, assign it to 'YOU'.", // This attribute is required required: true, }, ], }, ], // Define the handler function that executes when the action is invoked handler: ({ items }) => { // Log the items to the console for debugging purposes console.log(items); // Create a copy of the existing todos array const newTodos = [...todos]; // Iterate over each item in the items array for (const item of items) { // Find the index of the existing todo item with the same id const existingItemIndex = newTodos.findIndex( (todo) => todo.id === item.id ); // If an existing item is found, update it if (existingItemIndex !== -1) { newTodos[existingItemIndex] = item; } // If no existing item is found, add the new item to the newTodos array else { newTodos.push(item); } } // Update the state with the new todos array setTodos(newTodos); }, // Provide feedback or a message while the action is processing render: "Updating the todo list...", }); ``` 在上面的程式碼下方,新增以下程式碼,程式碼使用`useCopilotAction`掛鉤來設定名為`deleteTodo`的操作,該操作使您能夠刪除待辦事項。 該操作採用名為id 的參數,該參數可讓您透過id 刪除待辦事項,並包含一個處理函數,該函數透過過濾掉具有給定id 的已刪除待辦事項來更新待辦事項狀態。 ``` // Define the "deleteTodo" action using the useCopilotAction function useCopilotAction({ // Name of the action name: "deleteTodo", // Description of what the action does description: "Delete a todo item", // Define the parameters that the action accepts parameters: [ { // The name of the parameter name: "id", // The type of the parameter, a string type: "string", // Description of the parameter description: "The id of the todo item to delete.", }, ], // Define the handler function that executes when the action is invoked handler: ({ id }) => { // Update the state by filtering out the todo item with the given id setTodos(todos.filter((todo) => todo.id !== id)); }, // Provide feedback or a message while the action is processing render: "Deleting a todo item...", }); ``` 之後,請前往`/[root]/src/app/page.tsx`檔案並使用下面的程式碼匯入頂部的 CopilotKit 前端套件和樣式。 ``` import { CopilotKit } from "@copilotkit/react-core"; import { CopilotPopup } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; ``` 然後使用`CopilotKit`包裝`CopilotPopup`和`TodoList`元件,如下所示。 `CopilotKit`元件指定 CopilotKit 後端端點 ( `/api/copilotkit/` ) 的 URL,而`CopilotPopup`則呈現應用程式內聊天機器人,您可以提示產生待辦事項清單。 ``` export default function Home() { return ( <> <Header /> <div className="border rounded-md max-w-2xl mx-auto p-4 mt-4"> <h1 className="text-2xl text-white font-bold mb-4"> Create a to-do list </h1> <CopilotKit runtimeUrl="/api/copilotkit"> <TodoList /> <CopilotPopup instructions={ "Help the user manage a todo list. If the user provides a high level goal, " + "break it down into a few specific tasks and add them to the list" } defaultOpen={true} labels={{ title: "Todo List Copilot", initial: "Hi you! 👋 I can help you manage your todo list.", }} clickOutsideToClose={false} /> </CopilotKit> </div> </> ); } ``` 之後,執行開發伺服器並導航至[http://localhost:3000](http://localhost:3000/) 。您應該會看到應用程式內聊天機器人已整合到待辦事項清單產生器中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ryeqnymp5lm397thpn5f.png) ### **將 CopilotKit 後端加入博客** 在這裡,我將引導您完成將待辦事項清單產生器與 CopilotKit 後端整合的過程,該後端處理來自前端的請求,並提供函數呼叫和各種 LLM 後端(例如 GPT)。 首先,在根目錄中建立一個名為`.env.local`的檔案。然後將下面的環境變數加入到儲存`ChatGPT` API 金鑰的檔案中。 ``` OPENAI_API_KEY="Your ChatGPT API key” ``` 若要取得 ChatGPT API 金鑰,請導覽至 https://platform.openai.com/api-keys。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0bibiuouk5wvrxcuy2g.jpeg) 之後,轉到`/[root]/src/app`並建立一個名為`api`的資料夾。在`api`資料夾中,建立一個名為`copilotkit`的資料夾。 在`copilotkit`資料夾中,建立一個名為`route.ts`的文件,其中包含設定後端功能以處理POST 請求的程式碼。 ``` // Import the necessary modules from the "@copilotkit/backend" package import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/backend"; // Define an asynchronous function to handle POST requests export async function POST(req: Request): Promise<Response> { // Create a new instance of CopilotRuntime const copilotKit = new CopilotRuntime({}); // Use the copilotKit to generate a response using the OpenAIAdapter // Pass the incoming request (req) and a new instance of OpenAIAdapter to the response method return copilotKit.response(req, new OpenAIAdapter()); } ``` 如何產生待辦事項列表 ---------- 現在轉到您之前整合的應用程式內聊天機器人,並給它一個提示,例如「我想去健身房做全身運動。加入到我應該遵循的鍛煉程序列表” 生成完成後,您應該會看到應遵循的全身運動程序列表,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48n9ssymxhm5i2yv0pdf.png) 您可以透過向聊天機器人發出「將待辦事項清單指派給 Doe」之類的提示來將待辦事項清單指派給某人。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6r1f816c1o8da5z0t1kk.png) 您可以透過向聊天機器人提供「將待辦事項清單標記為已完成」等提示來將待辦事項清單標記為已完成。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qx2wpg6ee7hswjl68t4q.png) 您可以透過向聊天機器人發出「刪除待辦事項清單」之類的提示來刪除待辦事項清單。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksoj6mtlxt8ag8opsocs.png) 恭喜!您已完成本教學的專案。 結論 -- [CopilotKit](https://copilotkit.ai/)是一款令人難以置信的工具,可讓您在幾分鐘內將 AI Copilot 加入到您的產品中。無論您是對人工智慧聊天機器人和助理感興趣,還是對複雜任務的自動化感興趣,CopilotKit 都能讓您輕鬆實現。 如果您需要建立 AI 產品或將 AI 工具整合到您的軟體應用程式中,您應該考慮 CopilotKit。 您可以在 GitHub 上找到本教學的源程式碼:https://github.com/TheGreatBonnie/AIpoweredToDoListGenerator --- 原文出處:https://dev.to/copilotkit/how-to-build-an-ai-powered-to-do-list-nextjs-gpt4-copilotkit-20i4

您可能不知道的 HTML 標籤

開發者們大家好👋 在這篇文章中,我將分享一些在 HTML5 中加入的新的、有用的 html 標籤,用於編寫簡單快速的程式碼來建立複雜、動態、引人入勝且有效的網站。 讓我們開始吧🚀 對話 -- ➡ 現在您可以使用`<dialog>`標籤輕鬆建立對話方塊或彈出視窗。這是建立自訂模式對話方塊的好方法,而無需嚴重依賴**JavaScript** 。 ``` <dialog id="myDialog"> <p>This is a dialog box</p> <button onclick="document.getElementById('myDialog').close()">Close </button> </dialog> <button onclick="document.getElementById('myDialog').showModal()">Open Dialog </button> ``` 範本 -- ➡ `<template>`標籤用作儲存頁面載入時不希望顯示的客戶端內容的容器。可以使用**JavaScript**克隆該內容並將其插入到文件中。 ``` <button onclick="showContent()">Show hidden content</button> <template> <h2>Hello, This is Kiran</h2> <p>Thanks for reading this</p> </template> <script> function showContent() { let temp = document.getElementsByTagName("template")[0]; let clon = temp.content.cloneNode(true); document.body.appendChild(clon); } </script> ``` 圖片 -- ➡ 透過使用`<picture>`標籤,您可以為一張圖片定義多個來源,現在瀏覽器會根據螢幕尺寸、解析度選擇最佳的來源。這對於**響應式**設計特別有用。 ``` <picture> <source media="(min-width:650px)" srcset="img_pink_flowers.jpg"> <source media="(min-width:465px)" srcset="img_white_flower.jpg"> <img src="img_orange_flowers.jpg" alt="Flowers" style="width:auto;"> </picture> ``` 儀表 -- ➡ `<meter>`標籤可用來表示已知範圍內的標量測量,例如磁碟使用情況或查詢結果的相關性。它有助於**直觀地**顯示一定範圍內的值。 ``` <label for="diskUsage">Disk Usage:</label> <meter id="diskUsage" value="0.6">60%</meter> ``` 輸出 -- ➡ `<output>`標籤代表計算的結果。它可以與**JavaScript**一起使用來顯示計算**結果**。 ``` <form oninput="result.value=parseInt(a.value)+parseInt(b.value)"> <input type="number" id="a" value="50"> + <input type="number" id="b" value="25"> = <output name="result" for="a b">75</output> </form> ``` 進步 -- ➡ `<progress>`標籤代表**任務**的完成進度,例如下載或檔案上傳。 ``` <label for="fileProgress">File upload progress:</label> <progress id="fileProgress" value="70" max="100">70%</progress> ``` 標記 -- ➡ `<mark>`標籤用於反白顯示文字。它對於您想要**突出顯示**匹配文字的搜尋結果頁面特別有用。 ``` <p>The word <mark>highlighted</mark> is important.</p> ``` 縮寫 -- ➡ `<abbr>`標籤用於定義縮寫詞或首字母縮寫詞,在**標題**屬性中提供完整的**描述**。 ``` <p>I'm a true<abbr title="Marvel Cinematic Universe">MCU</abbr>fan.</p> ``` 時間 -- ➡ `<time>`標籤用於表示日期、時間或持續時間。它對於使與時間相關的資料變得機器可讀非常有用。 ``` <p>The concert starts at <time datetime="20:00">8 PM</time>.</p> ``` 巴迪 -- ➡ `<bdi>`標籤用於隔離可能與外部其他文字的格式方向不同的文字部分。無論涉及何種語言或文字**方向**,它都能確保您的網頁內容保持**一致**和可讀。 ``` <ul> <li>Product: <bdi>ABC1234</bdi></li> <li>Product: <bdi>مرحبا5678</bdi></li> </ul> ``` 沃伯 -- ➡ `<wbr>`標記指定文字可以在何處**換行**(如有必要)。這對於長單字或 URL 很有用。 ``` <p>Thisisaverylongword<wbr>thatmightneedbreaking.</p> ``` 主要的 --- ➡ `<main>`標籤用於指定文件的**主要內容**。它每頁只能使用一次,並且排除文件中重複的內容,例如頁首、頁尾、導覽和側邊欄。 ``` <main> <h1>Welcome to my blog post</h1> <p>Today we will learn some new html tags</p> </main> ``` 圖片標題 ---- ➡ `<figcaption>`標籤用於為圖形提供**標題**。 ``` <figure> <img src="Thanos.jpg" alt="Thanos image"> <figcaption>Thanos snapping his fingers</figcaption> </figure> ``` 這就是本文的內容。 感謝您的閱讀❤ 在 👉 https://x.com/kiran\_\_a\_\_n X https://github.com/Kiran1689 GitHub 上找到我 https://dev.to/dev\_kiran --- 原文出處:https://dev.to/dev_kiran/html-tags-you-might-not-know-about-3gk7

用不到 40 行程式碼建立您自己的 React 狀態管理庫 - 並支援 TypeScript

你有沒有想過 React 狀態管理函式庫是如何建構的?從像 redux 這樣具有大量樣板和大包大小的解決方案,到像 zustand 或 jotai 這樣更輕、更簡單的庫。今天我們將建立我們自己的狀態管理庫,並看看幕後發生的魔法。 了解 useSyncExternalStore ----------------------- React 18 引入了一個名為[useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore)的新鉤子,它允許 React 同步到任何外部儲存。 ``` useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?) ``` 以下是其參數的詳細說明: - **subscribe**接收一個回呼作為參數,並將該回調訂閱到外部 store,以便當 store 狀態改變時呼叫它,需要傳回一個取消訂閱函數。 - **getSnapshot**取得儲存的目前快照,該快照必須是快取值,因為 React 使用`Object.is(getSnapshot(), oldSnapshot)`在每次渲染上比較該值,每次提供新值將導致無限循環。 - **getServerSnapshot** (可選)允許我們在伺服器上渲染時返回快照,這在外部儲存或訂閱源無法在伺服器上執行或需要特定處理才能在伺服器上執行的某些情況下很有幫助。 利用 useSyncExternalStore,我們可以根據我們的要求建立一個簡約的 store 。 為什麼不直接使用 React Context? ----------------------- [React Context](https://react.dev/learn/passing-data-deeply-with-context)是 React 中的一個功能,它允許元件將 props 傳遞到其下面的整個元件樹,這意味著它可以用作存儲,是一個可行的選擇。 React 上下文需要一些樣板: ``` const context = createContext(); const CountProvider = ({ children }) => { const [count, setCount] = useState(0); return <context.Provider value={{ count, setCount }}>{children}</context.Provider>; }; export function App() { return ( <CountProvider> <Outer /> <Other /> </CountProvider> ); } ``` 大量使用 Context 可能會導致“Context Hell”,其中大量上下文提供者嵌套在 App 元件中: ``` export function App() { return ( <CountProvider> <AuthProvider> <ThemeProvider> <CacheProvider> <IntlProvider> <TooltipProvider> <UserSettingsProvider> <NotificationProvider> <AnalyticsProvider> <Content /> </UserSettingsProvider> </NotificationProvider> </AnalyticsProvider> </TooltipProvider> </IntlProvider> </CacheProvider> </ThemeProvider> </AuthProvider> </CountProvider> ); } ``` 此外,使用上下文可能會無意中觸發整個元件樹的重新渲染,如下所示: ``` export function App() { const [count, setCount] = useState(0); return ( <context.Provider value={{ count, setCount }}> <Outer /> <Other /> </context.Provider> ); } ``` 從上下文的用戶使用 setCount 將導致整個應用程式的重新渲染(外部和其他都會重新渲染),因為狀態是在應用程式元件上設定的,並且當它重新渲染時,它的所有子元件元件也被重新渲染。 此外,使用外部存儲可以讓我們更輕鬆地與 http 請求等外部系統同步反應,在上下文中您將使用 useEffect,而使用外部存儲您可以直接更新存儲,更改將在訂閱元件。 建造我們的 store ------- 讓我們深入研究一下我們 store 的實現。我們將從一個基本結構開始,然後根據我們的要求逐步增強它。 ``` import { useSyncExternalStore } from 'react'; export type Listener = () => void; function createStore<T>({ initialState }: { initialState: T }) { let subscribers: Listener[] = []; let state = initialState; const notifyStateChanged = () => { subscribers.forEach((fn) => fn()); }; return { subscribe(fn: Listener) { subscribers.push(fn); return () => { subscribers = subscribers.filter((listener) => listener !== fn); }; }, getSnapshot() { return state; }, setState(newState: T) { state = newState; notifyStateChanged(); }, }; } ``` **訂閱者**是一組偵聽器,我們的 store 將在 store 狀態的每次變更時通知它們。 **State**是 store 的狀態,我們將在呼叫 setState 時更新它,然後通知所有 store 的訂閱者更新。 為了在 React 中使用 store,我們將建立 createUseStore,它是一個以方便的方式包裝 createStore 和 useSyncExternalStore 的幫助器: ``` export function createUseStore<T>(initialState: T) { const store = createStore({ initialState }); return () => [useSyncExternalStore(store.subscribe, store.getSnapshot), store.setState] as const; } ``` 使用 store ---- store 就位後,讓我們開始建立一個 Counter 元件: ``` import React, { useState } from "react"; export function Counter() { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; const decrement = () => { setCount(count - 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); } ``` 並在我們的應用程式中渲染三次: ``` import React from 'react'; import ReactDOM from 'react-dom/client'; import { Counter } from './Counter.tsx'; ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <Counter /> <Counter /> <Counter /> </React.StrictMode>, ); ``` 現在,我們在頁面中看到三個計數器,點擊「增量」只會增量其中一個計數器: ![計數器起始點](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ark89f633vqifdh41d2.png) 讓我們使用我們的 store 讓這 3 個計數器使用相同的狀態,首先我們將使用我們先前建立的 createUseStore 幫助器建立 useCountStore: ``` export const useCountStore = createUseStore(0); ``` 現在讓我們在計數器中使用 useCountStore 鉤子: ``` import { useCountStore } from "./countStore"; function Counter() { const [count, setCount] = useCountStore(); const increment = () => { setCount(count + 1); }; const decrement = () => { setCount(count - 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); } ``` 現在我們的 3 個計數器已同步,並且所有計數器一起遞增: ![3 個計數器現在共用相同的狀態](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j33b0ns2ep2g02obopjg.png) 由於使用了泛型,TypeScript 知道 count 是一個數字,而 setCount 是一個接受數字的回呼: ![Typescript 對狀態的支持](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dk0yplo7zsc2ukdgbzf6.png) ![Typescript 對 setState 的支持](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b2l2mb8htqc16glwzczb.png) 下一步 --- 關於如何改進和建立我們的簡單 store 的一些想法: **還原狀態** 在我們的儲存中設定狀態非常直接,這很方便,但有時我們在確定狀態時可能需要處理複雜的邏輯,這就是減速器可能幫助我們的地方,我們可以為我們的儲存加入一個新的調度函數: ``` dispatch(action) { state = reducer(action); notifyStateChanged(); }, ``` **處理深度嵌套狀態** 設定新狀態需要解構現有狀態,如果我們有深度嵌套的狀態,這可能會很煩人,為了解決這個問題,我們可以使用 immer 或類似的函式庫: ``` // without immer setState({ ...state, nested: { ...state.nested, sub: { ...state.nested.sub, new: true, } } }); // with immer import { produce } from "immer"; const nextState = produce(state, s => { s.nested.sub.new = true; }); setState(nextState); ``` 我們甚至可以在內部將 immer 加入到我們的 store,並在 setState 中接受回調,如下所示: ``` setState((state) => { state.nested.sub.new = true; return state; }); ``` 結論 -- 在本教學中,我們完成了建立一個具有 TypeScript 支援的簡單 React 狀態管理函式庫的步驟。 透過利用 React 的`useSyncExternalStore`鉤子,我們建立了一個簡單但功能強大的存儲,可以與 React 元件無縫整合。 現在您已經掌握了它的竅門,您就可以建立自己的自訂狀態管理庫了。 --- 在 React 文件中閱讀有關[useSyncExternalStore 的](https://react.dev/reference/react/useSyncExternalStore)更多資訊。 要查看本文中討論的概念的實際實現,請查看[此處的](https://github.com/paripsky/tinystate)**tinystate-react** 。該庫是使用本教程中描述的方法建置的,可讓您更深入地研究程式碼和範例。 --- 原文出處:https://dev.to/paripsky/build-your-own-react-state-management-library-in-under-40-lines-of-code-with-typescript-support-hji

我正在建立一個人工智慧驅動的部落格:具體方法如下...

人工智慧時代已經來臨。身為開發者,要想取得成功,在你的投資組合中加入一些人工智慧驅動的專案是很棒的。 今天,我們將建立一個由人工智慧驅動的部落格平台,該平台具有一些很棒的功能,例如研究、自動完成和 Copilot。 我[在這裡](https://dev.to/copilotkit/how-to-build-an-ai-powered-blogging-platform-nextjs-langchain-supabase-1hdp)建立了該專案的初始版本。一位評論者提出了一些非常酷的建議,可以將其提升到一個新的水平。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b2pbxvo6rff8xz8a0h3b.png) 所以我們決定建造它! **長話短說** -------- 我們正在建立一個由人工智慧驅動的部落格平台 Pt。二 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bi1j9iq1jkll1r21f1e8.gif) --- CopilotKit:建構應用內人工智慧副駕駛的框架 ========================== CopilotKit是一個[開源的AI副駕駛平台](https://github.com/CopilotKit/CopilotKit)。我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。 建造: - ChatBot:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 💬 - CopilotTextArea:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能📝 - 聯合代理:應用程式內人工智慧代理,可以與您的應用程式和使用者互動🤖 ![上傳圖片](...) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} 現在回到文章! --- 先決條件 ---- 要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。 以下是建立人工智慧部落格所需的工具: - [Quill 富文本編輯器](https://quilljs.com/)- 一個文字編輯器,可讓您輕鬆設定文字格式、新增圖片、新增程式碼以及在 Web 應用程式中建立自訂互動式內容。 - [Supabase](https://supabase.com/) - 一項 PostgreSQL 託管服務,為您提供專案所需的所有後端功能。 - [Langchain](https://www.langchain.com/) - 提供了一個框架,使人工智慧代理能夠搜尋網路並研究任何主題。 - [OpenAI API](https://platform.openai.com/api-keys) - 提供 API 金鑰,讓您能夠使用 ChatGPT 模型執行各種任務。 - [Tavily AI](https://tavily.com/) - 一個搜尋引擎,使人工智慧代理能夠在應用程式中進行研究並存取即時知識。 - [CopilotKit](https://github.com/CopilotKit) - 一個開源副駕駛框架,用於建立自訂 AI 聊天機器人、應用程式內 AI 代理程式和文字區域。 專案設定和套件安裝 --------- 首先,透過在終端機中執行以下程式碼片段來建立 Next.js 應用程式: ``` npx create-next-app@latest aiblogapp ``` 選擇您首選的配置設定。在本教學中,我們將使用 TypeScript 和 Next.js App Router。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pae912scgh9tjfa75l8t.png) 接下來,安裝 Quill 富文本編輯器、Supabase 和 Langchain 軟體包及其相依性。 ``` npm install quill react-quill @supabase/supabase-js @supabase/ssr @supabase/auth-helpers-nextjs @langchain/langgraph ``` 最後,安裝 CopilotKit 軟體套件。這些套件使我們能夠從 React 狀態檢索資料並將 AI copilot 新增至應用程式。 ``` npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend ``` 恭喜!您現在已準備好建立由人工智慧驅動的部落格。 **建構部落格前端** ----------- 在本節中,我將引導您完成使用靜態內容建立部落格前端以定義部落格使用者介面的過程。 部落格的前端將由四個頁面組成:主頁、貼文頁面、建立貼文頁面和登入/註冊頁面。 首先,請在程式碼編輯器中前往`/[root]/src/app`並建立一個名為`components`的資料夾。在 Components 資料夾中,建立五個名為`Header.tsx` 、 `Posts.tsx` 、 `Post.tsx` 、 `Comment.tsx`和`QuillEditor.tsx`的文件 在`Header.tsx`檔案中,新增以下程式碼,定義一個名為`Header`的功能元件,該元件將呈現部落格的導覽列。 ``` "use client"; import Link from "next/link"; export default function Header() { return ( <> <header className="flex flex-wrap sm:justify-start sm:flex-nowrap z-50 w-full bg-gray-800 border-b border-gray-200 text-sm py-3 sm:py-0 "> <nav className="relative max-w-7xl w-full mx-auto px-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8" aria-label="Global"> <div className="flex items-center justify-between"> <Link className="flex-none text-xl text-white font-semibold " href="/" aria-label="Brand"> AIBlog </Link> </div> <div id="navbar-collapse-with-animation" className=""> <div className="flex flex-col gap-y-4 gap-x-0 mt-5 sm:flex-row sm:items-center sm:justify-end sm:gap-y-0 sm:gap-x-7 sm:mt-0 sm:ps-7"> <Link className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 " href="/createpost"> Create Post </Link> <form action={""}> <button className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 " type="submit"> Logout </button> </form> <Link className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 " href="/login"> Login </Link> </div> </div> </nav> </header> </> ); } ``` 在`Posts.tsx`檔案中,新增以下程式碼,定義一個名為`Posts`的功能元件,該元件呈現部落格平台主頁,該首頁將顯示已發佈文章的清單。 ``` "use client"; import React, { useEffect, useState } from "react"; import Image from "next/image"; import Link from "next/link"; export default function Posts() { const [articles, setArticles] = useState<any[]>([]); return ( <div className="max-w-[85rem] h-full px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto"> <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6"> <Link key={""} className="group flex flex-col h-full bg-gray-800 border border-gray-200 hover:border-transparent hover:shadow-lg transition-all duration-300 rounded-xl p-5 " href=""> <div className="aspect-w-16 aspect-h-11"> <Image className="object-cover h-48 w-96 rounded-xl" src={`https://source.unsplash.com/featured/?${encodeURIComponent( "Hello World" )}`} width={500} height={500} alt="Image Description" /> </div> <div className="my-6"> <h3 className="text-xl font-semibold text-white ">Hello World</h3> </div> </Link> </div> </div> ); } ``` 在`QuillEditor.tsx`檔案中,新增以下程式碼,用於動態匯入 QuillEditor 元件、定義 Quill 編輯器工具列的模組配置以及定義 Quill 編輯器的文字格式。 ``` // Import the dynamic function from the "next/dynamic" package import dynamic from "next/dynamic"; // Import the CSS styles for the Quill editor's "snow" theme import "react-quill/dist/quill.snow.css"; // Export a dynamically imported QuillEditor component export const QuillEditor = dynamic(() => import("react-quill"), { ssr: false }); // Define modules configuration for the Quill editor toolbar export const quillModules = { toolbar: [ // Specify headers with different levels [{ header: [1, 2, 3, false] }], // Specify formatting options like bold, italic, etc. ["bold", "italic", "underline", "strike", "blockquote"], // Specify list options: ordered and bullet [{ list: "ordered" }, { list: "bullet" }], // Specify options for links and images ["link", "image"], // Specify alignment options [{ align: [] }], // Specify color options [{ color: [] }], // Specify code block option ["code-block"], // Specify clean option for removing formatting ["clean"], ], }; // Define supported formats for the Quill editor export const quillFormats = [ "header", "bold", "italic", "underline", "strike", "blockquote", "list", "bullet", "link", "image", "align", "color", "code-block", ]; ``` 在`Post.tsx`檔案中,新增以下程式碼,定義一個名為`CreatePost`功能元件,該元件將用於呈現文章建立表單。 ``` "use client"; // Importing React hooks and components import { useRef, useState } from "react"; import { QuillEditor } from "./QuillEditor"; import { quillModules } from "./QuillEditor"; import { quillFormats } from "./QuillEditor"; import "react-quill/dist/quill.snow.css"; // Define the CreatePost component export default function CreatePost() { // Initialize state variables for article outline, copilot text, and article title const [articleOutline, setArticleOutline] = useState(""); const [copilotText, setCopilotText] = useState(""); const [articleTitle, setArticleTitle] = useState(""); // State variable to track if research task is running const [publishTaskRunning, setPublishTaskRunning] = useState(false); // Handle changes to the editor content const handleEditorChange = (newContent: any) => { setCopilotText(newContent); }; return ( <> {/* Main */} <div className="p-3 max-w-3xl mx-auto min-h-screen"> <h1 className="text-center text-white text-3xl my-7 font-semibold"> Create a post </h1> {/* Form for creating a post */} <form action={""} className="flex flex-col gap-4 mb-2 mt-2"> <div className="flex flex-col gap-4 sm:flex-row justify-between mb-2"> {/* Input field for article title */} <input type="text" id="title" name="title" placeholder="Title" value={articleTitle} onChange={(event) => setArticleTitle(event.target.value)} className="flex-1 block w-full rounded-lg border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500" /> </div> {/* Hidden textarea for article content */} <textarea className="p-4 w-full aspect-square font-bold text-xl bg-slate-800 text-white rounded-lg resize-none hidden" id="content" name="content" value={copilotText} placeholder="Write your article content here" onChange={(event) => setCopilotText(event.target.value)} /> {/* Quill editor component */} <QuillEditor onChange={handleEditorChange} modules={quillModules} formats={quillFormats} className="h-80 mb-12 text-white" /> {/* Submit button for publishing the post */} <button type="submit" disabled={publishTaskRunning} className={`bg-blue-500 text-white font-bold py-2 px-4 rounded ${ publishTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700" }`} onClick={async () => { try { setPublishTaskRunning(true); } finally { setPublishTaskRunning(false); } }}> {publishTaskRunning ? "Publishing..." : "Publish"} </button> </form> </div> </> ); } ``` 在`Comment.tsx`檔案中,新增以下程式碼,定義一個名為 Comment 的功能元件,該元件呈現貼文評論表單和貼文評論。 ``` // Client-side rendering "use client"; // Importing React and Next.js components import React, { useEffect, useRef, useState } from "react"; import Image from "next/image"; // Define the Comment component export default function Comment() { // State variables for comment, comments, and article content const [comment, setComment] = useState(""); const [comments, setComments] = useState<any[]>([]); const [articleContent, setArticleContent] = useState(""); return ( <div className="max-w-2xl mx-auto w-full p-3"> {/* Form for submitting a comment */} <form action={""} className="border border-teal-500 rounded-md p-3 mb-4"> {/* Textarea for entering a comment */} <textarea id="content" name="content" placeholder="Add a comment..." rows={3} onChange={(e) => setComment(e.target.value)} value={comment} className="hidden" /> {/* Submit button */} <div className="flex justify-between items-center mt-5"> <button type="submit" className="bg-blue-500 text-white font-bold py-2 px-4 rounded"> Submit </button> </div> </form> {/* Comments section */} <p className="text-white mb-2">Comments:</p> {/* Comment item (currently hardcoded) */} <div key={""} className="flex p-4 border-b dark:border-gray-600 text-sm"> <div className="flex-shrink-0 mr-3"> {/* Profile picture */} <Image className="w-10 h-10 rounded-full bg-gray-200" src={`(link unavailable){encodeURIComponent( "Silhouette" )}`} width={500} height={500} alt="Profile Picture" /> </div> <div className="flex-1"> <div className="flex items-center mb-1"> {/* Username (currently hardcoded as "Anonymous") */} <span className="font-bold text-white mr-1 text-xs truncate"> Anonymous </span> </div> {/* Comment text (currently hardcoded as "No Comments") */} <p className="text-gray-500 pb-2">No Comments</p> </div> </div> </div> ); } ``` 接下來,前往`/[root]/src/app`並建立一個名為`[slug]`的資料夾。在 \[slug\] 資料夾中,建立一個`page.tsx`檔案。 然後將以下程式碼新增至匯入`Comment`和`Header`元件的檔案中,並定義一個名為`Post`功能元件,該元件將呈現導覽列、貼文內容、評論表單和貼文評論。 ``` import Header from "../components/Header"; import Comment from "../components/Comment"; export default async function Post() { return ( <> <Header /> <main className="p-3 flex flex-col max-w-6xl mx-auto min-h-screen"> <h1 className="text-3xl text-white mt-10 p-3 text-center font-serif max-w-2xl mx-auto lg:text-4xl"> Hello World </h1> <div className="flex justify-between text-white p-3 border-b border-slate-500 mx-auto w-full max-w-2xl text-xs"> <span></span> <span className="italic">0 mins read</span> </div> <div className="p-3 max-w-2xl text-white mx-auto w-full post-content border-b border-slate-500 mb-2"> No Post Content </div> <Comment /> </main> </> ); } ``` 之後,轉到`/[root]/src/app`並建立一個名為`createpost`的資料夾。在`createpost`資料夾中,建立一個名為`page.tsx`檔案。 然後將以下程式碼新增至匯入`CreatePost`和`Header`元件的檔案中,並定義一個名為`WriteArticle`的功能元件,該元件將呈現導覽列和貼文建立表單。 ``` import CreatePost from "../components/Post"; import Header from "../components/Header"; import { redirect } from "next/navigation"; export default async function WriteArticle() { return ( <> <Header /> <CreatePost /> </> ); } ``` 接下來,前往`/[root]/src/page.tsx`文件,新增以下程式碼,導入`Posts`和`Header`元件並定義名為`Home`功能元件。 ``` import Header from "./components/Header"; import Posts from "./components/Posts"; export default async function Home() { return ( <> <Header /> <Posts /> </> ); } ``` 之後,請轉到`next.config.mjs`檔案並將其重新命名為`next.config.js` 。然後加入以下程式碼,允許您使用 Unsplash 中的圖像作為已發布文章的封面圖像。 ``` module.exports = { images: { remotePatterns: [ { protocol: "https", hostname: "source.unsplash.com", }, ], }, }; ``` 接下來,刪除 globals.css 檔案中的 CSS 程式碼並新增以下 CSS 程式碼。 ``` @tailwind base; @tailwind components; @tailwind utilities; body { height: 100vh; background-color: rgb(16, 23, 42); } .ql-editor { font-size: 1.25rem; } .post-content p { margin-bottom: 0.5rem; } .post-content h1 { font-size: 1.5rem; font-weight: 600; font-family: sans-serif; margin: 1.5rem 0; } .post-content h2 { font-size: 1.4rem; font-family: sans-serif; margin: 1.5rem 0; } .post-content a { color: rgb(73, 149, 199); text-decoration: none; } .post-content a:hover { text-decoration: underline; } ``` 最後,在命令列上執行命令`npm run dev` ,然後導航到 http://localhost:3000/。 現在您應該在瀏覽器上查看部落格平台前端,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz1qewlt9gkf2vbp5jqq.png) **使用 CopilotKit 將 AI 功能整合到部落格中** -------------------------------- 在本節中,您將學習如何為部落格加入 AI 副駕駛,以使用 CopilotKit 執行部落格主題研究和內容自動建議。 CopilotKit 提供前端和[後端](https://docs.copilotkit.ai/getting-started/quickstart-backend)套件。它們使您能夠插入 React 狀態並使用 AI 代理在後端處理應用程式資料。 首先,讓我們將 CopilotKit React 元件加入到部落格前端。 ### **將 CopilotKit 新增至部落格前端** 在這裡,我將引導您完成將部落格與 CopilotKit 前端整合的過程,以方便部落格文章研究和文章大綱生成。 首先,使用下面的程式碼片段在`/[root]/src/app/components/Post.tsx`檔案頂部匯入`useMakeCopilotReadable` 、 `useCopilotAction` 、 `CopilotTextarea`和`HTMLCopilotTextAreaElement`自訂掛鉤。 ``` import { useMakeCopilotReadable, useCopilotAction, } from "@copilotkit/react-core"; import { CopilotTextarea, HTMLCopilotTextAreaElement, } from "@copilotkit/react-textarea"; ``` 在 CreatePost 函數內的狀態變數下方,加入以下程式碼,該程式碼使用`useMakeCopilotReadable`掛鉤來新增將作為應用程式內聊天機器人的上下文產生的文章大綱。鉤子使副駕駛可以閱讀文章大綱。 ``` useMakeCopilotReadable( "Blog article outline: " + JSON.stringify(articleOutline) ); ``` 在`useMakeCopilotReadable`掛鉤下方,使用以下程式碼建立一個名為`copilotTextareaRef`的引用,該引用指向名為`HTMLCopilotTextAreaElement`的文字區域元素。 ``` const copilotTextareaRef = useRef<HTMLCopilotTextAreaElement>(null); ``` 在上面的程式碼下方,加入以下程式碼,該程式碼使用`useCopilotAction`掛鉤來設定名為`researchBlogArticleTopic`的操作,該操作將啟用對部落格文章的給定主題的研究。 此操作採用兩個參數,稱為`articleTitle`和`articleOutline` ,這兩個參數可以產生文章標題和大綱。 該操作包含一個處理程序函數,該函數根據給定主題生成文章標題和大綱。 在處理函數內部, `articleOutline`狀態會使用新產生的大綱進行更新,而`articleTitle`狀態會使用新產生的標題進行更新,如下所示。 ``` // Define a Copilot action useCopilotAction( { // Action name and description name: "researchBlogArticleTopic", description: "Research a given topic for a blog article.", // Parameters for the action parameters: [ { // Parameter 1: articleTitle name: "articleTitle", type: "string", description: "Title for a blog article.", required: true, // This parameter is required }, { // Parameter 2: articleOutline name: "articleOutline", type: "string", description: "Outline for a blog article that shows what the article covers.", required: true, // This parameter is required }, ], // Handler function for the action handler: async ({ articleOutline, articleTitle }) => { // Set the article outline and title using state setters setArticleOutline(articleOutline); setArticleTitle(articleTitle); }, }, [] // Dependencies (empty array means no dependencies) ); ``` 在上面的程式碼下方,前往表單元件並新增以下`CopilotTextarea`元件,該元件可讓您為文章內容新增文字完成、插入和編輯。 ``` <CopilotTextarea className="p-4 h-72 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none" ref={copilotTextareaRef} placeholder="Start typing for content autosuggestion." value={articleOutline} rows={5} autosuggestionsConfig={{ textareaPurpose: articleTitle, chatApiConfigs: { suggestionsApiConfig: { forwardedParams: { max_tokens: 5, stop: ["\n", ".", ","], }, }, insertionApiConfig: {}, }, debounceTime: 250, }} /> ``` 之後,請前往`/[root]/src/app/createpost/page.tsx`檔案並使用下面的程式碼匯入頂部的 CopilotKit 前端套件和樣式。 ``` import { CopilotKit } from "@copilotkit/react-core"; import { CopilotPopup } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; ``` 然後使用`CopilotKit`包裝`CopilPopup`和`CreatePost`元件,如下圖所示。 `CopilotKit`元件指定 CopilotKit 後端端點 ( `/api/copilotkit/` ) 的 URL,而`CopilotPopup`則呈現應用程式內聊天機器人,您可以提示您研究文章的任何主題。 ``` export default async function WriteArticle() { return ( <> <Header /> <CopilotKit url="/api/copilotkit"> <CopilotPopup instructions="Help the user research a blog article topic." defaultOpen={true} labels={{ title: "Blog Article Research AI Assistant", initial: "Hi! 👋 I can help you research any topic for a blog article.", }} /> <CreatePost /> </CopilotKit> </> ); } ``` 之後,使用下面的程式碼片段在`/[root]/src/app/components/Comment.tsx`檔案頂部導入`useMakeCopilotReadable` 、 `CopilotKit` 、 `CopilotTextarea`和`HTMLCopilotTextAreaElement`自訂掛鉤。 ``` import { useMakeCopilotReadable, CopilotKit } from "@copilotkit/react-core"; import { CopilotTextarea, HTMLCopilotTextAreaElement, } from "@copilotkit/react-textarea"; ``` 在 Comment 函數內的狀態變數下方,新增以下程式碼,程式碼使用`useMakeCopilotReadable`掛鉤將貼文內容新增為評論內容自動建議的上下文。 ``` useMakeCopilotReadable( "Blog article content: " + JSON.stringify(articleContent) ); const copilotTextareaRef = useRef<HTMLCopilotTextAreaElement>(null); ``` 然後將CopilotTextarea元件加入評論表單中,並使用`CopilotKit`包裹表單,如下所示。 ``` <CopilotKit url="/api/copilotkit"> <form action={""} className="border border-teal-500 rounded-md p-3 mb-4"> <textarea id="content" name="content" placeholder="Add a comment..." rows={3} onChange={(e) => setComment(e.target.value)} value={comment} className="hidden" /> <CopilotTextarea className="p-4 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none" ref={copilotTextareaRef} placeholder="Start typing for content autosuggestion." onChange={(event) => setComment(event.target.value)} rows={5} autosuggestionsConfig={{ textareaPurpose: articleContent, chatApiConfigs: { suggestionsApiConfig: { forwardedParams: { max_tokens: 5, stop: ["\n", ".", ","], }, }, insertionApiConfig: {}, }, debounceTime: 250, }} /> <div className="flex justify-between items-center mt-5"> <button type="submit" className="bg-blue-500 text-white font-bold py-2 px-4 rounded"> Submit </button> </div> </form> </CopilotKit> ``` 之後,執行開發伺服器並導航到 http://localhost:3000/createpost。您應該會看到彈出的應用程式內聊天機器人和 CopilotTextarea 已整合到部落格中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0j5ly5l5cf0ldaf3hwgx.png) 恭喜!您已成功將 CopilotKit 新增至部落格前端**。** ### **將 CopilotKit 後端加入博客** 在這裡,我將引導您完成部落格與 CopilotKit 後端的整合過程,CopilotKit 後端處理來自前端的請求,並提供函數呼叫和各種 LLM 後端(例如 GPT)。 此外,我們將整合一個名為 Tavily 的人工智慧代理,它可以研究網路上的任何主題。 首先,在根目錄中建立一個名為`.env.local`的檔案。然後在保存`ChatGPT`和`Tavily` Search API 金鑰的檔案中加入下面的環境變數。 ``` OPENAI_API_KEY="Your ChatGPT API key" TAVILY_API_KEY="Your Tavily Search API key" ``` 若要取得 ChatGPT API 金鑰,請導覽至 https://platform.openai.com/api-keys。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ei04w6fadsmalbjaocun.png) 若要取得 Tavilly Search API 金鑰,請導覽至 https://app.tavily.com/home ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqbqb5wvrnkv1vc8z7hf.png) 之後,轉到`/[root]/src/app`並建立一個名為`api`的資料夾。在`api`資料夾中,建立一個名為`copilotkit`的資料夾。 在`copilotkit`資料夾中,建立一個名為`research.ts`的檔案。然後導航到[該 Research.ts gist 文件](https://gist.github.com/TheGreatBonnie/58dc21ebbeeb8cbb08df665db762738c),複製程式碼,並將其新增至**`research.ts`**檔案中 接下來,在`/[root]/src/app/api/copilotkit`資料夾中建立一個名為`route.ts`的檔案。該文件將包含設定後端功能來處理 POST 請求的程式碼。它有條件地包括對給定主題進行研究的“研究”操作。 現在在文件頂部導入以下模組。 ``` import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend"; // For backend functionality with CopilotKit. import { researchWithLangGraph } from "./research"; // Import a custom function for conducting research. import { AnnotatedFunction } from "@copilotkit/shared"; // For annotating functions with metadata. ``` 在上面的程式碼下面,定義一個執行時間環境變數和一個名為`researchAction`的函數,該函數使用下面的程式碼研究某個主題。 ``` // Define a runtime environment variable, indicating the environment where the code is expected to run. export const runtime = "edge"; // Define an annotated function for research. This object includes metadata and an implementation for the function. const researchAction: AnnotatedFunction<any> = { name: "research", // Function name. description: "Call this function to conduct research on a certain topic. Respect other notes about when to call this function", // Function description. argumentAnnotations: [ // Annotations for arguments that the function accepts. { name: "topic", // Argument name. type: "string", // Argument type. description: "The topic to research. 5 characters or longer.", // Argument description. required: true, // Indicates that the argument is required. }, ], implementation: async (topic) => { // The actual function implementation. console.log("Researching topic: ", topic); // Log the research topic. return await researchWithLangGraph(topic); // Call the research function and return its result. }, }; ``` 然後在上面的程式碼下加入下面的程式碼來定義處理POST請求的非同步函數。 ``` // Define an asynchronous function that handles POST requests. export async function POST(req: Request): Promise<Response> { const actions: AnnotatedFunction<any>[] = []; // Initialize an array to hold actions. // Check if a specific environment variable is set, indicating access to certain functionality. if (process.env["TAVILY_API_KEY"]) { actions.push(researchAction); // Add the research action to the actions array if the condition is true. } // Instantiate CopilotBackend with the actions defined above. const copilotKit = new CopilotBackend({ actions: actions, }); // Use the CopilotBackend instance to generate a response for the incoming request using an OpenAIAdapter. return copilotKit.response(req, new OpenAIAdapter()); } ``` 恭喜!您已成功將 CopilotKit 後端新增至 Blog **。** **使用 Supabase 將資料庫整合到博客** ------------------------- 在本節中,我將引導您完成將部落格與 Supabase 資料庫整合以插入和獲取部落格文章和評論資料的過程。 首先,導覽至[supabase.com](http://supabase.com/)並點擊主頁上的「啟動您的專案」按鈕。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qu4h1xuhvkbtttqvry1o.png) 然後新建一個專案,名為AiBloggingPlatform,如下圖所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01uq8s2l9yoa6cvpmocl.png) 建立專案後,將 Supabase URL 和 API 金鑰新增至`env.local`檔案中的環境變數中,如下所示。 ``` NEXT_PUBLIC_SUPABASE_URL=”Your Supabase URL” NEXT_PUBLIC_SUPABASE_ANON_KEY=”Your Supabase API Key” ``` ### **為部落格設定 Supabase 身份驗證** 在這裡,我將引導您完成為部落格設定身份驗證的過程,使用戶能夠註冊、登入或登出。 首先,前往`/[root]/src/`並建立一個名為`utils`資料夾。在`utils`資料夾中,建立一個名為`supabase`的資料夾。 然後在`supabase`資料夾中建立兩個名為`client.ts`和`server.ts`檔案。 之後,導航到此[supabase 文件連結](https://supabase.com/docs/guides/auth/server-side/nextjs#:~:text=Write%20utility%20functions%20to%20create%20Supabase%20clients),複製此處提供的程式碼,並將其貼上到您在`supabase`資料夾中建立的相應文件中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d99g065wf471fag7rbjs.png) 接下來,在專案根目錄中建立一個名為`middleware.ts`的文件,並在`/[root]/src/utils/supabase`資料夾中建立另一個同名文件。 之後,導航到此[supabase 文件連結](https://supabase.com/docs/guides/auth/server-side/nextjs#:~:text=4-,Hook%20up%20middleware,-Create%20a%20middleware),複製此處提供的程式碼,並將其貼上到相應的`middleware.ts`文件中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo21y007yjpw6w50xb9l.png) 接下來,前往`/[root]/src/app/login`資料夾並建立一個名為`actions.ts`的檔案。之後,導航到此[supabase 文件連結](https://supabase.com/docs/guides/auth/server-side/nextjs#:~:text=5-,Create%20a%20login%20page,-Create%20a%20login),複製此處提供的程式碼,並將其貼上到`actions.ts` 。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q1ql5mf0dhqyyli9dipm.png) 之後,更改電子郵件範本以支援身份驗證流程。為此,請前往 Supabase 儀表板中的[驗證範本](https://supabase.com/dashboard/project/_/auth/templates)頁面。 在`Confirm signup`模板中,將`{{ .ConfirmationURL }}`更改為`{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=signup`如下所示,然後按一下儲存按鈕。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9n8n4wdycoqnx3l41rn.png) 之後,建立路由處理程序以使用電子郵件進行身份驗證確認**。**為此\*\*,請前往\*\* `/[root]/src/app/`並建立一個名為`auth`的資料夾。然後在`auth`資料夾中建立一個名為`confirm`資料夾。 在`confirm`資料夾中,建立一個名為`route.ts`檔案並導航到此[supabase文件連結](https://supabase.com/docs/guides/auth/server-side/nextjs#:~:text=Create%20a%20route%20handler%20for%20Auth%20confirmation),複製那裡提供的程式碼,並將其貼到`route.ts`檔案中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mws1datksq4dml3a8z8n.png) 之後,請前往`/[root]/src/app/login/page.tsx`檔案並使用下面的程式碼片段匯入 Supabase `signup`和`login`功能。 ``` import { login, signup } from "./actions"; ``` 在註冊/登入表單中,按一下登入和註冊按鈕時,使用 formAction 呼叫 Supabase `signup`和`login`函數,如下所示。 ``` <button className="bg-blue-500 text-white font-bold py-2 px-4 rounded" formAction={login}> Log in </button> <button className="bg-blue-500 text-white font-bold py-2 px-4 rounded" formAction={signup}> Sign up </button> ``` 之後,執行開發伺服器並導航至[http://localhost:3000/login](http://localhost:3000/createpost) 。如下所示新增電子郵件和密碼,然後按一下「註冊」按鈕。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/37ee0ii966uhhpi8lqz8.png) 然後轉到您用於註冊的電子郵件的收件匣,然後按一下確認您的電子郵件按鈕,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekukm36uhh833uuhg1k3.png) 之後,請前往 Supabase 儀表板中的[「身份驗證用戶」](https://supabase.com/dashboard/project/_/auth/users)頁面,您應該會看到新建立的用戶,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qmv02jptpsn9k06ytkri.png) 接下來,設定註銷功能。為此,請前往`/[root]/src/app`並建立一個名為`logout`的資料夾。然後建立一個名為`route.ts`的檔案並將以下程式碼加入該檔案。 ``` // Server-side code "use server"; // Importing Next.js functions import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; // Importing Supabase client creation function from utils import { createClient } from "@/utils/supabase/server"; // Exporting the logout function export async function logout() { // Creating a Supabase client instance const supabase = createClient(); // Signing out of Supabase auth const { error } = await supabase.auth.signOut(); // If there's an error, redirect to the error page if (error) { redirect("/error"); } // Revalidate the "/" path for the "layout" cache revalidatePath("/", "layout"); // Redirect to the homepage redirect("/"); } ``` 之後,請前往`/[root]/src/app/components/Header.tsx`檔案並使用下面的程式碼片段匯入 Supabase 登出功能。 ``` import { logout } from "../logout/actions"; ``` 然後在action參數中加入註銷功能,如下所示。 ``` <form action={logout}> <button className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 " type="submit"> Logout </button> </form> ``` 如果按一下「登出」按鈕,已登入的使用者將被登出。 ### **設定部落格的使用者角色和權限** 在這裡,我將引導您完成設定使用者角色和權限的過程,以控制不同使用者可以在部落格上執行的操作。 首先,前往`/[root]/src/app/components/Header.tsx`檔案並導入 Supabase createClient 函數。 ``` import { createClient } from "@/utils/supabase/client"; ``` 然後導入`useState`和`useEffect`掛鉤,並使用下面的程式碼片段定義一個名為 user 的類型。 ``` import { useEffect, useState } from "react"; type User = {}; ``` 在 Header 功能元件內,新增以下程式碼,程式碼使用`useState`掛鉤來儲存`user`和`admin`資料,並使用`useEffect`掛鉤在元件安裝時從 Supabase auth 取得使用者資料。 `getUser`函數檢查錯誤並相應地設定使用者和管理員狀態。 ``` // State variables for user and admin const [user, setUser] = useState<User | null>(null); const [admin, setAdmin] = useState<User | null>(null); // useEffect hook to fetch user data on mount useEffect(() => { // Define an async function to get the user async function getUser() { // Create a Supabase client instance const supabase = createClient(); // Get the user data from Supabase auth const { data, error } = await supabase.auth.getUser(); // If there's an error or no user data, log a message if (error || !data?.user) { console.log("No User"); } // If user data is available, set the user state else { setUser(data.user); } // Define the email of the signed-up user const userEmail = "email of signed-up user"; // Check if the user is an admin (email matches) if (!data?.user || data.user?.email !== userEmail) { console.log("No Admin"); } // If the user is an admin, set the admin state else { setAdmin(data.user); } } // Call the getUser function getUser(); }, []); // Dependency array is empty, so the effect runs only once on mount ``` 之後,更新導覽列程式碼,如下所示。更新後的程式碼會根據是否有登入使用者或登入使用者是管理員來控制將呈現哪些按鈕。 ``` <div id="navbar-collapse-with-animation" className=""> {/* Navbar content container */} <div className="flex flex-col gap-y-4 gap-x-0 mt-5 sm:flex-row sm:items-center sm:justify-end sm:gap-y-0 sm:gap-x-7 sm:mt-0 sm:ps-7"> {/* Conditional rendering for admin link */} {admin ? ( // If admin is true, show the "Create Post" link <Link className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 " href="/createpost"> Create Post </Link> ) : ( // If admin is false, render an empty div <div></div> )} {/* Conditional rendering for user link/logout button */} {user ? ( // If user is true, show the logout button <form action={logout}> <button className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 " type="submit"> Logout </button> </form> ) : ( // If user is false, show the "Login" link <Link className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 " href="/login"> Login </Link> )} </div> </div> ``` 如果導航到[http://localhost:3000,](http://localhost:3000/)您應該會看到僅呈現「建立貼文」和「登出」按鈕,因為使用者已登入並設定為管理員。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdhh0x1ln8xl9y1tq7ua.png) 之後,請前往`/[root]/src/app/createpost/page.tsx`檔案並匯入 Supabase createClient 函數。 ``` import { createClient } from "@/utils/supabase/client"; ``` 在 WriteArticle 功能元件中,新增以下程式碼,使用 Supabase createClient 函數取得登入用戶,並驗證用戶的電子郵件是否與設定為管理員的用戶的電子郵件相同。 ``` // Define the email of the user you want to make admin const userEmail = "email of admin user"; // Create a Supabase client instance const supabase = createClient(); // Get the user data from Supabase auth const { data, error } = await supabase.auth.getUser(); // Check for errors or if the user data doesn't match the expected email if (error || !data?.user || data?.user.email !== userEmail) { // If any of the conditions are true, redirect to the homepage redirect("/"); } ``` 現在只有設定為admin的使用者才能存取http://localhost:3000/createpost頁面,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vrnvib7ikzsgt8webrz.png) ### **使用 Supabase 功能設定插入和取得發布資料** 在這裡,我將引導您完成使用 Supabase 資料庫向部落格設定插入和取得資料功能的過程。 首先,請前往 Supabase 儀表板中的[SQL 編輯器](https://supabase.com/dashboard/project/_/sql/new)頁面。然後將下列 SQL 程式碼新增至編輯器中,然後按一下 CTRL + Enter 鍵建立一個名為articles 的表。 articles 表包含 id、title、slug、content 和created\_at 欄位。 ``` create table if not exists articles ( id bigint primary key generated always as identity, title text, slug text, content text, created_at timestamp ); ``` 建立表格後,您應該會收到一條成功訊息,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dd3xgg0o8ygztkab3cir.png) 之後,請前往`/[root]/src/utils/supabase`資料夾並建立一個名為`AddArticle.ts`的檔案。然後將以下程式碼新增至該檔案中,將部落格文章資料插入 Supabase 資料庫。 ``` // Server-side code "use server"; // Importing Supabase auth helpers and Next.js functions import { createServerComponentClient } from "@supabase/auth-helpers-nextjs"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; // Exporting the addArticle function export async function addArticle(formData: any) { // Extracting form data const title = formData.get("title"); const content = formData.get("content"); const slug = formData .get("title") .split(" ") .join("-") .toLowerCase() .replace(/[^a-zA-Z0-9-]/g, ""); // Generating a slug from the title const created_at = formData.get(new Date().toDateString()); // Getting the current date // Creating a cookie store const cookieStore = cookies(); // Creating a Supabase client instance with cookie store const supabase = createServerComponentClient({ cookies: () => cookieStore }); // Inserting data into the "articles" table const { data, error } = await supabase.from("articles").insert([ { title, content, slug, created_at, }, ]); // Handling errors if (error) { console.error("Error inserting data", error); return; } // Redirecting to the homepage redirect("/"); // Returning a success message return { message: "Success" }; } ``` 接下來,前往`/[root]/src/app/components/Post.tsx`檔案並導入 addArticle 函數。 ``` import { addArticle } from "@/utils/supabase/AddArticle"; ``` 然後加入`addArticle`函數作為表單動作參數,如下所示。 ``` <form action={addArticle} className="w-full h-full gap-10 flex flex-col items-center p-10"> </form> ``` 之後,導覽至 http://localhost:3000/createpost 並向右側的聊天機器人提供類似「研究有關 JavaScript 框架的部落格文章主題」的提示。 聊天機器人將開始研究該主題,然後產生部落格標題和大綱,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w3i5ndwct44094iczrpo.png) 當您開始在 CopilotKitTextarea 上書寫時,您應該會看到內容自動建議,如下所示。 {% 嵌入 https://youtu.be/2oMNV1acKIs %} 如果內容符合您的喜好,請將其複製並貼上到 Quill 富文本編輯器。然後開始編輯它,如下所示。 {% 嵌入 https://youtu.be/-r3woCeq4vs %} 然後點擊底部的發布按鈕即可發布文章。前往 Supabase 上專案的儀表板並導航至「表格編輯器」部分。點擊文章表,您應該會看到您的文章資料已插入到 Supabase 資料庫中,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4umd2qijpdt9z3evfkj.png) 接下來,前往`/[root]/src/app/components/Posts.tsx`檔案並導入 createClient 函數。 ``` import { createClient } from "@/utils/supabase/client"; ``` 在 Posts 功能元件內,加入以下程式碼,該程式碼使用 useState 掛鉤來儲存文章資料,並使用 useEffect 掛鉤在元件安裝時從 Supabase 取得文章。 fetchArticles 函數建立一個 Supabase 用戶端實例,取得文章,並在資料可用時更新狀態。 ``` // State variable for articles const [articles, setArticles] = useState<any[]>([]); // useEffect hook to fetch articles on mount useEffect(() => { // Define an async function to fetch articles const fetchArticles = async () => { // Create a Supabase client instance const supabase = createClient(); // Fetch articles from the "articles" table const { data, error } = await supabase.from("articles").select("*"); // If data is available, update the articles state if (data) { setArticles(data); } }; // Call the fetchArticles function fetchArticles(); }, []); // Dependency array is empty, so the effect runs only once on mount ``` 之後,如下所示更新元素程式碼,以在部落格主頁上呈現已發佈的文章。 ``` // Return a div element with a max width, full height, padding, and horizontal margin return ( <div className="max-w-[85rem] h-full px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto"> // Create a grid container with dynamic number of columns based on screen size <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6"> // Map over the articles array and render a Link component for each item {articles?.map((post) => ( <Link // Assign a unique key prop to each Link component key={post.id} // Apply styles for the Link component className="group flex flex-col h-full bg-gray-800 border border-gray-200 hover:border-transparent hover:shadow-lg transition-all duration-300 rounded-xl p-5 " // Set the href prop to the post slug href={`/${post.slug}`}> // Create a container for the image <div className="aspect-w-16 aspect-h-11"> // Render an Image component with a dynamic src based on the post title <Image className="object-cover h-48 w-96 rounded-xl" src={`(link unavailable){encodeURIComponent( post.title )}`} // Set the width and height props for the Image component width={500} height={500} // Set the alt prop for accessibility alt="Image Description" /> </div> // Create a container for the post title <div className="my-6"> // Render an h3 element with the post title <h3 className="text-xl font-semibold text-white "> {post.title} </h3> </div> </Link> ))} </div> </div> ); ``` 然後導航到<http://localhost:3000> ,您應該會看到您發布的文章,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpwvh0b3cm7dfrgwixtv.png) 接下來,前往`/[root]/src/app/[slug]/page.tsx`檔案並匯入 createClient 函數。 ``` import { createClient } from "@/utils/supabase/client"; ``` 在導入下方,定義一個名為「getArticleContent」的非同步函數,該函數根據 slug 參數從 supabase 資料庫檢索文章資料,如下所示。 ``` // Define an asynchronous function to retrieve article content async function getArticleContent(params: any) { // Extract the slug parameter from the input params object const { slug } = params; // Create a Supabase client instance const supabase = createClient(); // Query the "articles" table in Supabase // Select all columns (*) // Filter by the slug column matching the input slug // Retrieve a single record (not an array) const { data, error } = await supabase .from("articles") .select("*") .eq("slug", slug) .single(); // Return the retrieved article data return data; } ``` 之後,如下所示更新功能元件Post,以渲染文章內容。 ``` export default async function Post({ params }: { params: any }) { // Fetch the post content using the getArticleContent function const post = await getArticleContent(params); // Return the post component return ( // Fragment component to wrap multiple elements <> // Header component <Header /> // Main container with max width and height <main className="p-3 flex flex-col max-w-6xl mx-auto min-h-screen"> // Post title <h1 className="text-3xl text-white mt-10 p-3 text-center font-serif max-w-2xl mx-auto lg:text-4xl"> {post && post.title} // Display post title if available </h1> // Post metadata (author, date, etc.) <div className="flex justify-between text-white p-3 border-b border-slate-500 mx-auto w-full max-w-2xl text-xs"> <span></span> // Estimated reading time <span className="italic"> {post && (post.content.length / 1000).toFixed(0)} mins read </span> </div> // Post content <div className="p-3 max-w-2xl text-white mx-auto w-full post-content border-b border-slate-500 mb-2" // Use dangerouslySetInnerHTML to render HTML content dangerouslySetInnerHTML={{ __html: post && post.content }}></div> // Comment component <Comment /> </main> </> ); } ``` 導覽至<http://localhost:3000>並點擊部落格主頁上顯示的文章。然後您應該被重定向到文章的內容,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byfdvwr7anceq09gto1g.png) ### **使用 Supabase 功能設定插入和取得評論資料** 在這裡,我將引導您完成使用 Supabase 資料庫為部落格內容評論設定插入和獲取資料功能的過程。 首先,請前往 Supabase 儀表板中的[SQL 編輯器](https://supabase.com/dashboard/project/_/sql/new)頁面。然後將以下 SQL 程式碼新增至編輯器中,然後按一下 CTRL + Enter 鍵建立一個名為 comments 的表。評論表有 id、content 和 postId 欄位。 ``` create table if not exists comments ( id bigint primary key generated always as identity, postId text, content text, ); ``` 建立表格後,您應該會收到一條成功訊息,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5h05jkk1g33ewtupsb5a.png) 之後,請前往`/[root]/src/utils/supabase`資料夾並建立一個名為`AddComment.ts`的檔案。然後將以下程式碼新增至該檔案中,將部落格文章評論資料插入 Supabase 資料庫。 ``` // Importing necessary functions and modules for server-side operations "use server"; import { createServerComponentClient } from "@supabase/auth-helpers-nextjs"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; // Define an asynchronous function named 'addComment' that takes form data as input export async function addComment(formData: any) { // Extract postId and content from the provided form data const postId = formData.get("postId"); const content = formData.get("content"); // Retrieve cookies from the HTTP headers const cookieStore = cookies(); // Create a Supabase client configured with the provided cookies const supabase = createServerComponentClient({ cookies: () => cookieStore }); // Insert the article data into the 'comments' table on Supabase const { data, error } = await supabase.from("comments").insert([ { postId, content, }, ]); // Check for errors during the insertion process if (error) { console.error("Error inserting data", error); return; } // Redirect the user to the home page after successfully adding the article redirect("/"); // Return a success message return { message: "Success" }; } ``` 接下來,到`/[root]/src/app/components/Comment.tsx`文件,導入 addArticle createClient 函數。 ``` import { addComment } from "@/utils/supabase/AddComment"; import { createClient } from "@/utils/supabase/client"; ``` 然後將postId作為prop參數加入Comment功能元件中。 ``` export default function Comment({ postId }: { postId: any }) {} ``` 在函數內部,新增以下程式碼,程式碼使用`useEffect`掛鉤在元件安裝或`postId`更改時從 Supabase 取得評論和文章內容。 `fetchComments`函數取得所有評論,而`fetchArticleContent`函數則取得具有目前`postId`的文章內容。 ``` useEffect(() => { // Define an async function to fetch comments const fetchComments = async () => { // Create a Supabase client instance const supabase = createClient(); // Fetch comments from the "comments" table const { data, error } = await supabase.from("comments").select("*"); // If data is available, update the comments state if (data) { setComments(data); } }; // Define an async function to fetch article content const fetchArticleContent = async () => { // Create a Supabase client instance const supabase = createClient(); // Fetch article content from the "articles" table // Filter by the current postId const { data, error } = await supabase .from("articles") .select("*") .eq("id", postId) .single(); // If the fetched article ID matches the current postId if (data?.id == postId) { // Update the article content state setArticleContent(data.content); } }; // Call the fetch functions fetchArticleContent(); fetchComments(); }, [postId]); // Dependency array includes postId, so the effect runs when postId changes ``` 然後新增`addComment`函數作為表單操作參數,如下所示。 ``` <form action={addComment} className="border border-teal-500 rounded-md p-3 mb-4"> <textarea id="content" name="content" placeholder="Add a comment..." rows={3} onChange={(e) => setComment(e.target.value)} value={comment} className="hidden" /> <CopilotTextarea className="p-4 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none" ref={copilotTextareaRef} placeholder="Start typing for content autosuggestion." onChange={(event) => setComment(event.target.value)} rows={5} autosuggestionsConfig={{ textareaPurpose: articleContent, chatApiConfigs: { suggestionsApiConfig: { forwardedParams: { max_tokens: 5, stop: ["\n", ".", ","], }, }, insertionApiConfig: {}, }, debounceTime: 250, }} /> <input type="text" id="postId" name="postId" value={postId} className="hidden" /> <div className="flex justify-between items-center mt-5"> <button type="submit" className="bg-blue-500 text-white font-bold py-2 px-4 rounded"> Submit </button> </div> </form> ``` 在表單元素下方,新增以下用於呈現貼文評論的程式碼。 ``` {comments?.map( (postComment: any) => postComment.postId == postId && ( <div key={postComment.id} className="flex p-4 border-b dark:border-gray-600 text-sm"> <div className="flex-shrink-0 mr-3"> <Image className="w-10 h-10 rounded-full bg-gray-200" src={`https://source.unsplash.com/featured/?${encodeURIComponent( "Silhouette" )}`} width={500} height={500} alt="Profile Picture" /> </div> <div className="flex-1"> <div className="flex items-center mb-1"> <span className="font-bold text-white mr-1 text-xs truncate"> Anonymous </span> </div> <p className="text-gray-500 pb-2">{postComment.content}</p> </div> </div> ) )} ``` 接下來,前往`/[root]/src/app/[slug]/page.tsx`檔案並將 postId 作為 prop 新增至 Comment 元件。 ``` <Comment postId={post && [post.id](http://post.id/)} /> ``` 前往已發佈的文章內容頁面並開始在文字區域中輸入評論。您應該在鍵入時獲得內容自動建議。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m6br70wf3mhxzovgfet.png) 然後點擊提交按鈕以加入您的評論。前往 Supabase 上專案的儀表板並導航到表格編輯器部分。點擊評論表,您應該會看到您的評論資料已插入到 Supabase 資料庫中,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3j9c6cy82cw9fcao2pe9.png) 返回您發表評論的文章內容頁面,您應該會看到您的評論,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n98o9kxqnyzm90tzvw40.png) 恭喜!您已完成本教學的專案。 結論 -- [CopilotKit](https://copilotkit.ai/)是一款令人難以置信的工具,可讓您在幾分鐘內將 AI Copilot 加入到您的產品中。無論您是對人工智慧聊天機器人和助理感興趣,還是對複雜任務的自動化感興趣,CopilotKit 都能讓您輕鬆實現。 如果您需要建立 AI 產品或將 AI 工具整合到您的軟體應用程式中,您應該考慮 CopilotKit。 您可以在 GitHub 上找到本教學的源程式碼:https://github.com/TheGreatBonnie/aiblogapp --- 原文出處:https://dev.to/copilotkit/im-building-an-ai-powered-blog-nextjs-langchain-supabase-5145

每個前端開發人員都應該了解的軟體工程原理

作為前端開發人員,我們通常專注於建立漂亮的使用者介面。然而,重要的是要記住,美麗也在於內部,像素完美的方法也應該轉化為我們的程式碼組織和結構。在本文中,我們將探討每個前端開發人員都應該了解並在其專案中應用的一些基本軟體工程原理。 1. DRY(不要重複) ------------ DRY 原則強調程式碼可重複使用性和維護的重要性。透過將通用功能提取到可重複使用元件、函數或模組中來避免重複程式碼。透過堅持 DRY 原則,您可以減少程式碼重複,提高可維護性,並使您的程式碼庫更加模組化。 React 鼓勵元件驅動的架構,其中職責被隔離,以便於未來的開發和可擴展性。 我們以一個簡單的電子商務應用程式中的產品頁面為例。我們希望看到待售產品清單。我們可以將頁面分解為更小的、可重複使用的元件。 成分: 1. 產品卡:顯示單一產品及其名稱、價格和描述。 2. 產品清單:顯示產品清單。 ``` // ProductCard.js import React from 'react'; const ProductCard = ({ product }) => { return ( <div> <h2>{product.name}</h2> <p>Price: ${product.price}</p> <p>Description: {product.description}</p> </div> ); }; export default ProductCard; ``` ``` // ProductList.js import React, { useState } from 'react'; import ProductCard from './ProductCard'; const ProductList = () => { const [products, setProducts] = useState([ { id: 1, name: 'Product 1', price: 9.99, description: 'Description 1' }, { id: 2, name: 'Product 2', price: 19.99, description: 'Description 2' }, // ... ]); return ( <div> {products.map((product) => ( <ProductCard key={product.id} product={product} /> ))} </div> ); }; export default ProductList; ``` 在此範例中,我們看到,透過將與產品相關的邏輯分離到`ProductCard`元件中,我們可以在`ProductList`元件的`map`功能中重複使用它,並避免清單頁面中每個產品專案的重複程式碼。 2. SOLID 原則 -------- SOLID 是一個縮寫詞,代表物件導向設計的五個關鍵原則: - **單一**責任原則 (SRP):每個模組或類別應該只有一個更改理由。 - **開放**/封閉原則(OCP):軟體實體應該對擴展開放,對修改關閉。 - **李斯科夫**替換原則(LSP):子類型應該可以替換其基本類型,而不改變程式的正確性。 - **介面**隔離原則 (ISP):不應強迫客戶端依賴他們不使用的介面。 - **依賴**倒置原則(DIP):高層模組不應該依賴低層模組。兩者都應該依賴抽象。 讓我們來看看如何在 React TypeScript 元件中應用里氏替換原則 (LSP): ``` // Vehicle.ts interface Vehicle { drive(): void; name: string; } // Car.ts class Car implements Vehicle { constructor(private name: string) { this.name = name; } drive(): void { console.log(`Driving a ${this.name}`); } } // Motorcycle.ts class Motorcycle implements Vehicle { constructor(private name: string) { this.name = name; } drive(): void { console.log(`Riding a ${this.name}`); } } // App.tsx import React from 'react'; import { Vehicle } from './Vehicle'; import Car from './Car'; import Motorcycle from './Motorcycle'; function VehicleComponent(props: { vehicle: Vehicle }) { props.vehicle.drive(); return <div>Driving a {props.vehicle.name}</div>; } const App = () => { const car = new Car(); const motorcycle = new Motorcycle(); return ( <div> <VehicleComponent vehicle={car} /> <VehicleComponent vehicle={motorcycle} /> </div> ); }; export default App; ``` 在此範例中,我們有一個`Vehicle`接口,它定義了`name`屬性和`drive`方法。然後我們有兩個具體的實作: `Car`和`Motorcycle` ,它們都實作了`Vehicle`介面。 在 App 元件中,我們建立`Car`和`Motorcycle`的實例並將它們傳遞給 VehicleComponent。 `VehicleComponent`對傳入的車輛物件呼叫驅動方法。 LSP 確保我們可以用`Car`或`Motorcycle`代替`Vehicle`接口,而不改變程序的正確性。 `VehicleComponent`可與`Car`和`Motorcycle`實例無縫協作,展示子類型對其基本類型的可替換性。 3. KISS(保持簡單,愚蠢) ---------------- KISS 原則提倡設計和實現的簡單性。編寫易於理解、簡單且能做好一件事的程式碼。避免不必要的複雜性和過度設計,因為從長遠來看,這可能會導致混亂和維護挑戰。 讓我們來看看`Counter`元件的 2 個實作。 ``` // Complex Counter import React, { useState, useEffect } from 'react'; import { debounce } from 'lodash'; const ComplexCounter = () => { const [count, setCount] = useState(0); const [clicked, setClicked] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (clicked) { setCount(prev => prev + 1) setClicked(false) } }, [clicked, setClicked]); const handleClick = (clicked: boolean) => { setClicked(!clicked); }; return ( <div> <p>Count: {count}</p> <button onClick={() => handleClick(clicked)}>Increment</button> </div> ); }; export default ComplexCounter; ``` ``` // Simple Counter import React, { useState } from 'react'; const SimpleCounter = () => { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={handleClick}>Increment</button> </div> ); }; export default SimpleCounter; ``` 我們發現`ComplexCounter`實作更難理解和維護,而且更容易出錯。 它引入了一個不必要的`clicked`狀態變數和一個`useEffect`掛鉤。 這是一個如何不實作 React 元件的範例。 4. YAGNI(你不需要它) --------------- YAGNI 提醒我們避免基於推測的未來需求過早加入功能。相反,應專注於正確實現當前所需的功能。當您建立一個非常以用戶為中心的產品時,這一點變得非常重要。最好不要根據您認為用戶可能想要的假設來引入新功能。使用適當的使用者研究框架和原型設計方法。 遵循 YAGNI,您可以防止不必要的複雜性、減少開發時間並維護精簡的程式碼庫。 5. 乾淨的程式碼 --------- 乾淨的程式碼是可讀的、可理解的、可維護的。遵循編碼約定和最佳實踐,使用有意義的變數名稱,並編寫不言自明的程式碼。保持函數和類別小而集中,堅持一致的格式,並努力使程式碼庫清晰。 讓我們來看一個簡單的實用函數,用於出於資料安全目的隱藏部分使用者的私人資訊。 ``` const hashUsersPrivateInformation = (privateInformation: string): string => { // Calculate the length of the private info to determine how many characters to mask const maxLength = privateInformation.length > 4 ? privateInformation.length - 4 : privateInformation.length; // Create a regular expression pattern to match the desired number of characters const regexPattern = `.{1,${maxLength}}`; const regex = new RegExp(regexPattern); return privateInformation.replace(regex, (match) => '*'.repeat(match.length)); }; ``` 我們看到: 1. 函數的名稱是自我描述的 2. 它包含可以幫助其他開發人員的有用註釋。 3. 它有一個可以理解的主要目的。 我們應該以類似的方式建立我們的程式碼。 結論 -- 將這些軟體工程原理融入您的前端開發工作流程中,您可以編寫更高品質的程式碼,改善與團隊成員的協作,並建立強大且可擴展的應用程式。軟體工程不僅僅是編寫程式碼;還涉及編寫程式碼。它是為複雜問題建立可靠、可維護且優雅的解決方案。 --- 原文出處:https://dev.to/gboladetrue/software-engineering-principles-every-frontend-developer-should-know-1ej7

使用 NextJS 和 Wing 建立您自己的 ChatGPT 圖形客戶端 🤯

--- 標題:使用 NextJS 和 Wing 建立您自己的 ChatGPT 圖形客戶端 🤯 描述:使用 Winglang 和 NextJS 建立的 ChatGPT 客戶端應用程式 canonical\_url:https://www.winglang.io/blog/2024/05/16/chatgpt-client-with-nextjs-and-wing 發表:真實 --- 長話短說 ---- 在本文結束時,您將使用 Wing 和 Next.js 建置並部署 ChatGPT 用戶端。 該應用程式可以在本地執行(在本地雲端模擬器中)或將其部署到您自己的雲端提供者。 ![舞蹈](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1sm2cj4sbcm4skp0ho23.gif) --- 介紹 -- 建置 ChatGPT 用戶端並將其部署到您自己的雲端基礎架構是確保對資料進行控制的好方法。 將 LLM 部署到您自己的雲端基礎架構可為您的專案提供隱私和安全性。 有時,在使用 OpenAI 的 ChatGPT 等專有 LLM 平台時,您可能會擔心資料在遠端伺服器上儲存或處理,這可能是由於輸入平台的資料的敏感度或其他隱私原因。 在這種情況下,將 LLM 自託管到您的雲端基礎架構或在您的電腦上本地執行可以讓您更好地控制資料的隱私和安全性。 > [Wing](https://git.new/wing-repo)是一種面向雲端的程式語言,可讓您建置和部署基於雲端的應用程式,而無需擔心底層基礎架構。 它允許您使用相同的語言定義和管理雲端基礎架構和應用程式程式碼,從而簡化了您在雲端上建置的方式。 Wing 與雲端無關——用它建置的應用程式可以編譯並部署到各種雲端平台。 > {% cta https://git.new/wing-repo %} 看 ⭐ Wing {% endcta %} [![給我們一顆星星](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rg63klimgm7s0aw72rn2.png)](https://git.new/wing-repo) --- 讓我們開始吧! ------- 要繼續操作,您需要: - 對 Next.js 有一定了解 - 在您的機器上[安裝 Wing](https://www.winglang.io/docs/) 。如果您不知道如何操作,請不要擔心。我們將在這個專案中一起討論它。 - 取得您的 OpenAI API 金鑰。 建立您的專案 ------ 首先,您需要在電腦上安裝 Wing。執行以下命令: ``` npm install -g winglang ``` 透過檢查版本確認安裝: ``` wing -V ``` ### 建立您的 Next.js 和 Wing 應用程式。 ``` mkdir assistant cd assistant npx create-next-app@latest frontend mkdir backend && cd backend wing new empty ``` 我們已在 Assistant 目錄中成功建立了 Wing 和 Next.js 專案。我們的 ChatGPT 用戶端的名稱是 Assistant。聽起來很酷,對吧? 前端和後端目錄分別包含我們的 Next 和 Wing 應用程式。 `wing new empty`建立三個檔案: `package.json` 、 `package-lock.json`和`main.w` 。後者是應用程式的入口點。 ### 在 Wing 模擬器中本地執行您的應用程式 Wing 模擬器可讓您在本機電腦內執行程式碼、編寫單元測試和偵錯程式碼,而無需部署到實際的雲端供應商,從而幫助您更快地進行迭代。 使用以下命令在本機上執行您的 Wing 應用程式: ``` wing it ``` 您的 Wing 應用程式將在`localhost:3000`上執行。 ![安慰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5ytrntrz7lc5225w8w8.png) 設定您的後端 ------ - 讓我們安裝 Wing 的 OpenAI 和 React 函式庫。 OpenAI 庫提供了與 LLM 互動的標準介面。 React 程式庫可讓您將 Wing 後端連接到 Next 應用程式。 ``` npm i @winglibs/openai @winglibs/react ``` - 將這些套件匯入到`main.w`檔案中。我們還導入需要的所有其他庫。 ``` bring openai bring react bring cloud bring ex bring http ``` `bring`是 Wing 中的導入語句。這樣想,Wing 使用`bring`來實現與 JavaScript 中`import`相同的功能。 `cloud`是 Wing 的雲端庫。它公開了雲端 API、儲存桶、計數器、網域、端點、函數和更多雲端資源的標準介面。 `ex`是用於與表格和雲端 Redis 資料庫介面的標準庫, `http`用於呼叫不同的 HTTP 方法 - 從遠端資源發送和檢索資訊。 取得您的 OpenAI API 金鑰 ------------------ 我們將在我們的應用程式中使用`gpt-4-turbo`但您可以使用任何 OpenAI 模型。 - 如果您還沒有[OpenAI](https://platform.openai.com/signup)帳戶,請建立一個。若要建立新的 API 金鑰,請前往[platform.openai.com/api-keys](http://platform.openai.com/api-keys)並選擇**建立新金鑰。** ![OpenAI 金鑰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9645jxsf1fj8902iwnr7.png) - 設定**名稱**、**專案**和**權限,**然後按一下**建立金鑰。** ![OpenAI Key2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yng28wns7esezf94t3uq.png) 初始化 OpenAI ---------- 建立一個`Class`來初始化您的 OpenAI API。我們希望它可以重複使用。 我們將向`Assistant`類別加入`personality` ,以便在向 AI 助手傳遞提示時可以指定 AI 助手的個性。 ``` let apiKeySecret = new cloud.Secret(name: "OAIAPIKey") as "OpenAI Secret"; class Assistant { personality: str; openai: openai.OpenAI; new(personality: str) { this.openai = new openai.OpenAI(apiKeySecret: apiKeySecret); this.personality = personality; } pub inflight ask(question: str): str { let prompt = `you are an assistant with the following personality: ${this.personality}. ${question}`; let response = this.openai.createCompletion(prompt, model: "gpt-4-turbo"); return response.trim(); } } ``` Wing 分別使用`preflight`和`inflight`概念來統一基礎設施定義和應用程式邏輯。 **預檢**程式碼(通常是基礎設施定義)在編譯時執行一次,而執行**中**程式碼將在執行時執行以實現應用程式的行為。 雲端儲存桶、佇列和 API 端點是預檢的一些範例。定義預檢時不需要新增預檢關鍵字,Wing 預設知道這一點。但對於飛行塊,您需要在其中加入“飛行”一詞。 > 上面的程式碼中有一個飛行中的區塊。 Inflight 區塊是您編寫非同步執行時間程式碼的地方,這些程式碼可以透過其 inflight API 直接與資源互動。 > 測試和儲存雲端秘密 --------- 讓我們來看看如何保護我們的 API 金鑰,因為我們肯定要[考慮安全性](https://techhq.com/2022/09/hardcoded-api-keys-jeopardize-data-in-the-cloud/)。 讓我們在後端的根目錄中建立一個`.env`檔案並傳入我們的 API 金鑰: ``` OAIAPIKey = Your_OpenAI_API_key ``` 我們可以在本地引用 .env 檔案來測試 OpenAI API 金鑰,然後由於我們計劃部署到 AWS,因此我們將逐步設定[AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) 。 ![AWS 主控台](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e2a1nbh0egmjkckxnaov.png) 首先,我們前往 AWS 並登入控制台。如果您沒有帳戶,可以免費建立一個。 ![AWS平台](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n937801fzs0lajf2knaq.png) 導覽至 Secrets Manager,讓我們儲存 API 金鑰值。 ![AWS 秘密管理器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/scbb1snyzjdoip2nvdpl.png) ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lf79xzn6vfhqylao8iuo.png) 我們已將 API 金鑰儲存在名為`OAIAPIKey`的雲端機密中。複製您的金鑰,我們將跳到終端並連接到現在儲存在 AWS 平台中的金鑰。 ``` wing secrets ``` 現在將您的 API 金鑰貼上為終端中的值。您的密鑰現已正確存儲,我們可以開始與我們的應用程式互動。 --- 將人工智慧的回應儲存在雲端。 -------------- 將人工智慧的回應儲存在雲端可以讓您控制資料。它駐留在您自己的基礎設施上,與 ChatGPT 等專有平台不同,您的資料位於您無法控制的第三方伺服器上。您也可以在需要時檢索這些回應。 讓我們建立另一個類,使用 Assistant 類來傳遞 AI 的個性和提示。我們還將每個模型的回應作為`txt`檔案儲存在雲端儲存桶中。 ``` let counter = new cloud.Counter(); class RespondToQuestions { id: cloud.Counter; gpt: Assistant; store: cloud.Bucket; new(store: cloud.Bucket) { this.gpt = new Assistant("Respondent"); this.id = new cloud.Counter() as "NextID"; this.store = store; } pub inflight sendPrompt(question: str): str { let reply = this.gpt.ask("{question}"); let n = this.id.inc(); this.store.put("message-{n}.original.txt", reply); return reply; } } ``` --- 我們為我們的助理設定了「受訪者」的個性。我們希望它能夠回答問題。您也可以讓前端使用者在發送提示時指定此個性。 每次產生回應時,計數器都會遞增,並且計數器的值會傳遞到用於在雲端中儲存模型回應的`n`變數中。然而,我們真正想要的是建立一個資料庫來儲存來自前端的使用者提示和模型的回應。 讓我們定義我們的資料庫。 定義我們的資料庫 -------- Wing 內建了`ex.Table` - 一個用於儲存和查詢資料的 NoSQL 資料庫。 ``` let db = new ex.Table({ name: "assistant", primaryKey: "id", columns: { question: ex.ColumnType.STRING, answer: ex.ColumnType.STRING } }); ``` --- 我們在資料庫定義中新增了兩列 - 第一列用於儲存使用者提示,第二列用於儲存模型的回應。 建立 API 路由和邏輯 ------------ 我們希望能夠在後端發送和接收資料。讓我們建立 POST 和 GET 路由。 ``` let api = new cloud.Api({ cors: true }); api.post("/assistant", inflight((request) => { // POST request logic goes here })); api.get("/assistant", inflight(() => { // GET request logic goes here })); ``` --- ``` let myAssistant = new RespondToQuestions(store) as "Helpful Assistant"; api.post("/assistant", inflight((request) => { let prompt = request.body; let response = myAssistant.sendPrompt(JSON.stringify(prompt)); let id = counter.inc(); // Insert prompt and response in the database db.insert(id, { question: prompt, answer: response }); return cloud.ApiResponse({ status: 200 }); })); ``` 在 POST 路由中,我們希望將從前端收到的使用者提示傳遞到模型中並獲得回應。提示和回應都將儲存在資料庫中。 `cloud.ApiResponse`可讓您傳送對使用者要求的回應。 新增前端發出 GET 請求時檢索資料庫專案的邏輯。 --- 新增前端發出 GET 請求時檢索資料庫專案的邏輯。 ``` api.get("/assistant", inflight(() => { let questionsAndAnswers = db.list(); return cloud.ApiResponse({ body: JSON.stringify(questionsAndAnswers), status: 200 }); })); ``` 我們的後端已經準備好了。我們在本地雲端模擬器中測試一下。 跑`wing it` 。 讓我們轉到`localhost:3000`並向我們的助理詢問一個問題。 ![助理回應](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ox67623b9vye7o6quqe.png) 我們的問題和助理的回答都已儲存到資料庫中。看一看。 ![表資料](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ajd94ywkhjw04yb21e2.png) 向前端公開您的 API URL --------------- 我們需要將後端的 API URL 公開給 Next 前端。這就是之前安裝的 React 函式庫派上用場的地方。 ``` let website = new react.App({ projectPath: "../frontend", localPort: 4000 }); website.addEnvironment("API_URL", api.url); ``` 將以下內容加入 Next 應用程式的`layout.js`中。 ``` import { Inter } from "next/font/google"; import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); export const metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children }) { return ( <html lang="en"> <head> <script src="./wing.js" defer></script> </head> <body className={inter.className}>{children}</body> </html> ); } ``` 我們現在可以在 Next 應用程式中存取`API_URL` 。 實作前端邏輯 ------ 讓我們實作前端邏輯來呼叫後端。 ``` import { useEffect, useState, useCallback } from 'react'; import axios from 'axios'; function App() { const [isThinking, setIsThinking] = useState(false); const [input, setInput] = useState(""); const [allInteractions, setAllInteractions] = useState([]); const retrieveAllInteractions = useCallback(async (api_url) => { await axios ({ method: "GET", url: `${api_url}/assistant`, }).then(res => { setAllInteractions(res.data) }) }, []) const handleSubmit = useCallback(async (e)=> { e.preventDefault() setIsThinking(!isThinking) if(input.trim() === ""){ alert("Chat cannot be empty") setIsThinking(true) } await axios({ method: "POST", url: `${window.wingEnv.API_URL}/assistant`, headers: { "Content-Type": "application/json" }, data: input }) setInput(""); setIsThinking(false); await retrieveAllInteractions(window.wingEnv.API_URL); }) useEffect(() => { if (typeof window !== "undefined") { retrieveAllInteractions(window.wingEnv.API_URL); } }, []); // Here you would return your component's JSX return ( // JSX content goes here ); } export default App; ``` `retrieveAllInteractions`函數取得後端資料庫中的所有問題和答案。 `handSubmit`函數將使用者的提示傳送到後端。 讓我們加入 JSX 實作。 ``` import { useEffect, useState } from 'react'; import axios from 'axios'; import './App.css'; function App() { // ... return ( <div className="container"> <div className="header"> <h1>My Assistant</h1> <p>Ask anything...</p> </div> <div className="chat-area"> <div className="chat-area-content"> {allInteractions.map((chat) => ( <div key={chat.id} className="user-bot-chat"> <p className='user-question'>{chat.question}</p> <p className='response'>{chat.answer}</p> </div> ))} <p className={isThinking ? "thinking" : "notThinking"}>Generating response...</p> </div> <div className="type-area"> <input type="text" placeholder="Ask me any question" value={input} onChange={(e) => setInput(e.target.value)} /> <button onClick={handleSubmit}>Send</button> </div> </div> </div> ); } export default App; ``` 在本地執行您的專案 --------- 導航到您的後端目錄並使用以下命令在本地執行您的 Wing 應用程式 ``` cd ~assistant/backend wing it ``` 也執行您的 Next.js 前端: ``` cd ~assistant/frontend npm run dev ``` 讓我們看一下我們的應用程式。 ![聊天應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97g8kikxfwwb7ephfdni.png) 讓我們透過 Next 應用程式向 AI 助理詢問幾個開發人員問題。 ![聊天應用程式2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5uoz1y9czt0nwwtsesrz.png) 將您的應用程式部署到 AWS -------------- 我們已經了解了我們的應用程式如何在本地執行。 Wing 也允許您部署到包括 AWS 在內的任何雲端提供者。要部署到 AWS,您需要使用您的憑證來設定[Terraform](https://terraform.io/downloads)和[AWS CLI](https://docs.aws.amazon.com/cli/) 。 - 使用`tf-aws`編譯到 Terraform/AWS 。此指令指示編譯器使用 Terraform 作為配置引擎,將所有資源綁定到預設的 AWS 資源集。 ``` cd ~/assistant/backend wing compile --platform tf-aws main.w ``` --- - 執行 Terraform 初始化並應用 ``` cd ./target/main.tfaws terraform init terraform apply ``` --- 注意: `terraform apply`需要一些時間才能完成。 您可以[在此處](https://github.com/NathanTarbert/chatgpt-client-wing-nextjs)找到本教程的完整程式碼。 總結一下 ---- 正如我之前提到的,我們都應該關心我們的應用程式的安全性,建立您自己的 ChatGPT 用戶端並將其部署到您的雲端基礎設施可以為您的應用程式提供一些非常好的[保障](https://docs.aws.amazon.com/whitepapers/latest/aws-overview/security-and-compliance.html#:~:text=Keep%20Your%20data%20safe%20%E2%80%94%20The,compliance%20programs%20in%20its%20infrastructure.)。 我們在本教程中演示了[Wing](https://git.new/wing-repo)如何提供一種簡單的方法來建置可擴展的雲端應用程式,而無需擔心底層基礎設施。 如果您有興趣建立更酷的東西,Wing 擁有一個活躍的開發人員社區,他們可以合作建立雲端願景。我們很高興在那裡見到你。 只需前往我們的[Discord](https://t.winglang.io/discord)打個招呼即可! --- 原文出處:https://dev.to/winglang/building-your-own-chatgpt-graphical-client-with-nextjs-and-wing-29jj

掌握 React 中的 S.O.L.I.D 原則:簡單範例和最佳實踐

### 單一職責原則(SRP) **一個元件應該只有一個改變的理由,這意味著它應該只有一項工作。** #### 範例:使用者設定檔元件 **應該這樣:** - 將職責分解為更小的功能元件。 ``` // UserProfile.js const UserProfile = ({ user }) => { return ( <div> <UserAvatar user={user} /> <UserInfo user={user} /> </div> ); }; // UserAvatar.js const UserAvatar = ({ user }) => { return <img src={user.avatarUrl} alt={`${user.name}'s avatar`} />; }; // UserInfo.js const UserInfo = ({ user }) => { return ( <div> <h1>{user.name}</h1> <p>{user.bio}</p> </div> ); }; ``` **不要這樣:** - 將顯示、資料取得和業務邏輯組合在一個元件中。 ``` // IncorrectUserProfile.js const IncorrectUserProfile = ({ user }) => { // Fetching data, handling business logic and displaying all in one const handleEdit = () => { console.log("Edit user"); }; return ( <div> <img src={user.avatarUrl} alt={`${user.name}'s avatar`} /> <h1>{user.name}</h1> <p>{user.bio}</p> <button onClick={handleEdit}>Edit User</button> </div> ); }; ``` ### 開閉原理 (OCP) **軟體實體應該對擴充開放,但對修改關閉。** #### 範例:主題按鈕 **應該這樣:** - 使用 props 來擴充元件功能,而無需修改原始元件。 ``` // Button.js const Button = ({ onClick, children, style }) => { return ( <button onClick={onClick} style={style}> {children} </button> ); }; // Usage const PrimaryButton = (props) => { const primaryStyle = { backgroundColor: 'blue', color: 'white' }; return <Button {...props} style={primaryStyle} />; }; ``` **不要這樣:** - 修改原有元件程式碼,直接加入新的樣式或行為。 ``` // IncorrectButton.js // Modifying the original Button component directly for a specific style const Button = ({ onClick, children, primary }) => { const style = primary ? { backgroundColor: 'blue', color: 'white' } : null; return ( <button onClick={onClick} style={style}> {children} </button> ); }; ``` ### 里氏替換原理 (LSP) **超類別的物件可以用其子類別的物件替換,而不會破壞應用程式。** #### 範例:基本按鈕和圖示按鈕 **應該這樣:** - 確保子類元件可以無縫替換超類元件。 ``` // BasicButton.js const BasicButton = ({ onClick, children }) => { return <button onClick={onClick}>{children}</button>; }; // IconButton.js const IconButton = ({ onClick, icon, children }) => { return ( <button onClick={onClick}> <img src={icon} alt="icon" /> {children} </button> ); }; ``` **不要這樣:** - 引入特定於子類別的屬性,這些屬性在替換時會破壞功能。 ``` // IncorrectIconButton.js // This button expects an icon and does not handle the absence of one, breaking when used as a BasicButton const IncorrectIconButton = ({ onClick, icon }) => { if (!icon) { throw new Error("Icon is required"); } return ( <button onClick={onClick}> <img src={icon} alt="icon" /> </button> ); }; ``` ### 介面隔離原則(ISP) **任何客戶端都不應該被迫依賴它不使用的方法。** #### 範例:文字元件 **應該這樣:** - 針對不同的用途提供特定的介面。 ``` // Text.js const Text = ({ type, children }) => { switch (type) { case 'header': return <h1>{children}</h1>; case 'title': return <h2>{children}</h2>; default: return <p>{children}</p>; } }; ``` **不要這樣:** - 用不必要的屬性使元件變得混亂。 ``` // IncorrectText.js // This component expects multiple unrelated props, cluttering the interface const IncorrectText = ({ type, children, onClick, isLoggedIn }) => { if (isLoggedIn && onClick) { return <a href="#" onClick={onClick}>{children}</a>; } return type === 'header' ? <h1>{children}</h1> : <p>{children}</p>; }; ``` ### 依賴倒置原則(DIP) **高層模組不應該依賴低層模組。兩者都應該依賴抽象。** #### 範例:資料獲取 **應該這樣:** - 使用鉤子或類似的模式來抽象資料獲取 和狀態管理。 ``` // useUserData.js (Abstraction) const useUserData = (userId) => { const [user, setUser] = useState(null); useEffect(() => { fetchData(userId).then(setUser); }, [userId]); return user; }; // UserProfile.js const UserProfile = ({ userId }) => { const user = useUserData(userId); if (!user) return <p>Loading...</p>; return <div><h1>{user.name}</h1></div>; }; ``` **不要這樣:** - 在元件內部硬編碼資料取得。 ``` // IncorrectUserProfile.js const IncorrectUserProfile = ({ userId }) => { const [user, setUser] = useState(null); useEffect(() => { // Fetching data directly inside the component fetch(`https://api.example.com/users/${userId}`) .then(response => response.json()) .then(setUser); }, [userId]); if (!user) return <p>Loading...</p>; return <div><h1>{user.name}</h1></div>; }; ``` --- 原文出處:https://dev.to/drruvari/mastering-solid-principles-in-react-easy-examples-and-best-practices-142b

使用 AI-copilot(Next.js、gpt4、LangChain 和 CopilotKit)建立電子表格應用程式

**長話短說** -------- 在本文中,您將學習如何建立人工智慧驅動的電子表格應用程式,該應用程式允許您使用簡單的英語命令執行各種會計功能並輕鬆與資料互動。 我們將介紹如何: - 使用 Next.js 建立 Web 應用程式, - 使用 React Spreadsheet 建立電子表格應用程式,以及 - 使用 CopilotKit 將 AI 整合到軟體應用程式中。 - 讓電子表格更容易使用、更有趣 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ruxylrd07dga5ngw98np.gif) --- CopilotKit:建構應用內人工智慧副駕駛的框架 ========================== CopilotKit是一個[開源的AI副駕駛平台](https://github.com/CopilotKit/CopilotKit)。我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。 建造: - ChatBot:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 💬 - CopilotTextArea:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能📝 - 聯合代理:應用程式內人工智慧代理,可以與您的應用程式和使用者互動🤖 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3us3vc140aun0dvrdof.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} (請原諒 AI 的拼字錯誤並給 CopilotKit 加上星號:) 現在回到文章! --- 先決條件 ---- 要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。 以下是建立人工智慧驅動的電子表格應用程式所需的工具: - [React Spreadsheet](https://github.com/iddan/react-spreadsheet) - 一個簡單的包,使我們能夠在 React 應用程式中加入電子表格。 - [OpenAI API](https://platform.openai.com/api-keys) - 提供 API 金鑰,使我們能夠使用 ChatGPT 模型執行各種任務。 - [Tavily AI](https://tavily.com/) - 一個搜尋引擎,使人工智慧代理能夠在應用程式中進行研究並存取即時知識。 - [CopilotKit](https://github.com/CopilotKit) - 一個開源副駕駛框架,用於建立自訂 AI 聊天機器人、應用程式內 AI 代理程式和文字區域。 專案設定和套件安裝 --------- 首先,透過在終端機中執行以下程式碼片段來建立 Next.js 應用程式: ``` npx create-next-app spreadsheet-app ``` 選擇您首選的配置設定。在本教學中,我們將使用 TypeScript 和 Next.js App Router。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2zg8z22tdgmlv4wmfil.png) 接下來,安裝[OpenAI 函式庫](https://platform.openai.com/docs/introduction)、 [Heroicons](https://www.npmjs.com/package/@heroicons/react)和[React Spreadsheet](https://github.com/iddan/react-spreadsheet)套件及其相依性: ``` npm install openai react-spreadsheet scheduler @heroicons/react ``` 最後,安裝 CopilotKit 軟體套件。這些套件使我們能夠從 React 狀態檢索資料並將 AI copilot 新增至應用程式。 ``` npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend ``` 恭喜!您現在已準備好建立應用程式。 --- 建立電子表格應用程式 ---------- 在本節中,我將引導您使用 React Spreadsheet 建立電子表格應用程式。 該應用程式分為兩個元件: `Sidebar`和`SingleSpreadsheet` 。 要設定這些元件,請導航至 Next.js 應用程式資料夾並建立一個包含以下檔案的`components`資料夾: ``` cd app mkdir components && cd components touch Sidebar.tsx SingleSpreadsheet.tsx ``` ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bd9crph3qdenb2eikfoh.png) 將新建立的元件匯入到**`app/page.tsx`**檔案中。 ``` "use client"; import React, { useState } from "react"; //👇🏻 import the components import { SpreadsheetData } from "./types"; import Sidebar from "./components/Sidebar"; import SingleSpreadsheet from "./components/SingleSpreadsheet"; const Main = () => { return ( <div className='flex'> <p>Hello world</p> </div> ); }; export default Main; ``` 接下來,建立將包含電子表格資料的 React 狀態,並將它們作為 props 傳遞到元件中。 ``` const Main = () => { //👇🏻 holds the title and data within a spreadsheet const [spreadsheets, setSpreadsheets] = React.useState<SpreadsheetData[]>([ { title: "Spreadsheet 1", data: [ [{ value: "" }, { value: "" }, { value: "" }], [{ value: "" }, { value: "" }, { value: "" }], [{ value: "" }, { value: "" }, { value: "" }], ], }, ]); //👇🏻 represents the index of a spreadsheet const [selectedSpreadsheetIndex, setSelectedSpreadsheetIndex] = useState(0); return ( <div className='flex'> <Sidebar spreadsheets={spreadsheets} selectedSpreadsheetIndex={selectedSpreadsheetIndex} setSelectedSpreadsheetIndex={setSelectedSpreadsheetIndex} /> <SingleSpreadsheet spreadsheet={spreadsheets[selectedSpreadsheetIndex]} setSpreadsheet={(spreadsheet) => { setSpreadsheets((prev) => { console.log("setSpreadsheet", spreadsheet); const newSpreadsheets = [...prev]; newSpreadsheets[selectedSpreadsheetIndex] = spreadsheet; return newSpreadsheets; }); }} /> </div> ); }; ``` 此程式碼片段建立了 React 狀態,用於保存電子表格資料及其索引,並將它們作為 props 傳遞到元件中。 `Sidebar`元件接受所有可用的電子表格, `SingleSpreadsheet`元件接收所有電子表格,包括更新電子表格資料的`setSpreadsheet`函數。 將下面的程式碼片段複製到`Sidebar.tsx`檔案中。它顯示應用程式中的所有電子表格,並允許使用者在它們之間進行切換。 ``` import React from "react"; import { SpreadsheetData } from "../types"; interface SidebarProps { spreadsheets: SpreadsheetData[]; selectedSpreadsheetIndex: number; setSelectedSpreadsheetIndex: (index: number) => void; } const Sidebar = ({ spreadsheets, selectedSpreadsheetIndex, setSelectedSpreadsheetIndex, }: SidebarProps) => { return ( <div className='w-64 h-screen bg-gray-800 text-white overflow-auto p-5'> <ul> {spreadsheets.map((spreadsheet, index) => ( <li key={index} className={`mb-4 cursor-pointer ${ index === selectedSpreadsheetIndex ? "ring-2 ring-blue-500 ring-inset p-3 rounded-lg" : "p-3" }`} onClick={() => setSelectedSpreadsheetIndex(index)} > {spreadsheet.title} </li> ))} </ul> </div> ); }; export default Sidebar; ``` ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c36qccuwa0knqokag5mk.gif) 更新`SingleSpreadsheet.tsx`文件,如下所示: ``` import React from "react"; import Spreadsheet from "react-spreadsheet"; import { SpreadsheetData, SpreadsheetRow } from "../types"; interface MainAreaProps { spreadsheet: SpreadsheetData; setSpreadsheet: (spreadsheet: SpreadsheetData) => void; } //👇🏻 adds a new row to the spreadsheet const addRow = () => { const numberOfColumns = spreadsheet.rows[0].length; const newRow: SpreadsheetRow = []; for (let i = 0; i < numberOfColumns; i++) { newRow.push({ value: "" }); } setSpreadsheet({ ...spreadsheet, rows: [...spreadsheet.rows, newRow], }); }; //👇🏻 adds a new column to the spreadsheet const addColumn = () => { const spreadsheetData = [...spreadsheet.data]; for (let i = 0; i < spreadsheet.data.length; i++) { spreadsheet.data[i].push({ value: "" }); } setSpreadsheet({ ...spreadsheet, data: spreadsheetData, }); }; const SingleSpreadsheet = ({ spreadsheet, setSpreadsheet }: MainAreaProps) => { return ( <div className='flex-1 overflow-auto p-5'> {/** -- Spreadsheet title ---*/} <div className='flex items-start'> {/** -- Spreadsheet rows and columns---*/} {/** -- Add column button ---*/} </div> {/** -- Add row button ---*/} </div> ); }; export default SingleSpreadsheet; ``` - 從上面的程式碼片段來看, ``` - The `SingleSpreadsheet.tsx` file includes the addRow and addColumn functions. ``` ``` - The `addRow` function calculates the current number of rows, adds a new row, and updates the spreadsheet accordingly. ``` ``` - Similarly, the `addColumn` function adds a new column to the spreadsheet. ``` ``` - The `SingleSpreadsheet` component renders placeholders for the user interface elements. ``` 更新`SingleSpreadsheet`元件以呈現電子表格標題、其資料以及新增行和列按鈕。 ``` return ( <div className='flex-1 overflow-auto p-5'> {/** -- Spreadsheet title ---*/} <input type='text' value={spreadsheet.title} className='w-full p-2 mb-5 text-center text-2xl font-bold outline-none bg-transparent' onChange={(e) => setSpreadsheet({ ...spreadsheet, title: e.target.value }) } /> {/** -- Spreadsheet rows and columns---*/} <div className='flex items-start'> <Spreadsheet data={spreadsheet.data} onChange={(data) => { console.log("data", data); setSpreadsheet({ ...spreadsheet, data: data as any }); }} /> {/** -- Add column button ---*/} <button className='bg-blue-500 text-white rounded-lg ml-6 w-8 h-8 mt-0.5' onClick={addColumn} > + </button> </div> {/** -- Add row button ---*/} <button className='bg-blue-500 text-white rounded-lg w-8 h-8 mt-5 ' onClick={addRow} > + </button> </div> ); ``` 為了確保一切按預期工作,請在`app`資料夾中建立一個`types.ts`文件,其中包含應用程式中聲明的所有靜態類型。 ``` export interface Cell { value: string; } export type SpreadsheetRow = Cell[]; export interface SpreadsheetData { title: string; rows: SpreadsheetRow[]; } ``` 恭喜! 🎉 您的電子表格應用程式應該可以完美執行。在接下來的部分中,您將了解如何新增 AI 副駕駛,以使用 CopilotKit 自動執行各種任務。 --- 使用 CopilotKit 改進應用程式功能 ---------------------- 在這裡,您將學習如何將 AI 副駕駛加入到電子表格應用程式,以使用 CopilotKit 自動執行複雜的操作。 CopilotKit 提供前端和[後端](https://docs.copilotkit.ai/getting-started/quickstart-backend)套件。它們使您能夠插入 React 狀態並使用 AI 代理在後端處理應用程式資料。 首先,我們將 CopilotKit React 元件新增到應用程式前端。 ### 將 CopilotKit 加入前端 在`app/page.tsx`中,將以下程式碼片段加入`Main`元件的頂部。 ``` import "@copilotkit/react-ui/styles.css"; import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import { INSTRUCTIONS } from "./instructions"; const HomePage = () => { return ( <CopilotKit url='/api/copilotkit'> <CopilotSidebar instructions={INSTRUCTIONS} labels={{ initial: "Welcome to the spreadsheet app! How can I help you?", }} defaultOpen={true} clickOutsideToClose={false} > <Main /> </CopilotSidebar> </CopilotKit> ); }; const Main = () => { //--- Main component // }; export default HomePage; ``` - 從上面的程式碼片段來看, ``` - I imported the CopilotKit, its sidebar component, and CSS file to use its frontend components within the application. ``` ``` - The [CopilotKit component](https://docs.copilotkit.ai/reference/CopilotKit) accepts a `url` prop that represents the API server route where CopilotKit will be configured. ``` ``` - The Copilot component also renders the [CopilotSidebar component](https://docs.copilotkit.ai/reference/CopilotSidebar) , allowing users to provide custom instructions to the AI copilot within the application. ``` ``` - Lastly, you can export the `HomePage` component containing the `CopilotSidebar` and the `Main` components. ``` 從上面的程式碼片段中,您會注意到`CopilotSidebar`元件有一個`instructions`屬性。此屬性使您能夠為 CopilotKit 提供額外的上下文或指導。 因此,在`app`資料夾中建立`instructions.ts`檔案並將這些[命令](https://github.com/CopilotKit/spreadsheet-demo/blob/main/src/app/instructions.ts)複製到該檔案中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/whs8k2ly9as7j4wn6h5o.png) 接下來,您需要將 CopilotKit 插入應用程式的狀態以存取應用程式的資料。為了實現這一點,CopilotKit 提供了兩個鉤子: [useCopilotAction](https://docs.copilotkit.ai/reference/useCopilotAction)和[useMakeCopilotReadable](https://docs.copilotkit.ai/reference/useMakeCopilotReadable) 。 [useCopilotAction](https://docs.copilotkit.ai/reference/useCopilotAction)掛鉤可讓您定義 CopilotKit 執行的動作。它接受包含以下參數的物件: - `name` - 操作的名稱。 - `description` - 操作的描述。 - `parameters` - 包含所需參數清單的陣列。 - `render` - 預設的自訂函數或字串。 - `handler` - 由操作觸發的可執行函數。 ``` useCopilotAction({ name: "sayHello", description: "Say hello to someone.", parameters: [ { name: "name", type: "string", description: "name of the person to say greet", }, ], render: "Process greeting message...", handler: async ({ name }) => { alert(`Hello, ${name}!`); }, }); ``` [useMakeCopilotReadable](https://docs.copilotkit.ai/reference/useMakeCopilotReadable)掛鉤向 CopilotKit 提供應用程式狀態。 ``` import { useMakeCopilotReadable } from "@copilotkit/react-core"; const appState = ...; useMakeCopilotReadable(JSON.stringify(appState)); ``` 現在,讓我們回到電子表格應用程式。在`SingleSpreadsheet`元件中,將應用程式狀態傳遞到 CopilotKit 中,如下所示。 ``` import { useCopilotAction, useMakeCopilotReadable, } from "@copilotkit/react-core"; const SingleSpreadsheet = ({ spreadsheet, setSpreadsheet }: MainAreaProps) => { //👇🏻 hook for providing the application state useMakeCopilotReadable( "This is the current spreadsheet: " + JSON.stringify(spreadsheet) ); // --- other lines of code }; ``` 接下來,您需要在`SingleSpreadsheet`元件中新增兩個操作,該元件在使用者更新電子表格資料並使用 CopilotKit 新增資料行時執行。 在繼續之前,請在`app`資料夾中建立一個包含`canonicalSpreadsheetData.ts`檔案的`utils`資料夾。 ``` cd app mkdir utils && cd utils touch canonicalSpreadsheetData.ts ``` 將下面的程式碼片段複製到檔案中。它接受對電子表格所做的更新,並將其轉換為電子表格中資料行所需的格式。 ``` import { SpreadsheetRow } from "../types" export interface RowLike { cells: CellLike[] | undefined; } export interface CellLike { value: string; } export function canonicalSpreadsheetData( rows: RowLike[] | undefined ): SpreadsheetRow[] { const canonicalRows: SpreadsheetRow[] = []; for (const row of rows || []) { const canonicalRow: SpreadsheetRow = []; for (const cell of row.cells || []) { canonicalRow.push({value: cell.value}); } canonicalRows.push(canonicalRow); } return canonicalRows; } ``` 現在,讓我們使用`SingleSpreadsheet`元件中的`useCopilotAction`掛鉤建立操作。複製下面的第一個操作: ``` import { canonicalSpreadsheetData } from "../utils/canonicalSpreadsheetData"; import { PreviewSpreadsheetChanges } from "./PreviewSpreadsheetChanges"; import { SpreadsheetData, SpreadsheetRow } from "../types"; import { useCopilotAction } from "@copilotkit/react-core"; useCopilotAction({ name: "suggestSpreadsheetOverride", description: "Suggest an override of the current spreadsheet", parameters: [ { name: "rows", type: "object[]", description: "The rows of the spreadsheet", attributes: [ { name: "cells", type: "object[]", description: "The cells of the row", attributes: [ { name: "value", type: "string", description: "The value of the cell", }, ], }, ], }, { name: "title", type: "string", description: "The title of the spreadsheet", required: false, }, ], render: (props) => { const { rows } = props.args const newRows = canonicalSpreadsheetData(rows); return ( <PreviewSpreadsheetChanges preCommitTitle="Replace contents" postCommitTitle="Changes committed" newRows={newRows} commit={(rows) => { const updatedSpreadsheet: SpreadsheetData = { title: spreadsheet.title, rows: rows, }; setSpreadsheet(updatedSpreadsheet); }} /> ) }, handler: ({ rows, title }) => { // Do nothing. // The preview component will optionally handle committing the changes. }, }); ``` 上面的程式碼片段執行使用者的任務並使用 CopilotKit 產生 UI 功能顯示結果預覽。 `suggestSpreadsheetOverride`操作傳回一個自訂元件 ( `PreviewSpreadsheetChanges` ),該元件接受以下內容為 props: - 要新增到電子表格的新資料行, - 一些文字 - `preCommitTitle`和`postCommitTitle` ,以及 - 更新電子表格的`commit`函數。 您很快就會學會如何使用它們。 在元件資料夾中建立`PreviewSpreadsheetChanges`元件,並將下列程式碼片段複製到檔案中: ``` import { CheckCircleIcon } from '@heroicons/react/20/solid' import { SpreadsheetRow } from '../types'; import { useState } from 'react'; import Spreadsheet from 'react-spreadsheet'; export interface PreviewSpreadsheetChanges { preCommitTitle: string; postCommitTitle: string; newRows: SpreadsheetRow[]; commit: (rows: SpreadsheetRow[]) => void; } export function PreviewSpreadsheetChanges(props: PreviewSpreadsheetChanges) { const [changesCommitted, setChangesCommitted] = useState(false); const commitChangesButton = () => { return ( <button className="inline-flex items-center gap-x-2 rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" onClick={() => { props.commit(props.newRows); setChangesCommitted(true); }} > {props.preCommitTitle} </button> ); } const changesCommittedButtonPlaceholder = () => { return ( <button className=" inline-flex items-center gap-x-2 rounded-md bg-gray-100 px-3.5 py-2.5 text-sm font-semibold text-green-600 shadow-sm cursor-not-allowed" disabled > {props.postCommitTitle} <CheckCircleIcon className="-mr-0.5 h-5 w-5" aria-hidden="true" /> </button> ); } return ( <div className="flex flex-col"> <Spreadsheet data={props.newRows} /> <div className="mt-5"> {changesCommitted ? changesCommittedButtonPlaceholder() : commitChangesButton() } </div> </div> ); } ``` `PreviewSpreadsheetChanges`元件傳回一個電子表格,其中包含從請求產生的資料和一個按鈕(帶有`preCommitTitle`文字),該按鈕允許您將這些變更提交到主電子表格表(透過觸發`commit`函數)。這可確保您在將結果新增至電子表格之前對結果感到滿意。 將下面的第二個操作加入到`SingleSpreadsheet`元件。 ``` useCopilotAction({ name: "appendToSpreadsheet", description: "Append rows to the current spreadsheet", parameters: [ { name: "rows", type: "object[]", description: "The new rows of the spreadsheet", attributes: [ { name: "cells", type: "object[]", description: "The cells of the row", attributes: [ { name: "value", type: "string", description: "The value of the cell", }, ], }, ], }, ], render: (props) => { const status = props.status; const { rows } = props.args const newRows = canonicalSpreadsheetData(rows); return ( <div> <p>Status: {status}</p> <Spreadsheet data={newRows} /> </div> ) }, handler: ({ rows }) => { const canonicalRows = canonicalSpreadsheetData(rows); const updatedSpreadsheet: SpreadsheetData = { title: spreadsheet.title, rows: [...spreadsheet.rows, ...canonicalRows], }; setSpreadsheet(updatedSpreadsheet); }, }); ``` `appendToSpreadsheet`操作透過在電子表格中新增資料行來更新電子表格。 以下是操作的簡短示範: \[https://www.youtube.com/watch?v=kGQ9xl5mSoQ\] 最後,在`Main`元件中新增一個操作,以便在使用者提供指令時建立一個新的電子表格。 ``` useCopilotAction({ name: "createSpreadsheet", description: "Create a new spreadsheet", parameters: [ { name: "rows", type: "object[]", description: "The rows of the spreadsheet", attributes: [ { name: "cells", type: "object[]", description: "The cells of the row", attributes: [ { name: "value", type: "string", description: "The value of the cell", }, ], }, ], }, { name: "title", type: "string", description: "The title of the spreadsheet", }, ], render: (props) => { const { rows, title } = props.args; const newRows = canonicalSpreadsheetData(rows); return ( <PreviewSpreadsheetChanges preCommitTitle="Create spreadsheet" postCommitTitle="Spreadsheet created" newRows={newRows} commit={ (rows) => { const newSpreadsheet: SpreadsheetData = { title: title || "Untitled Spreadsheet", rows: rows, }; setSpreadsheets((prev) => [...prev, newSpreadsheet]); setSelectedSpreadsheetIndex(spreadsheets.length); }} /> ); }, handler: ({ rows, title }) => { // Do nothing. // The preview component will optionally handle committing the changes. }, }); ``` 恭喜!您已成功為此應用程式建立所需的操作。現在,讓我們將應用程式連接到 Copilotkit 後端。 ### 將 Tavily AI 和 OpenAI 加入到 CopilotKit 在本教程的開頭,我向您介紹了[Tavily AI](https://tavily.com/) (一個為 AI 代理提供知識的搜尋引擎)和 OpenAI(一個使我們能夠存取[GPT-4 AI 模型的](https://openai.com/gpt-4)庫)。 在本部分中,您將了解如何取得 Tavily 和 OpenAI API 金鑰並將它們整合到 CopilotKit 中以建立高級智慧應用程式。 造訪[Tavily AI 網站](https://app.tavily.com/sign-in),建立一個帳戶,然後將您的 API 金鑰複製到您專案的`.env.local`檔案中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w8shpxr5hh9r9kggk1jv.png) 接下來,導覽至[OpenAI 開發者平台](https://platform.openai.com/api-keys),建立 API 金鑰,並將其複製到`.env.local`檔案中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4ugtobr70wg6z6ru3cj.png) 以下是`.env.local`檔案的預覽,其中包括 API 金鑰並指定要使用的 OpenAI 模型。請注意,存取 GPT-4 模型需要[訂閱 ChatGPT Plus](https://openai.com/chatgpt/pricing) 。 ``` TAVILY_API_KEY=<your_API_key> OPENAI_MODEL=gpt-4-1106-preview OPENAI_API_KEY=<your_API_key> ``` 回到我們的應用程式,您需要為 Copilot 建立 API 路由。因此,建立一個包含`route.ts`的`api/copilotkit`資料夾並新增一個`tavily.ts`檔案。 ``` cd app mkdir api && cd api mkdir copilotkit && cd copilotkit touch route.ts tavily.ts ``` 在`tavily.ts`檔案中建立一個函數,該函數接受使用者的查詢,使用 Tavily Search API 對查詢進行研究,並使用[OpenAI GPT-4 模型](https://openai.com/gpt-4)總結結果。 ``` import OpenAI from "openai"; export async function research(query: string) { //👇🏻 sends the request to the Tavily Search API const response = await fetch("https://api.tavily.com/search", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ api_key: process.env.TAVILY_API_KEY, query, search_depth: "basic", include_answer: true, include_images: false, include_raw_content: false, max_results: 20, }), }); //👇🏻 the response const responseJson = await response.json(); const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! }); //👇🏻 passes the response into the OpenAI GPT-4 model const completion = await openai.chat.completions.create({ messages: [ { role: "system", content: `Summarize the following JSON to answer the research query \`"${query}"\`: ${JSON.stringify( responseJson )} in plain English.`, }, ], model: process.env.OPENAI_MODEL, }); //👇🏻 returns the result return completion.choices[0].message.content; } ``` 最後,您可以透過將使用者的查詢傳遞到函數中並向 CopilotKit 提供其回應來執行`route.ts`檔案中的`research`函數。 ``` import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend"; import { Action } from "@copilotkit/shared"; import { research } from "./tavily"; //👇🏻 carries out a research on the user's query const researchAction: Action<any> = { name: "research", description: "Call this function to conduct research on a certain query.", parameters: [ { name: "query", type: "string", description: "The query for doing research. 5 characters or longer. Might be multiple words", }, ], handler: async ({ query }) => { console.log("Research query: ", query); const result = await research(query); console.log("Research result: ", result); return result; }, }; export async function POST(req: Request): Promise<Response> { const actions: Action<any>[] = []; if (process.env.TAVILY_API_KEY!) { actions.push(researchAction); } const copilotKit = new CopilotBackend({ actions: actions, }); const openaiModel = process.env.OPENAI_MODEL; return copilotKit.response(req, new OpenAIAdapter({ model: openaiModel })); } ``` 恭喜!您已完成本教學的專案。 結論 -- [CopilotKit](https://copilotkit.ai/)是一款令人難以置信的工具,可讓您在幾分鐘內將 AI Copilot 加入到您的產品中。無論您是對人工智慧聊天機器人和助理感興趣,還是對複雜任務的自動化感興趣,CopilotKit 都能讓您輕鬆實現。 如果您需要建立 AI 產品或將 AI 工具整合到您的軟體應用程式中,您應該考慮 CopilotKit。 您可以在 GitHub 上找到本教學的源程式碼: https://github.com/CopilotKit/spreadsheet-demo 感謝您的閱讀! --- 原文出處:https://dev.to/copilotkit/build-an-ai-powered-spreadsheet-app-nextjs-langchain-copilotkit-109d

30 多個應用程式創意以及完整的源程式碼

這是科技進步的令人興奮的時刻。 作為開發人員,我們所有人都需要從事可以產生收入或幫助建立我們聲譽的副業專案。 今天,我們將介紹 10 個令人興奮的專案,並發現使用每個專案建立的 3-4 個流行應用程式。總共有 30 多個專案,提供程式碼存取供您學習。 這些將讓您編碼一段時間,所以讓我們開始吧! ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fhpnyrvncqsbultjfk9.gif) --- 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 在數小時內為您的產品提供 AI Copilot。 ------------------------------------------------------------------------------------ ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/769q31e3wi56efcmkx1s.png) 將 AI 功能整合到 React 中是很困難的,這就是 Copilot 的用武之地。一個簡單快速的解決方案,可將可投入生產的 Copilot 整合到任何產品中! 您可以使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。它們還提供內建(完全可自訂)Copilot 原生 UX 元件,例如`<CopilotKit />` 、 `<CopilotPopup />` 、 `<CopilotSidebar />` 、 `<CopilotTextarea />` 。 開始使用以下 npm 指令。 ``` npm i @copilotkit/react-core @copilotkit/react-ui ``` Copilot Portal 是 CopilotKit 提供的元件之一,CopilotKit 是一個應用程式內人工智慧聊天機器人,可查看目前應用狀態並在應用程式內採取操作。它透過插件與應用程式前端和後端以及第三方服務進行通訊。 這就是整合聊天機器人的方法。 `CopilotKit`必須包裝與 CopilotKit 互動的所有元件。建議您也開始使用`CopilotSidebar` (您可以稍後切換到不同的 UI 提供者)。 ``` "use client"; import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; export default function RootLayout({children}) { return ( <CopilotKit url="/path_to_copilotkit_endpoint/see_below"> <CopilotSidebar> {children} </CopilotSidebar> </CopilotKit> ); } ``` 您可以使用此[快速入門指南](https://docs.copilotkit.ai/getting-started/quickstart-backend)設定 Copilot 後端端點。 之後,您可以讓 Copilot 採取行動。您可以閱讀如何提供[外部上下文](https://docs.copilotkit.ai/getting-started/quickstart-chatbot#provide-context)。您可以使用`useMakeCopilotReadable`和`useMakeCopilotDocumentReadable`反應掛鉤來執行此操作。 ``` "use client"; import { useMakeCopilotActionable } from '@copilotkit/react-core'; // Let the copilot take action on behalf of the user. useMakeCopilotActionable( { name: "setEmployeesAsSelected", // no spaces allowed in the function name description: "Set the given employees as 'selected'", argumentAnnotations: [ { name: "employeeIds", type: "array", items: { type: "string" } description: "The IDs of employees to set as selected", required: true } ], implementation: async (employeeIds) => setEmployeesAsSelected(employeeIds), }, [] ); ``` 您可以閱讀[文件](https://docs.copilotkit.ai/getting-started/quickstart-textarea)並查看[演示影片](https://github.com/CopilotKit/CopilotKit?tab=readme-ov-file#demo)。 您可以輕鬆整合 Vercel AI SDK、OpenAI API、Langchain 和其他 LLM 供應商。您可以按照本[指南](https://docs.copilotkit.ai/getting-started/quickstart-chatbot)將聊天機器人整合到您的應用程式中。 基本概念是在幾分鐘內建立可用於基於 LLM 的應用程式的 AI 聊天機器人。 用例是巨大的,作為開發人員,我們絕對應該在下一個專案中嘗試使用 CopilotKit。 CopilotKit 在 GitHub 上擁有超過 4,200 個星星,發布了 200 多個版本,這意味著它們正在不斷改進。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p8i6roafbjxvds26fl35.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- ### 🎯 使用 CopilotKit 建立的熱門應用程式。 我們可以使用 CopilotKit 建立許多創新應用程式,所以讓我們探索一些脫穎而出的應用程式! ### ✅ [人工智慧驅動的部落格平台](https://dev.to/copilotkit/how-to-build-an-ai-powered-blogging-platform-nextjs-langchain-supabase-1hdp)。 ![部落格平台](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b89eub6whw7kxzbyw1dl.png) 您可以閱讀本文,使用`Next.js` 、 `Langchain` 、 `Supabase`和`CopilotKit`來建立這個令人驚嘆的應用程式。 LangChain &amp; Tavily 用於網路搜尋人工智慧代理,Supabase 用於儲存和檢索部落格平台文章資料,而 CopilotKit 用於將人工智慧整合到應用程式中。 您可以檢查[GitHub 儲存庫](https://github.com/TheGreatBonnie/aipoweredblog)。 ### ✅ [文字到 Powerpoint 應用程式](https://dev.to/copilotkit/how-to-build-ai-powered-powerpoint-app-nextjs-openai-copilotkit-ji2)。 您可以閱讀本文,使用`Next.js` 、 `OpenAI`和`CopilotKit`建立 Text to Powerpoint 應用程式。 您可以檢查[GitHub 儲存庫](https://github.com/TheGreatBonnie/aipoweredpresentation)。 ### ✅ [V0.dev 複製](https://dev.to/copilotkit/i-created-a-v0-clone-with-nextjs-gpt4-copilotkit-3cmb)。 ![v0](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyutbegrv571lp3i6081.png) 如果您不熟悉,Vercel 的 V0 是一款人工智慧驅動的工具,可讓您根據提示產生 UI,以及許多其他有用的功能。 您可以使用`Next.js` 、 `GPT4`和`CopilotKit`建立 V0 的克隆。這篇文章名列前 7 名,總的來說,這是一個值得加入到您的作品集中的偉大專案。 您可以檢查[GitHub 儲存庫](https://github.com/Tabintel/v0-copilot-next)。 ### ✅[與您的履歷聊天](https://dev.to/copilotkit/how-to-build-the-with-nextjs-openai-1mhb)。 您可以閱讀本文,使用`Next.js` 、 `OpenAI`和`CopilotKit`來建立這個很棒的工具。 您不僅可以使用 ChatGPT 產生履歷,還可以將其匯出為 PDF,甚至可以透過與其對話來進一步改進它。多酷啊,對吧:) 您可以檢查[GitHub 儲存庫](https://github.com/TheGreatBonnie/AIPoweredResumeBuilder)。 --- 2. [Appwrite](https://github.com/appwrite/appwrite) - 減少後端的麻煩。 -------------------------------------------------------------- ![應用程式寫入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8x568uz21seyygw6b72z.png) ![帶有 appwrite 的 sdk 列表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cp7k8qnamsluto7eifpl.png) Appwrite 的開源平台可讓您將身份驗證、資料庫、函數和儲存體新增至您的產品中,並建立任何規模的任何應用程式、擁有您的資料並使用您喜歡的編碼語言和工具。 類似的選項是supabase,但儘管它們有相似之處,但它們在幾個方面有很大不同。 Restack 非常漂亮地涵蓋了[Appwrite 與 Supabase](https://www.restack.io/docs/supabase-knowledge-supabase-vs-appwrite) 。一探究竟! 他們有很好的貢獻指南,甚至不厭其煩地詳細解釋架構。 開始使用以下 npm 指令。 ``` npm install appwrite ``` 您可以像這樣建立一個登入元件。 ``` "use client"; import { useState } from "react"; import { account, ID } from "./appwrite"; const LoginPage = () => { const [loggedInUser, setLoggedInUser] = useState(null); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [name, setName] = useState(""); const login = async (email, password) => { const session = await account.createEmailSession(email, password); setLoggedInUser(await account.get()); }; const register = async () => { await account.create(ID.unique(), email, password, name); login(email, password); }; const logout = async () => { await account.deleteSession("current"); setLoggedInUser(null); }; if (loggedInUser) { return ( <div> <p>Logged in as {loggedInUser.name}</p> <button type="button" onClick={logout}> Logout </button> </div> ); } return ( <div> <p>Not logged in</p> <form> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} /> <button type="button" onClick={() => login(email, password)}> Login </button> <button type="button" onClick={register}> Register </button> </form> </div> ); }; export default LoginPage; ``` 您可以閱讀[文件](https://appwrite.io/docs)。 Appwrite 可以非常輕鬆地建立具有開箱即用的擴充功能的可擴展後端應用程式。 Appwrite 最近推出的「Init」發布了一些令人興奮的功能。對於我們可以用 init 做什麼,我並沒有達到 100% 的標準,所以請發表評論讓我們了解更多資訊。 它有一些很酷的功能,對於將我們的應用程式提升到一個新的水平非常有用。好奇心超載:D ![熱](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yflpzhvz7h0shs0dsrp8.png) 我很高興它可以連接到 Twilio、Vonage 和 Mailgun。更多選擇意味著更好的產品。 Appwrite 在 GitHub 上擁有 40k+ Stars,並且發布了`v1.5`版本。 {% cta https://github.com/appwrite/appwrite %} Star Appwrite ⭐️ {% endcta %} ### 🎯 使用 Appwrite 建立的熱門應用程式。 Appwrite 非常受歡迎,尤其是因為它的易用性。這些是一些很酷的專案,您可以從中獲得靈感。 ### ✅ [FoodMagic](https://github.com/Sameerkash/FoodMagic) - 擴增實境食品應用程式。 ![食物魔法](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/verpfy365uzrdyhopgdu.png) FoodMagic 是使用擴增實境和令人驚嘆的使用者介面的獨特食品配送服務。 它是使用`Appwrite`和 Flutter 建立的。 涉及 Appwrite 函數、資料庫、儲存和更多概念,因此您可以使用它學到很多東西。 您可以檢查[GitHub 儲存庫](https://github.com/TheGreatBonnie/aipoweredpresentation)。 ### ✅[回購評級員](https://github.com/EddieHubCommunity/RepoRater)。 此專案可讓您從開發者體驗 (DX) 的角度對 GitHub 儲存庫進行評分。 它是使用`Appwrite` 、 `Headless UI (React)` 、 `Next.js`和`Tailwind CSS`建立的。 您可以檢查[GitHub 儲存庫](https://github.com/TheGreatBonnie/aipoweredpresentation)並查看[即時執行情況](https://repo-rater.eddiehub.io/)。 ### ✅ [Twitter 克隆](https://www.youtube.com/watch?v=njLEDvoDjtk)- FreeCodeCamp (YouTube)。 它具有各種功能,例如使用電子郵件和密碼註冊和登入、發送文字、圖像和連結、辨識和儲存主題標籤、顯示推文、喜歡推文、轉發、評論/回應、關注用戶、搜尋用戶、顯示追蹤者、追蹤和最近的推文、編輯用戶個人資料、顯示帶有特定主題標籤的推文以及名為「Twitter Blue」的高級功能。 講師還實現了一個通知選項卡,當有人回覆您、追蹤您、喜歡您的推文或轉發時,該選項卡將顯示通知。在本教程結束時,您將擁有一個功能齊全的 Twitter 克隆,您可以對其進行進一步自訂和改進。意味著一切:) 他使用過`Flutter` 、 `Appwrite`和`Riverpod` ,並且教學超過 9 個小時,所以這是一個很長的教學。 ### ✅ [Dart 線上編譯器](https://github.com/aadarshadhakalg/Dart-Playground) ![飛鏢編譯器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfazlnc0j33nrlybsngr.png) 一個應用程式,用戶可以編寫和執行小型 dart 程序,而無需在系統中安裝 dart SDK。該應用程式使用 Appwrite 函數來執行 dart 程式碼。 它是使用`Appwrite`和`Flutter`建構的。 這使用了 Appwrite Auth、函數和資料庫來進行工作。 您可以檢查[GitHub 儲存庫](https://github.com/aadarshadhakalg/Dart-Playground)。 --- 3.[重新發送](https://github.com/resend)- 為開發人員提供的電子郵件 API。 ------------------------------------------------------ ![重發](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3auhh3hbxjmmzehe5v0.png) 您可以使用 React 建立和傳送電子郵件。 2023 年最受炒作的產品之一。 他們提供了大量的 SDK 選項,因此您不必從您首選的技術堆疊進行切換。 ![開發工具包](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1e8qmaxzk00t7etu4f0z.png) Resend 非常值得信賴,許多公司(例如 Payload 和 Dub)都使用它。您可以看到[客戶](https://resend.com/customers)清單。 開始使用以下 npm 指令。 ``` npm install @react-email/components -E ``` 這是將其與 next.js 專案整合的方法。 ``` import { EmailTemplate } from '@/components/email-template'; import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); export async function POST() { const { data, error } = await resend.emails.send({ from: '[email protected]', to: '[email protected]', subject: 'Hello world', react: EmailTemplate({ firstName: 'John' }), }); if (error) { return Response.json({ error }); } return Response.json(data); } ``` 您可以閱讀[文件](https://resend.com/docs/introduction)。 ![重發](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rer9ym187e4i9l11afkg.png) 如果您是教學人員,我推薦 YouTube 上的這個[播放清單系列](https://www.youtube.com/playlist?list=PL8HkCX2C5h0VVXsgSXtj2KXpoPATnMFeF),它涵蓋了大部分內容並且易於理解。 基本理念是一個簡單、優雅的介面,使您能夠在幾分鐘內開始發送電子郵件。它可以透過適用於您最喜歡的程式語言的 SDK 直接融入您的程式碼中。 出於顯而易見的原因,React email 在 GitHub 上擁有最高的星數(12k+),並且超過 5000 名開發人員在他們的應用程式中使用它。 {% cta https://github.com/resend %} 星標重新發送 ⭐️ {% endcta %} ### 🎯 使用重新發送發送電子郵件的熱門應用程式。 讓我們看看一些使用重新發送來發送電子郵件的應用程式。 ### ✅ [gitroom](https://github.com/gitroomhq/gitroom) 。 提前安排所有社群媒體貼文和文章。您也可以與其他團隊成員合作交換或購買貼文。 它是使用`NX (Monorepo)` 、 `NextJS (React)` 、 `NestJS` 、 `Prisma (Default to PostgreSQL)` 、 `Redis`和`Resend`建構的。 您可以檢查[GitHub 儲存庫](https://github.com/gitroomhq/gitroom)和[網站](https://gitroom.com/)。 Gitroom 在 GitHub 上有 3k+ Stars。 ### ✅[任何郵件](https://github.com/anymail/django-anymail)。 Anymail 可讓您使用您選擇的交易電子郵件服務提供者 (ESP) 在 Django 中傳送和接收電子郵件。 您可以檢查[GitHub 儲存庫](https://github.com/anymail/django-anymail)和[網站](https://anymail.dev/en/stable/)。他們在 GitHub 上有超過 1,500 個 Stars,並且正在發布 v10 版本。 ### ✅[徽章](https://github.com/projectx-codehagen/Badget)。 ![徽章](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xmfd2wzpfj0c0qmxkt22.png) Badget 旨在透過使用者友善的介面和強大的後端來簡化財務管理。 它是使用`Next.js 14` 、 `Turborepo` 、 `Drizzle ORM` 、 `Planetscale` 、 `Clerk` 、 `Resend` 、 `React Email` 、 `Shadcn/ui`和`Stripe`建置的。 您可以檢查[GitHub 儲存庫](https://github.com/projectx-codehagen/Badget)。 這個專案很快就會在 GitHub 上達到 2k Stars。 --- 4. [Shadcn UI](https://ui.shadcn.com/docs) - 您可以將其複製並貼上到應用程式中的元件。 ----------------------------------------------------------------- ![shadcn使用者介面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0xhp1p50dd3b51weao3b.png) 這個開源專案無需介紹。 由於其簡單性、自訂選項和靈活性,它一推出就受到了熱烈歡迎。 然而,我確實同意它並不像看起來那麼簡單,特別是如果您不熟悉它的語法和結構。 開始使用以下命令(Next.js 應用程式)。 ``` npx shadcn-ui@latest init ``` 其餘的將自動完成,您可以匯入[元件](https://ui.shadcn.com/docs/components/accordion)並相應地使用它們。 您可以根據您使用的框架閱讀[文件](https://ui.shadcn.com/docs)和[安裝指南](https://ui.shadcn.com/docs/installation)。 Shadcn UI 在 GitHub 上擁有超過 55,000 顆星,並被超過 3,000 名開發者使用。 {% cta https://ui.shadcn.com/docs %} Star Shadcn UI ⭐️ {% endcta %} ### 🎯 使用 Shadcn UI 建立的熱門應用程式。 我不會介紹非常簡單的專案,所以不用擔心。 ### ✅ [10000+ shadcn/ui 主題](https://github.com/jln13x/ui.jln.dev/)。 ![10000+ 主題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ywbrkhizpjqogtrk7svm.png) 有了這個,您可以探索、保存、產生新主題,甚至對隨機主題進行投票。您可以使用的好專案之一。 使用者介面也很糟糕。 它是使用很多套件建構的,例如`react-query` 、 `Framer` 、 `Zod` ,當然還有`shadcn ui` 。 您可以查看[GitHub 儲存庫](https://github.com/jln13x/ui.jln.dev/)和[現場演示](https://ui.jln.dev/)。 它在 GitHub 上有 600 多個 Star。 ### ✅[開啟 v0](https://github.com/raidendotai/openv0) 。 ![開放v0](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rubuowp2oerrexy9adp1.png) 我正在報道 v0.dev 但意識到它不是開源的。 我不會放棄這個想法。 Openv0 是另一個使用 AI 產生 UI 元件的專案。元件產生是一個多通道管道 - 每個通道都是一個完全獨立的插件。 它支援 React、Next.js 和 Svelte 等前端框架。使用 Flowbite、NextUI 和 Shadcn 建置。 檢查[GitHub 儲存庫](https://github.com/raidendotai/openv0)並閱讀[安裝指南](https://github.com/raidendotai/openv0?tab=readme-ov-file#install)。 您也可以在[Replit](https://replit.com/@n-raidenai/openv0-react)上執行它。它在 GitHub 上有 3k+ Stars。 很多專案都使用Shadcn,請自行探索。 --- 5. [Buildship](https://buildship.com/) - 低程式碼視覺化後端建構器。 ------------------------------------------------------ ![建造船](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rzlrynz5xephv4t9layd.png) 對於您正在使用無程式碼應用程式建構器(FlutterFlow、Webflow、Framer、Adalo、Bubble、BravoStudio...)或前端框架(Next.js、React、Vue...)建立的應用程式,您需要一個後端來支援可擴展的 API、安全工作流程、自動化等。 BuildShip 為您提供了一種完全視覺化的方式,可以在易於使用的完全託管體驗中可擴展地建立這些後端任務。 這意味著您無需在雲端平台上爭論或部署事物或執行 DevOps。只需立即建造和發貨 🚀 他們甚至與 TypeSense 合作並且發展得非常快! ![建造船](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6oc3rc713mjg9cwqj7d4.png) 我嘗試過Buildship,它很強大。 {% cta https://github.com/rowyio/buildship %} 明星建造 ⭐️ {% endcta %} ### 🎯 使用 Buildship 建立的熱門應用程式。 大多數資源都是影片,但值得一看。 YouTube 官方頻道上有很多教程,但以下是一些有趣的教程。 ### ✅[使用低程式碼和 AI 建立旅遊 WebApp](https://www.youtube.com/watch?v=Pj08uTOzNPQ) 。 ![旅行應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8c179msfljpnesbrf4vi.png) 它是使用`Buildship`和`Locofy`建構的。 Locofy.ai 用於從設計到應用程式前端的過渡,而 BuildShip.com 用於應用程式的後端。 它還計算實時距離和旅程成本。他們使用 Figma 來源進行設計。 ### ✅ [Telegram 上的人工智慧助理](https://www.youtube.com/watch?v=Pz1t1KCnrbs)。 您可以使用 OpenAI Assistant 和 BuildShip 建立智慧型 Telegram 機器人,而無需編碼。這將幫助您與資料聊天。看起來很令人興奮,對吧:) ### ✅ [AI YouTube 時間戳產生器](https://www.youtube.com/watch?v=7DkLUY6kfTg)。 ![時間戳生成器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b2wv0s9mz9wpuez4egee.png) 相信我,使用本教程您會學到很多東西。您可以查看開發人員上[未發布的有關自訂提示的帖子](https://dev.to/jamesmurdza/building-a-fcg-temp-slug-4578922?preview=4210cdff8fea25a8cd4d81363155c451b20e6484504a41fa0f0d992a272c21a3a707c0cb6ddac2f740234c032a02af5ce442841ad4033efc46424c84)。 您可以檢查[前端程式碼](https://github.com/jamesmurdza/timestamp-generator-app/)。 --- 6. [Taipy](https://github.com/Avaiga/taipy) - 將資料和人工智慧演算法整合到生產就緒的 Web 應用程式中。 ---------------------------------------------------------------------------- ![打字](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/deak7rre409rzv5j5viv.png) Taipy 是一個開源 Python 庫,可用於輕鬆的端到端應用程式開發,具有假設分析、智慧管道執行、內建調度和部署工具。 我相信你們大多數人都不明白 Taipy 用於為基於 Python 的應用程式建立 GUI 介面並改進資料流管理。 因此,您可以繪製資料集的圖表,並使用類似 GUI 的滑桿來提供使用其他實用功能來處理資料的選項。 雖然 Streamlit 是一種流行的工具,但在處理大型資料集時,其效能可能會顯著下降,這使得它在生產級使用上不切實際。 另一方面,Taipy 在不犧牲性能的情況下提供了簡單性和易用性。透過嘗試 Taipy,您將親身體驗其用戶友好的介面和高效的資料處理。 在底層,Taipy 利用各種函式庫來簡化開發並增強功能。 ![圖書館](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9xts3nof4uapr7dakrl.png) 開始使用以下命令。 ``` pip install taipy ``` 他們還使用分散式運算提高了效能,但最好的部分是 Taipy,它的所有依賴項現在都與 Python 3.12 完全相容,因此您可以在使用 Taipy 進行專案的同時使用最新的工具和程式庫。 您可以閱讀[文件](https://docs.taipy.io/en/latest/)。 另一個有用的事情是,Taipy 團隊提供了一個名為[Taipy Studio](https://docs.taipy.io/en/latest/manuals/studio/)的 VSCode 擴充功能來加速 Taipy 應用程式的建置。 ![太皮工作室](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kc1umm5hcxes0ydbuspb.png) 如果您想閱讀部落格來了解程式碼庫結構,您可以閱讀 HuggingFace[的使用 Taipy 在 Python 中為您的 LLM 建立 Web 介面](https://huggingface.co/blog/Alex1337/create-a-web-interface-for-your-llm-in-python)。 Taipy 在 GitHub 上有 8k+ Stars,並且處於`v3`版本,因此它們正在不斷改進。 {% cta https://github.com/Avaiga/taipy %} Star Taipy ⭐️ {% endcta %} ### 🎯 使用 Taipy 建立的熱門應用程式。 嘗試新技術通常很困難,但 Taipy 提供了 10 多個演示教程,其中包含程式碼和適當的文件供您遵循。我們將看到開發人員建構的其他一些專案。 ### ✅[錢包方面](https://github.com/Ujj1225/from_Taipy-walletWISE)。 ![錢包明智](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vva4tu9dxrz9fgaiavlb.png) WalletWise 就像是我們財務的友善幫手,幫助我們追蹤收入和支出。它使用 Gemini 進行交易,使用 Taipy 來了解支出。 對使用者的收入和支出進行分析,以數學方式顯示,並顯示 7 個做出更好、更明智的財務決策的提示。 它還具有視覺化工具,您可以在其中查看不同的標題,以了解有關您的支出的更多資訊。 就創造力而言,這是下面提到的所有內容中最好的。 ### ✅[人口普查](https://github.com/SusheelThapa/from_taipy_census)。 透過由 Taipy 提供支持的「人口普查」專案,將資料編織到動態視覺化中,揭開 2021 年尼泊爾的住房和人口故事。 這有很多選擇,所以如果您想用更少的錢學到更多,這是最好的選擇! ### ✅[太皮象棋](https://github.com/KorieDrakeChaney/taipy-chess)。 ![棋](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xasxqldf7z1q5ie3r4nn.png) 所有應用程式中我最喜歡的一個,因為我喜歡國際象棋。哈哈! 這是一個基於20,000盤棋的國際象棋視覺化工具。您可以查看所有比賽、他們參加的開局、對手、表現最好的開局以及最成功的開局。您可以查看資料的熱圖和圖表。 您還可以查看[Olympic Medals Taipy 應用程式](https://github.com/enarroied/Olympic-Medals-Taipy-App),該應用程式提供了一個儀表板,其中包含有關奧運獎牌、 [Covid 儀表板](https://covid-dashboard.taipy.cloud/Country)和[資料視覺化的](https://production-planning.taipy.cloud/Data-Visualization)訊息。 --- 7. [xyflow](https://github.com/xyflow/xyflow) - 使用 React 建立基於節點的 UI。 -------------------------------------------------------------------- ![XY流](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yevpzvqpt3u6ahkqdrsl.png) XYFlow 是一個強大的開源程式庫,用於使用 React 或 Svelte 建立基於節點的 UI。它是一個單一的倉庫,提供[React Flow](https://reactflow.dev)和[Svelte Flow](https://svelteflow.dev) 。讓我們更多地了解可以使用 React flow 做什麼。 ![反應流](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mzezlna4v4bx75z3omr.png) 您可以觀看此影片,在 60 秒內了解 React Flow。 {% 嵌入 https://www.youtube.com/watch?v=aUBWE41a900 %} 有些功能在專業模式下可用,但免費層中的功能足以形成一個非常互動的流程。 React 流程以 TypeScript 編寫並使用 Cypress 進行測試。 開始使用以下 npm 指令。 ``` npm install reactflow ``` 以下介紹如何建立兩個節點( `Hello`和`World` ,並透過邊連接。節點具有預先定義的初始位置以防止重疊,並且我們還應用樣式來確保有足夠的空間來渲染圖形。 ``` import ReactFlow, { Controls, Background } from 'reactflow'; import 'reactflow/dist/style.css'; const edges = [{ id: '1-2', source: '1', target: '2' }]; const nodes = [ { id: '1', data: { label: 'Hello' }, position: { x: 0, y: 0 }, type: 'input', }, { id: '2', data: { label: 'World' }, position: { x: 100, y: 100 }, }, ]; function Flow() { return ( <div style={{ height: '100%' }}> <ReactFlow nodes={nodes} edges={edges}> <Background /> <Controls /> </ReactFlow> </div> ); } export default Flow; ``` 這就是它的樣子。您還可以新增標籤、更改類型並使其具有互動性。 ![你好世界](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xzerdd3ng0vtnz5rbgau.png) 您可以在 React Flow 的 API 參考中查看[完整的選項清單](https://reactflow.dev/api-reference/react-flow)以及元件、鉤子和實用程式。 最好的部分是您還可以加入[自訂節點](https://reactflow.dev/learn/customization/custom-nodes)。在您的自訂節點中,您可以渲染您想要的一切。您可以定義多個來源和目標句柄並呈現表單輸入或圖表。您可以查看此[codesandbox](https://codesandbox.io/p/sandbox/pensive-field-z4kv3w?file=%2FApp.js&utm_medium=sandpack)作為範例。 您可以閱讀[文件](https://reactflow.dev/learn)並查看 Create React App、Next.js 和 Remix 的[範例 React Flow 應用程式](https://github.com/xyflow/react-flow-example-apps)。 React Flow 附帶了幾個額外的[插件](https://reactflow.dev/learn/concepts/plugin-components)元件,可以幫助您使用 Background、Minimap、Controls、Panel、NodeToolbar 和 NodeResizer 元件製作更高級的應用程式。 例如,您可能已經注意到許多網站的背景中有圓點,增強了美觀性。要實現此模式,您可以簡單地使用 React Flow 中的後台元件。 ``` import { Background } from 'reactflow'; <Background color="#ccc" variant={'dots'} /> // this will be under React Flow component. Just an example. ``` ![背景元件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/en2tl17ef31nydaycw18.png) 如果您正在尋找一篇快速文章,我建議您查看 Webkid 的[React Flow - A Library for Rendering Interactive Graphs](https://webkid.io/blog/react-flow-node-based-graph-library/) 。 React Flow 由 Webkid 開發和維護。 它在 GitHub 上有超過 19k 顆星星,並且在`v11.10.4`上顯示它們正在不斷改進,npm 套件每週下載量超過 40 萬次。您可以輕鬆使用的最佳專案之一。 ![統計資料](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o99csz9epqmai3ixt859.png) {% cta https://github.com/xyflow/xyflow %} 星 xyflow ⭐️ {% endcta %} ### 🎯 使用 React Flow 建立的熱門應用程式。 很多公司都使用 React flow,例如 Zapier 和 Stripe。夠可信,可以使用。我不會介紹使用 Svelte Flow 製作的應用程式,因為 React 更受歡迎。 ### ✅[條紋文件](https://docs.stripe.com/payments/checkout/how-checkout-works#lifecycle)。 ![條紋](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/crma9z46y0u2m5p3z9wa.png) Stripe 使用它,特別是在展示結帳的工作原理時。 您可以閱讀[完整的文件](https://stripe.com/docs)。 ### ✅[著色蛙](https://shaderfrog.com/2/editor/cln84z4950000pan66v5fcunv)。 ![著色蛙](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ri9aw9vynoiflkbvclq.png) 我選擇這個是因為這個專案很酷。 ### ✅ [類型](https://www.typeform.com/help/a/use-the-logic-map-to-add-logic-to-your-forms-5514792640916/)。 ![打字機](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48gc4m8ewm4j65luuavs.png) Typeform 使用它來展示如何使用邏輯圖為表單新增邏輯。 您也可以發現它被用於[FlowwiseAI](https://flowiseai.com/)和[Doubleloop](https://app.doubleloop.app/strategy/2236/map) 。想讓您知道,Supabase 是 GitHub 上 XYflow 的贊助商之一。 --- 8. [Pieces](https://github.com/pieces-app) - 您的工作流程副駕駛。 ------------------------------------------------------- ![件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qf2qgqtpv78fxw5guqm5.png) Pieces 是一款支援人工智慧的生產力工具,旨在透過智慧程式碼片段管理、情境化副駕駛互動和主動呈現有用材料來幫助開發人員管理混亂的工作流程。 它最大限度地減少了上下文切換、簡化了工作流程並提升了整體開發體驗,同時透過完全離線的 AI 方法維護了工作的隱私和安全性。太棒了:D ![整合](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2ro3rcwnqp4qrmv5e8s.png) 它與您最喜歡的工具無縫集成,以簡化、理解和提升您的編碼流程。 它具有比表面上看到的更令人興奮的功能。 - 它可以透過閃電般快速的搜尋體驗找到您需要的材料,讓您根據您的喜好透過自然語言、程式碼、標籤和其他語義進行查詢。可以放心地說“您的個人離線谷歌”。 - Pieces 使用 OCR 和 Edge-ML 升級螢幕截圖,以提取程式碼並修復無效字元。因此,您可以獲得極其準確的程式碼提取和深度元資料豐富。 您可以查看 Pieces 可用[功能的完整清單](https://pieces.app/features)。 您可以閱讀[文件](https://docs.pieces.app/)並存取[網站](https://pieces.app/)。 他們為 Pieces OS 用戶端提供了一系列 SDK 選項,包括[TypeScript](https://github.com/pieces-app/pieces-os-client-sdk-for-typescript) 、 [Kotlin](https://github.com/pieces-app/pieces-os-client-sdk-for-kotlin) 、 [Python](https://github.com/pieces-app/pieces-os-client-sdk-for-python)和[Dart](https://github.com/pieces-app/pieces-os-client-sdk-for-dart) 。 {% cta https://github.com/pieces-app/ %} 星星碎片 ⭐️ {% endcta %} ### 🎯 用 Pieces 建置的熱門應用程式。 由於它更像是一個工具,因此不會有那麼多專案,但開發人員仍然使用它來建立很棒的專案。 ### ✅[辦公桌夥伴](https://github.com/ayothekingg/deskbuddy)。 一個社區專案,可透過分析和 Copilot Conversation 幫助您了解、評估和改善您的編碼習慣。 使用的主要語言是 TypeScript。 您可以檢查[GitHub 儲存庫](https://github.com/ayothekingg/deskbuddy)。 ### ✅ [CLI 代理](https://github.com/pieces-app/cli-agent)。 一個全面的命令列介面 (CLI) 工具,旨在與 Pieces OS 無縫互動。它提供了一系列功能,例如資產管理、應用程式互動以及與各種 Pieces OS 功能的整合。 使用的主要語言是Python。 您可以檢查[GitHub 儲存庫](https://github.com/pieces-app/cli-agent)。 ### ✅ [Streamlit 和碎片](https://github.com/pieces-app/pieces-copilot-streamlit-example)。 Pieces Copilot Streamlit Bot 是一款使用 Streamlit 建立的互動式聊天機器人應用程式,旨在為用戶提供無縫介面來即時提問和接收答案。 使用的主要語言是Python。 您可以檢查[GitHub 儲存庫](https://github.com/pieces-app/pieces-copilot-streamlit-example)。 --- 9. [Typesense](https://github.com/typesense/typesense) - 快速、容錯、記憶體中模糊搜尋引擎。 -------------------------------------------------------------------------- ![類型感](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4uc2r5owew7bkeckc81n.png) Typesense 是一款開源、容錯的搜尋引擎,針對即時(通常低於 50 毫秒)的即輸入即搜尋體驗和開發人員工作效率進行了最佳化。 如果您聽說過 ElasticSearch 或 Algolia,那麼考慮 Typesense 的一個好方法是,它是 Algolia 的開源替代品,解決了一些關鍵問題,並且是 ElasticSearch 的更易於使用、包含電池的替代品。 您可以在[Algolia vs ElasticSearch vs Meilsearch vs Typesense](https://typesense.org/typesense-vs-algolia-vs-elasticsearch-vs-meilisearch/)中對它們進行比較。 它是一個快速、容錯、內存中模糊搜尋引擎,用於建置令人愉快的搜尋體驗 ![特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dj1dov237eyg662vqw6y.png) 您可以使用此指令安裝 Typesense 的 python 用戶端。 ``` pip install typesense ``` 根據文件,在這些情況下不應使用 Typesense。 A。 Typesense 不應用作主資料存儲,它存儲資料的唯一副本。 b. Typesense 通常不太適合搜尋應用程式日誌。 您可以閱讀[文件](https://typesense.org/docs/)和[安裝指南](https://github.com/typesense/typesense?tab=readme-ov-file#install)。 ![類型感](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bwf0c9jgjrju1xtrwfqv.png) 我建議您閱讀快速入門[指南](https://typesense.org/docs/guide/#quick-start),該指南將逐步指導您如何安裝和建立搜尋 UI。他們還提供了高達 28M 的資料集的明確[基準測試](https://typesense.org/docs/overview/benchmarks.html#typesense-benchmarks),以便您可以檢查將獲得的效能。 如果您更喜歡教程,那麼我建議您觀看這個[YouTube 影片](https://www.youtube.com/watch?v=kwtHOkf7Jdg)。您將獲得 Typesense 的概述,作者將向您展示端到端演示。 TypeSense 在 GitHub 上有 17k+ Stars,而且版本為 26,這真是太瘋狂了。它是使用 C++ 建構的。 {% cta https://github.com/typesense/typesense?tab=readme-ov-file %} 明星 Typesense ⭐️ {% endcta %} ### 🎯 使用 Typesense 建立的熱門應用程式。 一些使用 Typesense 的現場演示和應用程式。 ### ✅ 現場示範。 他們還提供現場演示,展示 Typesense 在大型資料集上的實際應用,例如: - [從 Linux 核心搜尋 1M Git 提交訊息](linux-commits-search.typesense.org)- [GitHub Repo](https://github.com/typesense/showcase-linux-commits-search) - [從 MusicBrainz 搜尋 3200 萬首歌曲資料集](songs-search.typesense.org)- [GitHub Repo](https://github.com/typesense/showcase-songs-search) - [具有預先輸入功能的拼字檢查器,包含 333K 英文單字](spellcheck.typesense.org)- [GitHub Repo](https://github.com/typesense/showcase-spellcheck) - [從 OpenLibrary 搜尋 28M 圖書資料集](books-search.typesense.org)- [GitHub Repo](https://github.com/typesense/showcase-books-search) - [GeoSearch / 瀏覽體驗](airbnb-geosearch.typesense.org)- [GitHub Repo](https://github.com/typesense/showcase-airbnb-geosearch) - [電子商務瀏覽與搜尋體驗](https://ecommerce-store.typesense.org/)- [GitHub Repo](https://github.com/typesense/showcase-ecommerce-store) - [搜尋 2M 烹飪食譜](https://recipe-search.typesense.org/)- [GitHub Repo](https://github.com/typesense/showcase-recipe-search) 其他一些公司使用 Typesense 雲端來完成整個工作。 ![類型感知雲](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0kn37bf908emr04ahilo.png) 這些公司包括 Codecademy、Logitech、Buildship、n8n 和 Storipress CMS。 --- 10. [Payload](https://github.com/payloadcms/payload) - 建立未來網路的最快方式。 ------------------------------------------------------------------- ![有效負載](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h79j0zte5eo7n32639jy.png) 建立現代後端 + 管理 UI 的最佳方式。 Payload 沒有黑魔法,全是 TypeScript,並且完全開源,它既是一個應用程式框架,也是一個無頭 CMS。我全心全意欽佩的少數專案之一。 他們的網站擁有最乾淨的使用者介面之一,我看過 1000 多個網站,其中包括非常瘋狂的網站。快去看看吧! ![有效負載客戶](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ep8brjas1iaptifw97e.png) 您可以觀看這段 YouTube 影片,其中 James(聯合創始人)談論了 Payload CMS 簡介以及它如何縮小 Headless CMS 和應用程式框架之間的差距。 {% 嵌入 https://www.youtube.com/watch?v=In\_lFhzmbME %} 簡而言之,Payload 是一個無頭 CMS 和應用程式框架。它旨在為您的開發過程提供巨大的推動力,但重要的是,當您的應用程式變得更加複雜時,請不要妨礙您。 開始使用以下命令。 ``` npx create-payload-app@latest ``` 您可以閱讀 Payload 與普通 CMS 不同的完整[功能清單](https://github.com/payloadcms/payload?tab=readme-ov-file#-features)。 如果您是 next.js 的粉絲,我建議您閱讀[The Ultimate Guide To Use Next.js with Payload](https://payloadcms.com/blog/the-ultimate-guide-to-using-nextjs-with-payload) 。 您可以閱讀[文件](https://payloadcms.com/docs)和[安裝指南](https://payloadcms.com/docs/getting-started/installation)。 v3 beta 版本的有效負載也變得很困難,所以請密切注意。 Payload 在 GitHub 上擁有 19k+ Stars,並被 8k+ 開發者使用。 {% cta https://github.com/payloadcms/payload %} 明星有效負載 ⭐️ {% endcta %} ### 🎯 使用 Payload 的熱門應用程式 + 模板。 我們將看到可協助您將 Payload 用於特定用例的範本和應用程式。 ### ✅[混音和有效負載](https://github.com/payloadcms/remix-server) 帶有 Remix 和 Payload 的單聲道儲存庫範本。 這可以幫助您設定 Payload CMS 與 Remix 一起進行內容管理,從而將每個應用程式分為其套件(包括 Express 伺服器應用程式)。 ### ✅ [Astro 和有效負載](https://github.com/mooxl/astroad) 這是 Astro 和 Payloadcms 的預先配置設置,旨在讓您輕鬆開始建立網站。借助 Astroad,您將擁有一個可以使用 Docker 在本地執行的完整開發環境。此設定簡化了將網站部署到生產環境之前的測試和開發。 ### ✅[電子商務範本](https://github.com/payloadcms/payload/tree/main/templates/ecommerce)。 他們還提供了一個電子商務模板,可幫助您更專注於業務策略而不是技術。您的 API 是您自己的,您的資料也屬於您。您無需依賴第三方服務,這些服務可能會在每月費用之外向您收取 API 超額費用,並可能限制您對資料庫的存取。經營線上商店的成本永遠不會超過伺服器的成本(加上支付處理費)。 開始做一些我們不喜歡的事情總是感覺很奇怪,因此您可以閱讀[如何使用使用此範本的 Next.js 建立電子商務網站](https://payloadcms.com/blog/how-to-build-an-e-commerce-site-with-nextjs)。 使用 Payload 的一些流行公司包括[Speechify](https://speechify.com/) 、 [Bizee](https://bizee.com/)等。 閱讀以下案例研究。他們將告訴您 Payload 的功能以及它如何奠定堅實的基礎。 ### ✅[快速犁](https://miquikplow.com/) ![快犁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4k2rlwzf1u3vnfe1yxr.png) Quikplow 是一個創新的隨選服務平台,通常被稱為「掃雪機的 Uber」。 Quikplow 為其應用程式開發和部署功能齊全的後端的速度不僅是無與倫比的,而且幾乎是聞所未聞的。整個應用程式涵蓋身份驗證、基於位置的搜尋、電子商務功能等,開發時間不到 120 天。 前所未有的速度歸功於 Payload 的身份驗證、CRUD 操作和管理面板生成功能,為 Quikplow 節省了寶貴的開發時間和預算資源。 ### ✅[紙三角形](https://www.papertriangles.com/) ![紙三角形](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73t78tva710qj5owgos1.png) Paper Triangles 需要在線展示,以反映其著名的沉浸式體驗,但發現自己受到過時且緩慢的內容管理系統的限制。 與他們的代理商合作夥伴 Old Friends 合作,面臨的挑戰是建立一個能夠反映他們尖端工作的網站 - 需要自動播放影片、動態動畫、整合式相機庫等,而不犧牲內容更新的速度或便利性。 有效負載成為完美的選擇。它的開源特性以及 TypeScript 和 React 的強大基礎使其成為開發高度客製化的互動式前端的理想選擇。 對於像 Old Friends 這樣的代理商來說,Payload 是向 Paper Triangles 這樣的客戶兌現承諾的最佳選擇。 「Payload 為我們的客戶提供了易於使用的介面,並為我們提供了執行客製化設計所需的開發自由度,」Old Friends 的設計工程師 James Clements 說。 ### ✅[比茲](https://bizee.com/) ![比西](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1mai4bkcu4vdi3138gqm.png) 他們需要在短短三個月內遷移和檢修 2,500 個頁面,同時重新建立新的 CMS 平台,並在全面品牌重塑下實施全面的網站重新設計。 為了兌現對 Bizee 的承諾,Ritters(代理商)依靠 Payload 乾淨的、TypeScript 驅動的架構,事實證明該架構具有變革性,簡化了設計整合並確保了無錯誤、可維護的程式碼。這加速了內容遷移並保留了 SEO 和用戶體驗。 它甚至促進了從設計到開發的過程,幫助 Riotters 將 Figma 概念轉化為實際實施。 至關重要的是,Payload 與 Next.js 的天然協同作用促進了開發人員、設計師、UX 專業人員、QA 團隊和行銷人員之間的跨職能協作。 有許多公司決定使用 Payload,這是他們做出的最佳決定之一。不管怎樣,去探索你能用它做什麼。 --- 哇! 這花了我很長很長的時間來寫。我希望你喜歡它。 我得到它! 建立良好的長期副專案可能很困難,但即使是一個簡單的用例也可以帶來顯著的成果。誰知道?從長遠來看,您甚至可能會獲得對您有幫助的直接機會。 我試圖涵蓋每個專案製作的最好和最有用的應用程式。 不管怎樣,請讓我們知道您的想法以及您計劃在未來建立任何可擴展的副專案嗎? 祝你有美好的一天!直到下一次。 請在 Twitter 上關注我,我將非常感激。 |如果你喜歡這類東西, 請關注我以了解更多:) | [![用戶名 Anmol_Codes 的 Twitter 個人資料](https://img.shields.io/badge/Twitter-d5d5d5?style=for-the-badge&logo=x&logoColor=0A0209)](https://twitter.com/Anmol_Codes) [![用戶名 Anmol-Baranwal 的 GitHub 個人資料](https://img.shields.io/badge/github-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/Anmol-Baranwal) [![用戶名 Anmol-Baranwal 的 LinkedIn 個人資料](https://img.shields.io/badge/LinkedIn-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/Anmol-Baranwal/) | |------------|----------| 請關注 CopilotKit 以了解更多此類內容。 {% 嵌入 https://dev.to/copilotkit %} --- 原文出處:https://dev.to/copilotkit/30-app-ideas-with-complete-source-code-5f76

使用 useReducer 和 Typescript 回應上下文。

--- 標題:使用 useReducer 和 Typescript 反應上下文。 發表:真實 說明:將 React Context API 與強類型化的縮減器和操作一起使用。 標籤: typescript、react、上下文、reducers --- [只是程式碼嗎?](https://codesandbox.io/s/context-reducer-ts-9ctis) React 應用程式中有很多處理狀態的選項。顯然你可以使用`setState`來處理一些小的邏輯,但是如果你有一個複雜的狀態需要管理怎麼辦? 也許您會使用 Redux 或 MobX 來處理這種情況,但也可以選擇使用 React Context,並且您不必安裝其他依賴項。 讓我們看看如何使用 Context API 和 Typescript 來管理複雜的狀態。 > 在本教程中,我們將建立一個帶有購物車計數器的產品清單。 首先,使用`create-react-app`建立一個新的 React 專案。 ``` npx create-react-app my-app --template typescript cd my-app/ ``` 接下來,在`src`目錄中建立一個新的`context.tsx`檔案。 ``` /*context.tsx*/ import React, { createContext } from 'react'; const AppContext = createContext({}); ``` 您可以使用任何您想要的值來初始化上下文 api,就這麼簡單,在本例中,我使用的是一個空物件。 現在讓我們建立一個初始狀態,其中產品清單為零,購物車計數器為零。另外,讓我們為此加入一些類型。 ``` /*context.tsx*/ import React, { createContext } from 'react'; type ProductType = { id: number; name: string; price: number; } type InitialStateType = { products: ProductType[]; shoppingCart: number; } const initialState = { products: [], shoppingCart: 0, } const AppContext = createContext<InitialStateType>(initialState); ``` 產品清單中的每個產品都有一個 ID、名稱和價格。 現在我們將使用減速器和操作來建立和刪除產品,並將購物車計數器加一。首先,建立一個名為`reducers.ts`的新檔案。 ``` /*reducers.ts*/ export const productReducer = (state, action) => { switch (action.type) { case 'CREATE_PRODUCT': return [ ...state, { id: action.payload.id, name: action.payload.name, price: action.payload.price, } ] case 'DELETE_PRODUCT': return [ ...state.filter(product => product.id !== action.payload.id), ] default: return state; } } export const shoppingCartReducer = (state, action) => { switch (action.type) { case 'ADD_PRODUCT': return state + 1; } } ``` 一個reducer函數接收兩個參數,第一個是狀態,我們在使用`useReducer`鉤子時傳遞,第二個是一個物件,表示事件和一些將改變狀態(動作)的資料。 在本例中,我們建立兩個減速器,一個用於產品,另一個用於購物車。在產品縮減器上,我們新增了兩個操作,一個用於建立新產品,另一個用於刪除任何產品。對於購物車減速器,我們加入的唯一操作是每次加入新產品時增加計數器。 正如您所看到的,為了建立產品,我們傳遞 id、名稱和價格,並使用新物件返回當前狀態。要刪除一個,我們只需要 id ,返回的是狀態,但沒有具有該 id 的產品。 現在讓我們更改上下文文件以導入這些減速器函數。 ``` /*context.tsx*/ import React, { createContext, useReducer } from 'react'; import { productReducer, shoppingCartReducer } from './reducers'; type ProductType = { id: number; name: string; price: number; } type InitialStateType = { products: ProductType[]; shoppingCart: number; } const intialState = { products: [], shoppingCart: 0, } const AppContext = createContext<{ state: InitialStateType; dispatch: React.Dispatch<any>; }>({ state: initialState, dispatch: () => null }); const mainReducer = ({ products, shoppingCart }, action) => ({ products: productReducer(products, action), shoppingCart: shoppingCartReducer(shoppingCart, action), }); const AppProvider: React.FC = ({ children }) => { const [state, dispatch] = useReducer(mainReducer, initialState); return ( <AppContext.Provider value={{state, dispatch}}> {children} </AppContext.Provider> ) } export { AppContext, AppProvider }; ``` 有一個`mainReducer`函數,它結合了我們將擁有的兩個減速器(產品減速器和購物車減速器),每個減速器管理狀態的選定部分。 此外,我們建立了`AppProvider`元件,在其中, `useReducer`鉤子採用`mainReducer`和初始狀態來傳回`state`和`dispatch` 。 我們將這些值傳遞到`AppContext.Provider`中,這樣做我們可以使用`useContext`鉤子存取`state`和`dispatch` 。 接下來,為減速器和操作加入這些類型。 ``` /*reducers.ts*/ type ActionMap<M extends { [index: string]: any }> = { [Key in keyof M]: M[Key] extends undefined ? { type: Key; } : { type: Key; payload: M[Key]; } }; export enum Types { Create = 'CREATE_PRODUCT', Delete = 'DELETE_PRODUCT', Add = 'ADD_PRODUCT', } // Product type ProductType = { id: number; name: string; price: number; } type ProductPayload = { [Types.Create] : { id: number; name: string; price: number; }; [Types.Delete]: { id: number; } } export type ProductActions = ActionMap<ProductPayload>[keyof ActionMap<ProductPayload>]; export const productReducer = (state: ProductType[], action: ProductActions | ShoppingCartActions) => { switch (action.type) { case Types.Create: return [ ...state, { id: action.payload.id, name: action.payload.name, price: action.payload.price, } ] case Types.Delete: return [ ...state.filter(product => product.id !== action.payload.id), ] default: return state; } } // ShoppingCart type ShoppingCartPayload = { [Types.Add]: undefined; } export type ShoppingCartActions = ActionMap<ShoppingCartPayload>[keyof ActionMap<ShoppingCartPayload>]; export const shoppingCartReducer = (state: number, action: ProductActions | ShoppingCartActions) => { switch (action.type) { case Types.Add: return state + 1; default: return state; } } ``` 我從這篇[文章](https://medium.com/hackernoon/finally-the-typescript-redux-hooks-events-blog-you-were-looking-for-c4663d823b01)中採用了這種方法,基本上我們正在檢查使用了哪個`action.type` ,並根據該方法,我們產生有效負載的類型。 --- **筆記** 您可以採取的另一種方法是使用像這樣的`Discriminated unions` 。 ``` type Action = | { type: 'ADD' } | { type: 'CREATE', create: object } | { type: 'DELETE', id: string }; ``` 在前面的程式碼中,所有這些類型都有一個稱為 type 的公共屬性。 Typescript 將為可區分的聯合建立類型保護,並讓我們現在根據我們正在使用的類型以及物件類型具有的其他屬性。 但在本教程中,我們為操作`type`和`payload`使用兩個常見屬性,並且`payload`物件類型會根據`type`而變化,因此可區分的聯合類型將無法運作。 --- 現在,讓我們將定義的類型匯入到`context`文件中。 ``` /*context.tsx*/ import React, { createContext, useReducer, Dispatch } from 'react'; import { productReducer, shoppingCartReducer, ProductActions, ShoppingCartActions } from './reducers'; type ProductType = { id: number; name: string; price: number; } type InitialStateType = { products: ProductType[]; shoppingCart: number; } const initialState = { products: [], shoppingCart: 0, } const AppContext = createContext<{ state: InitialStateType; dispatch: Dispatch<ProductActions | ShoppingCartActions>; }>({ state: initialState, dispatch: () => null }); const mainReducer = ({ products, shoppingCart }: InitialStateType, action: ProductActions | ShoppingCartActions) => ({ products: productReducer(products, action), shoppingCart: shoppingCartReducer(shoppingCart, action), }); const AppProvider: React.FC = ({ children }) => { const [state, dispatch] = useReducer(mainReducer, initialState); return ( <AppContext.Provider value={{state, dispatch}}> {children} </AppContext.Provider> ) } export { AppProvider, AppContext }; ``` 不要忘記用`AppProvider`包裝您的主要元件。 ``` /* App.tsx */ import React from 'react'; import { AppProvider } from './context'; import Products from './products'; const App = () => { <AppProvider> // your stuff <Products /> </AppProvider> } export default App ``` 建立一個`Products`元件並在其中加入以下程式碼。 ``` /* Products.tsx */ import React, { useContext } from 'react'; import { AppContext } from './context'; import { Types } from './reducers'; const Products = () => { const { state, dispatch } = useContex(AppContext); return ( <div> <button onClick={() => { dispatch({ type: Types.Add, }) }}> click </button> {state.shoppingCart} </div> ) } export default Products; ``` 現在一切都是強類型的。 您可以[在此處](https://codesandbox.io/s/context-reducer-ts-9ctis)查看程式碼。 #### 來源。 https://medium.com/hackernoon/finally-the-typescript-redux-hooks-events-blog-you-were-looking-for-c4663d823b01 --- 原文出處:https://dev.to/elisealcala/react-context-with-usereducer-and-typescript-4obm

我使用 Next.js、GPT4 和 CopilotKit 建立了 v0.dev 克隆

長話短說 ---- 在本文中,您將了解如何建立 Vercel 的 V0.dev 的克隆。這是一個很棒的專案,可以加入到您的投資組合中並磨練您的人工智慧能力。 我們將介紹使用: - 用於應用程式框架的 Next.js 🖥️ - 法學碩士 OpenAI 🧠 - v0 👾 的應用程式邏輯 - 使用 CopilotKit 將 AI 整合到您的應用程式中 🪁 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shhjdu2k5s02gzoby3p0.gif) --- CopilotKit:應用內人工智慧的作業系統框架 ========================= CopilotKit 是[開源人工智慧副駕駛平台。](https://github.com/CopilotKit/CopilotKit)我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。 建造: - ChatBot:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 💬 - CopilotTextArea:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能📝 - 聯合代理:應用程式內人工智慧代理,可以與您的應用程式和使用者互動🤖 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8gltoave8490fg234ro.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} (原諒人工智慧的拼字錯誤並給一顆星:) 現在回到文章。 --- 先決條件 ---- 要開始學習本教程,您需要具備以下條件: - 文字編輯器(VS Code、遊標) - React、Next.js、Typescript 和 Tailwind CSS 的基本知識。 - Node.js 安裝在您的 PC/Mac 上 - 套件管理器 (npm) - [OpenAI](https://platform.openai.com/docs/overview) API 金鑰 - [CopilotKit](https://docs.copilotkit.ai/getting-started/quickstart-textarea)安裝在您的 React 專案中 v0是什麼? ------ **v0**是[Vercel 開發的](https://vercel.com/blog/announcing-v0-generative-ui)生成式使用者介面 (UI) 工具,允許使用者給予提示並描述他們的想法,然後將其轉換為用於建立 Web 介面的 UI 程式碼。它利用[生成式 AI](https://medium.com/data-science-at-microsoft/generative-ai-openai-and-chatgpt-what-are-they-3c80397062c4)以及[React](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started) 、 [Tailwind CSS](https://tailwindcss.com/)和[Shadcn UI](https://ui.shadcn.com/)等開源工具,根據使用者提供的描述產生程式碼。 *這是使用 v0 產生的 Web 應用程式 UI 的範例* https://v0.dev/t/nxGnMd1uVGc 了解專案要求 ------ 在本逐步教程結束時,克隆將具有以下專案要求: 1. **使用者輸入:**使用者輸入文字作為提示,描述他們想要產生的 UI。這將使用 CopilotKit 聊天機器人來完成,該聊天機器人由[CopilotSidebar](https://docs.copilotkit.ai/reference/CopilotSidebar)提供。 2. **CopilotKit 整合:** CopilotKit 將用於為 Web 應用程式提供 AI 功能以產生 UI。 3. **渲染 UI:**在 UI React/JSX 程式碼和渲染 UI 之間切換的切換開關。 使用 CopilotKit 建立 v0 克隆 ---------------------- **第 1 步:建立一個新的 Next.JS 應用程式** 在終端機中開啟工作區資料夾並執行以下命令建立新的 Next.js 應用程式: ``` npx create-next-app@latest copilotkit-v0-clone ``` 這將建立一個名為`copilotkit-v0-clone`新目錄,其中包含 Next.JS 專案結構,並安裝了所需的依賴項。它將在您的終端中顯示這一點,並對除最後一個之外的所有選項都選擇**“是”** ,因為建議使用預設`import alias` 。其他提示安裝我們將在專案中使用的 Typescript 和 TailwindCSS。 ![終端](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o6l07dxihdi8hw68kv4e.png) 使用`cd`指令導航到專案目錄,如下所示: ``` cd copilotkit-v0-clone ``` **步驟 2:設定 CopilotKit 後端端點。閱讀[文件](https://docs.copilotkit.ai/getting-started/quickstart-backend)以了解更多資訊。** 執行以下命令來安裝 CopilotKit 後端軟體包: ``` npm i @copilotkit/backend ``` 然後造訪 https://platform.openai.com/api-keys 以取得您的**GPT 4** OpenAI API 金鑰。 ![開放伊](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ise7y3qnb3cyg4j0mjr6.png) 取得 API 金鑰後,在根目錄中建立一個`.env.local`檔案。 `.env.local`檔案應該是這樣的: ``` OPENAI_API_KEY=Your OpenAI API key ``` 在**app**目錄下建立該目錄; `api/copilot/openai`並建立一個名為`route.ts`的檔案。該檔案用作 CopilotKit 請求和 OpenAI 互動的**後端**端點。它處理傳入的請求,使用 CopilotKit 處理它們,並傳回適當的回應。 我們將在`route.ts`檔案中建立一個POST請求函數,在post請求內部建立一個`CopilotBackend`類別的新實例,該類別提供了處理CopilotKit請求的方法。 然後,我們呼叫`CopilotBackend`實例的`response`方法,並傳遞請求物件 ( `req` ) 和`OpenAIAdapter`類別的新實例作為參數。此方法使用 CopilotKit 和 OpenAI API 處理請求並回傳回應。 如下面的程式碼所示,我們從`@copilotkit/backend`套件導入`CopilotBackend`和`OpenAIAdapter`類別。這些類別對於與 CopilotKit 和 OpenAI API 互動是必需的。 ``` import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend"; export const runtime = "edge"; export async function POST(req: Request): Promise<Response> { const copilotKit = new CopilotBackend(); return copilotKit.response(req, new OpenAIAdapter()); } ``` **步驟 3:為 v0 克隆建立元件** 我們將使用 Shadcn UI 庫中的元件。要處理這個問題,讓我們透過執行`shadcn-ui init`命令來設定 Shadcn UI 庫來設定您的專案 ``` npx shadcn-ui@latest init ``` 然後我們將用這個問題來配置components.json ``` Which style would you like to use? › Default Which color would you like to use as base color? › Slate Do you want to use CSS variables for colors? › no / yes ``` 我們在 Shadcn UI 中使用的元件是**按鈕**和**對話框**。那麼讓我們來安裝它們吧! 對於[按鈕](https://ui.shadcn.com/docs/components/button),執行此命令 ``` npx shadcn-ui@latest add button ``` 若要安裝[對話](https://ui.shadcn.com/docs/components/dialog)方塊元件,請執行以下命令 ``` npx shadcn-ui@latest add dialog ``` **第 4 步:設定 CopilotKit 前端。閱讀[文件](https://docs.copilotkit.ai/getting-started/quickstart-textarea)以了解更多資訊。** 若要安裝 CopilotKit 前端軟體包,請執行以下命令: ``` npm i @copilotkit/react-core @copilotkit/react-ui ``` 根據[CopilotKit 文件](https://docs.copilotkit.ai/getting-started/quickstart-textarea),要使用 CopilotKit,我們必須設定前端包裝器以透過 Copilot 傳遞任何 React 應用程式。當提示傳遞到 CopilotKit 時,它會透過 URL 將其傳送到 OpenAI,後者會回傳回應。 在**應用程式**目錄中,讓我們更新`layout.tsx`檔案。該文件將定義我們應用程式的佈局結構並將 CopilotKit 整合到前端。 輸入以下程式碼: ``` "use client"; import { CopilotKit } from "@copilotkit/react-core"; import "@copilotkit/react-textarea/styles.css"; // also import this if you want to use the CopilotTextarea component import "@copilotkit/react-ui/styles.css"; import { Inter } from "next/font/google"; import "./globals.css"; import { CopilotSidebar, } from "@copilotkit/react-ui"; const inter = Inter({ subsets: ["latin"] }); export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body className={inter.className}> <CopilotKit url="/api/copilotkit/openai/"> <CopilotSidebar defaultOpen>{children}</CopilotSidebar> </CopilotKit> </body> </html> ); } ``` 該元件代表我們應用程式的根佈局。它使用 CopilotKit 包裝整個應用程式,根據我們在**步驟 2**中為後端建立的內容指定 CopilotKit 後端端點的 URL ( `/api/copilotkit/openai/` )。此外,它還包括一個 CopilotSidebar 元件,可作為 CopilotKit 的側邊欄,並將 Children 屬性作為其內容傳遞。 **第 5 步:設定主應用程式** 讓我們建立應用程式的結構。它將有一個標題、側邊欄和預覽畫面。 對於**Header** ,導航到**元件**目錄,如下所示, `src/components`然後建立一個`header.tsx`檔案並輸入以下程式碼: ``` import { CodeXmlIcon } from "lucide-react"; import { Button } from "./ui/button"; const Header = (props: { openCode: () => void }) => { return ( <div className="w-full h-20 bg-white flex justify-between items-center px-4"> <h1 className="text-xl font-bold">Copilot Kit</h1> <div className="flex gap-x-2"> <Button className=" px-6 py-1 rounded-md space-x-1" variant={"default"} onClick={props.openCode} > <span>Code</span> <CodeXmlIcon size={20} /> </Button> </div> </div> ); }; export default Header; ``` 對於**側欄,**建立一個`sidebar.tsx`檔案並輸入以下程式碼: ``` import { ReactNode } from "react"; const Sidebar = ({ children }: { children: ReactNode }) => { return ( <div className="w-[12%] min-h-full bg-white rounded-md p-4"> <h1 className="text-sm mb-1">History</h1> {children} </div> ); }; export default Sidebar; ``` 然後對於**預覽**螢幕,建立一個`preview-screen.tsx`檔案並輸入程式碼: ``` const PreviewScreen = ({ html_code }: { html_code: string }) => { return ( <div className="w-full h-full bg-white rounded-lg shadow-lg p-2 border"> <div dangerouslySetInnerHTML={{ __html: html_code }} /> </div> ); }; export default PreviewScreen; ``` 現在讓我們將它們放在一起,打開`page.tsx`檔案並貼上以下程式碼: ``` "use client"; import { useState } from "react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import Header from "@/components/header"; import Sidebar from "@/components/sidebar"; import PreviewScreen from "@/components/preview-screen"; import { Input } from "@/components/ui/input"; export default function Home() { const [code, setCode] = useState<string[]>([ `<h1 class="text-red-500">Hello World</h1>`, ]); const [codeToDisplay, setCodeToDisplay] = useState<string>(code[0] || ""); const [showDialog, setShowDialog] = useState<boolean>(false); const [codeCommand, setCodeCommand] = useState<string>(""); return ( <> <main className="bg-white min-h-screen px-4"> <Header openCode={() => setShowDialog(true)} /> <div className="w-full h-full min-h-[70vh] flex justify-between gap-x-1 "> <Sidebar> <div className="space-y-2"> {code.map((c, i) => ( <div key={i} className="w-full h-20 p-1 rounded-md bg-white border border-blue-600" onClick={() => setCodeToDisplay(c)} > v{i} </div> ))} </div> </Sidebar> <div className="w-10/12"> <PreviewScreen html_code={readableCode || ""} /> </div> </div> <div className="w-8/12 mx-auto p-1 rounded-full bg-primary flex my-4 outline-0"> <Input type="text" placeholder="Enter your code command" className="w-10/12 p-6 rounded-l-full outline-0 bg-primary text-white" value={codeCommand} onChange={(e) => setCodeCommand(e.target.value)} /> <button className="w-2/12 bg-white text-primary rounded-r-full" onClick={() => generateCode.run(context)} > Generate </button> </div> </main> <Dialog open={showDialog} onOpenChange={setShowDialog}> <DialogContent> <DialogHeader> <DialogTitle>View Code.</DialogTitle> <DialogDescription> You can use the following code to start integrating into your application. </DialogDescription> <div className="p-4 rounded bg-primary text-white my-2"> {readableCode} </div> </DialogHeader> </DialogContent> </Dialog> </> ); } ``` 我們來分解一下上面的程式碼: `const [code, setCode] = useState<string[]>([]);`將用於保存生成的程式碼 `const [codeToDisplay, setCodeToDisplay] = useState<string>(code[0] || "");`將用於保存預覽畫面上顯示的程式碼。 `const [showDialog, setShowDialog] = useState<boolean>(false);`這將保持對話框的狀態,該對話框顯示您可以複製的生成程式碼。 在下面的程式碼中,我們循環產生的程式碼(一串陣列)將其顯示在側邊欄上,這樣當我們選擇一個程式碼時,它就會顯示在預覽畫面上。 ``` <Sidebar> <div className="space-y-2"> {code.map((c, i) => ( <div key={i} className="w-full h-20 p-1 rounded-md bg-white border border-blue-600" onClick={() => setCodeToDisplay(c)} > v{i} </div> ))} </div> </Sidebar> ``` `<PreviewScreen html_code={codeToDisplay} />`在這裡,我們發送要在預覽畫面上顯示的程式碼。預覽畫面元件採用 CopilotKit 產生的程式碼字串,並使用`dangerouslySetInnerHTML`來呈現產生的程式碼。 下面我們有一個`Dialog`元件,它將顯示 CoplilotKit 產生的程式碼,可以將其複製並加入到您的程式碼中。 ``` <Dialog open={showDialog} onOpenChange={setShowDialog}> <DialogContent> <DialogHeader> <DialogTitle>View Code.</DialogTitle> <DialogDescription> You can use the following code to start integrating into your application. </DialogDescription> <div className="p-4 rounded bg-primary text-white my-2"> {readableCode} </div> </DialogHeader> </DialogContent> </Dialog> ``` **步驟6:實作主要應用程式邏輯** 在這一步驟中,我們將 CopilotKit 整合到我們的 v0 克隆應用程式中,以促進人工智慧驅動的 UI 生成。我們將使用 CopilotKit 的 React hook 來管理狀態,使元件可供 Copilot 讀取和操作,並與 OpenAI API 互動。 在您的`page.tsx`檔案中,匯入以下內容: ``` import { CopilotTask, useCopilotContext, useMakeCopilotReadable, } from "@copilotkit/react-core"; ``` 然後我們在`Home`元件中使用`CopilotTask`定義一個`generateCode`任務: ``` const readableCode = useMakeCopilotReadable(codeToDisplay); const generateCode = new CopilotTask({ instructions: codeCommand, actions: [ { name: "generateCode", description: "Create Code Snippet with React.js, tailwindcss.", parameters: [ { name: "code", type: "string", description: "Code to be generated", required: true, }, ], handler: async ({ code }) => { setCode((prev) => [...prev, code]); setCodeToDisplay(code); }, }, ], }); const context = useCopilotContext(); ``` 我們使用`useMakeCopilotReadable`來傳遞現有程式碼並確保可讀性。然後我們使用`CopilotTask`產生UI,並將`generateCode`任務綁定到**生成**按鈕,這樣就可以透過與按鈕元件互動來產生程式碼片段。 此操作由使用者互動觸發,並在呼叫時執行非同步`handler`函數。 `handler`將產生的程式碼新增至程式碼陣列中,更新應用程式狀態以包含新產生的程式碼片段,並將產生的程式碼傳送到預覽畫面上顯示和呈現,預覽畫面也可以複製。 此外, `instructions`屬性指定提供給 Copilot 的命令,該命令儲存在`codeCommand`狀態變數中。 有關`CopilotTask`運作方式的完整說明,請查看此處的文件:https://docs.copilotkit.ai/reference/CopilotTask **第 6 步:執行 v0 克隆應用程式** 至此,我們已經完成了 v0 克隆設置,然後可以透過執行來啟動開發伺服器 ``` npm run dev ``` ![終端](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wm5oddqlzewca34ko0g.png) 可以使用此 URL 在瀏覽器中存取該 Web 應用程式 [http://本地主機:3000](http://localhost:3000/) 然後您可以輸入提示並點擊**“生成”。**這裡有些例子: - **定價頁面:**如下所示,這是產生的UI,有一個切換按鈕可以在UI和React程式碼之間切換: ![在](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cm3gyodvbl0x9i0uvxp9.png) 如果點擊右上角的**Code** &lt;/&gt; 按鈕,它會切換到產生的 UI 的 React 程式碼,如下所示: ![在](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1b5sruonnxl7x42ad8y1.png) - 註冊頁面 UI 範例: ![報名](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/350l7o66l6lq5d4kxiav.png) - 還有一個結帳頁面 ![查看](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dpj6j338fp2gavsvgtti.png) 要克隆專案並在本地執行它,請打開終端並執行以下命令: ``` git clone https://github.com/Tabintel/v0-copilot-next ``` 然後執行`npm install`以安裝專案所需的所有依賴項,並`npm run dev`來執行 Web 應用程式。 結論 -- 總而言之,您可以使用[CopilotKit](https://github.com/CopilotKit/CopilotKit)建立 v0 克隆,為您的設計提供 UI 提示。 CopilotKit 不僅適用於 UI 提示,它還可以用於建立[AI 驅動的 PowerPoint 生成器](https://dev.to/copilotkit/how-to-build-ai-powered-powerpoint-app-nextjs-openai-copilotkit-ji2)、 [AI 簡歷產生器](https://dev.to/copilotkit/how-to-build-the-with-nextjs-openai-1mhb)等應用程式。 可能性是無限的,立即查看 CopilotKit,將您的 AI 想法變為現實。 在[GitHub](https://github.com/Tabintel/v0-copilot-next)上取得完整原始碼。 從[文件](https://docs.copilotkit.ai/getting-started/quickstart-textarea)中了解有關如何使用 CopilotKit 的更多資訊。 另外,別忘了[Star CopilotKit!](https://github.com/CopilotKit/CopilotKit) ⭐ --- 原文出處:https://dev.to/copilotkit/i-created-a-v0-clone-with-nextjs-gpt4-copilotkit-3cmb

React 元件設計模式 - 第 1 部分

React 設計模式是開發人員用來解決使用 React 建立應用程式時遇到的常見問題和挑戰的既定實踐和解決方案。這些模式封裝了針對重複出現的設計問題的可重複使用解決方案,從而提高了可維護性、可擴展性和效率。它們提供了一種結構化的方法來組織元件、管理狀態、處理資料流和最佳化效能。 **請考慮以下 6 種 React 設計模式:** 1. 容器和呈現模式 2. HOC(高階元件)模式 3. 複合元件模式 4. 提供者模式(使用提供者進行資料管理) 5. 狀態減速器模式 6. 元件組成模式 1. 容器和呈現模式 ---------- 在此模式中,容器元件負責管理資料和狀態邏輯。它們從外部來源獲取資料,在必要時對其進行操作,並將其作為 props 傳遞給展示元件。它們通常連接到外部服務、Redux 儲存或上下文提供者。 另一方面,展示元件僅專注於 UI 元素的展示。他們透過 props 從容器元件接收資料,並以視覺上吸引人的方式呈現它。展示元件通常是無狀態的功能元件或純元件,這使得它們更容易測試和重複使用。 **讓我們考慮一個複雜的範例來說明這些模式:** 假設我們正在建立一個社群媒體儀表板,用戶可以在其中查看朋友的貼文並與他們互動。以下是我們建立元件的方式: **容器元件(FriendFeedContainer):** 該元件將負責從 API 獲取有關好友貼文的資料、處理任何必要的資料轉換以及管理提要的狀態。它將相關資料傳遞給展示元件。 ``` import React, { useState, useEffect } from 'react'; import FriendFeed from './FriendFeed'; const FriendFeedContainer = () => { const [friendPosts, setFriendPosts] = useState([]); useEffect(() => { // Fetch friend posts from API const fetchFriendPosts = async () => { const posts = await fetch('https://api.example.com/friend-posts'); const data = await posts.json(); setFriendPosts(data); }; fetchFriendPosts(); }, []); return <FriendFeed posts={friendPosts} />; }; export default FriendFeedContainer; ``` **展示元件(FriendFeed):** 該元件將從其父容器元件 (FriendFeedContainer) 接收好友貼文資料作為 props,並以視覺上吸引人的方式呈現它們。 ``` import React from 'react'; const FriendFeed = ({ posts }) => { return ( <div> <h2>Friend Feed</h2> <ul> {posts.map(post => ( <li key={post.id}> <p>{post.content}</p> <p>Posted by: {post.author}</p> </li> ))} </ul> </div> ); }; export default FriendFeed; ``` 透過以這種方式建立我們的元件,我們將獲取資料和管理狀態的問題與 UI 渲染邏輯分開。這種分離使得我們的 React 應用程式在擴充時可以更輕鬆地進行測試、重複使用和維護。 2.HOC(高階元件)模式 ------------- 高階元件 (HOC) 是 React 中的一種模式,可讓您跨多個元件重複使用元件邏輯。它們是接受元件並傳回具有附加功能的新元件的函數。 為了示範 HOC 在具有 React hook 的社群媒體儀表板範例中的使用,讓我們考慮一個場景,其中您有多個元件需要從 API 取得使用者資料。您可以建立一個 HOC 來處理資料獲取並將獲取的資料作為 props 傳遞給包裝的元件,而不是在每個元件中重複取得邏輯。 這是一個基本範例: ``` import React, { useState, useEffect } from 'react'; // Define a higher-order component for fetching user data const withUserData = (WrappedComponent) => { return (props) => { const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { // Simulate fetching user data from an API const fetchData = async () => { try { const response = await fetch('https://api.example.com/user'); const data = await response.json(); setUserData(data); setLoading(false); } catch (error) { console.error('Error fetching user data:', error); setLoading(false); } }; fetchData(); }, []); return ( <div> {loading ? ( <p>Loading...</p> ) : ( <WrappedComponent {...props} userData={userData} /> )} </div> ); }; }; // Create a component to display user data const UserProfile = ({ userData }) => { return ( <div> <h2>User Profile</h2> {userData && ( <div> <p>Name: {userData.name}</p> <p>Email: {userData.email}</p> {/* Additional user data fields */} </div> )} </div> ); }; // Wrap the UserProfile component with the withUserData HOC const UserProfileWithUserData = withUserData(UserProfile); // Main component where you can render the wrapped component const SocialMediaDashboard = () => { return ( <div> <h1>Social Media Dashboard</h1> <UserProfileWithUserData /> </div> ); }; export default SocialMediaDashboard; ``` 在這個例子中: - `withUserData`是一個高階元件,用於處理從 API 取得使用者資料。它包裝傳遞的元件 ( `WrappedComponent` ) 並將取得的使用者資料作為 prop ( `userData` ) 提供給它。 - `UserProfile`是一個功能元件,它接收`userData`屬性並顯示使用者設定檔資訊。 - `UserProfileWithUserData`是透過使用`withUserData`包裝`UserProfile`傳回的元件。 - `SocialMediaDashboard`是主要元件,您可以在其中呈現`UserProfileWithUserData`或任何其他需要使用者資料的元件。 使用此模式,您可以輕鬆地跨社交媒體儀表板應用程式中的多個元件重複使用資料取得邏輯,而無需重複程式碼。 3. 複合元件模式 --------- React 中的複合元件模式是一種設計模式,可讓您建立協同工作以形成有凝聚力的 UI 的元件,同時仍保持明確的關注點分離並提供自訂元件行為和外觀的靈活性。 在此模式中,父元件充當一個或多個子元件(稱為「複合元件」)的容器。這些子元件協同工作以實現特定的功能或行為。複合元件的關鍵特徵是它們透過父元件彼此共享狀態和功能。 以下是使用 hooks 在 React 中實作複合元件模式的簡單範例: ``` import React, { useState } from 'react'; // Parent component that holds the compound components const Toggle = ({ children }) => { const [isOn, setIsOn] = useState(false); // Function to toggle the state const toggle = () => { setIsOn((prevIsOn) => !prevIsOn); }; // Clone the children and pass the toggle function and state to them const childrenWithProps = React.Children.map(children, (child) => { if (React.isValidElement(child)) { return React.cloneElement(child, { isOn, toggle }); } return child; }); return <div>{childrenWithProps}</div>; }; // Child component for the toggle button const ToggleButton = ({ isOn, toggle }) => { return ( <button onClick={toggle}> {isOn ? 'Turn Off' : 'Turn On'} </button> ); }; // Child component for the toggle status const ToggleStatus = ({ isOn }) => { return <p>The toggle is {isOn ? 'on' : 'off'}.</p>; }; // Main component where you use the compound components const App = () => { return ( <Toggle> <ToggleStatus /> <ToggleButton /> </Toggle> ); }; export default App; ``` 在這個例子中: - `Toggle`是保存複合元件( `ToggleButton`和`ToggleStatus` )的父元件。 - `ToggleButton`是負責渲染切換按鈕的子元件。 - `ToggleStatus`是另一個負責顯示切換狀態的子元件。 - `Toggle`元件管理狀態 ( `isOn` ) 並提供`toggle`功能來控制狀態。它克隆其子級並將`isOn`狀態和`toggle`函數作為 props 傳遞給它們。 透過使用複合元件模式,您可以建立可重複使用和可組合的元件,封裝複雜的 UI 邏輯,同時仍允許自訂和靈活性。 ### 4. Provider Pattern(使用Provider進行資料管理) React 中的提供者模式是一種設計模式,用於跨多個元件管理和共用應用程式狀態或資料。它涉及建立一個封裝狀態或資料的提供者元件,並透過 React 的上下文 API 將其提供給其後代元件。 讓我們透過一個範例來說明 React 中用於管理使用者身份驗證資料的 Provider 模式: ``` // UserContext.js import React, { createContext, useState } from 'react'; // Create a context for user data const UserContext = createContext(); // Provider component export const UserProvider = ({ children }) => { const [user, setUser] = useState(null); // Function to login user const login = (userData) => { setUser(userData); }; // Function to logout user const logout = () => { setUser(null); }; return ( <UserContext.Provider value={{ user, login, logout }}> {children} </UserContext.Provider> ); }; export default UserContext; ``` 在這個例子中: - 我們使用 React 的`createContext`函數來建立一個名為`UserContext`上下文。此上下文將用於跨元件共享用戶資料和與身份驗證相關的功能。 - 我們定義一個`UserProvider`元件作為`UserContext`的提供者。該元件使用`useState`鉤子管理使用者狀態,並提供`login`和`logout`等方法來更新使用者狀態。 - 在`UserProvider`內部,我們用`UserContext.Provider`包裝`children` ,並將`user`狀態以及`login`和`logout`函數作為提供者的值傳遞。 - 現在,任何需要存取使用者資料或驗證相關功能的元件都可以使用`useContext`掛鉤來使用`UserContext` 。 讓我們建立一個使用上下文中的使用者資料的元件: ``` // UserProfile.js import React, { useContext } from 'react'; import UserContext from './UserContext'; const UserProfile = () => { const { user, logout } = useContext(UserContext); return ( <div> {user ? ( <div> <h2>Welcome, {user.username}!</h2> <button onClick={logout}>Logout</button> </div> ) : ( <div> <h2>Please log in</h2> </div> )} </div> ); }; export default UserProfile; ``` 在此元件中: 我們匯入`UserContext`並使用`useContext`鉤子來存取`UserProvider`提供的使用者資料和`logout`功能。 根據使用者是否登錄,我們呈現不同的 UI 元素。 最後,我們用`UserProvider`包裝我們的應用程式,以使用戶資料和身份驗證相關的功能可供所有元件使用: ``` // App.js import React from 'react'; import { UserProvider } from './UserContext'; import UserProfile from './UserProfile'; const App = () => { return ( <UserProvider> <div> <h1>My App</h1> <UserProfile /> </div> </UserProvider> ); }; export default App; ``` 透過這種方式,Provider 模式允許我們跨多個元件管理和共享應用程式狀態或資料,而無需進行 prop 鑽取,從而使我們的程式碼更乾淨、更易於維護。 --- 原文出處:https://dev.to/fpaghar/react-component-design-patterns-part-1-5f0g

使用這些 React 函式庫和雲端後端來建立全端應用程式。

今天,我們將學習如何使用 Wing 作為後端建立全端應用程式。 ![反應 + 維特 + 翅膀](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vb7jf7dk9b08x042p0vl.png) 我們將使用 React 和 Vite 作為前端。 我知道還有其他框架,如 Vue、Angular 和 Next,但 React 仍然是最常見的,並且迄今為止有大量值得信賴的新創公司使用它。 如果您不知道, [React](https://github.com/facebook/react)是 Facebook 建立的開源程式庫,用於建立 Web 和本機使用者介面。正如您從儲存庫中看到的,它被超過 2040 萬開發人員使用。所以,這是值得的。 讓我們看看如何使用 Wing 作為後端。 ![豎起大拇指](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pskz2tyzodt4wnxbqa8y.gif) --- [Wing](https://git.new/wing-repo) - 一種雲端程式語言。 --------------------------------------------- ![翅膀](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n97bowkrexjk46n94bcc.png) Winglang 是一種專為雲端(又稱「面向雲端」)設計的新型開源程式語言。它允許您在雲端中建立應用程式,並且具有相當簡單的語法。 Wing 程式可以使用功能齊全的模擬器在本地執行(是的,不需要網路),也可以部署到任何雲端供應商。 ![機翼基礎設施](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eun3zd1gkp870rj57eeu.png) Wing 需要 Node `v20 or higher` 。 建立一個父目錄(我們使用的`shared-counter` )並使用 Vite 使用新的 React 應用程式設定前端。您可以使用這個 npm 指令。 ``` npm create -y vite frontend -- --template react-ts // once installed, you can check if it's running properly. cd frontend npm install npm run dev ``` 您可以使用此 npm 命令安裝 Wing。 ``` npm install -g winglang ``` 您可以使用`wing -V`驗證安裝。 Wing 還提供官方[VSCode 擴充功能](https://marketplace.visualstudio.com/items?itemName=Monada.vscode-wing)和[IntelliJ](https://plugins.jetbrains.com/plugin/22353-wing) ,後者提供語法突出顯示、補全、轉到定義和嵌入式 Wing 控制台支援。您可以在建立應用程式之前安裝它! 建立後端目錄。 ``` mkdir ~/shared-counter/backend cd ~/shared-counter/backend ``` 建立一個新的空 Wing 專案。 ``` wing new empty // This will generate three files: package.json, package-lock.json and main.w file with a simple "hello world" program wing it // to run it in the Wing simulator // The Wing Simulator will be opened in your browser and will show a map of your app with a single function. //You can invoke the function from the interaction panel and check out the result. ``` 使用指令`wing new empty`後的結構如下。 ``` bring cloud; // define a queue, a bucket, and a counter let bucket = new cloud.Bucket(); let counter = new cloud.Counter(initial: 1); let queue = new cloud.Queue(); // When a message is received in the queue -> it should be consumed // by the following closure queue.setConsumer(inflight (message: str) => { // Increment the distributed counter, the index variable will // store the value before the increment let index = counter.inc(); // Once two messages are pushed to the queue, e.g. "Wing" and "Queue". // Two files will be created: // - wing-1.txt with "Hello Wing" // - wing-2.txt with "Hello Queue" bucket.put("wing-{index}.txt", "Hello, {message}"); log("file wing-{index}.txt created"); }); ``` 您可以安裝`@winglibs/vite`來啟動開發伺服器,而不是使用`npm run dev`來啟動本機 Web 伺服器。 ``` // in the backend directory npm i @winglibs/vite ``` 您可以使用`backend/main.w`中提供的 publicEnv 將資料傳送到前端。 讓我們來看一個小例子。 ``` // backend/main.w bring vite; new vite.Vite( root: "../frontend", publicEnv: { TITLE: "Wing + Vite + React" } ); // import it in frontend // frontend/src/App.tsx import "../.winglibs/wing-env.d.ts" //You can access that value like this. <h1>{window.wing.env.TITLE}</h1> ``` 你還可以做更多: - 讀取/更新 API 路線並使用 Wing Simulator 檢查它。 - 使用後端獲取值。 - 使用`@winglibs/websockets`同步瀏覽器,它在後端部署一個 WebSocket 伺服器,您可以連接此 WebSocket 來接收即時通知。 您可以閱讀完整的逐步指南,以了解[如何使用 React 作為前端和 Wing 作為後端建立簡單的 Web 應用程式](https://www.winglang.io/docs/guides/react-vite-websockets)。測試是使用 Wing Simulator 完成的,並使用 Terraform 部署到 AWS。 部署後的AWS架構是這樣的。 ![建築學](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/27awil840ktgh3jvklij.png) 為了提供開發者選擇和更好的體驗,Wing 推出了對[TypeScript (Wing)](https://www.winglang.io/docs/typescript/)等其他語言的全面支援。唯一強制性的事情是您必須安裝 Wing SDK。 這也將使控制台完全可用於本地偵錯和測試,而無需學習 Wing 語言。 Wing 甚至還有其他[指南](https://www.winglang.io/docs/category/guides),因此更容易遵循。 ![指南](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/31czxehkg10ezmlpf7ac.png) 您可以閱讀[文件](https://www.winglang.io/docs)並查看[範例](https://www.winglang.io/docs/category/examples)。 您也可以在[Playground](https://www.winglang.io/play/?code=LwAvACAAVABoAGkAcwAgAGkAcwAgAHQAaABlACAAaQBtAHAAbwByAHQAIABzAHQAYQB0AGUAbQBlAG4AdAAgAGkAbgAgAFcAaQBuAGcALgAKAC8ALwAgAEgAZQByAGUAIAB3AGUAIABiAHIAaQBuAGcAIAB0AGgAZQAgAFcAaQBuAGcAIABzAHQAYQBuAGQAYQByAGQAIABsAGkAYgByAGEAcgB5ACAAdABoAGEAdAAgAAoALwAvACAAYwBvAG4AdABhAGkAbgBzACAAYQBiAHMAdAByAGEAYwB0AGkAbwBuAHMAIABvAGYAIABwAG8AcAB1AGwAYQByACAAYwBsAG8AdQBkACAAcwBlAHIAdgBpAGMAZQBzAC4ACgBiAHIAaQBuAGcAIABjAGwAbwB1AGQAOwAKAAoALwAvACAAVABoAGkAcwAgAGMAbwBkAGUAIABkAGUAZgBpAG4AZQBzACAAYQAgAGIAdQBjAGsAZQB0ACAAYQBzACAAcABhAHIAdAAgAG8AZgAgAHkAbwB1AHIAIABhAHAAcAAuAAoALwAvACAAVwBoAGUAbgAgAGMAbwBtAHAAaQBsAGkAbgBnACAAdABvACAAYQAgAHMAcABlAGMAaQBmAGkAYwAgAGMAbABvAHUAZAAgAHAAcgBvAHYAaQBkAGUAcgAKAC8ALwAgAGkAdAAgAHcAaQBsAGwAIABiAGUAIABzAHUAYgBzAHQAaQB0AHUAdABlAGQAIABiAHkAIABhAG4AIABpAG0AcABsAGUAbQBlAG4AdABhAHQAaQBvAG4AIABmAG8AcgAKAC8ALwAgAHQAaABhAHQAIABjAGwAbwB1AGQALgAgAEkALgBlACwAIABmAG8AcgAgAEEAVwBTACAAaQB0ACAAdwBpAGwAbAAgAGIAZQAgAGEAbgAgAFMAMwAgAEIAdQBjAGsAZQB0AC4ACgBsAGUAdAAgAGIAdQBjAGsAZQB0ACAAPQAgAG4AZQB3ACAAYwBsAG8AdQBkAC4AQgB1AGMAawBlAHQAKAApADsACgAKAC8ALwAgACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAKAC8ALwAgAFkAbwB1ACAAYwBhAG4AIABpAG4AdABlAHIAYQBjAHQAIAB3AGkAdABoACAAdABoAGUAIABhAHAAcAAgAGkAbgAgAHQAaABlACAAYwBvAG4AcwBvAGwAZQAgAC0ALQA%2BAAoALwAvACAACgAvAC8AIABDAGwAaQBjAGsAIABvAG4AIAB0AGgAZQAgAEYAdQBuAGMAdABpAG8AbgAsACAAYQBuAGQAIAB0AGgAZQBuACAAaQBuAHYAbwBrAGUAIABpAHQAIABpAG4AIAB0AGgAZQAKAC8ALwAgAGwAbwB3AGUAcgAgAHIAaQBnAGgAdAAgAHAAYQBuAGUAbAAsACAAbwByACAAYwBsAGkAYwBrACAAbwBuACAAdABoAGUAIABCAHUAYwBrAGUAdAAKAC8ALwAgAHQAbwAgAHMAZQBlACAAaQB0AHMAIABjAG8AbgB0AGUAbgB0AHMAIABpAG4AIAB0AGgAZQAgAHAAYQBuAGUAbAAsACAAZQB0AGMALgAKAC8ALwAgACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAhACEAIQAKAAoALwAvACAAYABpAG4AZgBsAGkAZwBoAHQAcwBgACAAcgBlAHAAcgBlAHMAZQBuAHQAIABjAG8AZABlACAAdABoAGEAdAAgAHIAdQBuAHMAIABsAGEAdABlAHIALAAgAG8AbgAKAC8ALwAgAG8AdABoAGUAcgAgAG0AYQBjAGgAaQBuAGUAcwAsACAAaQBuAHQAZQByAGEAYwB0AGkAbgBnACAAdwBpAHQAaAAgAGMAYQBwAHQAdQByAGUAZAAgAGQAYQB0AGEAIABhAG4AZAAKAC8ALwAgAHIAZQBzAG8AdQByAGMAZQBzACAAZgByAG8AbQAgAHQAaABlACAAcAByAGUALQBmAGwAaQBnAGgAdAAgAHAAaABhAHMAZQAuAAoAbABlAHQAIABoAGUAbABsAG8AXwB3AG8AcgBsAGQAIAA9ACAAaQBuAGYAbABpAGcAaAB0ACAAKAApACAAPQA%2BACAAewAKACAAIABiAHUAYwBrAGUAdAAuAHAAdQB0ACgAIgBoAGUAbABsAG8ALgB0AHgAdAAiACwAIAAiAEgAZQBsAGwAbwAsACAAVwBvAHIAbABkACEAIgApADsACgB9ADsACgAKAC8ALwAgAEkAbgBmAGwAaQBnAGgAdABzACAAYwBhAG4AIABiAGUAIABkAGUAcABsAG8AeQBlAGQAIABhAHMAIABzAGUAcgB2AGUAcgBsAGUAcwBzACAAZgB1AG4AYwB0AGkAbwBuAHMACgBuAGUAdwAgAGMAbABvAHUAZAAuAEYAdQBuAGMAdABpAG8AbgAoAGgAZQBsAGwAbwBfAHcAbwByAGwAZAApADsACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAACgAvAC8AIACRISAAUwB3AGkAdABjAGgAIABmAGkAbABlAHMAIABhAG4AZAAgAHMAZQBlACAAbwB0AGgAZQByACAAZQB4AGEAbQBwAGwAZQBzACAAdwBpAHQAaAAgAG0AbwByAGUACgAvAC8AIABlAHgAcABsAGUAbgBhAHQAaQBvAG4AcwAgAGEAYgBvAHYAZQAuAA%3D%3D)中使用 Wing 查看結構和範例。 如果你比較像輔導員。看這個! https://www.youtube.com/watch?v=wzqCXrsKWbo Wing 在 GitHub 上擁有超過 3500 個 Star,發布了 1500 多個版本,但仍未進入 v1 版本,這意味著意義重大。 去嘗試一下,做一些很酷的事情吧! https://git.new/wing-repo 星翼 ⭐️ --- 開發者生態系統不斷發展,許多開發者圍繞 React 建置了一些獨特的東西。 我不會介紹如何使用 React,因為這是一個非常廣泛的主題,我在最後貼了一些資源來幫助您學習 React。 但為了幫助您建立出色的 React 專案,我們介紹了 25 個開源專案,您可以使用它們來使您的工作更輕鬆。 這將有大量的資源、想法和概念。 我甚至會給你一些學習資源,以及一些產品的專案範例來學習 React。 一切都是免費的,而且只有 React。 讓我們涵蓋這一切! --- 1. [Mantine Hooks](https://www.npmjs.com/package/@mantine/hooks) - 用於狀態和 UI 管理的 React hooks。 -------------------------------------------------------------------------------------------- ![曼丁鉤子](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g9gxhpt4zpmxgg2cfbqi.png) 這可能不是專門針對 React 的,但是您可以使用這些鉤子來使您的工作更輕鬆。這些鉤子隨時可用,每個鉤子都有許多選項。 如果我必須評價的話,這將是每個人都可以使用的最有用的專案,而不是從頭開始編寫程式碼。 相信我,獲得 60 多個 Hooks 是一件大事,因為他們有一個簡單的方法讓您可以透過簡單的文件查看每個 Hooks 的演示。 開始使用以下 npm 指令。 ``` npm install @mantine/hooks ``` 這就是如何使用`useScrollIntoView`作為 mantine 掛鉤的一部分。 ``` import { useScrollIntoView } from '@mantine/hooks'; import { Button, Text, Group, Box } from '@mantine/core'; function Demo() { const { scrollIntoView, targetRef } = useScrollIntoView<HTMLDivElement>({ offset: 60, }); return ( <Group justify="center"> <Button onClick={() => scrollIntoView({ alignment: 'center', }) } > Scroll to target </Button> <Box style={{ width: '100%', height: '50vh', backgroundColor: 'var(--mantine-color-blue-light)', }} /> <Text ref={targetRef}>Hello there</Text> </Group> ); } ``` 它們幾乎擁有從本地儲存到分頁、滾動視圖、交叉點,甚至一些非常酷的實用程式(例如滴管和文字選擇)的所有功能。這實在太有幫助了! ![滴管](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pighzv57fvyp5uxvw8dz.png) 您可以閱讀[文件](https://mantine.dev/hooks/use-click-outside/)。 如果您正在尋找更多選項,您也可以使用[替代庫](https://antonioru.github.io/beautiful-react-hooks/)。 他們在 GitHub 上擁有超過 23k star,但這不僅僅是為了 hooks,因為他們是 React 的元件庫。 隨著`v7`版本的發布,它的每週下載量已超過 38 萬次,這表明它們正在不斷改進且值得信賴。 https://github.com/mantinedev/mantine Star Mantine Hooks ⭐️ --- 2. [React Grid Layout](https://github.com/react-grid-layout/react-grid-layout) - 可拖曳且可調整大小的網格佈局,具有響應式斷點。 -------------------------------------------------------------------------------------------------------- ![反應網格佈局](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyg7g1bm1d3hvkexrnh3.png) React-Grid-Layout 是專為 React 應用程式建構的響應式網格佈局系統。 透過支援可拖曳、可調整大小和靜態小部件,它提供了使用網格的簡單解決方案。 與 Packery 或 Gridster 等類似系統不同,React-Grid-Layout 不含 jQuery,確保輕量級且高效的實作。 它與伺服器渲染應用程式的無縫整合以及序列化和恢復佈局的能力使其成為開發人員在 React 專案中使用網格佈局的寶貴工具。 開始使用以下 npm 指令。 ``` npm install react-grid-layout ``` 這就是如何使用響應式網格佈局。 ``` import { Responsive as ResponsiveGridLayout } from "react-grid-layout"; class MyResponsiveGrid extends React.Component { render() { // {lg: layout1, md: layout2, ...} const layouts = getLayoutsFromSomewhere(); return ( <ResponsiveGridLayout className="layout" layouts={layouts} breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }} cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }} > <div key="1">1</div> <div key="2">2</div> <div key="3">3</div> </ResponsiveGridLayout> ); } } ``` 您可以閱讀[文件](https://github.com/react-grid-layout/react-grid-layout?tab=readme-ov-file#installation)並查看[演示](https://react-grid-layout.github.io/react-grid-layout/examples/0-showcase.html)。有一系列[演示](https://github.com/react-grid-layout/react-grid-layout?tab=readme-ov-file#demos),甚至可以透過點擊“查看下一個範例”來獲得。 您也可以嘗試[codesandbox](https://codesandbox.io/p/devbox/github/gilbarbara/react-joyride-demo/tree/main/?embed=1)上的東西。 該專案在 GitHub 上有超過 19k+ 的星星,有超過 16k+ 的開發者使用,並且[npm 套件](https://www.npmjs.com/package/react-grid-layout)的每週下載量超過 600k+。 https://github.com/react-grid-layout/react-grid-layout 明星 React 網格佈局 ⭐️ --- 3. [React Spectrum](https://github.com/adobe/react-spectrum) - 提供出色使用者體驗的程式庫和工具的集合。 ----------------------------------------------------------------------------------- ![反應譜](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4wkgbdpd1gve36vgjne.png) React Spectrum 是一個庫和工具的集合,可幫助您建立自適應、可存取且強大的使用者體驗。 它們提供了太多的東西,以至於很難在一篇文章中涵蓋所有內容。 總的來說,他們提供了這四個庫。 ![反應譜](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m97vdq3x7nllmhyjy7p9.png) - [反應譜](https://react-spectrum.adobe.com/react-spectrum/index.html) - [React Stately](https://react-spectrum.adobe.com/react-stately/index.html) - 一組龐大的 React Hooks,為您的設計系統提供跨平台狀態管理。 - [反應詠嘆調](https://react-spectrum.adobe.com/react-aria/index.html) - [國際化](https://react-spectrum.adobe.com/internationalized/index.html) 我們將了解一些有關 React Aria 的內容,它是一個無樣式 React 元件和鉤子庫,可幫助您為應用程式建立可存取的、高品質的 UI 元件。 它經過了各種設備、互動方式和輔助技術的精心測試,以確保為所有用戶提供最佳體驗。 開始使用以下 npm 指令。 ``` npm i react-aria-components ``` 這就是建立自訂`select`的方法。 ``` import {Button, Label, ListBox, ListBoxItem, Popover, Select, SelectValue} from 'react-aria-components'; <Select> <Label>Favorite Animal</Label> <Button> <SelectValue /> <span aria-hidden="true">▼</span> </Button> <Popover> <ListBox> <ListBoxItem>Cat</ListBoxItem> <ListBoxItem>Dog</ListBoxItem> <ListBoxItem>Kangaroo</ListBoxItem> </ListBox> </Popover> </Select> ``` 相信我,出於學習目的,這是一座金礦。 ![選擇的設計結構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ndy61o8vtjjbq78e8vl8.png) 他們使用自己強大的[40 多個樣式元件](https://opensource.adobe.com/spectrum-css/),這比通常提供的要多得多。他們也有自己的一套[設計系統,](https://spectrum.adobe.com/)例如字體、UI、版面、動作等等。 ![造型元件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a047jcb2ou7h057yf2d4.png) ![造型元件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y1w5jq1vfbhd6o9c9ehm.png) 您可以詳細了解[Spectrum](https://react-spectrum.adobe.com/index.html)及其[架構](https://react-spectrum.adobe.com/architecture.html)。 他們在 GitHub 上擁有超過 11,000 顆星,這表明了他們的質量,儘管他們並不廣為人知。研究它們可以為您建立圖書館提供寶貴的見解。 https://github.com/adobe/react-spectrum Star React Spectrum ⭐️ --- 4.[保留 React](https://github.com/StaticMania/keep-react) - Tailwind CSS 和 React.js 的 UI 元件庫。 ------------------------------------------------------------------------------------------- ![保持反應](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5s2z1xig75on0j2gjt1g.png) Keep React 是一個基於 Tailwind CSS 和 React.js 建立的開源元件庫。它提供了一組多功能的預先設計的 UI 元件,使開發人員能夠簡化現代、響應式且具有視覺吸引力的 Web 應用程式的建立。 開始使用以下 npm 指令。 ``` npm i keep-react ``` 這就是使用時間軸的方法。 ``` "use client"; import { Timeline } from "keep-react"; import { CalendarBlank } from "phosphor-react"; export const TimelineComponent = () => { return ( <Timeline horizontal={true}> <Timeline.Item> <Timeline.Point icon={<CalendarBlank size={16} />} /> <Timeline.Content> <Timeline.Title>Keep Library v1.0.0</Timeline.Title> <Timeline.Time>Released on December 2, 2021</Timeline.Time> <Timeline.Body> Get started with dozens of web components and interactive elements. </Timeline.Body> </Timeline.Content> </Timeline.Item> <Timeline.Item> <Timeline.Point icon={<CalendarBlank size={16} />} /> <Timeline.Content> <Timeline.Title>Keep Library v1.1.0</Timeline.Title> <Timeline.Time>Released on December 23, 2021</Timeline.Time> <Timeline.Body> Get started with dozens of web components and interactive elements. </Timeline.Body> </Timeline.Content> </Timeline.Item> <Timeline.Item> <Timeline.Point icon={<CalendarBlank size={16} />} /> <Timeline.Content> <Timeline.Title>Keep Library v1.3.0</Timeline.Title> <Timeline.Time>Released on January 5, 2022</Timeline.Time> <Timeline.Body> Get started with dozens of web components and interactive elements. </Timeline.Body> </Timeline.Content> </Timeline.Item> </Timeline> ); } ``` 輸出如下。 ![時間軸元件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v22pagugp45z68jap3en.png) 流暢的小動畫讓這一切都是值得的,如果你想快速建立一個 UI,沒有任何麻煩,你可以使用它。 ![上傳](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfy9f9w0nc6ipn6wigil.png) ![通知](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5zpwcnozi5ye3wpnev1g.png) 您可以閱讀[文件](https://react.keepdesign.io/docs/getting-started/Introduction)並查看[故事書](https://react-storybook.keepdesign.io/?path=/docs/components-accordion--docs)以進行詳細的使用測驗。 該專案在 GitHub 上有超過 1000 顆星,而且它的一些元件使用起來非常方便。 https://github.com/StaticMania/keep-react Star Keep React ⭐️ --- 5. [React Content Loader](https://github.com/danilowoz/react-content-loader) - SVG 支援的元件,可輕鬆建立骨架載入。 --------------------------------------------------------------------------------------------------- ![反應內容載入器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g8g2yc0zush5vfgwo6hv.png) 該專案為您提供了一個由 SVG 驅動的元件,可以輕鬆建立佔位符載入(如 Facebook 的卡片載入)。 在載入狀態期間使用骨架來向使用者指示內容仍在載入。 總的來說,這是一個非常方便的專案,可以增強整體使用者體驗。 開始使用以下 npm 指令。 ``` npm i react-content-loader --save ``` 您可以這樣使用它。 ``` import React from "react" import ContentLoader from "react-content-loader" const MyLoader = (props) => ( <ContentLoader speed={2} width={400} height={160} viewBox="0 0 400 160" backgroundColor="#f3f3f3" foregroundColor="#ecebeb" {...props} > <rect x="48" y="8" rx="3" ry="3" width="88" height="6" /> <rect x="48" y="26" rx="3" ry="3" width="52" height="6" /> <rect x="0" y="56" rx="3" ry="3" width="410" height="6" /> <rect x="0" y="72" rx="3" ry="3" width="380" height="6" /> <rect x="0" y="88" rx="3" ry="3" width="178" height="6" /> <circle cx="20" cy="20" r="20" /> </ContentLoader> ) export default MyLoader ``` ![輸出](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnvqlf6fmg2fayd29ojr.png) 您甚至可以拖曳單一骨架或使用為 Facebook 和 Instagram 等不同社群媒體預先定義的骨架。 您可以閱讀[文件](https://github.com/danilowoz/react-content-loader?tab=readme-ov-file#gettingstarted)並查看[演示](https://skeletonreact.com/)。 該專案在 GitHub 上擁有 13k+ Stars,並在 GitHub 上有 45k+ 開發人員使用。 https://github.com/danilowoz/react-content-loader Star React 內容載入器 ⭐️ --- 6. [React PDF](https://github.com/diegomura/react-pdf) - 使用 React 建立 PDF 檔案。 ---------------------------------------------------------------------------- ![反應 pdf](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6jd7sz8eqda09rgjpf13.png) 該套件用於使用 React 建立 PDF。 開始使用以下 npm 指令。 ``` npm install @react-pdf/renderer --save ``` 您可以這樣使用它。 ``` import React from 'react'; import { Document, Page, Text, View, StyleSheet } from '@react-pdf/renderer'; // Create styles const styles = StyleSheet.create({ page: { flexDirection: 'row', backgroundColor: '#E4E4E4', }, section: { margin: 10, padding: 10, flexGrow: 1, }, }); // Create Document Component const MyDocument = () => ( <Document> <Page size="A4" style={styles.page}> <View style={styles.section}> <Text>Section #1</Text> </View> <View style={styles.section}> <Text>Section #2</Text> </View> </Page> </Document> ); ``` ![輸出](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cb5fpfzijv3g5fi5utmw.png) ![輸出pdf分頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f46t80n0redm14icia1r.png) 您可以閱讀[文件](https://react-pdf.org/)並查看[演示](https://react-pdf.org/repl)。 React-pdf 現在提供了一個名為`usePDF`的鉤子,可以透過 React hook API 存取所有 PDF 建立功能。如果您需要更多控製文件的呈現方式或更新頻率,這非常有用。 ``` const [instance, update] = usePDF({ document }); ``` 該專案在 GitHub 上有 13k+ Stars,有超過 270 個版本,[每週下載量超過 400k](https://www.npmjs.com/package/@react-pdf/renderer) ,這是一個好兆頭。 https://github.com/diegomura/react-pdf Star React PDF ⭐️ --- 7. [Recharts](https://github.com/recharts/recharts) - 使用 React 和 D3 建立的重新定義的圖表庫。 -------------------------------------------------------------------------------- ![重新繪製圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6817tmlix6n7wtgp1yq.png) 該庫的主要目的是幫助您輕鬆地在 React 應用程式中編寫圖表。 Recharts 的主要原則是。 1. 只需使用 React 元件進行部署即可。 2. 原生 SVG 支持,輕量級,僅依賴一些 D3 子模組。 3. 聲明性元件、圖表元件純粹是表示性的。 開始使用以下 npm 指令。 ``` npm install recharts ``` 您可以這樣使用它。 ``` <LineChart width={500} height={300} data={data} accessibilityLayer> <XAxis dataKey="name"/> <YAxis/> <CartesianGrid stroke="#eee" strokeDasharray="5 5"/> <Line type="monotone" dataKey="uv" stroke="#8884d8" /> <Line type="monotone" dataKey="pv" stroke="#82ca9d" /> <Tooltip/> </LineChart> ``` ![輸出](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uqtp999q1ahq8ajmvuwf.png) 您可以閱讀[文件](https://recharts.org/en-US/guide)並查看有關[Storybook](https://recharts.org/en-US/storybook)的更多資訊。 他們提供了大量的選項來自訂它,這就是開發人員喜歡它的原因。他們也提供一般常見問題的[wiki](https://github.com/recharts/recharts/wiki)頁面。 您也可以在此處的codesandbox 上嘗試。 https://codesandbox.io/embed/kec3v?view=Editor+%2B+Preview&module=%2Fsrc%2Findex.tsx 該專案在 GitHub 上有 22k+ Stars,有 200k+ 開發人員使用。 https://github.com/recharts/recharts 明星 Recharts ⭐️ --- 8. [React Joyride](https://github.com/gilbarbara/react-joyride) - 在您的應用程式中建立導遊。 ------------------------------------------------------------------------------- ![反應兜風](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ph7rt2bxqbxi67r47on8.png) ![反應兜風](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ov4wzohwszgv5v06cin4.png) 導覽是向新用戶展示您的應用程式或解釋新功能的絕佳方式。它改善了用戶體驗並可以創造個人化的觸感。 開始使用以下 npm 指令。 ``` npm i react-joyride ``` 您可以這樣使用它。 ``` import React, { useState } from 'react'; import Joyride from 'react-joyride'; /* * If your steps are not dynamic you can use a simple array. * Otherwise you can set it as a state inside your component. */ const steps = [ { target: '.my-first-step', content: 'This is my awesome feature!', }, { target: '.my-other-step', content: 'This another awesome feature!', }, ]; export default function App() { // If you want to delay the tour initialization you can use the `run` prop return ( <div> <Joyride steps={steps} /> ... </div> ); } ``` 它們還提供[元件列表](https://docs.react-joyride.com/custom-components)以及自訂預設用戶介面的簡單方法。 您可以閱讀[文件](https://docs.react-joyride.com/)並查看[演示](https://react-joyride.com/)。 您也可以嘗試[codesandbox](https://codesandbox.io/p/devbox/github/gilbarbara/react-joyride-demo/tree/main/?embed=1)上的東西。 他們在 GitHub 上有超過 6k 顆星,npm 套件每週下載量超過 25 萬次。 https://github.com/gilbarbara/react-joyride Star React Joyride ⭐️ --- 9. [SVGR](https://github.com/gregberge/svgr) - 將 SVG 轉換為 React 元件。 ------------------------------------------------------------------ ![svgr](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/94hpre3yl3ttu5zdexsv.png) SVGR 是一個將 SVG 轉換為 React 元件的通用工具。 它需要一個原始的 SVG 並將其轉換為隨時可用的 React 元件。 開始使用以下 npm 指令。 ``` npm install @svgr/core ``` 例如,您採用這個 SVG。 ``` <?xml version="1.0" encoding="UTF-8"?> <svg width="48px" height="1px" viewBox="0 0 48 1" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" > <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch --> <title>Rectangle 5</title> <desc>Created with Sketch.</desc> <defs></defs> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="19-Separator" transform="translate(-129.000000, -156.000000)" fill="#063855" > <g id="Controls/Settings" transform="translate(80.000000, 0.000000)"> <g id="Content" transform="translate(0.000000, 64.000000)"> <g id="Group" transform="translate(24.000000, 56.000000)"> <g id="Group-2"> <rect id="Rectangle-5" x="25" y="36" width="48" height="1"></rect> </g> </g> </g> </g> </g> </g> </svg> ``` 執行SVGR後,將轉換為. ``` import * as React from 'react' const SvgComponent = (props) => ( <svg width="1em" height="1em" viewBox="0 0 48 1" {...props}> <path d="M0 0h48v1H0z" fill="currentColor" fillRule="evenodd" /> </svg> ) export default SvgComponent ``` 它使用[SVGO](https://github.com/svg/svgo)優化 SVG,並使用 Prettier 格式化程式碼。 將 HTML 轉換為 JSX 需要幾個步驟: 1. 將 SVG 轉換為 HAST (HTML AST) 2. 將 HAST 轉換為 Babel AST (JSX AST) 3. 使用 Babel 轉換 AST(重新命名屬性、更改屬性值…) 您可以在[Playground](https://react-svgr.com/playground/)閱讀[文件](https://react-svgr.com/docs/getting-started)並檢查內容。 該專案在 GitHub 上擁有 10k+ Stars,有超過 800 萬開發者使用,npm 上每週下載量超過 800k。 https://github.com/gregberge/svgr 明星 SVGR ⭐️ --- 10. [React Sortable Tree](https://github.com/frontend-collective/react-sortable-tree) - 用於巢狀資料和層次結構的拖放可排序元件。 ------------------------------------------------------------------------------------------------------------ ![反應可排序樹](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/907c4rnmev2wx1abq0r7.png) 一個 React 元件,支援對分層資料進行拖放排序。 ![反應可排序樹](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z4tm32vuteqaw5m7crag.png) 開始使用以下 npm 指令。 ``` npm install react-sortable-tree --save ``` 您可以這樣使用它。 ``` import React, { Component } from 'react'; import SortableTree from 'react-sortable-tree'; import 'react-sortable-tree/style.css'; // This only needs to be imported once in your app export default class Tree extends Component { constructor(props) { super(props); this.state = { treeData: [ { title: 'Chicken', children: [{ title: 'Egg' }] }, { title: 'Fish', children: [{ title: 'fingerline' }] }, ], }; } render() { return ( <div style={{ height: 400 }}> <SortableTree treeData={this.state.treeData} onChange={treeData => this.setState({ treeData })} /> </div> ); } } ``` 檢查由此獲得的各種[道具選項](https://github.com/frontend-collective/react-sortable-tree?tab=readme-ov-file#props)和[主題](https://github.com/frontend-collective/react-sortable-tree?tab=readme-ov-file#featured-themes)。 您可以閱讀[文件](https://github.com/frontend-collective/react-sortable-tree?tab=readme-ov-file#getting-started)並查看[Storybook](https://frontend-collective.github.io/react-sortable-tree/?path=/story/basics--minimal-implementation) ,以獲取一些基本和高級功能的演示。 它可能不會被積極維護(仍然沒有存檔),因此您也可以使用[維護的 fork 版本](https://github.com/nosferatu500/react-sortable-tree)。 該專案在 GitHub 上擁有超過 4,500 個 Star,並被超過 5,000 名開發人員使用。 https://github.com/frontend-collective/react-sortable-tree Star React 可排序樹 ⭐️ --- 11. [React Hot Toast](https://github.com/timolins/react-hot-toast) - 冒煙的 Hot React 通知。 -------------------------------------------------------------------------------------- ![反應熱吐司](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lw4veo990lspkchhwz64.png) React Hot Toast 透過簡單的自訂選項提供了驚人的 🔥 預設體驗。它利用 Promise API 進行自動加載,確保平穩過渡。 它重量輕,不到 5kb,但仍然可以存取,同時為開發人員提供了像`useToaster()`這樣的無頭鉤子。 首先將 Toaster 加入到您的應用程式中。它將負責渲染發出的所有通知。現在您可以從任何地方觸發 toast() ! 開始使用以下 npm 指令。 ``` npm install react-hot-toast ``` 這就是它的易用性。 ``` import toast, { Toaster } from 'react-hot-toast'; const notify = () => toast('Here is your toast.'); const App = () => { return ( <div> <button onClick={notify}>Make me a toast</button> <Toaster /> </div> ); }; ``` ![主題選項](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tl8ezjabacdllw8qnd41.png) ![主題選項](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zksldf8goqbytcuumhac.png) 他們有很多自訂選項,但`useToaster()`掛鉤為您提供了一個無頭系統,可以為您管理通知狀態。這使得建立您的通知系統變得更加容易。 您可以閱讀[文件](https://react-hot-toast.com/docs)、[樣式指南](https://react-hot-toast.com/docs/styling)並查看[示範](https://react-hot-toast.com/)。 該專案在 GitHub 上有 8k+ Stars,有 230k+ 開發者使用。 https://github.com/timolins/react-hot-toast Star React Hot Toast ⭐️ --- 12. [Payload](https://github.com/payloadcms/payload) - 建立現代後端+管理 UI 的最佳方式。 -------------------------------------------------------------------------- ![有效負載](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xev60f07ilzqlfdwni0p.png) Payload 是一個無頭 CMS 和應用程式框架。它旨在促進您的開發過程,但重要的是,當您的應用程式變得更加複雜時,不要妨礙您。 Payload 沒有黑魔法,完全開源,它既是一個應用程式框架,也是一個無頭 CMS。它確實是適用於 TypeScript 的 Rails,並且您會獲得一個管理面板。您可以使用此[YouTube 影片](https://www.youtube.com/watch?v=In_lFhzmbME)了解有關 Payload 的更多資訊。 https://www.youtube.com/watch?v=In\_lFhzmbME 您可以透過使用Payload來了解[其中涉及的概念](https://payloadcms.com/docs/getting-started/concepts)。 ![特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqn1uqupsdkexoq913mm.png) 有效負載透過您選擇的資料庫適配器與您的資料庫進行互動。目前,Payload 正式支援兩種資料庫適配器: 1. MongoDB 與 Mongoose 2. Postgres 帶毛毛雨 開始使用以下命令。 ``` npx create-payload-app@latest ``` 您必須產生 Payload 金鑰並更新`server.ts`以初始化 Payload。 ``` import express from 'express' import payload from 'payload' require('dotenv').config() const app = express() const start = async () => { await payload.init({ secret: process.env.PAYLOAD_SECRET, express: app, }) app.listen(3000, async () => { console.log( "Express is now listening for incoming connections on port 3000." ) }) } start() ``` ![使用 nextjs 進行有效負載](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ghnnf34k70hpb0zjsf5f.png) 您可以閱讀[文件](https://payloadcms.com/docs/getting-started/what-is-payload)並查看[演示](https://demo.payloadcms.com/?_gl=1*9x0za3*_ga*NzEzMzkwNzIuMTcxMDE2NDk1MA..*_ga_FLQ5THRMZQ*MTcxMDE2NDk1MC4xLjEuMTcxMDE2NDk1MS4wLjAuMA..)。 他們還提供與 Payload + Stripe 無縫整合的[電子商務模板](https://github.com/payloadcms/payload/tree/main/templates/ecommerce)。此範本具有令人驚嘆的、功能齊全的前端,包括購物車、結帳流程、訂單管理等元件。 Payload 在 GitHub 上擁有 18k+ Stars,並且有超過 290 個版本,因此它們不斷改進,尤其是在資料庫支援方面。 https://github.com/payloadcms/payload 明星有效負載 ⭐️ --- 13. [React Player](https://github.com/cookpete/react-player) - 用於播放各種 URL 的 React 元件。 ------------------------------------------------------------------------------------- ![反應玩家](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/immw7vlgrdfbfxgts0a0.png) 用於播放各種 URL 的 React 元件,包括檔案路徑、YouTube、Facebook、Twitch、SoundCloud、Streamable、Vimeo、Wistia、Mixcloud、DailyMotion 和 Kaltura。您可以看到[支援的媒體](https://github.com/cookpete/react-player?tab=readme-ov-file#supported-media)清單。 ReactPlayer 的維護工作由 Mux 接管,這使它們成為一個不錯的選擇。 開始使用以下 npm 指令。 ``` npm install react-player ``` 您可以這樣使用它。 ``` import React from 'react' import ReactPlayer from 'react-player' // Render a YouTube video player <ReactPlayer url='https://www.youtube.com/watch?v=LXb3EKWsInQ' /> // If you only ever use one type, use imports such as react-player/youtube to reduce your bundle size. // like this: import ReactPlayer from 'react-player/youtube' ``` 您也可以使用`react-player/lazy`為您傳入的URL 延遲載入適當的播放器。這會為您的輸出加入幾個reactPlayer 區塊,但會減少主包的大小。 ``` import React from 'react' import ReactPlayer from 'react-player/lazy' // Lazy load the YouTube player <ReactPlayer url='https://www.youtube.com/watch?v=ysz5S6PUM-U' /> ``` 您可以閱讀[文件](https://github.com/cookpete/react-player?tab=readme-ov-file#props)並查看[演示](https://cookpete.github.io/react-player/)。他們提供了大量的選項,包括加入字幕並以簡單的方式使其響應。 它們在 GitHub 上擁有超過 8000 顆星,被超過 135,000 名開發人員使用,並且 npm 軟體包[每週的下載量超過 800k](https://www.npmjs.com/package/react-player) 。 https://github.com/cookpete/react-player 明星 React 播放器 ⭐️ --- 14. [Victory](https://github.com/FormidableLabs/victory) - 用於建立互動式資料視覺化的 React 元件。 ---------------------------------------------------------------------------------- ![勝利](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbayfgbrutvffkk2slja.png) Victory 是一個可組合 React 元件的生態系統,用於建立互動式資料視覺化。 ![元件類型](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ua3jegboex4n21aid20.png) 開始使用以下 npm 指令。 ``` npm i --save victory ``` 您可以這樣使用它。 ``` <VictoryChart domainPadding={{ x: 20 }} > <VictoryHistogram style={{ data: { fill: "#c43a31" } }} data={sampleHistogramDateData} bins={[ new Date(2020, 1, 1), new Date(2020, 4, 1), new Date(2020, 8, 1), new Date(2020, 11, 1) ]} /> </VictoryChart> ``` 這就是它的渲染方式。他們還提供通常有用的動畫和主題選項。 ![勝利圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdxztxui9zjtue0fz1jo.png) 您可以閱讀[文件](https://commerce.nearform.com/open-source/victory/docs)並按照[教學](https://commerce.nearform.com/open-source/victory/docs/native)開始。他們提供大約 15 種不同的圖表選項。 它也可用於[React Native(文件)](https://commerce.nearform.com/open-source/victory/docs/native) ,所以這是一個優點。我還建議您查看他們的常見[問題解答](https://commerce.nearform.com/open-source/victory/docs/faq#frequently-asked-questions-faq),其中描述了常見問題的程式碼解決方案和解釋,例如樣式、註釋(標籤)、處理軸。 該專案在 GitHub 上擁有 10k+ Stars,並在 GitHub 上有 23k+ 開發人員使用。 https://github.com/FormidableLabs/victory 勝利之星 ⭐️ --- 15. [React Slick](https://github.com/akiran/react-slick) - React 輪播元件。 ---------------------------------------------------------------------- ![反應圓滑](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fn2aafcxs281yliyyv0.png) React Slick 是一個使用 React 建構的輪播元件。它是一個光滑的旋轉木馬的反應端口 開始使用以下 npm 指令。 ``` npm install react-slick --save ``` 這是使用自訂分頁的方法。 ``` import React, { Component } from "react"; import Slider from "react-slick"; import { baseUrl } from "./config"; function CustomPaging() { const settings = { customPaging: function(i) { return ( <a> <img src={`${baseUrl}/abstract0${i + 1}.jpg`} /> </a> ); }, dots: true, dotsClass: "slick-dots slick-thumb", infinite: true, speed: 500, slidesToShow: 1, slidesToScroll: 1 }; return ( <div className="slider-container"> <Slider {...settings}> <div> <img src={baseUrl + "/abstract01.jpg"} /> </div> <div> <img src={baseUrl + "/abstract02.jpg"} /> </div> <div> <img src={baseUrl + "/abstract03.jpg"} /> </div> <div> <img src={baseUrl + "/abstract04.jpg"} /> </div> </Slider> </div> ); } export default CustomPaging; ``` ![自訂分頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hh3qtgnftoapsrdx8w4y.png) 您可以閱讀有關可用的[prop 選項](https://react-slick.neostack.com/docs/api)和[方法](https://react-slick.neostack.com/docs/api#methods)的資訊。 您可以閱讀[文件](https://react-slick.neostack.com/docs/get-started)和所有帶有程式碼和輸出[的範例集](https://react-slick.neostack.com/docs/example/)。 他們在 GitHub 上有超過 11k 顆星,並且有超過 36 萬開發者在 GitHub 上使用它。 https://github.com/akiran/react-slick Star React Slick ⭐️ --- 16. [Medusa](https://github.com/medusajs/medusa) - 數位商務的建構模組。 ------------------------------------------------------------- ![美杜莎](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h7vd1qsx7l1jdsz2cnq0.png) Medusa 是一組商務模組和工具,可讓您建立豐富、可靠且高效能的商務應用程式,而無需重新發明核心商務邏輯。 這些模組可以客製化並用於建立高級電子商務商店、市場或任何需要基礎商務原語的產品。所有模組都是開源的,可以在 npm 上免費取得。 開始使用以下 npm 指令。 ``` npm install medusa-react @tanstack/[email protected] @medusajs/medusa ``` 將其包含在`app.ts`中。 只有 MedusaProvider 的子級才能從其鉤子中受益。因此,Storefront 元件及其子元件現在可以使用 Medusa React 公開的鉤子。 ``` import { MedusaProvider } from "medusa-react" import Storefront from "./Storefront" import { QueryClient } from "@tanstack/react-query" import React from "react" const queryClient = new QueryClient() const App = () => { return ( <MedusaProvider queryClientProviderProps={{ client: queryClient }} baseUrl="http://localhost:9000" > <Storefront /> </MedusaProvider> ) } export default App ``` 例如,這就是您如何使用突變來建立購物車。 ``` import { useCreateCart } from "medusa-react" const Cart = () => { const createCart = useCreateCart() const handleClick = () => { createCart.mutate({}) // create an empty cart } return ( <div> {createCart.isLoading && <div>Loading...</div>} {!createCart.data?.cart && ( <button onClick={handleClick}> Create cart </button> )} {createCart.data?.cart?.id && ( <div>Cart ID: {createCart.data?.cart.id}</div> )} </div> ) } export default Cart ``` 他們提供了一套電子商務模組(大量選項),例如折扣、價目表、禮品卡等。 ![電子商務模組](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x00lbkpny66esa1yep4u.png) 它們還提供了一種簡單的管理員和客戶身份驗證方法,您可以在[文件](https://docs.medusajs.com/)中閱讀。 他們提供了[nextjs 入門模板](https://docs.medusajs.com/starters/nextjs-medusa-starter)和[Medusa React](https://docs.medusajs.com/medusa-react/overview)作為 SDK。 該專案在 GitHub 上有 22k+ Stars,有 4k+ 開發者使用。 https://github.com/medusajs/medusa 明星美杜莎 ⭐️ --- 17. [React Markdown](https://github.com/remarkjs/react-markdown) - React 的 Markdown 元件. --------------------------------------------------------------------------------------- ![反應降價](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcl4bq3m0r415mknvv5h.png) Markdown 至關重要,使用 React 渲染它對於各種場景都非常有用。 它提供了一個 React 元件,能夠安全地將一串 Markdown 渲染到 React 元素中。您可以透過傳遞外掛程式並指定要使用的元件而不是標準 HTML 元素來自訂 Markdown 的轉換。 開始使用以下 npm 指令。 ``` npm i react-markdown ``` 您可以這樣使用它。 ``` import React from 'react' import {createRoot} from 'react-dom/client' import Markdown from 'react-markdown' import remarkGfm from 'remark-gfm' const markdown = `Just a link: www.nasa.gov.` createRoot(document.body).render( <Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown> ) ``` 等效的 JSX 是。 ``` <p> Just a link: <a href="http://www.nasa.gov">www.nasa.gov</a>. </p> ``` 他們還提供了一份[備忘錄](https://commonmark.org/help/)和一個十分鐘的逐步[教學](https://commonmark.org/help/tutorial/)。 ![教學](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oboj1ooemoo2j9uh2d7.png) 您可以閱讀[文件](https://github.com/remarkjs/react-markdown?tab=readme-ov-file#install)並查看[演示](https://remarkjs.github.io/react-markdown/)。 該專案在 GitHub 上有 12k+ Stars,[每週下載量超過 2700k](https://www.npmjs.com/package/react-markdown) ,並被 200k+ 開發人員使用,證明了它的真正有用性。 https://github.com/remarkjs/react-markdown Star React Markdown ⭐️ --- 18. [React JSONSchema Form](https://github.com/rjsf-team/react-jsonschema-form) - 用於從 JSON Schema 建立 Web 表單。 ------------------------------------------------------------------------------------------------------------ ![反應 jsonform 模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36bma59hylme02fg5mmi.png) `react-jsonschema-form`會自動從 JSON Schema 產生 React 表單,使其非常適合僅使用 JSON schema 為任何資料產生表單。它提供了像 uiSchema 這樣的自訂選項來自訂預設主題之外的表單外觀。 開始使用以下 npm 指令。 ``` npm install @rjsf/core @rjsf/utils @rjsf/validator-ajv8 --save ``` 您可以這樣使用它。 ``` import { RJSFSchema } from '@rjsf/utils'; import validator from '@rjsf/validator-ajv8'; const schema: RJSFSchema = { title: 'Todo', type: 'object', required: ['title'], properties: { title: { type: 'string', title: 'Title', default: 'A new task' }, done: { type: 'boolean', title: 'Done?', default: false }, }, }; const log = (type) => console.log.bind(console, type); render( <Form schema={schema} validator={validator} onChange={log('changed')} onSubmit={log('submitted')} onError={log('errors')} />, document.getElementById('app') ); ``` 他們提供[高級定制](https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/)選項,包括定制小部件。 您可以閱讀[文件](https://rjsf-team.github.io/react-jsonschema-form/docs/)並查看[即時遊樂場](https://rjsf-team.github.io/react-jsonschema-form/)。 它在 GitHub 上擁有超過 13k 個 Star,並被 5k+ 開發人員使用。他們在`v5`上發布了 190 多個版本,因此他們正在不斷改進。 https://github.com/rjsf-team/react-jsonschema-form Star React JSONSchema 表單 ⭐️ --- 19. [Craft.js](https://github.com/prevwong/craft.js) - 建立可擴充的拖放頁面編輯器。 --------------------------------------------------------------------- ![craft.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ydxmz82mswa2tlk5onbs.png) 頁面編輯器可以增強使用者體驗,但從頭開始建立頁面編輯器可能會令人望而生畏。現有庫提供具有可編輯元件的預先建置編輯器,但自訂通常需要修改庫本身。 Craft.js 透過模組化頁面編輯器元件、透過拖放功能簡化自訂以及渲染管理來解決這個問題。在 React 中設計你的編輯器,無需複雜的插件系統,專注於你的特定需求和規格。 開始使用以下 npm 指令。 ``` npm install --save @craftjs/core ``` 他們還提供了有關如何入門的[簡短教程](https://craft.js.org/docs/guides/basic-tutorial)。我不會介紹它,因為它非常簡單且詳細。 您可以閱讀[文件](https://craft.js.org/docs/overview)並查看[即時演示](https://craft.js.org/)以及另一個[即時範例](https://craft.js.org/examples/basic)。 它在 GitHub 上有大約 6k+ Stars,但考慮到它們正在改進,仍然很有用。 https://github.com/prevwong/craft.js Star Craft.js ⭐️ --- 20. [Gatsby](https://github.com/gatsbyjs/gatsby) - 最好的基於 React 的框架,具有內建的效能、可擴展性和安全性。 ------------------------------------------------------------------------------------ ![蓋茲比](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ybxi9gplvm2kr8abbtzy.png) Gatsby 是一個基於 React 的框架,使開發人員能夠建立閃電般快速的網站和應用程式,將動態渲染的靈活性與靜態網站生成的速度融為一體。 憑藉可自訂的 UI 和對各種資料來源的支援等功能,Gatsby 提供了無與倫比的控制和可擴展性。此外,它還可以自動進行效能最佳化,使其成為靜態網站的首選。 開始使用以下 npm 指令。 ``` npm init gatsby ``` 這就是如何在 Gatsby(反應元件)中使用`Link` 。 ``` import React from "react" import { Link } from "gatsby" const Page = () => ( <div> <p> Check out my <Link to="/blog">blog</Link>! </p> <p> {/* Note that external links still use `a` tags. */} Follow me on <a href="https://twitter.com/gatsbyjs">Twitter</a>! </p> </div> ) ``` 他們提供了一組[入門模板,](https://www.gatsbyjs.com/starters/)其中包含如何使用它、涉及的依賴項以及每個模板的演示。 ![範本](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8l35rwb1is60d5q506qu.png) 您可以閱讀有關 Gatsby 的一些[常見概念,](https://www.gatsbyjs.com/docs/conceptual/gatsby-concepts/)例如 React Hydration、Gatsby 建置流程等。 您可以閱讀[文件](https://www.gatsbyjs.com/docs/)並查看入門[教學課程](https://www.gatsbyjs.com/docs/tutorial/)。 Gatsby 在 GitHub 上擁有超過 55,000 顆星,並被超過 240,000 名開發者使用 https://github.com/gatsbyjs/gatsby 明星蓋茲比 ⭐️ --- 21. [Chat UI Kit React](https://github.com/chatscope/chat-ui-kit-react) - 在幾分鐘內使用 React 建立您的聊天 UI。 -------------------------------------------------------------------------------------------------- ![chatscope 聊天 ui 套件反應](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ynb25x1se0riwbvq5uv.png) Chatscope 的聊天 UI 工具包是一個用於開發網頁聊天應用程式的開源 UI 工具包。 儘管該專案並未廣泛使用,但這些功能對於剛剛查看該專案的初學者來說還是很有用的。 ![特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m1y87b1clbi00tojxgzi.png) 開始使用以下 npm 指令。 ``` npm install @chatscope/chat-ui-kit-react ``` 這就是建立 GUI 的方法。 ``` import styles from '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css'; import { MainContainer, ChatContainer, MessageList, Message, MessageInput } from '@chatscope/chat-ui-kit-react'; <div style={{ position:"relative", height: "500px" }}> <MainContainer> <ChatContainer> <MessageList> <Message model={{ message: "Hello my friend", sentTime: "just now", sender: "Joe" }} /> </MessageList> <MessageInput placeholder="Type message here" /> </ChatContainer> </MainContainer> </div> ``` 您可以閱讀[文件](https://chatscope.io/docs/)。 故事書中有更[詳細的文件](https://chatscope.io/storybook/react/?path=/docs/documentation-introduction--docs)。 它提供了一些方便的元件,例如[`TypingIndicator`](https://chatscope.io/storybook/react/?path=/docs/components-typingindicator--docs) 、 [`Multiline Incoming`](https://chatscope.io/storybook/react/?path=/story/components-message--multiline-incoming)等等。 我知道你們中的一些人更喜歡透過部落格來了解整個結構,因此你可以閱讀使用 Chat UI Kit React 的 Rollbar 的[如何將 ChatGPT 與 React 整合](https://rollbar.com/blog/how-to-integrate-chatgpt-with-react/)。 您可以看到的一些演示: - [聊天機器人使用者介面](https://mars.chatscope.io/) - [與朋友聊天](https://chatscope.io/demo/chat-friends/)- 看看這個! ![聊天朋友演示快照](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0hyhqti9yl02rludkocy.png) https://github.com/chatscope/chat-ui-kit-react Star Chat UI Kit React ⭐️ --- 22. [Botonic](https://github.com/hubtype/botonic) - 用於建立會話應用程式的 React 框架。 ------------------------------------------------------------------------- ![植物性的](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yxeslrg9cjbkej0hcth4.png) Botonic 是一個全端 Javascript 框架,用於建立在多個平台上執行的聊天機器人和現代對話應用程式:Web、行動和訊息應用程式(Messenger、WhatsApp、Telegram 等)。它建構在 ⚛️ React、Serverless 和 Tensorflow.js 之上。 如果您不了解對話應用程式的概念,可以在[官方部落格](https://www.hubtype.com/blog/what-are-conversational-apps)上閱讀它們。 使用 Botonic,您可以建立包含最佳文字外介面(簡單性、自然語言互動)和圖形介面(多媒體、視覺上下文、豐富互動)的會話應用程式。 這是一個強大的組合,可以提供比僅依賴文字和 NLP 的傳統聊天機器人更好的用戶體驗。 這就是 Botonic 的簡單方式。 ``` export default class extends React.Component { static async botonicInit({ input, session, params, lastRoutePath }) { await humanHandOff(session)) } render() { return ( <Text> Thanks for contacting us! One of our agents will attend you as soon as possible. </Text> ) } } ``` 它們也支援 TypeScript,所以這是一個優點。 您可以看到一些使用 Botonic 建置的[範例](https://botonic.io/examples/)及其原始程式碼。 您可以閱讀[文件](https://botonic.io/docs/welcome)以及如何[從頭開始建立會話應用程式](https://botonic.io/docs/create-convapp)。 https://github.com/hubtype/botonic Star Botonic ⭐️ --- 23. [React Flowbite](https://github.com/themesberg/flowbite-react) - 為 Flowbite 和 Tailwind CSS 建構的 React 元件. ------------------------------------------------------------------------------------------------------------ ![反應流咬](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vt1coti9k3ppmv0y28u.png) 每個人對他們想要用來建立網站的使用者介面都有不同的偏好。 Flowbite React 是 UI 元件的開源集合,在 React 中建置,具有來自 Tailwind CSS 的實用程式類,您可以將其用作使用者介面和網站的起點。 開始使用以下 npm 指令。 ``` npm i flowbite-react ``` 這是一起使用表格和鍵盤元件的方法。 ``` 'use client'; import { Kbd, Table } from 'flowbite-react'; import { MdKeyboardArrowDown, MdKeyboardArrowLeft, MdKeyboardArrowRight, MdKeyboardArrowUp } from 'react-icons/md'; function Component() { return ( <Table> <Table.Head> <Table.HeadCell>Key</Table.HeadCell> <Table.HeadCell>Description</Table.HeadCell> </Table.Head> <Table.Body className="divide-y"> <Table.Row className="bg-white dark:border-gray-700 dark:bg-gray-800"> <Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white"> <Kbd>Shift</Kbd> <span>or</span> <Kbd>Tab</Kbd> </Table.Cell> <Table.Cell>Navigate to interactive elements</Table.Cell> </Table.Row> <Table.Row className="bg-white dark:border-gray-700 dark:bg-gray-800"> <Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white"> <Kbd>Enter</Kbd> or <Kbd>Spacebar</Kbd> </Table.Cell> <Table.Cell>Ensure elements with ARIA role="button" can be activated with both key commands.</Table.Cell> </Table.Row> <Table.Row className="bg-white dark:border-gray-700 dark:bg-gray-800"> <Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white"> <span className="inline-flex gap-1"> <Kbd icon={MdKeyboardArrowUp} /> <Kbd icon={MdKeyboardArrowDown} /> </span> <span> or </span> <span className="inline-flex gap-1"> <Kbd icon={MdKeyboardArrowLeft} /> <Kbd icon={MdKeyboardArrowRight} /> </span> </Table.Cell> <Table.Cell>Choose and activate previous/next tab.</Table.Cell> </Table.Row> </Table.Body> </Table> ); } ``` ![kbd 和表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnu5xqlqob72t9oxkb4k.png) 您可以閱讀[文件](https://www.flowbite-react.com/docs/getting-started/introduction)並查看[Storybook](https://storybook.flowbite-react.com/?path=/story/components-accordion--always-open)中的功能。您也可以查看[元件](https://www.flowbite-react.com/docs/components/accordion)清單。 在我看來,如果您想快速設定 UI,但又不想最終為高品質的開源專案使用預先定義的庫元件,那麼這很好。 該專案在 GitHub 上擁有超過 1,500 顆星,擁有超過 37,000 名開發者的用戶群,並受到社群的廣泛認可和信任,使其成為一個可靠的選擇。 https://github.com/themesberg/flowbite-react Star React Flowbite ⭐️ --- 24. [DND 套件](https://github.com/clauderic/dnd-kit)- 輕量級、高效能、可存取且可擴展的拖放功能。 ------------------------------------------------------------------------- ![免打擾套件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oz5m8hf4t4u4v2jzusl1.png) 這是一個強大的 React 拖放工具包,擁有可自訂的碰撞檢測、多個啟動器和自動滾動等功能。 它的設計考慮到了 React,提供了方便集成的鉤子,無需進行重大的架構更改。支援從清單到網格和虛擬化清單的各種用例,它既是動態的又是輕量級的,沒有外部相依性。 開始使用以下 npm 指令。 ``` npm install @dnd-kit/core ``` 這就是建立可拖放元件的方法。 `Example.jsx` ``` import React, {useState} from 'react'; import {DndContext} from '@dnd-kit/core'; import {Draggable} from './Draggable'; import {Droppable} from './Droppable'; function Example() { const [parent, setParent] = useState(null); const draggable = ( <Draggable id="draggable"> Go ahead, drag me. </Draggable> ); return ( <DndContext onDragEnd={handleDragEnd}> {!parent ? draggable : null} <Droppable id="droppable"> {parent === "droppable" ? draggable : 'Drop here'} </Droppable> </DndContext> ); function handleDragEnd({over}) { setParent(over ? over.id : null); } } ``` `Droppable.jsx` ``` import React from 'react'; import {useDroppable} from '@dnd-kit/core'; export function Droppable(props) { const {isOver, setNodeRef} = useDroppable({ id: props.id, }); const style = { opacity: isOver ? 1 : 0.5, }; return ( <div ref={setNodeRef} style={style}> {props.children} </div> ); } ``` `Draggable.jsx` ``` import React from 'react'; import {useDraggable} from '@dnd-kit/core'; import {CSS} from '@dnd-kit/utilities'; function Draggable(props) { const {attributes, listeners, setNodeRef, transform} = useDraggable({ id: props.id, }); const style = { // Outputs `translate3d(x, y, 0)` transform: CSS.Translate.toString(transform), }; return ( <button ref={setNodeRef} style={style} {...listeners} {...attributes}> {props.children} </button> ); } ``` 我將可拖曳元件放在可放置元件上。 ![自訂元件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cf98be5hq9am3f2s1dwv.png) 您可以閱讀[文件](https://docs.dndkit.com/)以及滑鼠和指標等[感測器的選項](https://docs.dndkit.com/introduction/installation#core-library)。 它在 GitHub 上擁有 10k+ Stars,並被 GitHub 上 47k+ 開發人員使用。 https://github.com/clauderic/dnd-kit 明星免打擾套件 ⭐️

使用 React 開發時應該了解的 17 個函式庫

長話短說 ==== 我收集了您應該了解的 React 庫,以建立許多不同類型的專案並成為 React 奇才🧙‍♂️。 其中每一項都是獨一無二的,並且都有自己的用例。 別忘了給他們加星號🌟 讓我們開始吧! ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/16rwdtymlmp6y17ocz59.gif) --- 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 建立應用內人工智慧聊天機器人、代理程式和文字區域 ------------------------------------------------------------------------------------ ![副駕駛套件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzuxjfog2ldam3csrl62.png) 將 AI 功能整合到 React 中是很困難的,這就是 Copilot 的用武之地。一個簡單快速的解決方案,可將可投入生產的 Copilot 整合到任何產品中! 您可以使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。它們還提供內建(完全可自訂)Copilot 原生 UX 元件,例如`<CopilotKit />` 、 `<CopilotPopup />` 、 `<CopilotSidebar />` 、 `<CopilotTextarea />` 。 開始使用以下 npm 指令。 ``` npm i @copilotkit/react-core @copilotkit/react-ui ``` Copilot Portal 是 CopilotKit 提供的元件之一,CopilotKit 是一個應用程式內人工智慧聊天機器人,可查看目前應用狀態並在應用程式內採取操作。它透過插件與應用程式前端和後端以及第三方服務進行通訊。 這就是整合聊天機器人的方法。 `CopilotKit`必須包裝與 CopilotKit 互動的所有元件。建議您也開始使用`CopilotSidebar` (您可以稍後切換到不同的 UI 提供者)。 ``` "use client"; import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; export default function RootLayout({children}) { return ( <CopilotKit url="/path_to_copilotkit_endpoint/see_below"> <CopilotSidebar> {children} </CopilotSidebar> </CopilotKit> ); } ``` 您可以使用此[快速入門指南](https://docs.copilotkit.ai/getting-started/quickstart-backend)設定 Copilot 後端端點。 之後,您可以讓 Copilot 採取行動。您可以閱讀如何提供[外部上下文](https://docs.copilotkit.ai/getting-started/quickstart-chatbot#provide-context)。您可以使用`useMakeCopilotReadable`和`useMakeCopilotDocumentReadable`反應掛鉤來執行此操作。 ``` "use client"; import { useMakeCopilotActionable } from '@copilotkit/react-core'; // Let the copilot take action on behalf of the user. useMakeCopilotActionable( { name: "setEmployeesAsSelected", // no spaces allowed in the function name description: "Set the given employees as 'selected'", argumentAnnotations: [ { name: "employeeIds", type: "array", items: { type: "string" } description: "The IDs of employees to set as selected", required: true } ], implementation: async (employeeIds) => setEmployeesAsSelected(employeeIds), }, [] ); ``` 您可以閱讀[文件](https://docs.copilotkit.ai/getting-started/quickstart-textarea)並查看[演示影片](https://github.com/CopilotKit/CopilotKit?tab=readme-ov-file#demo)。 您可以輕鬆整合 Vercel AI SDK、OpenAI API、Langchain 和其他 LLM 供應商。您可以按照本[指南](https://docs.copilotkit.ai/getting-started/quickstart-chatbot)將聊天機器人整合到您的應用程式中。 基本概念是在幾分鐘內建立可用於基於 LLM 的應用程式的 AI 聊天機器人。 用例是巨大的,作為開發人員,我們絕對應該在下一個專案中嘗試使用 CopilotKit。 https://github.com/CopilotKit/CopilotKit Star CopilotKit ⭐️ --- 2. [xyflow](https://github.com/xyflow/xyflow) - 使用 React 建立基於節點的 UI。 -------------------------------------------------------------------- ![XY流](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yevpzvqpt3u6ahkqdrsl.png) XYFlow 是一個功能強大的開源程式庫,用於使用 React 或 Svelte 建立基於節點的 UI。它是一個 monorepo,提供[React Flow](https://reactflow.dev)和[Svelte Flow](https://svelteflow.dev) 。讓我們更多地了解可以使用 React flow 做什麼。 ![反應流](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mzezlna4v4bx75z3omr.png) 您可以觀看此影片,在 60 秒內了解 React Flow。 https://www.youtube.com/watch?v=aUBWE41a900 有些功能在專業模式下可用,但免費層中的功能足以形成一個非常互動的流程。 React 流程以 TypeScript 編寫並使用 Cypress 進行測試。 開始使用以下 npm 指令。 ``` npm install reactflow ``` 以下介紹如何建立兩個節點( `Hello`和`World` ,並透過邊連接。節點具有預先定義的初始位置以防止重疊,並且我們還應用樣式來確保有足夠的空間來渲染圖形。 ``` import ReactFlow, { Controls, Background } from 'reactflow'; import 'reactflow/dist/style.css'; const edges = [{ id: '1-2', source: '1', target: '2' }]; const nodes = [ { id: '1', data: { label: 'Hello' }, position: { x: 0, y: 0 }, type: 'input', }, { id: '2', data: { label: 'World' }, position: { x: 100, y: 100 }, }, ]; function Flow() { return ( <div style={{ height: '100%' }}> <ReactFlow nodes={nodes} edges={edges}> <Background /> <Controls /> </ReactFlow> </div> ); } export default Flow; ``` 這就是它的樣子。您還可以新增標籤、更改類型並使其具有互動性。 ![你好世界](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xzerdd3ng0vtnz5rbgau.png) 您可以在 React Flow 的 API 參考中查看[完整的選項清單](https://reactflow.dev/api-reference/react-flow)以及元件、鉤子和實用程式。 最好的部分是您還可以加入[自訂節點](https://reactflow.dev/learn/customization/custom-nodes)。在您的自訂節點中,您可以渲染您想要的一切。您可以定義多個來源和目標句柄並呈現表單輸入或圖表。您可以查看此[codesandbox](https://codesandbox.io/p/sandbox/pensive-field-z4kv3w?file=%2FApp.js&utm_medium=sandpack)作為範例。 您可以閱讀[文件](https://reactflow.dev/learn)並查看 Create React App、Next.js 和 Remix 的[範例 React Flow 應用程式](https://github.com/xyflow/react-flow-example-apps)。 React Flow 附帶了幾個額外的[插件](https://reactflow.dev/learn/concepts/plugin-components)元件,可以幫助您使用 Background、Minimap、Controls、Panel、NodeToolbar 和 NodeResizer 元件製作更高級的應用程式。 例如,您可能已經注意到許多網站的背景中有圓點,增強了美觀性。要實現此模式,您可以簡單地使用 React Flow 中的後台元件。 ``` import { Background } from 'reactflow'; <Background color="#ccc" variant={'dots'} /> // this will be under React Flow component. Just an example. ``` ![背景元件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/en2tl17ef31nydaycw18.png) 如果您正在尋找一篇快速文章,我建議您查看 Webkid 的[React Flow - A Library for Rendering Interactive Graphs](https://webkid.io/blog/react-flow-node-based-graph-library/) 。 React Flow 由 Webkid 開發和維護。 它在 GitHub 上有超過 19k 顆星,並且在`v11.10.4`上顯示它們正在不斷改進,npm 套件每週下載量超過 40 萬次。您可以輕鬆使用的最佳專案之一。 ![統計資料](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o99csz9epqmai3ixt859.png) https://github.com/xyflow/xyflow 星 xyflow ⭐️ --- 3. [Zod](https://github.com/colinhacks/zod) + [React Hook Form](https://github.com/react-hook-form) - 致命的驗證組合。 -------------------------------------------------------------------------------------------------------------- ![佐德](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s6zvmqr0lv93vsrhofs.png) 第一個問題是:為什麼我在同一個選項中包含 Zod 和 React Hook 表單?好吧,請閱讀它來找出答案。 Zod 的目標是透過最大限度地減少重複的類型聲明來對開發人員友好。使用 Zod,您聲明一次驗證器,Zod 將自動推斷靜態 TypeScript 類型。將更簡單的類型組合成複雜的資料結構很容易。 開始使用以下 npm 指令。 ``` npm install zod ``` 這是您在建立字串架構時自訂一些常見錯誤訊息的方法。 ``` const name = z.string({ required_error: "Name is required", invalid_type_error: "Name must be a string", }); ``` ``` // It does provide lots of options // validations z.string().min(5, { message: "Must be 5 or more characters long" }); z.string().max(5, { message: "Must be 5 or fewer characters long" }); z.string().length(5, { message: "Must be exactly 5 characters long" }); z.string().email({ message: "Invalid email address" }); z.string().url({ message: "Invalid url" }); z.string().emoji({ message: "Contains non-emoji characters" }); z.string().uuid({ message: "Invalid UUID" }); z.string().includes("tuna", { message: "Must include tuna" }); z.string().startsWith("https://", { message: "Must provide secure URL" }); z.string().endsWith(".com", { message: "Only .com domains allowed" }); z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); z.string().ip({ message: "Invalid IP address" }); ``` 請閱讀[文件](https://zod.dev/)以了解有關 Zod 的更多資訊。 它適用於 Node.js 和所有現代瀏覽器。 現在,第二部分來了。 有很多可用的表單整合。 ![形式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zz290xe2bpdsjvj6pzao.png) 雖然 Zod 可以驗證物件,但如果沒有自訂邏輯,它不會影響您的用戶端和後端。 React-hook-form 是用於客戶端驗證的優秀專案。例如,它可以顯示輸入錯誤。 ![反應鉤子形式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy3m7inekd685t4nt59m.png) 開始使用以下 npm 指令。 ``` npm install react-hook-form ``` 這就是如何使用`React Hook Form` 。 ``` import { useForm, SubmitHandler } from "react-hook-form" type Inputs = { example: string exampleRequired: string } export default function App() { const { register, handleSubmit, watch, formState: { errors }, } = useForm<Inputs>() const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data) console.log(watch("example")) // watch input value by passing the name of it return ( /* "handleSubmit" will validate your inputs before invoking "onSubmit" */ <form onSubmit={handleSubmit(onSubmit)}> {/* register your input into the hook by invoking the "register" function */} <input defaultValue="test" {...register("example")} /> {/* include validation with required or other standard HTML validation rules */} <input {...register("exampleRequired", { required: true })} /> {/* errors will return when field validation fails */} {errors.exampleRequired && <span>This field is required</span>} <input type="submit" /> </form> ) } ``` 您甚至可以隔離重新渲染,從而提高整體效能。 您可以閱讀[文件](https://react-hook-form.com/get-started)。 兩者結合起來就是一個很好的組合。嘗試一下! 我透過 Shadcn 發現了它,它使用它作為表單元件的預設值。我自己在幾個專案中使用過它,效果非常好。它提供了很大的靈活性,這確實很有幫助。 https://github.com/colinhacks/zod Star Zod ⭐️ https://github.com/react-hook-form Star React Hook Form ⭐️ --- 4. [React DND](https://github.com/react-dnd/react-dnd) - 用於 React 的拖放。 ---------------------------------------------------------------------- ![反應 dnd](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0ywjp9hk8l4ocq145yr.png) 我還沒有完全實現拖放功能,而且我經常發現自己對選擇哪個選項感到困惑。我遇到的另一個選擇是[interactjs.io](https://interactjs.io/) ,根據我讀過的文件,它似乎非常有用。由於他們提供了詳細的範例,這非常容易。 ![拖放](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x2h85gcto3r3kwuj0nix.png) 但我現在只介紹 React DND。 開始使用以下 npm 指令。 ``` npm install react-dnd react-dnd-html5-backend ``` 除非您正在編寫自訂後端,否則您可能想要使用 React DnD 隨附的 HTML5 後端。 這是安裝`react-dnd-html5-backend`方法。閱讀[文件](https://react-dnd.github.io/react-dnd/docs/backends/html5)。 這是起點。 ``` import { HTML5Backend } from 'react-dnd-html5-backend' import { DndProvider } from 'react-dnd' export default class YourApp { render() { return ( <DndProvider backend={HTML5Backend}> /* Your Drag-and-Drop Application */ </DndProvider> ) } } ``` 透過這種方式,您可以非常輕鬆地實現卡片的拖放操作。 ``` // Let's make <Card text='Write the docs' /> draggable! import React from 'react' import { useDrag } from 'react-dnd' import { ItemTypes } from './Constants' export default function Card({ isDragging, text }) { const [{ opacity }, dragRef] = useDrag( () => ({ type: ItemTypes.CARD, item: { text }, collect: (monitor) => ({ opacity: monitor.isDragging() ? 0.5 : 1 }) }), [] ) return ( <div ref={dragRef} style={{ opacity }}> {text} </div> ) } ``` 請注意,HTML5 後端不支援觸控事件。因此它不適用於平板電腦和行動裝置。您可以將`react-dnd-touch-backend`用於觸控裝置。閱讀[文件](https://react-dnd.github.io/react-dnd/docs/backends/touch)。 ``` import { TouchBackend } from 'react-dnd-touch-backend' import { DndProvider } from 'react-dnd' class YourApp { <DndProvider backend={TouchBackend} options={opts}> {/* Your application */} </DndProvider> } ``` 這個codesandbox規定了我們如何正確使用React DND。 https://codesandbox.io/embed/3y5nkyw381?view=Editor+%2B+Preview&module=%2Fsrc%2Findex.tsx&hidenavigation=1 你可以看看React DND的[例子](https://react-dnd.github.io/react-dnd/examples)。 它們甚至有一個乾淨的功能,您可以使用 Redux 檢查內部發生的情況。 您可以透過為提供者新增 debugModeprop 來啟用[Redux DevTools](https://github.com/reduxjs/redux-devtools) ,其值為 true。 ``` <DndProvider debugMode={true} backend={HTML5Backend}> ``` 它提供了多種元件選項,我需要親自測試一下。總的來說,這看起來相當不錯,特別是如果你剛開始的話。 React DND 已獲得`MIT`許可,並在 GitHub 上擁有超過 20k Stars,這使其具有令人難以置信的可信度。 https://github.com/react-dnd/react-dnd Star React DND ⭐️ --- 5. [Cypress](https://github.com/cypress-io/cypress) - 快速測試瀏覽器中執行的內容。 -------------------------------------------------------------------- ![柏](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ybhbgvetu8tky7xiepdz.png) 近年來已經證明了測試的重要性,而 Jest 和 Cypress 等選項使其變得異常簡單。 但我們只會介紹 Cypress,因為它本身就很方便。 只需一張圖片就能證明 Cypress 值得付出努力。 ![柏](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ey0v3unpnblie1o610iv.png) 開始使用以下 npm 指令。 ``` npm install cypress -D ``` 如果您在專案中沒有使用 Node 或套件管理器,或者您想快速試用 Cypress,您始終可以[直接從 CDN 下載 Cypress](https://download.cypress.io/desktop) 。 一旦安裝並打開它。您必須使用`.cy.js`建立一個規範檔案。 ![規格文件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/077r7oilgyuf5j0chryv.png) 現在,您可以編寫並測試您的應用程式(範例程式碼)。 ``` describe('My First Test', () => { it('Does not do much!', () => { expect(true).to.equal(true) }) }) ``` Cypress 提供了多種選項,例如`cy.visit()`或`cy.contains()` 。由於我沒有廣泛使用 Cypress,因此您需要在其[文件](https://docs.cypress.io/guides/end-to-end-testing/writing-your-first-end-to-end-test)中進一步探索它。 如果它看起來很可怕,那麼請前往這個[為初學者解釋 Cypress 的](https://www.youtube.com/watch?v=u8vMu7viCm8&pp=ygUQY3lwcmVzcyB0dXRvcmlhbA%3D%3D)freeCodeCamp 教程。 Freecodecamp 影片確實是金礦 :D Cypress 在 GitHub 上擁有超過 45,000 顆星,並且在目前的 v13 版本中,它正在不斷改進。 https://github.com/cypress-io/cypress 星柏 ⭐️ --- [6.Refine](https://github.com/refinedev/refine) - 面向企業的開源 Retool。 ----------------------------------------------------------------- ![精煉](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wsti2yfikrhc9nggov5.png) Refine 是一個元 React 框架,可以快速開發各種 Web 應用程式。 從內部工具到管理面板、B2B 應用程式和儀表板,它可作為建立任何類型的 CRUD 應用程式(例如 DevOps 儀表板、電子商務平台或 CRM 解決方案)的全面解決方案。 ![電子商務](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xry9381y4s36emgb9psr.png) 您可以在一分鐘內使用單一 CLI 命令進行設定。 它具有適用於 15 多個後端服務的連接器,包括 Hasura、Appwrite 等。 您可以查看可用的[整合清單](https://refine.dev/integrations/)。 ![整合](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7h9tbp4u3llh8ywgb8m8.png) 但最好的部分是,Refine `headless by design` ,從而提供無限的樣式和自訂選項。 由於該架構,您可以使用流行的 CSS 框架(如 TailwindCSS)或從頭開始建立樣式。 這是最好的部分,因為我們不希望最終受到與特定庫的兼容性的樣式限制,因為每個人都有自己的風格並使用不同的 UI。 開始使用以下 npm 指令。 ``` npm create refine-app@latest ``` 這就是使用 Refine 新增登入資訊的簡單方法。 ``` import { useLogin } from "@refinedev/core"; const { login } = useLogin(); ``` 使用 Refine 概述程式碼庫的結構。 ``` const App = () => ( <Refine dataProvider={dataProvider} resources={[ { name: "blog_posts", list: "/blog-posts", show: "/blog-posts/show/:id", create: "/blog-posts/create", edit: "/blog-posts/edit/:id", }, ]} > /* ... */ </Refine> ); ``` 您可以閱讀[文件](https://refine.dev/docs/)。 您可以看到一些使用 Refine 建立的範例應用程式: - [全功能管理面板](https://example.admin.refine.dev/) - [優化不同的用例場景](https://github.com/refinedev/refine/tree/master/examples)。 他們甚至提供模板,這就是為什麼這麼多用戶喜歡Refine。 你可以看到[模板](https://refine.dev/templates/)。 ![範本](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87vbx5tqyicb9gmgirka.png) 他們在 GitHub 上擁有大約 22k+ 顆星。 https://github.com/refinedev/refine 星際精煉 ⭐️ --- 7. [Tremor](https://github.com/tremorlabs/tremor) - React 元件來建立圖表和儀表板。 ---------------------------------------------------------------------- ![樣品元件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hq6ehdstz94ya5kfvwl4.png) Tremor 提供了 20 多個開源 React 元件,用於建立基於 Tailwind CSS 的圖表和儀表板,使資料視覺化再次變得簡單。 ![社群](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dkwu1t43p0zfsmeehqxl.png) 開始使用以下 npm 指令。 ``` npm i @tremor/react ``` 這就是您如何使用 Tremor 快速建立東西。 ``` import { Card, ProgressBar } from '@tremor/react'; export default function Example() { return ( <Card className="mx-auto max-w-md"> <h4 className="text-tremor-default text-tremor-content dark:text-dark-tremor-content"> Sales </h4> <p className="text-tremor-metric font-semibold text-tremor-content-strong dark:text-dark-tremor-content-strong"> $71,465 </p> <p className="mt-4 flex items-center justify-between text-tremor-default text-tremor-content dark:text-dark-tremor-content"> <span>32% of annual target</span> <span>$225,000</span> </p> <ProgressBar value={32} className="mt-2" /> </Card> ); } ``` 這就是基於此生成的內容。 ![輸出](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tvpu7r0rig522zeqae8.png) 您可以閱讀[文件](https://www.tremor.so/docs/getting-started/installation)。其間,他們在引擎蓋下使用混音圖標。 從我見過的各種元件來看,這是一個很好的起點。相信我! Tremor 還提供了一個[乾淨的 UI 工具包](https://www.figma.com/community/file/1233953507961010067)。多麼酷啊! ![使用者介面套件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3jf4cwk5ybsc89dhz696.png) Tremor 在 GitHub 上擁有超過 14k 顆星,並有超過 280 個版本,這意味著它正在不斷改進。 https://github.com/tremorlabs/tremor 星震 ⭐️ --- 8. [Watermelon DB](https://github.com/Nozbe/WatermelonDB) - 用於 React 和 React Native 的反應式和非同步資料庫。 ------------------------------------------------------------------------------------------------ ![西瓜資料庫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbofucs4kcaix7igjfch.png) 我不知道為什麼資料庫有這麼多選項;甚至很難全部數清。但如果我們使用 React,Watermelon DB 是一個不錯的選擇。即使在 4k+ 提交之後,它們仍然處於`v0.28`版本,這是一個相當大的問題。 Rocket.chat 使用 Watermelon DB,這給了他們巨大的可信度。 開始使用以下 npm 指令。 ``` npm install @nozbe/watermelondb ``` 您需要做的第一件事是建立模型和後續遷移(閱讀文件)。 ``` import { appSchema, tableSchema } from '@nozbe/watermelondb' export default appSchema({ version: 1, tables: [ // We'll add to tableSchemas here ] }) ``` 根據文件,使用 WatermelonDB 時,您正在處理模型和集合。然而,在 Watermelon 之下有一個底層資料庫(SQLite 或 LokiJS),它使用不同的語言:表格和欄位。這些一起稱為資料庫模式。 您可以閱讀有關[CRUD 操作的](https://watermelondb.dev/docs/CRUD)[文件](https://watermelondb.dev/docs/Installation)和更多內容。 https://github.com/Nozbe/WatermelonDB 明星 WatermelonDB ⭐️ --- 9. [Evergreen UI](https://github.com/segmentio/evergreen) - 按 Segment 劃分的 React UI 框架。 -------------------------------------------------------------------------------------- ![常青用戶介面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dkfdl3thy6cdukhxg92j.png) 沒有 UI 框架的清單幾乎是不可能的。有許多受歡迎的選項,例如 Material、Ant Design、Next UI 等等。 但我們正在報道 Evergreen,它本身就非常好。 開始使用以下 npm 指令。 ``` $ npm install evergreen-ui ``` [Evergreen Segment 網站](https://evergreen.segment.com/foundations)上顯示了任何使用者介面的基礎以及詳細的選項。 ![基礎](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/imir9z0siqqwh99p6lno.png) 它提供了很多元件,其中一些非常好,例如`Tag Input`或`File uploader` 。 ![標籤輸入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrsxzhzdemj49aeauc8j.png) ![文件上傳器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fckysg2iz6iz7c4st3as.png) 您可以看到 Evergreen UI 提供的所有[元件](https://evergreen.segment.com/components)。 https://github.com/segmentio/evergreen Star Evergreen UI ⭐️ --- 10. [React Spring](https://www.react-spring.dev/) - 流暢的動畫來提升 UI 和互動。 -------------------------------------------------------------------- ![反應彈簧](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ouigl2pr2rwbyj2whzli.png) ![流體動畫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eosf22k1notx3wa1pfpd.png) 如果您喜歡 React-Motion 但感覺過渡不流暢,那是因為它專門使用 React 渲染。 如果你喜歡 Popmotion,但感覺自己的能力受到限制,那是因為它完全跳過了 React 渲染。 `react-spring`提供了兩種選擇,試試看! 開始使用以下 npm 指令。 ``` npm i @react-spring/web ``` 這就是導入高階元件來包裝動畫的方法。 ``` import { animated } from '@react-spring/web' // use it. export default function MyComponent() { return ( <animated.div style={{ width: 80, height: 80, background: '#ff6d6d', borderRadius: 8, }} /> ) } ``` 由於以下程式碼和框,我決定嘗試 React Spring。令人驚訝的是,我們可以使用 React Spring 做很多事情。 https://codesandbox.io/embed/mdovb?view=Editor+%2B+Preview&module=%2Fsrc%2Findex.tsx&hidenavigation=1 您可以閱讀[文件](https://www.react-spring.dev/docs/getting-started)。 他們還提供了很多您可以學習的[範例](https://www.react-spring.dev/examples)。 ![例子](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/muzldxpw58tun2yyn18t.png) 它提供了大量的選項,例如`useScroll` ,它允許您建立滾動連結動畫。 例如,這個codesandbox告訴了`useScroll`的用法。 https://codesandbox.io/embed/b07dmz?view=Editor+%2B+Preview&module=%2Fsrc%2Findex.tsx&hidenavigation=1 React Spring 在 GitHub 上有大約 27k+ Stars。 https://github.com/pmndrs/react-spring Star React Spring ⭐️ --- 11. [React Tweet](https://github.com/vercel/react-tweet) - 將推文嵌入到你的 React 應用程式中。 -------------------------------------------------------------------------------- ![反應推文](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9t2ktcvb8p6eitul8y9b.png) `React Tweet`可讓您在使用 Next.js、Create React App、Vite 等時將推文嵌入到 React 應用程式中。 該函式庫不需要使用 Twitter API。推文可以靜態呈現,從而無需包含 iframe 和額外的客戶端 JavaScript。 它是 Vercel 的開源專案。 開始使用以下 npm 指令。 ``` npm i react-tweet ``` 為了顯示推文,我們需要從 Twitter 的 API 請求資料。透過此 API 進行速率限制具有挑戰性,但如果您僅依賴我們提供的 SWR 端點 ( `react-tweet.vercel.app/api/tweet/:id` ),這是可能的,因為伺服器的IP 位址向Twitter 發出了許多請求API。這也適用於 RSC,其中 API 端點不是必需的,但伺服器仍然從相同 IP 位址發送請求。 為了避免 API 限制,您可以使用 Redis 或 Vercel KV 等資料庫快取推文。例如,您可以使用 Vercel KV。 ``` import { Suspense } from 'react' import { TweetSkeleton, EmbeddedTweet, TweetNotFound } from 'react-tweet' import { fetchTweet, Tweet } from 'react-tweet/api' import { kv } from '@vercel/kv' async function getTweet( id: string, fetchOptions?: RequestInit ): Promise<Tweet | undefined> { try { const { data, tombstone, notFound } = await fetchTweet(id, fetchOptions) if (data) { await kv.set(`tweet:${id}`, data) return data } else if (tombstone || notFound) { // remove the tweet from the cache if it has been made private by the author (tombstone) // or if it no longer exists. await kv.del(`tweet:${id}`) } } catch (error) { console.error('fetching the tweet failed with:', error) } const cachedTweet = await kv.get<Tweet>(`tweet:${id}`) return cachedTweet ?? undefined } const TweetPage = async ({ id }: { id: string }) => { try { const tweet = await getTweet(id) return tweet ? <EmbeddedTweet tweet={tweet} /> : <TweetNotFound /> } catch (error) { console.error(error) return <TweetNotFound error={error} /> } } const Page = ({ params }: { params: { tweet: string } }) => ( <Suspense fallback={<TweetSkeleton />}> <TweetPage id={params.tweet} /> </Suspense> ) export default Page ``` 您可以直接使用它,方法非常簡單。 ``` <div className="dark"> <Tweet id="1629307668568633344" /> </div> ``` 如果您不喜歡使用 Twitter 主題,您也可以使用多個選項建立自己的[自訂主題](https://react-tweet.vercel.app/custom-theme)。 例如,您可以建立自己的推文元件,但沒有回覆按鈕,如下所示: ``` import type { Tweet } from 'react-tweet/api' import { type TwitterComponents, TweetContainer, TweetHeader, TweetInReplyTo, TweetBody, TweetMedia, TweetInfo, TweetActions, QuotedTweet, enrichTweet, } from 'react-tweet' type Props = { tweet: Tweet components?: TwitterComponents } export const MyTweet = ({ tweet: t, components }: Props) => { const tweet = enrichTweet(t) return ( <TweetContainer> <TweetHeader tweet={tweet} components={components} /> {tweet.in_reply_to_status_id_str && <TweetInReplyTo tweet={tweet} />} <TweetBody tweet={tweet} /> {tweet.mediaDetails?.length ? ( <TweetMedia tweet={tweet} components={components} /> ) : null} {tweet.quoted_tweet && <QuotedTweet tweet={tweet.quoted_tweet} />} <TweetInfo tweet={tweet} /> <TweetActions tweet={tweet} /> {/* We're not including the `TweetReplies` component that adds the reply button */} </TweetContainer> ) } ``` 您可以閱讀[文件](https://react-tweet.vercel.app/#installation)。 您可以查看[React Tweet 的演示,](https://react-tweet-next.vercel.app/light/1761133168772489698)以了解它如何在頁面上呈現。 它們已發布`v3.2`版本,這表明它們正在不斷改進,並且[每週下載量超過 46k+](https://www.npmjs.com/package/react-tweet) 。 https://github.com/vercel/react-tweet Star React 推文 ⭐️ --- 12. [React 360](https://github.com/facebookarchive/react-360) - 使用 React 建立令人驚嘆的 360 度和 VR 內容。 ---------------------------------------------------------------------------------------------- ![反應 360](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/92546vucm4rnnseew2fi.png) 儘管 Facebook 已將其存檔,但許多開發人員仍然發現它足夠有用,因此繼續使用。 React 360 是一個函式庫,它利用大量 React Native 功能來建立在 Web 瀏覽器中執行的虛擬實境應用程式。 它使用 Three.js 進行渲染,並作為 npm 套件提供。透過將 WebGL 和 WebVR 等現代 API 與 React 的聲明性功能結合,React 360 有助於簡化建立跨平台 VR 體驗的過程。 開始使用以下 npm 指令。 ``` npm install -g react-360-cli ``` 涉及的事情有很多,但您可以使用 VrButton 加入重要的互動功能到您的 React VR 應用程式。 ``` import { AppRegistry, StyleSheet, Text, View, VrButton } from 'react-360'; state = { count: 0 }; _incrementCount = () => { this.setState({ count: this.state.count + 1 }) } <View style={styles.panel}> <VrButton onClick={this._incrementCount} style={styles.greetingBox}> <Text style={styles.greeting}> {`You have visited Simmes ${this.state.count} times`} </Text> </VrButton> </View> ``` 除了許多令人驚奇的東西之外,您還可以加入聲音。請參閱[使用 React 360 的 React Resources](https://reactresources.com/topics/react-360)範例。 您也可以閱讀 Log Rocket 撰寫的關於[使用 React 360 建立 VR 應用](https://blog.logrocket.com/building-a-vr-app-with-react-360/)程式的部落格。 這個codesandbox代表了我們可以使用React 360做什麼的一個常見範例。 https://codesandbox.io/embed/2bye27?view=Editor+%2B+Preview&module=%2Fsrc%2Findex.js&hidenavigation=1 https://github.com/facebookarchive/react-360 Star React 360 ⭐️ --- 13. [React Advanced Cropper](https://github.com/advanced-cropper/react-advanced-cropper) - 建立適合您網站的裁剪器。 ------------------------------------------------------------------------------------------------------- ![反應先進的作物](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9b7o2lchxua4urkot79.png) ![反應先進的作物](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tc5328gj9v9yjbptu3nn.png) React Advanced Cropper 是一個高級庫,可讓您建立適合任何網站設計的裁剪器。這意味著您不僅可以更改裁剪器的外觀,還可以自訂其行為。 它們仍處於測試版本,這意味著 API 可能會在未來版本中發生變化。 簡單的用例是設計軟體和裁剪圖像表面以獲得進一步的見解。 他們有很多選擇,因此值得。 ![選項](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nt5br00qyymlllmjlowk.png) ![選項](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atvlbxjowv1isjoi3p6m.png) 開始使用以下 npm 指令。 ``` npm install --save react-advanced-cropper ``` 您可以這樣使用它。 ``` import React, { useState } from 'react'; import { CropperRef, Cropper } from 'react-advanced-cropper'; import 'react-advanced-cropper/dist/style.css' export const GettingStartedExample = () => { const [image, setImage] = useState( 'https://images.unsplash.com/photo-1599140849279-1014532882fe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1300&q=80', ); const onChange = (cropper: CropperRef) => { console.log(cropper.getCoordinates(), cropper.getCanvas()); }; return ( <Cropper src={image} onChange={onChange} className={'cropper'} /> ) }; ``` 您可以閱讀[文件](https://advanced-cropper.github.io/react-advanced-cropper/docs/intro),它們提供了[20 多個自訂選項](https://github.com/advanced-cropper/react-advanced-cropper?tab=readme-ov-file#cropper)。 他們主要提供三種類型的[裁剪器選項](https://advanced-cropper.github.io/react-advanced-cropper/docs/guides/cropper-types/):固定、經典和混合以及範例和程式碼。 您可以使用 React Advanced Cropper 製作一些令人興奮的東西來向世界展示:) https://github.com/advanced-cropper/react-advanced-cropper Star React 進階裁剪器 ⭐️ --- 14. [Mobx](https://github.com/mobxjs/mobx) - 簡單、可擴展的狀態管理。 --------------------------------------------------------- ![行動裝置](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/od2isnsvbr1y349cpcnb.png) MobX 是一個經過驗證的基於訊號的函式庫,可透過函數反應式程式設計簡化和擴展狀態管理。它提供了靈活性,使您能夠獨立於任何 UI 框架來管理應用程式狀態。 這種方法會產生解耦、可移植且易於測試的程式碼。 以下是使用 MobX 的任何應用程式中處理事件的方式。 ![事件架構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3k0uxde1tnj8y8xizo8c.png) 圖片來自文件 開始使用以下 npm 指令。 ``` npm install mobx-react --save // CDN is also available ``` 這就是它的樣子。 ``` import { observer } from "mobx-react" // ---- ES6 syntax ---- const TodoView = observer( class TodoView extends React.Component { render() { return <div>{this.props.todo.title}</div> } } ) // ---- ESNext syntax with decorator syntax enabled ---- @observer class TodoView extends React.Component { render() { return <div>{this.props.todo.title}</div> } } // ---- or just use function components: ---- const TodoView = observer(({ todo }) => <div>{todo.title}</div>) ``` 您可以使用 props、全域變數或使用 React Context 在觀察者中使用外部狀態。 您可以閱讀[有關 React Integration](https://mobx.js.org/react-integration.html)和[npm docs](https://www.npmjs.com/package/mobx-react#api-documentation)的文件。 您也可以閱讀[MobX 和 React 的 10 分鐘互動介紹](https://mobx.js.org/getting-started)。 MobX 在 GitHub 上擁有超過 27k 顆星,並在 GitHub 上被超過 140K 開發者使用。 https://github.com/mobxjs/mobx 明星 Mobx ⭐️ --- 15. [React Virtualized](https://github.com/bvaughn/react-virtualized) - 渲染大型清單和表格資料。 ------------------------------------------------------------------------------------ ![反應虛擬化](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znt47ig09aebglto0915.png) 開始使用以下 npm 指令。 ``` npm install react-virtualized --save ``` 以下是如何在網格中使用 ColumnSizer 元件。探索演示(文件)以詳細了解可用選項。 ``` import React from 'react'; import ReactDOM from 'react-dom'; import {ColumnSizer, Grid} from 'react-virtualized'; import 'react-virtualized/styles.css'; // only needs to be imported once // numColumns, numRows, someCalculatedHeight, and someCalculatedWidth determined here... // Render your list ReactDOM.render( <ColumnSizer columnMaxWidth={100} columnMinWidth={50} columnCount={numColumns} width={someCalculatedWidth}> {({adjustedWidth, getColumnWidth, registerChild}) => ( <Grid ref={registerChild} columnWidth={getColumnWidth} columnCount={numColumns} height={someCalculatedHeight} cellRenderer={someCellRenderer} rowHeight={50} rowCount={numRows} width={adjustedWidth} /> )} </ColumnSizer>, document.getElementById('example'), ); ``` 您可以閱讀[文件](https://github.com/bvaughn/react-virtualized/tree/master/docs#documentation)和[演示](https://bvaughn.github.io/react-virtualized/#/components/List)。 他們提供了 React-window 作為輕量級的替代方案,但這個在發布和明星方面更受歡迎,所以我介紹了這個選項。您可以閱讀哪個選項更適合您: [React-Window 與 React-Virtualized 有何不同?](https://github.com/bvaughn/react-window?tab=readme-ov-file#how-is-react-window-different-from-react-virtualized) 。 它被超過 85,000 名開發人員使用,並在 GitHub 上擁有超過 25,000 顆星。它還擁有令人印象深刻的[170 萬+ 每週下載量](https://www.npmjs.com/package/react-virtualized)。 https://github.com/bvaughn/react-virtualized Star React 虛擬化 ⭐️ --- 16.React [Google Analytics](https://github.com/react-ga/react-ga) - React Google Analytics 模組。 ---------------------------------------------------------------------------------------------- ![反應Google分析](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a6lh8m8zussnyn32togy.png) 這是一個 JavaScript 模組,可用於在使用 React 作為前端程式碼庫的網站或應用程式中包含 Google Analytics 追蹤程式碼。 該模組對我們如何在前端程式碼中進行追蹤有一定的看法。我們的 API 比核心 Google Analytics 庫稍微詳細一些,以使程式碼更易於閱讀。 開始使用以下 npm 指令。 ``` npm install react-ga --save ``` 您可以這樣使用它。 ``` import ReactGA from 'react-ga'; ReactGA.initialize('UA-000000-01'); ReactGA.pageview(window.location.pathname + window.location.search); <!-- The core React library --> <script src="https://unpkg.com/[email protected]/dist/react.min.js"></script> <!-- The ReactDOM Library --> <script src="https://unpkg.com/[email protected]/dist/react-dom.min.js"></script> <!-- ReactGA library --> <script src="/path/to/bower_components/react-ga/dist/react-ga.min.js"></script> <script> ReactGA.initialize('UA-000000-01', { debug: true }); </script> ``` 執行`npm install` `npm start`並前往`port 8000 on localhost`後,您可以閱讀[文件](https://github.com/react-ga/react-ga?tab=readme-ov-file#installation)並查看[演示](https://github.com/react-ga/react-ga/tree/master/demo)。 它每週的下載量超過 35 萬次,在 GitHub 上擁有超過 5,000 顆星(已存檔)。 https://github.com/react-ga/react-ga Star React Google Analytics ⭐️ --- 17.react [-i18next](https://github.com/i18next/react-i18next) - React 的國際化做得很好。 ------------------------------------------------------------------------------- ![反應-i18next](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xrxn9omsv79bzy9j9mr4.png) 無需更改 webpack 配置或加入額外的 babel 轉譯器。 開始使用以下 npm 指令。 ``` npm i react-i18next ``` 我們來比較一下程式碼結構。 > 在使用react-i18next之前。 ``` ... <div>Just simple content</div> <div> Hello <strong title="this is your name">{name}</strong>, you have {count} unread message(s). <Link to="/msgs">Go to messages</Link>. </div> ... ``` > 使用react-i18next後。 ``` ... <div>{t('simpleContent')}</div> <Trans i18nKey="userMessagesUnread" count={count}> Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>. </Trans> ... ``` 您可以閱讀[文件](https://react.i18next.com/)並前往[Codesandbox 的互動式遊樂場](https://codesandbox.io/s/1zxox032q)。 該工具已被超過 182,000 名開發人員使用,在 GitHub 上擁有超過 8,000 顆星。軟體包中令人印象深刻的 3400k+ 下載量進一步鞏固了它的可信度,使其成為您下一個 React 專案的絕佳選擇。 您也可以閱讀 Locize 關於[React Localization - Internationalize with i18next](https://locize.com/blog/react-i18next/)的部落格。 https://github.com/i18next/react-i18next 明星react-i18next ⭐️ --- 哇!如此長的有用專案清單。 我知道您有更多想法,分享它們,讓我們一起建造:D 現在就這些了! 在開展新專案時,開發人員經驗至關重要,這就是為什麼有些專案擁有龐大的社區,而有些則沒有。 React 社群非常龐大,所以成為這些社群的一部分,並使用這些開源專案將您的專案提升到一個新的水平。 祝你有美好的一天!直到下一次。 在 GitHub 上關注我。 https://github.com/Anmol-Baranwal 請關注 CopilotKit 以了解更多此類內容。 https://dev.to/copilotkit --- 原文出處:https://dev.to/copilotkit/libraries-you-should-know-if-you-build-with-react-1807

useReducer() 優於 useState() 的 3 個理由

這是什麼 ---- `useReducer()`是 React Hooks API 中的一個方法,與`useState`類似,但為您提供了更多控制權來管理狀態。它接受一個reducer函數和初始狀態作為參數,並傳回狀態和調度方法: ``` const [state, dispatch] = React.useReducer(reducerFn, initialState, initFn); ``` reducer(之所以這樣稱呼,是因為您將傳遞給陣列方法`Array.prototype.reduce(reducer, initialValue)`函數類型)是取自 Redux 的模式。如果您不熟悉 Redux,簡而言之,reducer 是一個純函數,它將先前的狀態和操作作為參數,並傳回下一個狀態。 ``` (prevState, action) => newState ``` 操作是描述發生的事情的一條訊息,並且根據該訊息,reducer 指定狀態應如何變更。動作透過`dispatch(action)`方法傳遞。 使用它的 3 個理由 ---------- 大多數時候,您只需使用`useState()`方法就可以了,該方法建立在`useReducer()`之上。但有些情況下`useReducer()`較可取。 ### 下一個狀態取決於上一個狀態 當狀態依賴前一個方法時,使用此方法總是更好。它會給你一個更可預測的狀態轉換。簡單的例子是: ``` function reducer(state, action) { switch (action.type) { case 'ADD': return { count: state.count + 1 }; case 'SUB': return { count: state.count - 1 }; default: return state; } } function Counter() { const [state, dispatch] = React.useReducer(reducer, { count: 0 }); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'ADD'})}>Add</button> <button onClick={() => dispatch({type: 'SUB'})}>Substract</button> </> ); } ``` ### 複雜狀態形狀 當狀態包含多個原始值時,例如巢狀物件或陣列。例如: ``` const [state, dispatch] = React.useReducer( fetchUsersReducer, { users: [ { name: 'John', subscribred: false }, { name: 'Jane', subscribred: true }, ], loading: false, error: false, }, ); ``` 管理這種本地狀態更容易,因為參數相互依賴,並且所有邏輯都可以封裝到一個reducer中。 ### 易於測試 reducer是純函數,這意味著它們沒有副作用,並且在給定相同參數的情況下必須返回相同的結果。測試它們更容易,因為它們不依賴 React。讓我們從計數器範例中取得一個reducer並使用模擬狀態對其進行測試: ``` test("increments the count by one", () => { const newState = reducer({ count: 0 }, { type: "ADD" }); expect(newState.count).toBe(1) }) ``` 結論 -- `useReducer()`是`useState()`的替代方法,它使您可以更好地控制狀態管理並使測試更容易。所有情況都可以使用`useState()`方法來完成,因此總而言之,使用您熟悉的方法,並且對您和同事來說更容易理解。 --- 原文出處:https://dev.to/spukas/3-reasons-to-usereducer-over-usestate-43ad

建立文字到 PowerPoint 應用程式(LangChain、Next.js 和 CopilotKit)

長話短說 ==== 在本文中,您將學習如何建立由 AI 驅動的 PowerPoint 應用程式,該應用程式可以搜尋網路以自動製作有關任何主題的簡報。 我們將介紹使用: - 用於應用程式框架的 Next.js 🖥️ - 法學碩士 OpenAI 🧠 - LangChain 和 Tavily 的網路搜尋人工智慧代理🤖 - 使用 CopilotKit 將 AI 整合到您的應用程式中 🪁 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztokmi3hdcthmxkj2gny.gif) --- CopilotKit:為您的應用程式建立人工智慧副駕駛 --------------------------- CopilotKit 是[開源人工智慧副駕駛平台。](https://github.com/CopilotKit/CopilotKit)我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。 建造: - ChatBot:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 💬 - CopilotTextArea:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能📝 - 聯合代理:應用程式內人工智慧代理,可以與您的應用程式和使用者互動🤖 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h69i50cxsyvcknlcs6q0.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} 現在回到文章。 (本文是我們三週前發表的一篇文章的進展,但您無需閱讀該文章即可理解這一點)。 --- **先決條件** -------- 在開始建立應用程式之前,讓我們先查看建置應用程式所需的依賴項或套件 `copilotkit/react-core` :CopilotKit 前端包,帶有 React hooks,用於向副駕駛提供應用程式狀態和操作(AI 功能) `copilotkit/react-ui` :聊天機器人側邊欄 UI 的 CopilotKit 前端包 `copilotkit/react-textarea` :CopilotKit 前端包,用於在演講者筆記中進行人工智慧輔助文字編輯。 `LangChainJS` :一個用於開發由語言模型支援的應用程式的框架。 `Tavily Search API` :幫助將法學碩士和人工智慧應用程式連接到可信賴的即時知識的 API。 安裝所有專案包和依賴項 ----------- 在安裝所有專案包和依賴項之前,我們首先在終端機上執行以下命令來建立 Nextjs 專案。 ``` npx create-next-app@latest ``` 然後系統會提示您選擇一些選項。請隨意標記它們,如下所示。 ![建立 Nextjs 專案](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n668ixcvu5lnrsm9jpmq.png) 之後,使用您選擇的文字編輯器開啟新建立的 Nextjs 專案。然後在命令列中執行以下命令來安裝所有專案包和依賴項。 ``` npm i @copilotkit/backend @copilotkit/shared @langchain/langgraph @copilotkit/react-core @copilotkit/react-ui @copilotkit/react-textarea @heroicons/react ``` **建立 PowerPoint 應用程式前端** ------------------------ 讓我們先建立一個名為`Slide.tsx`的檔案。該文件將包含顯示和編輯投影片內容的程式碼,包括其`title` 、 `body text` 、 `background image`和`spoken narration text` 。 要建立該文件,請前往`/[root]/src/app`並建立一個名為`components`的資料夾。在 Components 資料夾中,建立`Slide.tsx`檔案。 之後,在文件頂部加入以下程式碼。程式碼定義了兩個名為`SlideModel`和`SlideProps`的 TypeScript 介面。 ``` "use client"; // Define an interface for the model of a slide, specifying the expected structure of a slide object. export interface SlideModel { title: string; content: string; backgroundImageDescription: string; spokenNarration: string; } // Define an interface for the properties of a component or function that manages slides. export interface SlideProps { slide: SlideModel; partialUpdateSlide: (partialSlide: Partial<SlideModel>) => void; } ``` 接下來,在上面的程式碼下面加入以下程式碼。程式碼定義了一個名為`Slide`功能元件,它接受`SlideProps`類型的 props。 ``` // Define a functional component named Slide that accepts props of type SlideProps. export const Slide = (props: SlideProps) => { // Define a constant for the height of the area reserved for speaker notes. const heightOfSpeakerNotes = 150; // Construct a URL for the background image using the description from slide properties, dynamically fetching an image from Unsplash. const backgroundImage = 'url("https://source.unsplash.com/featured/?' + encodeURIComponent(props.slide.backgroundImageDescription) + '")'; // Return JSX for the slide component. return ( <> {/* Slide content container with dynamic height calculation to account for speaker notes area. */} <div className="w-full relative bg-slate-200" style={{ height: `calc(100vh - ${heightOfSpeakerNotes}px)`, // Calculate height to leave space for speaker notes. }} > {/* Container for the slide title with centered alignment and styling. */} <div className="h-1/5 flex items-center justify-center text-5xl text-white text-center z-10" > {/* Textarea for slide title input, allowing dynamic updates. */} <textarea className="text-2xl bg-transparent text-black p-4 text-center font-bold uppercase italic line-clamp-2 resize-none flex items-center" style={{ border: "none", outline: "none", }} value={props.slide.title} placeholder="Title" onChange={(e) => { props.partialUpdateSlide({ title: e.target.value }); }} /> </div> {/* Container for the slide content with background image. */} <div className="h-4/5 flex" style={{ backgroundImage, backgroundSize: "cover", backgroundPosition: "center", }} > {/* Textarea for slide content input, allowing dynamic updates and styled for readability. */} <textarea className="w-full text-3xl text-black font-medium p-10 resize-none bg-red mx-40 my-8 rounded-xl text-center" style={{ lineHeight: "1.5", }} value={props.slide.content} placeholder="Body" onChange={(e) => { props.partialUpdateSlide({ content: e.target.value }); }} /> </div> </div> {/* Textarea for entering spoken narration with specified height and styling for consistency. */} <textarea className=" w-9/12 h-full bg-transparent text-5xl p-10 resize-none bg-gray-500 pr-36" style={{ height: `${heightOfSpeakerNotes}px`, background: "none", border: "none", outline: "none", fontFamily: "inherit", fontSize: "inherit", lineHeight: "inherit", }} value={props.slide.spokenNarration} onChange={(e) => { props.partialUpdateSlide({ spokenNarration: e.target.value }); }} /> </> ); }; ``` 之後,我們現在會建立一個名為`Presentation.tsx`的檔案。 該文件將包含初始化和更新投影片狀態、渲染目前投影片以及根據目前狀態動態啟用或停用按鈕實現導覽和投影片管理操作的程式碼。 要建立該文件,請將另一個文件新增至元件資料夾中,並將其命名為`Presentation.tsx` ,然後使用下列程式碼在檔案頂部匯入`React hooks` 、 `icons` 、 `SlideModel`和`Slide`元件。 ``` "use client"; import { useCallback, useMemo, useState } from "react"; import { BackwardIcon, ForwardIcon, PlusIcon, SparklesIcon, TrashIcon } from "@heroicons/react/24/outline"; import { SlideModel, Slide } from "./Slide"; ``` 之後,在上面的程式碼下面加入以下程式碼。程式碼定義了一個`ActionButton`功能元件,它將呈現具有可自訂屬性的按鈕元素。 ``` export const ActionButton = ({ disabled, onClick, className, children, }: { disabled: boolean; onClick: () => void; className?: string; children: React.ReactNode; }) => { return ( <button disabled={disabled} className={`bg-blue-500 text-white font-bold py-2 px-4 rounded ${disabled ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"} ${className}`} onClick={onClick} > {children} </button> ); }; ``` 然後在上面的程式碼下面加入下面的程式碼。程式碼定義了一個名為「Presentation」的功能元件,用於初始化投影片的狀態並定義一個用於更新目前投影片的函數。 ``` // Define the Presentation component as a functional component. export const Presentation = () => { // Initialize state for slides with a default first slide and a state to track the current slide index. const [slides, setSlides] = useState<SlideModel[]>([ { title: `Welcome to our presentation!`, // Title of the first slide. content: 'This is the first slide.', // Content of the first slide. backgroundImageDescription: "hello", // Description for background image retrieval. spokenNarration: "This is the first slide. Welcome to our presentation!", // Spoken narration text for the first slide. }, ]); const [currentSlideIndex, setCurrentSlideIndex] = useState(0); // Current slide index, starting at 0. // Use useMemo to memoize the current slide object to avoid unnecessary recalculations. const currentSlide = useMemo(() => slides[currentSlideIndex], [slides, currentSlideIndex]); // Define a function to update the current slide. This function uses useCallback to memoize itself to prevent unnecessary re-creations. const updateCurrentSlide = useCallback( (partialSlide: Partial<SlideModel>) => { // Update the slides state by creating a new array with the updated current slide. setSlides((slides) => [ ...slides.slice(0, currentSlideIndex), // Copy all slides before the current one. { ...slides[currentSlideIndex], ...partialSlide }, // Merge the current slide with the updates. ...slides.slice(currentSlideIndex + 1), // Copy all slides after the current one. ]); }, [currentSlideIndex, setSlides] // Dependencies for useCallback. ); // The JSX structure for the Presentation component. return ( <div className="relative"> {/* Render the current slide by passing the currentSlide and updateCurrentSlide function as props. */} <Slide slide={currentSlide} partialUpdateSlide={updateCurrentSlide} /> {/* Container for action buttons located at the top-left corner of the screen. */} <div className="absolute top-0 left-0 mt-6 ml-4 z-30"> {/* Action button to add a new slide. Disabled state is hardcoded to true for demonstration. */} <ActionButton disabled={true} onClick={() => { // Define a new slide object. const newSlide: SlideModel = { title: "Title", content: "Body", backgroundImageDescription: "random", spokenNarration: "The speaker's notes for this slide.", }; // Update the slides array to include the new slide. setSlides((slides) => [ ...slides.slice(0, currentSlideIndex + 1), newSlide, ...slides.slice(currentSlideIndex + 1), ]); // Move to the new slide by updating the currentSlideIndex. setCurrentSlideIndex((i) => i + 1); }} className="rounded-r-none" > <PlusIcon className="h-6 w-6" /> {/* Icon for the button. */} </ActionButton> {/* Another action button, currently disabled and without functionality. */} <ActionButton disabled={true} onClick={async () => { }} // Placeholder async function. className="rounded-l-none ml-[1px]" > <SparklesIcon className="h-6 w-6" /> {/* Icon for the button. */} </ActionButton> </div> {/* Container for action buttons at the top-right corner for deleting slides, etc. */} <div className="absolute top-0 right-0 mt-6 mr-24"> <ActionButton disabled={slides.length === 1} // Disable button if there's only one slide. onClick={() => {}} // Placeholder function for the button action. className="ml-5 rounded-r-none" > <TrashIcon className="h-6 w-6" /> {/* Icon for the button. */} </ActionButton> </div> {/* Display current slide number and total slides at the bottom-right corner. */} <div className="absolute bottom-0 right-0 mb-20 mx-24 text-xl" style={{ textShadow: "1px 1px 0 #ddd, -1px -1px 0 #ddd, 1px -1px 0 #ddd, -1px 1px 0 #ddd", }} > Slide {currentSlideIndex + 1} of {slides.length} {/* Current slide and total slides. */} </div> {/* Container for navigation buttons (previous and next) at the bottom-right corner. */} <div className="absolute bottom-0 right-0 mb-6 mx-24"> {/* Button to navigate to the previous slide. */} <ActionButton className="rounded-r-none" disabled={ currentSlideIndex === 0 || true} // Example condition to disable button; 'true' is just for demonstration. onClick={() => { setCurrentSlideIndex((i) => i - 1); // Update currentSlideIndex to move to the previous slide. }} > <BackwardIcon className="h-6 w-6" /> {/* Icon for the button. */} </ActionButton> {/* Button to navigate to the next slide. */} <ActionButton className="mr-[1px] rounded-l-none" disabled={ true || currentSlideIndex + 1 === slides.length} // Example condition to disable button; 'true' is just for demonstration. onClick={async () => { setCurrentSlideIndex((i) => i + 1); // Update currentSlideIndex to move to the next slide. }} > <ForwardIcon className="h-6 w-6" /> {/* Icon for the button. */} </ActionButton> </div> </div> ); }; ``` 要在瀏覽器上呈現 PowerPoint 應用程式,請前往`/[root]/src/app/page.tsx`檔案並新增以下程式碼。 ``` "use client"; import "./style.css"; import { Presentation } from "./components/Presentation"; export default function AIPresentation() { return ( <Presentation /> ); } ``` 如果您想要在 Powerpoint 應用程式前端新增樣式,請在`/[root]/src/app`資料夾中建立名為`style.css`的檔案。 然後導航[到此 gist 文件](https://gist.github.com/TheGreatBonnie/e7c0b790a2e2af3e669810539ba54fed),複製 CSS 程式碼,並將其新增至 style.css 檔案。 最後,在命令列上執行命令`npm run dev` ,然後導航到 http://localhost:3000/。 現在您應該在瀏覽器上查看 PowerPoint 應用程式,如下所示。 ![PowerPoint應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqcjqqmo1b2oow6y4p76.png) **將 PowerPoint 應用程式與 CopilotKit 後端集成** -------------------------------------- 讓我們先在根目錄中建立一個名為`.env.local`的檔案。然後在保存 ChatGPT 和 Tavily Search API 金鑰的檔案中加入下面的環境變數。 ``` OPENAI_API_KEY="Your ChatGPT API key" TAVILY_API_KEY="Your Tavily Search API key" ``` 若要取得 ChatGPT API 金鑰,請導覽至 https://platform.openai.com/api-keys。 ![ChatGPT API 金鑰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1u65aswytyym0zpoh5wx.png) 若要取得 Tavilly Search API 金鑰,請導覽至 https://app.tavily.com/home ![泰維利搜尋 API 金鑰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ugx1oqifnk24l69jjkf.png) 之後,轉到`/[root]/src/app`並建立一個名為`api`的資料夾。在`api`資料夾中,建立一個名為`copilotkit`的資料夾。 在`copilotkit`資料夾中,建立一個名為`research.ts`的檔案。然後導航到[該 Research.ts gist 文件](https://gist.github.com/TheGreatBonnie/58dc21ebbeeb8cbb08df665db762738c),複製程式碼,並將其新增至**`research.ts`**檔案中 接下來,在`/[root]/src/app/api/copilotkit`資料夾中建立一個名為`route.ts`的檔案。該文件將包含設定後端功能來處理 POST 請求的程式碼。它有條件地包括對給定主題進行研究的“研究”操作。 現在在文件頂部導入以下模組。 ``` import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend"; // For backend functionality with CopilotKit. import { researchWithLangGraph } from "./research"; // Import a custom function for conducting research. import { AnnotatedFunction } from "@copilotkit/shared"; // For annotating functions with metadata. ``` 在上面的程式碼下面,定義一個執行時環境變數和一個註解的函數,以便使用下面的程式碼進行研究。 ``` // Define a runtime environment variable, indicating the environment where the code is expected to run. export const runtime = "edge"; // Define an annotated function for research. This object includes metadata and an implementation for the function. const researchAction: AnnotatedFunction<any> = { name: "research", // Function name. description: "Call this function to conduct research on a certain topic. Respect other notes about when to call this function", // Function description. argumentAnnotations: [ // Annotations for arguments that the function accepts. { name: "topic", // Argument name. type: "string", // Argument type. description: "The topic to research. 5 characters or longer.", // Argument description. required: true, // Indicates that the argument is required. }, ], implementation: async (topic) => { // The actual function implementation. console.log("Researching topic: ", topic); // Log the research topic. return await researchWithLangGraph(topic); // Call the research function and return its result. }, }; ``` 然後在上面的程式碼下加入下面的程式碼來定義處理POST請求的非同步函數。 ``` // Define an asynchronous function that handles POST requests. export async function POST(req: Request): Promise<Response> { const actions: AnnotatedFunction<any>[] = []; // Initialize an array to hold actions. // Check if a specific environment variable is set, indicating access to certain functionality. if (process.env["TAVILY_API_KEY"]) { actions.push(researchAction); // Add the research action to the actions array if the condition is true. } // Instantiate CopilotBackend with the actions defined above. const copilotKit = new CopilotBackend({ actions: actions, }); // Use the CopilotBackend instance to generate a response for the incoming request using an OpenAIAdapter. return copilotKit.response(req, new OpenAIAdapter()); } ``` **將 PowerPoint 應用程式與 CopilotKit 前端集成** -------------------------------------- 讓我們先導入`/[root]/src/app/components/Slide.tsx`檔案頂部的`useMakeCopilotActionable`掛鉤。 ``` import { useMakeCopilotActionable } from "@copilotkit/react-core"; ``` 在 Slide 函數中,新增以下程式碼,該程式碼使用`useMakeCopilotActionable`掛鉤來設定一個名為`updateSlide`的操作,該操作具有特定參數以及根據提供的值更新投影片的實作。 ``` useMakeCopilotActionable({ // Defines the action name. This is a unique identifier for the action within the application. name: "updateSlide", // Describes what the action does. In this case, it updates the current slide. description: "Update the current slide.", // Details the arguments that the action accepts. Each argument has a name, type, description, and a flag indicating if it's required. argumentAnnotations: [ { name: "title", // The argument name. type: "string", // The data type of the argument. description: "The title of the slide. Should be a few words long.", // Description of the argument. required: true, // Indicates that this argument must be provided for the action to execute. }, { name: "content", type: "string", description: "The content of the slide. Should generally consists of a few bullet points.", required: true, }, { name: "backgroundImageDescription", type: "string", description: "What to display in the background of the slide. For example, 'dog', 'house', etc.", required: true, }, { name: "spokenNarration", type: "string", description: "The spoken narration for the slide. This is what the user will hear when the slide is shown.", required: true, }, ], // The implementation of the action. This is a function that will be called when the action is executed. implementation: async (title, content, backgroundImageDescription, spokenNarration) => { // Calls a function passed in through props to partially update the slide with new values for the specified properties. props.partialUpdateSlide({ title, content, backgroundImageDescription, spokenNarration, }); }, }, [props.partialUpdateSlide]); // Dependencies array for the custom hook or function. This ensures that the action is re-initialized only when `props.partialUpdateSlide` changes. ``` 之後,請前往`/[root]/src/app/components/Presentation.tsx`檔案並使用下面的程式碼匯入頂部的 CopilotKit 前端套件。 ``` import { useCopilotContext } from "@copilotkit/react-core"; import { CopilotTask } from "@copilotkit/react-core"; import { useMakeCopilotActionable, useMakeCopilotReadable } from "@copilotkit/react-core"; ``` 在演示函數中,加入以下程式碼,該程式碼使用`useMakeCopilotReadable`掛鉤加入`Slides`和`currentSlide`幻燈片陣列作為應用程式內聊天機器人的上下文。掛鉤使副駕駛可以讀取簡報中的整個幻燈片集合以及當前幻燈片的資料。 ``` useMakeCopilotReadable("These are all the slides: " + JSON.stringify(slides)); useMakeCopilotReadable( "This is the current slide: " + JSON.stringify(currentSlide) ); ``` 在`useMakeCopilotReadable`掛鉤下方,新增以下程式碼,該程式碼使用`useCopilotActionable`掛鉤來設定名為`appendSlide`的操作,其中包含說明和加入多張幻燈片的實作函數。 ``` useMakeCopilotActionable( { // Defines the action's metadata. name: "appendSlide", // Action identifier. description: "Add a slide after all the existing slides. Call this function multiple times to add multiple slides.", // Specifies the arguments that the action takes, including their types, descriptions, and if they are required. argumentAnnotations: [ { name: "title", // The title of the new slide. type: "string", description: "The title of the slide. Should be a few words long.", required: true, }, { name: "content", // The main content or body of the new slide. type: "string", description: "The content of the slide. Should generally consist of a few bullet points.", required: true, }, { name: "backgroundImageDescription", // Description for fetching or generating the background image of the new slide. type: "string", description: "What to display in the background of the slide. For example, 'dog', 'house', etc.", required: true, }, { name: "spokenNarration", // Narration text that will be read aloud during the presentation of the slide. type: "string", description: "The text to read while presenting the slide. Should be distinct from the slide's content, and can include additional context, references, etc. Will be read aloud as-is. Should be a few sentences long, clear, and smooth to read.", required: true, }, ], // The function to execute when the action is triggered. It creates a new slide with the provided details and appends it to the existing slides array. implementation: async (title, content, backgroundImageDescription, spokenNarration) => { const newSlide: SlideModel = { // Constructs the new slide object. title, content, backgroundImageDescription, spokenNarration, }; // Updates the slides state by appending the new slide to the end of the current slides array. setSlides((slides) => [...slides, newSlide]); }, }, [setSlides] // Dependency array for the hook. This action is dependent on the `setSlides` function, ensuring it reinitializes if `setSlides` changes. ); ``` 在上面的程式碼下方,定義一個名為`context`的變數,該變數使用名為`useCopilotContext`的自訂掛鉤從 copilot 上下文中檢索當前上下文。 ``` const context = useCopilotContext(); ``` 之後,定義一個名為`generateSlideTask`的函數,它包含一個名為`CopilotTask`的類別。 `CopilotTask`類別定義用於產生與簡報的整體主題相關的新投影片的指令 ``` const generateSlideTask = new CopilotTask({ instructions: "Make the next slide related to the overall topic of the presentation. It will be inserted after the current slide. Do NOT carry any research", }); ``` 然後在上面的程式碼下面初始化一個名為`generateSlideTaskRunning`的狀態變數,預設值為false。 ``` const [generateSlideTaskRunning, **setGenerateSlideTaskRunning**] = useState(false); ``` 之後,使用下面的程式碼更新簡報元件中的操作按鈕,以透過新增、刪除和導覽投影片來新增動態互動。 ``` // The JSX structure for the Presentation component. return ( <div className="relative"> {/* Renders the current slide using a Slide component with props for the slide data and a method to update it. */} <Slide slide={currentSlide} partialUpdateSlide={updateCurrentSlide} /> {/* Container for action buttons positioned at the top left corner of the relative parent */} <div className="absolute top-0 left-0 mt-6 ml-4 z-30"> {/* ActionButton to add a new slide. It is disabled when a generateSlideTask is running to prevent concurrent modifications. */} <ActionButton disabled={generateSlideTaskRunning} onClick={() => { const newSlide: SlideModel = { title: "Title", content: "Body", backgroundImageDescription: "random", spokenNarration: "The speaker's notes for this slide.", }; // Inserts the new slide immediately after the current slide and updates the slide index to point to the new slide. setSlides((slides) => [ ...slides.slice(0, currentSlideIndex + 1), newSlide, ...slides.slice(currentSlideIndex + 1), ]); setCurrentSlideIndex((i) => i + 1); }} className="rounded-r-none" > <PlusIcon className="h-6 w-6" /> </ActionButton> {/* ActionButton to generate a new slide based on the current context, also disabled during task running. */} <ActionButton disabled={generateSlideTaskRunning} onClick={async () => { setGenerateSlideTaskRunning(true); // Indicates the task is starting. await generateSlideTask.run(context); // Executes the task with the current context. setGenerateSlideTaskRunning(false); // Resets the flag when the task is complete. }} className="rounded-l-none ml-[1px]" > <SparklesIcon className="h-6 w-6" /> </ActionButton> </div> {/* Container for action buttons at the top right, including deleting the current slide and potentially other actions. */} <div className="absolute top-0 right-0 mt-6 mr-24"> {/* ActionButton for deleting the current slide, disabled if a task is running or only one slide remains. */} <ActionButton disabled={generateSlideTaskRunning || slides.length === 1} onClick={() => { console.log("delete slide"); // Removes the current slide and resets the index to the beginning as a simple handling strategy. setSlides((slides) => [ ...slides.slice(0, currentSlideIndex), ...slides.slice(currentSlideIndex + 1), ]); setCurrentSlideIndex((i) => 0); }} className="ml-5 rounded-r-none" > <TrashIcon className="h-6 w-6" /> </ActionButton> </div> {/* Display showing the current slide index and the total number of slides. */} <div className="absolute bottom-0 right-0 mb-20 mx-24 text-xl" style={{ textShadow: "1px 1px 0 #ddd, -1px -1px 0 #ddd, 1px -1px 0 #ddd, -1px 1px 0 #ddd", }} > Slide {currentSlideIndex + 1} of {slides.length} </div> {/* Navigation buttons to move between slides, disabled based on the slide index or if a task is running. */} <div className="absolute bottom-0 right-0 mb-6 mx-24"> {/* Button to move to the previous slide, disabled if on the first slide or a task is running. */} <ActionButton className="rounded-r-none" disabled={generateSlideTaskRunning || currentSlideIndex === 0} onClick={() => { setCurrentSlideIndex((i) => i - 1); }} > <BackwardIcon className="h-6 w-6" /> </ActionButton> {/* Button to move to the next slide, disabled if on the last slide or a task is running. */} <ActionButton className="mr-[1px] rounded-l-none" disabled={generateSlideTaskRunning || currentSlideIndex + 1 === slides.length} onClick={async () => { setCurrentSlideIndex((i) => i + 1); }} > <ForwardIcon className="h-6 w-6" /> </ActionButton> </div> </div> ); ``` 現在讓我們轉到`/[root]/src/app/page.tsx`文件,使用下面的程式碼匯入 CopilotKit 前端包和文件頂部的樣式。 ``` import { CopilotKit, } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; import "@copilotkit/react-textarea/styles.css"; ``` 然後使用`CopilotKit`和`CopilotSidebar`來包裝Presentation元件,如下所示。 ``` export default function AIPresentation() { return ( <CopilotKit url="/api/copilotkit/"> <CopilotSidebar instructions="Help the user create and edit a powerpoint-style presentation. IMPORTANT NOTE: SOMETIMES you may want to research a topic, before taking further action. BUT FIRST ASK THE USER if they would like you to research it. If they answer 'no', do your best WITHOUT researching the topic first." defaultOpen={true} labels={{ title: "Presentation Copilot", initial: "Hi you! 👋 I can help you create a presentation on any topic.", }} clickOutsideToClose={false} > <Presentation /> </CopilotSidebar> </CopilotKit> ); } ``` 之後,執行開發伺服器並導航到 http://localhost:3000/。您應該會看到應用程式內聊天機器人已整合到 PowerPoint Web 應用中。 ![應用程式內聊天機器人](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rb2g54qslrrfxyx2pbcy.png) 最後,給右側的聊天機器人一個提示,例如“在 JavaScript 上建立 PowerPoint 簡報”,聊天機器人將開始產生回應,完成後,使用底部的前進按鈕瀏覽產生的幻燈片。 注意:如果聊天機器人沒有立即產生投影片,請根據其回應給予適當的後續提示。 ![PowerPoint簡報](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v6dl4av0asoeokzopwra.png) 結論 -- 總而言之,您可以使用 CopilotKit 建立應用內 AI 聊天機器人,該機器人可以查看當前應用程式狀態並在應用程式內執行操作。 AI 聊天機器人可以與您的應用程式前端、後端和第三方服務對話。 完整的原始碼:https://github.com/TheGreatBonnie/aipoweredpowerpointapp --- 原文出處:https://dev.to/copilotkit/how-to-build-an-ai-powered-powerpoint-generator-langchain-copilotkit-openai-nextjs-4c76

在 React 中編寫乾淨、可重複使用的元件(最佳實踐)

🔑 關鍵概念 ------ 什麼是可重複使用的 React 元件?您可以將它們視為建置塊。 它們是獨立的程式碼片段,可以在整個網站中重複使用,以節省您的時間和精力。 它們可以是從簡單按鈕到複雜表單的任何內容。 **為什麼要使用可重複使用的元件?** 隨著您的網站的發展,您可以透過組合現有元件輕鬆加入新功能。這使您的程式碼更具可擴展性和適應性。 您可以**在未來的專案中使用可重複使用的程式碼,**而無需從頭開始重新編寫。 --- 🧩 如何寫出乾淨、可重複使用的 React 元件 ------------------------ 在編寫乾淨的可重複使用 React 元件時,需要記住以下*兩個最重要的*事情: ![React 可重複使用元件的圖像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yuprsf67nntkgexmo92w.jpg) **1. 🩼避免副作用。**不要將與外部資料互動的邏輯(例如進行 API 呼叫)直接放入可重複使用元件中。相反,將此邏輯作為`props`傳遞給元件。 例如,如果一個按鈕不僅僅是看起來漂亮,例如從網路獲取資料,**那麼它可能無法重複使用。** 在您掌握透過最佳實踐傳遞 props 的概念之前,我不會向您展示這方面的範例。繼續閱讀。 這是一個可重複使用的按鈕元件。但它缺乏最佳實踐。我將在範例部分向您展示原因。 ``` // This is a reusable button component (bad practice!) const Button = () => { return ( <button> Click Me </button> ); } ``` ‍ **2. 🗃️使用 props。** Props 是傳遞給元件以自訂其行為和外觀的參數。這允許您將相同的元件用於不同的目的。 ``` // This is a button component that can change its color const Button = ({ color }) => { return ( <button style={{ backgroundColor: color }}> Click Here </button> ); } ``` 這仍然是一個不好的做法,因為您有一個名為「點擊這裡」的固定標籤。如果您想更改按鈕上的文本,例如“註冊”,那麼您必須返回按鈕元件並進行更改。 這意味著每次我們想要使用不同的文字時,我們都必須返回並編輯程式碼。換句話說,它不再可重複使用。 **💪 挑戰:**那麼解決方案是什麼? 你已經有了答案。但如果您不這樣做,我將在範例部分向您展示。 **🌴提示:**考慮一下您可能希望如何在不同情況下使用該元件,並將其設計得靈活且適應性強。 🍃可重複使用 React 元件的範例 ------------------ 以下是可重複使用 React 元件的一些常見範例,以及一些幫助您入門的程式碼範例: **1. 按鈕:**具有不同樣式和功能的基本按鈕。 ``` // Button component import React from "react"; const Button = ({ color, label, onClick }) => { return ( <button className={`padding-2 shadow-none hover:shadow background-light-${color} hover:background-dark-${color}`} onClick={onClick} > {label} </button> ); }; export default Button; // Using the Button component <Button color="blue" label="Click Here" onClick={() => console.log("Button clicked!")} /> ``` 正如你所看到的,我沒有在按鈕元件中寫“Click Here”。我想讓我的按鈕可重複使用,因此它不知道有關自訂樣式或文字的任何資訊。 因此,我將它們作為 props(例如,顏色、標籤、onClick)傳遞,以便將來更改它們,而無需觸摸原始按鈕元件。希望這能說清楚。 > **🪜解決方案:**您需要將每個功能作為可重複使用元件中的`props`傳遞 - 就是這樣。 **2. 導覽列:**在整個網站上提供一致導覽的導覽列。 ``` // Navbar component import React from "react"; const Navbar = ({ isLoggedIn }) => { return ( <div className="navbar"> <div className="navbar-container"> <div className="navbar-logo"> <img src={logo} alt="logo" /> </div> <div className="navbar-links"> <a href="/">Home</a> <a href="/about">About</a> <a href="/contact">Contact</a> {isLoggedIn ? ( <a href="/profile">Profile</a> ) : ( <a href="/login">Login</a> )} </div> </div> </div> ); }; export default Navbar; // Using the Navbar component <Navbar isLoggedIn={true} /> ``` 如您所見,我通過了`<Navbar isLoggedIn={true} />` 此行利用`Navbar`元件並傳遞值為 true 的`isLoggedIn`屬性,表示使用者已登入。這將顯示「個人資料」連結並隱藏「登入」連結。 與按鈕元件類似,導覽列元件是可重複使用的,並接受 props 來自訂其行為。完美的! **3. 為什麼在按鈕元件中呼叫 API 是不好的做法** 現在,您已經了解了 React 中可重複使用元件的所有內容。 讓我們透過解決一個複雜的問題來更深入地研究。 考慮這樣的場景,您有一個執行 API 呼叫的按鈕。按鈕元件的程式碼可以如下: ``` import React from "react"; import doAPICall from "../api" const SaveButton = () => { return ( <button onClick={() => { doAPICall(); }} > Save </button> ); } export default SaveButton ``` 很明顯,您不能在多個地方重複使用上述按鈕,因為該按鈕元件內部包含副作用( `doAPICall()` )。 為了使該元件可重複使用,首先,您必須提取副作用並將其作為 prop 傳遞給按鈕元件,如下所示: ``` const App = () => { function doAPICall() { // Does an API call to save the current state of the app. } return ( <div> <SaveButton onClick={doAPICall}/> </div> ) } ``` 按鈕元件將如下所示: ``` const SaveButton = ({ onClick }) => { return ( <button onClick={onClick} > Save </button> ); } ``` 正如您所看到的,現在可以在您想要透過點擊按鈕儲存資料的所有位置重複使用上面的按鈕。該按鈕現在可以在多個地方像這樣使用: ``` const App = () => { function saveUser() { // Does an API call to save the user. } function saveProject() { // Does an API call to save the project. } return ( <div> <SaveButton onClick={saveUser}/> <SaveButton onClick={saveProject}/> </div> ) } ``` 您也可以透過使用 prop 來控制標籤,使按鈕元件更具可重複使用性,如下所示: ``` const App = () => { function saveUser() { // Does an API call to save the user. } function saveProject() { // Does an API call to save the project. } return ( <div> <SaveButton onClick={saveUser} label="Save user" /> <SaveButton onClick={saveProject} label="Save project" /> </div> ) } ``` 按鈕元件將如下所示: ``` const SaveButton = ({ onClick, label }) => { return ( <button onClick={onClick} > {label} </button> ); } ``` **🫧 推薦:🤖🦾 [Figma AI:](https://psxid.figma.com/8g4g75niz3sg-7v0ro9)** 如果您需要快速設計簡報或網站,Figma AI 可以提供現成的範本。並且它可以與[Figma](https://psxid.figma.com/pq1612tcamkx)順利配合。只要告訴它你想要什麼,畫一點,然後繁榮——你的想法就變成現實了! ![Figma AI 影像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7cj5cxhykwmfnmve77co.png) 您可以嘗試[Figma AI](https://psxid.figma.com/8g4g75niz3sg-7v0ro9) ,目前它對所有人免費。 --- 👏結論 --- 恭喜!您已經成功學習如何使用最佳實踐建立乾淨的可重複使用 React 元件。 請記住,**可重複使用元件是健壯的 React 開發的建置塊**。透過練習可重複使用元件,您可以建立更乾淨、更有效率、更易於維護的 React 應用程式。 您練習得越多,您就越能更好地辨識在專案中使用它們的機會! 如果您喜歡這篇文章,您可能也會喜歡我的[𝕏](https://twitter.com/shahancd)帳戶,以了解更多有關前端開發的課程。 **閱讀更多:**[前端開發的未來](https://dev.to/codewithshahan/the-future-of-frontend-development-1amd) --- 原文出處:https://dev.to/codewithshahan/writing-clean-reusable-components-in-react-best-practices-2gka