🔍 搜尋結果:class

🔍 搜尋結果:class

僅使用 HTML 和 CSS 建立側邊欄選單

如果您是 Web 開發新手,您可能在不同網站上看過[側邊欄](https://www.codingnepalweb.com/category/sidebar-menu/)。您是否想知道它們是如何僅使用 HTML 和 CSS 建立的?僅使用 HTML 和 CSS 製作側邊欄是學習網頁設計基礎知識和獲得實務經驗的好方法。 在這篇文章中,我將指導您僅使用[HTML](https://www.codingnepalweb.com/?s=html)和[CSS](https://www.codingnepalweb.com/category/html-and-css/)建立響應式側邊欄。最初,側邊欄將被隱藏,僅顯示每個連結的圖示。但是,將滑鼠懸停在側邊欄上將平滑展開以顯示與每個圖示關聯的連結。 為了建立這個側邊欄,我們將使用基本的 HTML 語意元素,例如`<aside>` 、 `<ul>` 、 `<li>`和`<a>`以及常見的 CSS 屬性來設定其樣式。這是一個簡單的專案,因此您應該毫無困難地遵循這些步驟或理解程式碼。 HTML 和 CSS 中的響應式側邊欄選單影片教學 ------------------------- https://www.youtube.com/watch?v=VU74s-XAn7M 如果您喜歡從影片教學中學習,上面的 YouTube 影片是一個很好的資源。在本影片中,我解釋了每一行程式碼並提供了資訊豐富的註釋,以使建立 HTML 側邊欄的過程易於遵循,尤其是對於初學者而言。 但是,如果您喜歡閱讀部落格文章或需要此專案的逐步指南,您可以繼續閱讀這篇文章。 在 HTML 和 CSS 中建立響應式側邊欄的步驟 ------------------------- 若要僅使用 HTML 和 CSS 建立響應式側邊欄,請按照以下簡單的逐步說明進行操作: - 首先,建立一個具有任何您喜歡的名稱的資料夾。然後,在其中建立必要的文件。 - 建立一個名為`index.html`的檔案作為主檔案。 - 為 CSS 程式碼建立一個名為`style.css`檔案。 - 最後,下載[Images](https://www.codingnepalweb.com/custom-projects/simple-sidebar-menu-html-css-only-images.zip)資料夾並將其放置在您的專案目錄中。該資料夾包含該側邊欄專案所需的所有圖像。 首先,將以下 HTML 程式碼新增至您的`index.html`檔案: 此程式碼包含具有不同語意標籤(如`<aside>` 、 `<ul>` 、 `<li>`和`<a>`的基本HTML 標記,用於建立我們的側邊欄佈局。 ``` <!DOCTYPE html> <!-- Coding By CodingNepal - www.codingnepalweb.com --> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Sidebar Menu HTML and CSS | CodingNepal</title> <!-- Linking Google Font Link For Icons --> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200" /> <link rel="stylesheet" href="style.css" /> </head> <body> <aside class="sidebar"> <div class="sidebar-header"> <img src="images/logo.png" alt="logo" /> <h2>CodingLab</h2> </div> <ul class="sidebar-links"> <h4> <span>Main Menu</span> <div class="menu-separator"></div> </h4> <li> <a href="#"> <span class="material-symbols-outlined"> dashboard </span >Dashboard</a > </li> <li> <a href="#" ><span class="material-symbols-outlined"> overview </span >Overview</a > </li> <li> <a href="#" ><span class="material-symbols-outlined"> monitoring </span >Analytic</a > </li> <h4> <span>General</span> <div class="menu-separator"></div> </h4> <li> <a href="#" ><span class="material-symbols-outlined"> folder </span>Projects</a > </li> <li> <a href="#" ><span class="material-symbols-outlined"> groups </span>Groups</a > </li> <li> <a href="#" ><span class="material-symbols-outlined"> move_up </span>Transfer</a > </li> <li> <a href="#" ><span class="material-symbols-outlined"> flag </span>All Reports</a > </li> <li> <a href="#" ><span class="material-symbols-outlined"> notifications_active </span >Notifications</a > </li> <h4> <span>Account</span> <div class="menu-separator"></div> </h4> <li> <a href="#" ><span class="material-symbols-outlined"> account_circle </span >Profile</a > </li> <li> <a href="#" ><span class="material-symbols-outlined"> settings </span >Settings</a > </li> <li> <a href="#" ><span class="material-symbols-outlined"> logout </span>Logout</a > </li> </ul> <div class="user-account"> <div class="user-profile"> <img src="images/profile-img.jpg" alt="Profile Image" /> <div class="user-detail"> <h3>Eva Murphy</h3> <span>Web Developer</span> </div> </div> </div> </aside> </body> </html> ``` 接下來,將以下 CSS 程式碼新增至您的`style.css`檔案中,以使您的側邊欄實用且具有視覺吸引力。請隨意嘗試不同的 CSS 屬性,例如顏色、字體、背景等,以使您的側邊欄更具吸引力。 ``` /* Importing Google font - Poppins */ @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"); * { margin: 0; padding: 0; box-sizing: border-box; font-family: "Poppins", sans-serif; } body { min-height: 100vh; background: #F0F4FF; } .sidebar { position: fixed; top: 0; left: 0; height: 100%; width: 85px; display: flex; overflow-x: hidden; flex-direction: column; background: #161a2d; padding: 25px 20px; transition: all 0.4s ease; } .sidebar:hover { width: 260px; } .sidebar .sidebar-header { display: flex; align-items: center; } .sidebar .sidebar-header img { width: 42px; border-radius: 50%; } .sidebar .sidebar-header h2 { color: #fff; font-size: 1.25rem; font-weight: 600; white-space: nowrap; margin-left: 23px; } .sidebar-links h4 { color: #fff; font-weight: 500; white-space: nowrap; margin: 10px 0; position: relative; } .sidebar-links h4 span { opacity: 0; } .sidebar:hover .sidebar-links h4 span { opacity: 1; } .sidebar-links .menu-separator { position: absolute; left: 0; top: 50%; width: 100%; height: 1px; transform: scaleX(1); transform: translateY(-50%); background: #4f52ba; transform-origin: right; transition-delay: 0.2s; } .sidebar:hover .sidebar-links .menu-separator { transition-delay: 0s; transform: scaleX(0); } .sidebar-links { list-style: none; margin-top: 20px; height: 80%; overflow-y: auto; scrollbar-width: none; } .sidebar-links::-webkit-scrollbar { display: none; } .sidebar-links li a { display: flex; align-items: center; gap: 0 20px; color: #fff; font-weight: 500; white-space: nowrap; padding: 15px 10px; text-decoration: none; transition: 0.2s ease; } .sidebar-links li a:hover { color: #161a2d; background: #fff; border-radius: 4px; } .user-account { margin-top: auto; padding: 12px 10px; margin-left: -10px; } .user-profile { display: flex; align-items: center; color: #161a2d; } .user-profile img { width: 42px; border-radius: 50%; border: 2px solid #fff; } .user-profile h3 { font-size: 1rem; font-weight: 600; } .user-profile span { font-size: 0.775rem; font-weight: 600; } .user-detail { margin-left: 23px; white-space: nowrap; } .sidebar:hover .user-account { background: #fff; border-radius: 4px; } ``` 結論和最後的話 ------- 對於 Web 開發初學者來說,使用 HTML 和 CSS 建立響應式側邊欄是一項可以完成的任務。透過遵循本文中提供的步驟和程式碼,您應該能夠成功建立自己的響應式和功能性側邊欄。 為了進一步提高您的網頁開發技能,我建議您嘗試重新建立本網站上提供的其他[漂亮的側邊欄](https://www.codingnepalweb.com/category/sidebar-menu/)。其中一些側邊欄使用 JavaScript 來增強其功能,例如加入[深色模式](https://www.codingnepalweb.com/sidebar-menu-in-html-css-javascript-dark-light-mode/)、[下拉式選單](https://www.codingnepalweb.com/dropdown-sidebar-menu-html-css/)等。 如果您在建立側邊欄時遇到任何問題,可以透過點擊「下載」按鈕免費下載專案的原始碼檔案。您也可以透過點擊“查看即時”按鈕來查看它的即時演示。 [查看現場演示](https://www.codingnepalweb.com/demos/create-sidebar-menu-html-css-only/) [下載程式碼文件](https://www.codingnepalweb.com/create-sidebar-menu-html-css-only/) --- 原文出處:https://dev.to/codingnepal/create-a-sidebar-menu-using-html-and-css-only-2e79

使用 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

使用 Langchain 為您的文件建立 QA 機器人 😻

--- 標題:使用 Langchain 為您的文件建立 QA 機器人 😻 描述:使用 Wing Framework、NextJS 和 Langchain 建立的 ChatGPT 用戶端應用程式 canonical\_url:https://www.winglang.io/blog/2024/05/29/qa-bot-for-your-docs-with-langchain 發表:真實 --- 長話短說 ---- 在本教學中,我們將為您的網站文件建立一個人工智慧驅動的問答機器人。 - 🌐 建立一個用戶友好的 Next.js 應用程式來接受問題和 URL - 🔧 設定一個 Wing 後端來處理所有請求 - 💡 透過使用 RAG 抓取和分析文件,將 @langchain 納入 AI 驅動的答案 - 🔄 前端輸入和人工智慧處理的回應之間的完整連接。 ![問題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ykw5f2sos4fdhj8akowt.gif) 什麼是翼? ----- [Wing](https://wing.cloud/redirect?utm_source=qa-bot-reddit&redirect=https%3A%2F%2Fwww.winglang.io%2Fblog%2F2024%2F05%2F29%2Fqa-bot-for-your-docs-with-langchain)是一個雲端開源框架。 它允許您將應用程式的基礎架構和程式碼組合為一個單元,並將它們安全地部署到您首選的雲端提供者。 Wing 讓您可以完全控制應用程式基礎架構的配置方式。除了其易於學習的[程式語言](https://www.winglang.io/docs/language-reference)之外,Wing 還支援 Typescript。 在本教學中,我們將使用 TypeScript。所以,別擔心,您的 JavaScript 和 React 知識足以理解本教學。 ![翼登陸頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u366v255drbwqmcoagrz.png) {% cta https://wingla.ng/github %} 看 Wing ⭐️ {% endcta %} --- 使用 Next.js 建立前端 --------------- 在這裡,您將建立一個簡單的表單,它接受文件 URL 和使用者的問題,然後根據網站的資料回傳回應。 首先,建立一個包含兩個子資料夾的資料夾 - `frontend`和`backend` 。 `frontend`資料夾包含 Next.js 應用程式, `backend`資料夾用於 Wing。 ``` mkdir qa-bot && cd qa-bot mkdir frontend backend ``` 在**`frontend`**資料夾中,透過執行以下程式碼片段來建立 Next.js 專案: ``` cd frontend npx create-next-app ./ ``` ![下一個應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyq8dnmmmplvzl7g41ir.png) 將下面的程式碼片段複製到`app/page.tsx`檔案中,以建立接受使用者問題和文件 URL 的表單: ``` "use client"; import { useState } from "react"; export default function Home() { const [documentationURL, setDocumentationURL] = useState<string>(""); const [question, setQuestion] = useState<string>(""); const [disable, setDisable] = useState<boolean>(false); const [response, setResponse] = useState<string | null>(null); const handleUserQuery = async (e: React.FormEvent) => { e.preventDefault(); setDisable(true); console.log({ question, documentationURL }); }; return ( <main className='w-full md:px-8 px-3 py-8'> <h2 className='font-bold text-2xl mb-8 text-center text-blue-600'> Documentation Bot with Wing & LangChain </h2> <form onSubmit={handleUserQuery} className='mb-8'> <label className='block mb-2 text-sm text-gray-500'>Webpage URL</label> <input type='url' className='w-full mb-4 p-4 rounded-md border text-sm border-gray-300' placeholder='https://www.winglang.io/docs/concepts/why-wing' required value={documentationURL} onChange={(e) => setDocumentationURL(e.target.value)} /> <label className='block mb-2 text-sm text-gray-500'> Ask any questions related to the page URL above </label> <textarea rows={5} className='w-full mb-4 p-4 text-sm rounded-md border border-gray-300' placeholder='What is Winglang? OR Why should I use Winglang? OR How does Winglang work?' required value={question} onChange={(e) => setQuestion(e.target.value)} /> <button type='submit' disabled={disable} className='bg-blue-500 text-white px-8 py-3 rounded' > {disable ? "Loading..." : "Ask Question"} </button> </form> {response && ( <div className='bg-gray-100 w-full p-8 rounded-sm shadow-md'> <p className='text-gray-600'>{response}</p> </div> )} </main> ); } ``` 上面的程式碼片段顯示了一個表單,該表單接受使用者的問題和文件 URL 並將它們暫時記錄到控制台。 ![QA 機器人表單](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7b4w3tq0srua93gnk73n.png) 完美的! 🎉您已經完成了應用程式的使用者介面。接下來,讓我們設定 Wing 後端。 --- 如何在電腦上設定 Wing ------------- Wing 提供了一個 CLI,使您能夠在專案中執行各種 Wing 操作。 它還提供[VSCode](https://marketplace.visualstudio.com/items?itemName=Monada.vscode-wing)和[IntelliJ](https://plugins.jetbrains.com/plugin/22353-wing)擴展,透過語法突出顯示、編譯器診斷、程式碼完成和片段等功能增強開發人員體驗。 在繼續之前,請停止 Next.js 開發伺服器並透過在終端機中執行下面的程式碼片段來安裝 Wing CLI。 ``` npm install -g winglang@latest ``` 執行以下程式碼片段以確保 Winglang CLI 已安裝並按預期工作: ``` wing -V ``` 接下來,導航到`backend`資料夾並建立一個空的 Wing Typescript 專案。確保選擇`empty`模板並選擇 Typescript 作為語言。 ``` wing new ``` ![永新](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezy04zqvz9lura0d25dj.png) 將下面的程式碼片段複製到`backend/main.ts`檔案中。 ``` import { cloud, inflight, lift, main } from "@wingcloud/framework"; main((root, test) => { const fn = new cloud.Function( root, "Function", inflight(async () => { return "hello, world"; }) ); }); ``` **`main()`**函數充當 Wing 的入口點。 它建立一個雲端函數並在編譯時執行。另一方面, **`inflight`**函數在執行時執行並返回`Hello, world!`文字. 透過執行下面的程式碼片段啟動 Wing 開發伺服器。它會自動在瀏覽器中開啟 Wing 控制台,網址為`http://localhost:3000` 。 ``` wing it ``` ![Wing TS 最小控制台](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1ejobkm0dq5akhut732.png) 您已在電腦上成功安裝 Wing。 --- 如何將 Wing 連接到 Next.js 應用程式 ------------------------- 在前面的部分中,您已在`frontend`資料夾中建立了 Next.js 前端應用程式,並在`backend`資料夾中建立了 Wing 後端。 在本部分中,您將了解如何在 Next.js 應用程式和 Wing 後端之間通訊和發送資料。 首先,透過執行以下程式碼在後端資料夾中安裝[Wing React](https://github.com/winglang/winglibs/tree/main/react)函式庫: ``` npm install @winglibs/react ``` 接下來,更新`main.ts`文件,如下所示: ``` import { main, cloud, inflight, lift } from "@wingcloud/framework"; import React from "@winglibs/react"; main((root, test) => { const api = new cloud.Api(root, "api", { cors: true }) ; //👇🏻 create an API route api.get( "/test", inflight(async () => { return { status: 200, body: "Hello world", }; }) ); //👉🏻 placeholder for the POST request endpoint //👇🏻 connects to the Next.js project const react = new React.App(root, "react", { projectPath: "../frontend" }); //👇🏻 an environment variable react.addEnvironment("api_url", api.url); }); ``` 上面的程式碼片段建立了一個 API 端點 ( `/test` ),它接受 GET 請求並傳回`Hello world`文字。 `main`函數也連接到 Next.js 專案並將`api_url`新增為環境變數。 環境變數中包含的 API URL 使我們能夠將請求傳送到 Wing API 路由。我們如何在 Next.js 應用程式中檢索 API URL 並發出這些請求? 更新 Next.js `app/layout.tsx`檔案中的`RootLayout`元件,如下所示: ``` export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang='en'> <head> {/** ---👇🏻 Adds this script tag 👇🏻 ---*/} <script src='./wing.js' defer /> </head> <body className={inter.className}>{children}</body> </html> ); } ``` 透過執行`npm run build`重新建置 Next.js 專案。 最後,啟動Wing開發伺服器。它會自動啟動 Next.js 伺服器,可以在瀏覽器中透過**`http://localhost:3001`**存取該伺服器。 ![控制台到 URL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7rkxw9bds97a0qzg5vh.gif) 您已成功將 Next.js 連接到 Wing。您也可以使用`window.wingEnv.<attribute_name>`存取環境變數中的資料。 ![視窗.wingEnv](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0up5430jmxufmyeb9e4h.gif) 使用LangChain和Wing處理用戶請求 ---------------------- 在本節中,您將學習如何向 Wing 發送請求,使用[LangChain 和 OpenA](https://js.langchain.com/docs/get_started/quickstart#llm-chain) I 處理這些請求,並在 Next.js 前端顯示結果。 首先,我們更新 Next.js **`app/page.tsx`**檔案以檢索 API URL 並將使用者資料傳送到 Wing API 端點。 為此,請透過在**`page.tsx`**檔案頂部新增以下程式碼片段來擴充 JavaScript **`window`**物件。 ``` "use client"; import { useState } from "react"; interface WingEnv { api_url: string; } declare global { interface Window { wingEnv: WingEnv; } } ``` 接下來,更新`handleUserQuery`函數以將包含使用者問題和網站URL 的POST 請求傳送到Wing API 端點。 ``` //👇🏻 sends data to the api url const [response, setResponse] = useState<string | null>(null); const handleUserQuery = async (e: React.FormEvent) => { e.preventDefault(); setDisable(true); try { const request = await fetch(`${window.wingEnv.api_url}/api`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ question, pageURL: documentationURL }), }); const response = await request.text(); setResponse(response); setDisable(false); } catch (err) { console.error(err); setDisable(false); } }; ``` 在建立接受 POST 請求的 Wing 端點之前,請在`backend`資料夾中安裝下列套件: ``` npm install @langchain/community @langchain/openai langchain cheerio ``` [Cheerio](https://js.langchain.com/v0.2/docs/integrations/document_loaders/web_loaders/web_cheerio/)使我們能夠抓取軟體文件網頁,而[LangChain 軟體包](https://js.langchain.com/v0.1/docs/get_started/quickstart/)使我們能夠存取其各種功能。 LangChain OpenAI整合包使用OpenAI語言模型;因此,您需要一個有效的 API 金鑰。您可以從[OpenAI 開發者平台](https://platform.openai.com/api-keys)取得。 ![朗查恩](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/omg4o524oklrssso5rqc.png) 接下來,讓我們建立處理傳入請求的`/api`端點。 端點將: - 接受來自 Next.js 應用程式的問題和文件 URL, - 使用[LangChain 文件載入器](https://js.langchain.com/v0.1/docs/modules/data_connection/document_loaders/)載入文件頁面, - 將檢索到的文件分成區塊, - 轉換分塊文件並將它們保存在[LangChain 向量儲存](https://js.langchain.com/v0.1/docs/modules/data_connection/vectorstores/)中, - 並建立一個[檢索器函數](https://js.langchain.com/v0.1/docs/modules/data_connection/),從向量儲存中檢索文件。 首先,將以下內容匯入`main.ts`檔案: ``` import { main, cloud, inflight, lift } from "@wingcloud/framework"; import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai"; import { ChatPromptTemplate } from "@langchain/core/prompts"; import { createStuffDocumentsChain } from "langchain/chains/combine_documents"; import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio"; import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"; import { MemoryVectorStore } from "langchain/vectorstores/memory"; import { createRetrievalChain } from "langchain/chains/retrieval"; import React from "@winglibs/react"; ``` 在`main()`函數中加入以下程式碼片段以建立`/api`端點: ``` api.post( "/api", inflight(async (ctx, request) => { //👇🏻 accept user inputs from Next.js const { question, pageURL } = JSON.parse(request.body!); //👇🏻 initialize OpenAI Chat for LLM interactions const chatModel = new ChatOpenAI({ apiKey: "<YOUR_OPENAI_API_KEY>", model: "gpt-3.5-turbo-1106", }); //👇🏻 initialize OpenAI Embeddings for Vector Store data transformation const embeddings = new OpenAIEmbeddings({ apiKey: "<YOUR_OPENAI_API_KEY>", }); //👇🏻 creates a text splitter function that splits the OpenAI result chunk size const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 200, //👉🏻 characters per chunk chunkOverlap: 20, }); //👇🏻 creates a document loader, loads, and scraps the page const loader = new CheerioWebBaseLoader(pageURL); const docs = await loader.load(); //👇🏻 splits the document into chunks const splitDocs = await splitter.splitDocuments(docs); //👇🏻 creates a Vector store containing the split documents const vectorStore = await MemoryVectorStore.fromDocuments( splitDocs, embeddings //👉🏻 transforms the data to the Vector Store format ); //👇🏻 creates a document retriever that retrieves results that answers the user's questions const retriever = vectorStore.asRetriever({ k: 1, //👉🏻 number of documents to retrieve (default is 2) }); //👇🏻 creates a prompt template for the request const prompt = ChatPromptTemplate.fromTemplate(` Answer this question. Context: {context} Question: {input} `); //👇🏻 creates a chain containing the OpenAI chatModel and prompt const chain = await createStuffDocumentsChain({ llm: chatModel, prompt: prompt, }); //👇🏻 creates a retrieval chain that combines the documents and the retriever function const retrievalChain = await createRetrievalChain({ combineDocsChain: chain, retriever, }); //👇🏻 invokes the retrieval Chain and returns the user's answer const response = await retrievalChain.invoke({ input: `${question}`, }); if (response) { return { status: 200, body: response.answer, }; } return undefined; }) ); ``` API 端點接受使用者的問題和來自 Next.js 應用程式的頁面 URL,初始化[`ChatOpenAI`](https://js.langchain.com/v0.2/docs/integrations/chat/openai/)和[`OpenAIEmbeddings`](https://js.langchain.com/v0.2/docs/integrations/text_embedding/openai/) ,載入文件頁面,並以文件的形式檢索使用者查詢的答案。 然後,將文件分割成區塊,將區塊保存在`MemoryVectorStore`中,並使我們能夠使用[LangChain 檢索器](https://js.langchain.com/v0.1/docs/modules/data_connection/)來取得問題的答案。 從上面的程式碼片段來看,OpenAI API金鑰直接輸入到程式碼中;這可能會導致安全漏洞,使 API 金鑰可供攻擊者存取。為了防止這種資料洩露,Wing 允許您將私鑰和憑證保存在名為`secrets`的變數中。 當您建立機密時,Wing 會將此資料保存在`.env`檔案中,確保其安全且可存取。 更新`main()`函數以從 Wing Secret 取得 OpenAI API 金鑰。 ``` main((root, test) => { const api = new cloud.Api(root, "api", { cors: true }); //👇🏻 creates the secret variable const secret = new cloud.Secret(root, "OpenAPISecret", { name: "open-ai-key", }); api.post( "/api", lift({ secret }) .grant({ secret: ["value"] }) .inflight(async (ctx, request) => { const apiKey = await ctx.secret.value(); const chatModel = new ChatOpenAI({ apiKey, model: "gpt-3.5-turbo-1106", }); const embeddings = new OpenAIEmbeddings({ apiKey, }); //👉🏻 other code snippets & configurations ); const react = new React.App(root, "react", { projectPath: "../frontend" }); react.addEnvironment("api_url", api.url); }); ``` - 從上面的程式碼片段來看, ``` - The `secret` variable declares a name for the secret (OpenAI API key). ``` ``` - The [`lift().grant()`](https://www.winglang.io/docs/typescript/inflights#permissions) grants the API endpoint access to the secret value stored in the Wing Secret. ``` ``` - The [`inflight()`](https://www.winglang.io/docs/typescript/inflights) function accepts the context and request object as parameters, makes a request to LangChain, and returns the result. ``` ``` - Then, you can access the `apiKey` using the `ctx.secret.value()` function. ``` 最後,透過在終端機中執行此命令將 OpenAI API 金鑰儲存為機密。 ![翅膀的秘密](https://www.winglang.io/assets/images/qa-bot-wing-secrets-883db5e81515894ae280d77b7f72bb25.gif) 恭喜!您已成功完成本教學的專案。 以下是該應用程式的簡短演示: ![QA 機器人演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ropklqge2xzpibl29vye.gif) --- 讓我們更深入地研究 Wing 文件,看看我們的 AI 機器人可以提取哪些資料。 ![QA 機器人演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hnmf6n6mszc6go0uiw1v.gif) --- 總結一下 ---- 到目前為止,我們已經討論了以下內容: - 什麼是翼? - 如何使用Wing並使用Langchain查詢資料, - 如何將 Wing 連接到 Next.js 應用程式, - 如何在 Next.js 前端和 Wing 後端之間發送資料。 > [Wing](https://github.com/winglang/wing)旨在恢復您的創意流並縮小想像力與創造之間的差距。 Wing 的另一個巨大優勢是它是開源的。因此,如果您希望建立利用雲端服務的分散式系統或為雲端開發的未來做出貢獻, [Wing](https://github.com/winglang/wing)是您的最佳選擇。 請隨意為[GitHub 儲存庫做出貢獻,](https://github.com/winglang/wing)並與團隊和大型開發人員社群[分享您的想法](https://t.winglang.io/discord)。 本教學的源程式碼可[在此處](https://github.com/NathanTarbert/wing-langchain-nextjs)取得。 感謝您的閱讀! 🎉 --- 原文出處:https://dev.to/winglang/build-a-qa-bot-for-your-documentation-with-langchain-27i4

Node.js 中的高階錯誤處理

錯誤處理是軟體開發的重要方面,可確保應用程式的行為可預測,並在出現問題時提供有意義的回饋。在 Node.js 中,由於其非同步特性,有效的錯誤處理可能特別具有挑戰性。本文深入探討了管理 Node.js 應用程式中的錯誤的先進技術和最佳實踐。 了解錯誤類型 ------ 在深入研究錯誤處理策略之前,了解可能遇到的錯誤類型非常重要: 1. 同步錯誤: 同步程式碼執行期間發生的錯誤可以使用 try-catch 區塊擷取。 2. 非同步錯誤: 錯誤發生在非同步程式碼執行期間,例如回呼、promise 和 async/await 函數。 3. 操作錯誤: 表示程式需要處理的執行時間問題(例如,無法連線到資料庫)的錯誤。 4. 程式設計師錯誤: 程式中的錯誤(例如,型別錯誤、斷言失敗)。通常不應以與操作錯誤相同的方式捕獲和處理這些錯誤。 **同步錯誤處理** ---------- 對於同步程式碼,錯誤處理使用 try-catch 區塊: ``` try { // Synchronous code that might throw an error let result = dafaultFunction(); } catch (error) { console.error('An error occurred:', error.message); // Handle the error appropriately } ``` **非同步錯誤處理** ----------- - *callback* 在基於回調的非同步程式碼中,錯誤通常是回呼函數中的第一個參數: ``` const fs = require('fs'); fs.readFile('/path/to/file', (err, data) => { if (err) { console.error('An error occurred:', err.message); // Handle the error return; } // Process the data }); ``` - *promise* Promise 提供了一種更簡潔的方法來使用 .catch() 處理非同步錯誤: ``` const fs = require('fs').promises; fs.readFile('/path/to/file') .then(data => { // Process the data }) .catch(err => { console.error('An error occurred:', err.message); // Handle the error }); ``` - *async/await* Async/await 語法允許在非同步程式碼中進行更同步的錯誤處理: ``` const fs = require('fs').promises; async function readFile() { try { const data = await fs.readFile('/path/to/file'); // Process the data } catch (err) { console.error('An error occurred:', err.message); // Handle the error } } readFile(); ``` 集中錯誤處理 ------ 對於較大的應用程式,集中錯誤處理可以幫助更有效地管理錯誤。這通常涉及 Express.js 應用程式中的中間件。 - *Express.js 中介軟體* Express.js 提供了一種透過中間件處理錯誤的機制。這個中間件應該是堆疊中的最後一個: ``` const express = require('express'); const app = express(); // Define routes and other middleware app.get('/', (req, res) => { throw new Error('Something went wrong!'); }); // Error-handling middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ message: 'Internal Server Error' }); }); app.listen(3000, () => { console.log('Server is running on port 3000'); }); ``` 先進技術 ---- - *自訂錯誤類別* 建立自訂錯誤類別可以幫助區分不同類型的錯誤並使錯誤處理更加精細: ``` class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; Error.captureStackTrace(this, this.constructor); } } // Usage try { throw new AppError('Custom error message', 400); } catch (error) { if (error instanceof AppError) { console.error(`AppError: ${error.message} (status: ${error.statusCode})`); } else { console.error('An unexpected error occurred:', error); } } ``` - *錯誤記錄* 實施強大的錯誤日誌記錄以監視和診斷問題。 Winston 或 Bunyan 等工具可以幫助記錄日誌: ``` const winston = require('winston'); const logger = winston.createLogger({ level: 'error', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log' }) ] }); // Usage try { // Code that might throw an error throw new Error('Something went wrong'); } catch (error) { logger.error(error.message, { stack: error.stack }); } ``` - *全域錯誤處理* 處理未捕獲的異常和未處理的承諾拒絕可確保不會漏掉任何錯誤: ``` process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); // Perform cleanup and exit process if necessary }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); // Perform cleanup and exit process if necessary }); ``` 最佳實踐 ---- - 快速失敗:儘早偵測並處理錯誤。 - 正常關閉:確保您的應用程式在發生嚴重錯誤時可以正常關閉。 - 有意義的錯誤訊息:提供清晰且可操作的錯誤訊息。 - 避免靜默故障:始終記錄或處理錯誤以避免靜默故障。 - 測試錯誤場景:編寫測試來覆蓋潛在的錯誤場景並確保錯誤處理按預期工作。 結論 -- 為了有效地處理 Node.js 中的錯誤,您需要結合使用同步和非同步技術、集中管理以及進階策略,例如自訂錯誤類別和強大的日誌記錄。透過結合這些最佳實踐和先進技術,您可以建立強大的 Node.js 應用程式,優雅地處理錯誤並為用戶提供改進的體驗。 --- 原文出處:https://dev.to/amritak27/advanced-error-handling-in-nodejs-1ep8

TypeScript 專案的自訂實用程式類型

在我們探索 TypeScript 開發的第二部分中,我們引入了另外十種自訂實用程式類型,這些類型可以擴展程式碼的功能,提供更多工具來更有效地管理類型。這些實用程式類型有助於保持您的程式碼庫乾淨、高效和健壯。 第一部分: [TypeScript 專案的 1-10 個自訂實用程式類型](https://dev.to/antonzo/10-sustom-utility-types-for-typescript-projects-48pe) 總有機碳 ---- - [不可空深](#NonNullableDeep) - [合併](#Merge) - [元組到物件](#TupleToObject) - [獨佔元組](#ExclusiveTuple) - [Promise類型](#PromiseType) - [省略方法](#OmitMethods) - [函數參數](#FunctionArguments) - [承諾](#Promisify) - [約束函數](#ConstrainedFunction) - [聯合解析器](#UnionResolver) <a name="NonNullableDeep"></a> `NonNullableDeep` ----------------- `NonNullableDeep`類型是一個實用程序,可從給定類型`T`的所有屬性中深度刪除`null`和`undefined` 。這意味著不僅物件的頂級屬性不可為空,而且所有嵌套屬性也遞歸地標記為不可為空。在必須確保物件的屬性(包括深度嵌套的屬性)不為`null`或`undefined`情況下(例如在處理必須完全填充的資料時),此類型特別有用。 ``` type NonNullableDeep<T> = { [P in keyof T]: NonNullable<T[P]> extends object ? NonNullableDeep<NonNullable<T[P]>> : NonNullable<T[P]>; }; ``` **例子** 以下範例示範如何套用`NonNullableDeep`類型來確保`Person`物件本身及其任何巢狀屬性都無法為`null`或`undefined` ,從而確保整個物件已完全填入。 ``` interface Address { street: string | null; city: string | null; } interface Person { name: string | null; age: number | null; address: Address | null; } const person: NonNullableDeep<Person> = { name: "Anton Zamay", age: 26, address: { street: "Secret Street 123", city: "Berlin", }, }; // Error: Type 'null' is not assignable to type 'string'. person.name = null; // Error: Type 'undefined' is not assignable to type 'number'. person.age = undefined; // Error: Type 'null' is not assignable to type 'Address'. person.address = null; // Error: Type 'null' is not assignable to type 'string'. person.address.city = null; ``` <a name="Merge"></a> `Merge` ------- `Merge<O1, O2>`類型對於透過組合兩個物件類型`O1`和`O2`的屬性來建立新類型非常有用。當屬性重疊時, `O2`中的屬性將覆寫`O1`中的屬性。當您需要擴展或自訂現有類型以確保特定屬性優先時,這特別有用。 ``` type Merge<O1, O2> = O2 & Omit<O1, keyof O2>; ``` **例子** 在此範例中,我們定義了兩種物件類型,分別表示預設設定和使用者設定。使用`Merge`類型,我們組合這些設定來建立最終配置,其中`userSettings`會覆蓋`defaultSettings` 。 ``` type DefaultSettings = { theme: string; notifications: boolean; autoSave: boolean; }; type UserSettings = { theme: string; notifications: string[]; debugMode?: boolean; }; const defaultSettings: DefaultSettings = { theme: "light", notifications: true, autoSave: true, }; const userSettings: UserSettings = { theme: "dark", notifications: ["Warning 1", "Error 1", "Warning 2"], debugMode: true, }; type FinalSettings = Merge<DefaultSettings, UserSettings>; const finalSettings: FinalSettings = { ...defaultSettings, ...userSettings }; ``` <a name="TupleToObject"></a> `TupleToObject` --------------- `TupleToObject`類型是將元組類型轉換為物件類型的實用程序,其中元組的元素成為物件的鍵,並根據這些元素在元組中的位置提取關聯的值。這種類型在需要將元組轉換為更結構化的物件形式的情況下特別有用,允許透過元素的名稱而不是位置更直接地存取元素。 ``` type TupleToObject<T extends [string, any][]> = { [P in T[number][0]]: Extract<T[number], [P, any]>[1]; }; ``` **例子** 考慮這樣一個場景,您正在使用將表架構資訊儲存為元組的資料庫。每個元組包含一個欄位名稱及其對應的資料類型。這種格式通常用於資料庫元資料 API 或架構遷移工具。元組格式緊湊且易於處理,但對於應用程式開發來說,使用物件更方便。 ``` type SchemaTuple = [ ['id', 'number'], ['name', 'string'], ['email', 'string'], ['isActive', 'boolean'] ]; const tableSchema: SchemaTuple = [ ['id', 'number'], ['name', 'string'], ['email', 'string'], ['isActive', 'boolean'], ]; // Define the type of the transformed schema object type TupleToObject<T extends [string, string | number | boolean][]> = { [P in T[number][0]]: Extract< T[number], [P, any] >[1]; }; type SchemaObject = TupleToObject<SchemaTuple>; const schema: SchemaObject = tableSchema.reduce( (obj, [key, value]) => { obj[key] = value; return obj; }, {} as SchemaObject ); // Now you can use the schema object console.log(schema.id); // Output: number console.log(schema.name); // Output: string console.log(schema.email); // Output: string console.log(schema.isActive); // Output: boolean ``` <a name="ExclusiveTuple"></a> `ExclusiveTuple` ---------------- `ExclusiveTuple`類型是一個實用程序,它產生包含來自給定聯合類型`T`的唯一元素的元組。此類型確保聯合的每個元素僅在結果元組中包含一次,從而有效地將聯合類型轉換為具有聯合元素的所有可能的唯一排列的元組類型。這在您需要枚舉聯合成員的所有唯一組合的情況下特別有用。 ``` type ExclusiveTuple<T, U extends any[] = []> = T extends any ? Exclude<T, U[number]> extends infer V ? [V, ...ExclusiveTuple<Exclude<T, V>, [V, ...U]>] : [] : []; ``` **例子** 考慮這樣一個場景:您正在開發一個旅行應用程式的功能,該功能可以為遊覽某個城市的遊客產生獨特的行程。該市有三個主要景點:博物館、公園和劇院。 ``` type Attraction = 'Museum' | 'Park' | 'Theater'; type Itineraries = ExclusiveTuple<Attraction>; // The Itineraries type will be equivalent to: // type Itineraries = // ['Museum', 'Park', 'Theater'] | // ['Museum', 'Theater', 'Park'] | // ['Park', 'Museum', 'Theater'] | // ['Park', 'Theater', 'Museum'] | // ['Theater', 'Museum', 'Park'] | // ['Theater', 'Park', 'Museum']; ``` <a name="PromiseType"></a> `PromiseType` ------------- `PromiseType`類型是一個實用程序,用於提取給定 Promise 解析為的值的類型。這在使用非同步程式碼時非常有用,因為它允許開發人員輕鬆推斷結果的類型,而無需明確指定它。 ``` type PromiseType<T> = T extends Promise<infer U> ? U : never; ``` 此類型使用 TypeScript 的條件類型和`infer`關鍵字來決定`Promise`的解析類型。如果`T`擴展`Promise<U>` ,則表示`T`是解析為類型`U` `Promise` ,而`U`是推斷的類型。如果`T`不是`Promise` ,則型別解析為`never` 。 **例子** 以下範例示範如何使用 PromiseType 類型從 Promise 中提取已解析的類型。透過使用此實用程式類型,您可以推斷 Promise 將解析為的值的類型,這有助於在處理非同步操作時進行類型檢查並避免錯誤。 ``` type PromiseType<T> = T extends Promise<infer U> ? U : never; interface User { id: number; name: string; } interface Post { id: number; title: string; content: string; userId: number; } async function fetchUser(userId: number): Promise<User> { return { id: userId, name: "Anton Zamay" }; } async function fetchPostsByUser(userId: number): Promise<Post[]> { return [ { id: 1, title: "Using the Singleton Pattern in React", content: "Content 1", userId }, { id: 2, title: "Hoisting of Variables, Functions, Classes, Types, " + "Interfaces in JavaScript/TypeScript", content: "Content 2", userId }, ]; } async function getUserWithPosts( userId: number ): Promise<{ user: User; posts: Post[] }> { const user = await fetchUser(userId); const posts = await fetchPostsByUser(userId); return { user, posts }; } // Using PromiseType to infer the resolved types type UserType = PromiseType<ReturnType<typeof fetchUser>>; type PostsType = PromiseType<ReturnType<typeof fetchPostsByUser>>; type UserWithPostsType = PromiseType<ReturnType<typeof getUserWithPosts>>; async function exampleUsage() { const userWithPosts: UserWithPostsType = await getUserWithPosts(1); // The following will be type-checked to ensure correctness const userName: UserType["name"] = userWithPosts.user.name; const firstPostTitle: PostsType[0]["title"] = userWithPosts.posts[0].title; console.log(userName); // Anton Zamay console.log(firstPostTitle); // Using the Singleton Pattern in React } exampleUsage(); ``` **為什麼我們需要`UserType`而不僅僅是使用`User` ?** 這是個好問題!使用`UserType`而不是直接使用`User`主要原因是為了確保從非同步函數的回傳類型準確推斷出類型。這種方法有幾個優點: 1. **類型一致性:**透過使用`UserType` ,您可以確保類型始終與`fetchUser`函數的實際回傳類型一致。如果`fetchUser`的回傳類型發生更改, `UserType`將自動反映該更改,而無需手動更新。 2. **自動類型推斷**:在處理複雜類型和巢狀承諾時,手動確定和追蹤解析的類型可能具有挑戰性。使用 PromiseType 允許 TypeScript 為您推斷這些類型,從而降低錯誤風險。 <a name="OmitMethods"></a> `OmitMethods` ------------- `OmitMethods`型別是個實用程序,可從給定型別`T`中刪除所有方法屬性。這意味著作為函數的類型`T`的任何屬性都將被省略,從而產生僅包含非函數屬性的新類型。 ``` type OmitMethods<T> = Pick<T, { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]>; ``` **例子** 此類型在您想要從物件類型中排除方法的情況下特別有用,例如將物件序列化為 JSON 或透過 API 發送物件時,其中方法不相關且不應包含在內。以下範例示範如何將`OmitMethods`套用至物件類型以刪除所有方法,確保產生的類型僅包含非函數的屬性。 ``` interface User { id: number; name: string; age: number; greet(): void; updateAge(newAge: number): void; } const user: OmitMethods<User> = { id: 1, name: "Alice", age: 30, // greet and updateAge methods are omitted from this type }; function sendUserData(userData: OmitMethods<User>) { // API call to send user data console.log("Sending user data:", JSON.stringify(userData)); } sendUserData(user); ``` <a name="FunctionArguments"></a> `FunctionArguments` ------------------- `FunctionArguments`類型是一個實用程序,用於提取給定函數類型`T`的參數類型。這意味著對於傳遞給它的任何函數類型,該類型將傳回一個表示函數參數類型的元組。此類型在需要捕獲或操作函數的參數類型的情況下特別有用,例如在高階函數中或建立類型安全的事件處理程序時。 ``` type FunctionArguments<T> = T extends (...args: infer A) => any ? A : never; ``` **例子** 假設您有一個高階函數包裝,它接受一個函數及其參數,然後使用這些參數來呼叫該函數。使用 FunctionArguments,您可以確保包裝函數參數的類型安全。 ``` function wrap<T extends (...args: any[]) => any>(fn: T, ...args: FunctionArguments<T>): ReturnType<T> { return fn(...args); } function add(a: number, b: number): number { return a + b; } type AddArgs = FunctionArguments<typeof add>; // AddArgs will be of type [number, number] const result = wrap(add, 5, 10); // result is 15, and types are checked ``` <a name="Promisify"></a> `Promisify` ----------- `Promisify`類型是一個實用程序,它將給定類型`T`的所有屬性轉換為各自類型的 Promise。這意味著結果類型中的每個屬性都將是該屬性的原始類型的`Promise` 。這種類型在處理非同步操作時特別有用,您希望確保整個結構符合基於`Promise`的方法,從而更輕鬆地處理和管理非同步資料。 ``` type Promisify<T> = { [P in keyof T]: Promise<T[P]> }; ``` **例子** 考慮一個顯示使用者個人資料、最近活動和設定的儀表板。這些資訊可能是從不同的服務獲取的。透過承諾單獨的屬性,我們確保使用者資料的每個部分都可以獨立取得、解析和處理,從而在處理非同步操作時提供靈活性和效率。 ``` interface Profile { name: string; age: number; email: string; } interface Activity { lastLogin: Date; recentActions: string[]; } interface Settings { theme: string; notifications: boolean; } interface UserData { profile: Profile; activity: Activity; settings: Settings; } // Promisify Utility Type type Promisify<T> = { [P in keyof T]: Promise<T[P]>; }; // Simulated Fetch Functions const fetchProfile = (): Promise<Profile> => Promise.resolve({ name: "Anton Zamay", age: 26, email: "[email protected]" }); const fetchActivity = (): Promise<Activity> => Promise.resolve({ lastLogin: new Date(), recentActions: ["logged in", "viewed dashboard"], }); const fetchSettings = (): Promise<Settings> => Promise.resolve({ theme: "dark", notifications: true }); // Fetching User Data const fetchUserData = async (): Promise<Promisify<UserData>> => { return { profile: fetchProfile(), activity: fetchActivity(), settings: fetchSettings(), }; }; // Using Promisified User Data const displayUserData = async () => { const user = await fetchUserData(); // Handling promises for each property (might be in different places) const profile = await user.profile; const activity = await user.activity; const settings = await user.settings; console.log(`Name: ${profile.name}`); console.log(`Last Login: ${activity.lastLogin}`); console.log(`Theme: ${settings.theme}`); }; displayUserData(); ``` <a name="ConstrainedFunction"></a> `ConstrainedFunction` --------------------- `ConstrainedFunction`類型是一個實用程序,它約束給定的函數類型 T 以確保保留其參數和傳回類型。它本質上捕獲函數的參數類型和返回類型,並強制結果函數類型必須遵守這些推斷類型。當您需要對高階函數實施嚴格的類型約束或建立必須符合原始函數簽署的包裝函數時,此類型非常有用。 ``` type ConstrainedFunction<T extends (...args: any) => any> = T extends (...args: infer A) => infer R ? (args: A extends any[] ? A : never) => R : never; ``` **例子** 在事先未知函數簽署且必須動態推斷的情況下, `ConstrainedFunction`可確保根據推斷的類型正確應用約束。想像一個實用程序,它包裝任何函數以記憶其結果: ``` function memoize<T extends (...args: any) => any>(fn: T): ConstrainedFunction<T> { const cache = new Map<string, ReturnType<T>>(); return ((...args: Parameters<T>) => { const key = JSON.stringify(args); if (!cache.has(key)) { cache.set(key, fn(...args)); } return cache.get(key)!; }) as ConstrainedFunction<T>; } const greet: Greet = (name, age) => { return `Hello, my name is ${name} and I am ${age} years old.`; }; const memoizedGreet = memoize(greet); const message1 = memoizedGreet("Anton Zamay", 26); // Calculates and caches const message2 = memoizedGreet("Anton Zamay", 26); // Retrieves from cache ``` 在這裡, `memoize`使用`ConstrainedFunction`來確保記憶函數保持與原始函數`fn`相同的簽名,而不需要明確定義函數類型。 <a name="UnionResolver"></a> `UnionResolver` --------------- `UnionResolver`類型是將聯合型別轉換為可區分聯合的實用程式。具體來說,對於給定的聯合類型`T` ,它會產生一個物件陣列,其中每個物件都包含一個屬性類型,該屬性類型保存聯合中的類型之一。在需要明確處理聯合的每個成員的情況下使用聯合類型時,此類型特別有用,例如在類型安全的 Redux 操作或 TypeScript 中的可區分聯合模式中。 ``` type UnionResolver<T> = T extends infer U ? { type: U }[] : never; ``` **例子** 以下範例示範如何應用`UnionResolver`類型將聯合類型轉換為物件陣列,每個物件都具有`type`屬性。這允許對聯合內的每個操作進行類型安全處理,確保考慮到所有情況並降低使用聯合類型時發生錯誤的風險。 ``` type ActionType = "ADD_TODO" | "REMOVE_TODO" | "UPDATE_TODO"; type ResolvedActions = UnionResolver<ActionType>; // The resulting type will be: // { // type: "ADD_TODO"; // }[] | { // type: "REMOVE_TODO"; // }[] | { // type: "UPDATE_TODO"; // }[] const actions: ResolvedActions = [ { type: "ADD_TODO" }, { type: "REMOVE_TODO" }, { type: "UPDATE_TODO" }, ]; // Now you can handle each action type distinctly actions.forEach(action => { switch (action.type) { case "ADD_TODO": console.log("Adding a todo"); break; case "REMOVE_TODO": console.log("Removing a todo"); break; case "UPDATE_TODO": console.log("Updating a todo"); break; } }); ``` --- 原文出處:https://dev.to/antonzo/11-20-sustom-utility-types-for-typescript-projects-2bg5

提升你的技能

介紹 -- 學習如何成為更好的開發人員需要不斷提升自己的技能。一個人如何學習成長並成為更好的開發人員?讓我們探討幾個總體上適用於大多數開發人員的想法。程式碼範例全部採用 C# 語言,之所以選擇它們是因為它們對於大多數開發人員來說並不常見,並且是在內部完成的。 腳步 -- - [Pluralsight](https://www.pluralsight.com/)是一個付費網站,提供數百門 C# 課程。首先使用他們的人工智慧評估,這將引導您走上正確的道路。許多課程也有自己的評估。 Pluralsight 讓您輕鬆地向高評價作者學習,並透過任何裝置(例如筆記型電腦、手機或平板電腦)存取課程。 Pluralsite 提供免費試用,有時還會在購買訂閱時提供折扣。 - 使用[微軟學習](https://learn.microsoft.com/en-us/training/)。無論您是剛開始職業生涯,還是經驗豐富的專業人士,我們的自我導向方法都可以幫助您更快、更有信心地按照自己的步調實現目標。透過互動式模組和路徑培養技能或向講師學習。以您的方式學習和成長。 - 花時間閱讀 Microsoft 文件,例如,閱讀[C# 程式的一般結構、類型運算子和表達式語句、](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/)各種[類別](https://learn.microsoft.com/en-us/dotnet/api/system.string?view=net-6.0)[、物件導向程式設計](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/tutorials/oop)等等。 - 在學習過程中,嘗試使用控制台或單元測試專案使事情變得簡單,換句話說,將後端學習與前端使用者介面學習分開。 - 在您感覺舒服的某個時間點,確定一個簡單的專案,在編碼之前寫出任務,然後編寫程式碼,而不是同時思考和編碼。新手等級的思考和編碼簡直就是一場即將發生的災難。 - 在網路上尋找資訊並找到解決方案時,不要簡單地複製和貼上,檢查程式碼,在使用所述程式碼之前先嘗試弄清楚它在做什麼。 - 了解如何在 Visual Studio 中使用 GitHub 來備份和版本程式碼。假設您編寫了程式碼並破壞了它,透過 GitHub 儲存庫中的正確版本控制,您可以還原變更並還原程式碼。 - 使用 .NET Framework Core 6 或 .NET Core Framework 8 而不是 .NET Framework classic,因為使用 .NET Core 有更多好處 - 如果學習使用資料,請從 SQL-Server Express 開始並安裝 SSMS (SQL-Server Management Studio),同時學習使用 Entity Framework Core。 - 充分了解學習任何語言時慢速學習比快速學習好,而且沒有人知道這一切。 ![了解如何使用除錯器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xp3uj4nuhzx7sple8tad.png) 加速學習的工具 ------- Microsoft Visual Studio 絕對是最好的 IDE(整合開發環境),它具有以下專案可以增加學習並節省編碼時間。 - 適用於 Visual Studio 和 SSMS 的 Red Gate [SQL 提示符](https://www.red-gate.com/products/sql-prompt/) - 高級 IntelliSense 風格的程式碼完成 - 重構 SQL 程式碼 - SSMS SQL 歷史記錄 - 以及更多 - Jetbrains [ReSharper](https://www.jetbrains.com/resharper/)這是一個非常寶貴的 Visual Studio 擴充功能。 - [EF Power Tools](https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools)可輕鬆對 EF Core 的 SQL-Server 資料庫進行逆向工程 深入了解程式碼基礎知識 ----------- 掌握基礎知識後,尋找有助於成長為更好的開發人員的程式碼範例。 一個可能的途徑是使用[Microsoft Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/) (EF Core) 或使用[Dapper](https://www.learndapper.com/)等資料提供者來處理資料庫。 還有其他處理資料的方法,但 EF Core 和 Dapper 在效能和易於學習方面是最好的。 在 Web 上尋找程式碼範例時,請確保它們適用於您的專案的 .NET Framework,因為 .NET Framework 4.8 程式碼範例與 .NET Core 8 Framework 有很大不同。 Microsoft 每年都會為 EF Core 建立程式碼範例,但在許多情況下,其結構可能不適合缺乏經驗的開發人員學習,因此 Karen Payne 採用 EF Core 8 程式碼範例並建立了以下[文章](https://dev.to/karenpayneoregon/microsoft-entity-framework-core-8-samples-3dj8)/[儲存庫,在大多數情況下,這些文章/儲存庫](https://github.com/karenpayneoregon/ef-code-8-samples)很容易學習。 第 1 課 - SQL-Server 計算列 ---------------------- ### EF 核心版本 {% cta https://github.com/karenpayneoregon/sql-basics/tree/master/EF\_CoreBirthdaysCompulatedColumns %} 範例專案 {% endcta %} [計算列](https://learn.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table?view=sql-server-ver16)是虛擬列,除非該列被標記為 PERSISTED,否則不會實際儲存在表中。計算列表達式可以使用其他欄位中的資料來計算其所屬列的值。您可以使用 SQL Server Management Studio (SSMS) 或 Transact-SQL (T-SQL) 為 SQL Server 中的計算列指定運算式。 完整文章,請參閱[SQL-Server:使用 Ef Core 計算列](https://dev.to/karenpayneoregon/sql-server-computed-columns-with-ef-core-3h8d) 但在這裡,我們將使用 EF Core 和 Dapper 從開始和演練使用情況建立一個計算列。 原文來自以下 Stackoverflow貼[文](https://stackoverflow.com/questions/9/how-do-i-calculate-someones-age-based-on-a-datetime-type-birthday?page=2&tab=modifieddesc#tab-top)。取得出生日期和目前日期,用出生日期減去目前日期,然後除以 10,000。 在 SSMS(SQL Server Management Studio)中 請注意,在程式碼範例中,完整資料庫存在於腳本資料夾下的專案 EF\_CoreBirthdaysCompulatedColumns 中。在執行腳本之前,請在 SSMS 中建立資料庫,然後執行腳本來建立表格並填入資料。 另請注意,在程式碼範例中,連接字串使用 NuGet 套件[ConsoleConfigurationLibrary](https://www.nuget.org/packages/ConsoleConfigurationLibrary/)駐留在 appsettings.json 中。 **表結構** ![表結構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06sfbbb9i4ru5203l1nx.png) **SQL** ![選擇語句](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8bqiabe7j0a4nvez1yqa.png) 將聲明分開。 - 使用日期分隔符號格式化兩個日期並將每個日期轉換為整數。 - 從目前日期減去出生日期,括號很重要。 - 將以上除以 10,000 即可得到年齡。 **結果** ![SELECT 的結果](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cvzc25dc1ojr4arurnqy.png) 現在為名為 YearsOld 的表建立一個 nvarchar 類型的新欄位,並將此語句放入計算列屬性中,然後儲存變更。 ``` (CAST(FORMAT(GETDATE(), 'yyyyMMdd') AS INTEGER) - CAST(FORMAT(BirthDate, 'yyyyMMdd') AS INTEGER)) / 10000 ``` ![ssms中的表設計](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tvwq73hel66uumzbixh5.png) - 建立一個新的 C# 控制台專案。 - 新增[Microsoft.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer/8.0.0?_src=template)的依賴項 - 安裝 Visual Studio 擴充[EF Power Tools](https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools) 。若要了解如何使用 EF Power Tools,請觀看作者提供的以下[影片](https://www.youtube.com/watch?v=uph-AGyOd8c)。新增[完整文件](https://github.com/ErikEJ/EFCorePowerTools/wiki/Reverse-Engineering)。 使用 EF Power Tools 後,將產生以下類別。 代表 SQL-Server 資料庫表的模型。 ``` public partial class BirthDays { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateOnly? BirthDate { get; set; } public int? YearsOld { get; set; } } ``` 所謂的[DbContext](https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.dbcontext?view=entity-framework-6.2.0)和與資料庫互動的配置。 注意 YearsOld 上的[HasCompulatedColumnSql](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationalpropertybuilderextensions.hascomputedcolumnsql?view=efcore-8.0) ,這是我們的計算列。 ``` public partial class Context : DbContext { public Context() { } public Context(DbContextOptions<Context> options) : base(options) { } public virtual DbSet<BirthDays> BirthDays { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) #warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. => optionsBuilder.UseSqlServer(DataConnections.Instance.MainConnection); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BirthDays>(entity => { entity.Property(e => e.YearsOld).HasComputedColumnSql("((CONVERT([int],format(getdate(),'yyyyMMdd'))-CONVERT([int],format([BirthDate],'yyyyMMdd')))/(10000))", false); }); OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); } ``` > **筆記** > 執行上述工作有兩個陣營:資料庫優先或程式碼優先。對於剛開始使用 EF Core 的人來說,資料庫優先是最好的路徑。 要查看資料, [Spectre.Console](https://spectreconsole.net/)用於建立一個漂亮的表格。 ``` internal partial class Program { static async Task Main(string[] args) { await Setup(); var table = CreateTable(); await using (var context = new Context()) { var list = await context.BirthDays.ToListAsync(); foreach (var bd in list) { table.AddRow( bd.Id.ToString(), bd.FirstName, bd.LastName, bd.BirthDate.ToString(), bd.YearsOld.ToString()); } AnsiConsole.Write(table); } ExitPrompt(); } public static Table CreateTable() { var table = new Table() .AddColumn("[b]Id[/]") .AddColumn("[b]First[/]") .AddColumn("[b]Last[/]") .AddColumn("[b]Birth date[/]") .AddColumn("[b]Age[/]") .Alignment(Justify.Left) .BorderColor(Color.LightSlateGrey); return table; } } ``` ![上述程式碼的截圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i60dye7au7fve08y1wfu.png) 為了獲取我們的資料,一行程式碼用於實例化 EF Core,一行程式碼用於讀取資料。 EF Core 也非常適合關聯式資料庫,請參閱以下[儲存庫](https://github.com/karenpayneoregon/ef-code-8-samples)。 有關記錄 EF Core 產生的 SQL,請參閱下列[專案](https://github.com/karenpayneoregon/ef-code-8-samples/tree/master/DualContextsApp),該專案也展示如何使用兩個不同的 SQL-Server 實例。 ### 短小精悍的版本 {% cta https://github.com/karenpayneoregon/sql-basics/tree/master/DapperBirthdaysCompulatedColumns %} 範例專案 {% endcta %} 與 EF Core 不同,使用 Dapper,開發人員在 SSMS 中編寫 SQL 語句並將有效語句新增到程式碼中。有關 Dapper 的更多訊息,請參閱我的[系列](https://dev.to/karenpayneoregon/series/25270)。 這裡 SQL 儲存在唯讀字串中,替代方法是將 SQL(或任何語句)儲存在預存程序中。 ![學習編寫正確的 SQL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyn1awyunv0pvbhtgzy1.png) ``` internal class SqlStatements { public static string GetBirthdays => """ SELECT Id ,FirstName ,LastName ,BirthDate ,YearsOld FROM BirthDaysDatabase.dbo.BirthDays """; } ``` 讀取資料的程式碼。 ``` internal class DapperOperations { private IDbConnection _cn; public DapperOperations() { _cn = new SqlConnection(DataConnections.Instance.MainConnection); SqlMapper.AddTypeHandler(new SqlDateOnlyTypeHandler()); SqlMapper.AddTypeHandler(new SqlTimeOnlyTypeHandler()); } public async Task<List<BirthDays>> GetBirthdaysAsync() { return (await _cn.QueryAsync<BirthDays>(SqlStatements.GetBirthdays)).AsList(); } } ``` 在類別構造函數中 1. 使用[Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient/5.2.1?_src=template) NuGet 套件建立連線。 1. 使用[kp.Dapper.Handlers](https://www.nuget.org/packages/kp.Dapper.Handlers/1.0.0?_src=template) NuGet 套件為 Dapper 新增理解 DateOnly 類型的功能。 讀取資料是一個單行資料,表示我們需要非同步生日列表。 ``` public async Task<List<BirthDays>> GetBirthdaysAsync() { return (await _cn.QueryAsync<BirthDays>(SqlStatements.GetBirthdays)).AsList(); } ``` 回到 Program.cs,除了建立 Dapper 類別的實例並呼叫方法之外,程式碼與 EF Core 相同。 ``` internal partial class Program { static async Task Main(string[] args) { await Setup(); var table = CreateTable(); var operations = new DapperOperations(); var list = await operations.GetBirthdaysAsync(); foreach (var bd in list) { table.AddRow( bd.Id.ToString(), bd.FirstName, bd.LastName, bd.BirthDate.ToString(), bd.YearsOld.ToString()); } AnsiConsole.Write(table); ExitPrompt(); } public static Table CreateTable() { var table = new Table() .AddColumn("[b]Id[/]") .AddColumn("[b]First[/]") .AddColumn("[b]Last[/]") .AddColumn("[b]Birth date[/]") .AddColumn("[b]Age[/]") .Alignment(Justify.Left) .BorderColor(Color.LightSlateGrey); return table; } } ``` ### 計算列的摘要 並未詳細介紹程式碼的每個方面,這意味著在專案中採用技術之前需要花時間剖析程式碼以及使用了哪些 NuGet 套件。也可以考慮透過[Visual Studio 偵錯器](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-debugger?view=vs-2022)執行程式碼。 偵錯是許多新手開發人員忽略的事情,也是 Visual Studio 的最佳功能之一。學習如何除錯並不需要花費大量時間。 第 2 課 - 重構程式碼 ------------- 許多人認為編碼的主要任務是讓程式碼正常工作,然後返回並重構程式碼。從個人經驗來看,這種情況一般不會發生。這就是開發人員需要在工作專案之外磨練技能的原因。 ![從未停止學習](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju4889uewhkpfrfmp0w5.png) ### 實施例1 開發人員被要求將字串拆分為大寫字符,並將字串放在前面。 例如,給定 ThisIsATest,輸出將是 This Is A Test。開發者在網路上搜尋並找到以下內容。 ``` public static class StringExtensions { private static readonly Regex CamelCaseRegex = new(@"([A-Z][a-z]+)"); /// <summary> /// KarenPayne => Karen Payne /// </summary> [DebuggerStepThrough] public static string SplitCamelCase(this string sender) => string.Join(" ", CamelCaseRegex.Matches(sender) .Select(m => m.Value)); } ``` 這是可行的,但有一個更好的版本,在下面的範例中是由GitHub Copilot 編寫的,並且是第二次迭代,這意味著第一次copilot 被問到時,它提供了一個未經優化的解決方案,因為問題是如何提出的。 ``` [DebuggerStepThrough] public static string SplitCamelCase(this string input) { if (string.IsNullOrEmpty(input)) { return input; } Span<char> result = stackalloc char[input.Length * 2]; var resultIndex = 0; for (var index = 0; index < input.Length; index++) { var currentChar = input[index]; if (index > 0 && char.IsUpper(currentChar)) { result[resultIndex++] = ' '; } result[resultIndex++] = currentChar; } return result[..resultIndex].ToString(); } ``` ![更少的程式碼並不總是最好的](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8u0zjrr2qjgot6tmav8.png) 等一下,第二個版本的程式碼多了很多,這個版本怎麼會更好呢?新手和經驗豐富的開發人員都有一種心態,就是程式碼行數越少越好,也許是為了提高可讀性。當然,開發人員應該始終努力編寫可讀的程式碼,但多行程式碼也可以輕鬆閱讀。 如何編寫可讀的程式碼。 - 使用有意義的變數名稱,例如在 for 語句中使用索引而不是 i 或使用firstName 而不是fName。 - 折疊程式碼而不是一行,如下所示 ``` public static class CheckedListBoxExtensions { public static List<T> CheckedList<T>(this CheckedListBox sender) => sender.Items.Cast<T>() .Where((_, index) => sender.GetItemChecked(index)) .Select(item => item) .ToList(); } ``` 而不是 ``` public static class CheckedListBoxExtensions { public static List<T> CheckedList<T>(this CheckedListBox sender) => sender.Items.Cast<T>().Where((_, index) => sender.GetItemChecked(index)).Select(item => item).ToList(); } ``` 下一步 --- 這裡有一些想法,即使是許多經驗豐富的開發人員也會避免,而不是你! - [泛型](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics) - [介面](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface) - 建立公共庫 - [JSON 序列化與反序列化](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview) 概括 -- 這些是成為更好的開發人員的更多技巧中的一些。實現這一目標的唯一方法是在專案之外不斷學習。 如果您的老闆或團隊領導者沒有提供時間來學習新技能,您可以每週花一兩個小時來學習和成長。 --- 原文出處:https://dev.to/karenpayneoregon/push-your-skills-2pho

編寫 SOLID React Hooks

SOLID 是較常用的設計模式之一。它在許多語言和框架中都很常用,也有一些文章介紹如何在 React 中使用它。 每篇關於 SOLID 的 React 文章都以稍微不同的方式介紹該模型,有些將其應用於元件,有些將其應用於 TypeScript,但很少有人將這些原理應用於鉤子。 由於 hooks 是 React 基礎的一部分,我們將在這裡看看 SOLID 原則如何應用於這些。 單一職責原則(SRP) ----------- Solid 中的第一個字母 S 是最容易理解的。本質上,它的意思是,讓一個鉤子/元件做一件事。 ``` // Single Responsibility Principle ``` ``` A module should be responsible to one, and only one, actor ``` 例如,看看下面的 useUser 鉤子,它會取得使用者和待辦事項,並將任務合併到使用者物件中。 ``` import { useState } from 'react' import { getUser, getTodoTasks } from 'somewhere' const useUser = () => { const [user, setUser] = useState() const [todoTasks, setTodoTasks] = useState() useEffect(() => { const userInfo = getUser() setUser(userInfo) }, []) useEffect(() => { const tasks = getTodoTasks() setTodoTasks(tasks) }, []) return { ...user, todoTasks } } ``` 這個鉤子並不牢固,它不遵守單一責任原則。這是因為它有責任獲取用戶資料和待辦任務,這是兩件事。 相反,上面的程式碼應該分為兩個不同的鉤子,一個用於獲取有關用戶的資料,另一個用於獲取任務。 ``` import { useState } from 'react' import { getUser, getTodoTasks } from 'somewhere' // useUser hook is no longer responsible for the todo tasks. const useUser = () => { const [user, setUser] = useState() useEffect(() => { const userInfo = getUser() setUser(userInfo) }, []) return { user } } // Todo tasks do now have their own hook. // The hook should actually be in its own file as well. Only one hook per file! const useTodoTasks = () => { const [todoTasks, setTodoTasks] = useState() useEffect(() => { const tasks = getTodoTasks() setTodoTasks(tasks) }, []) return { todoTasks } } ``` 這個原則適用於所有的鉤子和元件,它們都應該只做一件事。要問自己的事情是: 1. 這是一個應該顯示 UI(演示性)或處理資料(邏輯性)的元件嗎? 1. 這個鉤子應該處理什麼單一類型的資料? 1. 這個鉤子/元件屬於哪一層?它是處理資料儲存還是 UI 的一部分? 如果您發現自己建造的鉤子對上述每個問題都沒有單一答案,那麼您就違反了單一責任原則。 這裡值得注意的一件有趣的事情是第一個問題。這實際上意味著渲染 UI 的元件不應該處理資料。這意味著,要真正嚴格遵循這項原則,每個顯示資料的 React 元件都應該有一個鉤子來處理其邏輯和資料。換句話說,不應在顯示資料的相同元件中取得資料。 ### 為什麼在 React 中使用 SRP? 這種單一責任原則其實非常適合 React。 React 遵循基於元件的架構,這意味著它由組合在一起的小元件組成,因此它們一起可以建構並形成一個應用程式。元件越小,可重複使用的可能性就越大。這適用於元件和鉤子。 因此,React 或多或少是建立在單一職責原則上的。如果你不遵循它,你會發現自己總是在編寫新的鉤子和元件,並且很少重複使用它們中的任何一個。 違反單一責任原則將使您的程式碼難以測試。如果不遵循這個原則,您經常會發現您的測試文件有數百行,甚至可能多達 1000 行程式碼。 {% 嵌入 https://dev.to/perssondennis/how-to-use-mvvm-in-react-using-hooks-and-typescript-3o4m %} 開閉原理 (OCP) ---------- 讓我們繼續遵循開閉原則,畢竟這是 SOLID 中的下一個字母。 OCP 和 SRP 一樣是較容易理解的原則之一,至少其定義是如此。 ``` // Open/Closed Principle ``` ``` Software entities (classes, modules, functions, etc.) should ``` ``` be open for extension, but closed for modification ``` 對於最近開始使用 React 的傻瓜來說,這句話可以翻譯為: ``` Write hooks/component which you never will have a reason to ``` ``` touch again, only re-use them in other hooks/components ``` 回想一下本文前面所說的單一責任原則;在 React 中,您需要編寫小元件並將它們組合在一起。讓我們看看為什麼這有幫助。 ``` import { useState } from 'react' import { getUser, updateUser } from 'somewhere' const useUser = ({ userType }) => { const [user, setUser] = useState() useEffect(() => { const userInfo = getUser() setUser(userInfo) }, []) const updateEmail = (newEmail) => { if (user && userType === 'admin') { updateUser({ ...user, email: newEmail }) } else { console.error('Cannot update email') } } return { user, updateEmail } } ``` 上面的鉤子獲取用戶並返回它。如果使用者的類型是管理員,則允許該使用者更新其電子郵件。普通使用者不允許更新其電子郵件。 上面的程式碼絕對不會讓你被解僱。但這會惹惱你團隊中的後端人員,他會為他的孩子閱讀設計模式書籍作為睡前故事。我們就叫他皮特吧。 皮特會抱怨什麼?他會要求你重寫該元件,如下所示。將管理功能提升到它自己的 useAdmin 掛鉤,並讓 useUser 掛鉤除了那些應該可供普通用戶使用的功能之外沒有其他功能。 ``` import { useState } from 'react' import { getUser, updateUser } from 'somewhere' // useUser does now only return the user, // without any function to update its email. const useUser = () => { const [user, setUser] = useState() useEffect(() => { const userInfo = getUser() setUser(userInfo) }, []) return { user } } // A new hook, useAdmin, extends useUser hook, // with the additional feature to update its email. const useAdmin = () => { const { user } = useUser() const updateEmail = (newEmail) => { if (user) { updateUser({ ...user, email: newEmail }) } else { console.error('Cannot update email') } } return { user, updateEmail } } ``` 皮特為什麼要求更新?因為那個無禮挑剔的混蛋皮特寧願希望你現在花時間重寫那個鉤子,然後明天回來進行新的程式碼審查,而不是將來可能需要用一個微小的新 if 語句更新程式碼,如果有的話成為另一種類型的使用者。 好吧,這是消極的說法...樂觀的說法是,使用這個新的useAdmin 掛鉤,當您打算實現僅影響管理員用戶的功能時,或者當您打算實現僅影響管理員用戶的功能時,您不必更改useUser 掛鉤中的任何內容。 當新增新的使用者類型或更新 useAdmin 掛鉤時,無需弄亂 useUser 掛鉤或更新其任何測試。這意味著,當您新增的使用者類型(例如假使用者)時,您不必意外地將錯誤傳送給普通使用者。相反,您只需加入一個新的 userFakeUser 鉤子,您的老闆就不會在周五晚上 9 點給您打電話,因為客戶在發薪週末會遇到銀行帳戶顯示虛假資料的問題。 ![床下的前端開發人員](https://www.perssondennis.com/images/articles/write-solid-react-hooks/frontend-developer-under-the-bed.webp) *皮特的兒子知道要小心義大利麵式程式碼開發人員* ### 為什麼在 React 中使用 OCP? 一個 React 專案應該有多少個 hooks 和元件是有爭議的。每一個都需要渲染效果圖的代價。 React 不是 Java,其中 22 種設計模式導致 422 個類別用於簡單的 TODO 清單實作。這就是狂野西部網絡 (www) 的魅力所在。 然而,開放/封閉原則顯然也是在 React 中使用的少數模式。上面的鉤子範例是最小的,鉤子沒有做太多事情。隨著更多實質的掛鉤和更大的專案,這項原則變得非常重要。 這可能會花費您一些額外的鉤子,並且需要稍長的時間來實現,但是您的鉤子將變得更加可擴展,這意味著您可以更頻繁地重複使用它們。您將不必經常重寫測試,從而使掛鉤更加牢固。最重要的是,如果您從不接觸舊程式碼,則不會在舊程式碼中產生錯誤。 ![沒有破損的東西不要碰](https://www.perssondennis.com/images/articles/write-solid-react-hooks/dont-touch-what-is-not-broken.webp) *天知道不要碰沒有破損的東西* {% 嵌入 https://dev.to/perssondennis/react-anti-patterns-and-best-practices-dos-and-donts-3c2g %} 里氏替換原理 (LSP) ------------ 啊啊,這個名字……誰是利斯科夫?而誰來代替她呢?而這個定義,難道就沒有意義嗎? ``` If S subtypes T, what holds for T holds for S ``` 這個原則顯然是關於繼承的,在 React 或 JavaScript 中,繼承的實踐並不像大多數後端語言中那麼多。 JavaScript 在 ES6 之前甚至沒有類,ES6 是[在 2015/2016 年左右引入的,](https://caniuse.com/?search=class)作為基於原型的繼承的語法糖。 考慮到這一點,該原則的用例實際上取決於您的程式碼的外觀。類似 Liskov 的原則在 React 中有意義,可能是: ``` If a hook/component accepts some props, all hooks and components ``` ``` which extends that hook/component must accept all the props the ``` ``` hook/component it extends accepts. The same goes for return values. ``` 為了說明這一點,我們可以看一下兩個儲存鉤子:useLocalStorage 和 useLocalAndRemoteStorage。 ``` import { useState } from 'react' import { getFromLocalStorage, saveToLocalStorage, getFromRemoteStorage } from 'somewhere' // useLocalStorage gets data from local storage. // When new data is stored, it calls saveToStorage callback. const useLocalStorage = ({ onDataSaved }) => { const [data, setData] = useState() useEffect(() => { const storageData = getFromLocalStorage() setData(storageData) }, []) const saveToStorage = (newData) => { saveToLocalStorage(newData) onDataSaved(newData) } return { data, saveToStorage } } // useLocalAndRemoteStorage gets data from local and remote storage. // I doesn't have callback to trigger when data is stored. const useLocalAndRemoteStorage = () => { const [localData, setLocalData] = useState() const [remoteData, setRemoteData] = useState() useEffect(() => { const storageData = getFromLocalStorage() setLocalData(storageData) }, []) useEffect(() => { const storageData = getFromRemoteStorage() setRemoteData(storageData) }, []) const saveToStorage = (newData) => { saveToLocalStorage(newData) } return { localData, remoteData, saveToStorage } } ``` 透過上面的鉤子,useLocalAndRemoteStorage 可以被視為 useLocalStorage 的子類型,因為它與 useLocalStorage 執行相同的操作(保存到本地存儲),而且還通過將資料保存到其他位置來擴展 useLocalStorage 的功能。 這兩個鉤子有一些共享的屬性和回傳值,但是 useLocalAndRemoteStorage 缺少 useLocalStorage 接受的 onDataSaved 回呼屬性。傳回屬性的名稱也有不同的命名,本地資料在useLocalStorage中命名為data,而在useLocalAndRemoteStorage中命名為localData。 如果你問利斯科夫,這就違背了她的原則。實際上,當她嘗試更新Web 應用程式以在伺服器端保留資料時,她會非常憤怒,只是意識到她不能簡單地用useLocalAndRemoteStorage 鉤子替換useLocalStorage,只是因為一些懶惰的開發人員從未為useLocalAndRemoteStorage 鉤子實現onDataSaved回調。 利斯科夫會痛苦地更新鉤子來支持這一點。同時,她也會更新 useLocalStorage 掛鉤中的本地資料名稱,以符合 useLocalAndRemoteStorage 中的本地資料名稱。 ``` import { useState } from 'react' import { getFromLocalStorage, saveToLocalStorage, getFromRemoteStorage } from 'somewhere' // Liskov has renamed data state variable to localData // to match the interface (variable name) of useLocalAndRemoteStorage. const useLocalStorage = ({ onDataSaved }) => { const [localData, setLocalData] = useState() useEffect(() => { const storageData = getFromLocalStorage() setLocalData(storageData) }, []) const saveToStorage = (newData) => { saveToLocalStorage(newData) onDataSaved(newData) } // This hook does now return "localData" instead of "data". return { localData, saveToStorage } } // Liskov also added onDataSaved callback to this hook, // to match the props interface of useLocalStorage. const useLocalAndRemoteStorage = ({ onDataSaved }) => { const [localData, setLocalData] = useState() const [remoteData, setRemoteData] = useState() useEffect(() => { const storageData = getFromLocalStorage() setLocalData(storageData) }, []) useEffect(() => { const storageData = getFromRemoteStorage() setRemoteData(storageData) }, []) const saveToStorage = (newData) => { saveToLocalStorage(newData) onDataSaved(newData) } return { localData, remoteData, saveToStorage } } ``` 透過為鉤子提供通用介面(傳入的 props、傳出的返回值),它們可以變得非常容易交換。如果我們遵循里氏替換原則,繼承另一個鉤子/元件的鉤子和元件應該可以用它繼承的鉤子或元件替換。 ![擔心的利斯科夫](https://www.perssondennis.com/images/articles/write-solid-react-hooks/worried-liskov.webp) *當開發人員不遵循她的原則時,利斯科夫感到失望* ### 為什麼在 React 中使用 LSP? 儘管繼承在 React 中並不是很突出,但它肯定在幕後使用。 Web 應用程式通常可以有幾個外觀相似的元件。文字、標題、連結、圖示連結等都是類似類型的元件,可以從繼承中受益。 IconLink 元件可能會也可能不會包裝 Link 元件。無論哪種方式,它們都會受益於使用相同的介面(使用相同的 props)實作。這樣,您可以隨時在應用程式中的任何位置將 Link 元件替換為 IconLink 元件,而無需編輯任何其他程式碼。 鉤子也是如此。 Web 應用程式從伺服器取得資料。他們也可能使用本地儲存或狀態管理系統。這些最好可以共享道具以使它們可以互換。 應用程式可能會從後端伺服器取得使用者、任務、產品或任何其他資料。類似的函數也可以共享接口,從而更容易重複使用程式碼和測試。 {% 嵌入 https://dev.to/perssondennis/the-20-most-common-use-cases-for-javascript-arrays-2j8j %} 介面隔離原則(ISP) ----------- 另一個更明確的原則是介面隔離原則。定義很短。 ``` No code should be forced to depend on methods it does not use ``` 顧名思義,它與介面有關,基本上意味著函數和類別應該只實現它明確使用的介面。最容易實現這一點的方法是保持介面整潔,讓類別選擇其中的一些來實現,而不是被迫用它不關心的幾種方法來實現一個大介面。 例如,代表擁有網站的人的類別應該實現兩個接口,一個稱為 Person 的接口,描述有關此人的詳細訊息,另一個用於網站的接口,其中包含有關其擁有的網站的元資料。 ``` interface Person { firstname: string familyName: string age: number } interface Website { domain: string type: string } ``` 如果相反,建立一個單一介面網站,包括有關所有者和網站的訊息,則將違反介面隔離原則。 ``` interface Website { ownerFirstname: string ownerFamilyName: number domain: string type: string } ``` 你可能會想,上面的介面有什麼問題嗎?它的問題是它使介面不太可用。想想看,如果公司不是人,而是公司,你會怎麼做?公司其實沒有姓氏。然後您會修改介面以使其對人類和公司都可用嗎?或者您會建立一個新介面 CompanyOwnedWebsite 嗎? 然後,您最終會得到一個具有許多可選屬性的接口,或分別稱為 PersonWebsite 和 CompanyWebsite 的兩個接口。這些解決方案都不是最佳的。 ``` // Alternative 1 // This interface has the problem that it includes // optional attributes, even though the attributes // are mandatory for some consumers of the interface. interface Website { companyName?: string ownerFirstname?: string ownerFamilyName?: number domain: string type: string } // Alternative 2 // This is the original Website interface renamed for a person. // Which means, we had to update old code and tests and // potentially introduce some bugs. interface PersonWebsite { ownerFirstname: string ownerFamilyName: number domain: string type: string } // This is a new interface to work for a company. interface CompanyOwnedWebsite { companyName: string domain: string type: string } ``` ISP 遵循的解決方案如下所示。 ``` interface Person { firstname: string familyName: string age: number } interface Company { companyName: string } interface Website { domain: string type: string } ``` 透過上述適當的接口,代表公司網站的類別可以實現接口 Company 和 Website,但不需要考慮 Person 接口中的 firstname 和 familyName 屬性。 ### React 中使用 ISP 嗎? 所以,這個原則顯然適用於接口,這意味著它只應該在您使用 TypeScript 編寫 React 程式碼時才有意義,不是嗎? 當然不是!不輸入介面並不意味著它們不存在。到處都有,只是你沒有明確地輸入它們。 在 React 中,每個元件和鉤子都有兩個主要接口,輸入和輸出。 ``` // The input interface to a hook is its props. const useMyHook = ({ prop1, prop2 }) => { // ... // The output interface of a hook is its return values. return { value1, value2, callback1 } } ``` 使用 TypeScript,您通常會鍵入輸入接口,但輸出接口通常會被跳過,因為它是可選的。 ``` // Input interface. interface MyHookProps { prop1: string prop2: number } // Output interface. interface MyHookOutput { value1: string value2: number callback1: () => void } const useMyHook = ({ prop1, prop2 }: MyHookProps): MyHookOutput => { // ... return { value1, value2, callback1 } } ``` 如果鉤子不會將 prop2 用於任何用途,那麼它不應該成為其 props 的一部分。對於單一道具,可以輕鬆地將其從道具清單和介面中刪除。但是,如果 prop2 是物件類型,例如上一章不正確的 Website 介面範例,該怎麼辦? ``` interface Website { companyName?: string ownerFirstname?: string ownerFamilyName?: number domain: string type: string } interface MyHookProps { prop1: string website: Website } const useMyCompanyWebsite = ({ prop1, website }: MyHookProps) => { // This hook uses domain, type and companyName, // but not ownerFirstname or ownerFamilyName. return { value1, value2, callback1 } } ``` 現在我們有一個 useMyCompanyWebsite 鉤子,它有一個 website 屬性。如果鉤子中使用了網站介面的部分內容,我們不能簡單地刪除整個網站道具。我們必須保留 website 屬性,因此也保留ownerFirstname 和ownerFamiliyName 的介面屬性。這也意味著,該針對公司的掛鉤可以由人類擁有的網站所有者使用,即使該掛鉤可能不適用於該用途。 ### 為什麼在 React 中使用 ISP? 我們現在已經了解了 ISP 的含義,以及它如何應用於 React,即使不使用 TypeScript。透過查看上面的小例子,我們也看到了一些不遵循 ISP 的問題。 在更複雜的專案中,可讀性是最重要的。介面隔離原則的目的之一是避免混亂,避免不必要的程式碼的存在,這些程式碼只會破壞可讀性。不要忘記可測試性。您是否應該關心您實際未使用的道具的測試覆蓋率? 實現大型介面也迫使您將 props 設定為可選。導致更多的 if 語句來檢查函數的存在和潛在的誤用,因為在介面上顯示該函數將處理此類屬性。 {% 嵌入 https://dev.to/perssondennis/answers-to-common-nextjs-questions-1oki %} 依賴倒置原則(DIP) ----------- 最後一個原則,即 DIP,包括一些被廣泛誤解的術語。令人困惑的地方在於依賴反轉、依賴注入和控制反轉之間的差異。所以我們先聲明一下。 **依賴倒置** 依賴倒置原則(DIP)表示高階模組不應該從低階模組導入任何內容,兩者都應該依賴抽象。這意味著任何高階模組自然可能依賴它所使用的模組的實作細節,但不應該具有這種依賴性。 高級模組和低階模組的編寫方式應使它們都可以在不了解其他模組內部實現的任何細節的情況下使用。只要介面保持不變,每個模組都應該可以用它的替代實作來替換。 **控制反轉** 控制反轉(IoC)是用來解決依賴反轉問題的原理。它指出模組的依賴關係應由外部實體或框架提供。這樣,模組本身只需使用依賴項,而不必建立依賴項或以任何方式管理它。 **依賴注入** 依賴注入(DI)是實現 IoC 的常見方法。它透過建構函數或 setter 方法注入模組來提供對模組的依賴關係。這樣,模組就可以使用依賴項而無需負責建立它,這符合 IoC 原則。值得一提的是,依賴注入並不是實現控制反轉的唯一方法。 ### React 中使用 DIP 嗎? 澄清了這些術語,並知道 DIP 原則是關於依賴倒置的,我們可以再次看看這個定義是怎樣的。 ``` High-level modules should not import anything from low-level modules. ``` ``` Both should depend on abstractions ``` 這如何適用於 React? React 不是一個通常與依賴注入相關的函式庫,那我們該如何解決依賴倒置的問題呢? 這個問題最常見的解決方案是鉤子。鉤子不能算作依賴注入,因為它們被硬編碼到元件中,並且不可能在不更改元件實現的情況下用另一個鉤子替換鉤子。相同的鉤子將在那裡,使用相同的鉤子實例,直到開發人員更新程式碼。 但請記住,依賴注入並不是實現依賴倒置的唯一方法。 Hooks 可以被視為 React 元件的外部依賴,它有一個介面(它的 props),可以抽像出 hook 中的程式碼。這樣,鉤子就實現了依賴倒置的原則,因為元件依賴抽象接口,而不需要知道有關鉤子的任何細節。 React 中 DIP 的另一個更直觀的實作(實際上使用依賴注入)是 HOC 和上下文的使用。請參閱下面的 withAuth HOC。 ``` const withAuth = (Component) => { return (props) => { const { user } = useContext(AuthContext) if (!user) { return <LoginComponent> } return <Component {...props} user={user} /> } } const Profile = () => { // Profile component... } // Use the withAuth HOC to inject user to Profile component. const ProfileWithAuth = withAuth(Profile) ``` 上面顯示的 withAuth HOC 使用依賴項注入為使用者提供 Profile 元件。這個範例的有趣之處在於,它不僅顯示了依賴注入的一種用法,而且實際上包含了兩個依賴注入。 將使用者註入到設定檔元件並不是此範例中的唯一注入。 withAuth 鉤子實際上也透過 useContext 鉤子透過依賴注入來獲取使用者。在程式碼中的某個地方,有人聲明了一個將使用者註入上下文的提供者。該用戶實例甚至可以在執行時透過更新上下文中的用戶來更改。 ### 為什麼在 React 中使用 DIP? 儘管依賴注入不是與 React 相關的常見模式,但它實際上與 HOC 和上下文相關。鉤子從 HOC 和上下文中佔據了大量市場份額,也很好地證實了依賴倒置原則。 因此,DIP 已經內建到 React 庫本身中,當然應該使用。它既易於使用,又具有模組之間的鬆散耦合、鉤子和元件的可重複使用性和可測試性等優點。它也使得實現其他設計模式(例如單一職責原則)變得更加容易。 我不鼓勵的是,當確實有更簡單的解決方案可用時,請嘗試實施智慧解決方案並過度使用該模式。我在網路和書籍中看到了使用 React 上下文的建議,其唯一目的是實現依賴注入。像下面這樣的東西。 ``` const User = () => { const { role } = useContext(RoleContext) return <div>{`User has role ${role}`}</div> } const AdminUser = ({ children }) => { return ( <RoleContext.Provider value={{ role: 'admin' }}> {children} </RoleContext.Provider> ) } const NormalUser = ({ children }) => { return ( <RoleContext.Provider value={{ role: 'normal' }}> {children} </RoleContext.Provider> ) } ``` 儘管上面的範例確實將角色注入到 User 元件中,但為其使用上下文純粹是矯枉過正。當上下文本身有其用途時,應該在適當的時候使用 React 上下文。在這種情況下,一個簡單的道具可能是更好的解決方案。 ``` const User = ({ role }) => { return <div>{`User has role ${role}`}</div> } const AdminUser = () => <User role='admin' /> const NormalUser = () => <User role='normal' /> ``` {% cta https://2e015922.sibforms.com/serve/MUIFAGF3ypa0p6D6nTWI0MHVOIAC7q4TIJd0yXAhiBC9CswkNPnOlQBzeqSbR2XFM95gUn2G1IxWwCpDpDjkjk aaG9tz9UYhn\_O\_dWg1PPGS8kRM5ROREaJsslnGD8WEHszzZr0geJ9-g7lGsbn\_hTT-wZSKWa1C8ay4Ok85ozro %}訂閱我的文章{% endcta %} {% 嵌入 https://dev.to/perssondennis %} --- 原文出處:https://dev.to/perssondennis/write-solid-react-hooks-436o

如何建置:具有嵌入式 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

關於 laravel 多語系 model 的小研究

在研究 TwillCMS 的時候 發現他的多語系 model 功能很好用 比方說 `posts` 的資料表 可以建立 `post_translations` 資料表 然後多語系資料存裡面 就能用一般存取 eloquent attribute 的方式取得翻譯 這是怎麼做到的呢? --- 首先 使用多語的 model 要先使用 Trait `A17\Twill\Models\Behaviors\HasTranslation` 然後要在 `$translatedAttributes` 裡面設定好 查看那個 Trait 的原始碼 會發現要使用 另一個 Trait `Astrotomic\Translatable\Translatable` 原來,底層其實用到了另一個套件 https://github.com/Astrotomic/laravel-translatable --- 繼續翻閱那個套件的原始碼 `Astrotomic\Translatable\Translatable` 會看到 `public function getAttribute` 實際上的功能,就是實作在這邊! 因為在 laravel eloquent 的原始碼 `Illuminate\Database\Eloquent\Model` 有下面這段 ``` public function __get($key) { return $this->getAttribute($key); } ``` 也就是讀取 model attribute 時,其實有經過 php magic function `__get()` 所以 `Translatable` 這個 Trait 就是擴充了這個函數 ``` public function getAttribute($key) { [$attribute, $locale] = $this->getAttributeAndLocale($key); if ($this->isTranslationAttribute($attribute)) { if ($this->getTranslation($locale) === null) { return $this->getAttributeValue($attribute); } // 這邊省略 } return parent::getAttribute($key); } ``` 然後會去呼叫 `public function getTranslation` 這樣,答案揭曉! --- 題外話,翻閱 eloquent model source code 的時候 我發現 `getAttribute` 不是直接寫在 model 內 而是在 `Illuminate\Database\Eloquent\Concerns\HasAttributes` 裡面 eloquent 因為功能太豐富 所以打散在 `Concerns` 資料夾內 其實就是 Traits 的意思 只是因為 `Seperation of Concerns` 原則太有名 所以用 Concerns 來命名 其實也有道理 就好像我們不會把放了一堆類別的資料夾 命名為 `Classes` 這樣 在 laravel source code 放了一堆 interface 的資料夾也是命名為 `Contracts` 的

建立多個 Twill CMS 模組,並且整理後台導覽列

再來新增一個模組試試看,假設官網要顯示各地辦公室的資訊 來建立辦公室模組,這次選「不用支援多語」 ``` php artisan twill:make:module offices ``` 其餘參數都按照教材輸入 https://twillcms.com/guides/page-builder-with-blade/creating-the-page-module.html --- 完成之後,看一下 migration 檔案 會發現少了 `xxx_translations` 那張 table 然後 title 跟 description 出現在 offices 資料表,果然跟之前猜測的一樣! --- 同樣在 AppServiceProdiver 加入 ``` TwillNavigation::addLink( NavigationLink::make()->forModule('offices') ); ``` 就可以在後台管理檔案了 --- 接著修改 routes/web.php ``` Route::get('offices/{slug}', [\App\Http\Controllers\OfficeDisplayController::class, 'show'])->name('frontend.office'); ``` 並且新增控制器 ``` <?php namespace App\Http\Controllers; use App\Repositories\OfficeRepository; use Illuminate\Contracts\View\View; class OfficeDisplayController extends Controller { public function show(string $slug, OfficeRepository $officeRepository): View { $office = $officeRepository->forSlug($slug); if (! $office) { abort(404); } return view('site.office', ['item' => $office]); } } ``` 就可以在前台查看網頁囉! --- 目前後台的導覽列,有 `Pages` 跟 `Offices` 兩個項目 假設我們想整理一下後台導覽列,把 pages 跟 offices 都視為「About」的資訊,該怎麼做呢? https://twillcms.com/docs/getting-started/navigation.html 我先試試看只更新 routes,不更新 AppServiceProvider 這樣搞,應該會讓後台壞掉 ``` // TwillRoutes::module('pages'); // TwillRoutes::module('offices'); Route::group(['prefix' => 'about'], function () { TwillRoutes::module('pages'); TwillRoutes::module('offices'); }); ``` 結果打開後台面板,居然沒有故障! 倒是兩個模組,網址前面真的多了 `/about` 所以 `TwillRoutes` 不只是對應網址&控制器的功能 更是會根據 route group 前綴,去更新對應,太神奇了,不知道怎麼寫的? --- 現在後台導覽只有網址改變,導覽列 UI 結構沒變,還沒完成 我要讓導覽列只剩下 About,然後有次級導覽列 About -> Pages About -> Offices 我在官網找不到設定方法 https://twillcms.com/docs/getting-started/navigation.html 但是翻閱原始碼之後 https://github.com/area17/twill/blob/3.x/src/View/Components/Navigation/NavigationLink.php 會發現這樣寫即可 ``` public function boot(): void { // TwillNavigation::addLink( // NavigationLink::make()->forModule('pages') // ); // TwillNavigation::addLink( // NavigationLink::make()->forModule('offices') // ); TwillNavigation::addLink( NavigationLink::make()->forModule('pages') ->title('About') ->doNotAddSelfAsFirstChild() ->setChildren([ NavigationLink::make()->forModule('pages'), NavigationLink::make()->forModule('offices'), ]) ); } ``` 乍看之下,好像有點複雜,但實際測試一下相關函數的行為,會發現妙不可言! 這種設計給予了工程師極大的自由,可以把導覽列整理成各種樣子! 我真心認為寫出相關類別的工程師,具有非常深厚的功底、經驗! --- 相關 commit 1 https://github.com/howtomakeaturn/twill-play/commit/87849d8963b10412ccb7f3eaa46204f3202e6c08 相關 commit 2 https://github.com/howtomakeaturn/twill-play/commit/1e441eb81bb24642058d1826fc5b77a3d3873545

2024 年 7 個最適合開發者的圖表庫 🤯

許多應用程式使用圖表或圖形進行資料視覺化,這可以使用庫或底層技術來實現。庫提供時間效率、各種圖表類型和自訂選項。 作為開發人員,了解這些選項至關重要。雖然有些專案可能選擇手動實施,但許多專案都受益於圖表庫的效率。使用這些庫可以節省時間,並可以存取各種圖表類型和樣式選項來滿足您的需求。 在本文中,我們將踏上旅程,了解七個令人驚嘆的圖表庫、它們的功能以及如何使用它們。讓我們來看看! ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2Fqk7c7favj05g2qjice9m.gif](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqk7c7favj05g2qjice9m.gif) --- 1. Latitude for React:將圖表整合到前端應用程式的完美伴侶 --------------------------------------- **網址: <https://docs.latitude.so/react/getting-started/introduction>** ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2Fy7z7ks2yp21vj6fah42o.png](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7z7ks2yp21vj6fah42o.png) *是的,那就是我們!* Latitude 是用於嵌入式分析的開源框架,可讓您快速將 SQL 查詢公開為 API 端點。他們的產品中包括`@latitude-data/react` ,這是一組原生 React 元件,用於輕鬆繪製來自 Latitude API 的資料。如果您選擇攜帶自己的資料,它還可以用作獨立的圖表庫。 ### Latitude for React 的主要特性 - **多種圖表類型**:提供多種圖表類型顯示。例如長條圖、圓餅圖、面積圖、分散圖、混合圖等。 - **高度可自訂的元件**:元件預先捆綁了一組可用主題,或者您可以選擇輕鬆建立自己的自訂主題。最重要的是,大多數元件都可以使用自訂 html 類別進行擴充。 - **自動執行查詢或提供您自己的資料**:Latitude 為每個圖表元件提供兩個版本。其中一個版本會自動執行您的緯度查詢。另一個版本期望接收資料作為道具,讓使用者負責取得它。 ### 如何使用 Latitude for React 👀 首先,在您的 React 專案中安裝 Latitude 的 React 套件: ``` npm install --save @latitude-data/react ``` 之後,匯入 Latitude 元件所使用的核心樣式: ``` import '@latitude-data/react/dist/index.css'; ``` **帶有緯度後端** 如果您要從 Latitude 後端取得資料,**請**在專案根目錄下使用 LatitudeProvider 包裝您的應用程式: ``` import { LatitudeProvider } from '@latitude-data/react'; function App() { return ( <LatitudeProviderapiConfig={{ host: <YOUR_LATITUDE_API_HOST> }}> {/* Your app content */} </LatitudeProvider>); } ``` 並使用 Latitude 的任何帶有`Query`前綴的圖表元件: ``` import { QueryLineChart } from '@latitude-data/react'; function MyComponent() { return ( <QueryLineChart queryPath='titles' params={{ start_year: 2012, end_year: 2014 }} x='release_year'y={[ { name: 'count_shows' }, { name: 'count_movies' } ]} xTitle='Year' yTitle='Titles'/> ); } ``` 在此範例中,queryPath 對應於 Latitude 後端中定義的查詢的路徑。 **作為獨立的圖表庫** 只需使用獨立元件,例如: ``` import { LineChart } from '@latitude-data/react'; function MyComponent() { const data = [ { release_year: 2010, count_shows: 10, count_movies: 2 } ] return ( <LineChart data={data} x='release_year' y={[ { name: 'count_shows' }, { name: 'count_movies' } ]} /> ); } ``` 請閱讀[文件](https://docs.latitude.so/react/getting-started/introduction),以取得有關如何使用緯度動態顯示資料的更深入指南。 您可以隨時在 GitHub 上為我們的專案做出貢獻,如果您能為我們的專案給我們一顆星,我們將不勝感激! **[🌟 GitHub 上 React 的 Star Latitude](https://github.com/latitude-dev/latitude)** --- 2. D3.js:用於資料視覺化的JavaScript函式庫 ------------------------------ **D3 的網站: [https://d3js.org](https://d3js.org/)** ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2F1xhnyf40jvg3v50c2ba8.jpeg](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xhnyf40jvg3v50c2ba8.jpeg) D3 是一個低階圖表庫,提供無與倫比的客製化和靈活性。代價是學習曲線陡峭,並且與 React 或 Svelte 等常見前端框架的整合更加困難。如果您有一些非常具體的圖表需求,d3.js 可能是您唯一的選擇,但是,如果您希望在應用程式中快速整合一些簡單的圖表,那麼其他地方還有更好的選擇。 ### D3.js 的主要特性 - **自訂和靈活性:** D3.js 提供對 SVG、HTML 和 CSS 的低階存取,允許對視覺元素進行細粒度控制。這使得它具有高度的靈活性和可自訂性。 - **模組化:** D3.js 高度模組化,各種模組可以單獨使用,也可以一起使用。這允許對捆綁包大小和效能最佳化進行更多控制。 - **強大的轉換和動畫:** D3.js 為轉換和動畫提供強大的支持,為資料更新提供流暢而複雜的動畫。 - **社區和生態系統:** D3.js 擁有一個龐大且活躍的社區,提供大量文件、範例和插件。這使得您可以更輕鬆地找到支援和資源。 ### 如何使用 D3.js 👀 在您的專案中安裝 d3 的 npm 套件: ``` npm install d3 ``` 以下是有關如何將圖表新增至 id 為`#chart`的 html 元件的範例程式碼: ``` import * as d3 from 'd3'; // Data for the bar chart const data = [ { name: 'A', value: 30 }, { name: 'B', value: 80 }, { name: 'C', value: 45 }, { name: 'D', value: 60 }, { name: 'E', value: 20 }, { name: 'F', value: 90 }, { name: 'G', value: 55 } ]; // Set the dimensions and margins of the graph const margin = { top: 20, right: 30, bottom: 40, left: 40 }; const width = 500 - margin.left - margin.right; const height = 300 - margin.top - margin.bottom; // Append the svg object to the body of the page const svg = d3.select("#chart") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); // X axis const x = d3.scaleBand() .range([0, width]) .domain(data.map(d => d.name)) .padding(0.1); svg.append("g") .attr("transform", `translate(0,${height})`) .call(d3.axisBottom(x)) .selectAll("text") .attr("class", "axis-label") .attr("transform", "translate(-10,0)rotate(-45)") .style("text-anchor", "end"); // Y axis const y = d3.scaleLinear() .domain([0, d3.max(data, d => d.value)]) .nice() .range([height, 0]); svg.append("g") .call(d3.axisLeft(y)) .selectAll("text") .attr("class", "axis-label"); // Bars svg.selectAll(".bar") .data(data) .enter() .append("rect") .attr("class", "bar") .attr("x", d => x(d.name)) .attr("y", d => y(d.value)) .attr("width", x.bandwidth()) .attr("height", d => height - y(d.value)); ``` 正如您所看到的,D3.js 通常比其他替代方案更冗長,但它使您可以最大程度地控制可視化的各個方面。 D3 是免費且開源的。如果您覺得該專案很有趣,請在 GitHub 上給他們一個 Star ⭐️!他們目前的星數超過 108k! **[在 GitHub 上關注 D3.js ⭐️](https://github.com/d3/d3)** --- 3. Chart.js:用於基於 HTML 的圖表的靈活 JavaScript 程式庫 ------------------------------------------- **網址: [https://www.chartjs.org](https://www.chartjs.org/)** ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2F0m8eb9o6t93wwf7cvq7x.png](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0m8eb9o6t93wwf7cvq7x.png) 目前是業界最受歡迎的圖表庫之一,也是最容易使用的圖表庫之一。主要因其透過使用 Canvas 進行呈現而聞名,這與某些僅將資料呈現為 SVG 的圖表庫不同。 ### Chart.js 的主要特性 - **效能**:Chart.js 可以接受內部資料結構,從而減少解析和規範化的需要,從而提高效能。您還可以配置資料抽取,以在渲染資料集之前減少資料集的大小,從而加快渲染速度。 canvas的使用壓縮了DOM樹上的開銷;這有助於最大限度地減少捆綁包中 Chart.js 程式碼的大小,從而加快載入時間。 - **可存取性**:與大多數圖表庫相比,Chart.js 具有非常直接的方法,充分了解您所做的一切都在畫布上執行。您不需要複雜的自訂,因為它允許您使用其任何自訂選項來設定樣式、主題等。 - **出色的開發人員體驗**:除了擁有非常出色的社群影響力之外,Chart.js 還擁有簡單明了的文件,當出現新內容時,這些文件會經常更新。它還提供與您最喜歡的 JavaScript 框架的集成,例如 React、Svelte 等。 - **圖表響應能力**:預設情況下,Chart.js 提供響應式圖表。您可以自動將螢幕調整為不同的尺寸,並且它為圖表提供了可調整的尺寸。它確保您在所有裝置上都能獲得良好的資料視覺化效果。 ### 如何使用 Chart.js 👀 Chartjs 可以透過 npm 安裝或在執行時透過 CDN 直接下載: ``` <div> <canvas id="myChart"></canvas> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> const ctx = document.getElementById('myChart'); new Chart(ctx, { type: 'bar', data: { labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], datasets: [{ label: '# of Votes', data: [12, 19, 3, 5, 2, 3], borderWidth: 1 }] }, options: { scales: { y: { beginAtZero: true } } } }); </script> ``` 如果你覺得這個專案很酷,你可以在GitHub上給它一個Star⭐️!他們目前的星數超過 63k。 **[GitHub 上的 Star Chart.js ⭐️](https://github.com/chartjs/Chart.js)** --- 4. Apache ECharts:用於快速建立圖表的JavaScript視覺化工具 ------------------------------------------ **網址: [https://echarts.apache.org](https://echarts.apache.org/)** ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2Fce12dq55x4le8e4ezhvm.png](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce12dq55x4le8e4ezhvm.png) ECharts 是一個開源資料視覺化函式庫,最近越來越受歡迎。它由流行的[Apache](https://apache.org/)基金會建置,並依賴[ZRender](https://github.com/ecomfe/zrender)來呈現其圖形。與其他相比,它更複雜;它僅呈現動態資料並具有不同的顯示方式。 ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2Fnhnpwl5uuhhu7xxdp954.gif](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhnpwl5uuhhu7xxdp954.gif) ### ECharts的主要特點 - **廣泛的圖表類型**:ECharts 支援大量的圖表類型,實際上比大多數其他競爭對手更支援更多。其中包括長條圖、折線圖、圓餅圖、散佈圖、地圖、雷達圖、箱線圖、熱圖、平行座標、桑基圖等。這種多功能性允許用戶以多種格式視覺化資料。 - **互動性:** ECharts的突出特點之一是其豐富的互動性。它包括對互動式元素的內建支持,例如工具提示、縮放、平移和資料突出顯示。用戶可以與視覺化進行交互,以更深入地了解資料。 - **效能:** ECharts 專為高效能而設計,能夠有效處理大型資料集。它利用 Canvas 和 WebGL 進行渲染,這比傳統的基於 SVG 的渲染效能更高,適用於複雜的視覺化和大資料量。 ### 如何開始使用 Apache ECharts 👀 使用 npm 安裝 echarts: ``` npm install echarts ``` 以下程式碼片段在 id 為`#chart`的 DOM 元素中建立了一個簡單的長條圖: ``` // Import ECharts import * as echarts from 'echarts'; // Initialize the chart var chartDom = document.getElementById('chart'); var myChart = echarts.init(chartDom); var option; // Specify the chart configuration option = { title: { text: 'Simple Bar Chart' }, tooltip: {}, xAxis: { data: ['A', 'B', 'C', 'D', 'E', 'F'] }, yAxis: {}, series: [ { name: 'Value', type: 'bar', data: [5, 20, 36, 10, 10, 20] } ] }; // Use the specified chart configuration option && myChart.setOption(option); ``` 如果你覺得這個專案很有趣,可以在GitHub上給它一個Star⭐️,目前他們的Star數已經接近60k了! **[GitHub 上的 Star ECharts ⭐️](https://github.com/apache/echarts)** --- 5. Nivo:用於在 React 中建立圖表應用程式的庫 ----------------------------- **網址: [https://nivo.rocks](https://nivo.rocks/)** ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2F2k97ihdlfubm0zs5q1rm.png](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2k97ihdlfubm0zs5q1rm.png) Nivo 是一個高階開源 JavaScript 函式庫,用於建立資料視覺化,專門設計用於與 React 搭配使用。它建置在 D3 之上,提供廣泛的圖表類型,並具有內建主題、互動性和響應式設計。以下是一些關鍵功能和方面,使 Nivo 成為 React 應用程式中資料視覺化的熱門選擇: ### Nivo 的主要特點 - **易於使用:** Nivo 的設計比 D3.js 等替代方案更用戶友好且更易於使用。它提供了更高層次的抽象,使得無需深入了解 SVG 或 DOM 操作即可輕鬆建立複雜的圖表。 - **React 整合:** Nivo 專為 React 建置,使其成為在 React 生態系統中工作的開發人員的絕佳選擇。它利用 React 基於元件的架構來輕鬆整合和重複使用。 - **主題和響應性:** Nivo 包含對主題和響應式設計的內建支持,使您可以更輕鬆地建立在不同設備上看起來不錯並與應用程式的設計相匹配的圖表。 ### 如何開始使用 Nivo 👀 如果您是 React 開發人員,開始使用 Nivo 會很容易。安裝 nivo 核心套件以及您選擇的相關圖表特定庫。在本例中,我們正在實作一個長條圖: ``` yarn add @nivo/core @nivo/bar ``` 完成後,匯入相關圖表元件並在您的 React 環境中使用它: ``` import { Bar } from '@nivo/bar'; const MyBarChart = () => { const salesData = [ { "category": "Electronics", "value": 5000 }, { "category": "Clothing", "value": 3000 }, { "category": "Furniture", "value": 2500 } ]; return ( <Bar data={salesData} indexBy="category" maxValue={6000} keyBy="id"/> ); }; export default App; ``` Nivo 是開源的,您可以在 GitHub 上為他們的專案加註星標 - 目前它們的星標數量已超過 12k! **[⭐️ GitHub 上的 Star Nivo](https://github.com/plouc/nivo)** --- 6. Plotly:適用於不同技術堆疊的開源圖表庫 ------------------------- **網址: [https://plotly.com](https://plotly.com/)** ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2Fdxm6bnd8u9e4wxxjv3hh.png](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxm6bnd8u9e4wxxjv3hh.png) **Plotly**是一個開源資料視覺化函式庫,支援多種圖表類型和互動功能。它可用於多種程式語言,包括 Python、R 和 JavaScript。 Plotly.js 是該函式庫的 JavaScript 版本,建構在 D3 之上,廣泛用於建立基於 Web 的互動式視覺化。 ### Plotly 的主要特點 - **支援不同的程式語言**:Plotly 支援不同的語言,這與許多其他支援一種語言的圖表庫不同。它允許您使用任何您熟悉的語言將資料視覺化為圖表。這是[完整的清單](https://plotly.com/graphing-libraries/)。 - **複雜圖表類型:** Plotly 抽象化了 matplotlib、ggplot2 或 MATLAB 等軟體包中的統計和科學圖表類型。 - **可移植:** Plotly 圖表以 JSON 物件的形式進行聲明性描述。圖表的每個面向(例如顏色、網格線和圖例)都有一組對應的 JSON 屬性。這允許在所有不同的語言實作中使用相同的配置。 - **效能:** Plotly 的圖表主要使用 SVG,但它也可以利用 webGL 來渲染高效能視覺化。 ### 如何開始使用 Plotly 👀 如前所述,Plotly 可以與多種程式語言一起使用,但在本例中我們將專注於 Javascript。 首先,安裝相關的npm套件: ``` npm install plotly.js-dist ``` 然後,在 html 中建立一個空的 div 元素來繪製圖形: ``` <div id="tester" style="width:600px;height:250px;"></div> ``` 最後,編寫以下內容來繪製簡單的折線圖: ``` const TESTER = document.getElementById('tester'); Plotly.newPlot( TESTER, [{ x: [1, 2, 3, 4, 5], y: [1, 2, 4, 8, 16] }], { margin: { t: 0 } } ); ``` 如果您發現 Plotly 是一個很好的工具,您可以給它一顆星,並為他們在 GitHub 上的專案做出貢獻。 - **[GitHub 上的 Star Plotly Python 專案⭐️](https://github.com/plotly/plotly.py/)** --- 7.勝利:用於圖表和資料可視化的React元件 ----------------------- **網址: [https://commerce.nearform.com/open-source/victory](https://commerce.nearform.com/open-source/victory/)** ![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3 .amazonaws.com%2Fuploads%2Farticles%2Fy1hhhtiq4cbkwnlzusin.png](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1hhhtiq4cbkwnlzusin.png) **Victory**是一個適用於 React 和 React Native 的開源模組化圖表庫。它提供了一個簡單而優雅的 API,用於建立各種資料視覺化。與 Nivo 類似,Victory 利用了 React 的優勢,使其成為已經熟悉 React 生態系統的開發人員的自然選擇。 ### Victory JS 的主要特點 - **React 和 React Native 具有相同的 API** :Victory 專為 React 和 React Native 建置,允許無縫整合到 Web 和行動應用程式中。 - 易於使用:Victory 的聲明式、基於元件的 API 使得向 React 應用程式加入視覺化變得簡單,特別是與 D3 等其他命令式方法相比。 - **互動性**:Victory 提供開箱即用的工具提示、對事件和複雜動畫的支持,以使視覺化感覺更加生動。 ### 如何開始勝利 首先,安裝 npm 套件: ``` npm install victory ``` 然後,根據您的需求匯入您想要使用的相關元件。例如: ``` import React from 'react'; import { VictoryBar } from 'victory'; const data = [ {quarter: 1, earnings: 13000}, {quarter: 2, earnings: 16500}, {quarter: 3, earnings: 14250}, {quarter: 4, earnings: 19000} ] function App() { return ( <VictoryBar data={data} // data accessor for x values x="quarter" // data accessor for y values y="earnings" /> } ``` 如果您覺得 Victory 很酷,您可以繼續在 GitHub 上給該專案一顆星。目前他們的星數超過 10,000 **[GitHub 上的明星勝利 ⭐️](https://github.com/FormidableLabs/victory)** --- 結論 -- 恭喜您已經走到這一步了! 在本文中,我們介紹了今年要探索的 7 個圖表庫。我們已經超越了簡單的概述,而是發現了每個庫的工作原理、其底層架構以及用於建置它的技術。此外,我們還學習如何開始將這些庫合併到您的專案中。 在我們分道揚鑣之前,如果您認為本文有幫助,請考慮在[GitHub](https://github.com/latitude-dev/latitude)上給我們一顆星來表達您的支持。感謝您的閱讀,我期待您參與下一篇文章! 👋 --- 原文出處:https://dev.to/latitude/7-best-chart-libraries-for-developers-in-2024-25he

使用 Twill CMS 模組建立前台頁面

已經在後台建立資料了,我想要立刻做出前台頁面看看 所以官方教材我跳到 https://twillcms.com/guides/page-builder-with-blade/building-a-front-end.html 建立前台控制器 ``` php artisan make:controller PageDisplayController ``` 注意這是 laravel 原生指令喔 跟 twill 無關 在裡面放入 ``` <?php namespace App\Http\Controllers; use App\Repositories\PageRepository; use Illuminate\Contracts\View\View; class PageDisplayController extends Controller { public function show(string $slug, PageRepository $pageRepository): View { $page = $pageRepository->forSlug($slug); if (! $page) { abort(404); } return view('site.page', ['item' => $page]); } } ``` 然後在 routes/web.php 放入 ``` Route::get('pages/{slug}', [\App\Http\Controllers\PageDisplayController::class, 'show'])->name('frontend.page'); ``` 這時打開網址 http://twill-play.local/pages/my-1st 會看到顯示一個陽春的網頁了! 算是成功建立前台! 開發體驗也很好,基本上就是開發 laravel app 的感覺,只是用了 twill 核心在輔助! 而不是在開發 twill app,感覺好像在學一套新框架的感覺! --- 打開 twill 後台 會看到後台顯示網址依然是 http://twill-play.local/en/pages/my-1st 來把 i18n 先關掉吧 可以在 PageController 加入 ``` protected function setUpController(): void { $this->withoutLanguageInPermalink(); } ``` 即可修正這個問題! 這次的 commit 內容在這邊,可以看看 https://github.com/howtomakeaturn/twill-play/commit/989e5e357439e53092130dc2a4eed7befaa6c550 真的感覺就像在開發普通的 laravel app! --- 如果不希望前台網址有 `/pages/` 這樣的字串 可以在 routes/web.php 直接拿掉 後台的部份,可以在 PageController 加入 ``` $this->setPermalinkBase(''); ``` 來修正顯示網址的問題 真的很酷!

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

人工智慧時代已經來臨。身為開發者,要想取得成功,在你的投資組合中加入一些人工智慧驅動的專案是很棒的。 今天,我們將建立一個由人工智慧驅動的部落格平台,該平台具有一些很棒的功能,例如研究、自動完成和 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

AI 驅動的前端 UI 元件產生器(Next.js、GPT4、Langchain 和 CopilotKit)

**長話短說** -------- 在本文中,您將了解如何建立由 AI 驅動的前端 UI 元件產生器,該產生器使您能夠透過實作教學產生 Next.js Tailwind CSS UI 元件。 我們將介紹如何: - 使用 Next.js、TypeScript 和 Tailwind CSS 建立 UI 元件產生器 Web 應用程式。 - 使用 CopilotKit 將 AI 功能整合到 UI 元件產生器中。 - 整合嵌入式程式碼編輯器以變更產生的程式碼。 先決條件 ---- 要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。 以下是建立 AI 支援的 UI 元件產生器所需的工具: - [Ace 程式碼編輯器](https://ace.c9.io/)- 用 JvaScript 編寫的嵌入式程式碼編輯器,與本機編輯器的功能和效能相符。 - [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 aiuigenerator ``` 選擇您首選的配置設定。在本教學中,我們將使用 TypeScript 和 Next.js App Router。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0c2uq47mrd3ppxqvr1v7.png) 接下來,安裝 Ace 程式碼編輯器和 Langchain 軟體套件及其相依性。 ``` npm install react-ace @langchain/langgraph ``` 最後,安裝 CopilotKit 軟體套件。這些套件使我們能夠從 React 狀態檢索資料並將 AI copilot 新增至應用程式。 ``` npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend ``` 恭喜!您現在已準備好建立由人工智慧驅動的部落格。 **建構 UI 元件產生器前端** ----------------- 在本節中,我將引導您完成使用靜態內容建立 UI 元件產生器前端的過程,以定義生成器的使用者介面。 首先,請在程式碼編輯器中前往`/[root]/src/app`並建立一個名為`components`的資料夾。在 Components 資料夾中,建立兩個名為`Header.tsx`和`CodeTutorial.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="w-full flex-none text-xl text-white font-semibold p-6" href="/" aria-label="Brand"> AI-UI-Components-Generator </Link> </div> </nav> </header> </> ); } ``` 在`CodeTutorial.tsx`檔案中,加入以下程式碼,定義一個名為`CodeTutorial`的功能元件,該元件呈現 UI 元件產生器主頁,該首頁將顯示產生的 UI 元件、嵌入式程式碼編輯器和產生的實作教學。 ``` "use client"; import Markdown from "react-markdown"; import { useState } from "react"; import AceEditor from "react-ace"; import React from "react"; export default function CodeTutorial() { const [code, setCode] = useState<string[]>([ `<h1 class="text-red-500">Hello World</h1>`, ]); const [codeToDisplay, setCodeToDisplay] = useState<string>(code[0] || ""); const [codeTutorial, setCodeTutorial] = useState(``); function onChange(newCode: any) { setCodeToDisplay(newCode); } return ( <> <main className=" min-h-screen px-4"> <div className="w-full h-full min-h-[70vh] flex justify-between gap-x-1 "> <div className="w-2/3 min-h-[60vh] rounded-lg bg-white shadow-lg p-2 border mt-8 overflow-auto"> <div className="w-full min-h-[60vh] rounded-lg" dangerouslySetInnerHTML={{ __html: codeToDisplay }} /> </div> <AceEditor placeholder="Placeholder Text" mode="html" theme="monokai" name="blah2" className="w-[50%] min-h-[60vh] p-2 mt-8 rounded-lg" onChange={onChange} fontSize={14} lineHeight={19} showPrintMargin={true} showGutter={true} highlightActiveLine={true} value={codeToDisplay} setOptions={{ enableBasicAutocompletion: true, enableLiveAutocompletion: true, enableSnippets: false, showLineNumbers: true, tabSize: 2, }} /> </div> <div className="w-10/12 mx-auto"> <div className="mt-8"> <h1 className="text-white text-center text-xl font-semibold p-6"> Code Tutorial </h1> {codeTutorial ? ( <Markdown className="text-white">{codeTutorial}</Markdown> ) : ( <div className="text-white"> The Code Tutorial Will Appear Here </div> )} </div> </div> </main> </> ); } ``` 接下來,前往`/[root]/src/page.tsx`文件,新增以下程式碼,導入`CodeTutorial`和`Header`元件,並定義名為`Home`的功能元件。 ``` import React from "react"; import Header from "./components/Header"; import CodeTutorial from "./components/CodeTutorial"; export default function Home() { return ( <> <Header /> <CodeTutorial /> </> ); } ``` 接下來,刪除 globals.css 檔案中的 CSS 程式碼並新增以下 CSS 程式碼。 ``` @tailwind base; @tailwind components; @tailwind utilities; @tailwind base; @tailwind components; @tailwind utilities; body { height: 100vh; background-color: rgb(16, 23, 42); } pre { margin: 1rem; padding: 1rem; border-radius: 10px; background-color: black; overflow: auto; } h2, p { padding-bottom: 1rem; padding-top: 1rem; } code { margin-bottom: 2rem; } ``` 最後,在命令列上執行命令`npm run dev` ,然後導航到 http://localhost:3000/。 現在您應該在瀏覽器上查看 UI 元件產生器前端,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/id9z3bizkb2bghbe4bxb.png) **使用 CopilotKit 將 AI 功能整合到元件產生器** --------------------------------- 在本節中,您將學習如何為 UI 元件產生器新增 AI 副駕駛以產生 UI 元件程式碼以及使用 CopilotKit 的實作教學。 CopilotKit 提供前端和[後端](https://docs.copilotkit.ai/getting-started/quickstart-backend)套件。它們使您能夠插入 React 狀態並使用 AI 代理在後端處理應用程式資料。 首先,讓我們將 CopilotKit React 元件加入到部落格前端。 ### **將 CopilotKit 新增至部落格前端** 在這裡,我將引導您完成將 UI 元件產生器與 CopilotKit 前端整合的過程,以方便產生 UI 元件程式碼和實作教學。 首先,使用下面的程式碼片段導入`/[root]/src/app/components/CodeTutorial.tsx`檔案頂部的自訂掛鉤`useMakeCopilotReadable`和`useCopilotAction` 。 ``` import { useCopilotAction, useMakeCopilotReadable, } from "@copilotkit/react-core"; ``` 在`CodeTutorial`函數內的狀態變數下方,加入以下程式碼,該程式碼使用`useMakeCopilotReadable`掛鉤來新增將作為應用程式內聊天機器人的上下文產生的程式碼。該鉤子使副駕駛可以讀取程式碼。 ``` useMakeCopilotReadable(codeToDisplay); ``` 在上面的程式碼下方,新增以下程式碼,該程式碼使用`useCopilotAction`掛鉤來設定名為`generateCodeAndImplementationTutorial`的操作,該操作將啟用 UI 元件程式碼和實作教學課程的產生。 這個操作接受兩個參數,稱為`code`和`tutorial` ,這兩個參數可以產生 UI 元件程式碼和實作教程。 該操作包含一個處理函數,該函數根據給定的提示產生 UI 元件程式碼和實作教程。 在處理函數內部, `codeToDisplay`狀態會使用新產生的程式碼進行更新,而`codeTutorial`狀態會使用新產生的教學課程進行更新,如下所示。 ``` useCopilotAction( { name: "generateCodeAndImplementationTutorial", description: "Create Code Snippet with React.js(Next.js), tailwindcss and an implementation tutorial of the code generated.", parameters: [ { name: "code", type: "string", description: "Code to be generated", required: true, }, { name: "tutorial", type: "string", description: "Markdown of step by step guide tutorial on how to use the generated code accompanied with the code. Include introduction, prerequisites and what happens at every step accompanied with code generated earlier. Don't forget to add how to render the code on browser.", required: true, }, ], handler: async ({ code, tutorial }) => { setCode((prev) => [...prev, code]); setCodeToDisplay(code); setCodeTutorial(tutorial); }, }, [codeToDisplay, codeTutorial] ); ``` 之後,請前往`/[root]/src/app/page.tsx`檔案並使用下面的程式碼匯入頂部的 CopilotKit 前端套件和樣式。 ``` import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; ``` 然後使用`CopilotKit`包裝`CopilotSidebar`和`CodeTutorial`元件,如下所示。 `CopilotKit`元件指定 CopilotKit 後端端點 ( `/api/copilotkit/` ) 的 URL,而`CopilotSidebar`呈現應用程式內聊天機器人,您可以提示產生 UI 元件程式碼和實作教學。 ``` export default function Home() { return ( <> <Header /> <CopilotKit url="/api/copilotkit"> <CopilotSidebar instructions="Help the user generate code. Ask the user if to generate its tutorial." defaultOpen={true} labels={{ title: "Code & Tutorial Generator", initial: "Hi! 👋 I can help you generate code and its tutorial.", }}> <CodeTutorial /> </CopilotSidebar> </CopilotKit> </> ); } ``` 之後,執行開發伺服器並導航到 http://localhost:3000。您應該會看到應用程式內聊天機器人已整合到 UI 元件產生器中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ty4g6tuluhfiqtnnyxvg.png) ### **將 CopilotKit 後端加入博客** 在這裡,我將引導您完成將 UI 元件產生器與 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/mjl2g88iedd1n0qkd3ai.png) 若要取得 Tavilly Search API 金鑰,請導覽至 https://app.tavily.com/home ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m00kyux6biskw7xn2wec.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()); } ``` 如何產生 UI 元件 ---------- 現在轉到您之前整合的應用程式內聊天機器人,並給它一個提示,例如「產生聯絡表單」。 生成完成後,您應該會看到生成的聯絡表單元件及其實作教程,如下所示。您也可以使用嵌入式程式碼編輯器修改產生的程式碼。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43t9oauptomio4cy1gwr.png) ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/297vxxiqti56ydevfmyl.png) 恭喜!您已完成本教學的專案。 結論 -- [CopilotKit](https://copilotkit.ai/)是一款令人難以置信的工具,可讓您在幾分鐘內將 AI Copilot 加入到您的產品中。無論您是對人工智慧聊天機器人和助理感興趣,還是對複雜任務的自動化感興趣,CopilotKit 都能讓您輕鬆實現。 如果您需要建立 AI 產品或將 AI 工具整合到您的軟體應用程式中,您應該考慮 CopilotKit。 您可以在 GitHub 上找到本教學的源程式碼: <https://github.com/TheGreatBonnie/AIPoweredUIComponentsGenerator> --- 原文出處:https://dev.to/tcms/ai-powered-frontend-ui-components-generator-nextjs-gpt4-langchain-copilotkit-1hac

AI 驅動的前端 UI 元件產生器(Next.js、GPT4、Langchain 和 CopilotKit)

**長話短說** -------- 在本文中,您將了解如何建立由 AI 驅動的前端 UI 元件產生器,該產生器使您能夠透過實作教學產生 Next.js Tailwind CSS UI 元件。 我們將介紹如何: - 使用 Next.js、TypeScript 和 Tailwind CSS 建立 UI 元件產生器 Web 應用程式。 - 使用 CopilotKit 將 AI 功能整合到 UI 元件產生器中。 - 整合嵌入式程式碼編輯器以變更產生的程式碼。 先決條件 ---- 要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。 以下是建立 AI 支援的 UI 元件產生器所需的工具: - [Ace 程式碼編輯器](https://ace.c9.io/)- 用 JvaScript 編寫的嵌入式程式碼編輯器,與本機編輯器的功能和效能相符。 - [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 aiuigenerator ``` 選擇您首選的配置設定。在本教學中,我們將使用 TypeScript 和 Next.js App Router。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0c2uq47mrd3ppxqvr1v7.png) 接下來,安裝 Ace 程式碼編輯器和 Langchain 軟體套件及其相依性。 ``` npm install react-ace @langchain/langgraph ``` 最後,安裝 CopilotKit 軟體套件。這些套件使我們能夠從 React 狀態檢索資料並將 AI copilot 新增至應用程式。 ``` npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend ``` 恭喜!您現在已準備好建立由人工智慧驅動的部落格。 **建構 UI 元件產生器前端** ----------------- 在本節中,我將引導您完成使用靜態內容建立 UI 元件產生器前端的過程,以定義生成器的使用者介面。 首先,請在程式碼編輯器中前往`/[root]/src/app`並建立一個名為`components`的資料夾。在 Components 資料夾中,建立兩個名為`Header.tsx`和`CodeTutorial.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="w-full flex-none text-xl text-white font-semibold p-6" href="/" aria-label="Brand"> AI-UI-Components-Generator </Link> </div> </nav> </header> </> ); } ``` 在`CodeTutorial.tsx`檔案中,加入以下程式碼,定義一個名為`CodeTutorial`的功能元件,該元件呈現 UI 元件產生器主頁,該首頁將顯示產生的 UI 元件、嵌入式程式碼編輯器和產生的實作教學。 ``` "use client"; import Markdown from "react-markdown"; import { useState } from "react"; import AceEditor from "react-ace"; import React from "react"; export default function CodeTutorial() { const [code, setCode] = useState<string[]>([ `<h1 class="text-red-500">Hello World</h1>`, ]); const [codeToDisplay, setCodeToDisplay] = useState<string>(code[0] || ""); const [codeTutorial, setCodeTutorial] = useState(``); function onChange(newCode: any) { setCodeToDisplay(newCode); } return ( <> <main className=" min-h-screen px-4"> <div className="w-full h-full min-h-[70vh] flex justify-between gap-x-1 "> <div className="w-2/3 min-h-[60vh] rounded-lg bg-white shadow-lg p-2 border mt-8 overflow-auto"> <div className="w-full min-h-[60vh] rounded-lg" dangerouslySetInnerHTML={{ __html: codeToDisplay }} /> </div> <AceEditor placeholder="Placeholder Text" mode="html" theme="monokai" name="blah2" className="w-[50%] min-h-[60vh] p-2 mt-8 rounded-lg" onChange={onChange} fontSize={14} lineHeight={19} showPrintMargin={true} showGutter={true} highlightActiveLine={true} value={codeToDisplay} setOptions={{ enableBasicAutocompletion: true, enableLiveAutocompletion: true, enableSnippets: false, showLineNumbers: true, tabSize: 2, }} /> </div> <div className="w-10/12 mx-auto"> <div className="mt-8"> <h1 className="text-white text-center text-xl font-semibold p-6"> Code Tutorial </h1> {codeTutorial ? ( <Markdown className="text-white">{codeTutorial}</Markdown> ) : ( <div className="text-white"> The Code Tutorial Will Appear Here </div> )} </div> </div> </main> </> ); } ``` 接下來,前往`/[root]/src/page.tsx`文件,新增以下程式碼,導入`CodeTutorial`和`Header`元件,並定義名為`Home`的功能元件。 ``` import React from "react"; import Header from "./components/Header"; import CodeTutorial from "./components/CodeTutorial"; export default function Home() { return ( <> <Header /> <CodeTutorial /> </> ); } ``` 接下來,刪除 globals.css 檔案中的 CSS 程式碼並新增以下 CSS 程式碼。 ``` @tailwind base; @tailwind components; @tailwind utilities; @tailwind base; @tailwind components; @tailwind utilities; body { height: 100vh; background-color: rgb(16, 23, 42); } pre { margin: 1rem; padding: 1rem; border-radius: 10px; background-color: black; overflow: auto; } h2, p { padding-bottom: 1rem; padding-top: 1rem; } code { margin-bottom: 2rem; } ``` 最後,在命令列上執行命令`npm run dev` ,然後導航到 http://localhost:3000/。 現在您應該在瀏覽器上查看 UI 元件產生器前端,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/id9z3bizkb2bghbe4bxb.png) **使用 CopilotKit 將 AI 功能整合到元件產生器** --------------------------------- 在本節中,您將學習如何為 UI 元件產生器新增 AI 副駕駛以產生 UI 元件程式碼以及使用 CopilotKit 的實作教學。 CopilotKit 提供前端和[後端](https://docs.copilotkit.ai/getting-started/quickstart-backend)套件。它們使您能夠插入 React 狀態並使用 AI 代理在後端處理應用程式資料。 首先,我們將 CopilotKit React 元件加入到部落格前端。 ### **將 CopilotKit 新增至部落格前端** 在這裡,我將引導您完成將 UI 元件產生器與 CopilotKit 前端整合的過程,以方便產生 UI 元件程式碼和實作教學。 首先,使用下面的程式碼片段導入`/[root]/src/app/components/CodeTutorial.tsx`檔案頂部的自訂掛鉤`useMakeCopilotReadable`和`useCopilotAction` 。 ``` import { useCopilotAction, useMakeCopilotReadable, } from "@copilotkit/react-core"; ``` 在`CodeTutorial`函數內的狀態變數下方,加入以下程式碼,該程式碼使用`useMakeCopilotReadable`掛鉤來新增將作為應用程式內聊天機器人的上下文產生的程式碼。該鉤子使副駕駛可以讀取程式碼。 ``` useMakeCopilotReadable(codeToDisplay); ``` 在上面的程式碼下方,新增以下程式碼,該程式碼使用`useCopilotAction`掛鉤來設定名為`generateCodeAndImplementationTutorial`的操作,該操作將啟用 UI 元件程式碼和實作教學課程的產生。 這個操作接受兩個參數,稱為`code`和`tutorial` ,這兩個參數可以產生 UI 元件程式碼和實作教程。 該操作包含一個處理函數,該函數根據給定的提示產生 UI 元件程式碼和實作教程。 在處理函數內部, `codeToDisplay`狀態會使用新產生的程式碼進行更新,而`codeTutorial`狀態會使用新產生的教學課程進行更新,如下所示。 ``` useCopilotAction( { name: "generateCodeAndImplementationTutorial", description: "Create Code Snippet with React.js(Next.js), tailwindcss and an implementation tutorial of the code generated.", parameters: [ { name: "code", type: "string", description: "Code to be generated", required: true, }, { name: "tutorial", type: "string", description: "Markdown of step by step guide tutorial on how to use the generated code accompanied with the code. Include introduction, prerequisites and what happens at every step accompanied with code generated earlier. Don't forget to add how to render the code on browser.", required: true, }, ], handler: async ({ code, tutorial }) => { setCode((prev) => [...prev, code]); setCodeToDisplay(code); setCodeTutorial(tutorial); }, }, [codeToDisplay, codeTutorial] ); ``` 之後,請前往`/[root]/src/app/page.tsx`檔案並使用下面的程式碼匯入頂部的 CopilotKit 前端套件和樣式。 ``` import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; ``` 然後使用`CopilotKit`包裝`CopilotSidebar`和`CodeTutorial`元件,如下所示。 `CopilotKit`元件指定 CopilotKit 後端端點 ( `/api/copilotkit/` ) 的 URL,而`CopilotSidebar`呈現應用程式內聊天機器人,您可以提示產生 UI 元件程式碼和實作教學。 ``` export default function Home() { return ( <> <Header /> <CopilotKit url="/api/copilotkit"> <CopilotSidebar instructions="Help the user generate code. Ask the user if to generate its tutorial." defaultOpen={true} labels={{ title: "Code & Tutorial Generator", initial: "Hi! 👋 I can help you generate code and its tutorial.", }}> <CodeTutorial /> </CopilotSidebar> </CopilotKit> </> ); } ``` 之後,執行開發伺服器並導航到 http://localhost:3000。您應該會看到應用程式內聊天機器人已整合到 UI 元件產生器中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ty4g6tuluhfiqtnnyxvg.png) ### **將 CopilotKit 後端加入博客** 在這裡,我將引導您完成將 UI 元件產生器與 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/mjl2g88iedd1n0qkd3ai.png) 若要取得 Tavilly Search API 金鑰,請導覽至 https://app.tavily.com/home ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m00kyux6biskw7xn2wec.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()); } ``` 如何產生 UI 元件 ---------- 現在轉到您之前整合的應用程式內聊天機器人,並給它一個提示,例如「產生聯絡表單」。 生成完成後,您應該會看到生成的聯絡表單元件及其實作教程,如下所示。您也可以使用嵌入式程式碼編輯器修改產生的程式碼。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43t9oauptomio4cy1gwr.png) ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/297vxxiqti56ydevfmyl.png) 恭喜!您已完成本教學的專案。 結論 -- [CopilotKit](https://copilotkit.ai/)是一款令人難以置信的工具,可讓您在幾分鐘內將 AI Copilot 加入到您的產品中。無論您是對人工智慧聊天機器人和助理感興趣,還是對複雜任務的自動化感興趣,CopilotKit 都能讓您輕鬆實現。 如果您需要建立 AI 產品或將 AI 工具整合到您的軟體應用程式中,您應該考慮 CopilotKit。 您可以在 GitHub 上找到本教學的源程式碼: <https://github.com/TheGreatBonnie/AIPoweredUIComponentsGenerator> --- 原文出處:https://dev.to/the_greatbonnie/ai-powered-frontend-ui-components-generator-nextjs-gpt4-langchain-copilotkit-1hac

掌握關聯式資料庫設計:綜合指南

介紹 -- 在當今資料驅動的世界中,有效儲存和管理資訊對於各種規模的企業和組織都至關重要。關聯式資料庫已成為以結構化和可擴展的方式組織和操作資料的強大解決方案。在這篇文章中,我們將探討關聯式資料庫的基礎知識、它們的管理系統以及支援有效資料庫設計的原則。 --- 什麼是資料庫? ------- 資料庫是資料的結構化集合,其組織和儲存方式有利於高效檢索、操作和管理。將其視為數位文件櫃,其中不是實體資料夾和文件,而是整齊排列的表格和記錄,以便於存取。 什麼是關係資料庫? --------- 關聯式資料庫是一種將資料組織成具有行(記錄)和列(欄位)的表(關係)的資料庫。這些表透過關係互連,允許以各種方式存取和組合資料。想像一下電子表格的集合,每個電子表格代表資料的不同方面,但能夠無縫連結和組合它們之間的資訊。 關係型資料庫管理系統 ---------- 關聯式資料庫管理系統 (RDBMS) 是一種軟體應用程式,旨在建立、管理關聯式資料庫並與之互動。它提供了一個用於在資料庫中儲存、檢索和操作資料的結構化框架。 RDBMS 的一些熱門範例包括 MySQL、PostgreSQL、Oracle 和 Microsoft SQL Server。 --- SQL簡介 ----- SQL(結構化查詢語言)是用於與關聯式資料庫互動的標準程式語言。它允許您建立、讀取、更新和刪除資料庫中的資料,以及定義和修改資料庫結構本身。 SQL 就像一種通用語言,可讓您與不同的 RDBMS 平台進行通訊。 命名約定 ---- 在 SQL 中,遵循一致的命名約定對於清晰度和可維護性至關重要。這是一個例子: ``` -- Good naming conventions CREATE TABLE customers ( customer_id INT PRIMARY KEY, first_name VARCHAR(100), last_name VARCHAR(100), email VARCHAR(100) ); ``` 什麼是資料庫設計? --------- 資料庫設計是建立用於儲存和管理資料庫中的資料的高效且有組織的結構的過程。它涉及定義表、列、關係和約束,以確保資料完整性、最小化冗餘並優化效能。正確的資料庫設計是建立健全且可擴展的應用程式的基礎。 ![資料庫設計形象](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/de0l7km0yufdp2ssak3s.png) 資料的完整性 ------ 資料完整性是指資料庫中儲存的資料的準確性、一致性和可靠性。它確保資料遵循特定的規則和約束,防止錯誤和不一致。資料完整性分為三種: 1. **實體完整性**:確保表中的每一行都可以透過主鍵唯一標識,且主鍵不能有空值。 2. **參照完整性**:透過確保一個表中的外鍵值與另一表中的主鍵值相符來維護表之間的關係。 3. **域完整性**:透過限制可儲存的資料類型、格式和值範圍,強制給定列輸入有效的條目。 ``` -- Example: Enforcing data integrity CREATE TABLE orders ( order_id INT PRIMARY KEY, -- Entity integrity customer_id INT FOREIGN KEY REFERENCES customers(customer_id), -- Referential integrity order_date DATE NOT NULL, -- Domain integrity total_amount DECIMAL(10, 2) CHECK (total_amount >= 0) -- Domain integrity ); ``` 資料庫術語 ----- - **表**:按行和列組織的相關資料的集合。 - **行**:表中的單一實例或條目(也稱為記錄或元組)。 - **列**:表中資料的特定特徵或屬性(也稱為欄位或屬性)。 - **主鍵**:唯一標識表中每一行的列或列組合。 - **外鍵**:引用另一個表的主鍵的列或列的組合,在兩個表之間建立關係。 - **連接**:根據相關列組合兩個或多個表中的行的操作。 - **索引**:一種資料結構,透過建立表格中資料的排序表示來提高資料檢索操作的效能。 - **視圖**:從一個或多個基礎表動態產生的虛擬表。 - **預存程序**:預先編譯的 SQL 語句集合,可以作為單一單元執行。 - **觸發器**:特殊類型的預存程序,當表中發生特定事件(例如 INSERT、UPDATE 或 DELETE 語句)時會自動執行。 原子值 --- 在資料庫設計中,儲存原子值非常重要,這意味著儲存無法進一步劃分的最小資訊片段。這項原則有助於保持資料完整性並避免冗餘。 例如,與其將客戶的全名儲存在單一欄位中,不如將其分成名字和姓氏欄位。這樣,您可以輕鬆地獨立搜尋、排序或操作名稱的每個部分。 ``` -- Example: Storing atomic values CREATE TABLE customers ( customer_id INT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50), email VARCHAR(100) ); ``` --- 按鍵簡介 ---- 鍵是資料庫設計中的重要組成部分,有助於確保資料完整性並建立表之間的關係。它們充當記錄的唯一標識符,並實現高效的資料檢索和操作。 主鍵索引 ---- 主鍵是唯一標識表中每筆記錄的列或列的組合。它確保每個記錄都是唯一的並且可以輕鬆找到。通常會對主鍵建立索引以提高查詢效能。 ``` CREATE TABLE customers ( customer_id INT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50), email VARCHAR(100) ); ``` 查找表 --- 查找表也稱為參考表或程式碼表,是包含預定義值集的表,這些值可用於填入其他表中的列。查找表透過確保一致性和減少冗餘來幫助維護資料完整性。 ``` -- Lookup table for product categories CREATE TABLE product_categories ( category_id INT PRIMARY KEY, category_name VARCHAR(100) ); -- Products table referencing the lookup table CREATE TABLE products ( product_id INT PRIMARY KEY, product_name VARCHAR(100), category_id INT, FOREIGN KEY (category_id) REFERENCES product_categories(category_id) ); ``` 超級密鑰和候選密鑰 --------- 超級鍵是表中唯一標識每筆記錄的一組或多列。候選鍵是最小超鍵,這意味著它不包含不必要的列。換句話說,候選鍵是一個超級鍵,具有唯一標識每個記錄所需的最少列數。 主鍵和備用鍵 ------ 主鍵是選擇作為表的主要唯一辨識符的候選鍵。備用鍵也稱為唯一鍵,是任何其他本來可以被選為主鍵但沒有被選為主鍵的候選鍵。 ``` CREATE TABLE employees ( employee_id INT PRIMARY KEY, email VARCHAR(100) UNIQUE, -- Alternate key first_name VARCHAR(50), last_name VARCHAR(50) ); ``` 代理鍵和自然鍵 ------- 代理鍵是用作表中主鍵的人工鍵(通常是序號或 GUID)。它與資料本身沒有固有的意義或關係。另一方面,自然金鑰是從資料本身衍生的金鑰,例如員工 ID 或產品程式碼。 ``` -- Surrogate key CREATE TABLE orders ( order_id INT PRIMARY KEY IDENTITY(1,1), -- Surrogate key customer_name VARCHAR(100), order_date DATE ); -- Natural key CREATE TABLE products ( product_code VARCHAR(10) PRIMARY KEY, -- Natural key product_name VARCHAR(100), price DECIMAL(10,2) ); ``` 我應該使用代理鍵還是自然鍵? -------------- 代理鍵和自然鍵之間的選擇取決於多個因素,包括資料的性質、資料變更的可能性以及重複或衝突的可能性。 代理鍵通常是首選,因為它們是: - 不可變:即使資料發生變化,它們也不會隨著時間而改變。 - 保證唯一性:由資料庫系統生成,保證唯一性。 - 不透明:它們不會洩露有關資料本身的任何訊息,這對於安全和隱私來說是有益的。 另一方面,自然鍵在以下情況下可能會很有優勢: - 資料具有固有的唯一性,例如產品程式碼或員工 ID。 - 資料不太可能隨著時間的推移而改變,從而降低了衝突或重複的風險。 - 需要人類可讀且有意義的辨識符。 外鍵 -- 外鍵是一個表中引用另一個表的主鍵的列或列的組合。它在兩個表之間建立連結並強制引用完整性,確保子表中的資料有效且與父表中的資料一致。 ``` CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT, order_date DATE, FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ); ``` NOT NULL 外鍵 ----------- 在某些情況下,可能需要對外鍵列設定 NOT NULL 約束,這表示該列不能有空值。此約束可確保子表中的每筆記錄都與父表中的有效記錄相關聯。 ``` CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT NOT NULL, order_date DATE, FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ); ``` 外鍵約束 ---- 外鍵約束定義表之間引用完整性的規則。這些約束可以包括更新或刪除父表中引用的記錄時要採取的操作,例如: - `CASCADE` :當父表中的記錄被更新或刪除時,子表中對應的記錄也會被更新或刪除。 - `SET NULL` :當父表中的記錄被更新或刪除時,子表中對應的外鍵值被設定為NULL。 - `NO ACTION` :當父表中的記錄被更新或刪除時,子表中對應的外鍵值保持不變,如果違反引用完整性則回滾操作。 ``` CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT, order_date DATE, FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON UPDATE CASCADE ON DELETE SET NULL ); ``` 簡單鍵、複合鍵、複合鍵 ----------- - 簡單鍵是用作主鍵或外鍵的單一欄位。 - 複合鍵是用作主鍵或外鍵的兩個或多個列的組合。 - 複合鍵是用作外鍵的兩個或多個簡單鍵的組合。 ``` -- Simple key CREATE TABLE orders ( order_id INT PRIMARY KEY, ... ); -- Composite key CREATE TABLE order_items ( order_id INT, product_id INT, quantity INT, PRIMARY KEY (order_id, product_id) ); -- Compound key CREATE TABLE shipments ( shipment_id INT PRIMARY KEY, order_id INT, product_id INT, FOREIGN KEY (order_id, product_id) REFERENCES order_items(order_id, product_id) ); ``` --- 關係 ---- 關係是關聯式資料庫的基石,可讓您連接和組合來自不同表的資料。關係主要分為三種: ![關係形象](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qadj6qd495g7yovdcx2d.jpg) 一對一的關係 ------ 在一對一關係中,一個表中的每筆記錄都與另一個表中的一筆記錄相關聯,反之亦然。例如,考慮一個資料庫,其中每位員工都有一名且僅有一名經理,且每位經理管理一名且僅有一名員工。這種關係在實務上比較少見。 ![一對一影像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qm0xf8h40rktgqzbp4o4.png) 一對多關係 ----- 在一對多關係中,一個表格(「一」側)中的每筆記錄都可以與另一個表(「多」側)中的多個記錄關聯。例如,在學校的資料庫中,一位老師可以教授多個班級,但每個班級只有一位老師授課。 ![一對多影像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qe9iuj8n0n3b1cj24pdf.png) 多對多關係 ----- 在多對多關係中,一個表中的每筆記錄可以與另一表中的多筆記錄關聯,反之亦然。例如,在大學的資料庫中,一個學生可以註冊多個課程,每個課程可以有多個學生註冊。 ![多對多關係影像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r2prop1r97o0cj7cr6qw.jpg) ### 關係摘要 - 一對一:表A中的一筆記錄與表B中的一筆且只有一筆記錄相關,反之亦然。 - 一對多:A表中的一筆記錄可以與B表中的多筆記錄相關,但B表中的一筆記錄只能與A表中的一筆記錄相關。 - 多對多:表A中的多筆記錄可以與表B中的多筆記錄相關,反之亦然。 設計關係(SQL 實作) ------------ **1 - 設計一對一關係** 要設計一對一關係,您可以將兩個表中的所有列包含在一個表中,也可以建立兩個單獨的表並使用外鍵約束來連結它們。 ``` -- Option 1: Single table CREATE TABLE employees ( employee_id INT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50), manager_first_name VARCHAR(50), manager_last_name VARCHAR(50) ); -- Option 2: Two tables with foreign key CREATE TABLE employees ( employee_id INT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50), manager_id INT UNIQUE, FOREIGN KEY (manager_id) REFERENCES employees(employee_id) ); ``` 2 -**設計一對多關係** 要設計一對多關係,通常會建立兩個表:父表(“一”側)和子表(“多”側)。子表包含引用父表主鍵的外鍵列。 ``` -- Parent table CREATE TABLE teachers ( teacher_id INT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50) ); -- Child table CREATE TABLE classes ( class_id INT PRIMARY KEY, class_name VARCHAR(100), teacher_id INT, FOREIGN KEY (teacher_id) REFERENCES teachers(teacher_id) ); ``` **3 - 設計多對多關係** 要設計多對多關係,通常會建立第三個表(稱為聯結表或關聯表),將兩個主表連結在一起。此聯結表包括引用主表的外鍵列 ``` -- Table 1 CREATE TABLE students ( student_id INT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50) ); -- Table 2 CREATE TABLE courses ( course_id INT PRIMARY KEY, course_name VARCHAR(100), description TEXT ); -- Junction or Intemediary table CREATE TABLE enrollments ( enrollment_id INT PRIMARY KEY, student_id INT, course_id INT, FOREIGN KEY (student_id) REFERENCES students(student_id), FOREIGN KEY (course_id) REFERENCES courses(course_id) ); ``` 父表和子表 ----- 在一對多或多對多關係中, **“一”**側的表通常稱為父表,而**“多”**側的表稱為子表。子表包含引用父表主鍵的外鍵。 例如,在教師-班級關係中, `teachers`表是父表, `classes`表是子表。同樣,在學生-課程關係中, `students`和`courses`表是父表,而`enrollm`是父表。 實體關係建模簡介 -------- 實體關係建模(ER Modeling)是資料庫設計中使用的一種技術,用於直觀地表示資料庫的邏輯結構。它有助於辨識實體(表)、屬性(列)以及它們之間的關係,從而更容易理解和傳達資料庫設計。 ER 圖由以下部分組成: - **實體**:以矩形表示,實體是資料庫中的表或物件。 - **屬性**:在實體矩形內列出,屬性是描述實體的列或欄位。 - **關係**:以連接實體的線表示,關係描述了實體之間的關聯。 基數 -- 基數定義了兩個實體之間的數值關係。它指定一個實體可以與另一個實體的單一實例關聯的最大實例數。最常見的基數是: - 一對一(1:1):實體 A 的一個實例最多可以與實體 B 的一個實例關聯,反之亦然。 - 一對多(1:N):實體 A 的一個實例可以與實體 B 的多個實例關聯,但實體 B 的一個實例只能與實體 A 的一個實例關聯。 - 多對多 (M:N):實體 A 的多個實例可以與實體 B 的多個實例關聯,反之亦然。 在 ER 圖中,基數以特定的符號表示,例如單線表示一對一,帶箭頭的線表示一對多,兩端帶箭頭的線表示多對多關係。 ![基數影像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmife5ost450cgntriyi.png) 模態 -- 模態是指一個實體實例的存在是否依賴它與另一個實體的關係。有兩種類型的模態: - 部分模態:實例的存在不依賴它與另一個實體的關係。例如,客戶可以在沒有任何訂單的情況下存在。 - 總模態:實例的存在取決於它與另一個實體的關係。例如,沒有訂單,訂單項就不可能存在。 在 ER 圖中,模態使用特定的符號表示,例如單一條表示部分模態,雙條表示總模態。 --- 資料庫規範化簡介 -------- 資料庫規範化是組織資料庫中的資料以減少冗餘、最大限度地減少資料異常(插入、更新和刪除異常)並提高資料完整性的過程。它涉及將資料庫分解為較小的表,並根據特定規則或範式定義它們之間的關係。 資料庫規範化的主要目標是: - 消除冗餘資料 - 確保資料完整性 - 促進資料操作和維護 資料庫規範化有多種範式,每種都建立在前一種範式的基礎上。最常用的範式是: 1. 第一範式 (1NF) 2. 第二範式 (2NF) 3. 第三範式 (3NF) 1NF(資料庫規範化第一範式) --------------- 第一範式(1NF)是最基本的規範化形式。它規定表中的屬性(列)必須具有原子值,這意味著表中的每個單元格應包含單一值,而不是一組值。 例如,假設一個表格具有名為「PhoneNumbers」的資料列,該資料列儲存客戶的多個電話號碼。這違反了 1NF,因為該列包含一組值而不是單一值。為了符合 1NF,您需要將電話號碼分成單獨的列或為電話號碼建立單獨的表。 ``` -- Violates 1NF CREATE TABLE customers ( customer_id INT PRIMARY KEY, name VARCHAR(100), phone_numbers VARCHAR(200) -- Stores multiple phone numbers, violating 1NF ); -- Conforms to 1NF CREATE TABLE customers ( customer_id INT PRIMARY KEY, name VARCHAR(100), phone1 VARCHAR(20), phone2 VARCHAR(20), phone3 VARCHAR(20) ); ``` 2NF(資料庫規範化的第二範式) ---------------- 第二範式 (2NF) 建立在 1NF 的基礎上,解決了部分依賴問題。如果表屬於 1NF,且每個非主屬性(列)完全依賴整個主鍵,則該表屬於 2NF。 換句話說,如果一個表有一個複合主鍵(由多個列組成),那麼所有非鍵列必須依賴整個主鍵,而不僅僅是它的一部分。 例如,考慮一個具有複合主鍵`(student_id, course_id)`和列`grade`的表。如果`grade`欄位僅取決於`course_id` ,而不取決於`student_id`和`course_id`的組合,則該表違反了 2NF。 ``` -- Violates 2NF CREATE TABLE student_courses ( student_id INT, course_id INT, course_name VARCHAR(100), grade CHAR(2), -- Depends only on course_id, not the entire primary key PRIMARY KEY (student_id, course_id) ); -- Conforms to 2NF CREATE TABLE student_courses ( student_id INT, course_id INT, grade CHAR(2), PRIMARY KEY (student_id, course_id) ); CREATE TABLE courses ( course_id INT PRIMARY KEY, course_name VARCHAR(100) ); ``` 3NF(資料庫規範化的第三範式) ---------------- 第三範式 (3NF) 建立在 2NF 的基礎上,解決了傳遞依賴問題。如果表屬於 2NF,且每個非主屬性都非傳遞依賴於主鍵,則該表屬於 3NF。 換句話說,如果一個非鍵列依賴另一個非鍵列,那麼該表就違反了 3NF,並且非鍵列應該分離到它們自己的表中。 例如,考慮一個包含`student_id` 、 `student_name` 、 `class_id`和`class_name`欄位的表。 `student_name`欄位取決於`student_id` ,而`class_name`欄位取決於`class_id` 。但是, `class_name`欄位也透過`class_id`欄位傳遞依賴`student_id` 。這違反了 3NF。 ``` -- Violates 3NF CREATE TABLE student_classes ( student_id INT, student_name VARCHAR(100), class_id INT, class_name VARCHAR(100), PRIMARY KEY (student_id, class_id) ); -- Conforms to 3NF CREATE TABLE students ( student_id INT PRIMARY KEY, student_name VARCHAR(100) ); CREATE TABLE classes ( class_id INT PRIMARY KEY, class_name VARCHAR(100) ); CREATE TABLE student_classes ( student_id INT, class_id INT, PRIMARY KEY (student_id, class_id), FOREIGN KEY (student_id) REFERENCES students(student_id), FOREIGN KEY (class_id) REFERENCES classes(class_id) ); ``` 透過遵循資料庫規範化的原則,您可以建立結構良好且高效的資料庫,從而最大限度地減少冗餘、保持資料完整性並促進資料操作和維護。 --- 索引(聚集索引、非聚集索引、複合索引) ------------------- 索引是提高資料庫中資料檢索操作效能的資料結構。它們在表中建立資料的排序表示,從而允許更快的搜尋和查詢。索引有幾種類型: - **聚集索引**:聚集索引根據索引鍵值對表中的行進行實體重新排序。每個表只能有一個聚集索引。 - **非聚集索引**:非聚集索引是一個單獨的物件,其中包含索引鍵值和指向表中相應行的指標。一個表可以有多個非聚集索引。 - **複合索引**:複合索引是索引鍵中包含多個欄位的索引。它可以是集群的,也可以是非集群的。 ``` -- Clustered index CREATE CLUSTERED INDEX idx_customers_name ON customers (last_name, first_name); -- Nonclustered index CREATE NONCLUSTERED INDEX idx_orders_date ON orders (order_date); -- Composite index CREATE INDEX idx_products_category_price ON products (category_id, price); ``` 資料類型 ---- 在資料庫領域,資料類型就像是保存特定類型資訊的不同形狀的容器。就像您不會將液體儲存在籃子中或將固體物體儲存在罐子中一樣,資料庫需要強制執行特定的資料類型以確保資料的完整性和一致性。 SQL 中的一些常見資料類型包括: - `INT`或`INTEGER` :儲存整數,例如`42`或`17` 。 - `FLOAT`或`DOUBLE` :儲存十進制數,例如`3.14159`或`0.00005` 。 - `VARCHAR`或`TEXT` :儲存文字資料,例如名稱或描述。 - `DATE`或`DATETIME` :儲存日期和時間值,例如`'2023-05-06'`或`'2024-01-01 12:34:56'` 。 - `BOOLEAN` :儲存真/假值,例如`1` (真)或`0` (假)。 選擇正確的資料類型至關重要,因為它會影響資料的儲存、查詢和操作方式。例如,嘗試在`INT`列中儲存大字串會導致錯誤或資料截斷。 ``` CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, age INT, is_active BOOLEAN DEFAULT 1 ); ``` 在此範例中,我們建立一個`users`表,其中包含`id` (整數)、 `name` (最多 50 個字元的字串)、 `age` (整數)和`is_active` (布林值,預設值為`1`或 true)欄位。 --- 連接簡介 ---- 連接就像連接資料庫中不同表的橋樑,允許您組合和檢索來自多個來源的相關資料。它們是關係資料庫中的基本概念,對於有效查詢和操作資料至關重要。 ![加盟圖片](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iks7pvzjikxzgqicjh2x.png) 內部聯接 ---- 內部聯結就像兩個表之間的友好握手,其中只有在兩個表中具有匹配值的行才會包含在結果集中。這是一種基於公共列或列集組合多個表中的資料的方法。 ``` SELECT users.name, orders.order_date FROM users INNER JOIN orders ON users.id = orders.user_id; ``` 在這個範例中,我們從`users`表中檢索`name`列,從`orders`表中檢索`order_date`列,但僅限於`users`表中的`id`與`orders`表中的`user_id`相符的行。 3 個表上的內連接(範例) ------------- 假設我們有三個表: `users` 、 `orders`和`products` 。我們想要檢索每個訂單的使用者名稱、訂單日期和產品名稱。我們可以透過在所有三個表之間執行內部聯結來實現這一點: ``` SELECT users.name, orders.order_date, products.product_name FROM users INNER JOIN orders ON users.id = orders.user_id INNER JOIN products ON orders.product_id = products.id; ``` 在這裡,我們首先在`id`和`user_id`列上連接`users`和`orders`表。然後,我們將該連接的結果與`products`表的`product_id`和`id`列連接起來。這樣,我們可以在單一查詢中從所有三個表中檢索資料,但僅限於滿足連接條件的行。 外連接簡介 ----- 內部連接就像友好的握手,而外部連接更像是歡迎的擁抱。它們不僅包括兩個表中的匹配行,還包括一個或兩個表中的不匹配行,具體取決於外連接的類型。 右外連接 ---- 右外連接就像從右表到左表的溫暖擁抱。它包括右表中的所有行以及左表中的匹配行。如果左表中沒有符合的行,則結果將包含左表列的`NULL`值。 ``` SELECT users.name, orders.order_date FROM users RIGHT OUTER JOIN orders ON users.id = orders.user_id; ``` 在此範例中,我們從`orders`表(右表)中檢索所有行,以及`users`表(左表)中的符合`name`值。如果訂單沒有符合的用戶,則`name`列將包含`NULL` 。 JOIN 與 NOT NULL 列 ----------------- 有時,您可能只想對不為空的列執行聯接。當您想要從結果集中排除缺少資料的行時,這會很有用。 ``` SELECT users.name, orders.order_date FROM users INNER JOIN orders ON users.id = orders.user_id AND users.name IS NOT NULL; ``` 在此範例中,我們在`users`和`orders`表之間執行內部聯接,但我們新增了一個附加條件`users.name IS NOT NULL`以確保結果集中僅包含具有非空`name`值的行。 跨 3 個表的外連接 ---------- 與內連接範例類似,我們可以跨多個表執行外連接。假設我們想要檢索所有訂單以及使用者名稱和產品名稱,即使`users`或`products`表中缺少值。 ``` SELECT users.name, orders.order_date, products.product_name FROM orders LEFT OUTER JOIN users ON orders.user_id = users.id LEFT OUTER JOIN products ON orders.product_id = products.id; ``` 在這裡,我們從`orders`表開始,並對`users`和`products`表執行左外連接。這可確保所有訂單以及符合的使用者名稱和產品名稱(如果可用)都包含在結果集中。如果`users`或`products`表中沒有符合的行,則對應的列將包含`NULL`值。 別名 -- 別名就像 SQL 查詢中表格或欄位的暱稱。它們可以使查詢更具可讀性和更容易理解,特別是在處理長表或列名時,或在查詢中多次引用同一個表時。 ``` SELECT u.name, o.order_date, p.product_name FROM users u INNER JOIN orders o ON u.id = o.user_id INNER JOIN products p ON o.product_id = p.id; ``` 在此範例中,我們對`users`表使用別名`u` ,對`orders`表使用別名`o` ,對`products`表使用`p` 。這使得查詢更加簡潔且更易於閱讀,而不必多次重複完整的表名稱。 自加入 --- 自連結就像一張表在與自己對話。這是一種根據同一個表中的特定條件或關係將表與其自身連接的方法。這在處理分層或遞歸資料結構(例如員工-經理關係或巢狀類別)時非常有用。 ``` SELECT e.name AS employee, m.name AS manager FROM employees e LEFT OUTER JOIN employees m ON e.manager_id = m.id; ``` 在此範例中,我們對`employees`表執行自聯接以檢索每位員工的姓名及其對應經理的姓名。我們使用左外連接來確保所有員工都包含在結果集中,即使他們沒有指派經理。 --- 資料庫標記語言 (DBML) -------------- 資料庫建模語言(DBML)是一種簡單直觀的標記語言,用於描述關係型資料庫的結構。它提供了一種人類可讀的方式來定義表、列、關係和約束,從而使資料庫設計方面的溝通和協作變得輕鬆。 **DBML 入門** 要開始使用 DBML,您需要一個文字編輯器並對資料庫概念有基本的了解。讓我們建立第一個 DBML 檔案: ``` // my_database.dbml Table users { id int [pk, increment] username varchar email varchar [unique] created_at datetime [default: `now()`] } ``` 在這個範例中,我們定義了一個`users`表,其中包含`id` 、 `username` 、 `email`和`created_at`列。 `[pk]`標記指定`id`為主鍵, `[increment]`表示自動遞增, `[unique]`確保`email`的唯一性, `[default:` now() `]`將`created_at`的預設值設為目前時間戳記。 **建立表** DBML 可讓您在單一檔案中定義多個表格及其列。讓我們為資料庫新增更多表: ``` // my_database.dbml Table users { id int [pk, increment] username varchar email varchar [unique] created_at datetime [default: `now()`] } Table posts { id int [pk, increment] title varchar content text user_id int [ref: > users.id] created_at datetime [default: `now()`] } ``` 在這個範例中,我們新增了一個`posts`表,其中包含`id` 、 `title` 、 `content` 、 `user_id`和`created_at`欄位。 `[ref: > users.id]`標籤在`posts`表中的`user_id`欄位和`users`表中的`id`列之間建立外鍵關係。 **定義關係** DBML 支援表之間各種類型的關係,包括一對一、一對多和多對多。讓我們在資料庫中定義一些關係: ``` // my_database.dbml Table users { id int [pk, increment] username varchar email varchar [unique] created_at datetime [default: `now()`] } Table posts { id int [pk, increment] title varchar content text user_id int [ref: > users.id] created_at datetime [default: `now()`] } Ref: users.id < posts.user_id ``` 在此範例中,我們定義了`users`和`posts`表之間的一對多關係。 `Ref: users.id < posts.user_id`行指定`users`表中的`user_id`欄位引用`posts`表中的`id`列。 **新增約束** 約束確保資料完整性並在資料庫上強制執行規則。 DBML 支援各種約束,例如主鍵、外鍵、唯一約束和預設值。讓我們在表格中加入一些約束: ``` // my_database.dbml Table users { id int [pk, increment] username varchar [unique] email varchar [unique] created_at datetime [default: `now()`] } Table posts { id int [pk, increment] title varchar content text user_id int [ref: > users.id] created_at datetime [default: `now()`] } Ref: users.id < posts.user_id ``` 在此更新的範例中,我們為`users`表中的`username`名列新增了`[unique]`約束,以確保每個使用者名稱都是唯一的。 **記錄您的資料庫** DBML 可讓您為資料庫模式新增註解和註釋,使其更易於理解和維護。讓我們用註釋來記錄我們的表格: ``` // my_database.dbml Table users { id int [pk, increment] // Unique identifier for users username varchar [unique] // User's username email varchar [unique] // User's email address created_at datetime [default: `now()`] // Date and time when the user was created } Table posts { id int [pk, increment] // Unique identifier for posts title varchar // Title of the post content text // Content of the post user_id int [ref: > users.id] // ID of the user who created the post created_at datetime [default: `now()`] // Date and time when the post was created } Ref: users.id < posts.user_id // Relationship between users and posts ``` **DBML 的好處** - 簡單且人類可讀的語法 - 與資料庫無關的方法 - [dbdiagram.io](dbdiagram.io)上的免費視覺化工具 - 一致的可讀性和可維護性約定 - 豐富的文件和範例 --- 結論 -- 總而言之,關聯式資料庫設計構成了現代應用程式中高效能資料組織的支柱。了解其原理使您能夠建立強大且可擴展的資料庫。接下來,我們將深入研究 SQL 語法,釋放與資料庫有效互動的能力。請繼續關注我們對 SQL 世界的探索! --- 原文出處:https://dev.to/louaiboumediene/mastering-relational-database-design-a-comprehensive-guide-3jh8

🌝 適合您下一個專案的 15 個 JavaScript 框架 ⚔

開發者生態系統已經發展了很多,並且有許多開發者不知道的框架。 我們「作為開發人員」有很多關於如何建立我們的應用程式的框架選項。這些選擇非常重要。 讓我們介紹 15 個框架,供您製作下一個專案。我將提供詳細的資源,以便您可以學習其中的每一個。 相信我!這份清單就是您所需要的。 讓我們開始吧。 ![下一級](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ychbi440selxu1ftf5n8.gif) --- ### 庫與框架 在開始之前,讓我們先來了解一下框架與函式庫有何不同。開發人員可以互換使用它! 函式庫和框架都是由其他人編寫的可重複使用程式碼。 > 簡單來說: 將圖書館想像成IKEA之旅。您家裡有自己的空間,但您需要一些家具幫助。您不想從頭開始,所以您可以前往宜家,在那裡您可以挑選您需要的東西。你是做出決定的人。 現在,框架更像是建造一個樣品房。您已經有了一套計劃以及一些佈局和設計的選擇。但最終,藍圖和建造者處於控制之中。他們會讓你知道在哪裡可以加入你的意見,但他們正在掌控一切。 > 從技術角度來說。 透過庫,您可以指導應用程式的流程。您決定何時何地使用庫的功能。但有了框架,框架就控制了流程。它為您提供了一些插入程式碼的位置,但它是程式碼執行時發號施令的地方。 我使用了 Freecodecamp 的這篇文章“ [框架和庫之間的區別](https://www.freecodecamp.org/news/the-difference-between-a-framework-and-a-library-bd133054023f/)”,特別是因為它的解釋很簡單。完整閱讀一下! --- 1. [Wing](https://git.new/winlang-repo) - 一種雲端程式語言。 --------------------------------------------------- ![翅膀](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n97bowkrexjk46n94bcc.png) Wing是一個旨在開發雲端應用程式的框架。 它允許您在雲端中建立應用程式,並且具有相當簡單的語法。 核心概念是您可以直接在應用程式中指定資源。 您可以執行本機模擬並使用 Winglang 控制台視覺化每個步驟中發生的情況。 ![機翼基礎設施](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 控制台支援。您可以在建立應用程式之前安裝它! 您可以使用 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 來接收即時通知。 可以節省大量時間的一些功能包括熱重載,以獲得即時回饋並順利產生必要的安全策略。 無需學習每個雲端提供者的語法。 您的程式碼可以編譯到 AWS、GCP、Azure 或任何自訂平台。太棒了:D 您可以閱讀完整的逐步指南,以了解[如何使用 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 語言。 該翼目前支援以下輸出: - AWS CDK 平台 - Terraform/AWS 平台 - Terraform/GCP 平台 - Terraform/Azure 平台 - 模擬器平台 - 客製化平台 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/)中使用 Wing 查看結構和範例。 如果你比較像輔導員。看這個! {% 嵌入 https://www.youtube.com/watch?v=wzqCXrsKWbo %} ![機翼工作流程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l8zqja0w9kyoibrskjmp.gif) Wing 在 GitHub 上有 4500+ 顆星,1600+ 個版本,但仍未進入 v1 版本,這意味著意義重大。 {% cta https://git.new/winlang-repo %} 星翼 ⭐️ {% endcta %} --- 2. [Nest](https://github.com/nestjs/nest) - 高效且可擴展的伺服器端應用程式。 ------------------------------------------------------------ ![巢](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/inlcpt901r5kiwm4eeor.png) 一個先進的 Node.js 框架,用於使用 TypeScript/JavaScript 建立高效且可擴展的伺服器端應用程式。 它使用現代 JavaScript,使用 TypeScript 建構(保留與純 JavaScript 的兼容性),並結合了 OOP(物件導向程式設計)、FP(函數式程式設計)和 FRP(函數式反應式程式設計)的元素。 在底層,Nest 使用 Express,但也提供與 Fastify 等各種其他庫的兼容性,從而可以輕鬆使用無數可用的第三方外掛程式。 Nest 提供了高於這些常見 Node.js 框架(Express/Fastify)的抽象級別,但也直接向開發人員公開其 API。這為開發人員提供了一定程度的自由。 在我們了解更多之前,請觀看 100 秒內的 Nestjs! {% 嵌入 https://www.youtube.com/watch?v=0M8AYU\_hPas&amp;pp=ygUXZW1iZXIganMgaW4gMTAwIHNlY29uZHM%3D %} 考慮到它們提供的靈活性,您當然不必重新發明輪子。 ![特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6h6yjfmq1h5qn5765by0.png) 這是使用 Nest CLI 設定新專案的方法。 ``` npm i -g @nestjs/cli nest new project-name ``` 這將引導該應用程式。 ``` import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ``` 您可以閱讀[文件](docs.nestjs.com)。 他們還提供一套付費課程(我想知道為什麼)。如果您需要完整的路線圖並想成為 Nest 的使用專家,請隨時查看它們。 但我建議使用 Freecodecamp 提供的這些免費教學來學習。 - [NestJs 初學者課程 - 建立 REST API](https://www.youtube.com/watch?v=GHTA143_b-s) - 大約 3.42 小時,涵蓋許多[主題](https://www.freecodecamp.org/news/learn-nestjs-by-building-a-crud-api/)。 - [綜合 NestJS 課程](https://www.youtube.com/watch?v=sFnAHC9lLaw&t=1s)- 涵蓋 20 個模組,時長 14 小時。 {% 嵌入 https://www.youtube.com/watch?v=sFnAHC9lLaw&amp;t=1s %} 如果您正在尋找入門專案,請學習[如何在 NestJS 中使用 Nodemailer 發送電子郵件](https://www.freecodecamp.org/news/how-to-use-nodemailer-in-nestjs/)。您可以使用它來獲得紮實的基礎知識。 Nest.js 擁有龐大的開發人員社區,並被許多公司使用。尋找已使用 Nest[的專案和公司的完整清單](https://docs.nestjs.com/discover/companies)。 ![公司](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ic0z3dts7bmw5s3e5gmf.png) 順便說一句,作為初學者,我最常擔心的是相似的名稱:Nextjs、Nuxtjs 和 Nestjs。我涵蓋了所有內容,這樣您就不必感到困惑。哈哈! Nest 在 GitHub 上擁有超過 64k 個 star,提交次數超過 15k,並且已發布`v10`版本。 {% cta https://github.com/nestjs/nest %} 星巢 ⭐️ {% endcta %} --- 3. [Gatsby](https://github.com/gatsbyjs/gatsby) - 最好的基於 React 的框架,具有內建的效能、可擴展性和安全性。 ----------------------------------------------------------------------------------- ![蓋茲比](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0drinpwldeyfxd82lgf.png) Gatsby 是一個基於 React 的免費開源框架,可協助開發人員建立速度極快的網站和應用程式。 它將動態呈現網站的控制和可擴展性與靜態網站生成的速度結合起來,創造了一個全新的可能性網絡。 Gatsby 從任何資料來源提取資料,無論是 Markdown 檔案、Contentful 或 WordPress 等無頭 CMS,還是 REST 或 GraphQL API。使用來源插件載入資料,然後使用 Gatsby 的統一 GraphQL 介面進行開發。 與 Next.js 不同,Gatsby 不執行伺服器端渲染。相反,它會在建置期間在客戶端產生 HTML 內容。 我見過一些使用 Gatsby 建立的優秀作品集。 開始使用以下 npm 指令。 ``` npm init gatsby ``` 它會要求提供網站標題和專案目錄的名稱。繼續按照提示選擇您的首選語言(JavaScript 或 TypeScript)、CMS、樣式工具和其他功能。 您可以這樣使用它。 ``` cd my-gatsby-site // to start the local dev server npm run develop ``` 您可以閱讀[文件](https://www.gatsbyjs.com/docs)。我個人很喜歡文件的流程。 您也可以按照[教學](https://www.gatsbyjs.com/docs/tutorial/getting-started/)開始,[操作指南](https://www.gatsbyjs.com/docs/how-to/)和[概念指南](https://www.gatsbyjs.com/docs/conceptual/)深入了解 Gatsby 概念以及網站架構。 Gatsby 提供了開箱即用的 PWA 和大量主題。使用 Gatsby 主題,您的所有預設配置(共享功能、資料來源、設計)都會從您的網站中抽象化出來,並放入可安裝的套件中。您可以閱讀有關[主題的](https://www.gatsbyjs.com/docs/themes/)更多資訊。 例如, `gatsby-theme-blog`是用於建立部落格的官方 Gatsby 主題。可能有可以透過`gatsby-config.js`配置的主題選項。 ``` npm install gatsby-theme-blog ``` 對於電子商務商店或廣泛的媒體網站等內容密集的企業級網站來說,Gatsby 並不是理想的解決方案。隨著內容大小的增加,建置時間將急劇增加。 尋找使用 Gatsby 建立的[606 個網站](https://www.gatsbyjs.com/showcase/)的清單。其中,53 個網站是開源的,因此這可以提供靈感,也是一個起點。 ![展示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/439taxdhursvmjwhcxgc.png) 他們還提供了[大量按類別劃分的插件](https://www.gatsbyjs.com/plugins)以及每個插件中清晰的文件。其中一個範例是將 Google Analytics 新增至您的應用程式的插件。 ``` npm install gatsby-plugin-google-analytics ``` ![外掛](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwq1dsyfdqdkmfetebj9.png) 您也可以使用 Gatsby 的[Starter 庫](https://www.gatsbyjs.com/starters/)。使用 Gatsby 建立下一個應用程式還需要什麼? ![入門庫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u831zo26ttg6tvzu6shq.png) 使用這些[參考指南](https://www.gatsbyjs.com/docs/reference/)來獲取有關 Gatsby API 的詳細資訊。 如果您喜歡完整的課程,我建議您觀看[Gatsby 靜態網站產生器教學](https://www.youtube.com/watch?v=RaTpreA0v7Q)- Freecodecamp 提供的 9 小時教學。 Gatsby 在 GitHub 上有 55,000 顆星,目前處於 v5 版本,並有超過 245,000 名開發人員使用。 {% cta https://github.com/gatsbyjs/gatsby %} 明星蓋茲比 ⭐️ {% endcta %} --- 4. [Nextjs](https://github.com/vercel/next.js) - Web 的 React 框架。 ---------------------------------------------------------------- ![下一個js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/da26rscfzozpe307xz2g.png) 由於它提供的優化級別,它是我最喜歡的框架之一。 Next.js 使您能夠透過擴展最新的 React 功能並整合強大的基於 Rust 的 JavaScript 工具來建立全端 Web 應用程式,以實現最快的建置。 Next.js 由荷蘭公司 Vercel(以前稱為 ZEIT)於 2017 年建立。 Next.js 也像 Gatsby 一樣提供靜態產生器。 Next.js 的建置遵循`Build once, runs everywhere`的原則,因此您可以使用 Next.js 製作 Web 應用程式、行動應用程式、桌面應用程式和漸進式 Web 應用程式。 ![下一個js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4j16td403jbbyyk7xz2v.png) Nextjs 提供了許多功能,例如檔案路由、渲染技術(例如 ISR)以及深層的圖像和字體最佳化。你可以檢查任何 nextjs 網站的 SEO 統計資料,在大多數情況下它都是一流的。 ![特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zr9yx5n6gy2q764gak8x.png) ![特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5mf9r3zohetcsyatp90z.png) 開始使用以下 npm 指令。 ``` npx create-next-app@latest ``` 您可以閱讀[文件](https://nextjs.org/docs)並按照[本指南](https://nextjs.org/docs/getting-started/installation)開始使用。 其中涉及很多概念,閱讀完整的文件需要幾個月的時間。我前段時間寫過一篇文章,你可以看看。它並沒有出名,但它是我憑藉多年的 Nextjs 經驗編寫的最好的文章之一。我還提到了 Nextjs 團隊提供的[官方課程](https://nextjs.org/learn/dashboard-app/getting-started)。 {% 嵌入 https://dev.to/anmolbaranwal/12-things-you-didnt-know-you-could-do-with-nextjs-386b %} 如果您想透過 YouTube 教學進行學習,我建議您觀看這些最近的教程,因為文件更新得非常頻繁,因此最好觀看最近的內容,而不是幾年前的內容。 - \[帶有 TypeScript 的 Nextjs 13(應用程式路由器)\](https://www.youtube.com/watch?v=ZVnjOPwW4ZA&amp;pp=ygUTbmV4dGpzIGNyYXNoIGNvdXJzZQ%3D%3D ) - 1 小時教程。 - [Next.js 14 完整課程 2024](https://www.youtube.com/watch?v=wm5gMKuwSYk) - 3 小時教學。 您也可以觀看 Nextjs 100 秒。他們加入了一個基本教程,使其長達 11 分鐘。 {% 嵌入 https://www.youtube.com/watch?v=Sklc\_fQBmcs&amp;t=4s&amp;pp=ygUObmV4dGpzIGluIDEwMHM%3D %} 我自己使用文件學習了它,並使用它建置了超過 6 個專案,甚至是一個超過 20k 程式碼庫的 SAAS 應用程式。這就是為什麼我說,它是您可以選擇的最佳框架之一。 使用 Next.js 建立的一些熱門網站包括 Auth0、Coinbase、Docker、GitHub、Hulu、Netflix、Sesame、Starbucks、Trulia、Twitch 和 Uber。你可以看到所有使用Nextjs的[網站](https://nextjs.org/showcase)。 ![下一個js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iafbez4aptnb7f0iqtgz.png) 他們還提供了各種可以直接使用的[入門模板](https://vercel.com/templates/next.js)。 ![入門模板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1egye2mtz7f3ehzsm9ja.png) ![電子商務](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d4ge39gpt0xo3rtvn1i5.png) Next 在 GitHub 上有 12 萬顆星,發布`v14.2`版本,在 NPM 上每週下載量超過 600 萬次。如其儲存庫所示,有 260 萬開發人員使用。 {% cta https://github.com/vercel/next.js %} 明星 Nextjs ⭐️ {% endcta %} --- 5. [Preact](https://github.com/preactjs/preact) - 具有相同現代 API 的快速 3kB React 替代品。 ------------------------------------------------------------------------------- ![預反應](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/npzj10prb5i8noahsops.png) Preact 是一個輕量級、快速、高效能的函式庫,是 React 的替代品。 Preact 的大小僅為 3kb(經過壓縮和壓縮),但卻為您提供了 React 所需的所有功能,使其成為最好的 JavaScript 框架之一。 Preact 的建立者 Jason Miller 是 Google 的高級開發者計畫工程師。 Preact 基本上具有 Virtual DOM 元件的所有功能,而沒有諸如以下的開銷: - 熟悉 React API 和 ES6 類別、鉤子和功能元件模式。 - 透過簡單的 preact/compat 別名實現廣泛的 React 相容性。 - 您需要的一切,例如 JSX、VDOM、DevTools、HMR、SSR。 在生產過程中,您可以輕鬆地從現有專案中的 React 切換到 Preact,因為它們支援相同的 API。 程式碼範例結構如下所示。您也可以查看此範例[程式碼筆](https://codepen.io/developit/pen/LpNOdm),您可以查看它以了解 Preact 中程式碼庫的結構。 ![程式碼結構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3mwphw4vjfm1a3cquyv.png) 開始使用以下 npm 指令。 ``` npm init preact ``` 這是執行開發伺服器的方式。 ``` # Go into the generated project folder cd my-preact-app # Start a development server npm run dev ``` 您將必須配置一些東西,尤其是別名。請遵循[本指南](https://preactjs.com/guide/v10/getting-started)。 您可以閱讀[文件](https://preactjs.com/guide/v10/getting-started/)並查看詳細的[演示和範例](https://preactjs.com/about/demos-examples)清單。 他們還提供了基於 Web 的[教程](https://preactjs.com/tutorial/),您可以按照該教程來學習 Preact。 如果您需要範例應用程式、樣板檔案、元件、工具包等,請使用[Awesome Preact](https://github.com/preactjs/awesome-preact) 。 Preact 在 GitHub 上有 36,000 顆星,目前已發布`v10`版本。 {% cta https://github.com/preactjs/preact %} Star Preact ⭐️ {% endcta %} --- 6. [tRPC](https://github.com/trpc/trpc) - 端到端類型安全性 API 變得簡單。 ------------------------------------------------------------ ![特爾普克](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ck7ve1epya6ofshzmc2c.png) tRPC 可讓您輕鬆建立和使用完全類型安全的 API,而無需模式或程式碼產生。 ![gif 動態圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2bssocrvw9pt0y1lnunk.gif) 上面的客戶端沒有從伺服器導入任何程式碼,僅導入其類型聲明 如果我們要深入了解,那麼您絕對應該閱讀一些歷史。 {% 嵌入 https://dev.to/zenstack/a-brief-history-of-api-rpc-rest-graphql-trpc-fme %} 目前,GraphQL 是在 TypeScript 中實作型別安全 API 的主要方式(這太棒了!)。由於 GraphQL 被設計為用於實現 API 的與語言無關的規範,因此它沒有充分利用 TypeScript 這樣的語言的強大功能。 如果您的專案是使用全端 TypeScript 建置的,您可以直接在客戶端和伺服器之間共用類型,而無需依賴程式碼生成。 tRPC 適用於全端 TypeScript 開發人員。它使您可以輕鬆編寫可以在應用程式的前端和後端安全使用的端點。 API 合約的類型錯誤將在建置時被捕獲,從而減少應用程式在執行時出現錯誤的可能性。 這是為 Mono 儲存庫設計的,因為您需要從伺服器匯出/匯入類型定義。 ![特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6v56rl2jkgfat6xsf909.png) 開始使用以下 npm 指令。 ``` npm install @trpc/server@next @trpc/client@next ``` 您必須使用實例定義後端路由器。閱讀[快速入門指南](https://trpc.io/docs/quickstart)以了解更多詳細資訊。 了解[trpc 中涉及的概念](https://trpc.io/docs/concepts)(例如 rpc 和使用的術語)非常重要。 您可以閱讀[文件](https://trpc.io/docs)。 如果您已經在一個混合語言的團隊中工作,或者擁有您無法控制的第三方消費者,那麼您應該建立一個與語言無關的 GraphQL-API。 如果您想測試一下,我建議使用此[模板](https://github.com/new?template_name=examples-minimal&template_owner=trpc),其中包含一個最小的範例。 您還可以觀看這個[45 分鐘的 YouTube 教學](https://www.youtube.com/watch?v=UfUbBWIFdJs&pp=ygUMd2hhdCBpcyB0cnBj)來了解有關 trpc 的更多資訊。 {% 嵌入 https://www.youtube.com/watch?v=UfUbBWIFdJs&amp;pp=ygUMd2hhdCBpcyB0cnBj %} 它們在 GitHub 上擁有超過 32,000 顆星,目前處於`v11` beta 版本,並被 51,000 名開發人員使用。 {% cta https://github.com/trpc/trpc %} 啟動 tRPC ⭐️ {% endcta %} --- [7.Nuxtjs](https://github.com/nuxt/nuxt) - 直覺的 Vue 框架。 ------------------------------------------------------ ![努克斯特](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct9usemwuhvtrjcx0na8.png) Nuxt 是一個基於 Vue.js 生態系統的漸進式開源框架,用於建立高效能 Web 應用程式,尤其是伺服器端渲染應用程式。 但請記住,Nuxt 並不是 Vue.js 的替代品,因為它無法單獨運作。而且它也不能被視為像 Express 這樣成熟的後端框架。 100 秒觀看 Nuxtjs,掌握整體概念。 {% 嵌入 https://www.youtube.com/watch?v=dCxSsr5xuL8 %} Nuxt 是建立這三種 Web 應用程式的最佳 JavaScript 框架之一 - 預先渲染靜態頁面、單頁 Web 應用程式 (SPA)、伺服器端渲染 Web 應用程式 (SSR) 甚至通用應用程式。 開發人員特別喜歡 Nuxt,因為它有豐富的函式庫和模組。 ![組合特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kknguo9v9dnqu2npfp68.png) 開始使用以下 npm 指令。 ``` npx nuxi@latest init <my-project> ``` 您可以閱讀[文件](https://nuxt.com/docs/getting-started/introduction)並檢查[codesandbox範例](https://codesandbox.io/s/github/nuxt/starter/tree/v3/)。 您可以按照本[指南](https://nuxt.com/docs/guide/concepts/auto-imports)了解更多關鍵概念。 有許多整合選項,因此您可以更輕鬆地繼續使用您喜歡的工具和服務。 ![整合選項](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irikkgro0cp4l8svioi9.png) 您可以查看[免費課程清單](https://nuxt.com/video-courses)來了解 Nuxt 生態系統。 如果您想要推薦的課程,請學習[Nuxt 3 — 初學者課程](https://www.youtube.com/watch?v=fTPCKnZZ2dk)— Freecodecamp 提供的 3 小時教學。 使用 Nuxt 建立的一些流行網站包括 Aircall、Amplitude、Backmarket、Bitpay、Bootstrap Vue、Fox News、Gitlab、Icons8、Instrument、MyScript、Nespresso、Note.com、Ozon.ru、Roland Garros、System76、Todoist、加油,Wappalyzer 。尋找不同類別下[展示網站的完整清單](https://nuxt.com/showcase)。 如果您想快速測試和建置,那麼我建議您查看[入門模板](https://nuxt.com/templates)。 ![範本](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bn61xrcx9ym3a40kewwf.png) Nuxt 在 GitHub 上擁有超過 51,000 顆星,並被超過 318,000 名開發者使用。 {% cta https://github.com/nuxt/nuxt %} Star Nuxt ⭐️ {% endcta %} --- 8. [Ember.js](https://github.com/emberjs/ember.js) - 用於建立雄心勃勃的 Web 應用程式的 JavaScript 框架。 --------------------------------------------------------------------------------------- ![man.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8ygtjex9ve6e2gbsfh0.png) Ember.js 是一個 JavaScript 框架,用於為企業建立可擴展的單頁 Web 應用程式。與其他框架不同,模型-視圖-視圖模型 (MVVW) 架構是 Ember 的基礎。 Ember.js 最初是一個 SproutCore 2.0 框架,由其建立者 Yehuda Katz 更名為 Ember.js,Yehuda Katz 是一位出色的開發人員,被譽為 jQuery 的主要建立者之一。 他們還提供命令列介面工具。 Ember CLI 是建立、建置、測試和提供構成 Ember 應用程式或外掛程式的檔案的官方方式。 ``` npm install -g ember-cli ``` 儘管與 React、Vue 和 Svelte 相比,Ember.js 是一個較舊的前端 JavaScript 框架,但它仍然具有強大的功能,並且在 Microsoft、LinkedIn、Netflix 和 Twitch 等大公司中擁有龐大的用戶群。查看[完整清單](https://emberjs.com/ember-users/)。 ![使用 ember.js 的公司](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0w87sxixh8js9luyv7jc.png) 借助強大的預設設置,您可能永遠不需要在應用程式中配置任何內容,但如果您需要的話,選項就在那裡! 這意味著 Ember.js 遵循「CoC – 約定優於配置」方法,這可確保在大多數情況下不需要任何配置,以便您可以直接跳到編碼和建立 Web 應用程式。 它們還支援類似於 AngularJS 的 2 路資料綁定。 當我們深入研究時,了解 ember.js 是如何誕生的、其建立背後的先驅者以及製作開源軟體時做出的改變生活的決定非常重要。看這個! {% 嵌入 https://www.youtube.com/watch?v=Cvz-9ccflKQ %} 安裝 Ember CLI 後。 ``` npm install -g ember-cli ``` 您可以建立一個新應用程式,如圖所示。 ``` ember new ember-quickstart --lang en cd ember-quickstart npm start ``` 您可以閱讀[詳細的快速入門文件](https://guides.emberjs.com/release/getting-started/quick-start/)和[官方指南](https://guides.emberjs.com/release/)。 要學習 ember.js,您可以按照他們的官方團隊建立的[逐步教程](https://guides.emberjs.com/release/tutorial/part-1/)進行操作。您可以在[Ember API 文件](https://api.emberjs.com/ember/release)上閱讀有關 API 的更多資訊。 有數以千計的 JavaScript 庫可以在 Ember 中很好地工作。當 npm 套件提供一些 Ember 特定的功能時,他們稱之為`addon` 。外掛程式提供了一種編寫可重複使用程式碼、共用元件和樣式、擴充建置工具等的方法,所有這些都只需最少的配置。尋找[插件的完整清單](https://emberobserver.com/)。 ![外掛](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wlrw4m6u46fijp7kt7ky.png) 如果您正在尋找更多文章來學習 Ember.js,我推薦這些: - [Ember JS Essentials:Startech 提供的安裝及其功能的初學者指南](https://www.startechup.com/blog/ember-js/)。 - Toptal [建立您的第一個 Ember.js 應用程式的指南](https://www.toptal.com/javascript/a-step-by-step-guide-to-building-your-first-ember-js-app)。 這足以理解結構並決定 Ember 何時適合您的專案。 他們在 GitHub 上有 22k+ 顆星,而`v5.8`版本有 500 多個版本。 {% cta https://github.com/emberjs/ember.js %} 明星 Ember.js ⭐️ {% endcta %} --- 9. [Backbone.js](https://github.com/jashkenas/backbone) - 為您的 JS 應用程式提供一些帶有模型、視圖、集合和事件的 Backbone。 ------------------------------------------------------------------------------------------------- ![骨幹](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qysm5n76o7wdf1u48bii.png) Backbone.js 是一個基於 JavaScript 的框架,透過 RESTful JSON 介面連接到 API。 Jeremy Ashkenas 因建立一些最好的 JavaScript 框架(例如 CoffeeScript 和 Underscore.js)而聞名,他於 2010 年 10 月推出了 Backbone.js。 它旨在建立單頁 Web 應用程式並維護不同 Web 應用程式元件(例如眾多客戶端和伺服器)之間的同步。 Backbone.js 以小而輕而聞名,因為它只需要 jQuery 和一個 JavaScript 函式庫 Underscore.js 即可使用整個函式庫。 Backbone.js 透過提供具有鍵值綁定和自訂事件的模型、具有豐富的可枚舉函數API 的集合、具有聲明性事件處理的視圖,為JavaScript 密集型應用程式提供結構,並透過RESTful JSON 接口將其全部連接到您現有的應用程式。 這是一個簡單的主幹視圖。 ``` var AppView = Backbone.View.extend({ // el - stands for element. Every view has an element associated with HTML // content will be rendered. el: '#container', // It's the first function called when this view is instantiated. initialize: function(){ this.render(); }, // $el - it's a cached jQuery object (el), in which you can use jQuery functions // to push content. Like the Hello World in this case. render: function(){ this.$el.html("Hello World"); } }); ``` 您可以閱讀[文件](https://backbonejs.org/)。 Backbone.js 被許多值得信賴的公司使用,例如 Walmart、Pinterest、SoundCloud 等。 您可以參考他們的[wiki](https://github.com/jashkenas/backbone/wiki/Tutorials%2C-blog-posts-and-example-sites) ,其中記錄了教程、部落格文章和範例網站。 您可以參考幾篇很棒的文章來了解更多: - [BackboneJS:入門](https://auth0.com/blog/backbonejs-getting-started/)- 推薦。 - [適合絕對初學者的 Backbone.js](https://adrianmejia.com/backbone-dot-js-for-absolute-beginners-getting-started/) - [BackboneJS 教學](https://www.tutorialspoint.com/backbonejs/index.htm)- 教學點。 根據儲存庫統計,它們在 GitHub 上擁有超過 28,000 顆星,並被超過 66,000 名開發人員使用。 {% cta https://github.com/jashkenas/backbone %} 明星 Backbone.js ⭐️ {% endcta %} --- 10. [Svelte](https://github.com/sveltejs/svelte) - 控制論增強的網路應用程式。 ---------------------------------------------------------------- ![苗條的](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r8xe2ni4di3g5qr03woh.png) Svelte 是一種建立 Web 應用程式的新方法。 它是由 Rich Harris(著名前端開發人員)建立的。 Svelte 於 2016 年首次推出,人氣暴漲。 許多開發人員認為 Svelte 是一個真正改變遊戲規則的革命性想法,它從根本上改變了我們編碼 Web 應用程式的方式。 與 React 或 Vue.js 等其他 JavaScript 框架不同,Svelte 沒有虛擬 DOM。相反,您可以使用簡單的 HTML、CSS 和 JavaScript 程式碼來建立無樣板的元件。 然後,Svelte Compiler 在建置期間將此程式碼編譯成小型的無框架的普通 JavaScript 模組,並在狀態變更時精確地更新 DOM。 因此,與 React 或 Vue.js 等其他傳統框架不同,Svelte 不需要很高的瀏覽器處理能力。 Svelte 依靠反應式程式來徹底更新 DOM。因此,與幾乎任何其他框架相比,它可以實現最快的渲染,並且在大多數效能基準測試中名列前茅。 開始使用以下 npm 指令。 ``` npm create svelte@latest my-app ``` 您可以這樣使用它。 ``` cd my-app npm install npm run dev -- --open ``` 您可以閱讀[文件](https://svelte.dev/docs/introduction)。該團隊還提供了[官方的 VSCode 擴展](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode),它也可以與各種其他編輯器和工具整合。 ![苗條的](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/322jqc224gf3efcifmjs.png) 他們還提供了[詳細的基於網路的教程](https://svelte.dev/tutorial/basics)來學習 Svelte。 您可以查看所有[範例](https://svelte.dev/examples/nested-components)來了解關鍵概念和結構,包括 DOM 事件、生命週期、運動、過渡和處理 SVG。 ![例子](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vfwmw0q3p68byme0b4c.png) 您可以觀看這些教學來了解有關 Svelte 的所有知識。 - [Learn Svelte – 初學者完整課程](https://www.youtube.com/watch?v=UGBJHYpHPvA)– Freecodecamp 的 23 小時教學。 - [Sveltekit &amp; Tailwind](https://www.youtube.com/watch?v=vb7CgDcA_6U&t=2s) - Freecodecamp 的 2 小時教學。 非常感謝老師們免費提供如此詳細的教學! Svelte 在 GitHub 上擁有超過 76k 顆星,目前處於`v4.2`版本,有 282k 開發人員使用。 {% cta https://github.com/sveltejs/svelte %} Star Svelte ⭐️ {% endcta %} --- 11. [Remix](https://github.com/remix-run/remix) - 建立更好的網站。 ---------------------------------------------------------- ![混音](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/en7mvytauu0b7pkm04df.png) Remix 是一個全端Web 框架,可讓您專注於使用者介面並透過Web 基礎知識進行工作,以提供快速、流暢且有彈性的使用者體驗,可部署到任何Node.js 伺服器,甚至非Node. js 環境像 Cloudflare Workers 這樣的邊緣。 Remix 建構在 React Router 之上,有四個特點: - 一個編譯器 - 伺服器端 HTTP 處理程序 - 一個伺服器框架 - 一個瀏覽器框架 您可以觀看此內容以了解有關 Remix by Fireship 的更多資訊。 {% 嵌入 https://www.youtube.com/watch?v=r4B69HAOXnA&amp;pp=ygUUcmVtaXggaW4gMTAwIHNlY29uZHM%3D %} 透過嵌套路由,Remix 可以消除幾乎所有載入狀態,如圖所示。 ![混音](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wyr8c9opmrn4chvr88jz.png) 開始使用以下 npm 指令。 ``` npx create-remix@latest ``` 您可以這樣使用它。 ``` mkdir my-remix-app cd my-remix-app npm init -y # install runtime dependencies npm i @remix-run/node @remix-run/react @remix-run/serve isbot@4 react react-dom # install dev dependencies npm i -D @remix-run/dev vite ``` 如果您想包含您的伺服器,請閱讀此[快速入門指南](https://remix.run/docs/en/main/start/quickstart),並了解更多有關如何透過 Remix Vite 插件提供 Vite 配置的訊息,因為 Remix 使用 Vite。 您可以閱讀[文件](https://remix.run/docs/en/main)。他們根據你想做的事情來分發它,順便說一句,我很喜歡。 ![文件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ljjae4pcyukr1j4nnweo.png) 尋找使用 Remix 建立的[網站的完整清單](https://remix.run/showcase)。 ![混音](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qjzhuy4njph0gckemdq5.png) 您還應該查看社區製作的[Remix 資源](https://remix.run/resources?category=all)。其中一些是有幫助的,可以改善整個生態系統。 ![生態系統](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9q5hdc59czs31nkbyhqq.png) 如果您是第一次接觸 Remix,我建議您閱讀官方團隊建立的[Remix 教學 -30min](https://remix.run/docs/en/main/start/tutorial) 。 他們在 GitHub 上擁有超過 27k 個 star,並且發布了`v2.8`版本。 {% cta https://github.com/remix-run/remix %} 明星混音 ⭐️ {% endcta %} --- 12. [AdonisJS](https://github.com/adonisjs/core) - TypeScript 優先的 Web 框架,用於建立 Web 應用程式和 API 伺服器。 ------------------------------------------------------------------------------------------------ ![阿多尼斯](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k8ishs5q78nu1yc3qrl4.png) AdonisJS 是一個功能齊全的 Node.js 後端框架。該框架是從頭開始建立的,非常重視開發人員的人體工學和易用性。 AdonisJS 專注於後端,讓您選擇您選擇的前端堆疊,這意味著前端不可知。 它是 Node.js 社群中最稀有的框架之一,附帶一套第一方包,可幫助您建立和發布產品,而無需浪費數百小時組裝不同的 npm 包。 在基礎層面上,AdonisJS 為您的應用程式提供架構,配置無縫的 TypeScript 開發環境,為您的後端程式碼配置 HMR,並提供大量維護良好且記錄廣泛的軟體包。 他們強調了一點測試,這是非常好的。 ![測試](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g623aysi86rucg45yvru.png) 開始使用以下 npm 指令。 ``` npm init adonisjs@latest hello-world ``` AdonisJS 採用經典的 MVC 設計模式。首先,使用函數式 JavaScript API 定義路由,將控制器綁定到它們,並編寫邏輯來處理控制器內的 HTTP 請求。 ``` import router from '@adonisjs/core/services/router' import PostsController from '#controllers/posts_controller' router.get('posts', [PostsController, 'index']) ``` 控制器可以使用模型從資料庫中獲取資料並呈現視圖(也稱為模板)作為回應。 ``` import { HttpContext } from '@adonisjs/core/http' import Post from '#models/post' export default class PostsController { async index({ view }: HttpContext) { const posts = await Post.all() return view.render('pages/posts/list', { posts }) } } ``` 如果您正在建立 API 伺服器,則可以用 JSON 回應取代視圖層。但是,處理和回應 HTTP 請求的流程保持不變。 您可以閱讀[文件](https://docs.adonisjs.com/guides/introduction)。 您也可以參考[入門套件](https://docs.adonisjs.com/guides/installation#starter-kits)。 他們還提供了[VSCode 擴展,](https://marketplace.visualstudio.com/items?itemName=jripouteau.adonis-vscode-extension)如果您開始使用 Adonisjs,則應該使用該擴展。 您必須查看[Awesome Adonisjs](https://github.com/adonisjs-community/awesome-adonisjs) ,它提供了一系列很棒的書籤、軟體包、教程、影片、課程、擁有使用此內容的網站的公司以及來自 AdonisJS 生態系統的其他很酷的資源。 大多數時候,開始接觸一些非常新的東西是很困難的,因此團隊提供了[10 多個課程](https://adonismastery.com/)來了解 Adonisjs 生態系統。 ![培訓班](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nkhw95z0kxkg4wcopzxb.png) ![培訓班](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqisrlqefivdt8ozfwzv.png) 他們在 GitHub 上擁有超過 15k 個 star,並且發布了`v6.8`版本。 {% cta https://github.com/adonisjs/core %} 明星 AdonisJS ⭐️ {% endcta %} --- 13. [Astro](https://github.com/withastro/astro) - 內容驅動網站的網頁框架。 -------------------------------------------------------------- ![阿斯特羅](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7o0f21nevycm4kaqgytq.png) Astro 是一個開源、伺服器優先的 Web 框架,它結合了靜態網站產生 (SSG) 和伺服器端渲染 (SSR) 的優點,可建立快速、SEO 友善的網站。 Astro 專門為部落格和電子商務等內容豐富的網站提供支持,並擁有良好的開發生態系統。 開始使用以下 npm 指令。 ``` npm create astro@latest ``` 您可以閱讀使用 Astro 建立的[文件](https://docs.astro.build/en/getting-started/)和[展示的網站](https://astro.build/showcase/)。其中一些真的很棒並且視覺上令人驚嘆! Astro 支援 React、Preact、Svelte、Vue、Solid、Lit、HTMX、Web 元件等。閱讀所有[記錄的功能](https://docs.astro.build/en/concepts/why-astro/#features)。 您可以按照本教學[使用 Astro 建立您的第一個部落格](https://docs.astro.build/en/tutorial/0-introduction/)。或使用主題來快速啟動您的下一個專案。其中一些是免費的,而另一些則是付費的! ![主題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06r4rgm0e87djv8otb3o.png) 您可以看到如圖所示的加載性能,甚至我對此感到驚訝。 ![天文表演](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nctfb8xzgz6dbg1wwg99.png) 性能至關重要,尤其是在您從事商業活動時,因為高效的演算法將節省更多資金並減少麻煩。 ![表現](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a7db56tvaxi40youys75.png) 無論是在可存取性、圖標還是使用不同的庫方面,[整合選項](https://astro.build/integrations/)都是巨大的。 ![整合選項](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qezjrdyaq1pvwvq4kanm.png) 您可以觀看 Freecodecamp 提供的一小時[Astro Web 框架速成課程](https://www.youtube.com/watch?v=e-hTm5VmofI)。 {% 嵌入 https://www.youtube.com/watch?v=e-hTm5VmofI %} Astro 在 GitHub 上擁有超過 42k 顆星,處於`v4.6` (1800 多個版本),並由超過 112k 開發人員使用。 {% cta https://github.com/withastro/astro %} Star Astro ⭐️ {% endcta %} --- 14. [Fresh](https://github.com/denoland/fresh) - 下一代網路框架。 --------------------------------------------------------- ![新鮮的](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3978f35p4m3xg9vf9rsg.png) Fresh 是下一代 Web 框架,專為速度、可靠性和簡單性而建置。 一些突出的特點: - 島上的客戶水合作用可達到最大程度的互動。 - 零執行時開銷意味著預設不會將 JS 傳送到客戶端。 - 無需配置。 - 開箱即用的 TypeScript 支援。 該框架使用 Preact 和 JSX 進行渲染和模板化,處理伺服器和客戶端上的任務。 此外,Fresh 消除了建造步驟的需要。您編寫的程式碼直接在伺服器端和客戶端執行。 TypeScript 或 JSX 到純 JavaScript 的轉換是在需要時動態發生的。這有助於實現極其快速的迭代周期和快速部署。 從這個開始吧。 ``` deno run -A -r https://fresh.deno.dev ``` Fresh 採用的最重要的架構決策是其對[島嶼架構模式](https://www.patterns.dev/vanilla/islands-architecture)的使用。 這意味著 Fresh 應用程式預設將純 HTML 發送到客戶端。然後,伺服器渲染頁面的某些部分可以透過互動式小工具(島嶼)獨立重新水化。 客戶端只負責渲染頁面中互動性足以保證額外工作的部分。任何純靜態內容都沒有相關的客戶端 JavaScript,因此非常輕量級。 您可以閱讀[文件](https://fresh.deno.dev/docs/introduction)。 您可以找到所有使用此建立的[網站](https://fresh.deno.dev/showcase),例如[Max Schmidt](https://mooxl.dev/)的投資組合網站。 ![使用新鮮製作的投資組合網站](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5j9xwjw86by873vjkvk0.png) 他們在 GitHub 上擁有超過 11k 個 star,並且發布了`v1.6`版本。 {% cta https://github.com/denoland/fresh %} 明星新鮮 ⭐️ {% endcta %} --- 15. [Vue.js](https://github.com/vuejs/core) - 用於在網路上建立 UI 的漸進式 JavaScript 框架。 ----------------------------------------------------------------------------- ![看法](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2a8rdd0xohokbf0mx35q.png) Vue.js 是一個漸進式框架,因為它能夠透過雙整合模式促進高階單頁 Web 應用程式的設計。閱讀[使用 Vue 的所有方法](https://vuejs.org/guide/extras/ways-of-using-vue.html),包括從嵌入 Web 元件到獨立腳本,甚至使用伺服器端渲染或靜態網站生成來建立複雜的應用程式。 ![vue 用例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nxetclturvms3ve712u9.png) 使用 MVVM(模型-視圖-視圖模型)架構,Vue.js 讓事情變得簡單、靈活且適合初學者。 Vue.js 於 2014 年由 Google 的開發人員 Evan You 首次推出,他從 AngularJS 中汲取靈感,提供了一種簡單、輕量級且高效的替代方案。 Vue.js 借用了 ReactJS 和 AngularJS 的一些功能,並對其進行了增強,以提供更流暢、更用戶友好的體驗。例如,Vue.js 將 AngularJS 的 2 路資料綁定與 React 的高效虛擬 DOM 結合。 與 React 不同,Vue 有一個內建的 MVC,可以快速輕鬆地進行設定。此外,Vue.js 的壓縮版本只有 18-20 kb,比其臃腫笨重的競爭對手(如 React 或 AngularJS)輕得多。 Vue.js 還包含一個方便的內建 CSS 過渡和動畫元件。 100 秒觀看 Vue.js 了解更多! {% 嵌入 https://www.youtube.com/watch?v=nhBVL41-\_Cw&amp;pp=ygUXZW1iZXIganMgaW4gMTAwIHNlY29uZHM%3D %} 開始使用以下 npm 指令。 ``` npm create vue@latest ``` 該命令將安裝並執行 create-vue,官方的 Vue 專案腳手架工具。您將收到有關多個可選功能的提示,例如 TypeScript 和測試支援。 這是啟動開發伺服器的方法。 ``` cd <your-project-name> npm install npm run dev ``` 一個簡單的應用程式。 ``` import { createApp } from 'vue' createApp({ data() { return { count: 0 } } }).mount('#app') <div id="app"> <button @click="count++"> Count is: {{ count }} </button> </div> ``` 上面的例子展示了Vue的兩個核心特性: 1. **聲明式渲染**:Vue 使用模板語法擴充了標準 HTML,該模板語法基於 JavaScript 狀態以聲明方式描述 HTML 輸出。 2. **反應性**:Vue 會自動追蹤 JavaScript 狀態變化,並在變化發生時有效地更新 DOM。 您也可以使用 CDN 來使用它,CDN 將使用全域建置。閱讀[快速入門指南](https://vuejs.org/guide/quick-start)以了解更多資訊。 您可以閱讀[文件](https://vuejs.org/guide/introduction)並查看不同主題的[程式碼編輯器範例](https://vuejs.org/examples/#hello-world),甚至可以了解如何建立 Markdown 編輯器。 要體驗 Vue.js,您也可以直接在他們的[現場 Playground](https://play.vuejs.org/#eNp9kVFLwzAQx7/KeS9TmBuiT6MOVAbqg4oKvuSltLeuM01CcpmF0u/utaXVhzEISe7/vyS/yzV459ziEAlXmITMl47XylDtrGfIaZtGzdAoA5CnnJ5fDHsATxy9GSOAKhQrmD2S1ha+rNf52Wyw2m6RSUaynB6QgKlyOmWSCCDZXa2bprsF2jZZStSrpXGR4XBZ2Zz0rULxFYqVLKfTOEcOmTXbsljsgzVSRw+lMLOVKzX5V8elNUHhasRVmArnz3OvsY80H/VsR9n3EX0f6k5T+OYpkD+Qwsnj1BfEg735eKFa9pMp5FFL9gnznYLVsWMc0u6jyQX7X15P+1R1PSlN8Rk2NZMJY1EdaP/Jfb5CaebDidL/cK8XN2NzsP0F+HSp8w==)中嘗試。 我非常喜歡的一篇關於 Vue 的文章是 Michael 在 DEV 上發表的。必讀! {% 嵌入 https://dev.to/michaelthiessen/25-vue-tips-you-need-to-know-2h70 %} 如果您剛開始,您可以按照他們的團隊建立的[官方教程](https://vuejs.org/tutorial/#step-1)進行操作。 ![教學](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sf7dhgd0843jkpru9y27.png) 與 Astro 類似,他們也有[課程部分](https://www.vuemastery.com/courses/)和[Vue School](https://vueschool.io/) ,您可以在其中找到各種主題。 ![賽車視圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4gixw8cd2ltkippi68h.png) Vue.js 為許多知名網站提供支持,包括 Font Awesome、Upwork 和 Namecheap 等。 Freecodecamp 有一個[針對初學者的 3 小時 Vue](https://www.youtube.com/watch?v=4deVCNJq3qc)教程,但我不推薦它,因為它是 2019 年的,而且我們知道這些框架中的概念變化有多快。 他們在 GitHub 上擁有超過 44k 個 star,並且發布了`v3.4`版本。它是有史以來最受開發人員喜愛的框架之一。 {% cta https://github.com/vuejs/core %} Star Vuejs ⭐️ {% endcta %} --- 還有很多其他框架,您可以查看其中一些: [Aurelia.js](https://github.com/aurelia/framework) 、 [Mithril.js](https://github.com/MithrilJS/mithril.js) 、 [Stimulus.js](https://github.com/hotwired/stimulus) 、 [Meteor.js](https://github.com/meteor/meteor) 、 [Angular.js](https://github.com/angular/angular) 、 [React.js](https://github.com/facebook/react) 、 [Knockout.js](https://github.com/knockout/knockout)和[Alpine.js](https://github.com/alpinejs/alpine) 。 是的,我知道,我同時感到 😵 和興奮。哈哈! 我有一些影片推薦,可以讓本文更加深入。 {% 嵌入 https://www.youtube.com/watch?v=cuHDQhDhvPE&amp;pp=ygUXZW1iZXIganMgaW4gMTAwIHNlY29uZHM%3D %} {% 嵌入 https://www.youtube.com/watch?v=WJRf7dh5Zws&amp;pp=ygURZW1iZXIganMgdHV0b3JpYWw%3D %} --- 我特意製作了這一系列教程,以幫助您在一個地方找到所有內容。我希望你喜歡這個! 雖然我是 Next.js 的忠實粉絲,但探索 Wing 等其他出色的框架可能非常適合您的下一個專案。 讓我們知道您計劃使用哪些框架,或者您認為其他人是否應該了解其他內容。 祝你有美好的一天!直到下一次。 我建立技術內容是為了幫助其他人每天成長 1%,這樣您就可以在 Twitter 和 LinkedIn 上關注我以獲得每日見解。 |如果你喜歡這類東西, 請關注我以了解更多:) | [![用戶名 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/) | |------------|----------| 關注Winglang以獲取更多此類內容。 {% 嵌入 https://dev.to/winglang %} --- 原文出處:https://dev.to/winglang/15-javascript-frameworks-for-your-next-project-1o7n