🔍 搜尋結果:promise

🔍 搜尋結果:promise

深入探討 Javascript 函數式編程

[訂閱我的程式設計 YouTube 頻道](https://www.youtube.com/@alexmercedcoder) [訂閱我的資料 YouTube 頻道](https://www.youtube.com/@alexmerceddata) 函數式程式設計 (FP) 在軟體開發領域獲得了巨大的關注,JavaScript 開發人員越來越多地轉向這種範例,以更有效地解決問題並減少錯誤。從本質上講,函數式程式設計強調使用純函數、不變性以及柯里化、記憶化和 monad 等先進技術來建立更清晰、更可預測的程式碼。 在這篇文章中,我們將深入研究每個概念,以了解它們的工作原理以及它們在 JavaScript 開發中的重要性。我們將探索**純函數**的無副作用性質、用於維護狀態可預測性的**不變性**、用於增強函數重用和組合的**柯里化**、用於優化性能的**記憶化**以及用於以函數式風格處理副作用的**monad** 。 無論您是函數式程式設計的新手,還是希望加深對其在 JavaScript 中的應用的理解,這篇文章都將為您提供堅實的基礎和實際範例,以便將這些原則整合到您的編碼實踐中。讓我們揭開這些概念的神秘面紗,看看它們如何改變您編寫 JavaScript 程式碼的方式。 1. 純函數 ------ **純函數**是一種函數,在給定相同的輸入的情況下,將始終返回相同的輸出,並且不會導致任何可觀察到的副作用。這個概念在函數式程式設計中至關重要,因為它允許開發人員編寫更可預測和可測試的程式碼。 ### 在 JavaScript 中使用純函數的好處: - **可預測性:**由於純函數不依賴或修改其範圍之外的資料狀態,因此它們更容易推理和除錯。 - **可重複使用性:**純函數可以在應用程式的不同部分重複使用,而無需考慮外部上下文。 - **可測試性:**沒有隱藏狀態或副作用,純函數很容易測試;輸入和輸出是您需要考慮的全部。 ### JavaScript 中純函數的範例: 考慮一個簡單的函數來計算矩形的面積: ``` function rectangleArea(length, width) { return length * width; } ``` 這個函數是純粹的,因為它總是使用相同的參數來傳回相同的結果,而且它不會修改任何外部狀態或產生副作用。 ### 常見陷阱以及如何避免它們: 雖然純函數提供了許多好處,但開發人員在嘗試將它們整合到與資料庫、外部服務或全域狀態互動的應用程式時可能會面臨挑戰。以下是保持純度的一些技巧: - **避免副作用:**不要修改函數內的任何外部變數或物件。 - **本機處理狀態:**如果您的函數需要存取應用程式狀態,請考慮將狀態作為參數傳遞並傳回新狀態而不修改原始狀態。 透過理解和實現純函數,開發人員可以在充分利用 JavaScript 中函數式程式設計的全部功能方面邁出重要一步。 2. 不變性 ------ **不變性**是指資料建立後永不更改的原則。您無需修改現有物件,而是建立一個包含所需變更的新物件。這是函數式程式設計的基石,因為它有助於防止副作用並在應用程式的整個生命週期中保持資料的完整性。 ### JavaScript 如何處理不變性: 預設情況下,JavaScript 物件和陣列是可變的,這表示在需要時必須注意強制執行不變性。但是,有幾種技術和工具可以提供幫助: - **使用`const` :**雖然`const`不會使變數變得不可變,但它可以防止將變數辨識碼重新指派給新值,這是邁向不變性的一步。 - **Object.freeze():**此方法可以透過防止向物件新增屬性和修改現有屬性來使物件不可變。 - **陣列和物件的擴展語法:**使用擴展語法可以幫助建立新的陣列或物件,同時合併現有陣列或物件的元素或屬性,而無需修改原始陣列或物件。 ### 確保 JavaScript 中資料不變性的技術: 1. **寫入時複製:**始終建立一個新的物件或陣列,而不是修改現有的物件或陣列。例如: ``` const original = { a: 1, b: 2 }; const modified = { ...original, b: 3 }; // 'original' is not changed ``` 2. **使用函式庫:**像 Immutable.js 這樣的函式庫提供了高度最佳化的持久不可變資料結構,可以簡化不變性的實作。 ### 幫助實施不變性的函式庫: - **Immutable.js:**提供一系列本質上不可變的資料結構。 - **immer:**允許您透過使用臨時草稿狀態並套用變更來產生新的不可變狀態,以更方便的方式處理不可變狀態。 透過將不變性整合到 JavaScript 專案中,您可以增強資料完整性、提高應用程式效能(透過減少防禦性複製的需求)並提高程式碼的可預測性。它完全符合函數式程式設計的原則,從而產生更乾淨、更健壯的軟體。 3.柯里化 ----- **柯里化**是函數式程式設計中的一種變革性技術,其中具有多個參數的函數被轉換為一系列函數,每個函數採用一個參數。這種方法不僅使您的函數更加模組化,而且還增強了程式碼的可重複使用性和可組合性。 ### JavaScript 柯里化的實際用途: 柯里化允許建立高階函數,這些函數可以在應用程式的不同點使用不同的參數進行自訂和重複使用。它特別適用於: - **事件處理:**建立針對特定事件自訂的部分應用函數,但重複使用通用處理程序邏輯。 - **API 呼叫:**使用預定義參數(例如 API 金鑰或使用者 ID)設定函數,這些參數可以在不同的呼叫中重複使用。 ### 說明柯里化的逐步範例: 考慮一個簡單的函數來加兩個數字: ``` function add(a, b) { return a + b; } // Curried version of the add function function curriedAdd(a) { return function(b) { return a + b; }; } const addFive = curriedAdd(5); console.log(addFive(3)); // Outputs: 8 ``` 此範例展示了柯里化如何將簡單的加法函數轉變為更通用和可重複使用的函數。 ### 柯里化與部分應用: 雖然柯里化和部分應用都涉及將函數分解為更簡單、更具體的函數,但它們並不相同: - **柯里化(Currying):**將具有多個參數的函數轉換為一系列巢狀函數,每個函數只接受一個參數。 - **部分應用:**涉及透過預先填充一些參數來建立具有較少參數的函數。 這兩種技術在函數式程式設計中都很有價值,可以用來簡化複雜的函數簽名並提高程式碼模組化。 透過利用柯里化,開發人員可以增強函數的可重複使用性和組合性,從而在 JavaScript 專案中產生更清晰、更易於維護的程式碼。 4. 記憶 ----- **記憶化**是函數式程式設計中使用的最佳化技術,透過儲存昂貴的函數呼叫的結果並在相同的輸入再次發生時返回快取的結果來加速電腦程式。它在 JavaScript 中對於優化涉及繁重計算任務的應用程式的效能特別有用。 ### 為什麼記憶化在 JavaScript 很重要: - **效率:**減少使用相同參數重複呼叫函數所需的計算次數。 - **效能:**透過快取耗時操作的結果來提高應用程式回應能力。 - **可擴展性:**透過最小化計算開銷來幫助管理更大的資料集或更複雜的演算法。 ### 實現記憶化:範例和常用方法: 以下是 JavaScript 中記憶函數的基本範例: ``` function memoize(fn) { const cache = {}; return function(...args) { const key = args.toString(); if (!cache[key]) { cache[key] = fn.apply(this, args); } return cache[key]; }; } const factorial = memoize(function(x) { if (x === 0) { return 1; } else { return x * factorial(x - 1); } }); console.log(factorial(5)); // Calculates and caches the result console.log(factorial(5)); // Returns the cached result ``` 此範例示範了記憶化如何快取階乘計算的結果,從而顯著減少重複呼叫的計算時間。 ### 記憶化的優點和潛在缺點: #### 好處: - 顯著減少重複操作的處理時間。 - 透過避免冗餘計算來提高應用程式效率。 - 易於用高階函數實現。 #### 缺點: - 由於快取而增加記憶體使用量。 - 不適合具有不確定性輸出的函數或具有副作用的函數。 透過理解和實現記憶化,開發人員可以優化他們的 JavaScript 應用程式,使其更快、更有效率。然而,重要的是要考慮額外記憶體使用方面的權衡,並確保僅在能夠提供明顯好處的地方應用記憶化。 5. 單子 ----- **Monad**是函數式程式設計中使用的抽象資料類型,用於處理副作用,同時保持純函數原則。它們將行為和邏輯封裝在靈活的可連結結構中,允許順序操作,同時保持函數的純淨。 ### Monad 簡介及其在 FP 中的意義: Monad 提供了一個以受控方式處理副作用(如 IO、狀態、異常等)的框架,有助於保持功能的純度和可組合性。在 JavaScript 中,Promise 是一個常見的一元結構範例,可以乾淨且有效率地管理非同步操作。 ### JavaScript 中 Monad 的範例: - **Promises:**透過封裝待處理操作、成功值或錯誤來處理非同步操作,允許方法連結(如`.then()`和`.catch()` ): ``` new Promise((resolve, reject) => { setTimeout(() => resolve("Data fetched"), 1000); }) .then(data => console.log(data)) .catch(error => console.error(error)); ``` - **Maybe Monad:**透過封裝可能存在或不存在的值來幫助處理 null 或未定義的錯誤: ``` function Maybe(value) { this.value = value; } Maybe.prototype.bind = function(transform) { return this.value == null ? this : new Maybe(transform(this.value)); }; Maybe.prototype.toString = function() { return `Maybe(${this.value})`; }; const result = new Maybe("Hello, world!").bind(value => value.toUpperCase()); console.log(result.toString()); // Outputs: Maybe(HELLO, WORLD!) ``` ### Monad 定律與結構: Monad 必須遵循三個核心定律——同一性、關聯性和單位——以確保它們的行為可預測: - **同一性:**直接應用函數或透過 monad 傳遞函數應該會產生相同的結果。 - **關聯性:**執行操作的順序(連結)不影響結果。 - **單位:**一個值必須能夠被提升為一個單子而不改變其行為。 理解這些定律對於在函數式程式設計中有效地實現或利用 monad 至關重要。 ### Monad 如何管理副作用並保持功能純度: 透過封裝副作用,monad 允許開發人員保持程式碼庫的其餘部分純淨,從而更易於理解和維護。它們使副作用可預測和可管理,這對於維護狀態一致性和錯誤處理可能變得具有挑戰性的大型應用程式至關重要。 透過利用 monad,開發人員可以增強 JavaScript 應用程式的功能,確保它們以功能性的方式處理副作用,從而提高程式碼的可靠性和可維護性。 6. 這些概念如何相互關聯 ------------- 純函數、不變性、柯里化、記憶化和 monad 的概念不僅僅是單獨的元素,而是增強 JavaScript 應用程式的健全性和可維護性的互連工具。以下是他們如何共同創造一個有凝聚力的函數式程式設計環境。 ### 建立功能協同: - **純函數與不變性:**純函數確保函數沒有副作用,並為相同的輸入傳回相同的輸出,並透過防止資料意外變更的不變性來補充。它們共同確保了可預測且穩定的程式碼庫。 - **柯里化和記憶化:**柯里化允許將函數分解為更簡單的單參數函數,這些函數更易於管理和記憶。然後可以將記憶化應用於這些柯里化函數以緩存其結果,透過避免重複計算來優化應用程式的效能。 - **Monad 和純函數:** Monad 有助於以受控方式管理副作用,這使得純函數即使在處理 I/O 或狀態轉換等操作時也能保持純淨。這種副作用的封裝保留了功能架構的完整性。 ### 範例:一個小型功能模組: 讓我們考慮一個將這些概念結合在一起的實際範例。假設我們正在建立一個簡單的用戶註冊模組: ``` // A pure function to validate user input const validateInput = input => input.trim() !== ''; // A curried function for creating a user object const createUser = name => ({ id: Date.now(), name }); // Memoizing the createUser function to avoid redundant operations const memoizedCreateUser = memoize(createUser); // A monad for handling potential null values in user input const getUser = input => new Maybe(input).bind(validateInput); // Example usage const input = getUser(' John Doe '); const user = input.bind(memoizedCreateUser); console.log(user.toString()); // Outputs user details or empty Maybe ``` 在此範例中, `validateInput`是確保輸入有效性的純函數。 `createUser`是一個柯里化和記憶化的函數,針對效能進行了最佳化,而`getUser`使用 monad 來安全地處理潛在的 null 值。 結論: --- 理解和整合這些函數式程式設計概念可以顯著提高 JavaScript 程式碼的品質和可維護性。透過同時使用純函數、不變性、柯里化、記憶化和 monad,開發人員可以建立更可靠、更有效率、更乾淨的應用程式。 透過採用這些相互關聯的原則,JavaScript 開發人員可以充分利用函數式程式設計的潛力來編寫更好、更永續的程式碼。 --- 原文出處:https://dev.to/alexmercedcoder/deep-dive-into-functional-programming-in-javascript-851

如何讓 AI 融入您的使用者(Next.js、OpenAI、CopilotKit)

長話短說 ---- 在本文中,您將了解如何建立基於 AI 的行銷活動管理應用程式,該應用程式可讓您建立和分析廣告活動,從而使您能夠為您的業務做出正確的決策。 我們將介紹如何: - 使用 Next.js 建立 Web 應用程式, - 使用 CopilotKit 將 AI 助理整合到軟體應用程式中,以及 - 建立特定於操作的人工智慧副駕駛來處理應用程式中的各種任務。 - 建立一名競選經理 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9xqpz356qm79t90f1l87.gif) --- CopilotKit:建構應用內人工智慧副駕駛的框架 -------------------------- CopilotKit是一個[開源的AI副駕駛平台](https://github.com/CopilotKit/CopilotKit)。我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。 建造: - ChatBot:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 💬 - CopilotTextArea:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能📝 - 聯合代理:應用程式內人工智慧代理,可以與您的應用程式和使用者互動🤖 ![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%2Fx3us3vc140aun0dvrdof.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%2Fx3us3vc140aun0dvrdof.gif) {% cta https://git.new/devtoarticle1 %} Star CopilotKit ⭐️ {% endcta %} --- 先決條件 ---- 要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。 我們還將利用以下內容: - [Radix UI](https://www.radix-ui.com/) - 用於為應用程式建立可存取的 UI 元件。 - [OpenAI API 金鑰](https://platform.openai.com/api-keys)- 使我們能夠使用 GPT 模型執行各種任務。 - [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 一個開源副駕駛框架,用於建立自訂 AI 聊天機器人、應用程式內 AI 代理程式和文字區域。 --- 專案設定和套件安裝 --------- 首先,透過在終端機中執行以下程式碼片段來建立 Next.js 應用程式: ``` npx create-next-app campaign-manager ``` 選擇您首選的配置設定。在本教學中,我們將使用 TypeScript 和 Next.js App Router。 ![Next.js 應用程式安裝](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xboujt60i6lpoaqgjyap.png) 接下來,將[Heroicons](https://www.npmjs.com/package/@heroicons/react) 、 [Radix UI](https://www.radix-ui.com/)及其原始元件安裝到專案中。 ``` npm install @heroicons/react @radix-ui/react-avatar @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-icons @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-slot @radix-ui/react-tabs ``` 另外,安裝[Recharts 程式庫](https://recharts.org/en-US)(一個用於建立互動式圖表的 React 程式庫)以及以下實用程式套件: ``` npm install recharts class-variance-authority clsx cmdk date-fns lodash react-day-picker tailwind-merge tailwindcss-animate ``` 最後,安裝[CopilotKit 軟體套件](https://docs.copilotkit.ai/getting-started/quickstart-chatbot)。這些套件使 AI copilot 能夠從 React 狀態檢索資料並在應用程式中做出決策。 ``` npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend ``` 恭喜!您現在已準備好建立應用程式。 --- 使用 Next.js 建立 Campaign Manager 應用程式 ----------------------------------- 在本節中,我將引導您建立活動管理器應用程式的使用者介面。 首先,讓我們進行一些初始設定。 在`src`資料夾中建立一個`components`和`lib`資料夾。 ``` cd src mkdir components lib ``` 在**`lib`**資料夾中,我們將聲明應用程式的靜態類型和預設活動。因此,在**`lib`**資料夾中建立**`data.ts`**和**`types.ts`**檔案。 ``` cd lib touch data.ts type.ts ``` 將下面的程式碼片段複製到`type.ts`檔中。它聲明了活動屬性及其資料類型。 ``` export interface Campaign { id: string; objective?: | "brand-awareness" | "lead-generation" | "sales-conversion" | "website-traffic" | "engagement"; title: string; keywords: string; url: string; headline: string; description: string; budget: number; bidStrategy?: "manual-cpc" | "cpa" | "cpm"; bidAmount?: number; segment?: string; } ``` 為應用程式建立預設的行銷活動清單並將其複製到`data.ts`檔案中。 ``` import { Campaign } from "./types"; export let DEFAULT_CAMPAIGNS: Campaign[] = [ { id: "1", title: "CopilotKit", url: "https://www.copilotkit.ai", headline: "Copilot Kit - The Open-Source Copilot Framework", description: "Build, deploy, and operate fully custom AI Copilots. In-app AI chatbots, AI agents, AI Textareas and more.", budget: 10000, keywords: "AI, chatbot, open-source, copilot, framework", }, { id: "2", title: "EcoHome Essentials", url: "https://www.ecohomeessentials.com", headline: "Sustainable Living Made Easy", description: "Discover our eco-friendly products that make sustainable living effortless. Shop now for green alternatives!", budget: 7500, keywords: "eco-friendly, sustainable, green products, home essentials", }, { id: "3", title: "TechGear Solutions", url: "https://www.techgearsolutions.com", headline: "Innovative Tech for the Modern World", description: "Find the latest gadgets and tech solutions. Upgrade your life with smart technology today!", budget: 12000, keywords: "tech, gadgets, innovative, modern, electronics", }, { id: "4", title: "Global Travels", url: "https://www.globaltravels.com", headline: "Travel the World with Confidence", description: "Experience bespoke travel packages tailored to your dreams. Luxury, adventure, relaxation—your journey starts here.", budget: 20000, keywords: "travel, luxury, adventure, tours, global", }, { id: "5", title: "FreshFit Meals", url: "https://www.freshfitmeals.com", headline: "Healthy Eating, Simplified", description: "Nutritious, delicious meals delivered to your door. Eating well has never been easier or tastier.", budget: 5000, keywords: "healthy, meals, nutrition, delivery, fit", }, ]; ``` 由於我們使用 Radix UI 建立可以使用 TailwindCSS 輕鬆自訂的基本 UI 元件,因此請在**`lib`**資料夾中建立一個**`utils.ts`**文件,並將以下程式碼片段複製到該文件中。 ``` //👉🏻 The lib folder now contains 3 files - data.ts, type.ts, util.ts //👇🏻 Copy the code below into the "lib/util.ts" file. import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export function randomId() { return Math.random().toString(36).substring(2, 15); } ``` 導航到`components`資料夾並在其中建立其他三個資料夾。 ``` cd components mkdir app dashboard ui ``` `components/app`資料夾將包含應用程式中使用的各種元件,而儀表板資料夾包含某些元素的 UI 元件。 `ui`資料夾包含使用 Radix UI 建立的多個 UI 元素。將[專案儲存庫中的這些元素](https://github.com/CopilotKit/campaign-manager-demo/tree/main/src/components/ui)複製到該資料夾中。 恭喜! `ui`資料夾應包含必要的 UI 元素。現在,我們可以使用它們來建立應用程式中所需的各種元件。 ### 建立應用程式 UI 元件 在這裡,我將引導您完成為應用程式建立使用者介面的過程。 ![應用程式使用者介面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9l4lvv4394gg033oatwq.png) 首先,導航至**`app/page.tsx`**檔案並將以下程式碼片段貼到其中。該文件呈現在**`components/app`**資料夾中聲明的 App 元件。 ``` "use client"; import { App } from "@/components/app/App"; export default function DashboardPage() { return <App />; } ``` 在`components/app`資料夾中建立`App.tsx` 、 `CampaignForm.tsx` 、 `MainNav.tsx`和`UserNav.tsx`檔案。 ``` cd components/app touch App.tsx CampaignForm.tsx MainNav.tsx UserNav.tsx ``` 將下面的程式碼片段複製到`App.tsx`檔案中。 ``` "use client"; import { DEFAULT_CAMPAIGNS } from "@/lib/data"; import { Campaign } from "@/lib/types"; import { randomId } from "@/lib/utils"; import { Dashboard } from "../dashboard/Dashboard"; import { CampaignForm } from "./CampaignForm"; import { useState } from "react"; import _ from "lodash"; export function App() { //👇🏻 default segments const [segments, setSegments] = useState<string[]>([ "Millennials/Female/Urban", "Parents/30s/Suburbs", "Seniors/Female/Rural", "Professionals/40s/Midwest", "Gamers/Male", ]); const [campaigns, setCampaigns] = useState<Campaign[]>( _.cloneDeep(DEFAULT_CAMPAIGNS) ); //👇🏻 updates campaign list function saveCampaign(campaign: Campaign) { //👇🏻 newly created campaign if (campaign.id === "") { campaign.id = randomId(); setCampaigns([campaign, ...campaigns]); } else { //👇🏻 existing campaign - search for the campaign and updates the campaign list const index = campaigns.findIndex((c) => c.id === campaign.id); if (index === -1) { setCampaigns([...campaigns, campaign]); } else { campaigns[index] = campaign; setCampaigns([...campaigns]); } } } const [currentCampaign, setCurrentCampaign] = useState<Campaign | undefined>( undefined ); return ( <div className='relative'> <CampaignForm segments={segments} currentCampaign={currentCampaign} setCurrentCampaign={setCurrentCampaign} saveCampaign={(campaign) => { if (campaign) { saveCampaign(campaign); } setCurrentCampaign(undefined); }} /> <Dashboard campaigns={campaigns} setCurrentCampaign={setCurrentCampaign} segments={segments} setSegments={setSegments} /> </div> ); } ``` - 從上面的程式碼片段來看, - 我為行銷活動建立了預設細分列表,並對已定義的行銷活動列表進行了深層複製。 - `saveCampaign`函數接受行銷活動作為參數。如果行銷活動沒有 ID,則表示它是新建立的,因此會將其新增至行銷活動清單。否則,它會找到該活動並更新其屬性。 - `Dashboard`和`CampaignForm`元件接受細分和行銷活動作為 props。 [Dashboard 元件](https://github.com/CopilotKit/campaign-manager-demo/blob/main/src/components/dashboard/Dashboard.tsx)在儀表板上顯示各種 UI 元素,而[CampaignForm 元件](https://github.com/CopilotKit/campaign-manager-demo/blob/main/src/components/app/CampaignForm.tsx)使用戶能夠在應用程式中建立和保存新的行銷活動。 您也可以使用[GitHub 儲存庫](https://github.com/CopilotKit/campaign-manager-demo/tree/main/src/components)中的程式碼片段來更新儀表板和應用程式元件。 恭喜!您應該有一個有效的 Web 應用程式,可讓使用者查看和建立新的行銷活動。 在接下來的部分中,您將了解如何將 CopilotKit 加入到應用程式中,以根據每個行銷活動的目標和預算進行分析和決策。 ![應用概述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4phyaucli8pdcbe625u4.gif) --- 使用 CopilotKit 透過 AI 分析廣告活動 -------------------------- 在這裡,您將學習如何將人工智慧加入到應用程式中,以幫助您分析行銷活動並做出最佳決策。 在繼續之前,請造訪[OpenAI 開發者平台](https://platform.openai.com/api-keys)並建立一個新的金鑰。 ![取得 OpenAI API 金鑰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02972pt2aj3kf9l5suqq.png) 建立一個`.env.local`檔案並將新建立的金鑰複製到該檔案中。 ``` OPENAI_API_KEY=<YOUR_OPENAI_SECRET_KEY> OPENAI_MODEL=gpt-4-1106-preview ``` 接下來,您需要為 CopilotKit 建立 API 端點。在 Next.js 應用程式資料夾中,建立一個包含`route.ts`檔案的`api/copilotkit`資料夾。 ``` cd app mkdir api && cd api mkdir copilotkit && cd copilotkit touch route.ts ``` 將下面的程式碼片段複製到`route.ts`檔中。 [CopilotKit 後端](https://docs.copilotkit.ai/reference/CopilotBackend)接受使用者的請求並使用 OpenAI 模型做出決策。 ``` import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend"; export const runtime = "edge"; export async function POST(req: Request): Promise<Response> { const copilotKit = new CopilotBackend({}); const openaiModel = process.env["OPENAI_MODEL"]; return copilotKit.response(req, new OpenAIAdapter({ model: openaiModel })); } ``` 若要將您的應用程式連接到此 API 端點,請更新`app/page.tsx`文件,如下所示: ``` "use client"; import { App } from "@/components/app/App"; import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; export default function DashboardPage() { return ( <CopilotKit url='/api/copilotkit/'> <CopilotSidebar instructions='Help the user create and manage ad campaigns.' defaultOpen={true} labels={{ title: "Campaign Manager Copilot", initial: "Hello there! I can help you manage your ad campaigns. What campaign would you like to work on?", }} clickOutsideToClose={false} > <App /> </CopilotSidebar> </CopilotKit> ); } ``` `CopilotKit`元件包裝整個應用程式並接受包含 API 端點連結的`url`屬性。 `CopilotSidebar`元件為應用程式加入了一個聊天機器人側邊欄面板,使我們能夠向 CopilotKit 提供各種指令。 ![將 CopilotKit 加入 Next.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wzh8ui253ftzmgtrcksd.gif) ### 如何讓AI副駕駛執行各種動作 CopilotKit 提供了兩個鉤子,使我們能夠處理使用者的請求並插入應用程式狀態: [useCopilotAction](https://docs.copilotkit.ai/reference/useCopilotAction)和[useMakeCopilotReadable](https://docs.copilotkit.ai/reference/useMakeCopilotReadable) 。 `useCopilotAction`掛鉤可讓您定義 CopilotKit 執行的動作。它接受包含以下參數的物件: - name - 操作的名稱。 - 描述 - 操作的描述。 - 參數 - 包含所需參數清單的陣列。 - render - 預設的自訂函數或字串。 - handler - 由操作觸發的可執行函數。 ``` useCopilotAction({ name: "sayHello", description: "Say hello to someone.", parameters: [ { name: "name", type: "string", description: "name of the person to say greet", }, ], render: "Process greeting message...", handler: async ({ name }) => { alert(`Hello, ${name}!`); }, }); ``` `useMakeCopilotReadable`掛鉤向 CopilotKit 提供應用程式狀態。 ``` import { useMakeCopilotReadable } from "@copilotkit/react-core"; const appState = ...; useMakeCopilotReadable(JSON.stringify(appState)); ``` CopilotKit 還允許您為使用者提示提供上下文,使其能夠做出充分且準確的決策。 將`guidance.ts`和`script.ts`加入到專案內的`lib`資料夾中,並將此[指導](https://github.com/CopilotKit/campaign-manager-demo/blob/main/src/lib/guideline.ts)和[腳本建議](https://github.com/CopilotKit/campaign-manager-demo/blob/main/src/lib/script.ts)複製到檔案中,以便 CopilotKit 做出決策。 在應用程式元件中,將當前日期、腳本建議和指導傳遞到 CopilotKit。 ``` import { GUIDELINE } from "@/lib/guideline"; import { SCRIPT_SUGGESTION } from "@/lib/script"; import { useCopilotAction, useMakeCopilotReadable, } from "@copilotkit/react-core"; export function App() { //-- 👉🏻 ...other component functions //👇🏻 Ground the Copilot with domain-specific knowledge for this use-case: marketing campaigns. useMakeCopilotReadable(GUIDELINE); useMakeCopilotReadable(SCRIPT_SUGGESTION); //👇🏻 Provide the Copilot with the current date. useMakeCopilotReadable("Today's date is " + new Date().toDateString()); return ( <div className='relative'> <CampaignForm segments={segments} currentCampaign={currentCampaign} setCurrentCampaign={setCurrentCampaign} saveCampaign={(campaign) => { if (campaign) { saveCampaign(campaign); } setCurrentCampaign(undefined); }} /> <Dashboard campaigns={campaigns} setCurrentCampaign={setCurrentCampaign} segments={segments} setSegments={setSegments} /> </div> ); } ``` 在`App`元件中建立一個 CopilotKit 操作,該操作可在使用者提供此類指令時建立新的活動或編輯現有的活動。 ``` useCopilotAction({ name: "updateCurrentCampaign", description: "Edit an existing campaign or create a new one. To update only a part of a campaign, provide the id of the campaign to edit and the new values only.", parameters: [ { name: "id", description: "The id of the campaign to edit. If empty, a new campaign will be created", type: "string", }, { name: "title", description: "The title of the campaign", type: "string", required: false, }, { name: "keywords", description: "Search keywords for the campaign", type: "string", required: false, }, { name: "url", description: "The URL to link the ad to. Most of the time, the user will provide this value, leave it empty unless asked by the user.", type: "string", required: false, }, { name: "headline", description: "The headline displayed in the ad. This should be a 5-10 words", type: "string", required: false, }, { name: "description", description: "The description displayed in the ad. This should be a short text", type: "string", required: false, }, { name: "budget", description: "The budget of the campaign", type: "number", required: false, }, { name: "objective", description: "The objective of the campaign", type: "string", enum: [ "brand-awareness", "lead-generation", "sales-conversion", "website-traffic", "engagement", ], }, { name: "bidStrategy", description: "The bid strategy of the campaign", type: "string", enum: ["manual-cpc", "cpa", "cpm"], required: false, }, { name: "bidAmount", description: "The bid amount of the campaign", type: "number", required: false, }, { name: "segment", description: "The segment of the campaign", type: "string", required: false, enum: segments, }, ], handler: (campaign) => { const newValue = _.assign( _.cloneDeep(currentCampaign), _.omitBy(campaign, _.isUndefined) ) as Campaign; setCurrentCampaign(newValue); }, render: (props) => { if (props.status === "complete") { return "Campaign updated successfully"; } else { return "Updating campaign"; } }, }); ``` {% 嵌入 https://www.youtube.com/watch?v=gCJpH6Tnj5g %} 新增另一個模擬 API 呼叫的操作,以允許 CopilotKit 從先前建立的活動中檢索歷史資料。 ``` // Provide this component's Copilot with the ability to retrieve historical cost data for certain keywords. // Will be called automatically when needed by the Copilot. useCopilotAction({ name: "retrieveHistoricalData", description: "Retrieve historical data for certain keywords", parameters: [ { name: "keywords", description: "The keywords to retrieve data for", type: "string", }, { name: "type", description: "The type of data to retrieve for the keywords.", type: "string", enum: ["CPM", "CPA", "CPC"], }, ], handler: async ({ type }) => { // fake an API call that retrieves historical data for cost for certain keywords based on campaign type (CPM, CPA, CPC) await new Promise((resolve) => setTimeout(resolve, 2000)); function getRandomValue(min: number, max: number) { return (Math.random() * (max - min) + min).toFixed(2); } if (type == "CPM") { return getRandomValue(0.5, 10); } else if (type == "CPA") { return getRandomValue(5, 100); } else if (type == "CPC") { return getRandomValue(0.2, 2); } }, render: (props) => { // Custom in-chat component rendering. Different components can be rendered based on the status of the action. let label = "Retrieving historical data ..."; if (props.args.type) { label = `Retrieving ${props.args.type} for keywords ...`; } if (props.status === "complete") { label = `Done retrieving ${props.args.type} for keywords.`; } const done = props.status === "complete"; return ( <div className=''> <div className=' w-full relative max-w-xs'> <div className='absolute inset-0 h-full w-full bg-gradient-to-r from-blue-500 to-teal-500 transform scale-[0.80] bg-red-500 rounded-full blur-3xl' /> <div className='relative shadow-xl bg-gray-900 border border-gray-800 px-4 py-8 h-full overflow-hidden rounded-2xl flex flex-col justify-end items-start'> <h1 className='font-bold text-sm text-white mb-4 relative z-50'> {label} </h1> <p className='font-normal text-base text-teal-200 mb-2 relative z-50 whitespace-pre'> {props.args.type && `Historical ${props.args.type}: ${props.result || "..."}`} </p> </div> </div> </div> ); }, }); ``` ![應用程式預覽](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kz3xm63ciq5q3kyooz9s.gif) 恭喜!您已完成本教學的專案。 結論 -- [CopilotKit](https://copilotkit.ai/)是一款令人難以置信的工具,可讓您在幾分鐘內將 AI Copilot 加入到您的產品中。無論您是對人工智慧聊天機器人和助理感興趣,還是對複雜任務的自動化感興趣,CopilotKit 都能讓您輕鬆實現。 如果您需要建立 AI 產品或將 AI 工具整合到您的軟體應用程式中,您應該考慮 CopilotKit。 您可以在 GitHub 上找到本教學的源程式碼: <https://github.com/CopilotKit/campaign-manager-demo> 感謝您的閱讀! --- 原文出處:https://dev.to/copilotkit/build-an-ai-powered-campaign-manager-nextjs-openai-copilotkit-59ii

比較前 3 個 Javascript 測試框架

### 介紹 每個開發人員都知道編寫測試很重要,但是當涉及 JavaScript 測試時,[有許多框架](https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScript)可供選擇。那麼,下次啟動專案時,如何知道該選擇哪個框架呢? 在本文中,我將比較三個流行的框架[——Mocha](https://github.com/mochajs/mocha) 、 [Jest](https://jestjs.io/)和[Jasmine——](https://jasmine.github.io/)以幫助您做出更明智的決定。我將了解這些框架如何處理常見的測試場景,例如模擬函數和非同步呼叫。我將展示如何實施這些測試的範例。我還將討論一些最佳實踐以及為什麼應該使用測試框架。 ### 三個框架 Mocha、Jest 和 Jasmine 都是流行的框架,擁有有用的社群和多年的開發經驗。總體而言,Mocha 和 Jasmine 更適合測試後端,因為它們最初是為 Node 應用程式建立的;因此,他們比 Jest 擁有更多可用的後端工具和文件。對於前端來說,你的測試框架的選擇通常會受到你的前端框架的影響。 Jasmine 更常與 Angular 一起使用,而 Jest 是 Facebook 建立的與 React 一起使用。 無論您選擇哪一個框架,這三個框架都是成熟且有效的選擇。最好的選擇實際上取決於您的專案需求和您的個人喜好。為了幫助您決定哪個框架最適合您,讓我們看看每個框架在一些常見測試場景下的實際應用。 ### 模擬函數 您在應用程式中測試的最常見的事情是函數呼叫。為您的函數編寫可靠的測試非常重要,因為無論測試框架如何,糟糕的測試都可能觸發真實的函數,從而導致記憶體洩漏和瀏覽器中的意外行為。 測試函數呼叫時,您的測試應該: - 專注於函數呼叫的預期結果,而不是函數的實現 - 永遠不要更改應用程式的狀態 - 使用模擬函數,這樣您就不必擔心測試中出現意外的副作用 以下是如何在 Jest、Jasmine 和 Mocha 中模擬前端函數呼叫的範例。 **是** 如果您使用 React,Jest 不需要太多依賴項(如果有的話)。但是,如果您不想深入[研究react-testing-library](https://testing-library.com/docs/react-testing-library/intro) ,Jest也與一些特定於測試的函式庫(例如Enzyme)相容。此範例使用[Enzyme](https://enzymejs.github.io/enzyme/)對元件進行淺層渲染,按一下按鈕,然後查看模式是否已開啟。在這裡,您必須渲染元件並模擬單擊,以查看您的模擬函數呼叫是否按預期開啟模式。 ![Jest 中的模擬函數呼叫](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/b0bfcd5cf08dd70522fc4b8d5f1b9f3995a962452ddf5634.png/w_752) *Jest 中的模擬函數呼叫* **茉莉花** 在所有框架中,Jasmine 更適合 Angular。但是,一旦為 React 設定了所有正確的配置和幫助程式文件,編寫測試就不需要太多程式碼。 在這裡您可以看到使用[ReactTestUtils](https://reactjs.org/docs/test-utils.html)代替 Enzyme 或 React-testing-library(以顯示其他可用工具之一)。 ReactTestUtils 使得在前端使用 Jasmine 變得更加容易,並保持較低的程式碼行數。但是,您需要了解 ReactTestUtils API。 ![Jasmine 中的模擬函數呼叫](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/480043c0f2668f7edac640818492acfadbfa8f06a0ec858e.png/w_701) *Jasmine 中的模擬函數呼叫* **摩卡** Mocha 為您提供了更多的靈活性,因為它通常用於前端和後端測試。您必須匯入幾個庫,例如[Chai](https://www.chaijs.com/) ,才能使其與 React 一起使用。雖然 Chai 沒有直接連接到 React,但它是 Mocha 中最常用的斷言函式庫。安裝這些依賴項後,就類似於使用 Jest。此範例結合使用 Enzyme 進行渲染和 Chai 進行斷言。 ![Mocha 中的模擬函數呼叫](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/1a0a8d5b9414903de0665907e074d79879deb18fca3901bb.png/w_821) *Mocha 中的模擬函數呼叫* #### 我的看法 對於模擬函數,這三個函式庫在程式碼行數和複雜性方面都非常相似。我建議只使用最適合您的堆疊的程式庫:Jest for React、Jasmine for Angular 和 Mocha(如果您也在後端使用 Mocha 並且希望保持一致)。 ### 模擬資料 後端測試與前端測試一樣棘手。對於處理資料尤其如此,因為您不希望測試將資料插入真實資料庫中。如果您不小心,這種危險的副作用很容易潛入您的測試套件中。這就是為什麼使用模擬資料設定測試資料庫是最佳實踐的原因。 當您使用模擬資料時,您可以: - 準確地看到錯誤發生的位置,因為您知道期望的值是什麼 - 類型檢查您的後端響應並確保響應不會洩露真實資料 - 更快發現錯誤 您會經常遇到模擬資料以在請求中發送的情況,而這三個框架都支援這種情況。以下是這三個框架如何實現模擬資料的範例。 **是** 在此 Jest 測試中需要注意的最重要的事情是如何檢查資料是否已成功傳遞到 API 或資料庫。最後有幾個 Expect() 匹配器,它們的順序很重要。在發送模擬資料後,您必須準確地告訴 Jest 您希望找到什麼。這裡使用[supertest](https://www.npmjs.com/package/supertest)函式庫來使用假資料發出模擬 post 請求。 ![在 Jest 後端測試中處理資料](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/39d0b290154eb4dd1a79e0a2a96f8bfeadcdd9a1fa5dd2ec.png/w_559) *在 Jest 後端測試中處理資料* **茉莉花** 雖然編寫良好的後端 Jasmine 測試需要更多程式碼,但您可以控制建立和重置資料的方式和時間。 Jasmine 還具有內建工具,用於在測試的其他部分引用模擬資料。此範例使用[請求](https://www.npmjs.com/package/request)庫來處理模擬發布資料請求。 ![在 Jasmine 後端測試中處理資料](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/6f7f5e5070d8c2106f7b7264af5e3f8d38c342ddec26e9e5.png/w_638) *在 Jasmine 後端測試中處理資料* **摩卡** 在所有框架中,Mocha 需要最多的依賴來處理模擬資料和請求。您可能需要使用[chai-http](https://www.app.devspotlight.com/stories/draft/Comparing-the-top-3-Javascript-testing-frameworks)設定一個模擬伺服器來執行請求,而不是像其他伺服器一樣模擬請求和回應。 Mocha 確實有很好的內建工具,但需要更多時間才能上手。使用 Chai 及其相關函式庫是常見的做法,如下例所示: ![在 Mocha 後端測試中處理資料](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/e9535c9618549c6149ee5ca2c8668a104cf61b433709d20f.png/w_507) *在 Mocha 後端測試中處理資料* #### 我的看法 後端測試是 Mocha 和 Jasmine 最強的地方。它們是為測試 Node 應用程式而建構的,這一點在它們的工具中得到了體現。它們使您可以透過框架中包含的選項和功能進行更精細的控制。如果您願意花時間加入一些可用的庫,Jest 仍然是一個不錯的選擇。 ### 模擬異步呼叫 眾所周知,非同步程式碼會引起問題,因此此處的測試尤其重要。您不僅必須注意自己程式碼中的非同步為,而且許多進入生產的錯誤可能來自意外的非同步來源,例如第三方服務。當您編寫具有異步行為的測試時,請盡量避免觸發真正的函數呼叫,因為測試的非同步呼叫與實際程式碼的執行重疊。 所有測試框架都為您提供了編寫非同步程式碼的多種選項。如果您的程式碼使用回調,那麼您的測試就可以使用回呼。如果可以選擇,請考慮使用[async/await 模式](https://javascript.info/async-await)。它使您的程式碼更具可讀性,並幫助您快速找到測試的問題所在。 [Promise](https://javascript.info/promise-basics)也是編寫非同步測試的選擇。如果您正在使用不支援非同步/等待的舊程式碼,請考慮使用這些。但是,請確保它們按照您在生產中期望的順序執行。在整個執行過程中檢查您的值可以幫助捕捉奇怪的行為。 以下是 JavaScript 測試框架中的非同步測試範例。 **是** 儘管使用Jest 編寫後端測試很簡單,但由於它最初是為了與React 一起使用而建立的,因此您最終可能會花一些時間閱讀文件並安裝第三方庫,因為它的大多數工具都是特定於前端的。但 Jest 確實可以處理您需要處理的任何格式的非同步呼叫,例如回呼或 Promises。此 async/await 呼叫的工作方式與其他 JavaScript 程式碼中的工作方式相同。 ![使用 async/await 開玩笑非同步呼叫](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/0f4f8ad7e352e46c4e933bab0e365df9e5aca5084b02d0fb.png/w_498) *使用 async/await 開玩笑非同步呼叫* **茉莉花** Jasmine 最初是為 Node.js 建立的,因此它有很多內建功能。但是,在執行測試之前和之後可能需要一些設定。例如,您可以在此處看到,您應該在 beforeAll 方法中處理非同步呼叫,以防止稍後的測試中產生殘留影響。 ![使用 async/await 進行 Jasmine 非同步呼叫](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/fdb2d7178a6235735d656b013846daf7dba63b525e15c479.png/w_810) *使用 async/await 進行 Jasmine 非同步呼叫* **摩卡** 在這裡您可以看到用於處理返回承諾的完成方法。它使用與前面的 Mocha 範例相同的 chai-http 庫。這是在 Mocha 測試中編寫的非同步呼叫的常見方式。您可以在 Mocha 中使用 Promises 或非同步/等待模式。 ![使用 async/await 進行 Mocha 非同步呼叫](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/fc2d3391bc3492c18a966e80a6a2326f7ff15ef26c97b0c0.png/w_528) *使用 async/await 進行 Mocha 非同步呼叫* #### 我的看法 對於後端測試,Jasmine 可以輕鬆處理非同步方法並且開箱即用,這將是我的首選。 Mocha 和 Jest 也很有用,儘管它們需要更多地搜尋文件才能找到您需要的內容。 ### 模擬渲染元件 另一個重要且常見的測試是確保渲染的元件在預期時可用。和以前一樣,您通常會看到 Jest 與 React 一起使用,Jasmine 與 Angular 一起使用,但您可以在任何前端程式庫上使用這三個框架中的任何一個。 渲染元件可能是一項昂貴的任務,具體取決於渲染的深度。有些開發人員喜歡使用快照測試,它保存代表 UI 當前狀態的檔案。其他人則喜歡模擬渲染的元件。當您尋找 UI 中的變更時,快照更有用,而當您想要查看元件是否按預期工作時,渲染更有用。兩種方法都很有用。 **是** 正如我之前提到的,Jest 是為 React 建置的,因此您無需導入任何特殊的庫來進行渲染測試。這使得這些測試變得簡單並節省了依賴項的空間。 [ReactDOM](https://reactjs.org/docs/react-dom.html)在許多 React 專案中都很常見,並且附帶了檢查基本渲染所需的方法,如下例所示: ![Jest 框架中的 React 渲染測試](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/bb94ca1748e993345206d48020127b569c3ddb726f8bb83a.png/w_515) *Jest 框架中的 React 渲染測試* **茉莉花** 設定 Jasmine 來進行 React 渲染測試比看起來更困難;它涉及重要的初始設定。 [Angular 團隊](https://angular.io/guide/testing)使用並推薦[Karma](https://www.npmjs.com/package/karma-jasmine)和 Jasmine 來測試元件。下面的範例用於測試 Angular 元件。您必須匯入要測試的元件,並且可以使用 Angular 隨附的 @angular/core/testing 來設定元件的環境,然後再嘗試渲染該元件並檢查它是否可用。 ![Jasmine框架中的Angular渲染測試](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/47893f0aa0a46255cd976782196aeae45bee362ea9ea2d29.png/w_577) *Jasmine框架中的Angular渲染測試* **摩卡** 您通常會看到[Enzyme](https://enzymejs.github.io/enzyme/)和 Chai 與 Mocha 一起用於前端測試,並且測試 React 渲染沒有任何不同。一旦匯入了所需的特定方法(例如shallow 和expect),您將能夠編寫與其他框架類似的測試。下面的範例利用了 Enzyme 的淺層渲染和 Chai 的斷言。 ![Mocha框架中的React渲染測試](https://39296.cdn.cke-cs.com/xnX5w1TWo7hQQOywFbkx/images/705c430a50995cd2c5935a795c2f697f19b2eda62fab47dd.png/w_559) *Mocha框架中的React渲染測試* #### 我的看法 渲染元件的最佳實踐是僅使用為您的前端庫推薦的測試框架。使用附帶安裝的工具,您無需處理設定錯誤。如果可能,請嘗試使用淺渲染和快照來節省測試時間並專注於渲染元件的核心功能。 ### 結論 希望您現在對這三個流行框架之間的差異有了更好的了解。正如我所提到的,無論您選擇哪種框架,這三個框架都是成熟且有效的選擇,並且可以為您工作,這取決於您的專案的需要和您的偏好。現在您已準備好接受測試! --- 原文出處:https://dev.to/heroku/comparing-the-top-3-javascript-testing-frameworks-2cco

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

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

從頭開始實作 JavaScript 概念

在本文中,我們透過從頭開始建立幾個關鍵元件來探索 JavaScript 的基本建構塊。當我們深入研究這些概念時,我們將應用一系列從基礎到複雜的技術,使這種探索對於 JavaScript 世界的新手和專業人士都很有價值。 大綱 ---- - [`memoize()`](#memoize) - [`Array.map()`](#arraymap) - [`Array.filter()`](#arrayfilter) - [`Array.reduce()`](#arrayreduce) - [`bind()`](#bind) - [`call()` 、 `apply()`](#callapply) - [`setInterval()`](#setinterval) - [`cloneDeep()`](#clonedeep) - [`debounce()`](#debounce) - [`throttle()`](#throttle) - [`Promise`](#promise) - \[ `EventEmitter` ) <a name="memoize"></a> `memoize()` ----------- ### 任務說明 重新建立`memoize`函數(來自“lodash”),該函數透過快取函數呼叫的結果來優化效能。透過傳回快取的結果而不是重新計算,可以確保使用相同參數的重複函數呼叫更快。 ### 執行 ``` function customSerializer(entity, cache = new WeakSet()) { if (typeof entity !== 'object' || entity === null) { return `${typeof entity}:${entity}`; } if (cache.has(entity)) { return 'CircularReference'; } cache.add(entity); let objKeys = Object.keys(entity).sort(); let keyRepresentations = objKeys.map(key => `${customSerializer(key, cache)}:${ customSerializer(entity[key], cache) }` ); if (Array.isArray(entity)) { return `Array:[${keyRepresentations.join(',')}]`; } return `Object:{${keyRepresentations.join(',')}}`; } function myMemoize(fn) { const cache = new Map(); return function memoized(...args) { const keyRep = args.map(arg => customSerializer(arg) ).join('-'); const key = `${typeof this}:${this}-${keyRep}`; if (cache.has(key)) { return cache.get(key); } else { const result = fn.apply(this, args); cache.set(key, result); return result; } }; } ``` ### 實施的關鍵面 1. **快取機制:**它使用`Map`物件`cache`來儲存函數呼叫的結果。選擇`Map`物件是因為其高效率的鍵值配對和檢索功能。 2. **Custom Serializer** : `customSerializer`函數將函數參數轉換為用作快取鍵的字串表示形式。此序列化考慮了基本類型、物件(包括巢狀物件)、陣列和循環參考。對於物件和陣列,它們的鍵經過排序以確保一致的字串表示形式,無論屬性聲明順序如何。 3. **序列化`this`** : `this`的值指的是函數所屬的物件。在 JavaScript 中,方法可以根據呼叫它們的物件(即呼叫它們的上下文)而有不同的行為。這是因為`this`提供了對上下文物件的屬性和方法的存取,並且其值可能會根據函數的呼叫方式而變化。 4. **循環引用**:當物件直接或透過其屬性間接引用自身時,就會發生循環引用。這可能發生在更複雜的資料結構中,例如,物件`A`包含對物件`B`的引用,而物件`B`則直接或間接引用物件`A` 。處理循環引用以避免無限循環至關重要。 5. **使用`WeakSet`進行自動垃圾收集**: `WeakSet`保留對其物件的「弱」引用,這表示如果沒有其他引用, `WeakSet`中物件的存在不會阻止該物件被垃圾收集。此行為在需要臨時追蹤物件存在而又不會不必要地延長其生命週期的情況下特別有用。由於`customSerializer`函數可能只需要在序列化過程中標記物件的存取,而不儲存額外的資料,因此使用`WeakSet`可以確保物件不會僅僅因為它們在集合中的存在而保持活動狀態,從而防止潛在的內存洩漏。 <a name="arraymap"></a> `Array.map()` ------------- ### 任務說明 重新建立`Array.map()` ,它將轉換函數作為參數。此轉換函數將在陣列的每個元素上執行,並採用三個參數:當前元素、目前元素的索引和陣列本身。 ### 實施的關鍵面 1. **記憶體預先分配**: `new Array(this.length)`用於建立預先確定大小的陣列,以優化記憶體分配並透過避免加入元素時動態調整大小來提高效能。 ### 執行 ``` Array.prototype.myMap = function(fn) { const result = new Array(this.length); for (let i = 0; i < this.length; i++) { result[i] = fn(this[i], i, this); } return result; } ``` <a name="arrayfilter"></a> `Array.filter()` ---------------- ### 任務說明 重新建立`Array.filter()` ,它將謂詞函數作為輸入,迭代呼叫它的陣列的元素,將謂詞應用於每個元素。它傳回一個新陣列,僅包含謂詞函數傳回`true`元素。 ### 實施的關鍵面 1. **動態記憶體分配**:它動態地將符合條件的元素加入到`filteredArray`中,從而在很少有元素通過謂詞函數的情況下使該方法更有效地使用記憶體。 ### 執行 ``` Array.prototype.myFilter = function(pred) { const filteredArray = []; for (let i = 0; i < this.length; i++) { if (pred(this[i], i, this)) { filteredArray.push(this[i]); } } return filteredArray; } ``` <a name="arrayreduce"></a> `Array.reduce()` ---------------- ### 任務說明 重新建立`Array.reduce()` ,它對陣列的每個元素執行`reducer`函數,從而產生單一輸出值。 `reducer`函數有四個參數:累加器、currentValue、currentIndex 和整個陣列。 ### 實施的關鍵面 1. **`initialValue` value** : `accumulator`和`startIndex`會根據是否將`initialValue`作為參數傳遞來初始化。如果提供了`initialValue` (意味著`arguments.length`至少為`2` ),則`accumulator`設定為此`initialValue` ,並且迭代從第0個元素開始。否則,如果未提供`initialValue` ,則將陣列本身的第 0 個元素用作`initialValue` 。 ### 執行 ``` Array.prototype.myReduce = function(callback, initialValue) { let accumulator = arguments.length >= 2 ? initialValue : this[0]; let startIndex = arguments.length >= 2 ? 0 : 1; for (let i = startIndex; i < this.length; i++) { accumulator = callback(accumulator, this[i], i, this); } return accumulator; } ``` <a name="bind"></a> `bind()` -------- ### 任務說明 重新建立`bind()`函數,該函數允許將物件以及預先指定的初始參數(如果有)作為呼叫原始函數的上下文傳遞。它還應該支援`new`運算符的使用,從而能夠建立新實例,同時維護正確的原型鏈。 ### 執行 ``` Function.prototype.mybind = function(context, ...bindArgs) { const self = this; const boundFunction = function(...callArgs) { const isNewOperatorUsed = new.target !== undefined; const thisContext = isNewOperatorUsed ? this : context; return self.apply(thisContext, bindArgs.concat(callArgs)); }; if (self.prototype) { boundFunction.prototype = Object.create(self.prototype); } return boundFunction; }; ``` ### 實施的關鍵面 1. **處理`new` Operator** :語句`const isNewOperatorUsed = new.target !== undefined;`檢查是否透過`new`運算子將`boundFunction`作為建構函數呼叫。如果使用`new`運算符,則`thisContext`將設定為新建立的物件 ( `this` ) 而不是提供的`context` ,確認實例化應使用新的上下文而不是綁定期間提供的上下文。 2. **原型保留**:為了維護原始函數的原型鏈, `mybind`有條件地將`boundFunction`的原型設定為繼承自`self.prototype`的新物件。此步驟確保從`boundFunction` (用作建構函數時)建立的實例正確地繼承原始函數原型的屬性。此機制保留了預期的繼承層次結構並維護instanceof 檢查。 ### 將`bind()`與`new`一起使用的範例 讓我們考慮一個簡單的建構函數,它建立代表汽車的物件: ``` function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } ``` 想像一下,我們常常創造「豐田」品牌的`Car`物件。為了讓這個過程更有效率,我們可以使用`bind`為Toyotas建立一個專門的建構函數,預先填入`make`參數: ``` // Creating a specialized Toyota constructor with 'Toyota' // as the pre-set 'make' const ToyotaConstructor = Car.bind(null, 'Toyota'); // Now, we can create Toyota car instances // without specifying 'make' const myCar = new ToyotaConstructor('Camry', 2020); // Output: Car { make: 'Toyota', model: 'Camry', year: 2020 } console.log(myCar); ``` <a name="callapply"></a> `call()` 、 `apply()` -------------------- ### 任務說明 重新建立`call()`和`apply()`函數,它們允許使用給定的 this 值和單獨提供的參數來呼叫函數。 ### 執行 ``` Function.prototype.myCall = function(context, ...args) { const fnSymbol = Symbol('fnSymbol'); context[fnSymbol] = this; const result = context[fnSymbol](...args); delete context[fnSymbol]; return result; }; Function.prototype.myApply = function(context, args) { const fnSymbol = Symbol('fnSymbol'); context[fnSymbol] = this; const result = context[fnSymbol](...args); delete context[fnSymbol]; return result; }; ``` ### 實施的關鍵面 1. **屬性命名的符號用法**:為了防止覆蓋上下文物件上潛在的現有屬性或由於名稱衝突而導致意外行為,使用唯一的`Symbol`作為屬性名稱。這確保了我們的臨時屬性不會幹擾上下文物件的原始屬性。 2. **執行後清理**:函數呼叫執行後,新增到上下文物件中的臨時屬性將被刪除。此清理步驟對於避免在上下文物件上留下修改後的狀態至關重要。 <a name="setinterval"></a> `setInterval()` --------------- ### 任務說明 使用`setTimeout`重新建立`setInterval` 。此函數應以指定的時間間隔重複呼叫提供的回呼函數。它會傳回一個函數,當呼叫該函數時,該函數會停止間隔。 ### 執行 ``` function mySetInterval(callback, interval) { let timerId; const repeater = () => { callback(); timerId = setTimeout(repeater, interval); }; repeater(); return () => { clearTimeout(timerId); }; } ``` ### 實施的關鍵面 1. **取消功能**: `mySetInterval`傳回的函數提供了一種簡單直接的方法來取消正在進行的間隔,而無需在函數範圍之外公開或管理計時器 ID。 <a name="clonedeep"></a> `cloneDeep()` ------------- ### 任務說明 重新建立執行給定輸入的深度複製的`cloneDeep`函數(來自“lodash”)。該函數應該能夠複製複雜的資料結構,包括物件、陣列、映射、集合、日期和正規表示式,並保持每個元素的結構和類型完整性。 ### 執行 ``` function myCloneDeep(entity, map = new WeakMap()) { if (entity === null || typeof entity !== 'object') { return entity; } if (map.has(entity)) { return map.get(entity); } let cloned; switch (true) { case Array.isArray(entity): cloned = []; map.set(entity, cloned); cloned = entity.map(item => myCloneDeep(item, map)); break; case entity instanceof Date: cloned = new Date(entity.getTime()); break; case entity instanceof Map: cloned = new Map(Array.from(entity.entries(), ([key, val]) => [myCloneDeep(key, map), myCloneDeep(val, map)])); break; case entity instanceof Set: cloned = new Set(Array.from(entity.values(), val => myCloneDeep(val, map))); break; case entity instanceof RegExp: cloned = new RegExp(entity.source, entity.flags); break; default: cloned = Object.create( Object.getPrototypeOf(entity)); map.set(entity, cloned); for (let key in entity) { if (entity.hasOwnProperty(key)) { cloned[key] = myCloneDeep(entity[key], map); } } } return cloned; } ``` ### 實施的關鍵面 1. **循環引用處理**:利用`WeakMap`來追蹤已存取的物件。如果遇到已經克隆的物件,則返回先前克隆的物件,有效處理循環參考並防止堆疊溢位錯誤。 2. **特殊物件的處理**:區分幾種物件類型( `Array` 、 `Date` 、 `Map` 、 `Sets` 、 `RegExp` ),以確保每種類型都被適當地克隆,並保留其特定特徵。 ``` - **`Array`**: Recursively clones each element, ensuring deep cloning. ``` ``` - **`Date`**: Copies the date using its numeric value (timestamp). ``` ``` - **Maps and Sets**: Constructs a new instance, recursively cloning each entry (for `Map`) or value (for `Set`). ``` ``` - **`RegExp`**: Clones by creating a new instance with the source and flags of the original. ``` 3. **物件屬性的複製**:當輸入是普通物件時,它會建立一個與原始物件具有相同原型的物件,然後遞歸地複製每個自己的屬性,在保持原型鏈的同時確保深度克隆。 4. **效率和性能**:利用`WeakMap`進行記憶,有效處理具有重複引用和循環的複雜大型結構,透過避免冗餘克隆來確保最佳性能。 <a name="debounce"></a> `debounce()` ------------ ### 任務說明 重新建立`debounce`函數(來自“lodash”),它允許限制給定回調函數觸發的頻率。當在短時間內重複呼叫時,在指定的延遲後僅執行最後一次呼叫。 ``` function myDebounce(func, delay) { let timerId; const debounced = function(...args) { clearTimeout(timerId); timerId = setTimeout(() => { func.apply(this, args); }, delay); }; debounced.cancel = function() { clearTimeout(timerId); timerId = null; }; debounced.flush = function() { clearTimeout(timerId); func.apply(this, arguments); timerId = null; }; return debounced; } ``` ### 實施的關鍵面 1. **取消功能**:引入`.cancel`方法使外部控制能夠取消去抖函數的任何暫停執行。這增加了靈活性,允許響應特定事件或條件而取消去抖功能。 2. **透過 Flush 立即執行**: `.flush`方法允許立即執行去抖函數,而不考慮延遲。這在需要確保立即應用去抖函數的效果的情況下非常有用,例如,在卸載元件或完成互動之前。 <a name="throttle"></a> `throttle()` ------------ ### 任務說明 重新建立`throttle`函數(來自“lodash”),它確保給定的回調函數在每個指定的時間間隔內最多只呼叫一次(在我們的例子中是在開始時)。與去抖動不同,限制保證函數會定期執行,確保進行更新,儘管更新速度是受控的。 ### 執行 ``` function myThrottle(func, timeout) { let timerId = null; const throttled = function(...args) { if (timerId === null) { func.apply(this, args) timerId = setTimeout(() => { timerId = null; }, timeout) } } throttled.cancel = function() { clearTimeout(timerId); timerId = null; }; return throttled; } ``` ### 實施的關鍵面 1. **取消功能**:引入`.cancel`方法可以清除節流計時器的任何計劃重置。這在清理階段非常有用,例如 UI 庫/框架中的元件卸載,以防止過時的執行並有效管理資源。 <a name="promise"></a> `Promise` --------- ### 任務說明 重新建立`Promise`類別。它是為非同步程式設計的構造,允許暫停程式碼的執行,直到非同步進程完成。從本質上講,承諾代表了在其建立時不一定已知的值的代理。它允許您將處理程序與非同步操作的最終成功值或失敗原因相關聯。這使得非同步方法可以像同步方法一樣傳回值:非同步方法不是立即傳回最終值,而是傳回一個在未來某個時刻提供該值的承諾。 `Promise`包含處理已完成和拒絕狀態的方法( `then` 、 `catch` ),以及無論結果如何都執行程式碼的方法( `finally` )。 ``` class MyPromise { constructor(executor) { ... } then(onFulfilled, onRejected) { ... } catch(onRejected) { ... } finally(callback) { ... } } ``` ### `constructor`實現 ``` constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onFulfilledCallbacks.forEach(fn => fn()); } }; const reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn => fn()); } }; try { executor(resolve, reject); } catch (error) { reject(error); } } ``` ### `constructor`實現的關鍵方面 1. **狀態管理**:以「待處理」狀態初始化。解決時切換為“已完成”,被拒絕時切換為“拒絕”。 2. **值和原因**:保存承諾的最終結果( `value` )或拒絕的原因( `reason` )。 - **處理非同步**:接受包含非同步操作的`executor`函數。 `executor`採用兩個函數, `resolve`和`reject` ,當呼叫它們時,將promise轉換到對應的狀態。 3. **回呼陣列**:維護回呼佇列( `onFulfilledCallbacks` 、 `onRejectedCallbacks` ),以用於等待解決或拒絕承諾的延遲操作。 ### `.then`實施 ``` resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError( 'Chaining cycle detected for promise')); } if (x instanceof MyPromise) { x.then(resolve, reject); } else { resolve(x); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; let promise2 = new MyPromise((resolve, reject) => { if (this.state === 'fulfilled') { setTimeout(() => { try { let x = onFulfilled(this.value); this.resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); } else if (this.state === 'rejected') { setTimeout(() => { try { let x = onRejected(this.reason); this.resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); } else if (this.state === 'pending') { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); this.resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); this.resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); }); } }); return promise2; } ``` ### `.then`實施的關鍵方面 1. **預設處理程序**:將非函數處理程序轉換為標識函數(用於實現)或拋出程序(用於拒絕),以確保承諾鏈中的正確轉發和錯誤處理。 2. **Promise 連結**: `then`方法允許連結 Promise,從而實現順序非同步操作。它會建立一個新的 Promise ( `promise2` ),該 Promise 取決於傳遞給它的回呼函數 ( `onFulfilled` 、 `onRejected` ) 的結果。 3. **處理解決方案和拒絕**:僅在當前承諾解決(履行或拒絕)後才會呼叫所提供的回調。每個回呼的結果 ( `x` ) 可能是一個值或另一個 Promise,決定了`promise2`的解析。 4. **防止連結循環**: `resolvePromise`函數檢查`promise2`是否與結果 ( `x` ) 相同,避免 Promise 等待自身的循環,從而導致`TypeError` 。 5. **支援 MyPromise 和 Non-Promise 值**:如果結果 ( `x` ) 是`MyPromise`的實例, `then`使用其解析或拒絕來解決`promise2` 。此功能支援基於 Promise 的操作的無縫集成,無論是來自`MyPromise`實例還是本機 JavaScript Promise,假設它們具有相似的行為。對於非 Promise 值,或當`onFulfilled`或`onRejected`只是傳回一個值時, `promise2`將使用該值進行解析,從而在 Promise 鏈中實現簡單的轉換或分支邏輯。 6. **非同步執行保證**:透過使用`setTimeout`延遲`onFulfilled`和`onRejected`的執行, `then`確保非同步為。此延遲保持一致的執行順序,確保`onFulfilled`和`onRejected`在執行堆疊清除後呼叫。 7. **錯誤處理**:如果`onFulfilled`或`onRejected`內發生異常, `promise2`會因錯誤而被拒絕,從而允許錯誤處理通過 Promise 鏈傳播。 ### `catch`並`finally`實現 ``` static resolve(value) { if (value instanceof MyPromise) { return value; } return new MyPromise((resolve, reject) => resolve(value)); } catch(onRejected) { return this.then(null, onRejected); } finally(callback) { return this.then( value => MyPromise.resolve(callback()) .then(() => value), reason => MyPromise.resolve(callback()) .then(() => { throw reason; }) ); } ``` ### `.catch`實施的關鍵面向: 1. **簡化的錯誤處理:** `.catch`方法是`.then(null, onRejected)`的簡寫,專門專注於處理拒絕場景。當只需要拒絕處理程序時,它允許更清晰的語法,從而提高程式碼的可讀性和可維護性。 2. **Promise Chaining 支援:**由於它在內部委託給`.then` ,所以`.catch`返回一個新的 Promise,從而保持 Promise 鏈功能。這允許在錯誤恢復或透過重新拋出或返回新的被拒絕的承諾傳播錯誤後繼續進行鏈操作。 3. **錯誤傳播:**如果提供了`onRejected`並且執行時沒有錯誤,則傳回的 Promise 將使用`onRejected`的傳回值進行解析,從而有效地允許 Promise 鏈中的錯誤復原。如果`onRejected`拋出錯誤或傳回被拒絕的 Promise,則錯誤會沿著鏈傳播。 ### `.finally`實現的關鍵面向: 1. **始終執行:** `.finally`方法確保執行提供的`callback` ,無論 Promise 是履行還是拒絕。這對於需要在非同步操作之後發生的清理操作特別有用,與其結果無關。 2. **傳回值保留:**雖然`.finally`中的`callback`不接收任何參數(與`.then`或`.catch`不同),但 Promise 的原始履行值或拒絕原因將被保留並透過鏈傳遞。從`.finally`傳回的 Promise 會以相同的值或原因被解析或拒絕,除非`callback`本身導致被拒絕的 Promise。 3. **錯誤處理與傳播:**如果`callback`執行成功, `.finally`傳回的 Promise 將按照與原始 Promise 相同的方式進行結算。但是,如果`callback`拋出錯誤或返回被拒絕的 Promise,則從`.finally`返回的 Promise 會因這個新錯誤而被拒絕,從而允許錯誤攔截並更改 Promise 鏈中的拒絕原因。 <a name="eventemitter"></a> `EventEmitter` -------------- ### 任務說明 重新建立`EventEmitter`類,該類別允許實現觀察者模式,使物件(稱為「發射器」)能夠發出命名事件,從而導致呼叫先前註冊的偵聽器(或「處理程序」)。這是 Node.js 中用於處理非同步事件的關鍵元件,廣泛用於發出訊號以及管理應用程式狀態和行為。實作自訂`EventEmitter`涉及建立用於註冊事件偵聽器、觸發事件和刪除偵聽器的方法。 ``` class MyEventEmitter { constructor() { this.events = {}; } on(eventName, listener) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(listener); } once(eventName, listener) { const onceWrapper = (...args) => { listener.apply(this, args); this.off(eventName, onceWrapper); }; this.on(eventName, onceWrapper); } emit(eventName, ...args) { const listeners = this.events[eventName]; if (listeners && listeners.length) { listeners.forEach((listener) => { listener.apply(this, args); }); } } off(eventName, listenerToRemove) { if (!this.events[eventName]) { return; } const filterListeners = (listener) => listener !== listenerToRemove; this.events[eventName] = this.events[eventName].filter(filterListeners); } } ``` ### `EventEmitter`實現的關鍵面 1. **EventListener Registration `.on` :**將偵聽器函數新增至指定事件的偵聽器陣列中,如果該事件名稱尚不存在則建立一個新陣列。 2. **一次性事件偵聽器`.once` :**註冊一個偵聽器,該偵聽器在呼叫一次後會自行刪除。它將原始偵聽器包裝在一個函數 ( `onceWrapper` ) 中,該函數也會在執行後刪除包裝器,確保偵聽器僅觸發一次。 3. **發出事件`.emit` :**觸發事件,使用提供的參數呼叫所有已註冊的偵聽器。它將參數應用於每個偵聽器函數,從而允許將資料傳遞給偵聽器。 4. **刪除事件偵聽器`.off` :**從事件偵聽器陣列中刪除特定偵聽器。如果事件在刪除後沒有偵聽器,則可以將其保留為空陣列或可選地進一步清理(此實作中未顯示)。 --- 原文出處:https://dev.to/antonzo/implementing-javascript-concepts-from-scratch-4623

如何建構:人工智慧驅動的部落格平台(Next.js、Langchain 和 Supabase)

**長話短說** -------- 在本文中,您將學習如何建立一個人工智慧驅動的部落格平台,該平台可以搜尋網路並研究部落格文章的任何主題。 我們將涵蓋: - 用於應用程式框架的 Next.js 🖥️ - 法學碩士 OpenAI 🧠 - LangChain 和 Tavily 的網路搜尋人工智慧代理🤖 - 使用 CopilotKit 將 AI 整合到您的應用程式中 🪁 - Supabase 用於儲存和檢索部落格平台文章資料。 --- CopilotKit:開源 Copilot 框架 ======================== CopilotKit 是[開源 AI 副駕駛框架和平台。](https://github.com/CopilotKit/CopilotKit)我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。 建造: - ChatBots💬:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 - CopilotTextArea📝:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能 - 聯合代理🤖:應用程式內人工智慧代理,可以與您的應用程式和使用者互動。由浪鏈提供技術支援。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8gltoave8490fg234ro.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} (原諒人工智慧的拼字錯誤並給一顆星:) 現在回到文章。 --- **先決條件** -------- 在開始建立應用程式之前,讓我們先查看建置應用程式所需的依賴項或套件 - `copilotkit/react-core` :CopilotKit 前端包,帶有 React hooks,用於向副駕駛提供應用程式狀態和操作(AI 功能) - `copilotkit/react-ui:`聊天機器人側邊欄 UI 的 CopilotKit 前端包 - `copilotkit/react-textarea:` CopilotKit 前端包,用於在演講者筆記中進行人工智慧輔助文字編輯。 - `LangChainJS:`用於開發由語言模型支援的應用程式的框架。 - `Tavily Search API:`幫助將法學碩士和人工智慧應用程式連接到可信賴的即時知識的 API。 安裝所有專案包和依賴項 ----------- 在安裝所有專案包和依賴項之前,我們首先在終端機上執行以下命令來建立 Nextjs 專案。 ``` npx create-next-app@latest ``` 然後系統會提示您選擇一些選項。請隨意標記它們,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1j04grq2jykx6o606u1s.png) 之後,使用您選擇的文字編輯器開啟新建立的 Nextjs 專案。然後在命令列中執行以下命令來安裝所有專案包和依賴項。 ``` npm i @copilotkit/backend @copilotkit/shared @langchain/langgraph @copilotkit/react-core @copilotkit/react-ui @copilotkit/react-textarea @supabase/ssr @supabase/auth-helpers-nextjs ``` **建立部落格平台前端** ------------- 在本節中,我將引導您完成使用靜態內容建立部落格平台前端的過程,以定義平台的使用者介面。 首先,前往`/[root]/src/app`並建立一個名為`components`的資料夾。在 Components 資料夾中,建立一個名為`Article.tsx`的檔案。 之後,將以下程式碼新增至定義名為`Article`功能元件的檔案中,該元件將用於呈現文章建立表單。 ``` "use client"; import { useRef, useState } from "react"; export function Article() { // Define state variables for article outline, copilot text, and article title const [articleOutline, setArticleOutline] = useState(""); const [copilotText, setCopilotText] = useState(""); const [articleTitle, setArticleTitle] = useState(""); return ( // Form element for article input <form action={""} className="w-full h-full gap-10 flex flex-col items-center p-10"> {/* Input field for article title */} <div className="flex w-full items-start gap-3"> <textarea className="p-2 w-full h-12 rounded-lg flex-grow overflow-x-auto overflow-y-hidden whitespace-nowrap" id="title" name="title" value={articleTitle} placeholder="Article Title" onChange={(event) => setArticleTitle(event.target.value)} /> </div> {/* 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" id="content" name="content" value={copilotText} placeholder="Write your article content here" onChange={(event) => setCopilotText(event.target.value)} /> {/* Publish button */} <button type="submit" className="p-4 w-full !bg-slate-800 text-white rounded-lg">Publish</button> </form> ); } ``` 接下來,將另一個檔案新增到元件資料夾中,並將其命名為`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-white 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 font-semibold " href="/" aria-label="Brand"> AIBlogging </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="/writearticle"> Create Post </Link> </div> </div> </nav> </header> </> ); } ``` 之後,轉到`/[root]/src/app`並建立一個名為`writearticle`的資料夾。在`writearticle`資料夾中,建立一個名為`page.tsx`檔案。然後將以下程式碼加入匯入`Article`和`Header`元件的檔案中。然後,程式碼定義了一個名為`WriteArticle`的功能元件,它將呈現導覽列和文章建立表單。 ``` import { Article } from "../components/Article"; import Header from "../components/Header"; export default function WriteArticle() { return ( <> <Header /> <Article /> </> ); } ``` 接下來,前往`/[root]/src/page.tsx`文件,並新增以下程式碼,該程式碼定義一個名為`Home`功能元件,該元件呈現將顯示已發佈文章清單的部落格平台主頁。 ``` import Image from "next/image"; import Link from "next/link"; import Header from "./components/Header"; const Home = async () => { return ( <> <Header /> <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-white 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-gray-800 "> Hello World </h3> </div> </Link> </div> </div> </> ); }; export default Home; ``` 之後,請轉到`next.config.js`檔案並加入以下程式碼,該程式碼允許您使用 Unsplash 中的圖像作為已發布文章的封面圖像。 ``` module.exports = { images: { remotePatterns: [ { protocol: "https", hostname: "source.unsplash.com", }, ], }, }; ``` 最後,在命令列上執行命令`npm run dev` ,然後導航到 http://localhost:3000/。現在您應該在瀏覽器上查看部落格平台前端,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m319h0j3ta0xa1fhkezo.png) **將部落格平台與 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/co9getwdzh34u1k5cehf.png) 若要取得 Tavilly Search API 金鑰,請導覽至 https://app.tavily.com/home ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju9syhy5w8oip67tyne0.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 前端集成** --------------------------- 在本節中,我將引導您完成將部落格平台與 CopilotKit 前端整合的過程,以促進部落格文章研究和文章大綱生成。我們將使用聊天機器人側欄元件、copilot 文字區域元件、用於向 Copilot 提供應用程式狀態和其他資訊的 useMakeCopilotReadable 掛鉤,以及用於提供 Copilot 可以呼叫的操作的 useCopilotAction 掛鉤 首先,導入`/[root]/src/app/components/Article.tsx`檔案頂部的`useMakeCopilotReadable` 、 `useCopilotAction` 、 `CopilotTextarea`和`HTMLCopilotTextAreaElement`掛鉤。 ``` import { useMakeCopilotReadable, useCopilotAction, } from "@copilotkit/react-core"; import { CopilotTextarea, HTMLCopilotTextAreaElement, } from "@copilotkit/react-textarea"; ``` 在 Article 函數內的狀態變數下方,新增以下程式碼,該程式碼使用`useMakeCopilotReadable`掛鉤來新增將作為應用程式內聊天機器人的上下文產生的文章大綱。鉤子使副駕駛可以閱讀文章大綱。 ``` useMakeCopilotReadable("Blog article outline: " + JSON.stringify(articleOutline)); ``` 在`useMakeCopilotReadable`掛鉤下方,使用以下程式碼建立一個名為`copilotTextareaRef`的引用,該引用指向名為`HTMLCopilotTextAreaElement`的文字區域元素。 ``` const copilotTextareaRef = useRef<HTMLCopilotTextAreaElement>(null); ``` 在上面的程式碼下方,加入以下程式碼,該程式碼使用`useCopilotAction`掛鉤來設定名為`researchBlogArticleTopic`的操作,該操作將啟用對部落格文章的給定主題的研究。此操作採用兩個參數,稱為`articleTitle`和`articleOutline` ,這兩個參數可以產生文章標題和大綱。 該操作包含一個處理程序函數,該函數根據給定主題生成文章標題和大綱。在處理函數內部, `articleOutline`狀態會使用新產生的大綱進行更新,而`articleTitle`狀態會使用新產生的標題進行更新,如下所示。 ``` useCopilotAction( { name: "researchBlogArticleTopic", description: "Research a given topic for a blog article.", parameters: [ { name: "articleTitle", type: "string", description: "Title for a blog article.", required: true, }, { name: "articleOutline", type: "string", description:"Outline for a blog article that shows what the article covers.", required: true, }, ], handler: async ({ articleOutline, articleTitle }) => { setArticleOutline(articleOutline); setArticleTitle(articleTitle); }, }, [] ); ``` 在上面的程式碼下方,前往表單元件並新增以下`CopilotTextarea`元素,該元素將使您能夠為文章內容新增補全、插入和編輯。 ``` <CopilotTextarea value={copilotText} ref={copilotTextareaRef} placeholder="Write your article content here" onChange={(event) => setCopilotText(event.target.value)} className="p-4 w-full aspect-square font-bold text-xl bg-slate-800 text-white rounded-lg resize-none" placeholderStyle={{ color: "white", opacity: 0.5, }} autosuggestionsConfig={{ textareaPurpose: articleTitle, chatApiConfigs: { suggestionsApiConfig: { forwardedParams: { max_tokens: 5, stop: ["\n", ".", ","], }, }, insertionApiConfig: {}, }, debounceTime: 250, }} /> ``` 然後將Tailwindcss隱藏類別加入文章內容的Textarea中,如下所示。文字區域將保存文章的內容,並在文章發布後將其插入資料庫。 ``` {/* 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)} /> ``` 之後,請前往`/[root]/src/app/writearticle/page.tsx`檔案並使用下面的程式碼匯入頂部的 CopilotKit 前端套件和樣式。 ``` import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; import "@copilotkit/react-textarea/styles.css"; ``` 然後使用`CopilotKit`和`CopilotSidebar`包裹Article元件,如下所示。 `CopilotKit`元件指定 CopilotKit 後端端點 ( `/api/copilotkit/openai/` ) 的 URL,而`CopilotSidebar`則呈現應用程式內聊天機器人,您可以提示您研究文章的任何主題。 ``` export default function WriteArticle() { return ( <> <Header /> <CopilotKit url="/api/copilotkit"> <CopilotSidebar instructions="Help the user research a blog article topic." defaultOpen={true} labels={{ title: "Blog Article Copilot", initial: "Hi you! 👋 I can help you research any topic for a blog article.", }} clickOutsideToClose={false}> <Article /> </CopilotSidebar> </CopilotKit> </> ); } ``` 之後,執行開發伺服器並導航到 http://localhost:3000/writearticle。您應該會看到應用程式內聊天機器人已整合到部落格平台中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yx7m6fhxm9gdg942a0sq.png) 給右側的聊天機器人一個提示,例如“研究一篇關於生成人工智慧的部落格文章主題,然後給我文章大綱。”聊天機器人將開始研究該主題,然後產生部落格標題。 當您開始在編輯器上寫作時,您應該會看到內容自動建議,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ocnhptqxnk5gopvlzfs8.png) **將部落格平台與 Supabase 資料庫集成** -------------------------- 在本節中,我將引導您完成將部落格平台與 Supabase 資料庫整合以插入和獲取部落格文章資料的過程。 首先,導覽至[supabase.com](http://supabase.com)並點擊主頁上的「啟動您的專案」按鈕。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95n06a6aj55r5w959gz6.png) 然後新建一個專案,名為AiBloggingPlatform,如下圖所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lifhztowgy3g1v9wwnjk.png) 建立專案後,將 Supabase URL 和 API 金鑰新增至 env.local 檔案中的環境變數中,如下所示。 ``` NEXT_PUBLIC_SUPABASE_URL=”Your Supabase URL” NEXT_PUBLIC_SUPABASE_ANON_KEY=”Your Supabase API Key” ``` 之後,請前往 Supabase 上專案的儀表板並開啟 SQL 編輯器部分。然後將下列 SQL 程式碼新增至編輯器中,然後按一下 CTRL + Enter 鍵建立一個名為articles 的表。文章表包含 id、標題和內容行。 ``` create table if not exists articles ( id bigint primary key generated always as identity, title text, content text ); ``` 建立表格後,您應該會收到一條成功訊息,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjb84czwivk6ue7duj0b.png) 之後,轉到`/[root]/src/`資料夾並建立一個名為`utils`的資料夾。在`utils`資料夾內,建立一個名為`supabase.ts`文件,並新增以下用於建立並傳回 Supabase 用戶端的程式碼。 ``` // Importing necessary functions and types from the Supabase SSR package import { createServerClient, type CookieOptions } from '@supabase/ssr' // Define a function named 'supabase' that takes a 'CookieOptions' object as input export const supabase = (cookies: CookieOptions) => { // Retrieve cookies from the provided 'CookieOptions' object const cookieStore = cookies() // Create and return a Supabase client configured with environment variables and cookie handling return createServerClient( // Retrieve Supabase URL from environment variables process.env.NEXT_PUBLIC_SUPABASE_URL!, // Retrieve Supabase anonymous key from environment variables process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { // Define a custom 'get' function to retrieve cookies by name from the cookie store get(name: string) { return cookieStore.get(name)?.value }, }, } ) } ``` 然後轉到`/[root]/src/app`資料夾並建立一個名為`serveractions`的資料夾。在`serveractions`資料夾中,建立一個名為`AddArticle.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 'addArticle' that takes form data as input export async function addArticle(formData: any) { // Extract title and content from the provided form data const title = formData.get("title"); 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 'articles' table on Supabase const { data, error } = await supabase.from("articles").insert([ { title, 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/Article.tsx`檔案並導入`addArticle`函數。 ``` import { addArticle } from "../serveractions/AddArticle"; ``` 然後加入`addArticle`函數作為表單動作參數,如下所示。 ``` // Form element for article input <form action={addArticle} className="w-full h-full gap-10 flex flex-col items-center p-10"> </form> ``` 之後,導覽至http://localhost:3000/writearticle,研究您選擇的主題,新增文章內容,然後點擊底部的發布按鈕來發布文章。 然後轉到 Supabase 上專案的儀表板並導航到表編輯器部分。您應該會看到您的文章資料已插入 Supabase 資料庫,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fklcuyv5q5eq1ajmdjyv.png) 接下來,前往`/[root]/src/app/page.tsx`檔案並在頂部導入 cookie 和 supabase 套件。 ``` import { cookies } from "next/headers"; import { supabase } from "@/utils/supabase"; ``` 然後在 Home 函數中加入以下程式碼,從 Supabase 資料庫中取得文章資料。 ``` const { data: articles, error } = await supabase(cookies).from('articles').select('*') ``` 之後,更新如下所示的元素程式碼,以將已發佈的文章呈現在部落格平台主頁上。 ``` return ( <> <Header /> <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"> {articles?.map((post: any) => ( <Link key={post.id} className="group flex flex-col h-full bg-white border border-gray-200 hover:border-transparent hover:shadow-lg transition-all duration-300 rounded-xl p-5 " href={`/posts/${post.id}`}> <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( post.title )}`} width={500} height={500} alt="Image Description" /> </div> <div className="my-6"> <h3 className="text-xl font-semibold text-gray-800 "> {post.title} </h3> </div> </Link> ))} </div> </div> </> ); ``` 然後導航到[http://localhost:3000](http://localhost:3000/writearticle) ,您應該會看到您發布的文章,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/429bilwyje2a4xh0il5n.png) 之後,轉到`/[root]/src/app`資料夾並建立一個名為`[id].`在`[id]`資料夾中,建立一個名為`page.tsx`的文件,並在頂部匯入以下套件和元件。 ``` import { supabase } from '@/utils/supabase'; import { cookies } from "next/headers"; import Header from '@/app/components/Header'; ``` 在導入下面,定義一個名為「getArticles」的非同步函數,該函數根據 id 參數從 supabase 資料庫檢索文章資料,如下所示。 ``` // Define an asynchronous function named 'getArticles' that retrieves article data based on the provided parameters async function getArticles(params: any) { // Extract the 'id' parameter from the provided 'params' object const { id } = params // Retrieve article data from Supabase database where the 'id' matches the provided value const { data, error } = await supabase(cookies) .from('articles') .select('*') .eq('id', id) .single(); // Return the retrieved data return data } ``` 在上面的程式碼下面,定義一個名為“Post”的函數,它將“params”作為 props,如下所示。 ``` // Define a default asynchronous function named 'Post' that takes 'params' as props export default async function Post({ params }: { params: any }) { // Retrieve the post data asynchronously based on the provided 'params' const post = await getArticles(params); // Return JSX to render the post details return ( <> {/* Render the header component */} <Header /> {/* Main content wrapper */} <div className="max-w-3xl px-4 pt-6 lg:pt-10 pb-12 sm:px-6 lg:px-8 mx-auto"> <div className="max-w-2xl"> <div className="space-y-5 md:space-y-8"> <div className="space-y-3"> {/* Render the post title */} <h2 className="text-2xl font-bold md:text-3xl dark:text-white"> {/* Render the post title only if 'post' is truthy */} {post && post.title} </h2> {/* Render the post content */} <p className="text-lg text-gray-800 dark:text-gray-200"> {/* Render the post content only if 'post' is truthy */} {post && post.content} </p> </div> </div> </div> </div> </> ); } ``` 之後,導覽至[http://localhost:3000](http://localhost:3000/writearticle)並點擊部落格平台主頁上顯示的文章。 然後您應該被重定向到文章的內容,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ojie4iwb8qn56gd2a907.png) 結論 -- 總而言之,您可以使用 CopilotKit 建立應用內 AI 聊天機器人,該機器人可以查看當前應用程式狀態並在應用程式內執行操作。 AI 聊天機器人可以與您的應用程式前端、後端和第三方服務對話。 完整的原始碼:https://github.com/TheGreatBonnie/aipoweredblog --- 原文出處:https://dev.to/copilotkit/how-to-build-an-ai-powered-blogging-platform-nextjs-langchain-supabase-1hdp

終於有人修復了 Javascript

JavaScript 生態系統正以驚人的速度發展。當您熟悉某種技術時,就會出現大量新方法。其中一些(例如 TypeScript)獲得了廣泛採用,而另一些(例如 CoffeeScript)則悄悄消失。每項創新最初都會引起人們的興奮,但隨著時間的推移,社區經常分裂,批評者最終會產生自己的框架。這種無止盡的循環讓我對聲稱可以解決所有問題的最新「神奇」框架越來越警惕。我已經從尋求工具作為解決方案轉變為擁抱對模式的理解,而不是不斷追求新技術。 這就是為什麼我向您指出針對 TypeScript 專案的特殊工具,不僅僅是另一個工具,而是鼓勵良好實踐的範例: [Effect](https://effect.website/) 。 讓我們來看看為什麼你應該踏出這一步。 彩色函數 ---- 您是否曾經問過自己,[您的功能是什麼顏色?](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/) 讓我為您總結一下。想像一下您的程式碼庫中有藍色和紅色函數。規則很簡單:您可以在藍色函數中使用紅色函數,但反之則不行。那不是一場惡夢嗎?現在用“async”替換藍色。是的,你在 Javascript 得到了函數著色。 那我們該如何對抗這種著色問題呢?如果我們想刪除彩色函數,我們需要建立某種包裝器,僅在需要時使用 Promise。例如「未來」還是…「效果」? ``` import { Effect, pipe } from "effect"; const fibonacci = (a: number): Effect.Effect<number> => a <= 1 ? Effect.succeed(a) : pipe( Effect.all([fibonacci(a - 1), fibonacci(a - 2)]), Effect.map(([a, b]) => a + b) ); await Effect.runPromise(fibonacci(10)); ``` 使用`Effect`與`Promise`主要差異在於如何處理並發。 Effect 提供了 Fiber,它是類似於綠色線程或 goroutine 的輕量級並發結構。此功能允許我們在不阻塞主執行緒的情況下執行長時間執行或非同步任務,即使在傳統的同步函數中也可以啟動主執行緒。 ``` import { Effect, Console, pipe } from "effect"; const longRunningTask = pipe( Console.log("Start of long running task"), Effect.delay(1000), Effect.tap(Console.log("End of long running task")) ); console.log("Start of program"); Effect.runFork(longRunningTask); console.log("End of program"); /** * OUTPUT: * Start of program * End of program * Start of long running task * End of long running task */ ``` 雖然Effect 並沒有消除JavaScript 中固有的非同步/同步差異(函數著色),但透過使用纖程處理非同步操作,它允許同步函數呼叫非同步效果,而不會使其本身成為非同步,從而在很大程度上緩解“著色”問題。 類型安全錯誤 ------ 我們來看看這個函數: ``` const divide = (a: number, b: number) => a / b; ``` 我們這裡剛剛引入了一個問題,我們不能除以零。那麼讓我們稍微重構一下程式碼: ``` const divide = (a: number, b: number) => { if (b === 0) throw new Error('Cannot divide by zero.'); return a / b; } ``` 你覺得不錯嗎?它不是。因為它不是類型安全的。想要使用您的函數的人不會知道您的函數可能會拋出異常。對於像這樣的簡單函數來說,這可能看起來微不足道,但是當您有數十個潛在錯誤時,它可能會變成一場噩夢。其他較成熟的語言有諸如`Either`或`Result`之類的概念來表示類型安全錯誤。看起來像這樣: ``` type Result<T, E> = Ok<T> | Err<E>; // With something like: type Ok<T> = { kind: "Ok", data: T }; type Err<E> = { kind: "Err", err: E }; ``` 使用 Effect,您將擁有開箱即用的功能: `Effect<T, E>` 。您不必問自己在執行過程中會發生什麼樣的錯誤,您可以直接從函數簽名中知道它。它還附帶幫助函數來從錯誤中恢復。 ``` const divide = (a: number, b: number): Effect<number, "DivisionByZeroError"> => { if (b === 0) return Effect.fail("DivisionByZeroError"); return Effect.succeed(a / b); } ``` 新類型或品牌類型 -------- 你知道,回顧我以前的職能,我意識到我們可以做得更好。 ``` const divide = (a: number, b: NonZeroNumber) => ... ``` 那麼如何定義`NonZeroNumber`呢?如果您只是`type NonZeroNumber = number`它不會阻止人們用「0」來呼叫它。有一個模式:新類型。是的,Effect 也支持這一點: ``` import { Brand } from "effect" type NonZeroNumber = number & Brand.Brand<"NonZeroNumber"> const NonZeroNumber = Brand.refined<NonZeroNumber>( (n) => n !== 0, // Check if the value is a non-zero number (n) => Brand.error(`Expected ${n} to be a non-zero number`) ) ``` 這樣,您就知道您的函數**不能**使用任何數字來呼叫:它需要一種不包括零的特殊類型的數字。 依賴注入 ---- 如果您想遵循「控制反轉」原則,您可能需要研究「依賴注入」。這個概念非常簡單:函數應該能夠從自己的上下文中存取它所需要的內容。 ``` // With a singleton const read = (filename) => FileReader.read(filename); // With dependency injection const read = (reader: FileReader) => (filename) => reader.read(filename); ``` 出於多種原因,最好這樣做,例如解耦事物、允許輕鬆測試、具有不同的上下文等。 雖然有幾個框架可以幫助實現這一點,但 Effect 確實透過簡化來粉碎它:將依賴項作為 Effect 的第三個參數。 ``` const read = (filename): Effect<File, Error, FileReader> => { return Effect.flatMap(FileReader, fileReader => { return fileReader.read(filename); }) } ``` 結論 -- 您應該考慮 Effect 的原因還有很多。當然,一開始並不容易,您必須學習以不同的方式編碼。但與許多讓你學習「他們的」做事方式的框架相反,Effect 實際上教你好的模式,這些模式已經在其他語言中得到了證明。實際上,Effect 很大程度上受到了 Scala 中的 ZIO 的啟發,而 Scala 本身也受到了 Haskell 的啟發,而 Haskell 至今仍被認為是良好程式模式的頂峰之一。 --- 原文出處:https://dev.to/almaju/someone-finally-fixed-javascript-426i

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

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

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

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

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

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

關於 Deno 和 Node 的未來

如果 Node 是今天寫的,它會是什麼樣子?一言以蔽之: [**Deno**](https://deno.land/) 。 JS 執行時內建了 Typescript 並簡化了模組解析。最重要的是,它將安全性提升到了一個新的水平,並縮小了我們在後端編寫 javascript 的方式與瀏覽器之間的差距。 不久前 ... ------- 2009 年發布的 node 以令人難以置信的速度席捲了世界。儘管最初對在後端執行 javascript 持懷疑態度,但社區的支持是無與倫比的。很快,複雜的工具出現了,幾年後(2014 年),微軟發布了 Typescript,對 Javascript 進行了雙重押注。 如今,Node 是後端開發最受歡迎的選擇之一。基於事件的伺服器理念確保了高效能/吞吐量比。執行 Javascript 對許多開發人員來說是一種易於使用的工具。在某種程度上,可以說,Node 透過降低進入門檻實現了後端開發的民主化。在過去的五年裡,我一直很高興地使用 Node,但同時,我想知道未來在等待著什麼? 街區的新來者:Deno ----------- 如網站所述,Deno 專案於 2018 年啟動,為 Javascript 和 Typescript 提供安全的執行時間。它基本上由兩部分組成: TypeScript 前端和 Rust 後端。兩者之間的通訊是透過使用`TypedArrays`進行訊息傳遞來進行。 ![替代文字](https://dev-to-uploads.s3.amazonaws.com/i/aqzvipzkxwk1v45n22s9.png) > Deno 是 JavaScript/TypeScript 執行時,具有安全的預設設定和出色的開發人員體驗。 — Deno 網站 在底層,我們找到了 Typescript 編譯器、V8 引擎和 Tokio 事件循環的快照版本。總而言之,以小於 10 MB 的二進位檔案或 Rust 箱子的形式提供。 API老化 ----- 早在 2010 年就取消了 Node 的承諾,這在早期階段對社群有所幫助。但隨著 JavaScript 開始變得越來越快並引入了等待和非同步功能,Node 的 API 開始老化。 今天我們付出了巨大的努力來讓他們加快速度,同時保持一致的版本控制。許多 API 呼叫仍必須包裝在建構函式(如`promisify`中才能與`Promise`語法一起使用。這個額外的步驟增加了開發的開銷並增加了應用程式中的樣板檔案。 相比之下,Promise 是 Deno 異步行為的本機綁定。 Rust 後端透過 Rust Futures 鏡像從 Typescript 前端接收的 Promise 物件。 Deno 中的非同步操作總是會傳回`Promise` 。 Node 的另一個值得注意的是它依賴`Buffer`物件來讀寫資料。為了實現瀏覽器介面的統一,Deno 在各處都使用了[`TypedArrays`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) 。使用相同的資料結構時,在後端和前端讀寫檔案時保持一致會容易得多。 零設定的 TypeScript ------- 如果你使用 Typescript,你就會知道它是一個出色的工具。它引入了一個可以隨著應用程式的成長而強制執行的類型系統。這透過提供靈活性減少了傳統靜態類型的開銷。專案可以在請求中進行部分類型化,並且類型覆蓋範圍可以隨著應用程式的成長而擴展。 在 Node 中,Typescript 可以直接與`ts-node`一起使用,儘管在生產中必須小心。最安全、效能最好的選擇是使用`ts-node`進行開發。然後編譯為 javascript 以進行生產。開發的設定可能很複雜,尤其是與熱程式碼重新載入等其他功能一起使用時。 另一方面,Deno 完全是關於 Typescript 的。它使用編譯器的快照版本並捕獲未更改的檔案。你想執行 Typescript 程式碼嗎?只需執行 Deno 二進位檔案即可。沒有配置。沒有喧囂。是不是很簡單,當然它也支援javascript。 類似瀏覽器的套件解析 ---------- Node 目前的解析方案使模組解析過於複雜。該演算法在文件位置和命名方面提供了靈活性,但在複雜性方面做出了相當大的權衡。 `require`呼叫將先搜尋具有相同名稱和`.js` 、 `.json`或`.node`副檔名的檔案。如果指定的路徑不包含前導`'/'` 、 `'./'`或`'../'` node ,則假定該模組是核心模組或`node_modules`資料夾中的依賴項。如果名稱不匹配,核心模組 node 將檢查該位置的node\_modules。如果沒有找到任何內容,它將到達父目錄並繼續這樣做,直到到達檔案系統的根目錄。 此外,資料夾可以在`package.json`檔案中指定為模組。 `require`函數也知道開始檢查的所有資料夾的`package.json`檔案。一旦找到資料夾,Node 將在其中查找`index.js`或`index.node`檔案。不必提供檔案副檔名的自由和`package.json`的靈活性會顯著增加複雜性並降低效能。 Deno 透過提供兩種類型的模組解析(相對解析和基於 URL 解析)來簡化演算法: ``` import * from "https://deno.land/std/testing/asserts.ts"; ``` 另外,解析演算法不使用`package.json`檔案或`node_modules`資料夾。它使用 ES 模組導入而不是`require` 。這使我們能夠使用現代方法進行程式碼管理,而無需預編譯器,並使我們再次更接近 Javascript 在瀏覽器中的使用方式。 分散式套件管理 ------- 目前,無伺服器的採用率每年翻倍。開發人員通常將單體應用程式拆分為微服務。現在我們將微服務拆分為功能。為什麼?嗯,一方面,沒有人願意處理編排,除非我們也有。另一方面,分散式系統更加靈活,可以更快地改變。最重要的是,應用程式正在成為由更小且獨立的部分組成的系統。 典型的 JavaScript 後端應用程式僅使用 0.3% 的程式碼。其餘部分由`node_modules`資料夾中的套件組成。而且許多在執行時幾乎不被使用。同時,整個生態系統依賴一個集中的套件管理器: `npm` 。 **Deno**帶來了一種分散式套件管理方法。套件可以透過 URL 解析並隨後捕獲。應用程式更輕,更少依賴單一的集中式套件註冊表。 關於安全 ---- 在進行後端開發時,我希望安全性能夠在盒子之外發揮作用。我最不想考慮的是存取網路或檔案系統的 linter 檔案或 node 模組。 在 Deno 中,內部函數不能像在 Node 中那樣任意呼叫 V8 API。 Deno 的 API 和 JS 引擎之間的通訊是集中且統一的,基於類型化陣列的訊息傳遞。 > 除非特別允許,否則腳本無法存取檔案、環境或網路。 — 德諾.蘭 只有當使用者明確指定時,使用 Deno 執行的腳本才能存取檔案系統和網路。更好的是,可以使用 —allow 標誌在檔案、資料夾層級或網路路徑層級授予權限。這為開發人員提供了對執行時發生的讀寫操作的精細控制。 ``` $ deno --allow-net https://deno.land/std/examples/echo_server.ts ``` 與應用於從`npn`提取的依賴項的「信任」策略相比,預設的安全性是一項重大升級。借助 Deno,您可以執行和開發應用程式,並確信它們會執行預期的操作。 加起來 --- 如果 Node 在今天建置, **Deno**就是它的樣子。它提高了安全性、簡化了模組解析並執行 Typescript。 當我寫這篇文章時,我們仍處於 0.33 版本並且正在快速發展。我確信您來到這裡是因為您在某種程度上使用了 Node 或 Javascript。如果你像我一樣,你可能會喜歡它。但正如他們所說,愛某樣東西真正意味著放手。 我期待看到 Deno 超越單純的腳本執行時,並聽到生產中的第一次經驗。只要開發人員繼續顛覆自己,我們總是能期待更快、更簡單、更可靠的軟體。 最初發佈於[bogdanned.com](https://bogdanned.com/blog) 。 --- 原文出處:https://dev.to/bogdanned/on-deno-and-the-future-of-node-1l0p

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

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

30 個 JavaScript 奇妙小技巧

歡迎使用我們精選的 JavaScript 技巧集合,它將幫助您優化程式碼、使其更具可讀性並節省您的時間。 讓我們深入研究 JavaScript 的功能和超越傳統的技巧,並發現這種強大的程式語言的全部潛力。 ### 1. 使用!!轉換為布林值 使用雙重否定快速將任何值轉換為布林值。 ``` let truthyValue = !!1; // true let falsyValue = !!0; // false ``` ### 2. 預設功能參數 設定函數參數的預設值以避免未定義的錯誤。 ``` function greet(name = "Guest") { return `Hello, ${name}!`; } ``` ### 3. 短 if-else 的三元運算符 `if-else`語句的簡寫。 ``` let price = 100; let message = price > 50 ? "Expensive" : "Cheap"; ``` ### 4. 動態字串的範本文字 使用模板文字在字串中嵌入表達式。 ``` let item = "coffee"; let price = 15; console.log(`One ${item} costs $${price}.`); ``` ### 5. 解構賦值 輕鬆從物件或陣列中提取屬性。 ``` let [x, y] = [1, 2]; let {name, age} = {name: "Alice", age: 30}; ``` ### 6. 用於陣列和物件克隆的擴展運算符 克隆陣列或物件而不引用原始陣列或物件。 ``` let originalArray = [1, 2, 3]; let clonedArray = [...originalArray]; ``` ### 7. 短路評估 使用邏輯運算子進行條件執行。 ``` let isValid = true; isValid && console.log("Valid!"); ``` ### 8. 可選連結 (?.) 如果引用為`nullish`則可以安全地存取巢狀物件屬性,而不會出現錯誤。 ``` let user = {name: "John", address: {city: "New York"}}; console.log(user?.address?.city); // "New York" ``` ### 9. 空合併運算子 (??) 使用`??`為`null`或`undefined`提供預設值。 ``` let username = null; console.log(username ?? "Guest"); // "Guest" ``` [![monday.com 的互動式橫幅展示了在數位介面上組織工作流程任務的雙手,並帶有「告訴我如何做」號召性用語按鈕。](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xd7qdmxdd726kskelydk.png)](https://try.monday.com/9xf9ftaa4i2f) ### 10. 使用`map` 、 `filter`和`reduce`進行陣列操作 無需傳統循環即可處理陣列的優雅方法。 ``` // Map let numbers = [1, 2, 3, 4]; let doubled = numbers.map(x => x * 2); // Filter const evens = numbers.filter(x => x % 2 === 0); // Reduce const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); ``` ### 11.標記模板文字 使用模板文字進行函數呼叫以進行自訂字串處理。 ``` function highlight(strings, ...values) { return strings.reduce((prev, current, i) => `${prev}${current}${values[i] || ''}`, ''); } let price = 10; console.log(highlight`The price is ${price} dollars.`); ``` ### 12.使用Object.entries()和Object.fromEntries() 將物件轉換為陣列並返回以方便操作。 ``` let person = {name: "Alice", age: 25}; let entries = Object.entries(person); let newPerson = Object.fromEntries(entries); ``` ### 13. 唯一元素的集合物件 使用 Set 儲存任何類型的唯一值。 ``` let numbers = [1, 1, 2, 3, 4, 4]; let uniqueNumbers = [...new Set(numbers)]; ``` ### 14. 物件中的動態屬性名稱 在物件文字表示法中使用方括號來建立動態屬性名稱。 ``` let dynamicKey = 'name'; let person = {[dynamicKey]: "Alice"}; ``` ### 15. 使用bind()進行函數柯里化 建立一個新函數,在呼叫時將其 this 關鍵字設定為提供的值。 ``` function multiply(a, b) { return a * b; } let double = multiply.bind(null, 2); console.log(double(5)); // 10 ``` ### 16. 使用 Array.from() 從類別陣列物件建立陣列 將類似陣列或可迭代的物件轉換為真正的陣列。 ``` let nodeList = document.querySelectorAll('div'); let nodeArray = Array.from(nodeList); ``` ### 17. 可迭代物件的 for...of 循環 直接迭代可迭代物件(包括陣列、映射、集合等)。 ``` for (let value of ['a', 'b', 'c']) { console.log(value); } ``` ### 18. 使用 Promise.all() 實作並發 Promise 同時執行多個 Promise 並等待所有的都解決。 ``` let promises = [fetch(url1), fetch(url2)]; Promise.all(promises) .then(responses => console.log('All done')); ``` ### 19. 函數參數的剩餘參數 將任意數量的參數捕獲到陣列中。 ``` function sum(...nums) { return nums.reduce((acc, current) => acc + current, 0); } ``` [![Coursera Plus 訂閱產品包括 AWS 基礎、Google IT 支援專業憑證和商業網路安全專業化。](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ys5gk2tfzcn4iniuq48s.png)](https://imp.i384100.net/c/3922519/1320997/14726) ### 20. 用於性能優化的記憶 儲存函數結果以避免冗餘處理。 ``` const memoize = (fn) => { const cache = {}; return (...args) => { let n = args[0]; // assuming single argument for simplicity if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = fn(n); cache[n] = result; return result; } }; }; ``` ### 21. 使用 ^ 交換值 使用 XOR 以位元運算子交換兩個變數的值,無需使用臨時變數。 ``` let a = 1, b = 2; a ^= b; b ^= a; a ^= b; // a = 2, b = 1 ``` ### 22. 使用 flat() 展平陣列 使用`flat()`方法輕鬆展平巢狀陣列,並將展平深度作為可選參數。 ``` let nestedArray = [1, [2, [3, [4]]]]; let flatArray = nestedArray.flat(Infinity); ``` ### 23. 用一元加法轉換為數字 使用一元加運算子快速將字串或其他值轉換為數字。 ``` let str = "123"; let num = +str; // 123 as a number ``` ### 24. HTML 片段的模板字串 使用模板字串建立 HTML 片段,使動態 HTML 生成更加清晰。 ``` let items = ['apple', 'orange', 'banana']; let html = `<ul>${items.map(item => `<li>${item}</li>`).join('')}</ul>`; ``` ### 25. 使用 Object.assign() 合併物件 將多個來源物件合併到一個目標物件中,有效地組合它們的屬性。 ``` let obj1 = { a: 1 }, obj2 = { b: 2 }; let merged = Object.assign({}, obj1, obj2); ``` [![符合人體工學的垂直滑鼠旨在減輕手腕壓力](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56c0z4lzxtkthnnk8gsb.jpg)](https://amzn.to/3UXGIyx) 使用符合人體工學的滑鼠優化您的編程設置,專為舒適和長時間的編碼會話而定制。 ### 26. 預設值短路 處理潛在的未定義或空變數時,使用邏輯運算子指派預設值。 ``` let options = userOptions || defaultOptions; ``` ### 27. 使用括號表示法動態存取物件屬性 使用括號表示法動態存取物件的屬性,當屬性名稱儲存在變數中時非常有用。 ``` let property = "name"; let value = person[property]; // Equivalent to person.name ``` ### 28. 使用 Array.includes() 進行存在檢查 使用includes() 檢查陣列是否包含某個值,它是indexOf 的更清晰的替代方法。 ``` if (myArray.includes("value")) { // Do something } ``` ### 29. Function.prototype.bind() 的強大功能 將函數綁定到上下文(此值)並部分套用參數,以建立更多可重複使用和模組化的程式碼。 ``` const greet = function(greeting, punctuation) { return `${greeting}, ${this.name}${punctuation}`; }; const greetJohn = greet.bind({name: 'John'}, 'Hello'); console.log(greetJohn('!')); // "Hello, John!" ``` ### 30.防止物件修改 使用`Object.freeze()`防止物件進行修改,使其不可變。對於更深層的不變性,請考慮更徹底地強制不變性的函式庫。 ``` let obj = { name: "Immutable" }; Object.freeze(obj); obj.name = "Mutable"; // Fails silently in non-strict mode ``` 我希望這些 JavaScript 技巧為您提供如何進行[JavaScript 程式設計的](https://www.w3schools.com/js/)新視角。 從利用模板文字的簡潔功能到掌握`map` 、 `filter`和`reduce`的效率,這些 JavaScript 技巧將豐富您的開發工作流程並激發您的下一個專案。 讓這些 JavaScript 技巧不僅可以完善您目前的專案,還可以在您的[程式設計之旅](https://www.webdevstory.com/programming-roadmap/)中激發未來創新的靈感。 ***支持我們的技術見解*** [![請我喝杯咖啡](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lsm9uucbnw7x9iw0loxr.png)](https://www.buymeacoffee.com/mmainulhasan) [![透過 PayPal 按鈕捐贈](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipnhbim2ba56kt32zhn3.png)](https://www.paypal.com/donate/?hosted_button_id=GDUQRAJZM3UR8) 注意:此頁面上的某些連結可能是附屬連結。如果您透過這些連結進行購買,我可能會賺取少量佣金,而無需您支付額外費用。感謝您的支持! --- 原文出處:https://dev.to/mmainulhasan/30-javascript-tricky-hacks-gfc

我正在建立一個全端應用程式:以下是我將要使用的庫......

您可以使用無數的框架和函式庫來改進您的全端應用程式。 我們將介紹令人興奮的概念,例如應用程式內通知、使用 React 製作影片、從為開發人員提供的電子郵件 API 到在瀏覽器中建立互動式音樂。 那我們就開始吧。 (不要忘記為這些庫加註星標以表示您的支持)。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqoipyuoxgb83swyoo4a.gif) https://github.com/CopilotKit/CopilotKit --- 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 在數小時內為您的產品提供 AI Copilot。 ------------------------------------------------------------------------------------ ![副駕駛套件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzuxjfog2ldam3csrl62.png) 您可以使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。它們還提供內建(完全可自訂)Copilot 原生 UX 元件,例如`<CopilotKit />` 、 `<CopilotPopup />` 、 `<CopilotSidebar />` 、 `<CopilotTextarea />` 。 開始使用以下 npm 指令。 ``` npm i @copilotkit/react-core @copilotkit/react-ui @copilotkit/react-textarea ``` 這是整合 CopilotTextArea 的方法。 ``` import { CopilotTextarea } from "@copilotkit/react-textarea"; import { useState } from "react"; export function SomeReactComponent() { const [text, setText] = useState(""); return ( <> <CopilotTextarea className="px-4 py-4" value={text} onValueChange={(value: string) => setText(value)} placeholder="What are your plans for your vacation?" autosuggestionsConfig={{ textareaPurpose: "Travel notes from the user's previous vacations. Likely written in a colloquial style, but adjust as needed.", chatApiConfigs: { suggestionsApiConfig: { forwardedParams: { max_tokens: 20, stop: [".", "?", "!"], }, }, }, }} /> </> ); } ``` 您可以閱讀[文件](https://docs.copilotkit.ai/getting-started/quickstart-textarea)。 基本概念是在幾分鐘內建立可用於基於 LLM 的全端應用程式的 AI 聊天機器人。 https://github.com/CopilotKit/CopilotKit --- 2. [Storybook](https://github.com/storybookjs/storybook) - UI 開發、測試和文件變得簡單。 --------------------------------------------------------------------------- ![故事書](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/78rfum1ydisn51qhb408.png) Storybook 是一個用於獨立建立 UI 元件和頁面的前端工作坊。它有助於 UI 開發、測試和文件編制。 他們在 GitHub 上有超過 57,000 次提交、81,000 多個 star 和 1300 多個版本。 這是您為專案建立簡單元件的方法。 ``` import type { Meta, StoryObj } from '@storybook/react'; import { YourComponent } from './YourComponent'; //👇 This default export determines where your story goes in the story list const meta: Meta<typeof YourComponent> = { component: YourComponent, }; export default meta; type Story = StoryObj<typeof YourComponent>; export const FirstStory: Story = { args: { //👇 The args you need here will depend on your component }, }; ``` 您可以閱讀[文件](https://storybook.js.org/docs/get-started/setup)。 如今,UI 除錯起來很痛苦,因為它們與業務邏輯、互動狀態和應用程式上下文糾纏在一起。 Storybook 提供了一個獨立的 iframe 來渲染元件,而不會受到應用程式業務邏輯和上下文的干擾。這可以幫助您將開發重點放在元件的每個變體上,甚至是難以觸及的邊緣情況。 https://github.com/storybookjs/storybook --- 3. [Appwrite](https://github.com/appwrite/appwrite) - 您的後端減少麻煩。 --------------------------------------------------------------- ![應用程式寫入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8x568uz21seyygw6b72z.png) ![帶有 appwrite 的 sdk 列表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cp7k8qnamsluto7eifpl.png) Appwrite 的開源平台可讓您將身份驗證、資料庫、函數和儲存體新增至您的產品中,並建立任何規模的任何應用程式、擁有您的資料並使用您喜歡的編碼語言和工具。 他們有很好的貢獻指南,甚至不厭其煩地詳細解釋架構。 開始使用以下 npm 指令。 ``` npm install appwrite ``` 您可以像這樣建立一個登入元件。 ``` "use client"; import { useState } from "react"; import { account, ID } from "./appwrite"; const LoginPage = () => { const [loggedInUser, setLoggedInUser] = useState(null); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [name, setName] = useState(""); const login = async (email, password) => { const session = await account.createEmailSession(email, password); setLoggedInUser(await account.get()); }; const register = async () => { await account.create(ID.unique(), email, password, name); login(email, password); }; const logout = async () => { await account.deleteSession("current"); setLoggedInUser(null); }; if (loggedInUser) { return ( <div> <p>Logged in as {loggedInUser.name}</p> <button type="button" onClick={logout}> Logout </button> </div> ); } return ( <div> <p>Not logged in</p> <form> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} /> <button type="button" onClick={() => login(email, password)}> Login </button> <button type="button" onClick={register}> Register </button> </form> </div> ); }; export default LoginPage; ``` 您可以閱讀[文件](https://appwrite.io/docs)。 Appwrite 可以非常輕鬆地建立具有開箱即用的擴充功能的可擴展後端應用程式。 https://github.com/appwrite/appwrite --- 4. [Wasp](https://github.com/wasp-lang/wasp) - 用於 React、node.js 和 prisma 的類似 Rails 的框架。 --------------------------------------------------------------------------------------- ![黃蜂](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi2mwazueoc3ezjx8a9q.png) 使用 React 和 Node.js 開發全端 Web 應用程式的最快方法。這不是一個想法,而是一種建立瘋狂快速全端應用程式的不同方法。 這是將其整合到元件中的方法。 ``` import getRecipes from "@wasp/queries/getRecipes"; import { useQuery } from "@wasp/queries"; import type { User } from "@wasp/entities"; export function HomePage({ user }: { user: User }) { // Due to full-stack type safety, `recipes` will be of type `Recipe[]` here. const { data: recipes, isLoading } = useQuery(getRecipes); // Calling our query here! if (isLoading) { return <div>Loading...</div>; } return ( <div> <h1>Recipes</h1> <ul> {recipes ? recipes.map((recipe) => ( <li key={recipe.id}> <div>{recipe.title}</div> <div>{recipe.description}</div> </li> )) : 'No recipes defined yet!'} </ul> </div> ); } ``` 您可以閱讀[文件](https://wasp-lang.dev/docs)。 https://github.com/wasp-lang/wasp --- 5. [Novu](https://github.com/novuhq/novu) - 將應用程式內通知新增至您的應用程式! -------------------------------------------------------------- ![再次](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/716b7biilet4auudjlcu.png) Novu 提供開源通知基礎架構和功能齊全的嵌入式通知中心。 這就是如何使用`React`建立 novu 元件以用於應用程式內通知。 ``` import { NovuProvider, PopoverNotificationCenter, NotificationBell, } from "@novu/notification-center"; function App() { return ( <> <NovuProvider subscriberId={process.env.REACT_APP_SUB_ID} applicationIdentifier={process.env.REACT_APP_APP_ID} > <PopoverNotificationCenter> {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />} </PopoverNotificationCenter> </NovuProvider> </> ); } export default App; ``` 您可以閱讀[文件](https://docs.novu.co/getting-started/introduction)。 https://github.com/novuhq/novu --- 6. [Remotion](https://github.com/remotion-dev/remotion) - 使用 React 以程式設計方式製作影片。 ------------------------------------------------------------------------------- ![遠端](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmnrxhsc7b9mm5oagflm.png) 使用 React 建立真正的 MP4 影片,使用伺服器端渲染和參數化擴展影片製作。 開始使用以下 npm 指令。 ``` npm init video ``` 它為您提供了一個幀號和一個空白畫布,您可以在其中使用 React 渲染任何您想要的內容。 這是一個範例 React 元件,它將當前幀渲染為文字。 ``` import { AbsoluteFill, useCurrentFrame } from "remotion";   export const MyComposition = () => { const frame = useCurrentFrame();   return ( <AbsoluteFill style={{ justifyContent: "center", alignItems: "center", fontSize: 100, backgroundColor: "white", }} > The current frame is {frame}. </AbsoluteFill> ); }; ``` 您可以閱讀[文件](https://www.remotion.dev/docs/)。 過去兩年,remotion 團隊因製作 GitHub Wrapped 而聞名。 https://github.com/remotion-dev/remotion --- [7.NocoDB](https://github.com/nocodb/nocodb) - Airtable 的替代品。 ------------------------------------------------------------- ![諾科資料庫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iw3tchfgyzehye5c39xq.png) Airtable 的免費開源替代品是 NocoDB。它可以使用任何 MySQL、PostgreSQL、SQL Server、SQLite 或 MariaDB 資料庫製作智慧型電子表格。 其主要目標是讓強大的計算工具得到更廣泛的使用。 開始使用以下 npx 指令。 ``` npx create-nocodb-app ``` 您可以閱讀[文件](https://docs.nocodb.com/)。 NocoDB 的建立是為了為世界各地的數位企業提供強大的開源和無程式碼資料庫介面。 您可以非常快速地將airtable資料匯入NocoDB。 https://github.com/nocodb/nocodb --- 8.[新穎](https://github.com/steven-tey/novel)- 所見即所得編輯器,具有人工智慧自動完成功能。 ------------------------------------------------------------------- ![小說](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo34vd9twpxcpbpzgchi.png) 它使用`Next.js` 、 `Vercel AI SDK` 、 `Tiptap`作為文字編輯器。 開始使用以下 npm 指令。 ``` npm i novel ``` 您可以這樣使用它。有多種選項可用於改進您的應用程式。 ``` import { Editor } from "novel"; export default function App() { return <Editor />; } ``` https://github.com/steven-tey/novel --- 9. [Blitz](https://github.com/blitz-js/blitz) - 缺少 NextJS 的全端工具包。 ----------------------------------------------------------------- ![閃電戰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vz6ineg1o7xyv7pwbuqn.png) Blitz 繼承了 Next.js 的不足,為全球應用程式的交付和擴展提供了經過實戰考驗的函式庫和約定。 開始使用以下 npm 指令。 ``` npm install -g blitz ``` 這就是您如何使用 Blitz 建立新頁面。 ``` const NewProjectPage: BlitzPage = () => { const router = useRouter() const [createProjectMutation] = useMutation(createProject) return ( <div> <h1>Create New Project</h1> <ProjectForm submitText="Create Project" schema={CreateProject} onSubmit={async (values) => { // This is equivalent to calling the server function directly const project = await createProjectMutation(values) // Notice the 'Routes' object Blitz provides for routing router.push(Routes.ProjectsPage({ projectId: project.id })) }} /> </div> ); }; NewProjectPage.authenticate = true NewProjectPage.getLayout = (page) => <Layout>{page}</Layout> export default NewProjectPage ``` 您可以閱讀[文件](https://blitzjs.com/docs/get-started)。 它使建築物改善了數倍。 ![閃電戰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc4mb5wdksjv1ybx71co.png) https://github.com/blitz-js/blitz --- 10. [Supabase](https://github.com/supabase/supabase) - 開源 Firebase 替代品。 ----------------------------------------------------------------------- ![蘇帕貝斯](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksfygjhrzhmsg9cnvobs.png) 我們大多數人都已經預料到 SUPABASE 會出現在這裡,因為它實在是太棒了。 開始使用以下 npm 指令 (Next.js)。 ``` npx create-next-app -e with-supabase ``` 這是使用 supabase 建立用戶的方法。 ``` import { createClient } from '@supabase/supabase-js' // Initialize const supabaseUrl = 'https://chat-room.supabase.co' const supabaseKey = 'public-anon-key' const supabase = createClient(supabaseUrl, supabaseKey) // Create a new user const { user, error } = await supabase.auth.signUp({ email: '[email protected]', password: 'example-password', }) ``` 您可以閱讀[文件](https://supabase.com/docs)。 您可以使用身份驗證、即時、邊緣功能、儲存等功能建立一個速度極快的應用程式。 Supabase 涵蓋了這一切! 他們還提供了一些入門套件,例如 AI 聊天機器人和 Stripe 訂閱。 https://github.com/supabase/supabase --- [11.Refine](https://github.com/refinedev/refine) - 企業開源重組工具。 ------------------------------------------------------------ ![精煉](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qx0kd6t2jzdtf90k5ke3.png) 建立具有無與倫比的靈活性的管理面板、儀表板和 B2B 應用程式 您可以在一分鐘內使用單一 CLI 命令進行設定。 它具有適用於 15 多個後端服務的連接器,包括 Hasura、Appwrite 等。 開始使用以下 npm 指令。 ``` npm create refine-app@latest ``` 這就是使用 Refine 新增登入資訊的簡單方法。 ``` import { useLogin } from "@refinedev/core"; const { login } = useLogin(); ``` 您可以閱讀[文件](https://refine.dev/docs/)。 https://github.com/refinedev/refine --- 12. [Zenstack](https://github.com/zenstackhq/zenstack) - 資料庫到 API 和 UI 只需幾分鐘。 ----------------------------------------------------------------------------- ![禪斯塔克](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3b6n2ea3jeeva6uujoex.png) TypeScript 工具包,透過強大的存取控制層增強 Prisma ORM,並釋放其全端開發的全部功能。 開始使用以下 npx 指令。 ``` npx zenstack@latest init ``` 這是透過伺服器適配器建立 RESTful API 的方法。 ``` // pages/api/model/[...path].ts import { requestHandler } from '@zenstackhq/next'; import { enhance } from '@zenstackhq/runtime'; import { getSessionUser } from '@lib/auth'; import { prisma } from '@lib/db'; // Mount Prisma-style APIs: "/api/model/post/findMany", "/api/model/post/create", etc. // Can be configured to provide standard RESTful APIs (using JSON:API) instead. export default requestHandler({ getPrisma: (req, res) => enhance(prisma, { user: getSessionUser(req, res) }), }); ``` 您可以閱讀[文件](https://zenstack.dev/docs/welcome)。 https://github.com/zenstackhq/zenstack --- 13. [Buildship](https://github.com/rowyio/buildship) - 低程式碼視覺化後端建構器。 -------------------------------------------------------------------- ![建造船](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rzlrynz5xephv4t9layd.png) 對於您正在使用無程式碼應用程式建構器(FlutterFlow、Webflow、Framer、Adalo、Bubble、BravoStudio...)或前端框架(Next.js、React、Vue...)建立的應用程式,您需要一個後端來支援可擴展的 API、安全工作流程、自動化等。BuildShip 為您提供了一種完全視覺化的方式,可以在易於使用的完全託管體驗中可擴展地建立這些後端任務。 這意味著您不需要在雲端平台上爭論或部署東西、執行 DevOps 等。只需立即建置和交付 🚀 https://github.com/rowyio/buildship --- 14. [Taipy](https://github.com/Avaiga/taipy) - 將資料和人工智慧演算法整合到生產就緒的 Web 應用程式中。 ----------------------------------------------------------------------------- ![打字](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohv3johuz92lsaux52oq.png) Taipy 是一個開源 Python 庫,用於輕鬆的端到端應用程式開發, 具有假設分析、智慧管道執行、內建調度和部署工具。 開始使用以下命令。 ``` pip install taipy ``` 這是一個典型的Python函數,也是過濾器場景中使用的唯一任務。 ``` def filter_genre(initial_dataset: pd.DataFrame, selected_genre): filtered_dataset = initial_dataset[initial_dataset['genres'].str.contains(selected_genre)] filtered_data = filtered_dataset.nlargest(7, 'Popularity %') return filtered_data ``` 您可以閱讀[文件](https://docs.taipy.io/en/latest/)。 他們還有很多可供您建立的[演示應用程式教學](https://docs.taipy.io/en/latest/knowledge_base/demos/)。 https://github.com/Avaiga/taipy --- 15. [LocalForage](https://github.com/localForage/localForage) - 改進了離線儲存。 ------------------------------------------------------------------------ ![當地飼料](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hlrka5pybvmgmo2djel.png) LocalForage 是一個 JavaScript 函式庫,它透過使用非同步資料儲存和簡單的、類似 localStorage 的 API 來改善 Web 應用程式的離線體驗。它允許開發人員儲存多種類型的資料而不僅僅是字串。 開始使用以下 npm 指令。 ``` npm install localforage ``` 只需包含 JS 檔案並開始使用 localForage。 ``` <script src="localforage.js"></script> ``` 您可以閱讀[文件](https://localforage.github.io/localForage/#installation)。 https://github.com/localForage/localForage --- 16. [Zod](https://github.com/colinhacks/zod) - 使用靜態類型推斷的 TypeScript-first 模式驗證。 ------------------------------------------------------------------------------- ![佐德](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s6zvmqr0lv93vsrhofs.png) Zod 的目標是透過最大限度地減少重複的類型聲明來對開發人員友好。使用 Zod,您聲明一次驗證器,Zod 將自動推斷靜態 TypeScript 類型。將更簡單的類型組合成複雜的資料結構很容易。 開始使用以下 npm 指令。 ``` npm install zod ``` 這是您在建立字串架構時自訂一些常見錯誤訊息的方法。 ``` const name = z.string({ required_error: "Name is required", invalid_type_error: "Name must be a string", }); ``` 您可以閱讀[文件](https://zod.dev/)。 它適用於 Node.js 和所有現代瀏覽器 https://github.com/colinhacks/zod --- 17.[多普勒](https://github.com/DopplerHQ)- 管理你的秘密。 ----------------------------------------------- ![多普勒](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gycxnuiiwsvibryrytlc.png) 您可以透過在具有開發、暫存和生產環境的專案中組織機密來消除機密蔓延。 開始使用以下指令 (MacOS)。 ``` $ brew install dopplerhq/cli/doppler $ doppler --version ``` 這是安裝 Doppler CLI[的 GitHub Actions 工作流程](https://github.com/DopplerHQ/cli-action)。 您可以閱讀[文件](https://docs.doppler.com/docs/start)。 ``` name: Example action on: [push] jobs: my-job: runs-on: ubuntu-latest steps: - name: Install CLI uses: dopplerhq/cli-action@v3 - name: Do something with the CLI run: doppler secrets --only-names env: DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }} ``` https://github.com/DopplerHQ --- 18. [FastAPI](https://github.com/tiangolo/fastapi) - 高效能、易於學習、快速編碼、可用於生產。 ------------------------------------------------------------------------- ![快速API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2awncoia6255ycl95lk.png) FastAPI 是一個現代、快速(高效能)的 Web 框架,用於基於標準 Python 類型提示使用 Python 3.8+ 建立 API。 開始使用以下命令。 ``` $ pip install fastapi ``` 這是您開始使用 FastAPI 的方式。 ``` from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` 您的編輯器將自動完成屬性並了解它們的類型,這是使用 FastAPI 的最佳功能之一。 您可以閱讀[文件](https://fastapi.tiangolo.com/)。 https://github.com/tiangolo/fastapi --- 19. [Flowise](https://github.com/FlowiseAI/Flowise) - 拖放 UI 來建立您的客製化 LLM 流程。 ---------------------------------------------------------------------------- ![流動](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct732wv07pvwx0nmavp5.png) Flowise 是一款開源 UI 視覺化工具,用於建立客製化的 LLM 編排流程和 AI 代理程式。 開始使用以下 npm 指令。 ``` npm install -g flowise npx flowise start OR npx flowise start --FLOWISE_USERNAME=user --FLOWISE_PASSWORD=1234 ``` 這就是整合 API 的方式。 ``` import requests url = "/api/v1/prediction/:id" def query(payload): response = requests.post( url, json = payload ) return response.json() output = query({ question: "hello!" )} ``` 您可以閱讀[文件](https://docs.flowiseai.com/)。 https://github.com/FlowiseAI/Flowise --- 20. [Scrapy](https://github.com/scrapy/scrapy) - Python 的快速進階網頁爬行和抓取框架.. ------------------------------------------------------------------------ ![鬥志旺盛](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k1b2y1hzdsphw43b6v7b.png) Scrapy 是一個快速的高級網路爬行和網頁抓取框架,用於爬行網站並從頁面中提取結構化資料。它可用於多種用途,從資料探勘到監控和自動化測試。 開始使用以下命令。 ``` pip install scrapy ``` 建造並執行您的網路蜘蛛。 ``` pip install scrapy cat > myspider.py <<EOF import scrapy class BlogSpider(scrapy.Spider): name = 'blogspider' start_urls = ['https://www.zyte.com/blog/'] def parse(self, response): for title in response.css('.oxy-post-title'): yield {'title': title.css('::text').get()} for next_page in response.css('a.next'): yield response.follow(next_page, self.parse) EOF scrapy runspider myspider.py ``` 您可以閱讀[文件](https://scrapy.org/doc/)。 它擁有大約 50k+ 的星星,因此對於網頁抓取來說具有巨大的可信度。 https://github.com/scrapy/scrapy --- 21. [Tone](https://github.com/Tonejs/Tone.js) - 在瀏覽器中製作互動式音樂。 ------------------------------------------------------------- ![音調.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fokxsoblaohgs4tx75g3.png) 開始使用以下 npm 指令。 ``` npm install tone ``` 這是您開始使用 Tone.js 的方法 ``` // To import Tone.js: import * as Tone from 'tone' //create a synth and connect it to the main output (your speakers) const synth = new Tone.Synth().toDestination(); //play a middle 'C' for the duration of an 8th note synth.triggerAttackRelease("C4", "8n"); ``` 您可以閱讀[文件](https://github.com/Tonejs/Tone.js?tab=readme-ov-file#installation)。 https://github.com/Tonejs/Tone.js --- 22. [Spacetime](https://github.com/spencermountain/spacetime) - 輕量級 javascript 時區庫。 ----------------------------------------------------------------------------------- ![時空](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/abfyfuzt4nw4h7b8usab.png) 您可以計算遠端時區的時間;支持夏令時、閏年和半球。按季度、季節、月份、週來定位時間.. 開始使用以下 npm 指令。 ``` npm install spacetime ``` 您可以這樣使用它。 ``` <script src="https://unpkg.com/spacetime"></script> <script> var d = spacetime('March 1 2012', 'America/New_York') //set the time d = d.time('4:20pm') d = d.goto('America/Los_Angeles') d.time() //'1:20pm' </script> ``` https://github.com/spencermountain/spacetime --- 23. [Mermaid](https://github.com/mermaid-js/mermaid) - 從類似 markdown 的文字產生圖表。 ---------------------------------------------------------------------------- ![美人魚](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggubn86xv7fznxol6fw7.png) 您可以使用 Markdown with Mermaid 等文字產生流程圖或序列圖等圖表。 這就是建立圖表的方法。 ``` sequenceDiagram Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! ``` 它將做出如下圖。 ![圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbuo2ey5q2x3sjwywizg.png) 您可以閱讀[VS Code](https://docs.mermaidchart.com/plugins/visual-studio-code)的[文件](https://mermaid.js.org/intro/getting-started.html)和外掛程式。 請參閱[即時編輯器](https://mermaid.live/edit#pako:eNpVkE1PwzAMhv9KlvM-2AZj62EIxJd24ADXXLzEbaKlcUkdUDX1v5MONomcnNevXz32UWoyKAvZ4mfCoPHRQRWhVuHeO42T7XZHNhTiFb0nMdRjYelbQETRUbpTwRM1uQ2erbaoDyqI_AbnZfjZVZYFVOBCy8J2DWlLwUQHKmAwKrwRo4gnF5Xid-gd2FEAL9hSyp12pMIpNcee2ArxEhH4LG-3D7TPoAPcnhL_4WVxcgHZkfedqIjMSI5ljbEGZ_LyxwFaSbZYo5JFLg3Eg5Iq9NkHiemjC1oWHBOOZWoM8PlQ_8Un45iiLErwbRY9gcH8PUrumuHKlWs5J2oKpasGPUWfZcvctMVsNrSnlWOb9lNN9ax1xkJk-7VZzVaL1RoWS1zdLuFmuTR6P9-sy8X1vDS3V_MFyL7vfwD_bJ1W)中的範例。 https://github.com/mermaid-js/mermaid --- 24.[公共 API](https://github.com/public-apis/public-apis) - 20 多個類別的 1400 多個 API。 ------------------------------------------------------------------------------- ![公共API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjapk9rwlzdl6bcyqdnl.png) 我們主要使用外部 API 來建立應用程式,在這裡您可以找到所有 API 的清單。網站連結在最後。 它在 GitHub 上擁有大約 279k+ 顆星。 ![公共API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rld5i88smezo1naawz7a.png) 從儲存庫取得網站連結非常困難。所以,我把它貼在這裡。 網址 - [Collective-api.vercel.app/](https://collective-api.vercel.app/) https://github.com/public-apis/public-apis --- 25. [Framer Motion](https://github.com/framer/motion) - 像魔法一樣的動畫。 ----------------------------------------------------------------- ![成幀器運動](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn4ecqkrhs8f4729bzps.png) 可用的最強大的動畫庫之一。 Framer 使用簡單的聲明性語法意味著您編寫的程式碼更少。更少的程式碼意味著您的程式碼庫更易於閱讀和維護。 您可以建立事件和手勢,並且使用 Framer 的社區很大,這意味著良好的支援。 開始使用以下 npm 指令。 ``` npm install framer-motion ``` 您可以這樣使用它。 ``` import { motion } from "framer-motion" <motion.div whileHover={{ scale: 1.2 }} whileTap={{ scale: 1.1 }} drag="x" dragConstraints={{ left: -100, right: 100 }} /> ``` 您可以閱讀[文件](https://www.framer.com/motion/introduction/)。 https://github.com/framer/motion --- 26.[順便說一句](https://github.com/btw-so/btw)- 在幾分鐘內建立您的個人部落格。 ---------------------------------------------------------- ![順便提一句](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnne3lrfpolotmxkdz2m.png) 順便說一句,您可以註冊並使用,而無需安裝任何東西。您也可以使用開源版本自行託管。 ![順便提一句](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rli7hpoccqwpvba29b4.png) 使用順便說一句建立的[範例部落格](https://www.siddg.com/about)。 https://github.com/btw-so/btw --- 27. [Formbricks](https://github.com/formbricks/formbricks) - 開源調查平台。 -------------------------------------------------------------------- ![成型磚](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tp6ggyom33vdifd3m1vt.png) Formbricks 提供免費、開源的測量平台。透過精美的應用程式內、網站、連結和電子郵件調查收集用戶旅程中每個點的回饋。在 Formbricks 之上建置或利用預先建置的資料分析功能。 開始使用以下 npm 指令。 ``` npm install @formbricks/js ``` 這就是您開始使用 formbricks 的方法。 ``` import formbricks from "@formbricks/js"; if (typeof window !== "undefined") { formbricks.init({ environmentId: "claV2as2kKAqF28fJ8", apiHost: "https://app.formbricks.com", }); } ``` 您可以閱讀[文件](https://formbricks.com/docs/getting-started/quickstart-in-app-survey)。 https://github.com/formbricks/formbricks --- 28. [Stripe](https://github.com/stripe) - 支付基礎設施。 ------------------------------------------------- ![條紋](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79yvcgsi4744cmryh15j.png) 數以百萬計的各種規模的公司在線上和親自使用 Stripe 來接受付款、發送付款、自動化財務流程並最終增加收入。 開始使用以下 npm 指令 (React.js)。 ``` npm install @stripe/react-stripe-js @stripe/stripe-js ``` 這就是使用鉤子的方法。 ``` import React, {useState} from 'react'; import ReactDOM from 'react-dom'; import {loadStripe} from '@stripe/stripe-js'; import { PaymentElement, Elements, useStripe, useElements, } from '@stripe/react-stripe-js'; const CheckoutForm = () => { const stripe = useStripe(); const elements = useElements(); const [errorMessage, setErrorMessage] = useState(null); const handleSubmit = async (event) => { event.preventDefault(); if (elements == null) { return; } // Trigger form validation and wallet collection const {error: submitError} = await elements.submit(); if (submitError) { // Show error to your customer setErrorMessage(submitError.message); return; } // Create the PaymentIntent and obtain clientSecret from your server endpoint const res = await fetch('/create-intent', { method: 'POST', }); const {client_secret: clientSecret} = await res.json(); const {error} = await stripe.confirmPayment({ //`Elements` instance that was used to create the Payment Element elements, clientSecret, confirmParams: { return_url: 'https://example.com/order/123/complete', }, }); if (error) { // This point will only be reached if there is an immediate error when // confirming the payment. Show error to your customer (for example, payment // details incomplete) setErrorMessage(error.message); } else { // Your customer will be redirected to your `return_url`. For some payment // methods like iDEAL, your customer will be redirected to an intermediate // site first to authorize the payment, then redirected to the `return_url`. } }; return ( <form onSubmit={handleSubmit}> <PaymentElement /> <button type="submit" disabled={!stripe || !elements}> Pay </button> {/* Show error message to your customers */} {errorMessage && <div>{errorMessage}</div>} </form> ); }; const stripePromise = loadStripe('pk_test_6pRNASCoBOKtIshFeQd4XMUh'); const options = { mode: 'payment', amount: 1099, currency: 'usd', // Fully customizable with appearance API. appearance: { /*...*/ }, }; const App = () => ( <Elements stripe={stripePromise} options={options}> <CheckoutForm /> </Elements> ); ReactDOM.render(<App />, document.body); ``` 您可以閱讀[文件](https://github.com/stripe/react-stripe-js?tab=readme-ov-file#minimal-example)。 您幾乎可以整合任何東西。它有一個巨大的選項清單。 ![整合](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/67f3pb2i8xolt635rp2p.png) https://github.com/stripe --- 29. [Upscayl](https://github.com/upscayl/upscayl) - 開源 AI 影像升級器。 ---------------------------------------------------------------- ![高級](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2c1837rev5jb260ro2sd.png) 適用於 Linux、MacOS 和 Windows 的免費開源 AI Image Upscaler 採用 Linux 優先概念建構。 它可能與全端無關,但它對於升級圖像很有用。 ![高級](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4qq1wm3wey3vihn9al4.png) 透過最先進的人工智慧,Upscayl 可以幫助您將低解析度影像變成高解析度。清脆又鋒利! https://github.com/upscayl/upscayl --- 30.[重新發送](https://github.com/resend)- 為開發人員提供的電子郵件 API。 ------------------------------------------------------- ![重發](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3auhh3hbxjmmzehe5v0.png) 您可以使用 React 建立和傳送電子郵件。 2023 年最受炒作的產品之一。 開始使用以下 npm 指令。 ``` npm install @react-email/components -E ``` 這是將其與 next.js 專案整合的方法。 ``` import { EmailTemplate } from '@/components/email-template'; import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); export async function POST() { const { data, error } = await resend.emails.send({ from: '[email protected]', to: '[email protected]', subject: 'Hello world', react: EmailTemplate({ firstName: 'John' }), }); if (error) { return Response.json({ error }); } return Response.json(data); } ``` 您可以閱讀[文件](https://resend.com/docs/introduction)。 ![重發](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rer9ym187e4i9l11afkg.png) 基本概念是一個簡單、優雅的介面,讓您可以在幾分鐘內開始發送電子郵件。它可以透過適用於您最喜歡的程式語言的 SDK 直接融入您的程式碼中。 https://github.com/resend --- 哇!如此長的專案清單。 我知道您有更多想法,分享它們,讓我們一起建造:D 如今建立全端應用程式並不難,但每個應用程式都可以透過有效地使用優秀的開源專案來解決任何問題來增加這一獨特因素。 例如,您可以建立一些提供通知或建立 UI 流來抓取資料的東西。 我希望其中一些內容對您的開發之旅有用。他們擁有一流的開發人員經驗;你可以依賴他們。 由於您將要建造東西,因此您可以在這裡找到一些[瘋狂的想法](https://github.com/florinpop17/app-ideas)。 祝你有美好的一天!直到下一次。 --- 原文出處:https://dev.to/copilotkit/im-building-a-full-stack-app-here-are-the-libraries-im-going-to-use-51nk

我如何建立 NotesGPT – 一個全端人工智慧語音筆記應用程式

上週,我推出了[notesGPT](https://usenotesgpt.com/) ,這是一款免費開源語音記事應用程式,上週迄今為止已有[35,000 名訪客](https://twitter.com/nutlope/status/1760053364791050285)、7,000 名用戶和超過 1,000 名 GitHub star。它允許您錄製語音筆記,使用[Whisper](https://github.com/openai/whisper)進行轉錄,並透過[Together](https://together.ai/)使用 Mixtral 來提取操作項並將其顯示在操作項視圖中。它也是[完全開源的](https://github.com/nutlope/notesgpt),配備了身份驗證、儲存、向量搜尋、操作項,並且在行動裝置上完全響應,易於使用。 我將向您詳細介紹我是如何建造它的。 架構和技術堆疊 ------- 這是架構的快速圖表。我們將更深入地討論每個部分,並同時展示程式碼範例。 ![架構圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjl3i4bu23fn0pabldsw.png) 這是我使用的整體技術堆疊: - 資料庫和雲端函數的[convex](https://convex.dev/) - Next.js [App Router](https://nextjs.org/docs/app)框架 - [複製](https://replicate.com/)Whisper 轉錄 - LLM 與[JSON 模式](https://docs.together.ai/docs/json-mode)的[Mixtral](https://mistral.ai/news/mixtral-of-experts/) - [Together.ai](http://Together.ai)用於推理和嵌入 - 用於儲存語音註釋的[凸檔存儲](https://docs.convex.dev/file-storage) - [凸向量搜尋](https://docs.convex.dev/vector-search)用於向量搜尋 - 負責使用者身份驗證的[職員](https://clerk.dev/) - [Tailwind CSS](https://tailwindcss.com/)樣式 登陸頁面 ---- 該應用程式的第一部分是您導航到notesGPT 時看到的登入頁面。 ![NotesGPT 的登陸頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0hfscmudh4l33oab3azw.png) 用戶首先看到的是這個登陸頁面,它與應用程式的其餘部分一起使用 Next.js 和 Tailwind CSS 進行樣式建立。我喜歡使用 Next.js,因為它可以輕鬆啟動 Web 應用程式並編寫 React 程式碼。 Tailwind CSS 也很棒,因為它允許您在網頁上快速迭代,同時與 JSX 保持在同一檔案中。 與 Clerk 和 Convex 進行身份驗證 ----------------------- 當使用者點擊主頁上的任一按鈕時,他們將被導向到登入畫面。這是由 Clerk 提供支援的,這是一個與 Convex 很好整合的簡單身份驗證解決方案,我們將在整個後端使用它,包括雲端功能、資料庫、儲存和向量搜尋。 ![認證頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02khgd6f2jfew1w7dufn.png) Clerk 和 Convex 都很容易設定。您只需在這兩個服務上建立一個帳戶,安裝它們的 npm 庫,執行`npx convex dev`來設定您的凸資料夾,然後建立一個如下所示的`ConvexProvider.ts`檔案來包裝您的應用程式。 ``` 'use client'; import { ReactNode } from 'react'; import { ConvexReactClient } from 'convex/react'; import { ConvexProviderWithClerk } from 'convex/react-clerk'; import { ClerkProvider, useAuth } from '@clerk/nextjs'; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); export default function ConvexClientProvider({ children, }: { children: ReactNode; }) { return ( <ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!} > <ConvexProviderWithClerk client={convex} useAuth={useAuth}> {children} </ConvexProviderWithClerk> </ClerkProvider> ); } ``` 請查看[Convex Quickstart](https://docs.convex.dev/quickstart/nextjs)和[Convex Clerk](https://docs.convex.dev/auth/clerk) auth 部分以了解更多詳細資訊。 設定我們的架構 ------- 您可以在有或沒有模式的情況下使用 Convex。就我而言,我知道資料的結構並想要定義它,所以我在下面這樣做了。這也為您提供了一個非常好的類型安全 API,可以在與資料庫互動時使用。我們定義兩個表格-一個用於儲存所有語音註解資訊的`notes`表和用於提取的操作專案的`actionItems`表。我們還將定義索引,以便能夠透過`userId`和`noteId`快速查詢資料。 ``` import { defineSchema, defineTable } from 'convex/server'; import { v } from 'convex/values'; export default defineSchema({ notes: defineTable({ userId: v.string(), audioFileId: v.string(), audioFileUrl: v.string(), title: v.optional(v.string()), transcription: v.optional(v.string()), summary: v.optional(v.string()), embedding: v.optional(v.array(v.float64())), generatingTranscript: v.boolean(), generatingTitle: v.boolean(), generatingActionItems: v.boolean(), }) .index('by_userId', ['userId']) .vectorIndex('by_embedding', { vectorField: 'embedding', dimensions: 768, filterFields: ['userId'], }), actionItems: defineTable({ noteId: v.id('notes'), userId: v.string(), task: v.string(), }) .index('by_noteId', ['noteId']) .index('by_userId', ['userId']), }); ``` 儀表板 --- 現在我們已經有了後端和身份驗證設定以及模式,我們可以看看如何獲取資料。登入應用程式後,用戶可以查看其儀表板,其中列出了他們錄製的所有語音筆記。 ![儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6u9f1b60kgfp4txbszur.png) 為此,我們首先在凸資料夾中定義一個查詢,該查詢使用 auth 接收`userId` ,驗證其有效,並傳回與使用者的`userId`相符的所有註解。 ``` export const getNotes = queryWithUser({ args: {}, handler: async (ctx, args) => { const userId = ctx.userId; if (userId === undefined) { return null; } const notes = await ctx.db .query('notes') .withIndex('by_userId', (q) => q.eq('userId', userId)) .collect(); const results = Promise.all( notes.map(async (note) => { const count = ( await ctx.db .query('actionItems') .withIndex('by_noteId', (q) => q.eq('noteId', note._id)) .collect() ).length; return { count, ...note, }; }), ); return results; }, }); ``` 之後,我們可以透過凸提供的函數使用使用者的驗證令牌來呼叫此`getNotes`查詢,以在儀表板中顯示所有使用者的註解。我們使用伺服器端渲染在伺服器上取得此資料,然後將其傳遞到`<DashboardHomePage />`客戶端元件。這也確保了客戶端上的資料也保持最新。 ``` import { api } from '@/convex/_generated/api'; import { preloadQuery } from 'convex/nextjs'; import DashboardHomePage from './dashboard'; import { getAuthToken } from '../auth'; const ServerDashboardHomePage = async () => { const token = await getAuthToken(); const preloadedNotes = await preloadQuery(api.notes.getNotes, {}, { token }); return <DashboardHomePage preloadedNotes={preloadedNotes} />; }; export default ServerDashboardHomePage; ``` 錄製語音筆記 ------ 最初,使用者的儀表板上不會有任何語音註釋,因此他們可以點擊「錄製新語音註釋」按鈕來錄製。他們將看到以下螢幕,允許他們進行錄製。 ![錄製語音筆記頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3lm22akd3zanf3ar0za.png) 這將使用本機瀏覽器 API 錄製語音筆記,將檔案保存在 Convex 檔案儲存中,然後透過 Replicate 將其傳送至 Whisper 進行轉錄。我們要做的第一件事是在凸資料夾中定義一個`createNote`突變,它將接收此記錄,在凸資料庫中保存一些訊息,然後呼叫耳語操作。 ``` export const createNote = mutationWithUser({ args: { storageId: v.id('_storage'), }, handler: async (ctx, { storageId }) => { const userId = ctx.userId; let fileUrl = (await ctx.storage.getUrl(storageId)) as string; const noteId = await ctx.db.insert('notes', { userId, audioFileId: storageId, audioFileUrl: fileUrl, generatingTranscript: true, generatingTitle: true, generatingActionItems: true, }); await ctx.scheduler.runAfter(0, internal.whisper.chat, { fileUrl, id: noteId, }); return noteId; }, }); ``` 耳語動作如下圖所示。它使用 Replicate 作為 Whisper 的託管提供者。 ``` export const chat = internalAction({ args: { fileUrl: v.string(), id: v.id('notes'), }, handler: async (ctx, args) => { const replicateOutput = (await replicate.run( 'openai/whisper:4d50797290df275329f202e48c76360b3f22b08d28c196cbc54600319435f8d2', { input: { audio: args.fileUrl, model: 'large-v3', translate: false, temperature: 0, transcription: 'plain text', suppress_tokens: '-1', logprob_threshold: -1, no_speech_threshold: 0.6, condition_on_previous_text: true, compression_ratio_threshold: 2.4, temperature_increment_on_fallback: 0.2, }, }, )) as whisperOutput; const transcript = replicateOutput.transcription || 'error'; await ctx.runMutation(internal.whisper.saveTranscript, { id: args.id, transcript, }); }, }); ``` 此外,所有這些檔案都可以在 Convex 儀表板的「檔案」下看到。 ![凸形儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz51ysreunwsk52tqjr9.png) 生成行動專案 ------ 使用者完成語音記錄並透過耳語進行轉錄後,輸出將傳遞到 Together AI 中。我們同時顯示此加載畫面。 ![頁面載入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rcr80meap2xql9nrzlf.png) 我們首先定義一個我們希望輸出所在的模式。然後,我們將此模式傳遞到 Together.ai 上託管的 Mixtral 模型中,並提示辨識語音註釋的摘要、文字記錄,並根據成績單。然後我們將所有這些資訊保存到 Convex 資料庫中。為此,我們在凸資料夾中建立一個凸動作。 ``` // convex/together.ts const NoteSchema = z.object({ title: z .string() .describe('Short descriptive title of what the voice message is about'), summary: z .string() .describe( 'A short summary in the first person point of view of the person recording the voice message', ) .max(500), actionItems: z .array(z.string()) .describe( 'A list of action items from the voice note, short and to the point. Make sure all action item lists are fully resolved if they are nested', ), }); export const chat = internalAction({ args: { id: v.id('notes'), transcript: v.string(), }, handler: async (ctx, args) => { const { transcript } = args; const extract = await client.chat.completions.create({ messages: [ { role: 'system', content: 'The following is a transcript of a voice message. Extract a title, summary, and action items from it and answer in JSON in this format: {title: string, summary: string, actionItems: [string, string, ...]}', }, { role: 'user', content: transcript }, ], model: 'mistralai/Mixtral-8x7B-Instruct-v0.1', response_model: { schema: NoteSchema, name: 'SummarizeNotes' }, max_tokens: 1000, temperature: 0.6, max_retries: 3, }); const { title, summary, actionItems } = extract; await ctx.runMutation(internal.together.saveSummary, { id: args.id, summary, actionItems, title, }); }); ``` 當 Together.ai 做出回應時,我們會看到最終畫面,使用者可以在左側的記錄和摘要之間切換,並查看並勾選右側的操作專案。 ![完整語音筆記頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnd6j68hgusa0aj2buhv.png) 向量搜尋 ---- 該應用程式的最後一部分是向量搜尋。我們使用 Together.ai 嵌入來嵌入文字記錄,並使人們可以根據文字記錄的語義在儀表板中進行搜尋。 我們透過在凸資料夾中建立一個`similarNotes`操作來實現此目的,該操作接受使用者的搜尋查詢,為其產生嵌入,並找到要在頁面上顯示的最相似的註釋。 ``` export const similarNotes = actionWithUser({ args: { searchQuery: v.string(), }, handler: async (ctx, args): Promise<SearchResult[]> => { // 1. Create the embedding const getEmbedding = await togetherai.embeddings.create({ input: [args.searchQuery.replace('/n', ' ')], model: 'togethercomputer/m2-bert-80M-32k-retrieval', }); const embedding = getEmbedding.data[0].embedding; // 2. Then search for similar notes const results = await ctx.vectorSearch('notes', 'by_embedding', { vector: embedding, limit: 16, filter: (q) => q.eq('userId', ctx.userId), // Only search my notes. }); return results.map((r) => ({ id: r._id, score: r._score, })); }, }); ``` 結論 -- 就像這樣,我們建立了一個可投入生產的全端人工智慧應用程式,配備身份驗證、資料庫、儲存和 API。請隨意查看[notesGPT,](https://usenotesgpt.com/)以從您的筆記或[GitHub 儲存庫](https://github.com/nutlope/notesGPT)產生操作專案以供參考。如果您有任何疑問,[請私訊我](twitter.com/nutlope),我將非常樂意回答! --- 原文出處:https://dev.to/nutlope/how-i-built-notesgpt-a-full-stack-ai-voice-note-app-265o

Next.js 14 使用瀏覽器爬蟲,進行即時資料抓取的預訂應用程式

目錄 == - [介紹](#introduction) - [技術堆疊](#tech-stack) - [特徵](#features) - [設定 Next.js 應用程式](#step-1-setting-up-the-nextjs-application) - [安裝所需的套件](#step-2-installing-required-packages) - [設定 Redis 連接](#step-3-setting-up-redis-connection) - [配置 BullMQ 佇列](#step-4-configuring-bullmq-queue) - [Next.js 儀器設置](#step-5-nextjs-instrumentation-setup) - [設定 Bright Data 的抓取瀏覽器](#step-6-setting-up-bright-datas-scraping-browser) - [Bright Data 的抓取瀏覽器是什麼?](#what-is-bright-datas-scraping-browser) - [設定 Bright Data 抓取瀏覽器的步驟](#steps-to-set-up-bright-datas-scraping-browser) - [使用 Puppeteer 實作抓取邏輯](#implementing-the-scraping-logic-with-puppeteer) - [航班搜尋功能](#flight-search-feature) - [顯示航班搜尋結果](#displaying-flight-search-results) - [探索完整的指南和程式碼庫](#discover-the-complete-guide-and-codebase) - [在 YouTube 上觀看詳細說明](#watch-the-detailed-explanation-on-youtube) - [在 GitHub 上探索完整程式碼](#explore-the-full-code-on-github) - [結論](#conclusion) 介紹 == 在不斷發展的 Web 開發領域,有效收集、處理和顯示外部來源資料的能力變得越來越有價值。無論是市場研究、競爭分析或客戶洞察,網路抓取在釋放網路資料的巨大潛力方面都發揮著至關重要的作用。 這篇部落格文章介紹了建立強大的 Next.js 應用程式的綜合指南,該應用程式旨在從領先的旅行搜尋引擎之一 Kayak 抓取航班資料。透過利用 Next.js 的強大功能以及 BullMQ、Redis 和 Puppeteer 等現代技術。 技術堆疊 ==== - [Next.js](https://nextjs.org/docs) - [順風CSS](https://tailwindcss.com/docs) - [下一個介面](https://nextui.org/docs) - [健康)狀況](https://zustand.surge.sh/) - [條紋](https://stripe.com/docs) - [Bright Data 的抓取瀏覽器](https://brdta.com/kishansheth21) - [打字稿](https://www.typescriptlang.org/docs) - [雷迪斯](https://redis.io/documentation) - [BullMQ](https://docs.bullmq.io/) - [傀儡師](https://pptr.dev/) - [智威湯遜](https://jwt.io/introduction) - [阿克西奧斯](https://axios-http.com/docs/intro) - [PostgreSQL](https://www.postgresql.org/docs) - [棱鏡](https://www.prisma.io/docs) 特徵 == - 🚀 帶有 Tailwind CSS 的 Next.js 14 應用程式目錄 - 體驗由最新 Next.js 14 提供支援的時尚現代的 UI,並使用 Tailwind CSS 進行設計,以實現完美的外觀和感覺。 - 🔗 API 路由和伺服器操作 - 深入研究與 Next.js 14 的 API 路由和伺服器操作的無縫後端集成,確保高效的資料處理和伺服器端邏輯執行。 - 🕷 使用 Puppeteer Redis 和 BullMQ 進行抓取 - 利用 Puppeteer 的強大功能進行進階 Web 抓取,並使用 Redis 和 BullMQ 管理佇列和作業以實現強大的後端操作。 - 🔑 用於身份驗證和授權的 JWT 令牌 - 使用 JWT 令牌保護您的應用程式,為整個平台提供可靠的身份驗證和授權方法。 - 💳 支付網關 Stripe - 整合 Stripe 進行無縫支付處理,為預訂旅行、航班和飯店提供安全、輕鬆的交易。 - ✈️ 使用 Stripe 支付網關預訂旅行、航班和飯店 - 使用我們的 Stripe 支援的支付系統,讓您的旅遊預訂體驗變得輕鬆。 - 📊 從多個網站抓取即時資料 - 從多個來源抓取即時資料,保持領先,讓您的應用程式更新最新資訊。 - 💾 使用 Prisma 將抓取的資料儲存在 PostgreSQL 中 - 利用 PostgreSQL 和 Prisma 高效儲存和管理抓取的資料,確保可靠性和速度。 - 🔄 用於狀態管理的 Zustand - 透過 Zustand 簡化狀態邏輯並增強效能,在您的應用程式中享受流暢且可管理的狀態管理。 - 😈 該應用程式的最佳功能 - 使用 Bright Data 的抓取瀏覽器抓取不可抓取的資料。 ![抓取瀏覽器迷因](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0ytki1wpsbvluk1qkpo.jpg) Bright Data的抓取瀏覽器為我們提供了自動驗證碼解決功能,可以幫助我們抓取不可抓取的資料。 第 1 步:設定 Next.js 應用程式 --------------------- 1. **建立 Next.js 應用程式**:首先建立一個新的 Next.js 應用程式(如果您還沒有)。您可以透過在終端機中執行以下命令來完成此操作: ``` npx create-next-app@latest booking-app ``` 2. **導航到您的應用程式目錄**:變更為您新建立的應用程式目錄: ``` cd booking-app ``` 步驟2:安裝所需的軟體包 ------------ 您需要安裝多個軟體包,包括 Redis、BullMQ 和 Puppeteer Core。執行以下命令來安裝它們: ``` npm install ioredis bullmq puppeteer-core ``` - `ioredis`是 Node.js 的強大 Redis 用戶端,支援與 Redis 進行通訊。 - `bullmq`以 Redis 作為後端來管理作業和訊息佇列。 - `puppeteer-core`可讓您控制外部瀏覽器以進行抓取。 步驟3:設定Redis連接 ------------- 在適當的目錄(例如`lib/` )中建立一個檔案(例如`redis.js` )來配置 Redis 連線: ``` // lib/redis.js import Redis from 'ioredis'; // Use REDIS_URL from environment or fallback to localhost const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; const connection = new Redis(REDIS_URL); export { connection }; ``` 步驟4:配置BullMQ佇列 -------------- 透過在 Redis 配置所在的相同目錄中建立另一個檔案(例如, `queue.js` )來設定 BullMQ 佇列: ``` // lib/queue.js import { Queue } from 'bullmq'; import { connection } from './redis'; export const importQueue = new Queue('importQueue', { connection, defaultJobOptions: { attempts: 2, backoff: { type: 'exponential', delay: 5000, }, }, }); ``` 第 5 步:Next.js 儀器設置 ------------------ Next.js 允許偵測,可以在 Next.js 配置中啟用。您還需要建立一個用於作業處理的工作文件。 1.**在 Next.js 中啟用 Instrumentation** :將以下內容新增至`next.config.js`以啟用 Instrumentation: ``` // next.config.js module.exports = { experimental: { instrumentationHook: true, }, }; ``` 2.**建立用於作業處理的 Worker** :在您的應用程式中,建立一個檔案 ( `instrumentation.js` ) 來處理作業處理。該工作人員將使用 Puppeteer 來執行抓取任務: ``` // instrumentation.js export const register = async () => { if (process.env.NEXT_RUNTIME === 'nodejs') { const { Worker } = await import('bullmq'); const puppeteer = await import('puppeteer-core'); const { connection } = await import('./lib/redis'); const { importQueue } = await import('./lib/queue'); new Worker('importQueue', async (job) => { // Job processing logic with Puppeteer goes here }, { connection, concurrency: 10, removeOnComplete: { count: 1000 }, removeOnFail: { count: 5000 }, }); } }; ``` 第 6 步:設定 Bright Data 的抓取瀏覽器 --------------------------- 在設定 Bright 資料抓取瀏覽器之前,我們先來談談什麼是抓取瀏覽器。 ### Bright Data 的抓取瀏覽器是什麼? Bright Data 的抓取瀏覽器是一款用於自動網頁抓取的尖端工具,旨在與 Puppeteer、Playwright 和 Selenium 無縫整合。它提供了一套網站解鎖功能,包括代理輪換、驗證碼解決等,以提高抓取效率。它非常適合需要互動的複雜網頁抓取,透過在 Bright Data 基礎架構上託管無限的瀏覽器會話來實現可擴展性。如欲了解更多詳情,請造訪[光明資料](https://brdta.com/kishansheth21)。 ![明亮的資料抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c70ochb1lgdrusgicwz4.png) <a id="steps-to-set-up-bright-datas-scraping-browser"></a> #### 第 1 步:導覽至 Bright Data 網站 首先造訪[Brightdata.com](https://brdta.com/kishansheth21) 。這是您存取 Bright Data 提供的豐富網頁抓取資源和工具的入口。 ![光明資料首頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/afgkpgytcytoqfuq0h8w.png) #### 第 2 步:建立帳戶 造訪 Bright Data 網站後,註冊並建立一個新帳戶。系統將提示您輸入基本資訊以啟動並執行您的帳戶。 ![登入/註冊 Bright Data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ffha75szs9tubh8kra9j.png) #### 第 3 步:選擇您的產品 在產品選擇頁面上,尋找代理商和抓取基礎設施產品。本產品專為滿足您的網路抓取需求而設計,提供強大的資料擷取工具和功能。 ![光明資料產品](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b058azlmjv30po6289fh.png) #### 第 4 步:新增代理 在「代理程式和抓取基礎設施」頁面中,您會找到一個「新增按鈕」。點擊此按鈕開始將新的抓取瀏覽器新增到您的工具包的過程。 ![新代理](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jscxsyt9yess1nvzi4z.png) #### 第五步:選擇抓取瀏覽器 將出現一個下拉列表,您應該從中選擇抓取瀏覽器選項。這告訴 Bright Data 您打算設定一個新的抓取瀏覽器環境。 ![選擇抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i483ipx7spwne65c6tep.png) #### 第 6 步:為您的抓取瀏覽器命名 為您的新抓取瀏覽器指定一個唯一的名稱。這有助於稍後辨識和管理它,特別是如果您計劃對不同的抓取專案使用多個瀏覽器。 ![抓取瀏覽器名稱](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0qnitec7s7gki7t3826.png) #### 步驟7:新增瀏覽器 命名您的瀏覽器後,按一下「新增」按鈕。此操作完成了新的抓取瀏覽器的建立。 ![新增抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ao6n1edyyx2no621nh01.png) #### 第 8 步:查看您的抓取瀏覽器詳細訊息 新增抓取瀏覽器後,您將被導向到一個頁面,您可以在其中查看新建立的抓取瀏覽器的所有詳細資訊。這些資訊對於整合和使用至關重要。 ![抓取瀏覽器詳細訊息](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ev33mbgznevn6h9p60g6.png) #### 第 9 步:存取程式碼和整合範例 尋找“查看程式碼和整合範例”按鈕。點擊此按鈕將為您提供如何跨多種程式語言和程式庫整合和使用抓取瀏覽器的全面視圖。對於希望自訂抓取設定的開發人員來說,此資源非常寶貴。 ![程式碼和整合範例按鈕](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30araqzko585yzmhrumd.png) #### 第 10 步:整合您的抓取瀏覽器 最後,複製 SRS\_WS\_ENDPOINT 變數。這是一條關鍵訊息,您需要將其整合到原始程式碼中,以便您的應用程式能夠與您剛剛設定的抓取瀏覽器進行通訊。 ![抓取瀏覽器端點](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqhpglz1lngobguk2nu4.png) 透過遵循這些詳細步驟,您已在 Bright Data 平台中成功建立了一個抓取瀏覽器,準備好處理您的網頁抓取任務。請記住,Bright Data 提供廣泛的文件和支持,幫助您最大限度地提高抓取專案的效率和效果。無論您是在收集市場情報、進行研究還是監控競爭格局,新設定的抓取瀏覽器都是資料收集庫中的強大工具。 ### 第 7 步:使用 Puppeteer 實作抓取邏輯 從我們上次設定用於抓取航班資料的 Next.js 應用程式的地方開始,下一個關鍵步驟是實現實際的抓取邏輯。此過程涉及利用 Puppeteer 連接到瀏覽器實例、導航到目標 URL(在我們的範例中為 Kayak)並抓取必要的飛行資料。提供的程式碼片段概述了實現此目標的複雜方法,與我們先前建立的 BullMQ 工作設定無縫整合。讓我們分解這個抓取邏輯的元件,並了解它如何適合我們的應用程式。 #### 建立與瀏覽器的連接 我們抓取過程的第一步是透過 Puppeteer 建立與瀏覽器的連線。這是透過利用`puppeteer.connect`方法來完成的,該方法使用 WebSocket 端點 ( `SBR_WS_ENDPOINT` ) 連接到現有的瀏覽器實例。此環境變數應設定為您正在使用的抓取瀏覽器服務的 WebSocket URL,例如 Bright Data: ``` const browser = await puppeteer.connect({ browserWSEndpoint: SBR_WS_ENDPOINT, }); ``` #### 開啟新頁面並導航到目標 URL 連線後,我們在瀏覽器中建立一個新頁面並導航到作業資料中指定的目標 URL。此 URL 是我們打算從中抓取航班資料的特定 Kayak 搜尋結果頁面: ``` const page = await browser.newPage(); await page.goto(job.data.url); ``` #### 抓取航班資料 我們邏輯的核心在於從頁面中抓取航班資料。我們透過使用`page.evaluate`來實現這一點,這是一種 Puppeteer 方法,允許我們在瀏覽器上下文中執行腳本。在此腳本中,我們等待必要的元素加載,然後繼續收集航班資訊: - **Flight Selector** :我們以`.nrc6-wrapper`類別為目標元素,其中包含航班詳細資訊。 - **資料擷取**:對於每個航班元素,我們提取詳細訊息,例如航空公司徽標、出發和到達時間、航班持續時間、航空公司名稱和價格。出發和到達時間經過清理,以刪除最後不必要的數值,確保我們準確地捕捉時間。 - **價格處理**:價格在刪除所有非數字字元後提取為整數,確保其可用於數值運算或比較。 擷取的資料被建構成飛行物件陣列,每個物件都包含上述詳細資訊: ``` const scrappedFlights = await page.evaluate(async () => { // Data extraction logic const flights = []; // Process each flight element // ... return flights; }); ``` #### 錯誤處理和清理 我們的抓取邏輯被包裝在一個 try-catch 區塊中,以在抓取過程中優雅地處理任何潛在的錯誤。無論結果如何,我們都會確保瀏覽器在finally區塊中正確關閉,從而保持資源效率並防止潛在的記憶體洩漏: ``` try { // Scraping logic } catch (error) { console.log({ error }); } finally { await browser.close(); console.log("Browser closed successfully."); } ``` #### 整個程式碼 ``` const SBR_WS_ENDPOINT = process.env.SBR_WS_ENDPOINT; export const register = async () => { if (process.env.NEXT_RUNTIME === "nodejs") { const { Worker } = await import("bullmq"); const puppeteer = await import("puppeteer"); const { connection } = await import("./lib/redis"); const { importQueue } = await import("./lib/queue"); new Worker( "importQueue", async (job) => { const browser = await puppeteer.connect({ browserWSEndpoint: SBR_WS_ENDPOINT, }); try { const page = await browser.newPage(); console.log("in flight scraping"); console.log("Connected! Navigating to " + job.data.url); await page.goto(job.data.url); console.log("Navigated! Scraping page content..."); const scrappedFlights = await page.evaluate(async () => { await new Promise((resolve) => setTimeout(resolve, 5000)); const flights = []; const flightSelectors = document.querySelectorAll(".nrc6-wrapper"); flightSelectors.forEach((flightElement) => { const airlineLogo = flightElement.querySelector("img")?.src || ""; const [rawDepartureTime, rawArrivalTime] = ( flightElement.querySelector(".vmXl")?.innerText || "" ).split(" – "); // Function to extract time and remove numeric values at the end const extractTime = (rawTime: string): string => { const timeWithoutNumbers = rawTime .replace(/[0-9+\s]+$/, "") .trim(); return timeWithoutNumbers; }; const departureTime = extractTime(rawDepartureTime); const arrivalTime = extractTime(rawArrivalTime); const flightDuration = ( flightElement.querySelector(".xdW8")?.children[0]?.innerText || "" ).trim(); const airlineName = ( flightElement.querySelector(".VY2U")?.children[1]?.innerText || "" ).trim(); // Extract price const price = parseInt( ( flightElement.querySelector(".f8F1-price-text")?.innerText || "" ) .replace(/[^\d]/g, "") .trim(), 10 ); flights.push({ airlineLogo, departureTime, arrivalTime, flightDuration, airlineName, price, }); }); return flights; }); } catch (error) { console.log({ error }); } finally { await browser.close(); console.log("Browser closed successfully."); } }, { connection, concurrency: 10, removeOnComplete: { count: 1000 }, removeOnFail: { count: 5000 }, } ); } }; ``` ### 步驟8:航班搜尋功能 基於我們的航班資料抓取功能,讓我們將全面的航班搜尋功能整合到我們的 Next.js 應用程式中。此功能將為使用者提供一個動態介面,透過指定出發地、目的地和日期來搜尋航班。利用強大的 Next.js 框架以及現代 UI 庫和狀態管理,我們建立了引人入勝且響應迅速的航班搜尋體驗。 #### 航班搜尋功能的關鍵組成部分 1. **動態城市選擇**:此功能包括來源和目的地輸入的自動完成功能,由預先定義的城市機場程式碼清單提供支援。當使用者輸入時,應用程式會過濾並顯示匹配的城市,透過更輕鬆地尋找和選擇機場來增強用戶體驗。 2. **日期選擇**:使用者可以透過日期輸入選擇預期的航班日期,為規劃旅行提供彈性。 3. **抓取狀態監控**:啟動抓取作業後,應用程式透過定期 API 呼叫來監控作業的狀態。這種非同步檢查允許應用程式使用抓取過程的狀態更新 UI,確保使用者了解進度和結果。 #### 航班搜尋元件的完整程式碼 ``` "use client"; import { useAppStore } from "@/store"; import { USER_API_ROUTES } from "@/utils/api-routes"; import { cityAirportCode } from "@/utils/city-airport-codes"; import { Button, Input, Listbox, ListboxItem } from "@nextui-org/react"; import axios from "axios"; import Image from "next/image"; import { useRouter } from "next/navigation"; import React, { useEffect, useRef, useState } from "react"; import { FaCalendarAlt, FaSearch } from "react-icons/fa"; const SearchFlights = () => { const router = useRouter(); const { setScraping, setScrapingType, setScrappedFlights } = useAppStore(); const [loadingJobId, setLoadingJobId] = useState<number | undefined>(undefined); const [source, setSource] = useState(""); const [sourceOptions, setSourceOptions] = useState< { city: string; code: string; }[] >([]); const [destination, setDestination] = useState(""); const [destinationOptions, setDestinationOptions] = useState< { city: string; code: string; }[] >([]); const [flightDate, setFlightDate] = useState(() => { const today = new Date(); return today.toISOString().split("T")[0]; }); const handleSourceChange = (query: string) => { const matchingCities = Object.entries(cityAirportCode) .filter(([, city]) => city.toLowerCase().includes(query.toLowerCase())) .map(([code, city]) => ({ code, city })) .splice(0, 5); setSourceOptions(matchingCities); }; const destinationChange = (query: string) => { const matchingCities = Object.entries(cityAirportCode) .filter(([, city]) => city.toLowerCase().includes(query.toLowerCase())) .map(([code, city]) => ({ code, city })) .splice(0, 5); setDestinationOptions(matchingCities); }; const startScraping = async () => { if (source && destination && flightDate) { const data = await axios.get(`${USER_API_ROUTES.FLIGHT_SCRAPE}?source=${source}&destination=${destination}&date=${flightDate}`); if (data.data.id) { setLoadingJobId(data.data.id); setScraping(true); setScrapingType("flight"); } } }; useEffect(() => { if (loadingJobId) { const checkIfJobCompleted = async () => { try { const response = await axios.get(`${USER_API_ROUTES.FLIGHT_SCRAPE_STATUS}?jobId=${loadingJobId}`); if (response.data.status) { set ScrappedFlights(response.data.flights); clearInterval(jobIntervalRef.current); setScraping(false); setScrapingType(undefined); router.push(`/flights?data=${flightDate}`); } } catch (error) { console.log(error); } }; jobIntervalRef.current = setInterval(checkIfJobCompleted, 3000); } return () => clearInterval(jobIntervalRef.current); }, [loadingJobId]); return ( <div className="h-[90vh] flex items-center justify-center"> <div className="absolute left-0 top-0 h-[100vh] w-[100vw] max-w-[100vw] overflow-hidden overflow-x-hidden"> <Image src="/flight-search.png" fill alt="Search" /> </div> <div className="absolute h-[50vh] w-[60vw] flex flex-col gap-5"> {/* UI and functionality for flight search */} </div> </div> ); }; export default SearchFlights; ``` ### 步驟9:航班搜尋頁面UI ![航班搜尋頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54bkhpea27fkzg4vlur1.png) ### 顯示航班搜尋結果 成功抓取飛行資料後,下一個關鍵步驟是以使用者友善的方式將這些結果呈現給使用者。 Next.js 應用程式中的 Flights 元件就是為此目的而設計的。 ``` "use client"; import { useAppStore } from "@/store"; import { USER_API_ROUTES } from "@/utils/api-routes"; import { Button } from "@nextui-org/react"; import axios from "axios"; import Image from "next/image"; import { useRouter, useSearchParams } from "next/navigation"; import React from "react"; import { FaChevronLeft } from "react-icons/fa"; import { MdOutlineFlight } from "react-icons/md"; const Flights = () => { const router = useRouter(); const searchParams = useSearchParams(); const date = searchParams.get("date"); const { scrappedFlights, userInfo } = useAppStore(); const getRandomNumber = () => Math.floor(Math.random() * 41); const bookFLight = async (flightId: number) => {}; return ( <div className="m-10 px-[20vw] min-h-[80vh]"> <Button className="my-5" variant="shadow" color="primary" size="lg" onClick={() => router.push("/search-flights")} > <FaChevronLeft /> Go Back </Button> <div className="flex-col flex gap-5"> {scrappedFlights.length === 0 && ( <div className="flex items-center justify-center py-5 px-10 mt-10 rounded-lg text-red-500 bg-red-100 font-medium"> No Flights found. </div> )} {scrappedFlights.map((flight: any) => { const seatsLeft = getRandomNumber(); return ( <div key={flight.id} className="grid grid-cols-12 border bg-gray-200 rounded-xl font-medium drop-shadow-md" > <div className="col-span-9 bg-white rounded-l-xl p-10 flex flex-col gap-5"> <div className="grid grid-cols-4 gap-4"> <div className="flex flex-col gap-3 font-medium"> <div> <div className="relative w-20 h-16"> <Image src={flight.logo} alt="airline name" fill /> </div> </div> <div>{flight.name}</div> </div> <div className="col-span-3 flex justify-between"> <div className="flex flex-col gap-2"> <div className="text-blue-600">From</div> <div> <span className="text-3xl"> <strong>{flight.departureTime}</strong> </span> </div> <div>{flight.from}</div> </div> <div className="flex flex-col items-center justify-center gap-2"> <div className="bg-violet-100 w-max p-3 text-4xl text-blue-600 rounded-full"> <MdOutlineFlight /> </div> <div> <span className="text-lg"> <strong>Non-stop</strong> </span> </div> <div>{flight.duration}</div> </div> <div className="flex flex-col gap-2"> <div className="text-blue-600">To</div> <div> <span className="text-3xl"> <strong>{flight.arrivalTime}</strong> </span> </div> <div>{flight.to}</div> </div> </div> </div> <div className="flex justify-center gap-10 bg-violet-100 p-3 rounded-lg"> <div className="flex"> <span>Airplane  </span> <span className="text-blue-600 font-semibold"> Boeing 787 </span> </div> <div className="flex"> <span>Travel Class:  </span> <span className="text-blue-600 font-semibold">Economy</span> </div> </div> <div className="flex justify-between font-medium"> <div> Refundable <span className="text-blue-600"> $5 ecash</span> </div> <div className={`${ seatsLeft > 20 ? "text-green-500" : "text-red-500" }`} > Only {seatsLeft} Seats Left </div> <div className="cursor-pointer">Flight Details</div> </div> </div> <div className="col-span-3 bg-violet-100 rounded-r-xl h-full flex flex-col items-center justify-center gap-5"> <div> <div> <span className="line-through font-light"> ${flight.price + 140} </span> </div> <div className="flex items-center gap-2"> <span className="text-5xl font-bold">${flight.price}</span> <span className="text-blue-600">20% OFF</span> </div> </div> <Button variant="ghost" radius="full" size="lg" color="primary" onClick={() => { if (userInfo) bookFLight(flight.id); }} > {userInfo ? "Book Now" : "Login to Book"} </Button> </div> </div> ); })} </div> </div> ); }; export default Flights; ``` #### 航班搜尋結果 ![航班搜尋結果](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxufusoyrf6hgtrrcmsw.png) ### 探索完整的指南和程式碼庫 上面共享的部分和程式碼片段僅代表使用 Next.js 建立強大的航班資料抓取和搜尋應用程式所需的完整功能和程式碼的一小部分。為了掌握這個專案的全部內容,包括高級功能、優化和最佳實踐,我邀請您更深入地研究我的線上綜合資源。 #### 在 YouTube 上觀看詳細說明 有關引導您完成此應用程式的開發過程、編碼細微差別和功能的逐步影片指南,請觀看我的 YouTube 影片。本教程旨在讓您更深入地了解這些概念,讓您按照自己的步調進行操作並獲得對 Next.js 應用程式開發的寶貴見解。 https://www.youtube.com/watch?v=ZWVhk0fxHM0 #### 在 GitHub 上探索完整程式碼 如果您渴望探索完整的程式碼,請造訪我的 GitHub 儲存庫。在那裡,您將找到完整的程式碼庫,包括讓該應用程式在您自己的電腦上執行所需的所有元件、實用程式和設定說明。 https://github.com/koolkishan/nextjs-travel-planner ### 結論 使用 Next.js 建立飛行資料抓取和搜尋工具等綜合應用程式展示了現代 Web 開發工具和框架的強大功能和多功能性。無論您是希望提高技能的經驗豐富的開發人員,還是渴望深入 Web 開發的初學者,這些資源都是為您的旅程量身定制的。在 YouTube 上觀看詳細教程,在 GitHub 上探索完整程式碼,並加入對話以增強您的開發專業知識並為充滿活力的開發者社群做出貢獻。 --- 原文出處:https://dev.to/kishansheth/nextjs-14-booking-app-with-live-data-scraping-using-scraping-browser-610

70 個 JavaScript 面試問題

嗨大家好,新年快樂:煙火::煙火::煙火:! ---------------------- 這是一篇很長的文章,所以請耐心聽我一秒鐘或一個小時。每個問題的每個答案都有一個向上箭頭**↑**連結,可讓您返回到問題列表,這樣您就不會浪費時間上下滾動。 ### 問題 - [1. `undefined`和`null`有什麼差別?](#1-whats-the-difference-between-undefined-and-null) - [2. &amp;&amp; 運算子的作用是什麼?](#2-what-does-the-ampamp-operator-do) - [3. || 是什麼意思?運營商做什麼?](#3-what-does-the-operator-do) - [4. 使用 + 或一元加運算子是將字串轉換為數字的最快方法嗎?](#4-is-using-the-or-unary-plus-operator-the-fastest-way-in-converting-a-string-to-a-number) - [5.什麼是DOM?](#5-what-is-the-dom) - [6.什麼是事件傳播?](#6-what-is-event-propagation) - [7.什麼是事件冒泡?](#7-whats-event-bubbling) - [8. 什麼是事件擷取?](#8-whats-event-capturing) - [9. `event.preventDefault()`和`event.stopPropagation()`方法有什麼差別?](#9-whats-the-difference-between-eventpreventdefault-and-eventstoppropagation-methods) - [10. 如何知道元素中是否使用了`event.preventDefault()`方法?](#10-how-to-know-if-the-eventpreventdefault-method-was-used-in-an-element) - [11. 為什麼這段程式碼 obj.someprop.x 會拋出錯誤?](#11-why-does-this-code-objsomepropx-throw-an-error) - \[12.什麼是`event.target` ?\](#12-什麼是 eventtarget- ) - [13.什麼是`event.currentTarget` ?](#13-what-is-eventcurrenttarget) - [14. `==`和`===`有什麼差別?](#14-whats-the-difference-between-and-) - [15. 為什麼在 JavaScript 中比較兩個相似的物件時回傳 false?](#15-why-does-it-return-false-when-comparing-two-similar-objects-in-javascript) - [16. `!!`是什麼意思?運營商做什麼?](#16-what-does-the-operator-do) - [17. 如何計算一行中的多個表達式?](#17-how-to-evaluate-multiple-expressions-in-one-line) - [18.什麼是吊裝?](#18-what-is-hoisting) - [19.什麼是範圍?](#19-what-is-scope) - [20.什麼是閉包?](#20-what-are-closures) - [21. JavaScript 中的假值是什麼?](#21-what-are-the-falsy-values-in-javascript) - [22. 如何檢查一個值是否為假值?](#22-how-to-check-if-a-value-is-falsy) - [23. `"use strict"`有什麼作用?](#23-what-does-use-strict-do) - [24. JavaScript 中`this`的值是什麼?](#24-whats-the-value-of-this-in-javascript) - [25. 物件的`prototype`是什麼?](#25-what-is-the-prototype-of-an-object) - \[26.什麼是 IIFE,它有什麼用?\](#26-what-is-an-iife-what-is-the-use-of-it ) - [27. `Function.prototype.apply`方法有什麼用?](#27-what-is-the-use-functionprototypeapply-method) - [28. `Function.prototype.call`方法有什麼用?](#28-what-is-the-use-functionprototypecall-method) - [29. `Function.prototype.apply`和`Function.prototype.call`有什麼差別?](#29-whats-the-difference-between-functionprototypeapply-and-functionprototypecall) - [30. `Function.prototype.bind`的用法是什麼?](#30-what-is-the-usage-of-functionprototypebind) - \[31.什麼是函數式程式設計以及 JavaScript 的哪些特性使其成為函數式語言的候選者?\](#31-什麼是函數式程式設計和 javascript 的特性是什麼-使其成為函數式語言的候選者 ) - [32.什麼是高階函數?](#32-what-are-higher-order-functions) - [33.為什麼函數被稱為First-class Objects?](#33-why-are-functions-called-firstclass-objects) - \[34.手動實作`Array.prototype.map`方法。\](#34-手動實作 arrayprototypemap-method ) - [35. 手動實作`Array.prototype.filter`方法。](#35-implement-the-arrayprototypefilter-method-by-hand) - [36. 手動實作`Array.prototype.reduce`方法。](#36-implement-the-arrayprototypereduce-method-by-hand) - [37.什麼是`arguments`物件?](#37-what-is-the-arguments-object) - [38. 如何創造沒有**原型的**物件?](#38-how-to-create-an-object-without-a-prototype) - [39. 為什麼當你呼叫這個函數時,這段程式碼中的`b`會變成全域變數?](#39-why-does-b-in-this-code-become-a-global-variable-when-you-call-this-function) - [40.什麼是**ECMAScript** ?](#40-what-is-ecmascript) - [41. **ES6**或**ECMAScript 2015**有哪些新功能?](#41-what-are-the-new-features-in-es6-or-ecmascript-2015) - [42. `var` 、 `let`和`const`關鍵字有什麼差別?](#42-whats-the-difference-between-var-let-and-const-keywords) - [43. 什麼是**箭頭函數**?](#43-what-are-arrow-functions) - [44.什麼是**類別**?](#44-what-are-classes) - [45.什麼是**模板文字**?](#45-what-are-template-literals) - [46.什麼是**物件解構**?](#46-what-is-object-destructuring) - [47.什麼是`ES6 Modules` ?](#47-what-are-es6-modules) - [48.什麼是`Set`物件以及它如何運作?](#48-what-is-the-set-object-and-how-does-it-work) - [49. 什麼是回呼函數?](#49-what-is-a-callback-function) - [50. 什麼是**Promise** ?](#50-what-are-promises) - [51. 什麼是*async/await*以及它是如何運作的?](#51-what-is-asyncawait-and-how-does-it-work) - [52. **Spread 運算子**和**Rest 運算**子有什麼差別?](#52-whats-the-difference-between-spread-operator-and-rest-operator) - [53. 什麼是**預設參數**?](#53-what-are-default-parameters) - [54.什麼是**包裝物件**?](#54-what-are-wrapper-objects) - [55.**隱性強制**和**顯性**強制有什麼差別?](#55-what-is-the-difference-between-implicit-and-explicit-coercion) - [56. 什麼是`NaN` ?以及如何檢查值是否為`NaN` ?](#56-what-is-nan-and-how-to-check-if-a-value-is-nan) - [57. 如何檢查一個值是否為一個**陣列**?](#57-how-to-check-if-a-value-is-an-array) - [58. 如何在不使用`%`或模運算子的情況下檢查數字是否為偶數?](#58-how-to-check-if-a-number-is-even-without-using-the-or-modulo-operator) - [59. 如何檢查物件中是否存在某個屬性?](#59-how-to-check-if-a-certain-property-exists-in-an-object) - [60.什麼是**AJAX** ?](#60-what-is-ajax) - [61. JavaScript 中建立物件的方式有哪些?](#61-what-are-the-ways-of-making-objects-in-javascript) - [62. `Object.seal`和`Object.freeze`方法有什麼不同?](#62-whats-the-difference-between-objectseal-and-objectfreeze-methods) - [63. `in`運算子和物件中的`hasOwnProperty`方法有什麼差別?](#63-whats-the-difference-between-the-in-operator-and-the-hasownproperty-method-in-objects) - [64. JavaScript中處理**非同步程式碼的**方法有哪些?](#64-what-are-the-ways-to-deal-with-asynchronous-code-in-javasscript) - [65.**函數表達式**和**函數宣告**有什麼不同?](#65-whats-the-difference-between-a-function-expression-and-function-declaration) - \[66.一個函數有多少種*呼叫*方式?\]( 66-函數可以有多少種方式被呼叫) ================= - [67. 什麼是*記憶*,它有什麼用?](#67-what-is-memoization-and-whats-the-use-it) - [68. 實現記憶輔助功能。](#68-implement-a-memoization-helper-function) - [69. 為什麼`typeof null`回傳`object` ?如何檢查一個值是否為`null` ?](#69-why-does-typeof-null-return-object-how-to-check-if-a-value-is-null) - [`new`關鍵字有什麼作用?](#70-what-does-the-new-keyword-do) ### 1. `undefined`和`null`有什麼差別? [^](#the-questions "返回問題")在了解`undefined`和`null`之間的差異之前,我們必須先了解它們之間的相似之處。 - 它們屬於**JavaScript 的**7 種基本型別。 ``` let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint']; ``` - 它們是**虛假的**價值觀。使用`Boolean(value)`或`!!value`將其轉換為布林值時計算結果為 false 的值。 ``` console.log(!!null); //logs false console.log(!!undefined); //logs false console.log(Boolean(null)); //logs false console.log(Boolean(undefined)); //logs false ``` 好吧,我們來談談差異。 - `undefined`是尚未指派特定值的變數的預設值。或一個沒有**明確**回傳值的函數。 `console.log(1)` 。或物件中不存在的屬性。 JavaScript 引擎為我們完成了**指派**`undefined`值的任務。 ``` let _thisIsUndefined; const doNothing = () => {}; const someObj = { a : "ay", b : "bee", c : "si" }; console.log(_thisIsUndefined); //logs undefined console.log(doNothing()); //logs undefined console.log(someObj["d"]); //logs undefined ``` - `null`是**「代表無值的值」** 。 `null`是已**明確**定義給變數的值。在此範例中,當`fs.readFile`方法未引發錯誤時,我們得到`null`值。 ``` fs.readFile('path/to/file', (e,data) => { console.log(e); //it logs null when no error occurred if(e){ console.log(e); } console.log(data); }); ``` 當比較`null`和`undefined`時,使用`==`時我們得到`true` ,使用`===`時得到`false` 。您可以[在此處](#14-whats-the-difference-between-and-)閱讀原因。 ``` console.log(null == undefined); // logs true console.log(null === undefined); // logs false ``` ### 2. `&&`運算子的作用是什麼? [^](#the-questions "返回問題") `&&`或**邏輯 AND**運算子在其運算元中尋找第一個*假*表達式並傳回它,如果沒有找到任何*假*表達式,則傳回最後一個表達式。它採用短路來防止不必要的工作。在我的一個專案中關閉資料庫連線時,我在`catch`區塊中使用了它。 ``` console.log(false && 1 && []); //logs false console.log(" " && true && 5); //logs 5 ``` 使用**if**語句。 ``` const router: Router = Router(); router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { if (conMobile) { conMobile.release(); } } }); ``` 使用**&amp;&amp;**運算子。 ``` const router: Router = Router(); router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { conMobile && conMobile.release() } }); ``` ### 3. `||`是什麼意思?運營商做什麼? [↑](#the-questions "返回問題") `||` or**邏輯 OR**運算子尋找其運算元中的第一個*真值*表達式並傳回它。這也採用短路來防止不必要的工作。在**ES6預設函數參數**被支援之前,它被用來初始化函數中的預設參數值。 ``` console.log(null || 1 || undefined); //logs 1 function logName(name) { var n = name || "Mark"; console.log(n); } logName(); //logs "Mark" ``` ### 4. 使用**+**或一元加運算子是將字串轉換為數字的最快方法嗎? [^](#the-questions "返回問題")根據[MDN 文件,](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus) `+`是將字串轉換為數字的最快方法,因為如果該值已經是數字,它不會對該值執行任何操作。 ### 5.什麼是**DOM** ? [^](#the-questions "返回問題") **DOM**代表**文件物件模型,**是 HTML 和 XML 文件的介面 ( **API** )。當瀏覽器第一次讀取(*解析*)我們的 HTML 文件時,它會建立一個大物件,一個基於 HTML 文件的非常大的物件,這就是**DOM** 。它是根據 HTML 文件建模的樹狀結構。 **DOM**用於互動和修改**DOM 結構**或特定元素或節點。 想像一下,如果我們有這樣的 HTML 結構。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document Object Model</title> </head> <body> <div> <p> <span></span> </p> <label></label> <input> </div> </body> </html> ``` 等效的**DOM**應該是這樣的。 ![DOM 等效項](https://thepracticaldev.s3.amazonaws.com/i/mbqphfbjfie45ynj0teo.png) **JavaScript**中的`document`物件代表**DOM** 。它為我們提供了許多方法,我們可以用來選擇元素來更新元素內容等等。 ### 6.什麼是**事件傳播**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**冒泡階段**,**事件**向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達`window` ,而在**捕獲階段**,事件從`window`開始向下到達觸發的元素事件或`<a href="#12-what-is-eventtarget-">event.target</a>` 。 **事件傳播**分為**三個**階段。 1. [捕獲階段](#8-whats-event-capturing)-事件從`window`開始,然後向下到達每個元素,直到到達目標元素。 2. [目標階段](#12-what-is-eventtarget-)– 事件已到達目標元素。 3. [冒泡階段](#7-whats-event-bubbling)-事件從目標元素冒起,然後向上移動到每個元素,直到到達`window` 。 ![事件傳播](https://thepracticaldev.s3.amazonaws.com/i/hjayqa99iejfhbsujlqd.png) ### 7.什麼是**事件冒泡**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**冒泡階段**,**事件**向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達`window` 。 如果我們有一個像這樣的範例標記。 ``` <div class="grandparent"> <div class="parent"> <div class="child">1</div> </div> </div> ``` 還有我們的js程式碼。 ``` function addEvent(el, event, callback, isCapture = false) { if (!el || !event || !callback || typeof callback !== 'function') return; if (typeof el === 'string') { el = document.querySelector(el); }; el.addEventListener(event, callback, isCapture); } addEvent(document, 'DOMContentLoaded', () => { const child = document.querySelector('.child'); const parent = document.querySelector('.parent'); const grandparent = document.querySelector('.grandparent'); addEvent(child, 'click', function (e) { console.log('child'); }); addEvent(parent, 'click', function (e) { console.log('parent'); }); addEvent(grandparent, 'click', function (e) { console.log('grandparent'); }); addEvent(document, 'click', function (e) { console.log('document'); }); addEvent('html', 'click', function (e) { console.log('html'); }) addEvent(window, 'click', function (e) { console.log('window'); }) }); ``` `addEventListener`方法有第三個可選參數**useCapture ,**預設值為`false`事件將在**冒泡階段**發生,如果為`true` ,事件將在**捕獲階段**發生。如果我們點擊`child`元素,它會分別在**控制台**上記錄`child` 、 `parent`元素、 `grandparent` 、 `html` 、 `document`和`window` 。這就是**事件冒泡**。 ### 8. 什麼是**事件擷取**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**捕獲階段**,事件從`window`開始一直到觸發事件的元素。 如果我們有一個像這樣的範例標記。 ``` <div class="grandparent"> <div class="parent"> <div class="child">1</div> </div> </div> ``` 還有我們的js程式碼。 ``` function addEvent(el, event, callback, isCapture = false) { if (!el || !event || !callback || typeof callback !== 'function') return; if (typeof el === 'string') { el = document.querySelector(el); }; el.addEventListener(event, callback, isCapture); } addEvent(document, 'DOMContentLoaded', () => { const child = document.querySelector('.child'); const parent = document.querySelector('.parent'); const grandparent = document.querySelector('.grandparent'); addEvent(child, 'click', function (e) { console.log('child'); }, true); addEvent(parent, 'click', function (e) { console.log('parent'); }, true); addEvent(grandparent, 'click', function (e) { console.log('grandparent'); }, true); addEvent(document, 'click', function (e) { console.log('document'); }, true); addEvent('html', 'click', function (e) { console.log('html'); }, true) addEvent(window, 'click', function (e) { console.log('window'); }, true) }); ``` `addEventListener`方法有第三個可選參數**useCapture ,**預設值為`false`事件將在**冒泡階段**發生,如果為`true` ,事件將在**捕獲階段**發生。如果我們點擊`child`元素,它會分別在**控制台**上記錄`window` 、 `document` 、 `html` 、 `grandparent` 、 `parent`和`child` 。這就是**事件捕獲**。 ### 9. `event.preventDefault()`和`event.stopPropagation()`方法有什麼差別? [↑](#the-questions "返回問題") `event.preventDefault()`方法**阻止**元素的預設行為。如果在`form`元素中使用,它**會阻止**其提交。如果在`anchor`元素中使用,它**會阻止**其導航。如果在`contextmenu`中使用,它**會阻止**其顯示或顯示。而`event.stopPropagation()`方法會停止事件的傳播或停止事件在[冒泡](#7-whats-event-bubbling)或[捕獲](#8-whats-event-capturing)階段發生。 ### 10. 如何知道元素中是否使用了`event.preventDefault()`方法? [↑](#the-questions "返回問題")我們可以使用事件物件中的`event.defaultPrevented`屬性。它傳回一個`boolean` ,指示是否在特定元素中呼叫了`event.preventDefault()` 。 ### 11. 為什麼這段程式碼`obj.someprop.x`會拋出錯誤? ``` const obj = {}; console.log(obj.someprop.x); ``` [^](#the-questions "返回問題")顯然,由於我們嘗試存取 a 的原因,這會引發錯誤 `someprop`屬性中的`x`屬性具有`undefined`值。請記住,物件中的**屬性**本身並不存在,且其**原型**具有預設值`undefined`且`undefined`沒有屬性`x` 。 ### 12.什麼是**event.target** ? [↑](#the-questions "返回問題")最簡單來說, **event.target**是**發生**事件的元素或**觸發**事件的元素。 HTML 標記範例。 ``` <div onclick="clickFunc(event)" style="text-align: center;margin:15px; border:1px solid red;border-radius:3px;"> <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;"> <div style="margin:25px;border:1px solid skyblue;border-radius:3px;"> <button style="margin:10px"> Button </button> </div> </div> </div> ``` JavaScript 範例。 ``` function clickFunc(event) { console.log(event.target); } ``` 如果您單擊按鈕,它會記錄**按鈕**標記,即使我們將事件附加在最外部的`div`上,它也會始終記錄**按鈕**,因此我們可以得出結論, **event.target**是觸發事件的元素。 ### 13.什麼是**event.currentTarget** ? [↑](#the-questions "返回問題") **event.currentTarget**是我們**明確**附加事件處理程序的元素。 複製**問題 12**中的標記。 HTML 標記範例。 ``` <div onclick="clickFunc(event)" style="text-align: center;margin:15px; border:1px solid red;border-radius:3px;"> <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;"> <div style="margin:25px;border:1px solid skyblue;border-radius:3px;"> <button style="margin:10px"> Button </button> </div> </div> </div> ``` 並且稍微改變我們的**JS** 。 ``` function clickFunc(event) { console.log(event.currentTarget); } ``` 如果您按一下該按鈕,即使我們按一下該按鈕,它也會記錄最外層的**div**標記。在此範例中,我們可以得出結論, **event.currentTarget**是我們附加事件處理程序的元素。 ### 14. `==`和`===`有什麼差別? [^](#the-questions "返回問題") `==` \_\_(抽象相等)\_\_ 和`===` \_\_(嚴格相等)\_\_ 之間的區別在於`==`在*強制轉換*後按**值**進行比較,而`===`在不進行*強制轉換的*情況下按**值**和**類型**進行比較。 讓我們更深入地研究`==` 。那麼首先我們來談談*強制*。 *強制轉換*是將一個值轉換為另一種類型的過程。在本例中, `==`進行*隱式強制轉換*。在比較兩個值之前, `==`需要執行一些條件。 假設我們必須比較`x == y`值。 1. 如果`x`和`y`具有相同的類型。 然後將它們與`===`運算子進行比較。 2. 如果`x`為`null`且`y` `undefined` ,則傳回`true` 。 3. 如果`x` `undefined`且`y`為`null`則傳回`true` 。 4. 如果`x`是`number`類型, `y`是`string`類型 然後回傳`x == toNumber(y)` 。 5. 如果`x`是`string`類型, `y`是`number`類型 然後返回`toNumber(x) == y` 。 6. 如果`x`是`boolean`類型 然後返回`toNumber(x) == y` 。 7. 如果`y`是`boolean`類型 然後回傳`x == toNumber(y)` 。 8. 如果`x`是`string` 、 `symbol`或`number`且`y`是 type `object` 然後回傳`x == toPrimitive(y)` 。 9. 如果`x`是`object`且`x`是`string` 、 `symbol` 然後返回`toPrimitive(x) == y` 。 10. 返回`false` 。 **注意:** `toPrimitive`首先使用物件中的`valueOf`方法,然後使用`toString`方法來取得該物件的原始值。 讓我們舉個例子。 | `x` | `y` | `x == y` | | ------------- |:-------------:| ----------------: | | `5` | `5` | `true` | | `1` | `'1'` | `true` | | `null` | `undefined` | `true` | | `0` | `false` | `true` | | `'1,2'` | `[1,2]` | `true` | | `'[object Object]'` | `{}` | `true` | 這些範例都傳回`true` 。 **第一個範例**屬於**條件一**,因為`x`和`y`具有相同的類型和值。 **第二個範例**轉到**條件四,**在比較之前將`y`轉換為`number` 。 **第三個例子**涉及**條件二**。 **第四個範例**轉到**條件七,**因為`y`是`boolean` 。 **第五個範例**適用於**條件八**。使用`toString()`方法將陣列轉換為`string` ,該方法傳回`1,2` 。 **最後一個例子**適用於**條件十**。使用傳回`[object Object]`的`toString()`方法將該物件轉換為`string` 。 | `x` | `y` | `x === y` | | ------------- |:-------------:| ----------------: | | `5` | `5` | `true` | | `1` | `'1'` | `false` | | `null` | `undefined` | `false` | | `0` | `false` | `false` | | `'1,2'` | `[1,2]` | `false` | | `'[object Object]'` | `{}` | `false` | 如果我們使用`===`運算符,則除第一個範例之外的所有比較都將傳回`false` ,因為它們不具有相同的類型,而第一個範例將傳回`true` ,因為兩者俱有相同的類型和值。 ### 15. 為什麼在 JavaScript 中比較兩個相似的物件時回傳**false** ? [^](#the-questions "返回問題")假設我們有下面的例子。 ``` let a = { a: 1 }; let b = { a: 1 }; let c = a; console.log(a === b); // logs false even though they have the same property console.log(a === c); // logs true hmm ``` **JavaScript**以不同的方式比較*物件*和*基元*。在*基元*中,它透過**值**來比較它們,而在*物件*中,它透過**引用**或**儲存變數的記憶體位址**來比較它們。這就是為什麼第一個`console.log`語句回傳`false`而第二個`console.log`語句回傳`true`的原因。 `a`和`c`有相同的引用,而`a`和`b`則不同。 ### 16. **!!**是什麼意思?運營商做什麼? [↑](#the-questions "返回問題")**雙非**運算子或**!!**將右側的值強制轉換為布林值。基本上,這是一種將值轉換為布林值的奇特方法。 ``` console.log(!!null); //logs false console.log(!!undefined); //logs false console.log(!!''); //logs false console.log(!!0); //logs false console.log(!!NaN); //logs false console.log(!!' '); //logs true console.log(!!{}); //logs true console.log(!![]); //logs true console.log(!!1); //logs true console.log(!![].length); //logs false ``` ### 17. 如何計算一行中的多個表達式? [↑](#the-questions "返回問題")我們可以使用`,`或逗號運算子來計算一行中的多個表達式。它從左到右計算並傳回右側最後一項或最後一個操作數的值。 ``` let x = 5; x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10); function addFive(num) { return num + 5; } ``` 如果記錄`x`的值,它將是**27** 。首先,我們**增加**x 的值,它將是**6** ,然後我們呼叫函數`addFive(6)`並將 6 作為參數傳遞,並將結果分配給`x` , `x`的新值將是**11** 。之後,我們將`x`的當前值乘以**2**並將其分配給`x` , `x`的更新值將是**22** 。然後,我們將`x`的當前值減去 5 並將結果指派給`x` ,更新後的值將是**17** 。最後,我們將`x`的值增加 10 並將更新後的值指派給`x` ,現在`x`的值將是**27** 。 ### 18.什麼是**吊裝**? [^](#the-questions "返回問題")**提升**是一個術語,用於描述將*變數*和*函數*移動到其*(全域或函數)*作用域的頂部(即我們定義該變數或函數的位置)。 要理解**提升**,我必須解釋*執行上下文*。 **執行上下文**是目前正在執行的「程式碼環境」。**執行上下文**有兩個階段*:編譯*和*執行*。 **編譯**- 在此階段,它獲取所有*函數聲明*並將它們*提升*到作用域的頂部,以便我們稍後可以引用它們並獲取所有*變數聲明***(使用 var 關鍵字聲明)** ,並將它們*提升*並給它們一個默認值*未定義*的 . **執行**- 在此階段,它將值指派給先前*提升的*變數,並*執行*或*呼叫*函數**(物件中的方法)** 。 **注意:**只有使用*var*關鍵字宣告的**函數宣告**和變數才會*被提升*,而不是**函數表達式**或**箭頭函數**、 `let`和`const`關鍵字。 好吧,假設我們在下面的*全域範圍*內有一個範例程式碼。 ``` console.log(y); y = 1; console.log(y); console.log(greet("Mark")); function greet(name){ return 'Hello ' + name + '!'; } var y; ``` 此程式碼記錄`undefined` , `1` , `Hello Mark!`分別。 所以*編譯*階段看起來像這樣。 ``` function greet(name) { return 'Hello ' + name + '!'; } var y; //implicit "undefined" assignment //waiting for "compilation" phase to finish //then start "execution" phase /* console.log(y); y = 1; console.log(y); console.log(greet("Mark")); */ ``` 出於範例目的,我對變數和*函數呼叫*的*賦值*進行了評論。 *編譯*階段完成後,它開始*執行*階段,呼叫方法並向變數賦值。 ``` function greet(name) { return 'Hello ' + name + '!'; } var y; //start "execution" phase console.log(y); y = 1; console.log(y); console.log(greet("Mark")); ``` ### 19.什麼是**範圍**? [↑](#the-questions "返回問題") JavaScript 中的**作用域**是我們可以有效存取變數或函數的**區域**。 JavaScript 有三種類型的作用域。**全域作用域**、**函數作用域**和**區塊作用域(ES6)** 。 - **全域作用域**- 在全域命名空間中宣告的變數或函數位於全域作用域中,因此可以在程式碼中的任何位置存取。 ``` //global namespace var g = "global"; function globalFunc(){ function innerFunc(){ console.log(g); // can access "g" because "g" is a global variable } innerFunc(); } ``` - **函數作用域**- 函數內聲明的變數、函數和參數可以在該函數內部存取,但不能在函數外部存取。 ``` function myFavoriteFunc(a) { if (true) { var b = "Hello " + a; } return b; } myFavoriteFunc("World"); console.log(a); // Throws a ReferenceError "a" is not defined console.log(b); // does not continue here ``` - **區塊作用域**- 在區塊`{}`內宣告的變數**( `let` 、 `const` )**只能在區塊內存取。 ``` function testBlock(){ if(true){ let z = 5; } return z; } testBlock(); // Throws a ReferenceError "z" is not defined ``` **範圍**也是一組查找變數的規則。如果一個變數在**當前作用域中**不存在,它會在**外部作用域中查找**並蒐索該變數,如果不存在,它會再次**查找,**直到到達**全域作用域。**如果該變數存在,那麼我們可以使用它,如果不存在,我們可以使用它來拋出錯誤。它搜尋**最近的**變數,一旦找到它就停止**搜尋**或**尋找**。這稱為**作用域鏈**。 ``` /* Scope Chain Inside inner function perspective inner's scope -> outer's scope -> global's scope */ //Global Scope var variable1 = "Comrades"; var variable2 = "Sayonara"; function outer(){ //outer's scope var variable1 = "World"; function inner(){ //inner's scope var variable2 = "Hello"; console.log(variable2 + " " + variable1); } inner(); } outer(); // logs Hello World // because (variable2 = "Hello") and (variable1 = "World") are the nearest // variables inside inner's scope. ``` ![範圍](https://thepracticaldev.s3.amazonaws.com/i/l81b3nmdonimex0qsgyr.png) ### 20.什麼是**閉包**? [^](#the-questions "返回問題")這可能是所有這些問題中最難的問題,因為**閉包**是一個有爭議的話題。那我就從我的理解來解釋。 **閉包**只是函數在宣告時記住其當前作用域、其父函數作用域、其父函數的父函數作用域上的變數和參數的引用的能力,直到在**作用域鏈**的幫助下到達全域作用域。基本上它是聲明函數時建立的**作用域**。 例子是解釋閉包的好方法。 ``` //Global's Scope var globalVar = "abc"; function a(){ //testClosures's Scope console.log(globalVar); } a(); //logs "abc" /* Scope Chain Inside a function perspective a's scope -> global's scope */ ``` 在此範例中,當我們宣告`a`函數時**,全域**作用域是`a's`*閉包*的一部分。 ![a的閉包](https://thepracticaldev.s3.amazonaws.com/i/teatokuw4xvgtlzbzhn8.png) 變數`globalVar`在影像中沒有值的原因是該變數的值可以根據我們呼叫`a`**位置**和**時間**而改變。 但在上面的範例中, `globalVar`變數的值為**abc** 。 好吧,讓我們來看一個複雜的例子。 ``` var globalVar = "global"; var outerVar = "outer" function outerFunc(outerParam) { function innerFunc(innerParam) { console.log(globalVar, outerParam, innerParam); } return innerFunc; } const x = outerFunc(outerVar); outerVar = "outer-2"; globalVar = "guess" x("inner"); ``` ![複雜的](https://thepracticaldev.s3.amazonaws.com/i/e4hxm7zvz8eun2ppenwp.png) 這將列印“猜測外部內部”。對此的解釋是,當我們呼叫`outerFunc`函數並將`innerFunc`函數的回傳值指派給變數`x`時,即使我們將新值**outer-2**指派給`outerVar`變數, `outerParam`也會具有**outer**值,因為 重新分配發生在呼叫`outer`函數之後,當我們呼叫`outerFunc`函數時,它會在**作用域鏈**中尋找`outerVar`的值,而`outerVar`的值為**「outer」** 。現在,當我們呼叫引用了`innerFunc`的`x`變數時, `innerParam`的值為**inner,**因為這是我們在呼叫中傳遞的值,而`globalVar`變數的值為**猜測**,因為在呼叫`x`變數之前,我們為`globalVar`分配了一個新值,並且在呼叫`x`時**作用域鏈**中`globalVar`的值是**猜測**。 我們有一個例子來示範沒有正確理解閉包的問題。 ``` const arrFuncs = []; for(var i = 0; i < 5; i++){ arrFuncs.push(function (){ return i; }); } console.log(i); // i is 5 for (let i = 0; i < arrFuncs.length; i++) { console.log(arrFuncs[i]()); // all logs "5" } ``` 由於**Closures**的原因,此程式碼無法按我們的預期工作。 `var`關鍵字建立一個全域變數,當我們推送一個函數時 我們返回全域變數`i` 。因此,當我們在循環之後呼叫該陣列中的其中一個函數時,它會記錄`5` ,因為我們得到 `i`的目前值為`5` ,我們可以存取它,因為它是全域變數。因為**閉包**保留該變數的**引用,**而不是其建立時的**值**。我們可以使用**IIFES**或將`var`關鍵字變更為`let`來解決此問題,以實現區塊作用域。 ### 21. **JavaScript**中的**假**值是什麼? [↑](#the-questions "返回問題") ``` const falsyValues = ['', 0, null, undefined, NaN, false]; ``` **假**值是轉換為布林值時變成**false 的**值。 ### 22. 如何檢查一個值是否為**假值**? [↑](#the-questions "返回問題")使用**布林**函數或雙非運算符**[!!](#16-what-does-the-operator-do)** ### 23. `"use strict"`有什麼作用? [^](#the-questions "返回問題") `"use strict"`是**JavaScript**中的 ES5 功能,它使我們的程式碼在*函數*或*整個腳本*中處於**嚴格模式**。**嚴格模式**幫助我們避免程式碼早期出現**錯誤**並為其加入限制。 **嚴格模式**給我們的限制。 - 分配或存取未宣告的變數。 ``` function returnY(){ "use strict"; y = 123; return y; } ``` - 為唯讀或不可寫的全域變數賦值; ``` "use strict"; var NaN = NaN; var undefined = undefined; var Infinity = "and beyond"; ``` - 刪除不可刪除的屬性。 ``` "use strict"; const obj = {}; Object.defineProperty(obj, 'x', { value : '1' }); delete obj.x; ``` - 參數名稱重複。 ``` "use strict"; function someFunc(a, b, b, c){ } ``` - 使用**eval**函數建立變數。 ``` "use strict"; eval("var x = 1;"); console.log(x); //Throws a Reference Error x is not defined ``` - **該**值的預設值是`undefined` 。 ``` "use strict"; function showMeThis(){ return this; } showMeThis(); //returns undefined ``` **嚴格模式**的限制遠不止這些。 ### 24. JavaScript 中`this`的值是什麼? [↑](#the-questions "返回問題")基本上, `this`是指目前正在執行或呼叫函數的物件的值。我說**目前**是因為**它**的值會根據我們使用它的上下文和使用它的位置而改變。 ``` const carDetails = { name: "Ford Mustang", yearBought: 2005, getName(){ return this.name; }, isRegistered: true }; console.log(carDetails.getName()); // logs Ford Mustang ``` 這是我們通常所期望的,因為在**getName**方法中我們傳回`this.name` ,在此上下文中`this`指的是`carDetails`物件,該物件目前是正在執行的函數的「擁有者」物件。 好吧,讓我們加入一些程式碼讓它變得奇怪。在`console.log`語句下面加入這三行程式碼 ``` var name = "Ford Ranger"; var getCarName = carDetails.getName; console.log(getCarName()); // logs Ford Ranger ``` 第二個`console.log`語句印製了**「Ford Ranger」**一詞,這很奇怪,因為在我們的第一個`console.log`語句中它印了**「Ford Mustang」** 。原因是`getCarName`方法有一個不同的「擁有者」物件,即`window`物件。在全域作用域中使用`var`關鍵字聲明變數會在`window`物件中附加與變數同名的屬性。請記住,當未使用`"use strict"`時,全域範圍內的`this`指的是`window`物件。 ``` console.log(getCarName === window.getCarName); //logs true console.log(getCarName === this.getCarName); // logs true ``` 本例中的`this`和`window`指的是同一個物件。 解決此問題的一種方法是使用函數中的`<a href="#27-what-is-the-use-functionprototypeapply-method">apply</a>`和`<a href="#28-what-is-the-use-functionprototypecall-method">call</a>`方法。 ``` console.log(getCarName.apply(carDetails)); //logs Ford Mustang console.log(getCarName.call(carDetails)); //logs Ford Mustang ``` `apply`和`call`方法期望第一個參數是一個物件,該物件將是該函數內`this`的值。 **IIFE** (即**立即呼叫函數表達式)** 、在全域作用域中宣告的函數、物件內部方法中的**匿名函數**和內部函數都有一個指向**window**物件的預設**值**。 ``` (function (){ console.log(this); })(); //logs the "window" object function iHateThis(){ console.log(this); } iHateThis(); //logs the "window" object const myFavoriteObj = { guessThis(){ function getThis(){ console.log(this); } getThis(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; myFavoriteObj.guessThis(); //logs the "window" object myFavoriteObj.thisIsAnnoying(function (){ console.log(this); //logs the "window" object }); ``` 如果我們想要取得`myFavoriteObj`物件中的`name`屬性**(Marko Polo)**的值,有兩種方法可以解決這個問題。 首先,我們將`this`的值保存在變數中。 ``` const myFavoriteObj = { guessThis(){ const self = this; //saves the this value to the "self" variable function getName(){ console.log(self.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; ``` 在此圖像中,我們保存`this`的值,該值將是`myFavoriteObj`物件。所以我們可以在`getName`內部函數中存取它。 其次,我們使用**ES6[箭頭函數](#43-what-are-arrow-functions)**。 ``` const myFavoriteObj = { guessThis(){ const getName = () => { //copies the value of "this" outside of this arrow function console.log(this.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; ``` [箭頭函數](#43-what-are-arrow-functions)沒有自己的`this` 。它複製封閉詞法範圍的`this`值,或複製`getName`內部函數外部的`this`值(即`myFavoriteObj`物件)。我們也可以根據[函數的呼叫方式](#66-how-many-ways-can-a-function-be-invoked)來決定`this`的值。 ### 25. 物件的`prototype`是什麼? [↑](#the-questions "返回問題")最簡單的`prototype`是一個物件的*藍圖*。如果目前物件中確實存在它,則將其用作**屬性**和**方法**的後備。這是在物件之間共享屬性和功能的方式。這是 JavaScript**原型繼承**的核心概念。 ``` const o = {}; console.log(o.toString()); // logs [object Object] ``` 即使`o.toString`方法不存在於`o`物件中,它也不會拋出錯誤,而是傳回字串`[object Object]` 。當物件中不存在屬性時,它會尋找其**原型**,如果仍然不存在,則會尋找**原型的原型**,依此類推,直到在**原型鏈**中找到具有相同屬性的屬性。**原型鏈**的末尾在**Object.prototype**之後為`null` 。 ``` console.log(o.toString === Object.prototype.toString); // logs true // which means we we're looking up the Prototype Chain and it reached // the Object.prototype and used the "toString" method. ``` ### 26. 什麼是**IIFE** ,它有什麼用? [^](#the-questions "返回問題") **IIFE**或**立即呼叫函數表達式**是在建立或宣告後將被呼叫或執行的函數。建立**IIFE**的語法是,我們將`function (){}`包裝在括號`()`或**分組運算**子內,以將函數視為表達式,然後用另一個括號`()`呼叫它。所以**IIFE**看起來像這樣`(function(){})()` 。 ``` (function () { }()); (function () { })(); (function named(params) { })(); (() => { })(); (function (global) { })(window); const utility = (function () { return { //utilities }; })(); ``` 這些範例都是有效的**IIFE** 。倒數第二個範例顯示我們可以將參數傳遞給**IIFE**函數。最後一個範例表明我們可以將**IIFE**的結果保存到變數中,以便稍後引用它。 **IIFE**的最佳用途是進行初始化設定功能,並避免與全域範圍內的其他變數**發生命名衝突**或污染全域名稱空間。讓我們舉個例子。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> ``` 假設我們有一個指向庫`somelibrary.js`的連結,該庫公開了我們可以在程式碼中使用的一些全域函數,但該庫有兩個我們不使用`createGraph`和`drawGraph`方法,因為這些方法中有錯誤。我們想要實作我們自己的`createGraph`和`drawGraph`方法。 - 解決這個問題的一種方法是改變腳本的結構。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here } </script> ``` 當我們使用這個解決方案時,我們將覆蓋庫為我們提供的這兩種方法。 - 解決這個問題的另一種方法是更改我們自己的輔助函數的名稱。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> function myCreateGraph() { // createGraph logic here } function myDrawGraph() { // drawGraph logic here } </script> ``` 當我們使用此解決方案時,我們還將這些函數呼叫更改為新函數名稱。 - 另一種方法是使用**IIFE** 。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> const graphUtility = (function () { function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here } return { createGraph, drawGraph } })(); </script> ``` 在此解決方案中,我們建立一個實用程式變數,它是**IIFE**的結果,它傳回一個包含`createGraph`和`drawGraph`兩個方法的物件。 **IIFE**解決的另一個問題就是這個例子。 ``` var li = document.querySelectorAll('.list-group > li'); for (var i = 0, len = li.length; i < len; i++) { li[i].addEventListener('click', function (e) { console.log(i); }) } ``` 假設我們有一個`ul`元素,其類別為**list-group** ,並且它有 5 個`li`子元素。當我們**點擊**單一`li`元素時,我們希望`console.log` `i`的值。 但我們想要的程式碼中的行為不起作用。相反,它會在對`li`元素的任何**點擊**中記錄`5` 。我們遇到的問題是由於**閉包的**工作方式造成的。**閉包**只是函數記住其當前作用域、其父函數作用域和全域作用域中的變數引用的能力。當我們在全域範圍內使用`var`關鍵字聲明變數時,顯然我們正在建立一個全域變數`i` 。因此,當我們單擊`li`元素時,它會記錄**5** ,因為這是我們稍後在回調函數中引用它時的`i`值。 - 解決這個問題的一種方法是**IIFE** 。 ``` var li = document.querySelectorAll('.list-group > li'); for (var i = 0, len = li.length; i < len; i++) { (function (currentIndex) { li[currentIndex].addEventListener('click', function (e) { console.log(currentIndex); }) })(i); } ``` 這個解決方案之所以有效,是因為**IIFE**為每次迭代建立一個新範圍,並且我們捕獲`i`的值並將其傳遞到`currentIndex`參數中,因此當我們呼叫**IIFE**時,每次迭代的`currentIndex`值都是不同的。 ### 27. `Function.prototype.apply`方法有什麼用? [^](#the-questions "返回問題") `apply`呼叫一個函數,在呼叫時指定`this`或該函數的「所有者」物件。 ``` const details = { message: 'Hello World!' }; function getMessage(){ return this.message; } getMessage.apply(details); // returns 'Hello World!' ``` 這個方法的工作方式類似於`<a href="#28-what-is-the-use-functionprototypecall-method">Function.prototype.call</a>`唯一的差異是我們傳遞參數的方式。在`apply`中,我們將參數作為陣列傳遞。 ``` const person = { name: "Marko Polo" }; function greeting(greetingMessage) { return `${greetingMessage} ${this.name}`; } greeting.apply(person, ['Hello']); // returns "Hello Marko Polo!" ``` ### 28. `Function.prototype.call`方法有什麼用? [^](#the-questions "返回問題")此`call`呼叫一個函數,指定呼叫時該函數的`this`或「擁有者」物件。 ``` const details = { message: 'Hello World!' }; function getMessage(){ return this.message; } getMessage.call(details); // returns 'Hello World!' ``` 這個方法的工作方式類似於`<a href="#27-what-is-the-use-functionprototypeapply-method">Function.prototype.apply</a>`唯一的差異是我們傳遞參數的方式。在`call`中,我們直接傳遞參數,對於每個參數`,`用逗號分隔它們。 ``` const person = { name: "Marko Polo" }; function greeting(greetingMessage) { return `${greetingMessage} ${this.name}`; } greeting.call(person, 'Hello'); // returns "Hello Marko Polo!" ``` ### 29. `Function.prototype.apply`和`Function.prototype.call`有什麼差別? [↑](#the-questions "返回問題") `apply`和`call`之間的唯一區別是我們如何在被呼叫的函數中傳遞**參數**。在`apply`中,我們將參數作為**陣列**傳遞,而在`call`中,我們直接在參數列表中傳遞參數。 ``` const obj1 = { result:0 }; const obj2 = { result:0 }; function reduceAdd(){ let result = 0; for(let i = 0, len = arguments.length; i < len; i++){ result += arguments[i]; } this.result = result; } reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // returns 15 reduceAdd.call(obj2, 1, 2, 3, 4, 5); // returns 15 ``` ### 30. `Function.prototype.bind`的用法是什麼? [↑](#the-questions "返回問題") `bind`方法傳回一個新*綁定的*函數 到特定的`this`值或“所有者”物件,因此我們可以稍後在程式碼中使用它。 `call` 、 `apply`方法立即呼叫函數,而不是像`bind`方法那樣傳回一個新函數。 ``` import React from 'react'; class MyComponent extends React.Component { constructor(props){ super(props); this.state = { value : "" } this.handleChange = this.handleChange.bind(this); // Binds the "handleChange" method to the "MyComponent" component } handleChange(e){ //do something amazing here } render(){ return ( <> <input type={this.props.type} value={this.state.value} onChange={this.handleChange} /> </> ) } } ``` ### 31.什麼是**函數式程式設計**? **JavaScript**的哪些特性使其成為**函數式語言**的候選者? [^](#the-questions "返回問題")**函數式程式設計**是一種**聲明式**程式設計範式或模式,它介紹如何**使用表達式來**計算值而不改變傳遞給它的參數的函數來建立應用程式。 JavaScript**陣列**具有**map** 、 **filter** 、 **reduce**方法,這些方法是函數式程式設計世界中最著名的函數,因為它們非常有用,而且它們不會改變或改變陣列,這使得這些函數變得**純粹**,並且JavaScript 支援**閉包**和**高階函數**,它們是**函數式程式語言**的一個特徵。 - **map**方法建立一個新陣列,其中包含對陣列中每個元素呼叫提供的回調函數的結果。 ``` const words = ["Functional", "Procedural", "Object-Oriented"]; const wordsLength = words.map(word => word.length); ``` - **filter**方法會建立一個新陣列,其中包含透過回調函數中測試的所有元素。 ``` const data = [ { name: 'Mark', isRegistered: true }, { name: 'Mary', isRegistered: false }, { name: 'Mae', isRegistered: true } ]; const registeredUsers = data.filter(user => user.isRegistered); ``` - **reduce**方法對累加器和陣列中的每個元素(從左到右)套用函數,將其減少為單一值。 ``` const strs = ["I", " ", "am", " ", "Iron", " ", "Man"]; const result = strs.reduce((acc, currentStr) => acc + currentStr, ""); ``` ### 32.什麼是**高階函數**? [^](#the-questions "返回問題")**高階函數**是可以傳回函數或接收具有函數值的一個或多個參數的函數。 ``` function higherOrderFunction(param,callback){ return callback(param); } ``` ### 33.為什麼函數被稱為**First-class Objects** ? [^](#the-questions "返回問題") JavaScript 中的 \_\_Functions\_\_ 是**一流物件**,因為它們被視為該語言中的任何其他值。它們可以分配給**變數**,可以是稱為**方法的物件的屬性**,可以是**陣列中的專案**,可以**作為參數傳遞給函數**,也可以**作為函數的值返回**。函數與**JavaScript**中任何其他值之間的唯一區別是**函數**可以被呼叫。 ### 34. 手動實作`Array.prototype.map`方法。 [↑](#the-questions "返回問題") ``` function map(arr, mapCallback) { // First, we check if the parameters passed are right. if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { return []; } else { let result = []; // We're making a results array every time we call this function // because we don't want to mutate the original array. for (let i = 0, len = arr.length; i < len; i++) { result.push(mapCallback(arr[i], i, arr)); // push the result of the mapCallback in the 'result' array } return result; // return the result array } } ``` 正如`Array.prototype.map`方法的MDN描述。 **map() 方法建立一個新陣列,其中包含對呼叫陣列中的每個元素呼叫所提供函數的結果。** ### 35. 手動實作`Array.prototype.filter`方法。 [↑](#the-questions "返回問題") ``` function filter(arr, filterCallback) { // First, we check if the parameters passed are right. if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') { return []; } else { let result = []; // We're making a results array every time we call this function // because we don't want to mutate the original array. for (let i = 0, len = arr.length; i < len; i++) { // check if the return value of the filterCallback is true or "truthy" if (filterCallback(arr[i], i, arr)) { // push the current item in the 'result' array if the condition is true result.push(arr[i]); } } return result; // return the result array } } ``` 正如`Array.prototype.filter`方法的 MDN 描述。 **filter() 方法建立一個新陣列,其中包含透過所提供函數實現的測試的所有元素。** ### 36. 手動實作`Array.prototype.reduce`方法。 [↑](#the-questions "返回問題") ``js 函數reduce(arr,reduceCallback,initialValue){ // 首先,我們檢查傳遞的參數是否正確。 if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function') { ``` return []; ``` } 別的 { ``` // If no initialValue has been passed to the function we're gonna use the ``` ``` let hasInitialValue = initialValue !== undefined; ``` ``` let value = hasInitialValue ? initialValue : arr[0]; ``` ``` // first array item as the initialValue ``` ``` // Then we're gonna start looping at index 1 if there is no ``` ``` // initialValue has been passed to the function else we start at 0 if ``` ``` // there is an initialValue. ``` ``` for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) { ``` ``` // Then for every iteration we assign the result of the ``` ``` // reduceCallback to the variable value. ``` ``` value = reduceCallback(value, arr[i], i, arr); ``` ``` } ``` ``` return value; ``` } } ``` As the MDN description of the <code>Array.prototype.reduce</code> method. __The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.__ ###37. What is the __arguments__ object? [&uarr;](#the-questions "Back To Questions") The __arguments__ object is a collection of parameter values pass in a function. It's an __Array-like__ object because it has a __length__ property and we can access individual values using array indexing notation <code>arguments[1]</code> but it does not have the built-in methods in an array <code>forEach</code>,<code>reduce</code>,<code>filter</code> and <code>map</code>. It helps us know the number of arguments pass in a function. We can convert the <code>arguments</code> object into an array using the <code>Array.prototype.slice</code>. ``` 函數一(){ 返回 Array.prototype.slice.call(參數); } ``` Note: __the <code>arguments</code> object does not work on ES6 arrow functions.__ ``` 函數一(){ 返回參數; } 常數二 = 函數 () { 返回參數; } 常量三 = 函數三() { 返回參數; } const 四 = () =&gt; 參數; 四(); // 拋出錯誤 - 參數未定義 ``` When we invoke the function <code>four</code> it throws a <code>ReferenceError: arguments is not defined</code> error. We can solve this problem if your enviroment supports the __rest syntax__. ``` const 四 = (...args) =&gt; args; ``` This puts all parameter values in an array automatically. ###38. How to create an object without a __prototype__? [&uarr;](#the-questions "Back To Questions") We can create an object without a _prototype_ using the <code>Object.create</code> method. ``` 常數 o1 = {}; console.log(o1.toString()); // Logs \[object Object\] 取得此方法到Object.prototype const o2 = Object.create(null); // 第一個參數是物件「o2」的原型,在此 // case 將為 null 指定我們不需要任何原型 console.log(o2.toString()); // 拋出錯誤 o2.toString 不是函數 ``` ###39. Why does <code>b</code> in this code become a global variable when you call this function? [&uarr;](#the-questions "Back To Questions") ``` 函數 myFunc() { 令a = b = 0; } myFunc(); ``` The reason for this is that __assignment operator__ or __=__ has right-to-left __associativity__ or __evaluation__. What this means is that when multiple assignment operators appear in a single expression they evaluated from right to left. So our code becomes likes this. ``` 函數 myFunc() { 令 a = (b = 0); } myFunc(); ``` First, the expression <code>b = 0</code> evaluated and in this example <code>b</code> is not declared. So, The JS Engine makes a global variable <code>b</code> outside this function after that the return value of the expression <code>b = 0</code> would be 0 and it's assigned to the new local variable <code>a</code> with a <code>let</code> keyword. We can solve this problem by declaring the variables first before assigning them with value. ``` 函數 myFunc() { 令 a,b; a = b = 0; } myFunc(); ``` ###40. <div id="ecmascript">What is __ECMAScript__</div>? [&uarr;](#the-questions "Back To Questions") __ECMAScript__ is a standard for making scripting languages which means that __JavaScript__ follows the specification changes in __ECMAScript__ standard because it is the __blueprint__ of __JavaScript__. ###41. What are the new features in __ES6__ or __ECMAScript 2015__? [&uarr;](#the-questions "Back To Questions") * [Arrow Functions](#43-what-are-arrow-functions) * [Classes](#44-what-are-classes) * [Template Strings](#45-what-are-template-literals) * __Enhanced Object literals__ * [Object Destructuring](#46-what-is-object-destructuring) * [Promises](#50-what-are-promises) * __Generators__ * [Modules](#47-what-are-es6-modules) * Symbol * __Proxies__ * [Sets](#48-what-is-the-set-object-and-how-does-it-work) * [Default Function parameters](#53-what-are-default-parameters) * [Rest and Spread](#52-whats-the-difference-between-spread-operator-and-rest-operator) * [Block Scoping with <code>let</code> and <code>const</code>](#42-whats-the-difference-between-var-let-and-const-keywords) ###42. What's the difference between <code>var</code>, <code>let</code> and <code>const</code> keywords? [&uarr;](#the-questions "Back To Questions") Variables declared with <code>var</code> keyword are _function scoped_. What this means that variables can be accessed across that function even if we declare that variable inside a block. ``` 函數給MeX(showX) { 如果(顯示X){ ``` var x = 5; ``` } 返回x; } console.log(giveMeX(false)); console.log(giveMeX(true)); ``` The first <code>console.log</code> statement logs <code>undefined</code> and the second <code>5</code>. We can access the <code>x</code> variable due to the reason that it gets _hoisted_ at the top of the function scope. So our function code is intepreted like this. ``` 函數給MeX(showX) { 變數 x; // 有一個預設值未定義 如果(顯示X){ ``` x = 5; ``` } 返回x; } ``` If you are wondering why it logs <code>undefined</code> in the first <code>console.log</code> statement remember variables declared without an initial value has a default value of <code>undefined</code>. Variables declared with <code>let</code> and <code>const</code> keyword are _block scoped_. What this means that variable can only be accessed on that block <code>{}</code> on where we declare it. ``` 函數給MeX(showX) { 如果(顯示X){ ``` let x = 5; ``` } 返回x; } 函數給MeY(顯示Y){ 如果(顯示Y){ ``` let y = 5; ``` } 返回y; } ``` If we call this functions with an argument of <code>false</code> it throws a <code>Reference Error</code> because we can't access the <code>x</code> and <code>y</code> variables outside that block and those variables are not _hoisted_. There is also a difference between <code>let</code> and <code>const</code> we can assign new values using <code>let</code> but we can't in <code>const</code> but <code>const</code> are mutable meaning. What this means is if the value that we assign to a <code>const</code> is an object we can change the values of those properties but can't reassign a new value to that variable. ###43. What are __Arrow functions__? [&uarr;](#the-questions "Back To Questions") __Arrow Functions__ are a new way of making functions in JavaScript. __Arrow Functions__ takes a little time in making functions and has a cleaner syntax than a __function expression__ because we omit the <code>function</code> keyword in making them. ``` //ES5版本 var getCurrentDate = 函數 (){ 返回新日期(); } //ES6版本 const getCurrentDate = () =&gt; new Date(); ``` In this example, in the ES5 Version have <code>function(){}</code> declaration and <code>return</code> keyword needed to make a function and return a value respectively. In the __Arrow Function__ version we only need the <code>()</code> parentheses and we don't need a <code>return</code> statement because __Arrow Functions__ have a implicit return if we have only one expression or value to return. ``` //ES5版本 函數問候(名稱){ return '你好' + 名字 + '!'; } //ES6版本 const 問候 = (name) =&gt; `Hello ${name}` ; constgreet2 = 名稱 =&gt; `Hello ${name}` ; ``` We can also parameters in __Arrow functions__ the same as the __function expressions__ and __function declarations__. If we have one parameter in an __Arrow Function__ we can omit the parentheses it is also valid. ``` const getArgs = () =&gt; 參數 const getArgs2 = (...休息) =&gt; 休息 ``` __Arrow functions__ don't have access to the <code>arguments</code> object. So calling the first <code>getArgs</code> func will throw an Error. Instead we can use the __rest parameters__ to get all the arguments passed in an arrow function. ``` 常量資料 = { 結果:0, 數字:\[1,2,3,4,5\], 計算結果() { ``` // "this" here refers to the "data" object ``` ``` const addAll = () => { ``` ``` // arrow functions "copies" the "this" value of ``` ``` // the lexical enclosing function ``` ``` return this.nums.reduce((total, cur) => total + cur, 0) ``` ``` }; ``` ``` this.result = addAll(); ``` } }; ``` __Arrow functions__ don't have their own <code>this</code> value. It captures or gets the <code>this</code> value of lexically enclosing function or in this example, the <code>addAll</code> function copies the <code>this</code> value of the <code>computeResult</code> method and if we declare an arrow function in the global scope the value of <code>this</code> would be the <code>window</code> object. ###44. What are __Classes__? [&uarr;](#the-questions "Back To Questions") __Classes__ is the new way of writing _constructor functions_ in __JavaScript__. It is _syntactic sugar_ for using _constructor functions_, it still uses __prototypes__ and __Prototype-Based Inheritance__ under the hood. ``` //ES5版本 函數人(名字,姓氏,年齡,地址){ ``` this.firstName = firstName; ``` ``` this.lastName = lastName; ``` ``` this.age = age; ``` ``` this.address = address; ``` } Person.self = 函數(){ ``` return this; ``` } Person.prototype.toString = function(){ ``` return "[object Person]"; ``` } Person.prototype.getFullName = function (){ ``` return this.firstName + " " + this.lastName; ``` } //ES6版本 類人{ ``` constructor(firstName, lastName, age, address){ ``` ``` this.lastName = lastName; ``` ``` this.firstName = firstName; ``` ``` this.age = age; ``` ``` this.address = address; ``` ``` } ``` ``` static self() { ``` ``` return this; ``` ``` } ``` ``` toString(){ ``` ``` return "[object Person]"; ``` ``` } ``` ``` getFullName(){ ``` ``` return `${this.firstName} ${this.lastName}`; ``` ``` } ``` } ``` __Overriding Methods__ and __Inheriting from another class__. ``` //ES5版本 Employee.prototype = Object.create(Person.prototype); 函數 Employee(名字, 姓氏, 年齡, 地址, 職位名稱, 開始年份) { Person.call(this, 名字, 姓氏, 年齡, 地址); this.jobTitle = jobTitle; this.yearStarted = YearStarted; } Employee.prototype.describe = function () { return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}` ; } Employee.prototype.toString = function () { 返回“\[物件員工\]”; } //ES6版本 class Employee extends Person { //繼承自「Person」類 建構函數(名字,姓氏,年齡,地址,工作標題,開始年份){ ``` super(firstName, lastName, age, address); ``` ``` this.jobTitle = jobTitle; ``` ``` this.yearStarted = yearStarted; ``` } 描述() { ``` return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`; ``` } toString() { // 重寫「Person」的「toString」方法 ``` return "[object Employee]"; ``` } } ``` So how do we know that it uses _prototypes_ under the hood? ``` 類別東西{ } 函數 AnotherSomething(){ } const as = new AnotherSomething(); const s = new Something(); console.log(typeof Something); // 記錄“函數” console.log(AnotherSomething 類型); // 記錄“函數” console.log(as.toString()); // 記錄“\[物件物件\]” console.log(as.toString()); // 記錄“\[物件物件\]” console.log(as.toString === Object.prototype.toString); console.log(s.toString === Object.prototype.toString); // 兩個日誌都回傳 true 表示我們仍在使用 // 底層原型,因為 Object.prototype 是 // 原型鏈的最後一部分和“Something” // 和「AnotherSomething」都繼承自Object.prototype ``` ###45. What are __Template Literals__? [&uarr;](#the-questions "Back To Questions") __Template Literals__ are a new way of making __strings__ in JavaScript. We can make __Template Literal__ by using the backtick or back-quote symbol. ``` //ES5版本 vargreet = '嗨,我是馬克'; //ES6版本 讓問候 = `Hi I'm Mark` ; ``` In the ES5 version, we need to escape the <code>'</code> using the <code>\\</code> to _escape_ the normal functionality of that symbol which in this case is to finish that string value. In Template Literals, we don't need to do that. ``` //ES5版本 var 最後一個字 = '\\n' - ' 在' - '我\\n' - '鋼鐵人\\n'; //ES6版本 讓最後一個單字=` ``` I ``` ``` Am ``` 鋼鐵人 `; ``` In the ES5 version, we need to add this <code>\n</code> to have a new line in our string. In Template Literals, we don't need to do that. ``` //ES5版本 函數問候(名稱){ return '你好' + 名字 + '!'; } //ES6版本 const 問候 = 名稱 =&gt; { 返回`Hello ${name} !` ; } ``` In the ES5 version, If we need to add an expression or value in a string we need to use the <code>+</code> or string concatenation operator. In Template Literals, we can embed an expression using <code>${expr}</code> which makes it cleaner than the ES5 version. ###46. What is __Object Destructuring__? [&uarr;](#the-questions "Back To Questions") __Object Destructuring__ is a new and cleaner way of __getting__ or __extracting__ values from an object or an array. Suppose we have an object that looks like this. ``` 常量僱員 = { 名字:“馬可”, 姓氏:“波羅”, 職位:“軟體開發人員”, 聘用年份:2017 }; ``` The old way of getting properties from an object is we make a variable t

我建立了一個 AI PowerPoint 產生器 - 方法如下:(Next.js、OpenAI、CopilotKit)

長話短說 ==== 在本文中,您將學習如何使用 Nextjs、CopilotKit 和 OpenAI 建立人工智慧驅動的 PowerPoint 應用程式。我們將涵蓋: - 利用 ChatGPT 建立您的 PowerPoint 簡報📊 - 與您的 PowerPoint 簡報聊天💬 - 將音訊和圖像新增至您的 PowerPoint 簡報🔉 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zkbn3i2vw59na8yn2gm0.gif) --- CopilotKit:建構深度整合的應用內人工智慧聊天機器人💬 ------------------------------- CopilotKit 是[開源人工智慧副駕駛平台。](https://github.com/CopilotKit/CopilotKit)我們可以輕鬆地將強大的人工智慧聊天機器人整合到您的 React 應用程式中。完全可定制和深度集成。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pixiay2v8raimvui28l6.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} ###### \*在布胡布上 現在回到文章。 --- **先決條件** -------- 要開始學習本教程,您需要在電腦上安裝以下軟體: - 文字編輯器(例如 Visual Studio Code) - Node.js - 套件管理器 使用 NextJS 建立 PowerPoint 應用程式前端 ------------------------------ **步驟 1:**使用下列指令 Git 複製 PowerPoint 應用程式樣板。 ``` git clone https://github.com/TheGreatBonnie/aipoweredpowerpointpresentation ``` **步驟 2:**在文字編輯器上開啟 PowerPoint 應用程式樣板,並使用下列指令安裝所有專案相依性。 ``` npm install ``` 步驟3: • 前往根目錄\*\*\*\*並建立一個名為**`.env.local`的檔案。**在該文件中,加入下面保存您的 ChatGPT API 金鑰的環境變數。 ``` OPENAI_API_KEY="Your ChatGPT API Key” ``` **步驟4:**在命令列執行命令*npm run dev* 。導航至 http://localhost:3000/,您應該會看到 PowerPoint 應用程式前端。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kom1urw8ggeevz2dspk8.png) 建立 PowerPoint 投影片功能 ------------------- 步驟 1:前往**`/[root]/src/app/components`** ,並建立一個名為`present.tsx`的檔案。然後在文件頂部導入以下相依性。 ``` "use client"; import { useCopilotContext } from "@copilotkit/react-core"; import { CopilotTask } from "@copilotkit/react-core"; import { useMakeCopilotActionable, useMakeCopilotReadable, } from "@copilotkit/react-core"; import { useEffect, useState } from "react"; import "./../presentation/styles.css"; import Markdown from "react-markdown"; ``` 步驟 2:定義一個名為 Slide 的 TypeScript 接口,其中包含 PowerPoint 簡報投影片的標題和內容屬性。 ``` // Define slide interface interface Slide { title: string; content: string; } ``` 步驟 3:建立一個名為`Presentation`函數,並使用usestate 初始化名為`allSlides`和`currentSlideIndex`狀態變數`usestate.`變數`allSlides`將保存幻燈片陣列,而`currentSlideIndex`將保存目前幻燈片索引。 ``` export function Presentation (){ const [allSlides, setAllSlides] = useState<Slide[]>([]); const [currentSlideIndex, setCurrentSlideIndex] = useState<number>(0); } ``` 步驟 4:在`Presentation`函數中,使用`useMakeCopilotReadable`掛鉤新增投影片的`allSlides`陣列作為應用程式內聊天機器人的上下文。 ``` useMakeCopilotReadable("Powerpoint presentation slides: " + JSON.stringify(allSlides)); ``` 步驟 5:使用`useMakeCopilotActionable`掛鉤設定一個名為`createNewPowerPointSlide`操作,其中包含描述和更新`allSlides`和`currentSlideIndex`狀態變數的實作函數,如下所示。 ``` useMakeCopilotActionable( { name: "createNewPowerPointSlide", description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.", argumentAnnotations: [ { name: "slideTitle", type: "string", description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.", required: true, }, { name: "content", type: "string", description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.", required: true }, ], implementation: async (newSlideTitle, newSlideContent) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent}; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); }, }, [], ); ``` 步驟6:定義一個名為`displayCurrentSlide`的函數,用於在前端顯示目前投影片索引。 ``` // Display current slide const displayCurrentSlide = () => { const slide = allSlides[currentSlideIndex]; return slide ? ( <div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center" style={{ textShadow: "1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000", }} > <Markdown className="markdown">{slide.title}</Markdown> <Markdown className="markdown">{slide.content}</Markdown> </div> ) : ( <div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center"> No Slide To Display </div> ); }; ``` 步驟 7: 定義一個名為 addSlide 的函數,該函數包含一個名為 CopilotTask 的類別。 CopilotTask 類別定義新增投影片的功能。 ``` // Add new slide function const addSlide = new CopilotTask({ instructions: "create a new slide", actions: [ { name: "newSlide", description: "Make a new slide related to the current topic.", argumentAnnotations: [ { name: "title", type: "string", description: "The title to display in the presentation slide.", required: true, }, { name: "content", type: "string", description: "The title to display in the presentation slide.", required: true, }, ], implementation: async (newSlideTitle,newSlideContent,) => { const newSlide: Slide = {title: newSlideTitle,content: newSlideContent,}; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); }, }, ], }); const context = useCopilotContext(); const [randomSlideTaskRunning, setRandomSlideTaskRunning] = useState(false); ``` 步驟 8:定義兩個函數 goBack 和 goForward。 goBack 函數定義導覽到上一張投影片的功能,而 goForward 函數定義導覽到下一張投影片的功能。 ``` // Button click handlers for navigation const goBack = () => setCurrentSlideIndex((prev) => Math.max(0, prev - 1)); const goForward = () => setCurrentSlideIndex((prev) => Math.min(allSlides.length - 1, prev + 1)); ``` 步驟9:傳回一個呼叫displayCurrentSlide函數的元件,並包含呼叫addSlide、goBack和goForward函數的按鈕。 ``` return ( <div> {displayCurrentSlide()} <button disabled={randomSlideTaskRunning} className={`absolute bottom-12 left-0 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded ${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`} onClick={async () => { try { setRandomSlideTaskRunning(true); await addSlide.run(context); } finally { setRandomSlideTaskRunning(false); } }} > {randomSlideTaskRunning ? "Loading slide..." : "Add Slide +"} </button> <button className={`absolute bottom-0 left-0 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded ${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`} onClick={goBack}>Prev</button> <button className={`absolute bottom-0 left-20 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded ${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`} onClick={goForward}>Next</button> </div> ); ``` 步驟10:在Presentation資料夾中,將以下程式碼加入page.tsx檔案。 ``` "use client"; import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import {Presentation} from "../components/present"; import "./styles.css"; let globalAudio: any = undefined; let globalAudioEnabled = false; const Demo = () => { return ( <CopilotKit url="/api/copilotkit/openai"> <CopilotSidebar defaultOpen={true} labels={{ title: "Presentation Copilot", initial: "Hi you! 👋 I can give you a presentation on any topic.", }} clickOutsideToClose={false} onSubmitMessage={async (message) => { if (!globalAudioEnabled) { globalAudio.play(); globalAudio.pause(); } globalAudioEnabled = true; }} > <Presentation/> </CopilotSidebar> </CopilotKit> ); }; export default Demo; ``` 步驟11:導覽至http://localhost:3000/,點擊「開始」按鈕,您將被重定向到與聊天機器人整合的演示頁面,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a74ilgzf5ep6snbli7uh.png) 步驟12:給右側的聊天機器人一個提示,例如「在TypeScript上建立PowerPoint簡報」聊天機器人將開始產生回應,完成後,它將在頁面左側顯示產生的PowerPoint投影片,如下圖所示 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxhkvltf33uwyvrt4a8a.png) 步驟 13:關閉聊天機器人窗口,然後按一下新增投影片 + 按鈕將新投影片新增至 PowerPoint 簡報中,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxl59gxkt02pmyadxzuk.png) 第 14 步:按一下「上一張」按鈕,您將導覽至上一張投影片。如果您按一下「下一步」按鈕,您將導覽至下一張投影片。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/expdwslfo2o49x7y6a6p.png) 建立 PowerPoint 投影片 自動語音功能 ------------------------ 步驟1:在`present.tsx`檔案中,宣告一個名為`globalAudio`的變數,如下所示。 ``` let globalAudio: any = undefined; ``` 步驟2:在`Presentation`元件中,宣告一個`useEffect`鉤子,用一個新的**`Audio`**物件初始化**`globalAudio`** ,如下所示。 ``` useEffect(() => { if (!globalAudio) { globalAudio = new Audio(); } }, []); ``` 步驟 3:更新 useMakeCopilotActionable 掛鉤,透過 API 將 PowerPoint 幻燈片文字轉換為語音,如下所示。 ``` useMakeCopilotActionable( { name: "createNewPowerPointSlide", description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.", argumentAnnotations: [ { name: "slideTitle", type: "string", description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.", required: true, }, { name: "content", type: "string", description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.", required: true }, { name: "speech", type: "string", description: "An informative speech about the current slide.", required: true, }, ], implementation: async (newSlideTitle, newSlideContent, speech) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent }; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); const encodedText = encodeURIComponent(speech); const url = `/api/tts?text=${encodedText}`; globalAudio.src = url; await globalAudio.play(); await new Promise<void>((resolve) => { globalAudio.onended = function () { resolve(); }; }); await new Promise((resolve) => setTimeout(resolve, 500)); }, }, [], ); ``` 步驟 4:更新 addSlide 函數,透過 API 將新的 PowerPoint 投影片文字轉換為語音,如下所示。 ``` // Add new slide function const addSlide = new CopilotTask({ instructions: "create a new slide", actions: [ { name: "newSlide", description: "Make a new slide related to the current topic.", argumentAnnotations: [ { name: "title", type: "string", description:"The title to display in the presentation slide.", required: true, }, { name: "content", type: "string", description:"The title to display in the presentation slide.", required: true, }, { name: "speech", type: "string", description: "An informative speech about the current slide.", required: true, }, ], implementation: async (newSlideTitle, newSlideContent, speech) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent }; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); const encodedText = encodeURIComponent(speech); const url = `/api/tts?text=${encodedText}`; globalAudio.src = url; await globalAudio.play(); await new Promise<void>((resolve) => { globalAudio.onended = function () { resolve(); }; }); await new Promise((resolve) => setTimeout(resolve, 500)); }, } ] }); ``` 步驟 5: 再次向聊天機器人發出「在 TypeScript 上建立 PowerPoint 簡報」提示,您應該會聽到簡報投影片的聲音。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iu6s1c4q5foto1xe919a.png) 建立圖像生成功能 -------- 步驟1:在present.tsx檔案中,新增一個名為backgroundImage的新屬性來鍵入介面Slide,如下所示。 ``` // Define slide interface interface Slide { title: string; content: string; backgroundImage: string; } ``` 步驟 2:更新 useMakeCopilotActionable 掛鉤以產生 PowerPoint 簡報投影片的圖片。 ``` useMakeCopilotActionable( { name: "createPowerPointSlides", description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.", argumentAnnotations: [ { name: "slideTitle", type: "string", description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.", required: true, }, { name: "content", type: "string", description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.", required: true }, { name: "backgroundImage", type: "string", description: "What to display in the background of the slide (i.e. 'dog' or 'house').", required: true, }, { name: "speech", type: "string", description: "An informative speech about the current slide.", required: true, }, ], implementation: async (newSlideTitle, newSlideContent, newSlideBackgroundImage, speech) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent, backgroundImage: newSlideBackgroundImage }; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); const encodedText = encodeURIComponent(speech); const url = `/api/tts?text=${encodedText}`; globalAudio.src = url; await globalAudio.play(); await new Promise<void>((resolve) => { globalAudio.onended = function () { resolve(); }; }); await new Promise((resolve) => setTimeout(resolve, 500)); }, }, [], ); ``` 步驟 2:更新 addSlide 函數以產生新的 PowerPoint 簡報投影片的圖片。 **步驟3:**更新`slide.tsx`檔案中的`Slide`元件以透過[`unsplash.com`](http://unsplash.com/)產生映像 ``` // Add new slide function const addSlide = new CopilotTask({ instructions: "create a new slide", functions: [ { name: "newSlide", description: "Make a new slide related to the current topic.", argumentAnnotations: [ { name: "title", type: "string", description:"The title to display in the presentation slide.", required: true, }, { name: "content", type: "string", description:"The title to display in the presentation slide.", required: true, }, { name: "backgroundImage", type: "string", description: "What to display in the background of the slide (i.e. 'dog' or 'house').", required: true, }, { name: "speech", type: "string", description: "An informative speech about the current slide.", required: true, }, ], implementation: async (newSlideTitle, newSlideContent, newSlideBackgroundImage, speech) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent, backgroundImage: newSlideBackgroundImage }; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); const encodedText = encodeURIComponent(speech); const url = `/api/tts?text=${encodedText}`; globalAudio.src = url; await globalAudio.play(); await new Promise<void>((resolve) => { globalAudio.onended = function () { resolve(); }; }); await new Promise((resolve) => setTimeout(resolve, 500)); }, } ] }); ``` 步驟 3:更新 displayCurrentSlide 函數以將背景圖像新增至 PowerPoint 簡報投影片中。 ``` // Display current slide const displayCurrentSlide = () => { const slide = allSlides[currentSlideIndex]; return slide ? ( <div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center" style={{ backgroundImage: 'url("https://source.unsplash.com/featured/?' + encodeURIComponent(allSlides[currentSlideIndex].backgroundImage) + '")', textShadow: "1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000", }} > <Markdown className="markdown">{slide.title}</Markdown> <Markdown className="markdown">{slide.content}</Markdown> </div> ) : ( <div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center"> No Slide To Display </div> ); }; ``` **步驟 4:**前往 Web 應用程式,您應該會看到背景圖像已新增至 PowerPoint 投影片中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1zgcgn1h6c5hz8dh7m9a.png) 結論 -- 總而言之,您可以使用 CopilotKit 建立應用內 AI 聊天機器人,該機器人可以查看當前應用程式狀態並在應用程式內執行操作。 AI 聊天機器人可以與您的應用程式前端、後端和第三方服務對話。 筆記: - 告訴聊天機器人要寫什麼 - 建立包含文字、圖片和自動語音的幻燈片。 這是全部實作程式碼: https://github.com/CopilotKit/CopilotKit/blob/mme/tools/CopilotKit/examples/next-openai/src/app/helloworld/page.tsx 你可以拿程式碼寫文章,一定要截圖。 確保按照邏輯實現的順序寫文章: - 首先,外部 React 元件( `page.tsx`中的`HelloWorld`元件) ``` - cover `CopilotKit` and `CopliotSidebar` ``` - 然後覆蓋`Presentation`反應元件而不加入 Copilot 功能 ``` - include the `Slide` react component ``` - 最後,在建立基本的 React 應用程式後,加入描述 Copilot 特定功能的部分( `useMakeCopilotReadable`和`useMakeCopilotActionable` ) - 最後,介紹生成音訊+影像的函數。保持描述非常簡短,只需說“我們透過瀏覽器audio-gen API產生音訊”和“我們透過[`unsplash.com`](http://unsplash.com/)產生圖像” 執行演示: - 請參閱文件/貢獻指南,以了解如何執行 CopilotKit 儲存庫中包含的範例應用程式:https://docs.copilotkit.ai/contribute/quickstart-contribute - 執行 - 前往`/helloworld`頁面 --- 結論 -- 總而言之,您可以使用 CopilotKit 建立應用內 AI 聊天機器人,該機器人可以查看當前應用程式狀態並在應用程式內執行操作。 AI 聊天機器人可以與您的應用程式前端、後端和第三方服務對話。 對於完整的源程式碼: https://github.com/TheGreatBonnie/aipoweredpresentation 感謝@theGreatBonnie 在本文中所做的出色工作。 別忘了... ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pixiay2v8raimvui28l6.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- 原文出處:https://dev.to/copilotkit/how-to-build-ai-powered-powerpoint-app-nextjs-openai-copilotkit-ji2