🔍 搜尋結果:%'

🔍 搜尋結果:%'

讓我們將函數式程式設計引入 OOP 程式碼庫

時間越長,我就越成為函數式程式設計的愛好者。即使當我在 OOP 程式碼庫中工作時,我也會嘗試應用旨在簡化程式碼並更輕鬆地預測結果的小概念。身為 Ruby 專家,我也喜歡使用功能程式碼編寫單元測試是多麼簡單。 本文的目的是分享我對函數式程式設計概念的看法,並提出一種可以在已編寫的 OOP 程式碼中使用函數式概念的方法。希望我們能夠停止爭論哪種範式更好,並開始編寫好的想法,每次都能產生更好的程式碼! 目錄 -- - [一開始,有函數式編程](#in-the-beginning-there-was-functional-programming) - [進入OOP,這個範式是什麼?](#entering-oop-what-is-this-paradigm) - [什麼是類別以及我們如何以不同的方式思考它](#what-is-a-class-and-how-can-we-think-about-it-differently) - [變異還是不變異:什麼是不變性](#to-mutate-or-not-to-mutate-what-is-immutability) - [床下的怪物:有什麼副作用](#the-monster-under-the-bed-what-are-side-effects) - [隔離一切:什麼是純函數](#isolate-everything-what-are-pure-functions) - [使用函數式模式而不需要完整的 haskell](#using-functional-patterns-without-going-full-haskell) - [結論](#conclusion) 一開始,有函數式編程 ---------- ![功能性](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hpg7b5lx6czu67jv9e07.png) 函數式程式設計範式於 1958 年隨著第一個 Lisp 語言的出現而出現(美好的時光)。它的根源可以追溯到 Alonzo Church 的 lambda 演算。函數式程式設計的核心原則圍繞著最小化對程式碼庫中狀態的依賴。 與允許狀態但強調封裝的物件導向程式設計 (OOP) 不同,函數式程式設計師努力優先編寫無狀態元件。這種方法鼓勵建立獨立於外部狀態變數的程式碼。 此外,即使引入了狀態,在編寫狀態時也必須考慮不變性、函數的純度,甚至避免副作用。隨著本文的深入,所有這些概念都將進一步介紹。 進入OOP,這個範式是什麼? -------------- ![打開](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/em6b2ejnoml80awxyrhw.png) OOP,或更廣為人知的名稱為“物件導向程式設計”,是一種可以追溯到 1967 年的範式。其最偉大的代表是 Simula、Smalltalk 和 Java。背後的思想過程是透過強制封裝實踐來將這些狀態以及修改它們的任何行為分組到公共「實體」或「物件」下,從而減少「全局」狀態的數量。 事實上,「物件導向程式設計」這個名字多年來一直被廣泛討論。 OOP 的建立者之一 Alan Key 實際上希望更多地關注該範例的訊息傳遞方面。這意味著我們應該強調封裝並允許物件之間進行狀態和行為的通訊。也許在不同的宇宙中,我們可以擁有「面向訊息的程式」。然而,OOP 這個名字已經經久不衰,而我們就在這裡! 我不知道你怎麼想,但是這個考慮範式的另一個可能名稱的簡單過程讓我的思維變得瘋狂,重新思考了一些概念,並實際上簡化了我建置軟體的方式。 什麼是類別以及我們如何以不同的方式思考它 -------------------- ![什麼是類](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/70x9cp26qi2cvx13dm3q.png) 我想每個人都聽過那個經典的講座,其中我們學到了“動物”類,其中包括“狗”類,對嗎?你可能聽過同樣的話(至少我聽過)。 > 類別是現實世界中實體的藍圖,描述其特徵和操作。 雖然不正確,但我想建議稍微改變一下單字的用法,以幫助澄清封裝,以便更好地理解,就像它對我所做的那樣。讓我們考慮以下新引文: > 類別是一種封裝狀態和在該狀態上操作的行為的方法。 這個簡單的字的改變確實讓我的思想改變了。我不再嘗試將類別視為現實世界的實體,而是開始簡單地將其視為將具有相似上下文的狀態分組在一起並公開對這些狀態進行操作的函數的另一種方式。我希望這個小小的改變也能幫助你回顧自己的概念! 以這種方式抽象化這個概念的重要性是,在從具有不同結構(例如模組)的語言中讀取程式碼時變得熟練。我們可以觀察到這段 OOP 程式碼是用 typescript 寫的: ``` class Github { private _url: string; privale _repo: string; private _username: string; constructor(url: string, repo: string, username: string) { this._repo = repo this._username = username this._url = url } public createRepo(name: string): void { // TODO: do stuff here using the provided state in _url, _repo and _username } } ``` 與此 Elixir 程式碼完全等效,即使 Elixir 程式碼使用“模組”而不是“類別”: ``` defmodule Github do @url "" @repo "" @username "" defstruct url: @url, repo: @repo, username: @username def new(url, repo, username) do %Github{url: url, repo: repo, username: username} end def create_repo(%Github{repo: repo, username: username}, name) do # TODO: do stuff here using the provided state in url, repo, and username end end ``` 接下來我們將研究一些功能概念並進一步討論這些範例的合併,讓我們開始吧! 變異還是不變異:什麼是不變性 -------------- ![不變性](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fotff1cbifqi2c5ubtb.png) 現在我們正在達到第一個真正的功能概念,而且是一個非常重要的概念,我可以補充一下(一切都在這裡計劃)!為了正確理解不變性,讓我們回顧一下在程式設計中處理值的方式: 通常,我們將值綁定到變數,以便稍後可以對它們進行操作,對嗎?就像簡單的事情 ``` # Bounding values to variables name = 'Cherry' age = 23 # Operating on it and bounding to another variable year_born = Time.now.year - age # Printing it puts "#{name} has #{age} years old and was born at #{year_born}" ``` 對於這些變數,透過更新其值來更改原始變數是很常見的,但這裡缺少的是:修改變數是一種**破壞性**操作。 但為什麼?好吧,讓我們想像一下多個操作(函數或程式碼區塊)在不同時刻和頻率修改相同變數。在這種情況下,我們會產生很多問題,例如: - **1. 無法對操作重新排序或根本無法更改它們:**當我們有如此多的依賴程式碼時,甚至很難對程式碼進行重新排序或更改,因為所有內容都綁定到特定的更改順序。 - **2. 理解程式碼在做什麼的心理負擔:**雖然這是個人觀點,但我認為這是一個廣受認可的觀點。高度可變的程式碼很容易變得混亂且難以理解資料流,需要除錯器等工具來逐步完成轉換。 - **3. 測試時的困難:**模擬函數轉換的特定狀態確實很困難,這將逐漸擴展您的單元測試,直到它們不再是單元。 不變性可以定義為避免更改(或變異)程式內任何變數的做法。儘管根據語言的不同,我們可能需要做出讓步並改變一些控制變數,但這裡要學到的總體教訓是: > 我們應該不惜一切代價避免改變沒有定義範圍的變數。 透過這句話,我的意思是可以在函數內建立作用域變數並在那裡對其進行變異。然而,一旦您將這個可變變數傳遞給另一個函數,您就會增加改變相同變數的目標數量,並且您將慢慢失去控制。這正是我們想要避免的情況! 床底下的怪物:有什麼副作用 ------------- ![副作用](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yb1osy6ziacyc2l12h6v.png) 每當有人提出這個話題來討論時,這個話題就會引起很大的熱度。我可能不會涵蓋這個主題的每一個細微差別,但我一定會向您解釋它們是什麼以及我如何在我自己的軟體中管理副作用,好嗎? 那麼,副作用就是透過呼叫協定(HTTP、WebSocket、GraphQL 等)甚至操作 stdin/stdout 與外部資源(或「外部世界」)互動的每一次計算。是的,我知道,即使是我們無害的`print`也會在這裡受到指責。 😔 但與變異性不同的是,我們不應該盡可能避免使用它,而應該將其隔離在單獨處理副作用的特定函數中。這樣,我們將程式碼分為「不執行任何副作用的函數」和「執行副作用的函數」。但為什麼要擔心這種分離呢? 每次我們觸發對「外部世界」的任何操作時,我們都會失去對這個特定計算部分可能發生的情況的控制(例如在執行 HTTP 呼叫時,伺服器可能會關閉或可能根本不存在)。其他問題包括測試困難和程式碼可預測性降低。 由於我們無法編寫沒有副作用的任何現實世界軟體,因此一般建議是將其聚集成小函數,透過對錯誤的適當抽象來單獨處理它。這樣,就可以只測試我們 100% 控制的函數,並模擬所有執行副作用的函數。 例如,請考慮以下執行 HTTP 請求的函數以及轉換從該請求傳回的資料的小函數。 ``` require 'faraday' module MyServiceModule # This function perform side effects def perform_http_request conn = Faraday.new(url: "fakeapi.com") begin response = conn.get {ok: true, data: response.body} rescue => e {ok: false, error: e} end end # These functions doens't perform any side effects def upcase_name(name) return '' unless name.is_a?(String) name.upcase end def retrieve_born_year(age) return 0 unless age.is_a?(Integer) Time.now.year - age end end ``` > 看到我怎麼說「錯誤周圍的抽象」了嗎?這正是上面的程式碼範例中實現的,而不是讓異常在我們針對雜湊抽象的程式碼中冒泡。 在使用「副作用」和「無副作用」之間的明確定義來定義這些函數之後,很容易預測程式碼中會發生什麼,也更容易測試,如下所示: ``` require 'minitest/autorun' class TestingStuff < Minitest::Test def test_upcase_name assert_equal MyServiceModule.upcase_name "cherry", "CHERRY" assert_equal MyServiceModule.upcase_name "kalane", "KALANE" assert_equal MyServiceModule.upcase_name "Thales", "THALES" end def test_retrieve_born_year Time.stub :now, Time.new(2024, 3, 5) do assert_equal MyServiceModule.retrieve_born_year 23, 2001 assert_equal MyServiceModule.retrieve_born_year 20, 2004 assert_equal MyServiceModule.retrieve_born_year 14, 2010 end end end ``` 這個策略真的很棒,因為您甚至不需要在測試時擔心副作用部分,只需為實際執行某些操作的程式碼轉換部分編寫斷言,您最終會得到更好的測試,真正驗證重要的內容您的程式碼庫的一部分!整齊吧? 隔離一切:什麼是純函數 ----------- ![純函數](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bgnpfyig36sssuio9qpy.png) 現在是時候總結到目前為止所獲得的所有知識了。在前面的範例中,我們觀察到程式碼分為「副作用」和「無副作用」。我們還看到了這些功能如何更容易測試,並且我們的主要轉換業務邏輯應該保持隔離。您想知道這些函數叫什麼嗎?它們是**純函數**! 讓我們檢查純函數的正確形式定義並逐步探索這個概念。 > 純函數是尊重不變性、不執行任何副作用並且在給定相同參數的情況下傳回相同輸出的函數。 基本上,純函數遵循我們之前提到的所有原則,而且它們總是為相同的參數產生相同的返回。讓我們來看看之前的函數。 ``` def upcase_name(name) return '' unless name.is_a?(String) name.upcase end upcase_name('cherry') # => Will be *always* CHERRY ``` 使用純函數,我們可以輕鬆定義多個斷言,因為我們不受任何需要大量模擬的上下文的約束。我們只需用靜態值傳遞所需的參數,就這樣! 由於純函數非常小且可組合,因此它們的數量增加得非常快。為了解決這個問題,像 Elixir 這樣的函數式語言提供了像管道這樣的組合運算符,這使得按順序執行多個純函數變得非常容易。 ``` "cherry " |> trim |> upcase # => "CHERRY" ``` > 管道運算子源自 Bash 等函數。您可以在這裡閱讀更多相關資訊:\[ <https://dev.to/cherryramatis/linux-filters-how-to-streamline-text-like-a-boss-2dp4#what-is-a-pipeline> \] 使用函數式模式而不需要完整的 haskell ---------------------- ![都是功能啊](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/42ndx4tuvrs35q2woepg.png) 我一直害怕學習函數範式,因為社群透過使用現成的句子和大概念讓每個試圖學習一些小技巧的人變得非常複雜。在掌握了許多函數式語言並嘗試盡可能多地學習之後,我的目標是簡化這些概念,最重要的是,提倡在 OOP 程式碼中使用函數式概念。 應用純函數(或純方法,如果您願意)、不變性和副作用分離可以使您的 OOP 程式碼看起來更乾淨和解耦。你不需要知道什麼是 monad 或如何在 Haskell 中手動編寫編譯器;您可以使用簡單而有效的函數概念來堅持使用 Ruby on Rails! 我希望透過這篇小文章(以及本系列中的文章),無論您選擇哪種語言和框架,您都可以透過可組合性和簡單性來改進您的程式碼庫。 結論 -- 這篇文章是我嘗試使函數範式的知識民主化(在我的能力和專業知識範圍內)。需要強調的是,我不是函數式專家,本文針對的是了解 OOP 並對函數式程式設計感興趣的初學者。我希望它有用,並且我願意提供任何需要的幫助。願原力與你同在🍒 --- 原文出處:https://dev.to/cherryramatis/ending-the-war-or-continuing-it-lets-bring-functional-programming-to-oop-codebases-3mhd

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

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

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

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

Snake...純 HTML⁉️ [沒有 JS,沒有 CSS,沒有圖片!!] 😱

他們說有些人就是喜歡混亂。 大家好👋🏼,我是格雷厄姆「喜歡混亂」TheDev,這次我帶著另一個愚蠢的網路實驗回來了(如果你願意,你可以[直接跳到遊戲](#the-game))。 一切都是那麼天真地開始,「我可以寫一個貪吃蛇遊戲嗎?」。 但是,一如既往,我肩膀上的小惡魔低聲說「讓它變得更難」......所以我想「沒有 JavaScript,用純 CSS 來做」。 他再次嘰嘰喳喳地說:「噗,還是太簡單了,而且你最近做了太多 CSS 東西,用原始的、無樣式的 HTML 來做吧」。 我轉向另一邊肩膀,想聽聽天使的想法,希望得到更明智的結果。 然後我想起天使從來沒有在我身邊... 所以這就是,snake,純 HTML 格式(用一點 PHP 技巧來支援它)。 這是正確的! - 沒有 JavaScript - 沒有圖片 - 沒有CSS - 沒有 Cookie 不過,我想澄清一下(因為我不想被指責為點擊誘餌),我正在使用 PHP 渲染此 HTML。 雖然可以僅使用文件以純 HTML 形式完成此操作,無需後端語言,但它需要 640,345,228,142,352,307,046,244,325,015,114,448,670,890, 662,773,914,918,117,331,955,996,440,709,549,671,345,290,477,020,322,434,911,210,797,593,280,795,101, 545,372,667,251,627,877,890,009,349,763,765,710,326,350,331,533,965,349,868,386,831,339,352,024,373, 788,157,786,791,506,311,858,702,618,270,169,819,740,062,983,025,308,591,298,346,162,272,304,558,339, 520,759,611,505,302,236,086,810,433,297,255,194,852,674,432,232,438,669,948,422,404,232,599,805,551,610,635,942,376,961,399 ,231,917,134,063,858,996,537,970,147,827,206,606,320,217,379,472,010,321,356,624,613,809,077,942,304,597,360,699,567,595,836, 096,158,715,129,913,822,286,578,579,549,361,617,654,480,453,222,007,825,818,400,848,436,415,591,229,454,275,384,803,558,374,5 18,022,675,900,061,399,560,145,595,206,127,211,192,918,105,032,491,008,000,000,000,000,000,000,000,000,000,000,000,000,000,00 0,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 個文件。 所以請原諒使用 PHP 產生下一頁的「捷徑」(對於那些在技術 Twitter 上花費太多時間的人,也請原諒我使用「死」語言!😱🤣)。 不管怎樣,序言太多了,我知道你為什麼來這裡,你想看到它的實際效果! 玩 HTML 貪吃蛇(有警告!) ---------------- 在桌面 PC 上的 Chrome 上執行...對於我來說,在 Firefox 和 iOS 上的任何瀏覽器上執行速度太快...您將在本文後面了解原因。 所以基本上,在桌面版 Chrome 中玩吧! 關鍵是: - ALT + I向上, - ALT + J向左, - ALT + K向下, - ALT + L向右, - ALT + O開始新遊戲(一旦你輸了)。 在 Mac 上,我相信是Control + Option而不是Alt ! 如果你想知道為什麼奇怪的鍵而不是 WASD,不幸的是ALT + D已經在 Windows 上的 Chrome 中使用,所以我不得不選擇「安全」鍵。 **最後一個警告:**我們用於實現此功能的技巧之一會用大量 URL 淹沒您的瀏覽器歷史記錄...您已被警告! ### 遊戲 **遺憾的是,這無法在 codepen 中執行,因此您必須[在我的網站上玩 HTML Snake](https://grahamthe.dev/demos/snake/) 。** 當你玩完後,回來看看我用了什麼技巧來完成這個工作(並在評論中分享你的最高分!)。 問題 1:取得遊戲勾選 ----------- 對於遊戲,您*通常*需要有一個“遊戲勾選”。每個刻度是當一個動作發生或您計算一個新的遊戲狀態然後渲染新的遊戲狀態。 但這提出了我們的第一個問題,我們如何能夠在沒有 JavaScript 的情況下自動更新頁面? 嗯,在 HTML 中執行此操作實際上非常簡單,我們只需將[`<meta http-equiv="refresh"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#http-equiv)設為較低值即可。 因此,我們從 0.35 秒刷新時間開始,然後隨著您的分數攀升,將刷新時間加快至 0.2 秒。 「元刷新」允許我們做的是指示瀏覽器載入頁面的 HTML 後,等待 X 秒,然後載入另一個 URL。 透過設定較低的刷新時間,然後更改我們在每次刷新時重定向到的 URL(稍後會詳細介紹),我們有一種方法可以讓頁面自行更改,即使您不按任何按鈕! 以下是格式的簡單範例: ``` <meta http-equiv="refresh" content="[time];url=[url-to-redirect-to]"> ``` **附註:**這是我之前提到的它不適用於其他瀏覽器的地方。他們不排除部分第二次刷新時間,因此刷新是即時的,使得遊戲太快而無法玩。 但僅元刷新不足以使遊戲正常執行,我們需要某種方法來保存遊戲狀態並將蛇方向的變化傳達給伺服器。 為此,我們使用另一個直接的技巧:URL 編碼的 GET 參數。 問題 2:管理遊戲狀態 ----------- 因為我們不能使用 POST 請求或類似的東西,所以我們需要另一個機制來管理瀏覽器和伺服器之間的遊戲狀態。 起初,我使用多個 GET 參數來管理狀態,因此 URL 如下所示: ``` url?playing=1&x=2&y=6&foodx=3&foody=6&dir=left. ``` 這一直工作得很好,直到我需要為蛇儲存多個點(它佔據的每個方塊的 x,y 座標)。 雖然我確實讓它與一些 hacky x,y 座標列表和解析一起工作(例如`snake=1,1,2,1` ,蛇位於x=1,y=1 和x=2,y=1),但這是凌亂的。 因此,我們轉向我們的好朋友: [`urlencode()`](https://www.php.net/manual/en/function.urlencode.php)和[`json_encode()`](https://www.php.net/manual/en/function.json-encode.php) 。 一起使用時,我可以取得一個陣列(或在本例中為多維陣列),將其轉換為 JSON,然後將其轉換為 URL 的有效字元。 讓我解釋: ### 在 URL 中儲存複雜資料 以下是我用於遊戲狀態的資料範例: ``` $state = array( 'width' => $width, 'height' => $height, 'snake' => array(array('x' => 5, 'y' => 5)), 'food' => array('x' => 7, 'y' => 7), 'direction' => 'right', 'score' => 0, 'state' => true ); ``` 要將這些資料儲存在 URL 中,我們可以使用以下命令: ``` $url = urlencode(json_encode($state)); ``` 透過 JSON 編碼我們的陣列,然後用 URL 友好的字符替換無效字符,這以 URL 友好(儘管不是人類友好!)的方式為我們提供了狀態: ``` %7B%22width%22%3A20%2C%22height%22%3A20%2C%22snake%22%3A%5B%7B%22x%22%3A19%2C%22y%22%3A5%7D%5D%2C%22food%22%3A%7B%22x%22%3A4%2C%22y%22%3A11%7D%2C%22direction%22%3A%22right%22%2C%22score%22%3A0%2C%22state%22%3Afalse%7D ``` 現在我們有一個機制可以將遊戲狀態傳遞到瀏覽器並備份到伺服器。 ### 最大 URL 長度 那些了解自己的東西的人會知道這裡有一個問題。 URL 長度有最大限制! 在 Chrome 中是 2083 個字元。 如果您玩遊戲的時間足夠長,您實際上會達到字元限制,因為為了儲存 x,y 位置對,我們每次使用超過 10 個字元。 但這是一個愚蠢的演示,所以我只想說:讓我知道如果你讓你的蛇足夠長,會發生什麼錯誤! 哦,在現實世界中,**您不應該對 URL 中的參數進行 JSON 編碼**,我們就這樣吧! 我們有狀態和遊戲標記,現在怎麼辦? ----------------- 這就對了! 嗯,差不多了。 我們需要將按鍵資訊傳達給伺服器。 ### 問題3:改變蛇的方向 這是最後一個問題(也是我們最終在 URL 中顯示遊戲狀態的原因),我們需要向伺服器傳達按鍵訊息以更改蛇的方向。 #### 問題 3a:按下按鈕 在我們將按鍵傳達給伺服器之前,我們需要某種方法來實際捕獲它們。 請記住,我們沒有 JS 來捕獲按鍵操作。 我們也不能使用`<button>`元素,因為它們需要 JS 才能運作。 所以我們剩下的就是不起眼的錨元素( `<a>` )。 但讓某人點擊錨點會讓遊戲變得很難玩。 幸運的是,HTML 中內建了一種名為[`accesskey`的](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey)東西。 它們允許我們將一個字元指派給一個錨點,然後可以透過捷徑(ALT + Windows 上 Chrome 中的字元)存取這些字元。 這為我們提供了允許鍵盤控制的機制,我們只需要 4 個具有不同方向的連結(錨點)作為 URL,然後為每個連結分配一個`accesskey` 。 **重要提示:**應謹慎使用`accesskey` ,如果您選擇輔助科技 (AT) 使用者使用的按鍵,則可能會產生幹擾。 #### 問題 3b:方向 現在我們有了一種按鍵方法,以及一種將按鍵操作傳達給伺服器的方法,我們需要一種方法來管理按鍵操作,以便它們更新蛇的方向。 幸運的是,我們透過 URL 傳遞的狀態物件中已經有了一個`direction`屬性。 因此,我們要做的就是建立 4 個不同的 URL,每個方向一個。然後我們將這些加入到連結中就完成了。 ``` $encodedState = urlencode(json_encode($state)); <a href="index.php?state=<?php echo $encodedState; ?>&direction=up" accesskey="i">up (ALT + I)</a><br/> <a href="index.php?state=<?php echo $encodedState; ?>&direction=left" accesskey="j">left (ALT + J)</a> <a href="index.php?state=<?php echo $encodedState; ?>&direction=right" accesskey="l">right (ALT + L)</a> <a href="index.php?state=<?php echo $encodedState; ?>&direction=down" accesskey="k">down (ALT + K)</a> ``` 現在,例如,當您按ALT + K時,將單擊第四個連結,我們將當前狀態+新方向發送到伺服器! 現在剩下的就是獲取該資訊併計算下一個遊戲狀態。 ### 遊戲邏輯 最後,謎題的最後一部分是一些遊戲邏輯。 例如,在生成食物位置時,我們需要檢查它是否不在蛇已經佔據的圖塊上,因此我們有以下函數: ``` function generateFoodPosition($width, $height, $snake) { do { $food = array( 'x' => rand(0, $width - 1), 'y' => rand(0, $height - 1)); } while ( in_array($food, $snake) ); return $food; } ``` 還有一個移動蛇的功能 ``` function moveSnake($state) { $newHead = array('x' => $state['snake'][0]['x'], 'y' => $state['snake'][0]['y']); // Update snake's head position based on direction switch ($state['direction']) { case 'up': $newHead['y']--; break; case 'down': $newHead['y']++; break; case 'left': $newHead['x']--; break; case 'right': $newHead['x']++; break; } // Check if snake has collided with the wall or itself if ($newHead['x'] < 0 || $newHead['x'] >= $state['width'] || $newHead['y'] < 0 || $newHead['y'] >= $state['height'] || in_array($newHead, array_slice($state['snake'], 1))) { $state['state'] = false; return $state; // Game over } // Check if snake has eaten the food if ($newHead['x'] == $state['food']['x'] && $newHead['y'] == $state['food']['y']) { $state['score'] += 10; // Generate new food position $state['food'] = generateFoodPosition($state['width'], $state['height'], $state['snake']); } else { // Remove tail segment array_pop($state['snake']); } // Move snake array_unshift($state['snake'], $newHead); return $state; } ``` 以及建構遊戲板的循環。 ``` for ($y = 0; $y < 20; $y++) { echo "\r\n"; for ($x = 0; $x < 20; $x++) { if ($x == $state['food']['x'] && $y == $state['food']['y']) { echo '🍎'; } elseif (in_array(array('x' => $x, 'y' => $y), $state['snake'])) { echo '🟩'; }else{ echo '⬜'; } } } ``` 但我不會詳細介紹這些內容,因為您可以輕鬆找到(並找到更簡潔的方法)、找到其他人編寫的(更好的)程式碼並適應您的需求。 這是一個包裝 ------ 現在你已經知道了,我們使用元刷新、存取鍵和在 URL 中編碼複雜資料的技巧建立了一個遊戲。 這些東西對你的日常生活有用嗎?不,可能不會。 他們是否會在一個奇怪的邊緣情況下拯救你的屁股,完成這個工作,有能力使用黑客來交付產品情況?可能吧。 什麼?您沒想到我會提供您有用的教學嗎?你現在應該更清楚了。 但是,話雖如此,如果您確實喜歡這篇文章,或者奇蹟般地學到了一些新東西,請在下面給我留言,這真的意義重大! 祝大家週末愉快! 💗 --- 原文出處:https://dev.to/grahamthedev/snakein-pure-html-no-js-no-css-no-images-2ccg

html/css 小分享:設定複選框和開關的樣式

我可能不是唯一一個對瀏覽器的預設`<input type="checkbox">`感到沮喪的開發人員。 首先:**它不可擴展。**在此範例中,字體大小已縮放至`200%` ,但複選框仍保持其根大小,即`13.333333px` : ![預設複選框](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/delpw47gyetj091jeun0.png) 在本教程中,我們將剖析瀏覽器的預設複選框,看看是否可以做得更好。 --- 首先,我們需要使用`appearance:none`清除預設樣式並設定初始大小 - 這將是一個相對單位`em` : ``` [type=checkbox] { appearance: none; aspect-ratio: 1; box-sizing: border-box; font-size: 1em; width: 1em; } ``` `background-color`應該適應深色模式,因此我們將檢查它是否與任何[系統顏色](https://developer.mozilla.org/en-US/docs/Web/CSS/system-color)相符。它似乎與`Field`系統顏色匹配,所以讓我們使用它。 對於 Chrome 中的邊框顏色,它與系統顏色`ButtonBorder`匹配,但由於 Safari 使用*更輕的*`ButtonBorder` ,我們將使用適用於兩種瀏覽器的`GrayCanvas` 。 我們將加入一些 CSS 自訂屬性,稍後我們將使用它們來建立變體。 對於`border-radius`和`margin` ,我們將使用預設值,但將它們轉換為相對單位`em` 。 `border-width`似乎使用以下公式進行縮放: ``` (4/3) / root size ``` 由於根大小為`13.333333px` ,我們現在有: ``` [type=checkbox] { --_bdw: calc(1em * (4/3) / 13.333333); appearance: none; aspect-ratio: 1; background: var(--_bg, Field); border: var(--_bdw) solid var(--_bdc, GrayText); border-radius: var(--_bdrs, .2em); box-sizing: border-box; font-size: 1em; margin: var(--_m, .1875em .1875em .1875em .25em); position: relative; width: 1em; } ``` 讓我們看看它是否可擴展: ![可擴充複選框](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dp6npvshq378jt0yk8o6.png) 好的!深色模式呢? ![深色模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lm0aq2eixhf9or4ho67a.png) 這就是為什麼我**喜歡**系統顏色!接下來,讓我們新增瀏覽器在**未選取的**複選框上使用的相同懸停效果。 我們將混合`CanvasText` ,它在淺色模式下為黑色,在深色模式下為白色,並簡單地更新我們在上一步中加入的`--_bdc`屬性: ``` @media (hover: hover) { &:not(:checked):hover { --_bdc: color-mix(in srgb, GrayText 60%, CanvasText 40%); } } ``` --- 複選標記 ---- 現在是複選標記。我們**可以**在`::after`元素中使用旋轉的 CSS 方塊來做到這一點: ``` [type=checkbox]::after { border-color: GrayText; border-style: solid; border-width: 0 0.15em 0.15em 0; box-sizing: border-box; content: ''; aspect-ratio: 1 / 1.8; rotate: 40deg; width: 0.375em; } ``` ![CSS 中的複選標記](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56ff7lvbtsljaxtoyt3h.png) 雖然這工作得很好,但我更喜歡在蒙版中使用 SVG,因為它更靈活。為此,我們將為遮罩加入一個屬性,並為`::after`元素的背景加入另一個`--_bga` ,該屬性將是複選標記的**顏色**。 ``` [role=checkbox] { --_bga: Field; --_mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="3" stroke="%23000" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path d="M5 12l5 5l10 -10"/></svg>'); &::after { background: var(--_bga, transparent); content: ""; inset: 0; position: absolute; mask: var(--_mask) no-repeat center / contain; -webkit-mask: var(--_mask) no-repeat center / contain; } } ``` 所以,我們現在**確實**有一個複選標記,只是看不到它,因為蒙版顏色設定為`transparent` 。 讓我們使用`:checked` -state 更新點擊時複選框的顏色。但在此之前,我們需要先弄清楚**哪種**顏色! Safari 是唯一支援系統顏色`AccentColor`瀏覽器,因此我們需要為此建立自己的變數`--_accent` ,在 Mac 上對應於`#0075ff` : ``` [type=checkbox] { --_accent: #0075ff; &:checked { --_bdc: transparent; --_bg: var(--_accent); --_bga: Field; } } ``` 讓我們看看我們建構了什麼: ![複選框樣式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9l3tzwwqkmrr4bpzrg19.png) 還有深色模式?我們需要先更新`--_accent`屬性,因為`AccentColor`尚未在所有瀏覽器中運作: ``` @media (prefers-color-scheme: dark) { --_accent: #99C8FF; } ``` 讓我們檢查: ![複選框深色模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0kuid7fnr3obftfjr85s.png) 涼爽的!現在我們需要加入的是`:checked:hover` -state,它類似於我們之前新增的 border-hover: ``` @media (hover: hover) { &:checked:hover { --_bg: color-mix(in srgb, var(--_accent) 60%, CanvasText 40%); } } ``` 讓我們比較一下它在 Chrome、Safari 和 Firefox 中的外觀: ![瀏覽器比較](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hi9qej87nns2wjd4jqiw.png) 看來我們通過考驗了! --- 變體 -- 建立變體非常簡單:您只需要更新一些屬性。例子: ``` .rounded { --_bdrs: 50%; } .square { --_bdrs: 0; } ``` 然後在 HTML 中: ``` <input type="checkbox" class="rounded"> <input type="checkbox" class="square"> ``` ![變體](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vu5g82hu5zv65izekwsn.png) — 或全力以赴並建立*老式*複選框: ![老派複選框](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ngan912ja853wx4q8gil.png) > **關於圓形複選框的註釋:**這是*不好的*做法,正如您可以在[這篇精彩的文章](https://tonsky.me/blog/checkbox/)中讀到的那樣。不過,也有一些例外,例如這個「影像選擇器」: ![圓形影像選擇](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j36lzrpf7sxvimocpoud.png) 開關 -- 對於開關,我們將加入一個`role="switch"` ,所以它是: ``` <input type="checkbox" role="switch"> ``` 蘋果最近加入了[自己的 switch-control](https://dev.to/madsstoumann/a-first-look-at-apples-new-switch-control-3pnd) ,但`role="switch"`是跨瀏覽器的。同樣,我們只需要更新我們之前建立的許多屬性: ``` [role=switch] { --_bdc--hover: transparent; --_bdrs: 1em; --_bg: #d1d1d1; --_bga: Field; --_mask: none; aspect-ratio: 1.8 / 1; border: 0; display: grid; padding: .125em; place-content: center start; width: 1.8em; &::after { border-radius: 50%; height: .75em; inset: unset; position: static; width: .75em; } &:checked { --_bg: var(--_bg--checked, var(--_accent)); justify-content: end; } } ``` 這給了我們: ![開關](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rlfyayxyzkqcr78qovn.png) --- 示範 -- 就是這樣!下面是帶有演示的 Codepen: https://codepen.io/stoumann/pen/OJqQNgm --- 駭客與縫合 ----- 以下是我在 Codepen 上使用複選框所做的一系列工作: ### 複選框電影院 選擇座位。還可以修改為在火車、飛機上選擇座位...... https://codepen.io/stoumann/pen/eYXpEBa ### 天際線複選框 點擊窗戶打開公寓裡的燈… https://codepen.io/stoumann/pen/KKbGJqd ### 按數字繪畫 先選擇一種顏色(使用`<input type="radio">` ),然後按一下對應的數字(複選框)... https://codepen.io/stoumann/pen/VwRejpR ### 點對點 不需要 JavaScript,但我把它留在那裡供你玩… https://codepen.io/stoumann/pen/zYbNRNL ### 來自地獄的條款和條件 檢查全部… https://codepen.io/stoumann/pen/GRwRjYP --- 每日 toggle ---- Alvaro Montoro 正在建立大量開關/撥動開關 - 2024 年每天一個。請[在此處](https://codepen.io/collection/aMPYMo)查看。 --- 原文出處:https://dev.to/madsstoumann/styling-checkboxes-and-switches-pf0

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

為所有正規表示式討厭者(和愛好者)準備的正規表示式備忘錄👀

我使用正規表示式的經驗 =========== 我一直遠離正規表示式。在我第一年的電腦科學實驗室中,有一個涉及正規表示式的練習。我想那是我第一次被介紹給它。我當時認為這很酷,但看起來太難了,所以我一直在避免它,或者只是在谷歌上搜尋如何解決某個正規表示式問題。但我*終於*花了一些時間來正確學習它🎉 ![](https://media.giphy.com/media/czoS1CAP2YZBS/giphy.gif) 在閱讀了一些資源並涉獵之後,可以肯定地說我不再害怕正規表示式了!我發現自己在我所做的許多編碼練習中都使用了它。所需要的只是練習!以下是我根據我學到的正規表示式和我使用的資源編寫的備忘單(帶有範例) 備忘錄 === 我已經包含了一些我學到的 Javascript 中不可用的正規表示式。對於這些,我都註解掉了。如果需要的話請記住「g」修飾符!對於我的範例,我省略了修飾符。 ``` let regex; /* matching a specific string */ regex = /hello/; // looks for the string between the forward slashes (case-sensitive)... matches "hello", "hello123", "123hello123", "123hello"; doesn't match for "hell0", "Hello" regex = /hello/i; // looks for the string between the forward slashes (case-insensitive)... matches "hello", "HelLo", "123HelLO" regex = /hello/g; // looks for multiple occurrences of string between the forward slashes... /* wildcards */ regex = /h.llo/; // the "." matches any one character other than a new line character... matches "hello", "hallo" but not "h\nllo" regex = /h.*llo/; // the "*" matches any character(s) zero or more times... matches "hello", "heeeeeello", "hllo", "hwarwareallo" /* shorthand character classes */ regex = /\d/; // matches any digit regex = /\D/; // matches any non-digit regex = /\w/; // matches any word character (a-z, A-Z, 0-9, _) regex = /\W/; // matches any non-word character regex = /\s/; // matches any white space character (\r (carriage return),\n (new line), \t (tab), \f (form feed)) regex = /\S/; // matches any non-white space character /* specific characters */ regex = /[aeiou]/; // matches any character in square brackets regex = /[ck]atherine/; // matches catherine or katherine regex = /[^aeiou]/; // matches anything except the characters in square brackets /* character ranges */ regex = /[a-z]/; // matches all lowercase letters regex = /[A-Z]/; // matches all uppercase letters regex = /[e-l]/; // matches lowercase letters e to l (inclusive) regex = /[F-P]/; // matches all uppercase letters F to P (inclusive) regex = /[0-9]/; // matches all digits regex = /[5-9]/; // matches any digit from 5 to 9 (inclusive) regex = /[a-zA-Z]/; // matches all lowercase and uppercase letters regex = /[^a-zA-Z]/; // matches non-letters /* matching repetitions using quantifiers */ regex = /(hello){4}/; // matches "hellohellohellohello" regex = /hello{3}/; // matches "hellooo" and "helloooo" but not "helloo" regex = /\d{3}/; // matches 3 digits ("312", "122", "111", "12312321" but not "12") regex = /\d{3,7}/; // matches digits that occur between 3 and 7 times (inclusive) regex = /\d{3,}/; // matches digits that occur at least 3 times /* matching repetitions using star and plus */ regex = /ab*c/; // matches zero or more repetitions of "b" (matches "abc", "abbbbc", "ac") regex = /ab+c/; // matches one or more repetitions of "b" (matches "abc", "abbbbc", but not "ac") /* matching beginning and end items */ regex = /^[A-Z]\w*/; // matches "H", "Hello", but not "hey" regex = /\w*s$/; // matches "cats", "dogs", "avocados", but not "javascript" /* matching word boundaries positions of word boundaries: 1. before the first character in string (if first character is a word character) 2. after the last character in the string, if the last character is a word character 3. between two characters in string, where one is a word character and the other isn't */ regex = /\bmeow\b/; // matches "hey meow lol", "hey:meow:lol", but not "heymeow lol" /* groups */ regex = /it is (ice )?cold outside/; // matches "it is ice cold outside" and "it is cold outside" regex = /it is (?:ice )?cold outside/; // same as above except it is a non-capturing group regex = /do (cats) like taco \1/; // matches "do cats like taco cats" regex = /do (cats) like (taco)\? do \2 \1 like you\?/; // matches "do cats like taco? do taco cats like you?" //branch reset group (available in Perl, PHP, R, Delphi... commented out because this is a js file) // regex = /(?|(cat)|(dog))\1/; // matches "catcat" and "dogdog" /* alternative matching */ regex = /i like (tacos|boba|guacamole)\./; // matches "i like tacos.", "i like boba.", and "i like guacamole." /* forward reference (available in Perl, PHP, Java, Ruby, etc... commented out because this is a js file) */ // regex = /(\2train|(choo))+/; // matches "choo", "choochoo", "chootrain", choochootrain", but not "train" /* lookaheads */ regex = /z(?=a)/; // positive lookahead... matches the "z" before the "a" in pizza" but not the first "z" regex = /z(?!a)/; // negative lookahead... matches the first "z" but not the "z" before the "a" /* lookbehinds */ regex = /(?<=[aeiou])\w/; // positive lookbehind... matches any word character that is preceded by a vowel regex = /(?<![aeiou])\w/; // negative lookbehind... matches any word character that is not preceded by a vowel /* functions I find useful */ regex.test("hello"); // returns true if found a match, false otherwise regex.exec("hello"); // returns result array, null otherwise "football".replace(/foot/,"basket"); // replaces matches with second argument ``` 感謝 Sarthak 建立了我的備忘錄的[GitHub 要點](https://gist.github.com/sarthology/b269c4ab81832c03f80eb48920f1abce),也感謝 Xian-an 將其翻譯成[中文](https://gist.github.com/cxa/901e1862cd9ddf5c721cea6f7807d77a)👏 資源 == - 「正規表示式」挑戰是[FreeCodeCamp](https://freecodecamp.org)上「Javascript 演算法和資料結構認證」的一部分 - [MDN 正規表示式文件](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) - [正規表示式](https://regexone.com/) - [Regex101](https://regex101.com/)用於測試(您也可以使用 Chrome 開發者控制台) - [HackerRank](https://hackerrank.com/)正規表示式挑戰練習 ![](https://media.giphy.com/media/111ebonMs90YLu/giphy.gif) 就是這樣,夥計們!希望這對您有幫助☺️ --- 原文出處:https://dev.to/catherinecodes/a-regex-cheatsheet-for-all-those-regex-haters-and-lovers--2cj1

100 多個專案創意

**編輯**:大家好!在對本文做出驚人反應後,我建立了一個名為「每週專案俱樂部」的專案。每週您的收件匣都會收到需要解決的問題。你可以努力解決問題,並且你將得到整個俱樂部的幫助,讓你走上正軌。了解更多並[在這裡](https://weeklyproject.club)註冊! 有一天我注意到一個模式。我注意到很多人都在努力 學習編程,但他們心中沒有特定的目標。我已經討論過如何了解您想要學習程式設計的原因可以幫助您選擇要學習的語言[!](https://pickaframework.com/articles/why/) ,以及如何實際做出決定([在這裡!](https://pickaframework.com/feature_fishing/) )但是專案有什麼幫助呢? 當我指導程式設計師時,我發現有一個專案可以幫助排除其他一些幹擾,例如想知道你是否使用了正確的語言。透過專注於一個特定的目標,你就不用那麼費力去擔心*這*是否正是你應該使用的語言。結果是你建立了一些簡潔的東西,並且一路上你學到了一些東西! 2隻鳥,1塊石頭。 這就是為什麼我為初學者程式設計師策劃了這個專案清單。許多人列出了大量的專案來學習編程,但很少按照難度進行組織。我瀏覽了幾個流行的程式設計專案想法清單。如果您想查看完整列表,可以在頁面底部找到來源。 我將其分為教程和想法。教程包含資源連結,而想法只是專案的一般描述。我還列出了我最喜歡的初學者清單。 看看,看看是否有什麼啟發你! 教學 == 我的最愛 ---- - [透過 30 個教學在 30 天內建立 30 個東西](https://javascript30.com) - [在 30 分鐘內建立一個簡單的搜尋機器人](https://medium.freecodecamp.org/how-to-build-a-simple-search-bot-in-30-minutes-eb56fcedcdb1) - [使用 Xamarin 和 Visual Studio 建立 iOS 照片庫應用程式](https://www.raywenderlich.com/134049/building-ios-apps-with-xamarin-and-visual-studio) - [建立 Android 手電筒應用程式](https://www.youtube.com/watch?v=dhWL4DC7Krs)(影片) - [製作聊天應用程式](https://medium.freecodecamp.org/how-to-build-a-chat-application-using-react-redux-redux-saga-and-web-sockets-47423e4bc21a) - [使用 React Native 建立 ToDo 應用程式](https://blog.hasura.io/tutorial-fullstack-react-native-with-graphql-and-authentication-18183d13373a) 簡單的 --- - [使用 C# 和 Xamarin 建立空白應用程式(正在進行中)](https://www.intertech.com/Blog/xamarin-tutorial-part-1-create-a-blank-app/) - [使用 Xamarin 和 Visual Studio 建立 iOS 照片庫應用程式](https://www.raywenderlich.com/134049/building-ios-apps-with-xamarin-and-visual-studio) - [建立加載畫面](https://medium.freecodecamp.org/how-to-build-a-delightful-loading-screen-in-5-minutes-847991da509f) - [使用 JS 建立 HTML 計算器](https://medium.freecodecamp.org/how-to-build-an-html-calculator-app-from-scratch-using-javascript-4454b8714b98) - [建立 React Native Todo 應用程式](https://egghead.io/courses/build-a-react-native-todo-application) - 使用 Node.js 編寫 Twitter 機器人 ``` - [Part 1](https://codeburst.io/build-a-simple-twitter-bot-with-node-js-in-just-38-lines-of-code-ed92db9eb078) ``` ``` - [Part 2](https://codeburst.io/build-a-simple-twitter-bot-with-node-js-part-2-do-more-2ef1e039715d) ``` - [建立一個簡單的 RESTFUL Web 應用程式](https://closebrace.com/tutorials/2017-03-02/creating-a-simple-restful-web-app-with-nodejs-express-and-mongodb) - [在 30 分鐘內建立一個簡單的搜尋機器人](https://medium.freecodecamp.org/how-to-build-a-simple-search-bot-in-30-minutes-eb56fcedcdb1) - [建立一個工作抓取 Web 應用程式](https://medium.freecodecamp.org/how-i-built-a-job-scraping-web-app-using-node-js-and-indreed-7fbba124bbdc) - [使用 Python 挖掘 Twitter 資料](https://marcobonzanini.com/2015/03/02/mining-twitter-data-with-python-part-1/) - [使用 Scrapy 和 MongoDB 抓取網站](https://realpython.com/blog/python/web-scraping-with-scrapy-and-mongodb/) - [如何使用 Python 和 Selenium WebDriver 進行抓取](http://www.byperth.com/2018/04/25/guide-web-scraping-101-what-you-need-to-know-and-how-to-scrape-with-python-selenium-webdriver/) - [我應該使用 BeautifulSoup 觀看哪部電影](https://medium.com/@nishantsahoo.in/which-movie-should-i-watch-5c83a3c0f5b1) - [使用 Flask 建立微博](https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world) - 在 Django 中建立部落格 Web 應用程式 ``` - [Part I : Introduction](https://tutorial.djangogirls.org/en/) ``` ``` - [Part II : Extension To Add More Features](https://legacy.gitbook.com/book/djangogirls/django-girls-tutorial-extensions/details) ``` - [選擇您自己的冒險演示](https://www.twilio.com/blog/2015/03/choose-your-own-adventures-presentations-wizard-mode-part-1-of-3.html) - [使用 Flask 和 RethinkDB 建立待辦事項列表](https://realpython.com/blog/python/rethink-flask-a-simple-todo-list-powered-by-flask-and-rethinkdb/) 中等的 --- - [透過建立簡單的 RPG 遊戲來學習 C#](http://scottlilly.com/learn-c-by-building-a-simple-rpg-index/) - [用 C# 創作 Rogue-like 遊戲](https://roguesharp.wordpress.com/) - [使用 Clojure 建構 Twitter 機器人](http://howistart.org/posts/clojure/1/index.html) - [建立拼字檢查器](https://bernhardwenzel.com/articles/clojure-spellchecker/) - [使用 Java 建立簡單的 HTTP 伺服器](http://javarevisited.blogspot.com/2015/06/how-to-create-http-server-in-java-serversocket-example.html) - [建立 Android 手電筒應用程式](https://www.youtube.com/watch?v=dhWL4DC7Krs)(影片) - [建立具有使用者身份驗證的 Spring Boot 應用程式](https://scotch.io/tutorials/build-a-spring-boot-app-with-user-authentication) - [透過 30 個教學在 30 天內建立 30 個東西](https://javascript30.com) - [使用純 JS 建立應用程式](https://medium.com/codingthesmartway-com-blog/pure-javascript-building-a-real-world-application-from-scratch-5213591cfcd6) - [建立無伺服器 React.js 應用程式](http://serverless-stack.com/) - [建立 Trello 克隆](http://codeloveandboards.com/blog/2016/01/04/trello-tribute-with-phoenix-and-react-pt-1/) - [使用 React、Node、MongoDB 和 SocketIO 建立角色投票應用程式](http://sahatyalkabov.com/create-a-character-voting-app-using-react-nodejs-mongodb-and-socketio/) - [React 教學:克隆 Yelp](https://www.fullstackreact.com/articles/react-tutorial-cloning-yelp/) - [使用 React.js 和 Node.js 建立簡單的中型克隆](https://codeburst.io/build-simple-medium-com-on-node-js-and-react-js-a278c5192f47) - [在 JS 中整合 MailChimp](https://medium.freecodecamp.org/how-to-integrate-mailchimp-in-a-javascript-web-app-2a889fb43f6f) - [使用 React Native 建立 ToDo 應用程式](https://blog.hasura.io/tutorial-fullstack-react-native-with-graphql-and-authentication-18183d13373a) - [製作聊天應用程式](https://medium.freecodecamp.org/how-to-build-a-chat-application-using-react-redux-redux-saga-and-web-sockets-47423e4bc21a) - [使用 React Native 建立新聞應用程式](https://medium.freecodecamp.org/create-a-news-app-using-react-native-ced249263627) - [學習 React 的 Webpack](https://medium.freecodecamp.org/learn-webpack-for-react-a36d4cac5060) - [建立您自己的 React 樣板](https://medium.freecodecamp.org/how-to-build-your-own-react-boilerplate-2f8cbbeb9b3f) - [基本 React+Redux 入門教學](https://hackernoon.com/a-basic-react-redux-introductory-tutorial-adcc681eeb5e) - [建立一個預約安排程序](https://hackernoon.com/build-an-appointment-scheduler-using-react-twilio-and-cosmic-js-95377f6d1040) - 使用 Angular 2+ 建立具有離線功能的 Hacker News 用戶端 ``` - [Part 1](https://houssein.me/angular2-hacker-news) ``` ``` - [Part 2](https://houssein.me/progressive-angular-applications) ``` - 帶有 Angular 5 的 ToDo 應用程式 ``` - [Introduction to Angular](http://www.discoversdk.com/blog/intro-to-angular-and-the-evolution-of-the-web) ``` ``` - [Part 1](http://www.discoversdk.com/blog/angular-5-to-do-list-app-part-1) ``` - 帶有 Angular 5 的 ToDo 應用程式 ``` - [Introduction to Angular](http://www.discoversdk.com/blog/intro-to-angular-and-the-evolution-of-the-web) ``` ``` - [Part 1](http://www.discoversdk.com/blog/angular-5-to-do-list-app-part-1) ``` 難的 -- - [建構一個解釋器](http://www.craftinginterpreters.com/)(第 14 章是用 C 寫的) - [用 C 語言寫一個 Shell](https://brennan.io/2015/01/16/write-a-shell-in-c/) - [編寫 FUSE 文件系統](https://www.cs.nmsu.edu/~pfeiffer/fuse-tutorial/) - [建立您自己的文字編輯器](http://viewsourcecode.org/snaptoken/kilo/) - [建立自己的 Lisp](http://www.buildyourownlisp.com/) - [建構 CoreWiki](https://www.youtube.com/playlist?list=PLVMqA0_8O85yC78I4Xj7z48ES48IQBa7p)這是一個 Wiki 風格的內容管理系統,完全用 C# 使用 ASP.NET Core 和 Razor Pages 編寫。您可以[在這裡](https://github.com/csharpfritz/CoreWiki)找到原始程式碼。 - [建構 JIRA 與 Clojure 和 Atlassian Connect 的集成](https://hackernoon.com/building-a-jira-integration-with-clojure-atlassian-connect-506ebd112807) - [建構一個解釋器](http://www.craftinginterpreters.com/)(第 4-13 章是用 Java 寫的) - [使用 Mocha、React、Redux 和 Immutable 透過測試優先開發來建立全端電影投票應用程式](https://teropa.info/blog/2015/09/10/full-stack-redux-tutorial.html) - [使用 React 和 Node 建立 Twitter Stream](https://scotch.io/tutorials/build-a-real-time-twitter-stream-with-node-and-react-js) - 使用 Webtask.io 建立無伺服器 MERN Story 應用程式 ``` - [Part 1](https://scotch.io/tutorials/build-a-serverless-mern-story-app-with-webtask-io-zero-to-deploy-1) ``` ``` - [Part 2](https://scotch.io/tutorials/build-a-serverless-mern-story-app-with-webtask-io-zero-to-deploy-2) ``` - [使用 React + Parcel 建立 Chrome 擴充功能](https://medium.freecodecamp.org/building-chrome-extensions-in-react-parcel-79d0240dd58f) ``` [Testing React App With Pupepeteer and Jest](https://blog.bitsrc.io/testing-your-react-app-with-puppeteer-and-jest-c72b3dfcde59) ``` - [用 React 編寫生命遊戲](https://medium.freecodecamp.org/create-gameoflife-with-react-in-one-hour-8e686a410174) - [建立帶有情感分析的聊天應用程式](https://codeburst.io/build-a-chat-app-with-sentiment-analysis-using-next-js-c43ebf3ea643) - [建立全端 Web 應用程式設置](https://hackernoon.com/full-stack-web-application-using-react-node-js-express-and-webpack-97dbd5b9d708) - 建立隨機報價機 ``` - [Part 1](https://www.youtube.com/watch?v=3QngsWA9IEE) ``` ``` - [Part 2](https://www.youtube.com/watch?v=XnoTmO06OYo) ``` ``` - [Part 3](https://www.youtube.com/watch?v=us51Jne67_I) ``` ``` - [Part 4](https://www.youtube.com/watch?v=iZx7hqHb5MU) ``` ``` - [Part 5](https://www.youtube.com/watch?v=lpba9vBqXl0) ``` ``` - [Part 6](https://www.youtube.com/watch?v=Jvp8j6zrFHE) ``` ``` - [Part 7](https://www.youtube.com/watch?v=M_hFfrN8_PQ) ``` - 使用 Angular 6 建立美麗的現實世界應用程式: ``` - [Part I](https://medium.com/@hamedbaatour/build-a-real-world-beautiful-web-app-with-angular-6-a-to-z-ultimate-guide-2018-part-i-e121dd1d55e) ``` - [使用 BootStrap 4 和 Angular 6 建立響應式佈局](https://medium.com/@tomastrajan/how-to-build-responsive-layouts-with-bootstrap-4-and-angular-6-cfbb108d797b) - [使用 Django 和測試驅動開發建立待辦事項列表](http://www.obeythetestinggoat.com/) - [使用 Python 建立 RESTful 微服務](http://www.skybert.net/python/developing-a-restful-micro-service-in-python/) - [使用 Docker、Flask 和 React 的微服務](https://testdriven.io/) - [使用 Flask 建立簡單的 Web 應用程式](https://pythonspot.com/flask-web-app-with-python/) - [使用 Flask 建立 RESTful API – TDD 方式](https://scotch.io/tutorials/build-a-restful-api-with-flask-the-tdd-way) - [在 20 分鐘內建立 Django API](https://codeburst.io/create-a-django-api-in-under-20-minutes-2a082a60f6f3) 想法 == 簡單的 --- ### 99 瓶 - 建立一個程序,列印歌曲“牆上的 99 瓶啤酒”的每一行。 - 不要使用所有數字的列表,也不要手動輸入所有數字。請改用內建函數。 - 除了短語“取下一個”之外,您不得直接在歌詞中輸入任何數字/數字名稱。 - 請記住,當您還剩下 1 瓶時,「瓶子」一詞將變為單數。 ### 魔術8球 - 模擬神奇的 8 球。 - 允許使用者輸入他們的問題。 - 顯示正在進行的訊息(即“思考”)。 - 建立 20 個回應,並顯示隨機回應。 - 允許用戶提出另一個問題或退出。 - 獎金: ``` - Add a gui. ``` ``` - It must have a box for users to enter the question. ``` ``` - It must have at least 4 buttons: ``` ``` - ask ``` ``` - clear (the text box) ``` ``` - play again ``` ``` - quit (this must close the window) ``` ### 石頭剪刀布遊戲 - 建立一個石頭剪刀布遊戲。 - 讓玩家選擇石頭、剪刀或布。 - 讓計算機選擇它的移動方式。 - 比較選擇並決定誰獲勝。 - 列印結果。 - 子目標: ``` - Give the player the option to play again. ``` ``` - Keep a record of the score (e.g. Player: 3 / Computer: 6). ``` ### 倒數時鐘 - 建立一個程序,允許使用者選擇時間和日期,然後以給定的時間間隔(例如每秒)列印一條訊息,告訴使用者距離所選時間還有多長時間。 - 子目標: ``` - If the selected time has already passed, have the program tell the user to start over. ``` ``` - If your program asks for the year, month, day, hour, etc. separately, allow the user to be able to type in either the month name or its number. ``` ``` - TIP: Making use of built in modules such as time and datetime can change this project from a nightmare into a much simpler task. ``` 中等的 --- ### 番茄計時器 建立一個番茄計時器。 番茄計時器是一種時間管理方法。該技術使用計時器將工作分解為多個時間間隔,通常長度為 25 分鐘,中間間隔短暫的休息。這些間隔被命名為“pomodoros”,是意大利語單字“pomodoro”(番茄)的英文複數形式,以西里洛在大學時使用的番茄形狀的廚房計時器命名。 原始技巧有六個步驟: 決定要完成的任務。 設定番茄計時器(傳統上為 25 分鐘)。 完成任務。 當計時器響起時結束工作並在一張紙上畫上複選標記。 如果您的複選標記少於四個,請短暫休息(3-5 分鐘),然後轉到步驟 2。 四個番茄鐘後,休息較長時間(15-30 分鐘),將複選標記計數重設為零,然後轉到步驟 1。 要了解有關番茄計時器的更多訊息[,請單擊此處](https://en.wikipedia.org/wiki/Pomodoro_Technique) ### 谷歌案例 - 這是一個可以讓你玩英文句子的遊戲。 - 使用者將以任何格式輸入一個句子。(大寫或小寫或兩者的混合) - 程式必須將給定的句子轉換為Google大小寫。什麼是Google大小寫句子風格?\[know\_about\_it\_here:\](這是一種寫作風格,我們將所有小寫字母替換為大寫字母,留下所有單字的首字母)。 - 子目標: ``` - Program must then convert the given sentence in camel case.To know more about camel case ``` ``` [click_here](https://en.wikipedia.org/wiki/Camel_case) ``` ``` - Sentence can be entered with any number of spaces. ``` ### 擲骰子模擬器 - 允許使用者輸入骰子的面數以及應擲骰子的次數。 - 您的程式應該模擬擲骰子並追蹤每個數字出現的次數(這不必顯示)。 - 最後,列印出每個數字出現的次數。 - 子目標: ``` - Adjust your program so that if the user does not type in a number when they need to, the program will keep prompting them to type in a real number until they do so. ``` ``` - Put the program into a loop so that the user can continue to simulate dice rolls without having to restart the entire program. ``` ``` - In addition to printing out how many times each side appeared, also print out the percentage it appeared. If you can, round the percentage to 4 digits total OR two decimal places. ``` - 獎金: ``` - You are about to play a board game, but you realize you don't have any dice. Fortunately you have this program. ``` ``` - 1. Create a program that opens a new window and draws 2 six-sided dice ``` ``` - 2. Allow the user to quit, or roll again ``` ``` - Allow the user to select the number of dice to be drawn on screen(1-4) 2. Add up the total of the dice and display it ``` ### 計算並修復綠雞蛋和火腿 你們有些人可能還記得蘇博士的故事「綠雞蛋和火腿」。對於那些不記得或從未聽說過的人,[這](http://pastebin.com/XMY48CnN)是這個故事。然而,我給你的故事有一個問題——每次使用「我」這個詞時,它都是小寫的。 由於此問題,您的工作是執行以下操作: - 將我給您的故事複製到常規文字檔案中。 - 建立一個程式來通讀故事並在任何時候將字母 i 變為大寫。 (當它也用在 sam-I-am 的名字中時,請務必更改它。) - 讓你的程式建立一個新文件,並讓它正確地寫出故事。 - 印出有多少錯誤被修正。 - 完成後,您應該已經糾正了[這麼多](https://i.imgur.com/GRkj3yz.jpg)錯誤。 難的 -- ### 隨機維基百科文章 如果您曾造訪維基百科,您可能已經注意到螢幕左側有一個指向隨機文章的連結。雖然看到您被帶到哪篇文章可能很有趣,但有時看到文章的名稱會很好,這樣您就可以在聽起來很無聊時跳過它。幸運的是,維基百科有一個 API,允許我們這樣做[點擊這裡](https://en.wikipedia.org/w/api.php?action=query&list=random&rnnamespace=0&rnlimit=10&format=json)。 然而,有一個困境。由於維基百科擁有有關世界各地主題的文章,其中一些文章的標題中包含特殊字元。例如,關於西班牙畫家[埃拉斯托·科爾特斯·華雷斯 (Erasto Cortés Juárez)](https://en.wikipedia.org/wiki/Erasto_Cort%C3%A9s_Ju%C3%A1rez)的文章中就有 é 和 á。如果您查看這篇特定文章的[API](https://en.wikipedia.org/w/api.php?action=query&prop=info&pageids=39608394&inprop=url&format=json) ,您將看到標題是“Erasto Cort\\u00e9s Ju\\u00e1rez”,並且 \\u00e9 和 \\u00e1 正在替換前面提到的兩個字母。 (有關這是什麼的訊息,請首先查看文件中[本頁](https://docs.python.org/2/howto/unicode.html)的前半部分)。為了讓你的程式正常運作,你必須以某種方式處理這個問題。 - 建立一個程序,從官方維基百科 API 中提取標題,然後一一詢問用戶是否願意閱讀該文章。 - 例子: ``` - If the first title is Reddit, then the program should ask something along the lines of "Would you like to read about Reddit?" If the user says yes, then the program should open up the article for the user to read. ``` ``` - HINT: Click [here](https://en.wikipedia.org/wiki?curid=39608394) to see how the article's ID can be used to access the actual article. ``` - 子目標: ``` - As mentioned before, do something about the possibility of unicode appearing in the title. ``` ``` - Whether you want your program to simply filter out these articles or you want to actually turn the codes into readable characters, that's up to you. ``` ``` - Make the program pause once the user has selected an article to read, and allow him or her to continue browsing different article titles once finished reading. ``` ``` - Allow the user to simply press ENTER to be asked about a new article. ``` ### 天氣如何? 如果您想了解 API 的基礎知識,請查看 iamapizza 的[這篇](http://www.reddit.com/r/explainlikeimfive/comments/qowts/eli5_what_is_api/c3z9kok)文章。 - 建立一個程序,從 OpenWeatherMap.org 提取資料並列印有關當前天氣的訊息,例如您居住的地方的最高氣溫、最低氣溫和雨量。 - 子目標: ``` - Print out data for the next 5-7 days so you have a 5 day/week long forecast. ``` ``` - Print the data to another file that you can open up and view at, instead of viewing the information in the command line. ``` ``` - If you know html, write a file that you can print information to so that your project is more interesting. ``` - 尖端: ``` - APIs that are in Json are essentially lists and dictionaries. Remember that to reference something in a list, you must refer to it by what number element it is in the list, and to reference a key in a dictionary, you must refer to it by its name. ``` ``` - Don't like Celsius? Add &units=imperial to the end of the URL of the API to receive your data in Fahrenheit. ``` ### 來源 - https://github.com/tuvtran/project-based-learning - https://github.com/jorgegonzalez/beginner-projects - https://github.com/MunGell/awesome-for-beginners/blob/master/README.md - https://github.com/sarahbohr/AbsoluteBeginnerProjects --- 你怎麼認為?您喜歡透過特定專案進行學習還是不喜歡透過特定專案進行學習? --- 原文出處:https://dev.to/samborick/100-project-ideas-oda

4 分鐘解釋 JWT

https://youtu.be/0WH9oiYMS3M 介紹 == JWT 身份驗證和會話身份驗證是對 Web 應用程式使用者進行身份驗證的方法。 在本文中,我們將解釋 JWT 的詳細資訊、其結構及其優缺點。 JWT 代表**JSON Web Token** ,它是一種常用的無狀態用戶身份驗證[標準](https://datatracker.ietf.org/doc/html/rfc7519),用於以 JSON 格式在用戶端和伺服器之間安全地傳輸資訊。 預設情況下,JWT 已編碼但未加密。 它使用只有伺服器知道的密鑰進行數位簽署。 它可以加密,但在本文中,我們將重點放在簽署的非加密令牌。 JWT 的結構 ======= JSON Web 令牌由以句點分隔的 3 個部分組成。 標頭、有效負載和簽名。 每個部分均採用 Base64 編碼。 ![JWT編碼結構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ur5abitdnsxnmlpzs2pk.png) ### 標頭 標頭由令牌類型(JWT)和使用的簽章演算法(例如 HMAC SHA256 或 RSA)組成。 ``` { "typ": "JWT", "alg": "HS256" } ``` ### 有效載荷 有效負載由聲明組成。 聲明是關於用戶的聲明和附加資料。 例如,我們有令牌的發行時間。 我們也有它的過期時間,因為令牌應該過期。 ``` { "iss": "example_issuer", "sub": "user_id123", "exp": 1644768000, "iat": 1644744000 } ``` ### 簽名 簽名是 JWT 最重要的部分。 它是使用標頭、有效負載和秘密進行計算的,這些資訊將饋送到簽名演算法中使用。 ``` signature = HMAC-SHA256(base64urlEncode(header) + "." + base64urlEncode(payload), secret_salt ) ``` ![JWT 結構解碼](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o72ktulq29hondu66rbe.png) 步驟 == 典型的 JWT 授權流程涉及的步驟如下: **1- 身份驗證:**使用者使用使用者名稱和密碼登錄,或使用 Google 或 Facebook 等。 伺服器驗證提供的憑證。 **2- 令牌產生並將令牌發送到客戶端:**伺服器將產生 JWT 並將其發送到客戶端,客戶端儲存它以供將來使用。 **3-將令牌傳送到伺服器:**當客戶端想要存取伺服器上受保護的資源時,它會在 HTTP 請求的 Authorization 標頭中傳送 JWT。 ``` axios.get(URL, { headers: { 'Authorization': 'Bearer ' + token, }, }) ``` **4-驗證令牌:**伺服器接收請求並透過使用用於簽署的金鑰檢查其簽章來驗證 JWT。 如果 JWT 有效,伺服器會提取其中包含的資訊並使用它來確定使用者有權執行哪些操作。 **5-授權請求:**如果使用者被授權存取資源,伺服器將傳回請求的資料。如果用戶未獲得授權,伺服器將傳回錯誤訊息。 ![智威湯遜流程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igvye1880i9491jbz525.png) **優點** ====== ![智威湯遜優勢](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mg76chqzkpoz8pb7o9dt.png) - 輕的 - 便攜式:可在多個平台、網路和行動平台上處理。 - JSON 解析器在大多數程式語言中都很常見。 - 由於密鑰儲存在伺服器端,因此可以防止篡改。 - 由於 JWT 令牌的無狀態性質,伺服器不需要儲存任何會話資訊。 缺點 == ![缺點](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmvzoi46hyd3wt8t6hpp.png) - 在伺服器端,應在登出時手動將未過期的 JWT 標記為無效。 即使 JWT 從客戶端刪除後仍然有效。 ![JWT 仍然有效](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fdrm27cehhd0bck5djzt.png) 使用的方法是令牌黑名單,它涉及在伺服器端維護無效令牌的列表,以防止它們重新用於身份驗證。 ![令牌黑名單](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wultrfhdmneg7yoqtyv8.png) - 對於簽署的非加密令牌,我們不應該儲存敏感訊息,因為 JWT 可以防止篡改,但任何人都可以解碼和讀取。 - 如果被攔截,可以提供完全存取權。這就是為什麼客戶端的 JWT 應該儲存在安全的地方,例如在瀏覽器中的 HttpOnly cookie 中。 --- 原文出處:https://dev.to/jaypmedia/jwt-explained-in-4-minutes-with-visuals-g3n

學習 Rust:一個乾淨的開始

我決定是時候學習[Rust 了](https://www.rust-lang.org/),為了保持自己的動力,我將在這裡記錄學習的進展。 ![費里斯螃蟹](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0xl5mafhbjgfuowu62t.png) 有關於我的一些;我是一名 Web 開發人員,雖然已經涉足多年,但已經從事了大約 5 年。我有使用[Perl](https://www.perl.org/)和[PHP](https://www.php.net/)的經驗,但我的日常工作是 JavaScript/TypeScript,無論是透過[NodeJS](https://nodejs.org/en)還是[ReactJS](https://react.dev/) 。我想學習 Rust 沒有什麼特別的原因,只是學習新事物很有趣。 我的第一個停靠點是Google `learn rust` ,這引導我找到了[「這本書」](https://doc.rust-lang.org/book/) 。這本書是 Rust 社群為新手(或所謂的 Rustlings)編寫的入門指南,旨在「紮實掌握這門語言」。 公共學習 ---- 我選擇公開記錄我的 Rust 學習之旅,因為我相信公開學習的力量。透過分享我的成功、挑戰和見解,我將加強自己的理解,並希望為其他走類似道路的人提供資源。 我親眼目睹了這種方法的價值。我邀請讀者提供回饋、更正和貢獻。雖然我認識到公共學習並不適合所有人,但我個人發現它非常有益,並希望激勵其他人考慮它。那麼,讓我們深入學習這些課程。 第 1 課“入門” --------- 本課分為 3 個部分: - 安裝 - 你好世界! - 你好,貨物! ### 安裝 看到列出的安裝,我鬆了口氣,我擔心我必須查找如何安裝 Rust。我使用的是 Windows 計算機,但決定在 Linux 中學習 Rust,因此我將透過 WSL 使用 Ubuntu。 安裝指令看起來很簡單,它使用curl來下載一些東西,然後透過sh進行管道傳輸,所以我們可以假設下載的專案是某種bash腳本。 ``` curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh ``` 不管你信不信,這是我犯下的第一個錯誤。我看到`Rust is installed now. Great!`訊息並繼續下一課。如果我繼續閱讀下去,我會發現我需要單獨安裝編譯器。 > Linux 使用者通常應該根據其發行版的文件安裝 GCC 或 Clang。例如,如果您使用 Ubuntu,則可以安裝 build-essential 套件。 不過,這很容易解決,我很快就回到了正軌。 ``` sudo apt install build-essential ``` ### 你好世界! 下一部分是開發社群的主要內容,即深受喜愛的“Hello, World!”例子。 ![你好世界](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2navm68azq2jbk9mcsf.png) 我在這裡學到了一些東西,函數是用`fn`關鍵字聲明的,任何 Rust 應用程式的入口點都是`main.rs`檔案中的`main`函數,標準命名約定是使用下劃線來分隔函數和檔案名稱中的單字。 正是在這個階段,我發現我沒有安裝編譯器,我認為這是像這樣的簡單部分的真正原因,以確保我們都設定正確。 ### 你好,貨物! 上一節很簡單,這節也很簡單,但向我們介紹了[Cargo](https://crates.io/) ,它是 Rust 的套件管理器,作為一個 JS 開發者,我的腦海裡直接想到了 NPM。 Cargo 允許我們做一些很酷的事情: - 為我們的包命名。 - 新增包依賴項。 - 用一個命令執行我們的程式。 - 使用除錯模式和發布模式來建立我們的程式。 - 檢查我們的程式是否編譯,但沒有實際建立它。 這個範例讓我們重新創造我們的`Hello, World!`例如但以貨運方式。程式碼非常簡單,幾乎不值得展示,但它就是這樣。 ``` fn main() { println!("Hello, world!"); } ``` 第 2 課“猜謎遊戲” ----------- 第二課沒有任何小節,本課的目標是編寫一個猜謎遊戲,用戶輸入一個數字,我們將其與隨機選擇的數字進行比較,遊戲繼續,直到用戶猜出確切的數字。 我們仍然沒有做任何突破性的事情,但從列印靜態文字到動態獲取用戶輸入並返回結果的進展仍然很好。 ### VS程式碼 正是在這一點上,我決定在`nano`中進行程式碼變更不是一個好主意,我需要在 VSCode 中開啟專案。我加入了一些擴展,希望能讓開發變得更容易。這些是[rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 、[板條箱](https://marketplace.visualstudio.com/items?itemName=serayuzgur.crates)和[Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) 。你可以使用任何你喜歡的編輯器,我只是習慣了 VSCode。 ![VSCode 標誌](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rq9m8kkxv6milsxbv5zw.png) ### 製作遊戲 讓我們來看看遊戲教程,它讓我們使用貨物來設定專案,並很快向我們介紹了一些新概念 - `use`關鍵字。 - 可變變數。 - 錯誤處理。 - 文件位置 #### `use`關鍵字 `use`關鍵字允許我們從其他庫中提取程式碼,作為一名 Web 開發人員,我想將其與[import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)進行比較。預設情況下,Rust 將能夠存取「標準」庫中的一組專案,這稱為前奏,但如果您想存取其他任何內容,則必須使用`use` 。 在他們給出的範例中,我們確實`use std::io;`它允許我們存取`io`命名空間,這確實感覺有點奇怪,因為我們已經可以存取`std` ,這意味著`std::io`也可以存取。 #### 可變變數 在 JavaScript 領域,我們有不可變變數和可變變數的概念,它們是`const`和`let` ,其中`const`是不可變的,而`let`不是。 Rust 有點不同,因為除非另有說明,否則所有變數都是不可變的,變數關鍵字也總是`let` ,或者至少據我所知到目前為止是這樣。 ``` let mut var1 = String::new(); // mutable let mut var2 = String::from("Test String"); // mutable let var3 = 6; // immutable ``` 這本書讓我們知道,第三課將回歸可變性。 #### 錯誤處理 我們介紹了兩種類型的錯誤處理`.expect` ,它們不會嘗試任何類型的恢復,但會在應用程式崩潰和`match`時發布一條訊息。 `Match`從函數中取得`Result` ,然後允許您根據`Result`呼叫函數。在範例中,我們給出了`parse`並告訴它要么是`Ok`要么是`Err` ,在`match`中我們可以定義一個在這兩種情況下呼叫的函數。我假設當我們開始處理更多樣化的函數時,match 將能夠處理所有`Result`類型。 #### 文件位置 這是迄今為止我最喜歡 Rust 的部分,我知道它不應該那麼令人興奮,但我認為它是。當您執行命令`cargo doc` Cargo 時,Cargo 將掃描您正在使用的所有程式碼,並產生解釋功能以及如何使用它們的說明頁面。 目前還沒有太多解釋,但我希望這些文件是從程式碼中的註解產生的,即使這不是那種情況,可以自我記錄的程式碼庫對我來說是如此有趣。 ### 偏離了人跡罕至的地方 此時,我已經完成了前兩課,並決定對猜謎遊戲進行一些更改。我將遊戲循環提取到它自己的函數中,並加入了解析失敗的錯誤訊息。 我不喜歡的一件事是這條線的`magic` 。 ``` let guess: u32 = match guess.trim().parse() ``` 我不喜歡這種感覺,就像 parse 神奇地知道它的目標類型一樣。所以我閱讀了 VSCode 中的解析工具提示,它教導了有關`turbofish`語法的內容。我不知道人們是否不喜歡這種語法,或者這本書的作者是否認為它對於初學者來說太複雜,但在我看來,它更有意義。我們告訴 parse 我們想要什麼類型,然後我們的`let`從中推論出類型,而不是相反。 ``` let guess = match guess.trim().parse::<u32>() ``` 這是修改後的程式碼。 {% 嵌入 https://replit.com/@andrewb05/Guessing-game %} 註銷 -- 感謝您與我一起踏上這段旅程。我計劃繼續這個系列並涵蓋整本書。如果您想關注,可以按下「關注」按鈕以獲得新帖子的通知。 正如我之前所說,請隨意留下任何反饋,如果您也在公開學習,請在評論中留下您的系列的連結,以便我可以查看。 非常感謝您的閱讀。如果您想在開發之外與我聯繫,這裡有我的[Twitter](https://twitter.com/Link2Twenty)和[linkedin,](https://www.linkedin.com/in/andrew-bone-ba241b179/)歡迎來打個招呼 😊。 --- 原文出處:https://dev.to/link2twenty/learning-rust-a-clean-start-4eom

您需要了解的有關軟體要求的訊息

想像一下,您的客戶要求您建立一個 Web 應用程式,以滿足其客戶的所有需求。這就是你的客戶會告訴你的全部內容,你能建造它嗎?很明顯,您錯過了更多訊息,並且存在要求,但您知道什麼是要求嗎?如果我們沒有明確定義它們,會產生什麼後果?存在哪些類型的需求?本文將嘗試以簡單的方式回答其中一些問題。 概括 -- - [什麼是要求?](#what-is-a-requirement) - [為什麼它們很重要以及需求定義不明確的後果](#why-they-matters-and-consequence-of-poorly-defined-requirements) - [我們怎樣才能有一個好的需求](#how-we-can-have-a-good-requirement) - [要求等級](#levels-of-requirements) ``` - [User requirements](#user-requirements) ``` ``` - [System requirements](#system-requirements) ``` - [要求類型](#types-of-requirements) ``` - [Functional requirements](#functional-requirements) ``` ``` - [Non-functional requirements](#non-functional-requirements) ``` - [結論](#conclusion) 什麼是要求? ------ 首先,我們需要了解軟體開發環境中的需求是什麼。 Shari Pleeger 給我們下了一個很好的定義: > 系統特徵或系統為實現其目標而能夠完成的事情的描述 換句話說,要求是辨識產品或流程操作、功能或設計約束或特徵的聲明,該聲明是**明確的**、**可**測試的、**可測量**的,並且是接受產品或流程所必需的(由消費者或內部品質保證指南) 。 每個需求都是基於現實中存在的問題而建立的,而軟體只是解決它的一種方法。 為什麼它們很重要以及需求定義不明確的後果 -------------------- 現在我們知道什麼是要求,我們必須問: > “為什麼對於從事 IT 工作的人來說了解它如此重要?” 我會給你一些原因。根據要求,我們能夠: - 為專案規劃提供依據 - 對於研究變更請求至關重要 - 允許從開發的最早階段進行風險管理 - 它們是驗收測試的基礎 - 合約管理 ![圖片1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x13z95rmek1u9g1bhnu4.png) 如果我們的需求定義不明確會發生什麼事? 第一個也是合乎邏輯的後果是專案交付的延遲。當我們開始一個專案或衝刺時,沒有對要開發的內容有所有明確的要求,存在錯誤的可能性是巨大的!因此,開發人員和 QA 團隊需要承擔更多工作,從而降低了他們的生活品質。 如果出現在Production中,就會有許多不滿意的用戶,讓產品的信心降低,甚至導致系統報廢。另一個後果是維護系統的成本。 我們怎樣才能有一個好的需求 ------------- 需求的編寫方式因團隊而異,但它們都應該具有以下三個特徵: - 明確 - 可測試 - 可測量的 要求等級 ---- ### 用戶要求 使用者需求定義了我們的軟體需要做什麼。它可以透過使用者的需求、期望、限制和介面來描述使用者面臨的問題。 此類需求是為客戶編寫的,我們使用自然語言加圖表。我們可以使用待辦事項清單應用程式的使用者需求範例: > 使用者應該能夠在清單中插入新任務 ### 系統需求 系統需求是定義系統有效運作所需的硬體、軟體和網路元件的功能和約束的規格。 這些要求對於設計、實施和維護系統至關重要。他們負責描述必須做什麼,而不是如何做。 使用我們已經使用過的相同要求,我們可以詳細了解它: |用戶需求|系統需求 | | -------------------------------------------------- ------ | -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------------ | |使用者應該能夠在清單中插入新任務 | - 使用者應該能夠點擊插入按鈕以新增任務 \- 當使用者點擊按鈕時,任務新增應出現在清單中 插入 \- 只有登入使用者才能存取任務清單| 要求類型 ---- ### 功能要求 功能需求描述了一個系統應該執行的操作。它們描述了系統應該提供什麼、系統應該如何對特定輸入做出反應以及系統在特定情況下應該如何表現。他們還可以描述系統不應該做什麼。 功能需求可以描述使用者或系統需求。 ### 非功能性需求 非功能性需求描述系統或環境應提供的服務或功能。他們負責定義系統的架構。我們對非功能性需求有不同的類別,它們是: 1. **表現:** ``` - _Response Time:_ Specifies the maximum acceptable time for the system to respond to user input. ``` ``` - _Throughput:_ Defines the number of transactions or operations the system can handle in a given time. ``` 2. **可用性:** ``` - _User Interface (UI) and User Experience (UX):_ Specifies criteria related to the design, ease of use, and overall user experience. ``` ``` - _Accessibility:_ Ensures that the system is usable by individuals with disabilities. ``` 3. **可靠性:** ``` - _Availability:_ Specifies the percentage of time the system should be operational. ``` ``` - _Fault Tolerance:_ Defines the system's ability to continue functioning in the presence of faults or errors. ``` 4. **安全:** ``` - _Authentication:_ Describes how users are identified and verified. ``` ``` - _Authorization:_ Specifies the level of access granted to different users or roles. ``` ``` - _Data Encryption:_ Requires the use of encryption to protect sensitive data. ``` 5. **可擴充性:** ``` - _Horizontal Scalability:_ Describes how well the system can handle an increase in load by adding more hardware. ``` ``` - _Vertical Scalability:_ Describes how well the system can handle an increase in load by increasing the capacity of existing hardware. ``` 6. **相容性:** ``` - _Hardware Compatibility:_ Ensures the system can run on specified hardware configurations. ``` ``` - _Software Compatibility:_ Ensures the system can work with specified software components. ``` 7. **可維護性:** ``` - _Modifiability:_ Describes the ease with which the system can be modified or updated. ``` ``` - _Testability:_ Specifies the ease with which the system can be tested to ensure its correctness. ``` 8. **可移植性:** ``` - _Platform Independence:_ Describes the ability of the system to run on different operating systems. ``` ``` - _Data Portability:_ Ensures that data can be easily transferred between different systems. ``` 9. **文件:** ``` - _User Documentation:_ Specifies the requirements for user manuals and guides. ``` ``` - _Technical Documentation:_ Describes the requirements for system architecture, API documentation, etc. ``` 10. **監理與合規:** ``` - _Legal Requirements:_ Ensures that the system complies with relevant laws and regulations. ``` ``` - _Industry Standards:_ Specifies adherence to industry-specific standards. ``` 結論 -- 正如我們所看到的,軟體需求對我們來說非常重要,並且有很多不同類型的需求,但最重要的是確保在開始開發之前,我們已經很好地定義了它們。 我希望這些內容對您有用。 如果您有任何疑問,請隨時與我聯繫! 親吻💅🏼 --- 原文出處:https://dev.to/m4rri4nne/what-you-need-to-know-about-software-requirements-2hc0

使用外掛程式和主題在 OhMyZsh 和 Hyper 上設定自動完成功能的初學者指南!

您的普通 bash 可能具有您通常需要的功能,但**如果您是常規終端用戶,zsh 將改變您鍵入命令的方式。** zsh、ohmyzsh 和 hyper 一起提供的功能將讓您大吃一驚。 > 您知道您可以從終端控制 Spotify 嗎?是的,超級插件可以讓您做到這一點。 對於初學者來說,設定這些東西可能會讓人不知所措,所以這裡有一個非常簡單的入門指南! 🤩 --- 🔥 簡介 ---- 如果您使用的是如下所示的常規終端,則您會錯過 OhMyZsh 提供的許多功能。 ![Flaviocope 的 MacOS 終端](https://flaviocopes.com/macos-terminal-setup/Screenshot%202019-01-29%20at%2018.34.04.png) 今天,您將進行終端改造,使其看起來像這樣... ![我的超級終端](https://i.ibb.co/DW05RzF/Hyper-Terminal-Kumar-Abhirup.jpg) 不僅僅是外觀,OhMyZsh 還具有豐富的功能來點亮您的程式設計之旅。 在教程結束時,這就是您可以在終端機中執行的操作... - NPM、Git 自動完成 - 在終端機中輸入時自動建議 - 語法高亮顯示指令是否已定義 - 使用遊標編輯終端命令 - 查看目前目錄的`git branch`和`git status` - 開啟與目前分頁相同目錄的新分頁 - 使用 OhMyZsh 功能,例如不使用`cd`進行導航、使用`ll` 、更簡單的基於 Tab 鍵單擊的導航等等! --- ❤️ 開始吧 ------ 首先,您必須安裝`zsh` 。在某些情況下(取決於您正在執行的作業系統),它可能已經安裝。因此,請透過在終端機中執行`zsh --version`檢查它是否已安裝。 `zsh`在不同作業系統的安裝過程有所不同。查看[Zsh 安裝指南](https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH)來安裝 zsh。 安裝 Zsh 後,請確保將其設為預設 shell。為此,請在終端機中執行以下命令。 ``` $ sudo chsh -s $(which zsh) ``` 登出並登入回預設 shell。執行`echo $SHELL`並預期輸出`/bin/zsh`或類似內容。 --- 🔰 安裝 OhMyZsh ------------ > 請注意,zsh 和 OhMyZsh 是不同的。 透過在終端機中執行以下命令來安裝`OhMyZsh` 。 ``` $ sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" ``` 當您安裝 OhMyZsh 時,它會附帶許多插件來幫助您! 若要新增實用的插件,請在 TextEdit/Notepad/Vim/VSCode 中開啟`~/.zshrc` 。 在檔案中看到的插件清單中,只需新增一個名為`npm`的插件,如下所示 👇 ``` plugins=( git bundler dotenv osx rake rbenv ruby npm # you added this ) ``` 瞧!您已經完成了 OhMyZsh!若要查看更改,請在終端機中執行`source ~/.zshrc` ,現在您就擁有了 OhMyZsh shell 的功能。 --- 🔰 依時間安裝 HyperTerm ----------------- Zeit(now.sh 和 Next.js 的建立者)為我們建立了一個很棒的終端應用程式,它是用 Electron 建置的。 從[這裡](https://hyper.is/)下載 Hyper。 --- ### ⚛️ 使用 OhMyZsh 設定 Hyper 打開超級終端機。您不會看到 OhMyZsh 在那裡執行。因此,請轉到超級設定。在 OSX 上,它是`Hyper > Preferences` 。 這將在您最喜歡的編輯器中開啟一個`.hyper.js`檔案。該文件包含您的終端的所有設置,非常容易控制! 若要在 Hyper 中啟用 OhMyZsh 作為預設 shell,請在`.hyper.js`中進行此變更 👇 ``` - shell: '/bin/bash' + shell: '/bin/zsh' ``` **這將使 OhMyZsh 成為您的預設超級終端 shell!** --- ### 🤩 輸入指令時自動完成 Git 將`zsh-autocomplete`插件複製到 OhMyZsh 插件資料夾中。 ``` $ sudo git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions ``` 完成後,將外掛程式新增至`~/.zshrc`檔案的外掛程式清單中。 ``` plugins=( ... zsh-autosuggestions ) ``` --- ### 🎉 Zsh 語法高亮 Git 將`zsh-syntax-highlighting`外掛程式克隆到 OhMyZsh 外掛程式資料夾中。 ``` $ sudo git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting ``` 並再次將其新增至`.zshrc`檔案的外掛程式清單。 ``` plugins=( ... zsh-syntax-highlighting ) ``` > 注意:若要反映您所做的每項更改,請在終端機中執行`source ~/.zshrc` 。 --- ### 📯 啟用 Hyper 相關功能與主題 透過切換超級終端的設定來開啟`.hyper.js` 。 請查看`plugins: [...]`部分並將這些插件名稱貼到此處。 ``` plugins: [ ... 'hypercwd', 'hyper-statusline', 'hyper-alt-click', 'hyperterm-safepaste', 'hyper-search', 'hypergoogle', 'hyperborder', 'hyper-tab-icons', 'hyper-hide-title', 'shades-of-purple-hyper' ], ``` 儲存文件,Hyper 會自動為您安裝這些外掛程式和主題。要反映更改,只需關閉並再次啟動超級終端即可。 萬歲!**現在,您的終端機中已擁有本 DEV.to 文章開頭列出的所有功能。** --- 獎勵:在 VSCode 中為整合終端設定相同的終端配置 --------------------------- 在 VSCode 設定中,新增以下 JSON 鍵值對,然後就可以開始了! ``` { ... "terminal.integrated.shell.osx": "/bin/zsh", "terminal.integrated.fontSize": 16 } ``` **就是這樣,夥計們!** --- 🔥 資源 ---- - <https://ohmyz.sh> - <https://hyper.is> --- 🏆 關於我 ----- **我是 Kumar Abhirup,一位來自印度的 16 歲 JavaScript React 開發人員,每天都在學習新事物。** [在 Twitter 上與我聯絡 🐦](https://twitter.com/kumar_abhirup) [我的個人網站和作品集🖥️](https://kumar.now.sh) *請在下面評論您更好的方法以及改進本文的建議。 :)* --- 原文出處:https://dev.to/kumareth/a-beginner-s-guide-for-setting-up-autocomplete-on-ohmyzsh-hyper-with-plugins-themes-47f2

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

您可以使用無數的框架和函式庫來改進您的全端應用程式。 我們將介紹令人興奮的概念,例如應用程式內通知、使用 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

如何將 Google Maps API 與 React.js 結合使用

![同學好](https://media.giphy.com/media/3ohjV1r9VfQt0JLKzS/giphy.gif) 在嘗試在個人react.js專案中實作Google地圖API時,我遇到了幾個非常複雜且令人困惑的範例。這是我如何在我的應用程式中使用 Google 地圖的簡短範例! 首先,事情第一! -------- 前往[Google 地圖 API](https://developers.google.com/maps/documentation/)頁面,註冊並取得令牌以供使用!您必須輸入信用卡號才能接收令牌。然而,谷歌聲稱,如果您不親自升級服務,他們不會向您的帳戶收取費用。**請自行決定是否繼續**。 獲得 API 金鑰後,您就可以開始建立您的應用程式了! ### 建立您的 react 應用程式 ``` npm init react-app my-app ``` ### 安裝依賴項 ``` npm install --save google-maps-react ``` 這就是我們如何將谷歌地圖作為一個元件來獲取! 檢查您的 package.json 檔案以確保它已安裝! 初始設定完成後,您就可以開始編碼了! 1.導入google-maps-react! ---------------------- ``` import { Map, GoogleApiWrapper } from 'google-maps-react'; ``` 2. 將地圖元件加入渲染函數中! ---------------- ``` render() { return ( <Map google={this.props.google} zoom={8} style={mapStyles} initialCenter={{ lat: 47.444, lng: -122.176}} /> ); } ``` 3. 編輯您的匯出預設語句 ------------- ``` export default GoogleApiWrapper({ apiKey: 'TOKEN HERE' })(MapContainer); ``` 請務必在此處插入您的 API 金鑰! 4.加入樣式 ------ 如果您願意,可以變更一些樣式屬性。我在課堂外將其作為常變數。 ``` const mapStyles = { width: '100%', height: '100%', }; ``` 5. 啟動你的伺服器! ----------- ![谷歌地圖 API](https://i.ibb.co/47ZsVrd/Screen-Shot-2019-04-24-at-10-14-55-PM.png) 偉大的!你做到了!但說實話,沒有任何標記的地圖有什麼意義!那麼讓我們來加入一些吧! 6. 標記它! ------- ``` import { Map, GoogleApiWrapper, Marker } from 'google-maps-react'; ``` 更新您的地圖元件以包含標記元件! ``` render() { return ( <Map google={this.props.google} zoom={8} style={mapStyles} initialCenter={{ lat: 47.444, lng: -122.176}} > <Marker position={{ lat: 48.00, lng: -122.00}} /> </Map> ); } ``` 然後你就會擁有這個! ![帶有標記的谷歌地圖](https://i.ibb.co/RDWCvDg/Screen-Shot-2019-04-24-at-10-30-40-PM.png) 讓我們加入更多! -------- 您可以透過程式設計方式循環狀態來顯示地點,而不是新增一個標記。在我的範例中,我展示了該地區的一些舊貨店。您也可以為它們加入事件,例如 onClick! ``` export class MapContainer extends Component { constructor(props) { super(props); this.state = { stores: [{lat: 47.49855629475769, lng: -122.14184416996333}, {latitude: 47.359423, longitude: -122.021071}, {latitude: 47.2052192687988, longitude: -121.988426208496}, {latitude: 47.6307081, longitude: -122.1434325}, {latitude: 47.3084488, longitude: -122.2140121}, {latitude: 47.5524695, longitude: -122.0425407}] } } displayMarkers = () => { return this.state.stores.map((store, index) => { return <Marker key={index} id={index} position={{ lat: store.latitude, lng: store.longitude }} onClick={() => console.log("You clicked me!")} /> }) } render() { return ( <Map google={this.props.google} zoom={8} style={mapStyles} initialCenter={{ lat: 47.444, lng: -122.176}} > {this.displayMarkers()} </Map> ); } } ``` 這就是大家! ![帶有許多標記的谷歌地圖](https://i.ibb.co/BZL3qtb/Screen-Shot-2019-04-24-at-11-00-26-PM.png) 我希望本教程有助於建立您自己的應用程式! --- 原文出處:https://dev.to/jessicabetts/how-to-use-google-maps-api-and-react-js-26c2

6 個 JavaScript 控制台方法,例如泰勒絲民間傳說歌詞

--- 標題:6 個 JavaScript 控制台方法,如泰勒絲民間傳說歌詞 發表:真實 描述:了解六種鮮為人知的 JavaScript 控制台方法,這些方法與泰勒絲 (Taylor Swift) 最新專輯《Followre》中的歌詞類似。 標籤: javascript、webdev、除錯、生產力 封面圖片:https://twilio-cms-prod.s3.amazonaws.com/images/ts\_AMVvYGp.width-1616.png --- 這篇部落格文章是為[Twilio](https://www.twilio.com/)撰寫的,[最初發佈在 Twilio 部落格上](https://www.twilio.com/blog/js-console-methods-like-taylor-swift-folklore-lyrics)。 如果您進行 Web 開發,您可能至少使用過一次`console.log` (或超過一千次...誰在數?),因為這是最好的偵錯方法!但您知道還有其他控制台方法嗎? Taylor Swift 的最新專輯《Followre》 充滿了沉思的隱喻、典故和象徵意義,這篇文章將其中一些歌詞比作 6 個鮮為人知的 JavaScript `console`方法。 ### 控制台到底是什麼? `console`是一個[全域物件](https://developer.mozilla.org/en-US/docs/Glossary/Global_object),允許開發人員存取偵錯控制台。它有大量的方法,可以更輕鬆地記錄語句、變數、函數、錯誤等——天哪! #### 6種類似民間傳說歌詞的控制台方法 #### *1. console.log = "但這會很有趣 // 如果你是那個人"* `console.log`是最常用的方法。用於通用日誌記錄,它在 Web 控制台中顯示傳遞給它的訊息。你知道你可以用 CSS 來裝飾它嗎? ``` console.log("%cWARNING: you will be obsessed with folklore", "font: 2em sans-serif; color: yellow; background-color: red;"); ``` ![輸出console.log css](https://twilio-cms-prod.s3.amazonaws.com/images/Yz6zzAsmqMlZsQPirgqJZ1U1MWR_pPvMUExuwPavf-Orm.width-1000.png) `Log`簡單、可靠,並且可以完成工作,但它被過度使用,吸引了類似`console`方法的所有註意力,這些方法做得更多。如果`Log`是您需要的一種或唯一的控制台方法,那麼它會很有趣 - 但正如本文將要展示的,您將透過其他`console`方法獲得更多樂趣! #### *2. console.table = "我是一個鏡球 // 今晚我會向你展示你自己的每個版本"* `table`方法接受一個物件或陣列並將輸入記錄為表格,使其看起來更乾淨:它就像`log`的更好版本。與鏡像球一樣, `table`可以透過接受可選參數`columns`來選擇要顯示的列子集來顯示不同版本的輸入。 陣列中的每個元素(如果資料是物件,則為每個可枚舉屬性)將是表中的一行。下面的 JavaScript 程式碼有一個物件,您可以看到最初使用 log 的輸出。 ``` function Album(name, year, numSongs) { this.name = name; this.year = year; this.numSongs = numSongs; } const fearless = new Album("Fearless", 2008, 13); const speakNow = new Album("Speak Now", 2010, 19); const folklore = new Album("folklore", 2020, 16); console.log([fearless, speakNow, folklore]); ``` ![物件的console.log](https://twilio-cms-prod.s3.amazonaws.com/images/4w8wxLh0fE0C0YQ1-lO-MdqRmLx_QjFj-ng-UcbM-C8Q2.width-1000.png) 這很好,但是給定陣列時`table`的輸出看起來更好: ``` console.table([fearless, speakNow, folklore]); ``` ![物件的console.table](https://twilio-cms-prod.s3.amazonaws.com/images/MqAnPKv4jrbZVF4Z7-vf37jkzC6N4ppYKS_-pFH0eXuov.width-1000.png) 接受`columns`參數,如`console.table([fearless, speakNow, folklore], ["name"]);`會顯示: ![帶有額外列參數的 console.table](https://twilio-cms-prod.s3.amazonaws.com/images/UTQQlcite2SYJZMXWuKc_p0yetEku7jPZLGtsmBc5aTGY.width-1000.png) 您也可以傳遞它(而不是`name` ) `year`或`numSongs` - 就像鏡子球一樣,表格可以向您顯示其輸入的每個版本! #### *3. console.assert =“如果你從不流血,你就永遠不會成長”* `console.assert(expression, message)`僅在表達式為 false 時才列印。泰勒絲 (Taylor Swift) 在歌曲*《The 1》*中的歌詞「如果你從不流血,你就永遠不會成長」也同意這一點——如果你從不流血,或者失敗,或者有時不正確,你就永遠不會成長。 `assert`表明,透過錯誤,您可以成長為一名開發人員,因為您可以修復錯誤,控制台會透過將斷言變成漂亮的紅色來幫助您。 ``` const numFolkloreSongs = 16; const num1989Songs = 13; console.assert(numFolkloreSongs > num1989Songs, 'folklore has more songs than 1989'); //won't run console.assert(num1989Songs + 3 > numFolkloreSongs, 'the number of songs on 1989 + 3 is not greater than the number of folklore songs'); ``` ![斷言失敗](https://twilio-cms-prod.s3.amazonaws.com/images/6kyCsGK8UWI0nEEgZ703RHjPu67krRq-OCmTgkDuiJtYw.width-1000.png) #### *4. console.time/console.timeEnd = "時間,神秘的時間/切開我,然後治癒我。"* `console.time()`建立一個計時器來查看某些操作需要多長時間。它可以採用名稱或標籤的可選參數來區分網頁上最多 10,000 個計時器。 `console.timeEnd()`停止計時器,並在控制台中顯示結果。 時間可能很艱難——它可以讓你心碎,但它也可以治癒你,讓你感覺更好。 ``` console.time('sms timer'); let x = 0; while (x < 3) { console.log("They told me all of my cages were mental/So I got wasted like all my potential"); x+=1; } console.timeEnd('sms timer'); ``` ![控制台時間](https://twilio-cms-prod.s3.amazonaws.com/images/C0ExSxgVLyLR6uuPM911Lhw64QJ3qLZXDWKbrLdJ5FwnD.width-1000.png) 如果沒有標籤傳遞給`console.time()` ,它將記錄 default 而不是*sms time* 。 #### *5. console.clear:“如果我對你來說已經死了,為什麼你還在守靈?”* `console.clear`簡短、甜蜜、簡潔。它會清除控制台,並且在某些環境中,可能會列印諸如“控制台已清除”之類的訊息。 歌詞“如果我對你來說已經死了,為什麼你還在守靈?”是憂鬱的,但也有一些尖銳的味道:它非常適合當你想要結束對話時,就像`clear`一樣,你可以重新開始,重新開始。 #### *6. console.group/console.groupEnd ="想到一直以來有一條看不見的繩子把你和我綁在一起,這不是很美好嗎?"* `console.group`表示內聯訊息群組的開始, `console.groupEnd`標記其結束。如果群組包含日誌,它們將作為一個群組列印,因此格式更清晰,您可以更輕鬆地了解群組包含的內容。 就像有一些看不見的字串(或`console`命令)將群組中的專案捆綁在一起。 ``` console.group("folklore"); console.log("the 1"); console.log("cardigan"); console.log("the last great american dynasty"); console.log("invisible string"); console.log("my tears ricochet"); console.groupEnd(); console.log("outside"); ``` ![團體](https://twilio-cms-prod.s3.amazonaws.com/images/JiqlDPAtBRz7G_ExfwKLEhsOXwAMZKIRS-Bv3YaL9SKGb.width-1000.png) #### 控制台的下一步是什麼? ![tswift 太棒了 gif](https://twilio-cms-prod.s3.amazonaws.com/original_images/tsgif.gif) 還有許多其他控制台方法未包含在此處(部分原因是它們與 Taylor Swift 歌詞的關係不大。)有關控制台方法的更多訊息,[請查看 Mozilla 開發者網絡有關 Web 技術的文件](https://developer.mozilla.org/en-US/docs/Web/API/console)。讓我知道您最喜歡或最不喜歡的民俗歌曲在線或在評論中! 1. 推特: [@lizziepika](https://twitter.com/lizziepika) 2. GitHub:[伊莉莎白西格爾](https://github.com/elizabethsiegle) 3. 電子郵件:[email protected] --- 原文出處:https://dev.to/twilio/6-javascript-console-methods-like-taylor-swift-folklore-lyrics-3h0k

為初學者到專家提供的 101 個 Bash 指令和提示

> **2019 年 9 月 25 日更新:**感謝[ラナ・kuaru](https://twitter.com/rana_kualu)的辛勤工作,本文現已提供日文版。請點擊下面的連結查看他們的工作。如果您知道本文被翻譯成其他語言,請告訴我,我會將其發佈在這裡。 [🇯🇵 閱讀日語](https://qiita.com/rana_kualu/items/7b62898d373901466f5c) > **2019 年 7 月 8 日更新:**我最近發現大約兩年前發佈在法語留言板上的[這篇非常相似的文章](https://bookmarks.ecyseo.net/?EAWvDw)。如果您有興趣學習一些 shell 命令——並且您*會說 français* ,那麼它是對我下面的文章的一個很好的補充。 直到大約一年前,我幾乎只在 macOS 和 Ubuntu 作業系統中工作。在這兩個作業系統上, `bash`是我的預設 shell。在過去的六、七年裡,我對`bash`工作原理有了大致的了解,並想為那些剛入門的人概述一些更常見/有用的命令。如果您認為您了解有關`bash`所有訊息,請無論如何看看下面的內容 - 我已經提供了一些提示和您可能忘記的標誌的提醒,這可以讓您的工作更輕鬆一些。 下面的命令或多或少以敘述風格排列,因此如果您剛開始使用`bash` ,您可以從頭到尾完成操作。事情到最後通常會變得不那麼常見並且變得更加困難。 <a name="toc"></a> 目錄 -- - [基礎](#the-basics) ``` - [First Commands, Navigating the Filesystem](#first-commands) ``` ``` - [`pwd / ls / cd`](#pwd-ls-cd) ``` ``` - [`; / && / &`](#semicolon-andand-and) ``` ``` - [Getting Help](#getting-help) ``` ``` - [`-h`](#minus-h) ``` ``` - [`man`](#man) ``` ``` - [Viewing and Editing Files](#viewing-and-editing-files) ``` ``` - [`head / tail / cat / less`](#head-tail-cat-less) ``` ``` - [`nano / nedit`](#nano-nedit) ``` ``` - [Creating and Deleting Files and Directories](#creating-and-deleting-files) ``` ``` - [`touch`](#touch) ``` ``` - [`mkdir / rm / rmdir`](#mkdir-rm-rmdir) ``` ``` - [Moving and Copying Files, Making Links, Command History](#moving-and-copying-files) ``` ``` - [`mv / cp / ln`](#mv-cp-ln) ``` ``` - [Command History](#command-history) ``` ``` - [Directory Trees, Disk Usage, and Processes](#directory-trees-disk-usage-processes) ``` ``` - [`mkdir –p / tree`](#mkdir--p-tree) ``` ``` - [`df / du / ps`](#df-du-ps) ``` ``` - [Miscellaneous](#basic-misc) ``` ``` - [`passwd / logout / exit`](#passwd-logout-exit) ``` ``` - [`clear / *`](#clear-glob) ``` - [中間的](#intermediate) ``` - [Disk, Memory, and Processor Usage](#disk-memory-processor) ``` ``` - [`ncdu`](#ncdu) ``` ``` - [`top / htop`](#top-htop) ``` ``` - [REPLs and Software Versions](#REPLs-software-versions) ``` ``` - [REPLs](#REPLs) ``` ``` - [`-version / --version / -v`](#version) ``` ``` - [Environment Variables and Aliases](#env-vars-aliases) ``` ``` - [Environment Variables](#env-vars) ``` ``` - [Aliases](#aliases) ``` ``` - [Basic `bash` Scripting](#basic-bash-scripting) ``` ``` - [`bash` Scripts](#bash-scripts) ``` ``` - [Custom Prompt and `ls`](#custom-prompt-ls) ``` ``` - [Config Files](#config-files) ``` ``` - [Config Files / `.bashrc`](#config-bashrc) ``` ``` - [Types of Shells](#types-of-shells) ``` ``` - [Finding Things](#finding-things) ``` ``` - [`whereis / which / whatis`](#whereis-which-whatis) ``` ``` - [`locate / find`](#locate-find) ``` ``` - [Downloading Things](#downloading-things) ``` ``` - [`ping / wget / curl`](#ping-wget-curl) ``` ``` - [`apt / gunzip / tar / gzip`](#apt-gunzip-tar-gzip) ``` ``` - [Redirecting Input and Output](#redirecting-io) ``` ``` - [`| / > / < / echo / printf`](#pipe-gt-lt-echo-printf) ``` ``` - [`0 / 1 / 2 / tee`](#std-tee) ``` - [先進的](#advanced) ``` - [Superuser](#superuser) ``` ``` - [`sudo / su`](#sudo-su) ``` ``` - [`!!`](#click-click) ``` ``` - [File Permissions](#file-permissions) ``` ``` - [File Permissions](#file-permissions-sub) ``` ``` - [`chmod / chown`](#chmod-chown) ``` ``` - [User and Group Management](#users-groups) ``` ``` - [Users](#users) ``` ``` - [Groups](#groups) ``` ``` - [Text Processing](#text-processing) ``` ``` - [`uniq / sort / diff / cmp`](#uniq-sort-diff-cmp) ``` ``` - [`cut / sed`](#cut-sed) ``` ``` - [Pattern Matching](#pattern-matching) ``` ``` - [`grep`](#grep) ``` ``` - [`awk`](#awk) ``` ``` - [Copying Files Over `ssh`](#ssh) ``` ``` - [`ssh / scp`](#ssh-scp) ``` ``` - [`rsync`](#rsync) ``` ``` - [Long-Running Processes](#long-running-processes) ``` ``` - [`yes / nohup / ps / kill`](#yes-nohup-ps-kill) ``` ``` - [`cron / crontab / >>`](#cron) ``` ``` - [Miscellaneous](#advanced-misc) ``` ``` - [`pushd / popd`](#pushd-popd) ``` ``` - [`xdg-open`](#xdg-open) ``` ``` - [`xargs`](#xargs) ``` - [獎勵:有趣但大多無用的東西](#bonus) ``` - [`w / write / wall / lynx`](#w-write-wall-lynx) ``` ``` - [`nautilus / date / cal / bc`](#nautilus-date-cal-bc) ``` --- <a name="the-basics"></a> 基礎 == <a name="first-commands"></a> 第一個指令,瀏覽檔案系統 ------------ 現代檔案系統具有目錄(資料夾)樹,其中目錄要么是*根目錄*(沒有父目錄),要么是*子目錄*(包含在單一其他目錄中,我們稱之為“父目錄”)。向後遍歷檔案樹(從子目錄到父目錄)將始終到達根目錄。有些檔案系統有多個根目錄(如 Windows 的磁碟機: `C:\` 、 `A:\`等),但 Unix 和類別 Unix 系統只有一個名為`\`的根目錄。 <a name="pwd-ls-cd"></a> ### `pwd / ls / cd` [\[ 返回目錄 \]](#toc) 在檔案系統中工作時,使用者始終*在*某個目錄中工作,我們稱之為當前目錄或*工作目錄*。使用`pwd`列印使用者的工作目錄: ``` [ andrew@pc01 ~ ]$ pwd /home/andrew ``` 使用`ls`列出該目錄的內容(檔案和/或子目錄等): ``` [ andrew@pc01 ~ ]$ ls Git TEST jdoc test test.file ``` > **獎金:** > > 使用`ls -a`顯示隱藏(“點”)文件 > > 使用`ls -l`顯示文件詳細訊息 > > 組合多個標誌,如`ls -l -a` > > 有時您可以連結諸如`ls -la`之類的標誌,而不是`ls -l -a` 使用`cd`更改到不同的目錄(更改目錄): ``` [ andrew@pc01 ~ ]$ cd TEST/ [ andrew@pc01 TEST ]$ pwd /home/andrew/TEST [ andrew@pc01 TEST ]$ cd A [ andrew@pc01 A ]$ pwd /home/andrew/TEST/A ``` `cd ..`是「 `cd`到父目錄」的簡寫: ``` [ andrew@pc01 A ]$ cd .. [ andrew@pc01 TEST ]$ pwd /home/andrew/TEST ``` `cd ~`或只是`cd`是「 `cd`到我的主目錄」的簡寫(通常`/home/username`或類似的東西): ``` [ andrew@pc01 TEST ]$ cd [ andrew@pc01 ~ ]$ pwd /home/andrew ``` > **獎金:** > > `cd ~user`表示「 `cd`到`user`的主目錄 > > 您可以使用`cd ../..`等跳轉多個目錄等級。 > > 使用`cd -`返回到最近的目錄 > > `.`是「此目錄」的簡寫,因此`cd .`不會做太多事情 <a name="semicolon-andand-and"></a> ### `; / && / &` [\[ 返回目錄 \]](#toc) 我們在命令列中輸入的內容稱為*命令*,它們總是執行儲存在電腦上某處的一些機器碼。有時這個機器碼是一個內建的Linux命令,有時它是一個應用程式,有時它是你自己寫的一些程式碼。有時,我們會想依序執行一個指令。為此,我們可以使用`;` (分號): ``` [ andrew@pc01 ~ ]$ ls; pwd Git TEST jdoc test test.file /home/andrew ``` 上面的分號表示我首先 ( `ls` ) 列出工作目錄的內容,然後 ( `pwd` ) 列印其位置。連結命令的另一個有用工具是`&&` 。使用`&&`時,如果左側命令失敗,則右側命令將不會執行。 `;`和`&&`都可以在同一行中多次使用: ``` # whoops! I made a typo here! [ andrew@pc01 ~ ]$ cd /Giit/Parser && pwd && ls && cd -bash: cd: /Giit/Parser: No such file or directory # the first command passes now, so the following commands are run [ andrew@pc01 ~ ]$ cd Git/Parser/ && pwd && ls && cd /home/andrew/Git/Parser README.md doc.sh pom.xml resource run.sh shell.sh source src target ``` ....但是與`;` ,即使第一個命令失敗,第二個命令也會執行: ``` # pwd and ls still run, even though the cd command failed [ andrew@pc01 ~ ]$ cd /Giit/Parser ; pwd ; ls -bash: cd: /Giit/Parser: No such file or directory /home/andrew Git TEST jdoc test test.file ``` `&`看起來與`&&`類似,但實際上實現了完全不同的功能。通常,當您執行長時間執行的命令時,命令列將等待該命令完成,然後才允許您輸入另一個命令。在命令後面加上`&`可以防止這種情況發生,並允許您在舊命令仍在執行時執行新命令: ``` [ andrew@pc01 ~ ]$ cd Git/Parser && mvn package & cd [1] 9263 ``` > **額外的好處:**當我們在命令後使用`&`來「隱藏」它時,我們說該作業(或「進程」;這些術語或多或少可以互換)是「後台的」。若要查看目前正在執行的背景作業,請使用`jobs`指令: > ````bash \[ andrew@pc01 ~ \]$ 職位 \[1\]+ 執行 cd Git/Parser/ && mvn package & ``` <a name="getting-help"></a> ## Getting Help <a name="minus-h"></a> ### `-h` [[ Back to Table of Contents ]](#toc) Type `-h` or `--help` after almost any command to bring up a help menu for that command: ``` \[ andrew@pc01 ~ \]$ du --help 用法:你\[選項\]...\[檔案\]... 或: du \[選項\]... --files0-from=F 對目錄遞歸地總結文件集的磁碟使用情況。 長期權的強制性參數對於短期權也是強制性的。 -0, --null 以 NUL 結束每個輸出行,而不是換行符 -a, --all 計算所有檔案的寫入計數,而不僅僅是目錄 ``` --apparent-size print apparent sizes, rather than disk usage; although ``` ``` the apparent size is usually smaller, it may be ``` ``` larger due to holes in ('sparse') files, internal ``` ``` fragmentation, indirect blocks, and the like ``` -B, --block-size=SIZE 在列印前按 SIZE 縮放大小;例如, ``` '-BM' prints sizes in units of 1,048,576 bytes; ``` ``` see SIZE format below ``` … ``` <a name="man"></a> ### `man` [[ Back to Table of Contents ]](#toc) Type `man` before almost any command to bring up a manual for that command (quit `man` with `q`): ``` LS(1) 使用者指令 LS(1) 姓名 ``` ls - list directory contents ``` 概要 ``` ls [OPTION]... [FILE]... ``` 描述 ``` List information about the FILEs (the current directory by default). ``` ``` Sort entries alphabetically if none of -cftuvSUX nor --sort is speci- ``` ``` fied. ``` ``` Mandatory arguments to long options are mandatory for short options ``` ``` too. ``` … ``` <a name="viewing-and-editing-files"></a> ## Viewing and Editing Files <a name="head-tail-cat-less"></a> ### `head / tail / cat / less` [[ Back to Table of Contents ]](#toc) `head` outputs the first few lines of a file. The `-n` flag specifies the number of lines to show (the default is 10): ``` 列印前三行 ===== \[ andrew@pc01 ~ \]$ 頭 -n 3 c 這 文件 有 ``` `tail` outputs the last few lines of a file. You can get the last `n` lines (like above), or you can get the end of the file beginning from the `N`-th line with `tail -n +N`: ``` 從第 4 行開始列印文件末尾 ============== \[ andrew@pc01 ~ \]$ tail -n +4 c 確切地 六 線 ``` `cat` concatenates a list of files and sends them to the standard output stream (usually the terminal). `cat` can be used with just a single file, or multiple files, and is often used to quickly view them. (**Be warned**: if you use `cat` in this way, you may be accused of a [_Useless Use of Cat_ (UUOC)](http://bit.ly/2SPHE4V), but it's not that big of a deal, so don't worry too much about it.) ``` \[ andrew@pc01 ~ \]$ 貓 a 歸檔一個 \[ andrew@pc01 ~ \]$ 貓 ab 歸檔一個 文件b ``` `less` is another tool for quickly viewing a file -- it opens up a `vim`-like read-only window. (Yes, there is a command called `more`, but `less` -- unintuitively -- offers a superset of the functionality of `more` and is recommended over it.) Learn more (or less?) about [less](http://man7.org/linux/man-pages/man1/less.1.html) and [more](http://man7.org/linux/man-pages/man1/more.1.html) at their `man` pages. <a name="nano-nedit"></a> ### `nano / nedit` [[ Back to Table of Contents ]](#toc) `nano` is a minimalistic command-line text editor. It's a great editor for beginners or people who don't want to learn a million shortcuts. It was more than sufficient for me for the first few years of my coding career (I'm only now starting to look into more powerful editors, mainly because defining your own syntax highlighting in `nano` can be a bit of a pain.) `nedit` is a small graphical editor, it opens up an X Window and allows point-and-click editing, drag-and-drop, syntax highlighting and more. I use `nedit` sometimes when I want to make small changes to a script and re-run it over and over. Other common CLI (command-line interface) / GUI (graphical user interface) editors include `emacs`, `vi`, `vim`, `gedit`, Notepad++, Atom, and lots more. Some cool ones that I've played around with (and can endorse) include Micro, Light Table, and VS Code. All modern editors offer basic conveniences like search and replace, syntax highlighting, and so on. `vi(m)` and `emacs` have more features than `nano` and `nedit`, but they have a much steeper learning curve. Try a few different editors out and find one that works for you! <a name="creating-and-deleting-files"></a> ## Creating and Deleting Files and Directories <a name="touch"></a> ### `touch` [[ Back to Table of Contents ]](#toc) `touch` was created to modify file timestamps, but it can also be used to quickly create an empty file. You can create a new file by opening it with a text editor, like `nano`: ``` \[ andrew@pc01 前 \]$ ls \[ andrew@pc01 ex \]$ 奈米 a ``` _...editing file..._ ``` \[ andrew@pc01 前 \]$ ls A ``` ...or by simply using `touch`: ``` \[ andrew@pc01 ex \]$ touch b && ls ab ``` > **Bonus**: > > Background a process with \^z (Ctrl+z) > > ```bash > [ andrew@pc01 ex ]$ nano a > ``` > > _...editing file, then hit \^z..._ > > ```bash > Use fg to return to nano > > [1]+ Stopped nano a > [ andrew@pc01 ex ]$ fg > ``` > > _...editing file again..._ --- > **Double Bonus:** > > Kill the current (foreground) process by pressing \^c (Ctrl+c) while it’s running > > Kill a background process with `kill %N` where `N` is the job index shown by the `jobs` command <a name="mkdir-rm-rmdir"></a> ### `mkdir / rm / rmdir` [[ Back to Table of Contents ]](#toc) `mkdir` is used to create new, empty directories: ``` \[ andrew@pc01 ex \]$ ls && mkdir c && ls ab ABC ``` You can remove any file with `rm` -- but be careful, this is non-recoverable! ``` \[ andrew@pc01 ex \]$ rm a && ls 西元前 ``` You can add an _"are you sure?"_ prompt with the `-i` flag: ``` \[ andrew@pc01 前 \]$ rm -ib rm:刪除常規空文件“b”? y ``` Remove an empty directory with `rmdir`. If you `ls -a` in an empty directory, you should only see a reference to the directory itself (`.`) and a reference to its parent directory (`..`): ``` \[ andrew@pc01 ex \]$ rmdir c && ls -a 。 .. ``` `rmdir` removes empty directories only: ``` \[ andrew@pc01 ex \]$ cd .. && ls 測試/ \*.txt 0.txt 1.txt a a.txt bc \[ andrew@pc01 ~ \]$ rmdir 測試/ rmdir:無法刪除“test/”:目錄不為空 ``` ...but you can remove a directory -- and all of its contents -- with `rm -rf` (`-r` = recursive, `-f` = force): ``` \[ andrew@pc01 ~ \]$ rm –rf 測試 ``` <a name="moving-and-copying-files"></a> ## Moving and Copying Files, Making Links, Command History <a name="mv-cp-ln"></a> ### `mv / cp / ln` [[ Back to Table of Contents ]](#toc) `mv` moves / renames a file. You can `mv` a file to a new directory and keep the same file name or `mv` a file to a "new file" (rename it): ``` \[ andrew@pc01 ex \]$ ls && mv ae && ls A B C D BCDE ``` `cp` copies a file: ``` \[ andrew@pc01 ex \]$ cp e e2 && ls BCDE E2 ``` `ln` creates a hard link to a file: ``` ln 的第一個參數是 TARGET,第二個參數是 NEW LINK ================================= \[ andrew@pc01 ex \]$ ln bf && ls bcde e2 f ``` `ln -s` creates a soft link to a file: ``` \[ andrew@pc01 ex \]$ ln -sbg && ls BCDE E2 FG ``` Hard links reference the same actual bytes in memory which contain a file, while soft links refer to the original file name, which itself points to those bytes. [You can read more about soft vs. hard links here.](http://bit.ly/2D0W8cN) <a name="command-history"></a> ### Command History [[ Back to Table of Contents ]](#toc) `bash` has two big features to help you complete and re-run commands, the first is _tab completion_. Simply type the first part of a command, hit the \<tab\> key, and let the terminal guess what you're trying to do: ``` \[ andrew@pc01 目錄 \]$ ls 另一個長檔名 這是一個長檔名 一個新檔名 \[ andrew@pc01 目錄 \]$ ls t ``` _...hit the TAB key after typing `ls t` and the command is completed..._ ``` \[ andrew@pc01 dir \]$ ls 這是檔名 這是長檔名 ``` You may have to hit \<TAB\> multiple times if there's an ambiguity: ``` \[ andrew@pc01 目錄 \]$ ls a \[ andrew@pc01 目錄 \]$ ls an 一個新檔名另一個長檔名 ``` `bash` keeps a short history of the commands you've typed previously and lets you search through those commands by typing \^r (Ctrl+r): ``` \[ andrew@pc01 目錄 \] ``` _...hit \^r (Ctrl+r) to search the command history..._ ``` (反向搜尋)``: ``` _...type 'anew' and the last command containing this is found..._ ``` (reverse-i-search)`anew': 觸碰新檔名 ``` <a name="directory-trees-disk-usage-processes"></a> ## Directory Trees, Disk Usage, and Processes <a name="mkdir--p-tree"></a> ### `mkdir –p / tree` [[ Back to Table of Contents ]](#toc) `mkdir`, by default, only makes a single directory. This means that if, for instance, directory `d/e` doesn't exist, then `d/e/f` can't be made with `mkdir` by itself: ``` \[ andrew@pc01 ex \]$ ls && mkdir d/e/f ABC mkdir:無法建立目錄「d/e/f」:沒有這樣的檔案或目錄 ``` But if we pass the `-p` flag to `mkdir`, it will make all directories in the path if they don't already exist: ``` \[ andrew@pc01 ex \]$ mkdir -pd/e/f && ls A B C D ``` `tree` can help you better visualise a directory's structure by printing a nicely-formatted directory tree. By default, it prints the entire tree structure (beginning with the specified directory), but you can restrict it to a certain number of levels with the `-L` flag: ``` \[ andrew@pc01 前 \]$ 樹 -L 2 。 |-- 一個 |-- b |-- c `--d ``` `--e ``` 3個目錄,2個文件 ``` You can hide empty directories in `tree`'s output with `--prune`. Note that this also removes "recursively empty" directories, or directories which aren't empty _per se_, but which contain only other empty directories, or other recursively empty directories: ``` \[ andrew@pc01 ex \]$ 樹 --prune 。 |-- 一個 `--b ``` <a name="df-du-ps"></a> ### `df / du / ps` [[ Back to Table of Contents ]](#toc) `df` is used to show how much space is taken up by files for the disks or your system (hard drives, etc.). ``` \[ andrew@pc01 前 \]$ df -h 已使用的檔案系統大小 可用 使用% 安裝於 udev 126G 0 126G 0% /dev tmpfs 26G 2.0G 24G 8% /執行 /dev/mapper/ubuntu--vg-root 1.6T 1.3T 252G 84% / … ``` In the above command, `-h` doesn't mean "help", but "human-readable". Some commands use this convention to display file / disk sizes with `K` for kilobytes, `G` for gigabytes, and so on, instead of writing out a gigantic integer number of bytes. `du` shows file space usage for a particular directory and its subdirectories. If you want to know how much space is free on a given hard drive, use `df`; if you want to know how much space a directory is taking up, use `du`: ``` \[ andrew@pc01 ex \]$ 你 4 ./d/e/f 8./d/e 12 ./天 4./c 20 . ``` `du` takes a `--max-depth=N` flag, which only shows directories `N` levels down (or fewer) from the specified directory: ``` \[ andrew@pc01 ex \]$ du -h --max-深度=1 12K./天 4.0K./c 20K。 ``` `ps` shows all of the user's currently-running processes (aka. jobs): ``` \[ andrew@pc01 前 \]$ ps PID TTY 時間 CMD 16642 分/15 00:00:00 ps 25409 點/15 00:00:00 重擊 ``` <a name="basic-misc"></a> ## Miscellaneous <a name="passwd-logout-exit"></a> ### `passwd / logout / exit` [[ Back to Table of Contents ]](#toc) Change your account password with `passwd`. It will ask for your current password for verification, then ask you to enter the new password twice, so you don't make any typos: ``` \[ andrew@pc01 目錄 \]$ 密碼 更改安德魯的密碼。 (目前)UNIX 密碼: 輸入新的 UNIX 密碼: 重新輸入新的 UNIX 密碼: passwd:密碼更新成功 ``` `logout` exits a shell you’ve logged in to (where you have a user account): ``` \[ andrew@pc01 目錄 \]$ 註銷 ────────────────────────────────────────────────── ── ────────────────────────────── 會話已停止 ``` - Press <return> to exit tab ``` ``` - Press R to restart session ``` ``` - Press S to save terminal output to file ``` ``` `exit` exits any kind of shell: ``` \[ andrew@pc01 ~ \]$ 退出 登出 ────────────────────────────────────────────────── ── ────────────────────────────── 會話已停止 ``` - Press <return> to exit tab ``` ``` - Press R to restart session ``` ``` - Press S to save terminal output to file ``` ``` <a name="clear-glob"></a> ### `clear / *` [[ Back to Table of Contents ]](#toc) Run `clear` to move the current terminal line to the top of the screen. This command just adds blank lines below the current prompt line. It's good for clearing your workspace. Use the glob (`*`, aka. Kleene Star, aka. wildcard) when looking for files. Notice the difference between the following two commands: ``` \[ andrew@pc01 ~ \]$ ls Git/Parser/source/ PArrayUtils.java PFile.java PSQLFile.java PWatchman.java PDateTimeUtils.java PFixedWidthFile.java PStringUtils.java PXSVFile.java PDelimitedFile.java PNode.java PTextFile.java Parser.java \[ andrew@pc01 ~ \]$ ls Git/Parser/source/PD\* Git/Parser/source/PDateTimeUtils.java Git/Parser/source/PDelimitedFile.java ``` The glob can be used multiple times in a command and matches zero or more characers: ``` \[ andrew@pc01 ~ \]$ ls Git/Parser/source/P *D* m\* Git/Parser/source/PDateTimeUtils.java Git/Parser/source/PDelimitedFile.java ``` <a name="intermediate"></a> # Intermediate <a name="disk-memory-processor"></a> ## Disk, Memory, and Processor Usage <a name="ncdu"></a> ### `ncdu` [[ Back to Table of Contents ]](#toc) `ncdu` (NCurses Disk Usage) provides a navigable overview of file space usage, like an improved `du`. It opens a read-only `vim`-like window (press `q` to quit): ``` \[ andrew@pc01 ~ \]$ ncdu ncdu 1.11 ~ 使用箭頭鍵導航,按 ?求助 \--- /home/安德魯 ------------------------------------------- ------------------ 148.2 MiB \[##########\] /.m2 91.5 MiB \[######\] /.sbt 79.8 MiB \[######\] /.cache 64.9 MiB \[####\] /.ivy2 40.6 MiB \[##\] /.sdkman 30.2 MiB \[##\] /.local 27.4 MiB \[#\] /.mozilla 24.4 MiB \[#\] /.nanobackups 10.2 MiB \[ \] .confout3.txt ``` 8.4 MiB [ ] /.config ``` ``` 5.9 MiB [ ] /.nbi ``` ``` 5.8 MiB [ ] /.oh-my-zsh ``` ``` 4.3 MiB [ ] /Git ``` ``` 3.7 MiB [ ] /.myshell ``` ``` 1.7 MiB [ ] /jdoc ``` ``` 1.5 MiB [ ] .confout2.txt ``` ``` 1.5 MiB [ ] /.netbeans ``` ``` 1.1 MiB [ ] /.jenv ``` 564.0 KiB \[ \] /.rstudio-desktop 磁碟使用總量:552.7 MiB 表觀大小:523.6 MiB 專案:14618 ``` <a name="top-htop"></a> ### `top / htop` [[ Back to Table of Contents ]](#toc) `top` displays all currently-running processes and their owners, memory usage, and more. `htop` is an improved, interactive `top`. (Note: you can pass the `-u username` flag to restrict the displayed processes to only those owner by `username`.) ``` \[ andrew@pc01 ~ \]$ htop 1 \[ 0.0%\] 9 \[ 0.0%\] 17 \[ 0.0%\] 25 \[ 0.0%\] 2 \[ 0.0%\] 10 \[ 0.0%\] 18 \[ 0.0%\] 26 \[ 0.0%\] 3 \[ 0.0%\] 11 \[ 0.0%\] 19 \[ 0.0%\] 27 \[ 0.0%\] 4 \[ 0.0%\] 12 \[ 0.0%\] 20 \[ 0.0%\] 28 \[ 0.0%\] 5 \[ 0.0%\] 13 \[ 0.0%\] 21 \[| 1.3%\] 29 \[ 0.0%\] 6 \[ 0.0%\] 14 \[ 0.0%\] 22 \[ 0.0%\] 30 \[| 0.6%\] 7 \[ 0.0%\] 15 \[ 0.0%\] 23 \[ 0.0%\] 31 \[ 0.0%\] 8 \[ 0.0%\] 16 \[ 0.0%\] 24 \[ 0.0%\] 32 \[ 0.0%\] Mem\[|||||||||||||||||||1.42G/252G\] 任務:188、366 個; 1 執行 交換電壓\[| 2.47G/256G\]平均負載:0.00 0.00 0.00 ``` Uptime: 432 days(!), 00:03:55 ``` PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ 指令 9389 安德魯 20 0 23344 3848 2848 R 1.3 0.0 0:00.10 htop 10103 根 20 0 3216M 17896 2444 S 0.7 0.0 5h48:56 /usr/bin/dockerd ``` 1 root 20 0 181M 4604 2972 S 0.0 0.0 15:29.66 /lib/systemd/syst ``` 533 根 20 0 44676 6908 6716 S 0.0 0.0 11:19.77 /lib/systemd/syst 546 根 20 0 244M 0 0 S 0.0 0.0 0:01.39 /sbin/lvmetad -f 1526 根 20 0 329M 2252 1916 S 0.0 0.0 0:00.00 /usr/sbin/ModemMa 1544 根 20 0 329M 2252 1916 S 0.0 0.0 0:00.06 /usr/sbin/ModemMa F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit ``` <a name="REPLs-software-versions"></a> ## REPLs and Software Versions <a name="REPLs"></a> ### REPLs [[ Back to Table of Contents ]](#toc) A **REPL** is a Read-Evaluate-Print Loop, similar to the command line, but usually used for particular programming languages. You can open the Python REPL with the `python` command (and quit with the `quit()` function): ``` \[ andrew@pc01 ~ \]$ python Python 3.5.2(默認,2018 年 11 月 12 日,13:43:14)... > > > 辭職() ``` Open the R REPL with the `R` command (and quit with the `q()` function): ``` \[ andrew@pc01 ~ \]$ R R版3.5.2(2018-12-20)--「蛋殼冰屋」... > q() 儲存工作區影像? \[是/否/c\]: 否 ``` Open the Scala REPL with the `scala` command (and quit with the `:quit` command): ``` \[ andrew@pc01 ~ \]$ scala 歡迎使用 Scala 2.11.12 ... 斯卡拉>:退出 ``` Open the Java REPL with the `jshell` command (and quit with the `/exit` command): ``` \[ andrew@pc01 ~ \]$ jshell |歡迎使用 JShell——版本 11.0.1 ... jshell> /退出 ``` Alternatively, you can exit any of these REPLs with \^d (Ctrl+d). \^d is the EOF (end of file) marker on Unix and signifies the end of input. <a name="version"></a> ### `-version / --version / -v` [[ Back to Table of Contents ]](#toc) Most commands and programs have a `-version` or `--version` flag which gives the software version of that command or program. Most applications make this information easily available: ``` \[ andrew@pc01 ~ \]$ ls --version ls (GNU coreutils) 8.25 ... \[ andrew@pc01 ~ \]$ ncdu -版本 NCDU 1.11 \[ andrew@pc01 ~ \]$ python --version Python 3.5.2 ``` ...but some are less intuitive: ``` \[ andrew@pc01 ~ \]$ sbt scalaVersion … \[資訊\]2.12.4 ``` Note that some programs use `-v` as a version flag, while others use `-v` to mean "verbose", which will run the application while printing lots of diagnostic or debugging information: ``` SCP(1) BSD 通用指令手冊 SCP(1) 姓名 ``` scp -- secure copy (remote file copy program) ``` … -v 詳細模式。導致 scp 和 ssh(1) 列印偵錯訊息 ``` about their progress. This is helpful in debugging connection, ``` ``` authentication, and configuration problems. ``` … ``` <a name="env-vars-aliases"></a> ## Environment Variables and Aliases <a name="env-vars"></a> ### Environment Variables [[ Back to Table of Contents ]](#toc) **Environment variables** (sometimes shortened to "env vars") are persistent variables that can be created and used within your `bash` shell. They are defined with an equals sign (`=`) and used with a dollar sign (`$`). You can see all currently-defined env vars with `printenv`: ``` \[ andrew@pc01 ~ \]$ printenv SPARK\_HOME=/usr/local/spark 術語=xterm … ``` Set a new environment variable with an `=` sign (don't put any spaces before or after the `=`, though!): ``` \[ andrew@pc01 ~ \]$ myvar=你好 ``` Print a specific env var to the terminal with `echo` and a preceding `$` sign: ``` \[ andrew@pc01 ~ \]$ echo $myvar 你好 ``` Environment variables which contain spaces or other whitespace should be surrounded by quotes (`"..."`). Note that reassigning a value to an env var overwrites it without warning: ``` \[ andrew@pc01 ~ \]$ myvar="你好,世界!" && 回顯 $myvar 你好世界! ``` Env vars can also be defined using the `export` command. When defined this way, they will also be available to sub-processes (commands called from this shell): ``` \[ andrew@pc01 ~ \]$ export myvar="另一" && echo $myvar 另一個 ``` You can unset an environment variable by leaving the right-hand side of the `=` blank or by using the `unset` command: ``` \[ andrew@pc01 ~ \]$ 取消設定 mynewvar \[ andrew@pc01 ~ \]$ echo $mynewvar ``` <a name="aliases"></a> ### Aliases [[ Back to Table of Contents ]](#toc) **Aliases** are similar to environment variables but are usually used in a different way -- to replace long commands with shorter ones: ``` \[ andrew@pc01 apidocs \]$ ls -l -a -h -t 總計 220K drwxr-xr-x 5 安德魯 安德魯 4.0K 12 月 21 日 12:37 。 -rw-r--r-- 1 安德魯 安德魯 9.9K 十二月 21 12:37 help-doc.html -rw-r--r-- 1 安德魯 安德魯 4.5K 12 月 21 日 12:37 script.js … \[ andrew@pc01 apidocs \]$ 別名 lc="ls -l -a -h -t" \[ andrew@pc01 apidocs \]$ lc 總計 220K drwxr-xr-x 5 安德魯 安德魯 4.0K 12 月 21 日 12:37 。 -rw-r--r-- 1 安德魯 安德魯 9.9K 十二月 21 12:37 help-doc.html -rw-r--r-- 1 安德魯 安德魯 4.5K 12 月 21 日 12:37 script.js … ``` You can remove an alias with `unalias`: ``` \[ andrew@pc01 apidocs \]$ unalias lc \[ andrew@pc01 apidocs \]$ lc 目前未安裝程式“lc”。 … ``` > **Bonus:** > > [Read about the subtle differences between environment variables and aliases here.](http://bit.ly/2TDG8Tx) > > [Some programs, like **git**, allow you to define aliases specifically for that software.](http://bit.ly/2TG8X1A) <a name="basic-bash-scripting"></a> ## Basic `bash` Scripting <a name="bash-scripts"></a> ### `bash` Scripts [[ Back to Table of Contents ]](#toc) `bash` scripts (usually ending in `.sh`) allow you to automate complicated processes, packaging them into reusable functions. A `bash` script can contain any number of normal shell commands: ``` \[ andrew@pc01 ~ \]$ echo "ls && touch file && ls" > ex.sh ``` A shell script can be executed with the `source` command or the `sh` command: ``` \[ andrew@pc01 ~ \]$ 源 ex.sh 桌面 Git TEST c ex.sh 專案測試 桌面 Git TEST c ex.sh 檔案專案測試 ``` Shell scripts can be made executable with the `chmod` command (more on this later): ``` \[ andrew@pc01 ~ \]$ echo "ls && touch file2 && ls" > ex2.sh \[ andrew@pc01 ~ \]$ chmod +x ex2.sh ``` An executable shell script can be run by preceding it with `./`: ``` \[ andrew@pc01 ~ \]$ ./ex2.sh 桌面 Git TEST c ex.sh ex2.sh 檔案專案測試 桌面 Git TEST c ex.sh ex2.sh 檔案 file2 專案測試 ``` Long lines of code can be split by ending a command with `\`: ``` \[ andrew@pc01 ~ \]$ echo "for i in {1..3}; do echo \\ > \\"歡迎\\$i次\\";完成” > ex3.sh ``` Bash scripts can contain loops, functions, and more! ``` \[ andrew@pc01 ~ \]$ 源 ex3.sh 歡迎1次 歡迎2次 歡迎3次 ``` <a name="custom-prompt-ls"></a> ### Custom Prompt and `ls` [[ Back to Table of Contents ]](#toc) Bash scripting can make your life a whole lot easier and more colourful. [Check out this great bash scripting cheat sheet.](https://devhints.io/bash) `$PS1` (Prompt String 1) is the environment variable that defines your main shell prompt ([learn about the other prompts here](http://bit.ly/2SPgsmT)): ``` \[ andrew@pc01 ~ \]$ printf "%q" $PS1 $'\\n\\\[\\E\[1m\\\]\\\[\\E\[30m\\\]\\A'$'\\\[\\E\[37m\\\]|\\\[\\E\[36m\\\]\\u\\\[\\E\[37m \\\]@\\\[\\E\[34m\\\]\\h'$'\\\[\\E\[32m\\\]\\W\\\[\\E\[37m\\\]|'$'\\\[\\E(B\\E\[m\\\] ' ``` You can change your default prompt with the `export` command: ``` \[ andrew@pc01 ~ \]$ export PS1="\\n此處指令> " 此處指令> echo $PS1 \\n此處指令> ``` ...[you can add colours, too!](http://bit.ly/2TMbEit): ``` 此處指令> export PS1="\\e\[1;31m\\n程式碼: \\e\[39m" (這應該是紅色的,但在 Markdown 中可能不會這樣顯示) =============================== 程式碼:回顯$PS1 \\e\[1;31m\\n程式碼: \\e\[39m ``` You can also change the colours shown by `ls` by editing the `$LS_COLORS` environment variable: ``` (同樣,這些顏色可能不會出現在 Markdown 中) =========================== 程式碼:ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 程式碼:匯出 LS\_COLORS='di=31:fi=0:ln=96:or=31:mi=31:ex=92' 程式碼:ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 ``` <a name="config-files"></a> ## Config Files <a name="config-bashrc"></a> ### Config Files / `.bashrc` [[ Back to Table of Contents ]](#toc) If you tried the commands in the last section and logged out and back in, you may have noticed that your changes disappeared. _config_ (configuration) files let you maintain settings for your shell or for a particular program every time you log in (or run that program). The main configuration file for a `bash` shell is the `~/.bashrc` file. Aliases, environment variables, and functions added to `~/.bashrc` will be available every time you log in. Commands in `~/.bashrc` will be run every time you log in. If you edit your `~/.bashrc` file, you can reload it without logging out by using the `source` command: ``` \[ andrew@pc01 ~ \]$ nano ~/.bashrc ``` _...add the line `echo “~/.bashrc loaded!”` to the top of the file_... ``` \[ andrew@pc01 ~ \]$ 源 ~/.bashrc ~/.bashrc 已載入! ``` _...log out and log back in..._ ``` 最後登入:2019 年 1 月 11 日星期五 10:29:07 從 111.11.11.111 ~/.bashrc 已加載! \[ 安德魯@pc01 ~ \] ``` <a name="types-of-shells"></a> ### Types of Shells [[ Back to Table of Contents ]](#toc) _Login_ shells are shells you log in to (where you have a username). _Interactive_ shells are shells which accept commands. Shells can be login and interactive, non-login and non-interactive, or any other combination. In addition to `~/.bashrc`, there are a few other scripts which are `sourced` by the shell automatically when you log in or log out. These are: - `/etc/profile` - `~/.bash_profile` - `~/.bash_login` - `~/.profile` - `~/.bash_logout` - `/etc/bash.bash_logout` Which of these scripts are sourced, and the order in which they're sourced, depend on the type of shell opened. See [the bash man page](https://linux.die.net/man/1/bash) and [these](http://bit.ly/2TGCwA8) Stack Overflow [posts](http://bit.ly/2TFHFsf) for more information. Note that `bash` scripts can `source` other scripts. For instance, in your `~/.bashrc`, you could include the line: ``` 來源~/.bashrc\_addl ``` ...which would also `source` that `.bashrc_addl` script. This file can contain its own aliases, functions, environment variables, and so on. It could, in turn, `source` other scripts, as well. (Be careful to avoid infinite loops of script-sourcing!) It may be helpful to split commands into different shell scripts based on functionality or machine type (Ubuntu vs. Red Hat vs. macOS), for example: - `~/.bash_ubuntu` -- configuration specific to Ubuntu-based machines - `~/.bashrc_styles` -- aesthetic settings, like `PS1` and `LS_COLORS` - `~/.bash_java` -- configuration specific to the Java language I try to keep separate `bash` files for aesthetic configurations and OS- or machine-specific code, and then I have one big `bash` file containing shortcuts, etc. that I use on every machine and every OS. Note that there are also _different shells_. `bash` is just one kind of shell (the "Bourne Again Shell"). Other common ones include `zsh`, `csh`, `fish`, and more. Play around with different shells and find one that's right for you, but be aware that this tutorial contains `bash` shell commands only and not everything listed here (maybe none of it) will be applicable to shells other than `bash`. <a name="finding-things"></a> ## Finding Things <a name="whereis-which-whatis"></a> ### `whereis / which / whatis` [[ Back to Table of Contents ]](#toc) `whereis` searches for "possibly useful" files related to a particular command. It will attempt to return the location of the binary (executable machine code), source (code source files), and `man` page for that command: ``` \[ andrew@pc01 ~ \]$ whereis ls ls: /bin/ls /usr/share/man/man1/ls.1.gz ``` `which` will only return the location of the binary (the command itself): ``` \[ andrew@pc01 ~ \]$ 其中 ls /bin/ls ``` `whatis` prints out the one-line description of a command from its `man` page: ``` \[ andrew@pc01 ~ \]$ 什麼是哪裡是哪個什麼是 whereis (1) - 尋找指令的二進位、原始檔和手冊頁文件 which (1) - 定位指令 Whatis (1) - 顯示一行手冊頁描述 ``` `which` is useful for finding the "original version" of a command which may be hidden by an alias: ``` \[ andrew@pc01 ~ \]$ 別名 ls="ls -l" “original” ls 已被上面定義的別名“隱藏” =========================== \[ andrew@pc01 ~ \]$ ls 總計 36 drwxr-xr-x 2 安德魯 andrew 4096 Jan 9 14:47 桌面 drwxr-xr-x 4 安德魯 安德魯 4096 十二月 6 10:43 Git … 但我們仍然可以使用返回的位置來呼叫「原始」ls ======================= \[ andrew@pc01 ~ \]$ /bin/ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 ``` <a name="locate-find"></a> ### `locate / find` [[ Back to Table of Contents ]](#toc) `locate` finds a file anywhere on the system by referring to a semi-regularly-updated cached list of files: ``` \[ andrew@pc01 ~ \]$ 找到 README.md /home/andrew/.config/micro/plugins/gotham-colors/README.md /home/andrew/.jenv/README.md /home/andrew/.myshell/README.md … ``` Because it's just searching a list, `locate` is usually faster than the alternative, `find`. `find` iterates through the file system to find the file you're looking for. Because it's actually looking at the files which _currently_ exist on the system, though, it will always return an up-to-date list of files, which is not necessarily true with `locate`. ``` \[ andrew@pc01 ~ \]$ find ~/ -iname "README.md" /home/andrew/.jenv/README.md /home/andrew/.config/micro/plugins/gotham-colors/README.md /home/andrew/.oh-my-zsh/plugins/ant/README.md … ``` `find` was written for the very first version of Unix in 1971, and is therefore much more widely available than `locate`, which was added to GNU in 1994. `find` has many more features than `locate`, and can search by file age, size, ownership, type, timestamp, permissions, depth within the file system; `find` can search using regular expressions, execute commands on files it finds, and more. When you need a fast (but possibly outdated) list of files, or you’re not sure what directory a particular file is in, use `locate`. When you need an accurate file list, maybe based on something other than the files’ names, and you need to do something with those files, use `find`. <a name="downloading-things"></a> ## Downloading Things <a name="ping-wget-curl"></a> ### `ping / wget / curl` [[ Back to Table of Contents ]](#toc) `ping` attempts to open a line of communication with a network host. Mainly, it's used to check whether or not your Internet connection is down: ``` \[ andrew@pc01 ~ \]$ ping google.com PING google.com (74.125.193.100) 56(84) 位元組資料。 使用 32 位元組資料 Ping 74.125.193.100: 來自 74.125.193.100 的回覆:位元組=32 時間<1ms TTL=64 … ``` `wget` is used to easily download a file from the Internet: ``` \[ andrew@pc01 ~ \]$ wget \\ > http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso ``` `curl` can be used just like `wget` (don’t forget the `--output` flag): ``` \[ andrew@pc01 ~ \]$ 捲曲 \\ > http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso \\ > \--輸出ubuntu.iso ``` `curl` and `wget` have their own strengths and weaknesses. `curl` supports many more protocols and is more widely available than `wget`; `curl` can also send data, while `wget` can only receive data. `wget` can download files recursively, while `curl` cannot. In general, I use `wget` when I need to download things from the Internet. I don’t often need to send data using `curl`, but it’s good to be aware of it for the rare occasion that you do. <a name="apt-gunzip-tar-gzip"></a> ### `apt / gunzip / tar / gzip` [[ Back to Table of Contents ]](#toc) Debian-descended Linux distributions have a fantastic package management tool called `apt`. It can be used to install, upgrade, or delete software on your machine. To search `apt` for a particular piece of software, use `apt search`, and install it with `apt install`: ``` \[ andrew@pc01 ~ \]$ apt 搜尋漂白位 ...bleachbit/bionic、bionic 2.0-2 全部 從系統中刪除不需要的文件 您需要“sudo”來安裝軟體 ============== \[ andrew@pc01 ~ \]$ sudo apt installbleachbit ``` Linux software often comes packaged in `.tar.gz` ("tarball") files: ``` \[ andrew@pc01 ~ \]$ wget \\ > https://github.com/atom/atom/releases/download/v1.35.0-beta0/atom-amd64.tar.gz ``` ...these types of files can be unzipped with `gunzip`: ``` \[ andrew@pc01 ~ \]$gunzipatom-amd64.tar.gz && ls 原子 amd64.tar ``` A `.tar.gz` file will be `gunzip`-ped to a `.tar` file, which can be extracted to a directory of files using `tar -xf` (`-x` for "extract", `-f` to specify the file to "untar"): ``` \[ andrew@pc01 ~ \]$ tar -xfatom-amd64.tar && mv \\ 原子-beta-1.35.0-beta0-amd64 原子 && ls 原子atom-amd64.tar ``` To go in the reverse direction, you can create (`-c`) a tar file from a directory and zip it (or unzip it, as appropriate) with `-z`: ``` \[ andrew@pc01 ~ \]$ tar -zcf 壓縮.tar.gz 原子 && ls 原子atom-amd64.tar壓縮.tar.gz ``` `.tar` files can also be zipped with `gzip`: ``` \[ andrew@pc01 ~ \]$ gzipatom-amd64.tar && ls 原子 原子-amd64.tar.gz 壓縮.tar.gz ``` <a name="redirecting-io"></a> ## Redirecting Input and Output <a name="pipe-gt-lt-echo-printf"></a> ### `| / > / < / echo / printf` [[ Back to Table of Contents ]](#toc) By default, shell commands read their input from the standard input stream (aka. stdin or 0) and write to the standard output stream (aka. stdout or 1), unless there’s an error, which is written to the standard error stream (aka. stderr or 2). `echo` writes text to stdout by default, which in most cases will simply print it to the terminal: ``` \[ andrew@pc01 ~ \]$ 回顯“你好” 你好 ``` The pipe operator, `|`, redirects the output of the first command to the input of the second command: ``` 'wc'(字數)傳回檔案中的行數、字數、位元組數 ======================== \[ andrew@pc01 ~ \]$ echo "範例文件" |廁所 ``` 1 2 17 ``` ``` `>` redirects output from stdout to a particular location ``` \[ andrew@pc01 ~ \]$ echo "test" > 文件 && 頭文件 測試 ``` `printf` is an improved `echo`, allowing formatting and escape sequences: ``` \[ andrew@pc01 ~ \]$ printf "1\\n3\\n2" 1 3 2 ``` `<` gets input from a particular location, rather than stdin: ``` 'sort' 依字母/數字順序對檔案的行進行排序 ======================== \[ andrew@pc01 ~ \]$ sort <(printf "1\\n3\\n2") 1 2 3 ``` Rather than a [UUOC](#viewing-and-editing-files), the recommended way to send the contents of a file to a command is to use `<`. Note that this causes data to "flow" right-to-left on the command line, rather than (the perhaps more natural, for English-speakers) left-to-right: ``` \[ andrew@pc01 ~ \]$ printf "1\\n3\\n2" > 文件 && 排序 < 文件 1 2 3 ``` <a name="std-tee"></a> ### `0 / 1 / 2 / tee` [[ Back to Table of Contents ]](#toc) 0, 1, and 2 are the standard input, output, and error streams, respectively. Input and output streams can be redirected with the `|`, `>`, and `<` operators mentioned previously, but stdin, stdout, and stderr can also be manipulated directly using their numeric identifiers: Write to stdout or stderr with `>&1` or `>&2`: ``` \[ andrew@pc01 ~ \]$ 貓測試 回顯“標準輸出”>&1 回顯“標準錯誤”>&2 ``` By default, stdout and stderr both print output to the terminal: ``` \[ andrew@pc01 ~ \]$ ./測試 標準錯誤 標準輸出 ``` Redirect stdout to `/dev/null` (only print output sent to stderr): ``` \[ andrew@pc01 ~ \]$ ./test 1>/dev/null 標準錯誤 ``` Redirect stderr to `/dev/null` (only print output sent to stdout): ``` \[ andrew@pc01 ~ \]$ ./test 2>/dev/null 標準輸出 ``` Redirect all output to `/dev/null` (print nothing): ``` \[ andrew@pc01 ~ \]$ ./test &>/dev/null ``` Send output to stdout and any number of additional locations with `tee`: ``` \[ andrew@pc01 ~ \]$ ls && echo "測試" | tee 文件1 文件2 文件3 && ls 文件0 測試 文件0 文件1 文件2 文件3 ``` <a name="advanced"></a> # Advanced <a name="superuser"></a> ## Superuser <a name="sudo-su"></a> ### `sudo / su` [[ Back to Table of Contents ]](#toc) You can check what your username is with `whoami`: ``` \[ andrew@pc01 abc \]$ whoami 安德魯 ``` ...and run a command as another user with `sudo -u username` (you will need that user's password): ``` \[ andrew@pc01 abc \]$ sudo -u 測試觸摸 def && ls -l 總計 0 -rw-r--r-- 1 次測試 0 Jan 11 20:05 def ``` If `–u` is not provided, the default user is the superuser (usually called "root"), with unlimited permissions: ``` \[ andrew@pc01 abc \]$ sudo touch ghi && ls -l 總計 0 -rw-r--r-- 1 次測試 0 Jan 11 20:05 def -rw-r--r-- 1 root root 0 Jan 11 20:14 ghi ``` Use `su` to become another user temporarily (and `exit` to switch back): ``` \[ andrew@pc01 abc \]$ su 測試 密碼: test@pc01:/home/andrew/abc$ whoami 測試 test@pc01:/home/andrew/abc$ 退出 出口 \[ andrew@pc01 abc \]$ whoami 安德魯 ``` [Learn more about the differences between `sudo` and `su` here.](http://bit.ly/2SKQH77) <a name="click-click"></a> ### `!!` [[ Back to Table of Contents ]](#toc) The superuser (usually "root") is the only person who can install software, create users, and so on. Sometimes it's easy to forget that, and you may get an error: ``` \[ andrew@pc01 ~ \]$ apt 安裝 ruby E:無法開啟鎖定檔案 /var/lib/dpkg/lock-frontend - 開啟(13:權限被拒絕) E: 無法取得 dpkg 前端鎖定 (/var/lib/dpkg/lock-frontend),您是 root 嗎? ``` You could retype the command and add `sudo` at the front of it (run it as the superuser): ``` \[ andrew@pc01 ~ \]$ sudo apt install ruby 正在閱讀包裝清單... ``` Or, you could use the `!!` shortcut, which retains the previous command: ``` \[ andrew@pc01 ~ \]$ apt 安裝 ruby E:無法開啟鎖定檔案 /var/lib/dpkg/lock-frontend - 開啟(13:權限被拒絕) E: 無法取得 dpkg 前端鎖定 (/var/lib/dpkg/lock-frontend),您是 root 嗎? \[ andrew@pc01 ~ \]$ sudo !! sudo apt 安裝 ruby 正在閱讀包裝清單... ``` By default, running a command with `sudo` (and correctly entering the password) allows the user to run superuser commands for the next 15 minutes. Once those 15 minutes are up, the user will again be prompted to enter the superuser password if they try to run a restricted command. <a name="file-permissions"></a> ## File Permissions <a name="file-permissions-sub"></a> ### File Permissions [[ Back to Table of Contents ]](#toc) Files may be able to be read (`r`), written to (`w`), and/or executed (`x`) by different users or groups of users, or not at all. File permissions can be seen with the `ls -l` command and are represented by 10 characters: ``` \[ andrew@pc01 ~ \]$ ls -lh 總計 8 drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -rwxr-xr-x 1 安德魯 安德魯 40 Jan 11 16:16 測試 -rw-r--r-- 1 安德魯 安德魯 0 一月 11 16:34 tist ``` The first character of each line represents the type of file, (`d` = directory, `l` = link, `-` = regular file, and so on); then there are three groups of three characters which represent the permissions held by the user (u) who owns the file, the permissions held by the group (g) which owns the file, and the permissions held any other (o) users. (The number which follows this string of characters is the number of links in the file system to that file (4 or 1 above).) `r` means that person / those people have read permission, `w` is write permission, `x` is execute permission. If a directory is “executable”, that means it can be opened and its contents can be listed. These three permissions are often represented with a single three-digit number, where, if `x` is enabled, the number is incremented by 1, if `w` is enabled, the number is incremented by 2, and if `r` is enabled, the number is incremented by 4. Note that these are equivalent to binary digits (`r-x` -> `101` -> `5`, for example). So the above three files have permissions of 755, 755, and 644, respectively. The next two strings in each list are the name of the owner (`andrew`, in this case) and the group of the owner (also `andrew`, in this case). Then comes the size of the file, its most recent modification time, and its name. The `–h` flag makes the output human readable (i.e. printing `4.0K` instead of `4096` bytes). <a name="chmod-chown"></a> ### `chmod / chown` [[ Back to Table of Contents ]](#toc) File permissions can be modified with `chmod` by setting the access bits: ``` \[ andrew@pc01 ~ \]$ chmod 777 測試 && chmod 000 tit && ls -lh 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -rwxrwxrwx 1 安德魯 安德魯 40 Jan 11 16:16 測試 \---------- 1 安德魯 安德魯 0 一月 11 16:34 tist ``` ...or by adding (`+`) or removing (`-`) `r`, `w`, and `x` permissions with flags: ``` \[ andrew@pc01 ~ \]$ chmod +rwx Tist && chmod -w 測試 && ls -lh chmod:測試:新權限是 r-xrwxrwx,而不是 r-xr-xr-x 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -r-xrwxrwx 1 安德魯 安德魯 40 Jan 11 16:16 測試 -rwxr-xr-x 1 安德魯 安德魯 0 一月 11 16:34 tist ``` The user who owns a file can be changed with `chown`: ``` \[ andrew@pc01 ~ \]$ sudo chown 碼頭測試 ``` The group which owns a file can be changed with `chgrp`: ``` \[ andrew@pc01 ~ \]$ sudo chgrp hadoop tit && ls -lh 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 \-----w--w- 1 瑪麗娜·安德魯 2011 年 1 月 40 日 16:16 測試 -rwxr-xr-x 1 安德魯 hadoop 0 一月 11 16:34 tist ``` <a name="users-groups"></a> ## User and Group Management <a name="users"></a> ### Users [[ Back to Table of Contents ]](#toc) `users` shows all users currently logged in. Note that a user can be logged in multiple times if -- for instance -- they're connected via multiple `ssh` sessions. ``` \[ andrew@pc01 ~ \]$ 用戶 安德魯·科林·科林·科林·科林·科林·克里希納·克里希納 ``` To see all users (even those not logged in), check `/etc/passwd`. (**WARNING**: do not modify this file! You can corrupt your user accounts and make it impossible to log in to your system.) ``` \[ andrew@pc01 ~ \]$ alias au="cut -d: -f1 /etc/passwd \\ > |排序| uniq”&& au \_易於 一個廣告 安德魯... ``` Add a user with `useradd`: ``` \[ andrew@pc01 ~ \]$ sudo useradd aardvark && au \_易於 土豚 一個廣告... ``` Delete a user with `userdel`: ``` \[ andrew@pc01 ~ \]$ sudo userdel aardvark && au \_易於 一個廣告 安德魯... ``` [Change a user’s default shell, username, password, or group membership with `usermod`.](http://bit.ly/2D4upIg) <a name="groups"></a> ### Groups [[ Back to Table of Contents ]](#toc) `groups` shows all of the groups of which the current user is a member: ``` \[ andrew@pc01 ~ \]$ 組 andrew adm cdrom sudo dial plugdev lpadmin sambashare hadoop ``` To see all groups on the system, check `/etc/group`. (**DO NOT MODIFY** this file unless you know what you are doing.) ``` \[ andrew@pc01 ~ \]$ alias ag=“cut -d: -f1 /etc/group \\ > |排序”&& ag 管理員 一個廣告 安德魯... ``` Add a group with `groupadd`: ``` \[ andrew@pc01 ~ \]$ sudo groupadd aardvark && ag 土豚 管理員 一個廣告... ``` Delete a group with `groupdel`: ``` \[ andrew@pc01 ~ \]$ sudo groupdel aardvark && ag 管理員 一個廣告 安德魯... ``` [Change a group’s name, ID number, or password with `groupmod`.](https://linux.die.net/man/8/groupmod) <a name="text-processing"></a> ## Text Processing <a name="uniq-sort-diff-cmp"></a> ### `uniq / sort / diff / cmp` [[ Back to Table of Contents ]](#toc) `uniq` can print unique lines (default) or repeated lines: ``` \[ andrew@pc01 man \]$ printf "1\\n2\\n2" > a && \\ > printf "1\\n3\\n2" > b \[ andrew@pc01 人 \]$ uniq a 1 2 ``` `sort` will sort lines alphabetically / numerically: ``` \[ andrew@pc01 man \]$ 排序 b 1 2 3 ``` `diff` will report which lines differ between two files: ``` \[ andrew@pc01 人 \]$ diff ab 2c2 < 2 --- > 3 ``` `cmp` reports which bytes differ between two files: ``` \[andrew@pc01 人\]$ cmp ab ab 不同:字元 3,第 2 行 ``` <a name="cut-sed"></a> ### `cut / sed` [[ Back to Table of Contents ]](#toc) `cut` is usually used to cut a line into sections on some delimiter (good for CSV processing). `-d` specifies the delimiter and `-f` specifies the field index to print (starting with 1 for the first field): ``` \[ andrew@pc01 人 \]$ printf "137.99.234.23" > c \[ andrew@pc01 man \]$ cut -d'.' c-f1 137 ``` `sed` is commonly used to replace a string with another string in a file: ``` \[ andrew@pc01 man \]$ echo "舊" | sed s/舊/新/ 新的 ``` ...but `sed` is an extremely powerful utility, and cannot be properly summarised here. It’s actually Turing-complete, so it can do anything that any other programming language can do. `sed` can find and replace based on regular expressions, selectively print lines of a file which match or contain a certain pattern, edit text files in-place and non-interactively, and much more. A few good tutorials on `sed` include: - [https://www.tutorialspoint.com/sed/](https://www.tutorialspoint.com/sed/) - [http://www.grymoire.com/Unix/Sed.html](http://www.grymoire.com/Unix/Sed.html) - [https://www.computerhope.com/unix/used.htm](https://www.computerhope.com/unix/used.htm) <a name="pattern-matching"></a> ## Pattern Matching <a name="grep"></a> ### `grep` [[ Back to Table of Contents ]](#toc) The name `grep` comes from `g`/`re`/`p` (search `g`lobally for a `r`egular `e`xpression and `p`rint it); it’s used for finding text in files. `grep` is used to find lines of a file which match some pattern: ``` \[ andrew@pc01 ~ \]$ grep -e " *.fi.* " /etc/profile /etc/profile:Bourne shell 的系統範圍 .profile 檔案 (sh(1)) =================================================== ``` # The file bash.bashrc already sets the default PS1. ``` ``` fi ``` ``` fi ``` … ``` ...or contain some word: ``` \[ andrew@pc01 ~ \]$ grep "andrew" /etc/passwd 安德魯:x:1000:1000:安德魯,,,:/home/andrew:/bin/bash ``` `grep` is usually the go-to choice for simply finding matching lines in a file, if you’re planning on allowing some other program to handle those lines (or if you just want to view them). `grep` allows for (`-E`) use of extended regular expressions, (`-F`) matching any one of multiple strings at once, and (`-r`) recursively searching files within a directory. These flags used to be implemented as separate commands (`egrep`, `fgrep`, and `rgrep`, respectively), but those commands are now deprecated. > **Bonus**: [see the origins of the names of a few famous `bash` commands](https://kb.iu.edu/d/abnd) <a name="awk"></a> ### `awk` [[ Back to Table of Contents ]](#toc) `awk` is a pattern-matching language built around reading and manipulating delimited data files, like CSV files. As a rule of thumb, `grep` is good for finding strings and patterns in files, `sed` is good for one-to-one replacement of strings in files, and `awk` is good for extracting strings and patterns from files and analysing them. As an example of what `awk` can do, here’s a file containing two columns of data: ``` \[ andrew@pc01 ~ \]$ printf "A 10\\nB 20\\nC 60" > 文件 ``` Loop over the lines, add the number to sum, increment count, print the average: ``` \[ andrew@pc01 ~ \]$ awk 'BEGIN {sum=0;計數=0; OFS=“”} {sum+=$2; count++} END {print "平均值:", sum/count}' 文件 平均:30 ``` `sed` and `awk` are both Turing-complete languages. There have been multiple books written about each of them. They can be extremely useful with pattern matching and text processing. I really don’t have enough space here to do either of them justice. Go read more about them! > **Bonus**: [learn about some of the differences between `sed`, `grep`, and `awk`](http://bit.ly/2AI3IaN) <a name="ssh"></a> ## Copying Files Over `ssh` <a name="ssh-scp"></a> ### `ssh / scp` [[ Back to Table of Contents ]](#toc) `ssh` is how Unix-based machines connect to each other over a network: ``` \[ andrew@pc01 ~ \]$ ssh –p安德魯@137.xxx.xxx.89 上次登入:2019 年 1 月 11 日星期五 12:30:52,來自 137.xxx.xxx.199 ``` Notice that my prompt has changed as I’m now on a different machine: ``` \[ andrew@pc02 ~ \]$ 退出 登出 與 137.xxx.xxx.89 的連線已關閉。 ``` Create a file on machine 1: ``` \[ andrew@pc01 ~ \]$ echo "你好" > 你好 ``` Copy it to machine 2 using `scp` (secure copy; note that `scp` uses `–P` for a port #, `ssh` uses `–p`) ``` \[ andrew@pc01 ~ \]$ scp –P你好安德魯@137.xxx.xxx.89:~ 你好 100% 0 0.0KB/秒 00:00 ``` `ssh` into machine 2: ``` \[ andrew@pc02 ~ \]$ ssh –p安德魯@137.xxx.xxx.89 上次登入:2019 年 1 月 11 日星期五 22:47:37,來自 137.xxx.xxx.79 ``` The file’s there! ``` \[ andrew@pc02 ~ \]$ ls 你好多xargs \[ andrew@pc02 ~ \]$ 貓你好 你好 ``` <a name="rsync"></a> ### `rsync` [[ Back to Table of Contents ]](#toc) `rsync` is a file-copying tool which minimises the amount of data copied by looking for deltas (changes) between files. Suppose we have two directories: `d`, with one file, and `s`, with two files: ``` \[ andrew@pc01 d \]$ ls && ls ../s f0 f0 f1 ``` Sync the directories (copying only missing data) with `rsync`: ``` \[ andrew@pc01 d \]$ rsync -off ../s/\* . 正在發送增量文件列表... ``` `d` now contains all files that `s` contains: ``` \[ andrew@pc01 d \]$ ls f0 f1 ``` `rsync` can be performed over `ssh` as well: ``` \[ andrew@pc02 r \]$ ls \[ andrew@pc02 r \]$ rsync -avz -e "ssh -p “ [email protected]:~/s/\* 。 接收增量檔案列表 f0 f1 發送 62 位元組 接收 150 位元組 141.33 位元組/秒 總大小為 0 加速率為 0.00 \[ andrew@pc02 r \]$ ls f0 f1 ``` <a name="long-running-processes"></a> ## Long-Running Processes <a name="yes-nohup-ps-kill"></a> ### `yes / nohup / ps / kill` [[ Back to Table of Contents ]](#toc) Sometimes, `ssh` connections can disconnect due to network or hardware problems. Any processes initialized through that connection will be “hung up” and terminate. Running a command with `nohup` insures that the command will not be hung up if the shell is closed or if the network connection fails. Run `yes` (continually outputs "y" until it’s killed) with `nohup`: ``` \[ andrew@pc01 ~ \]$ nohup 是 & \[1\]13173 ``` `ps` shows a list of the current user’s processes (note PID number 13173): ``` \[ andrew@pc01 ~ \]$ ps | sed -n '/是/p' 13173 分/10 00:00:12 是 ``` _...log out and log back into this shell..._ The process has disappeared from `ps`! ``` \[ andrew@pc01 ~ \]$ ps | sed -n '/是/p' ``` But it still appears in `top` and `htop` output: ``` \[ andrew@pc01 ~ \]$ 頂部 -bn 1 | sed -n '/是/p' 13173 安德魯 20 0 4372 704 636 D 25.0 0.0 0:35.99 是 ``` Kill this process with `-9` followed by its process ID (PID) number: ``` \[ andrew@pc01 ~ \]$ 殺死 -9 13173 ``` It no longer appears in `top`, because it’s been killed: ``` \[ andrew@pc01 ~ \]$ 頂部 -bn 1 | sed -n '/是/p' ``` <a name="cron"></a> ### `cron / crontab / >>` [[ Back to Table of Contents ]](#toc) `cron` provides an easy way of automating regular, scheduled tasks. You can edit your `cron` jobs with `crontab –e` (opens a text editor). Append the line: ``` - - - - - 日期 >> ~/datefile.txt ``` This will run the `date` command every minute, appending (with the `>>` operator) the output to a file: ``` \[ andrew@pc02 ~ \]$ head ~/datefile.txt 2019 年 1 月 12 日星期六 14:37:01 GMT 2019 年 1 月 12 日星期六

💻Visual Studio Code 快捷指南、更高的生產力、以及您需要學習的 30 個快捷指令

介紹 == Visual Studio Code 允許您透過命令面板或鍵盤上的快捷鍵存取它提供的幾乎所有功能。 您可能每個工作日工作 8 小時,希望您能在這些工作時間中大部分時間進行編碼。所以你花了很多時間盯著你選擇的程式碼編輯器。 了解一些快捷方式可以幫助您更快地完成工作。知道如何更快地找到您需要的文件。您需要立即執行 NPM 命令,而不是開啟外部終端。 ![雙手在鍵盤上快速打字](https://media.giphy.com/media/eMxZ6lPl8dW9O/giphy.gif) 捷徑備忘單 ===== Visual Studio Code 的開發者 - [視窗](https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf) - [Linux](https://code.visualstudio.com/shortcuts/keyboard-shortcuts-linux.pdf) - [蘋果系統](https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf) 您可以下載這些備忘單,列印出來,然後將其放在辦公桌上以供快速參考,或嘗試在上班途中學習它們。不要試圖一次學會所有這些。這需要時間。所以要有耐心,你就會掌握它們。 鍵位圖 === 您是 Vim 用戶嗎?也許 Emacs 快捷方式已經刻在你的大腦裡了?或者,無論出於何種原因,您使用記事本++並欣賞記事本++的鍵盤快捷鍵😵? Visual Studio 為大家提供了一個擴充功能!讓我們安裝 ⚛ `Atom Keymap` 。我們將在沒有我們心愛的滑鼠的幫助下(幾乎)做到這一點。 1\) 開啟 Visual Studio 程式碼。 2\) Visual Studio Code 開啟後,按: `CTRL+SHIFT+X` 。該快捷方式將打開擴展列表,並且您的遊標將聚焦在搜尋欄上。輸入以下`@category:keymaps` 。 (如果您想了解更多有關本節中擴展程序如何工作的訊息,請在下面發表評論!) 3\) 您現在會看到鍵盤映射清單。按`Tab` ,然後按`Down Arrow ⬇` 。 4\) 按`⬇`直到選擇`Atom Keymap` 。現在按`Enter` 。 5)遺憾的是我找不到選擇「安裝」按鈕的方法。您現在需要點擊🖱! 您可以找到幾乎所有您能想像到的編輯器的鍵盤映射。安裝您最喜歡的那個,您就有了快捷方式!很酷吧? 鍵盤快速鍵設定 (JSON) ============== 有多種方法可以查看鍵盤快捷鍵設定。其中一種是透過圖形介面,也可以選擇使用透過 JSON 檔案來編輯捷徑。 圖形介面 ---- 我們可以按`CTRL+k`開啟圖形介面,然後仍然按住`CTRL` ,您應該按`CTRL+s` 。 ![鍵盤快捷鍵設定 GUI](https://thepracticaldev.s3.amazonaws.com/i/3etyosi4ljtp1d9lvk5i.png) 頂部有一個搜尋欄,您可以在其中搜尋要查看的命令或鍵盤快捷鍵。這些對話框在 Visual Studio Code 中看起來往往相同,您將開始經常看到它們。 您可以看到四列。讓我們來看看它們。 \*指令:Visual Studio Code 執行的操作。 - 鍵綁定:執行操作時必須按下的按鍵組合。 - 何時:這是 Visual Studio Code 的過濾器,它告訴 Visual Studio Code 捷徑是否應該在該上下文中可操作。有些過濾器可能是整合終端、原始碼中的錯誤等等。 - 來源:Visual Studio 程式碼可以透過多種方式了解捷徑。最常見的是`Default` ,這些命令是 Visual Studio Code 隨附的開箱即用命令。顧名思義`User`是由用戶建立的命令。第三種方式是透過`Extension` 。擴展作者還可以決定加入快捷方式。如果您最喜歡的快捷方式不起作用,這可能是它停止工作的原因。 若要變更鍵綁定,請雙擊該行,然後會彈出一個模式。然後按下所需的組合鍵並按下`Enter` 。 鍵綁定 JSON 文件 ----------- 現在我們知道了鍵綁定的一般工作原理,讓我們來看看`keybindings.json`檔案。 其中有兩個:預設的`keybindings.json`和使用者特定的`keybindings.json`檔案。按`CTRL+SHIFT+P`或`F1`開啟命令匣並鍵入`keyboard shortcuts`現在您應該在命令托盤中看到至少兩個條目。 - 首選項:開啟預設鍵盤快速鍵 (JSON)。 這是 Visual Studio Code 儲存所有預設快捷方式的文件,以及底部未使用的可用捷徑清單。我會避免在這裡更改它們。 - 首選項:開啟鍵盤快速鍵 (JSON) 這是用戶特定的鍵綁定文件,您應該編輯此文件。一開始,它只是一個空陣列而已! 要在 JSON 檔案中新增快捷方式,您只需新增一個如下所示的物件: ``` [ { "key": "CTRL+ALT+P", "command": "git.pull", "when": "" } ] ``` 您需要指定密鑰和命令。 `when`告訴 Visual Studio Code 應在何處執行此命令。如果你把它留空,它會到處監聽。我們在上一部分談到了這一點。 有用的快捷鍵 ====== 打開命令面板 ------ 您已經知道這一點,但也許您跳到了這一部分 😉 - `CRTL+SHIFT+P`或`F1` 這將開啟 Visual Studio Code 中最強大的功能。命令面板。只需輸入您認為想要的內容,它仍然可以找到它! 打開和關閉側邊欄 -------- 有時您想要更多的水平空間,但側邊欄卻妨礙了您!只需按 - `CTRL+B` 您可以打開和關閉側邊欄 輸入禪宗模式 ------ 你喜歡 Visual Studio 程式碼中的 Zen Mod 嗎?是的,它是內建的! 為此,您需要按: - `CTRL+k` ,放開兩個按鍵並按`z` 。 這將打開和關閉 Zen Mod。 聚焦綜合終端 ------ 我最喜歡的功能之一是 Visual Studio Code 中的整合終端機。我99%的時間都在用它!因此要快速打開或關閉它,您需要按: - `CTRL+j` 這將打開整合終端並將遊標聚焦在其中。如果您再次按下它,它將關閉,並且您的遊標將返回到原來的位置。 在您的專案中搜尋文件 ---------- Visual Studio Code 有一個很棒的檔案搜尋內建功能。當您使用遠端擴展時,它也非常快。要打開它,您只需按: - `CTRL+p` 這將打開一個對話框,您可以在其中查看最近打開的文件,這本身就非常好。它還支援模糊搜尋。這意味著您可以鍵入任何單字,它會在檔案的路徑中找到。所以你不必精確!該對話還支持更多的事情。例如`go-to line`或除錯以及更多功能!如果您想了解更多請在下面評論。 切換到最近開啟的工作區 ----------- 您在微服務架構中工作並且需要一直切換資料夾?因為你不使用 mono 倉庫?我有捷徑給你!按: - `CTRL+r` 這將開啟一個對話框,其中包含最近開啟的工作區/資料夾的清單。 額外提示:如果您在該對話方塊中按`CTRL+ENTER` Visual Studio Code 將在新視窗中開啟它。 分割編輯器視窗 ------- 人們喜歡 vim,因為它很容易在編輯器之間分割視圖。 Visual Studio Code 也內建了功能。只需按 - `CTRL+\` 若要建立 2 列或 - `CTRL+k` ,放開`k`並按住`CTRL`並按`\` 建立一個新行。第二個聽起來比它更難,但是一旦它進入你的大腦,它需要你幾秒鐘的時間,你現在知道如何更改或建立新的快捷方式😉 聚焦編輯器視窗 ------- 既然您知道如何拆分編輯器窗口,您還需要學習如何快速跳轉這些視圖。這非常簡單,並且還有預設的鍵綁定。你需要按 - `CTRL+[1-9]` 這表示您需要按`CTRL`加您想要關注的視窗的編號。對於第一個視窗按`CTRL+1` ,第二個視窗`CTRL+2` ,您明白了 Easy 的想法嗎? 關閉目前編輯器視窗 --------- 現在您打開了太多編輯器窗口,並且您想要關閉它們。這可以透過按快速完成 - `CTRL+w` 這將關閉目前開啟的視窗。 僅關閉已儲存的編輯器視窗 ------------ 有時您會開啟如此多的編輯器,以至於您不再知道要儲存了什麼。是的,我知道您可以透過選項卡欄中的那個點看到這一點,但是,您仍然無法集中精力並找到正確的檔案。 Visual Studio Code 為您提供支援!只需按 - `CTRL+k`然後放開`CTRL`和`k`並按`u` 這將保存所有窗口,以便您可以檢查未保存的窗口並保存它們。 開啟一個新文件 ------- 您需要一個新檔案來繪製一些程式碼嗎?或者,您需要為您的寵物專案建立一個新檔案?按 - `CTRL+n` 這將開啟一個新編輯器。 更改目前文件的語言 --------- 您想切換目前文件中選定的語言,因為您想要`Javascript (react)`而不是`Javascript` ?按 - `CTRL+k` 然後放開`CTRL`和`k`並按 'm`。 這將打開一個新的對話,您可以在其中搜尋所需的語言。 前往線路 ---- 現在讓我們專注於如何讓編輯變得更容易。第 1042 行有錯誤(如果您的文件那麼長,那麼問題就來了)。你不想滾動!按 - `CTRL+g` 這將開啟一個對話框,您需要輸入要跳到的行號。與`CTRL+p`結合使用會非常強大。 轉到符號 ---- 您的第一個問題是,什麼是符號?在程式語言中,符號通常是變數。在 CSS 中,它們是選擇器。若要查看對話,請按 - `CTRL+SHIFT+O` 這將開啟一個對話框,其中包含目前文件中可用符號的清單。 - `CTRL+t` 您會看到一個只有`#`的對話框,您需要鍵入所需的符號,Visual Studio Code 會在空工作區中搜尋該符號(如果您使用的語言支援該符號)。所以你需要自己檢查一下。 向上或向下移動一條線。 ----------- 有時您需要移出`if`內的那行程式碼,或只是移動一行,因為它被提前呼叫了。您可以透過按 - `Alt+Down` 將目前選定的行向下移動一行 - `Alt+Up` 將目前選定的行向上移動一行 複製目前行 ----- 您想用一些變數填充該陣列,但您懶得寫一個循環。那麼要如何填滿`array[0]` `array[1]`和`array[2]`呢?透過複製第一行兩次並僅更改您需要的內容。對於那個新聞界 - `ALT+SHIFT+Up` 這將複製當前選定的行並將其貼上到上面的一行中 - `ALT+SHIFT+Down` 這將複製目前選定的行並將其貼上到下面的一行中 (這個快捷方式在這裡會很方便) 顯示建議 ---- Visual Studio Code 有內建建議。大多數時候它會自動為您彈出,但有時不會,而您確實需要它。簡單,按 - `CTRL+Spacebar` 這將打開建議對話框 註解掉目前選擇 ------- 有時您需要隔離程式碼並註解掉它周圍的所有內容。按 - `CTRL+/` 如果您選擇了多行,則會將其註解掉。如果您沒有選擇任何內容,它只會註解掉這一行。 選擇多行程式碼 ------- 要註解掉該程式碼區塊,您需要選擇多行。這是透過按完成的 - `CTRL+Shift+Up` 從目前行開始選擇並向上移動遊標。 - `CTRL+Shift+down` 從目前行開始選擇並向下移動遊標。 折疊和展開您的程式碼 ---------- 你有這麼大的功能,但你真的看不到它了,因為它太大了,需要重構,但你沒有時間,所以你想忘記它嗎?您可以折疊和展開程式碼,以便在 100 行中可以產生 1 行。若要執行此操作,請按 - `CTRL+SHIFT+[` 折疊(隱藏)程式碼 - `CTRL+SHIFT+]` 展開(顯示)程式碼 切一條孔線 ----- 對於此,您不能選擇任何程式碼。按 - `CTRL+x` 當沒有選擇任何內容時,這會剪切整行。 縮排/減少線 ------ 人們通常知道如何縮排程式碼。您可以選擇要縮排的程式碼並按 - `Tab` 按下您想要的次數按 Tab 鍵,這樣效果就適合您了。 您知道可以取消縮排嗎?將程式碼從右移到左?您可以透過按 - `SHIFT+tab` 結論 == 還有更多的捷徑。這些快捷鍵是我最常使用的快捷鍵。我希望這可以幫助您更多地了解 Visual Studio Code 中的快捷方式,並且您現在可以建立自己的快捷方式。 我是否忽略了每個人都需要知道的有用命令? 你錯過了什麼嗎?有什麼不清楚嗎? 請寫評論。我盡我所能回答你所有的問題! **👋問好!** [Instagram](https://www.instagram.com/lampewebdev/) |[推特](https://twitter.com/lampewebdev)|[領英](https://www.linkedin.com/in/michael-lazarski-25725a87)|[中等](https://medium.com/@lampewebdevelopment)|[抽搐](https://dev.to/twitch_live_streams/lampewebdev)| [Youtube](https://www.youtube.com/channel/UCYCe4Cnracnq91J0CgoyKAQ) --- 原文出處:https://dev.to/lampewebdev/the-guide-to-visual-studio-code-shortcuts-higher-productivity-and-30-of-my-favourite-shortcuts-you-need-to-learn-mb3

S.O.L.I.D:提升編碼技能的 5 個黃金法則

在軟體開發領域,這個以其多樣化和強烈持有觀點而聞名的領域,很少有實踐能夠像 SOLID 原則那樣達成共識,作為成為更好的軟體工程師的保證途徑。 Robert C. Martin 在 2000 年代初期正式製定的 5 條黃金法則極大地影響了軟體開發產業,並為更好的程式碼品質和決策過程製定了新標準,至今仍具有相關性。 ![堅實的原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2mdlic5qf7f0ws36owf.jpg) SOLID 原則是專門為支援 OOP(物件導向程式設計)範例而設計的。因此,本文是為希望提高開發技能並編寫更優雅、可維護和可擴展程式碼的 OOP 開發人員而設計的。 這裡使用的語言是 TypeScript,遵循常見的跨語言 OOP 概念。需要基本的 OOP 知識。 --- 1. S = 單一職責原則(SRP) ------------------ 單一職責原則 (SRP) 是五個 SOLID 原則之一,它規定每個類別應該只有一個職責,以保持有意義的關注點分離。 此模式是一種稱為「上帝物件」的常見反模式的解決方案,「上帝物件」只是指承擔太多職責的類別或物件,使其難以理解、測試和維護。 遵循 SRP 規則有助於使程式碼元件可重複使用、鬆散耦合且易於理解。讓我們探討這項原則,展示 SRP 違規情況和解決方案。 ### 全域宣告 ``` enum Color { BLUE = 'blue', GREEN = 'green', RED = 'red' } enum Size { SMALL = 'small', MEDIUM = 'medium', LARGE = 'large' } class Product { private _name: string; private _color: Color; private _size: Size; constructor (name: string, color: Color, size: Size) { this._name = name; this._color = color; this._size = size; } public get name(): string { return this._name; } public get color(): Color { return this._color; } public get size(): Size { return this._size; } } ``` ### 違反 在下面的程式碼中, `ProductManager`類別負責**products 的建立和存儲**,違反了單一職責原則。 ``` class ProductManager { private _products: Product[] = []; createProduct (name: string, color: Color, size: Size): Product { return new Product(name, color, size); } storeProduct (product: Product): void { this._products.push(product); } getProducts (): Product[] { return this._products; } } const productManager: ProductManager = new ProductManager(); const product: Product = productManager.createProduct('Product 1', Color.BLUE, Size.LARGE); productManager.storeProduct(product); const allProducts: Product[] = productManager.getProducts(); ``` ### 解決 將產品建立和儲存的處理分離到兩個不同的類別可以減少`ProductManager`類別的職責數量。這種方法進一步模組化了程式碼並使其更易於維護。 ``` class ProductManager { createProduct (name: string, color: Color, size: Size): Product { return new Product(name, color, size); } } class ProductStorage { private _products: Product[] = []; storeProduct (product: Product): void { this._products.push(product); } getProducts (): Product[] { return this._products; } } ``` #### 用法: ``` const productManager: ProductManager = new ProductManager(); const productStorage: ProductStorage = new ProductStorage(); const product: Product = productManager.createProduct("Product 1", Color.BLUE, Size.LARGE); productStorage.storeProduct(product); const allProducts: Product[] = productStorage.getProducts(); ``` --- 2. O = 開閉原理 (OCP) ----------------- > “軟體實體應該對擴展開放,但對修改關閉” 開閉原則 (OCP) 是*「寫一次,寫得夠好以便可擴展,然後就忘記它」。* 這項原則的重要性取決於這樣一個事實:模組可能會根據新的需求不時發生變化。如果在模組編寫、測試並上傳到生產環境後出現新需求,則修改此模組通常是不好的做法,尤其是當其他模組依賴它時。為了防止這種情況,我們可以使用開閉原則。 ### 全域宣告 ``` enum Color { BLUE = 'blue', GREEN = 'green', RED = 'red' } enum Size { SMALL = 'small', MEDIUM = 'medium', LARGE = 'large' } class Product { private _name: string; private _color: Color; private _size: Size; constructor (name: string, color: Color, size: Size) { this._name = name; this._color = color; this._size = size; } public get name(): string { return this._name; } public get color(): Color { return this._color; } public get size(): Size { return this._size; } } class Inventory { private _products: Product[] = []; public add(product: Product): void { this._products.push(product); } addArray(products: Product[]) { for (const product of products) { this.add(product); } } public get products(): Product[] { return this._products; } } ``` ### 違反 讓我們描述一個實作產品過濾類別的場景。讓我們加入按顏色過濾產品的功能。 ``` class ProductsFilter { byColor(inventory: Inventory, color: Color): Product[] { return inventory.products.filter(p => p.color === color); } } ``` 我們已經測試了此程式碼並將其部署到生產中。 幾天后,客戶請求一項新功能 - 也按大小過濾。然後我們修改該類別以支援新的要求。 **現在違反了開閉原則!** ``` class ProductsFilter { byColor(inventory: Inventory, color: Color): Product[] { return inventory.products.filter(p => p.color === color); } bySize(inventory: Inventory, size: Size): Product[] { return inventory.products.filter(p => p.size === size); } } ``` ### 解決 在不違反 OCP 的情況下實現過濾機制的正確方法應該使用「規範」類別。 ``` abstract class Specification { public abstract isValid(product: Product): boolean; } class ColorSpecification extends Specification { private _color: Color; constructor (color) { super(); this._color = color; } public isValid(product: Product): boolean { return product.color === this._color; } } class SizeSpecification extends Specification { private _size: Size; constructor (size) { super(); this._size = size; } public isValid(product: Product): boolean { return product.size === this._size; } } // A robust mechanism to allow different combinations of specifications class AndSpecification extends Specification { private _specifications: Specification[]; // "...rest" operator, groups the arguments into an array constructor ((...specifications): Specification[]) { super(); this._specifications = specifications; } public isValid (product: Product): boolean { return this._specifications.every(specification => specification.isValid(product)); } } class ProductsFilter { public filter (inventory: Inventory, specification: Specification): Product[] { return inventory.products.filter(product => specification.isValid(product)); } } ``` #### 用法: ``` const p1: Product = new Product('Apple', Color.GREEN, Size.LARGE); const p2: Product = new Product('Pear', Color.GREEN, Size.LARGE); const p3: Product = new Product('Grapes', Color.GREEN, Size.SMALL); const p4: Product = new Product('Blueberries', Color.BLUE, Size.LARGE); const p5: Product = new Product('Watermelon', Color.RED, Size.LARGE); const inventory: Inventory = new Inventory(); inventory.addArray([p1, p2, p3, p4, p5]); const greenColorSpec: ColorSpecification = new ColorSpecification(Color.GREEN); const largeSizeSpec: SizeSpecification = new SizeSpecification(Size.LARGE); const andSpec: AndSpecification = new AndSpecification(greenColorSpec, largeSizeSpec); const productsFilter: ProductsFilter = new ProductsFilter(); const filteredProducts: Product[] = productsFilter.filter(inventory, andSpec); // All large green products ``` 過濾機制現在是完全可擴展的。現有的類別不應該再被修改。 如果有新的過濾要求,我們只需建立一個新規範即可。或者如果需要更改規範組合,可以透過使用`AndSpecification`類別輕鬆完成。 --- 3. L=里氏替換原理(LSP) ---------------- 里氏替換原則(LSP)是軟體元件靈活性和穩健性的重要規則。它由 Barbara Liskov 提出,並成為 SOLID 原則的基本要素。 LSP 規定**超類別的物件應該可以用子類別的物件替換,而不影響程式的正確性。**換句話說,子類別應該擴展超類別的行為而不改變其原始功能。採用這種方法可以提高軟體元件的質量,確保可重複使用性並減少意外的副作用。 ### 違反 下面的範例說明了違反里氏替換原則 (LSP) 的場景。當`Rectangle`物件被`Square`物件取代時,透過檢查程序的行為可以觀察到這種違規的跡象。 #### 聲明: ``` class Rectangle { protected _width: number; protected _height: number; constructor (width: number, height: number) { this._width = width; this._height = height; } get width (): number { return this._width; } get height (): number { return this._height; } set width (width: number) { this._width = width; } set height (height: number) { this._height = height; } getArea (): number { return this._width * this._height; } } // A square is also rectangle class Square extends Rectangle { get width (): number { return this._width; } get height (): number { return this._height; } set height (height: number) { this._height = this._width = height; // Changing both width & height } set width (width: number) { this._width = this._height = width; // Changing both width & height } } function increaseRectangleWidth(rectangle: Rectangle, byAmount: number) { rectangle.width += byAmount; } ``` #### 用法: ``` const rectangle: Rectangle = new Rectangle(5, 5); const square: Square = new Square(5, 5); console.log(rectangle.getArea()); // Expected: 25, Got: 25 (V) console.log(square.getArea()); // Expected: 25, Got: 25 (V) // LSP Violation Indication: Can't replace object 'rectangle' (superclass) with 'square' (subclass) since the results would be different. increaseRectangleWidth(rectangle, 5); increaseRectangleWidth(square, 5); console.log(rectangle.getArea()); // Expected: 50, Got: 50 (V) // LSP Violation, increaseRectangleWidth() changed both width and height of the square, unexpected behavior. console.log(square.getArea()); //Expected: 50, Got: 100 (X) ``` ### 解決 重構的程式碼現在遵循 LSP,確保超類別`Shape`的物件可以替換為子類別`Rectangle`和`Square`的物件,而不會影響計算面積的正確性,也不會引入任何改變程式行為的不必要的副作用。 #### 聲明: ``` abstract class Shape { public abstract getArea(): number; } class Rectangle extends Shape { private _width: number; private _height: number; constructor (width: number, height: number) { super(); this._width = width; this._height = height; } getArea (): number { return this._width * this._height; } } class Square extends Shape { private _side: number; constructor (side: number) { super(); this._side = side; } getArea (): number { return this._side * this._side; } } function displayArea (shape: Shape): void { console.log(shape.getArea()); } ``` #### 用法: ``` const rectangle: Rectangle = new Rectangle(5, 10); const square: Square = new Square(5); // The rectangle's area is correctly calculated displayArea(rectangle); // Expected: 50, Got: 50 (V) // The square's area is correctly calculated displayArea(square); // Expected: 25, Got: 25 (V) ``` --- 4. I = 介面隔離原則 (ISP) ------------------- 介面隔離原則 (ISP) 強調建立特定於客戶端的介面而不是一刀切的重要性。 這種方法根據客戶的需求集中類,消除了類別必須實現它實際上不使用或不需要的方法的情況。 透過應用介面隔離原則,軟體系統可以以更靈活、易於理解和易於重構的方式建構。讓我們來看一個例子。 ### 違反 這裡違反了 ISP 規則,因為`Robot`必須實現完全沒有必要的`eat()`函數。 ``` interface Worker { work(): void; eat(): void; } class Developer implements Worker { public work(): void { console.log('Coding..'); } public eat(): void { console.log('Eating..'); } } class Robot implements Worker { public work(): void { console.log('Building a car..'); } // ISP Violation: Robot is forced to implement this function even when unnecessary public eat(): void { throw new Error('Cannot eat!'); } } ``` ### 解決 下面的範例代表了我們之前遇到的問題的解決方案。現在,介面更加簡潔且更加特定於客戶端,允許客戶端類別僅實現與其相關的方法。 ``` interface Workable { work(): void; } interface Eatable { eat(): void; } class Developer implements Workable, Eatable { public work(): void { console.log('Coding..'); } public eat(): void { console.log('Eating...'); } } class Robot implements Workable { public work(): void { console.log('Building a car..'); } // No need to implement eat(), adhering ISP. } ``` #### ISP 前後: ![重構前後的介面隔離原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szh5wkpkc30z4t91j5oy.png) --- 5. D = 依賴倒置原理(DIP) ------------------ 依賴倒置原則(DIP)是最終的SOLID原則,重點是透過使用抽象來減少低層模組(例如資料讀取/寫入)和高層模組(執行關鍵操作)之間的耦合。 DIP 對於設計能夠適應變化、模組化且易於更新的軟體至關重要。 **DIP 的關鍵準則是:** 1. **高層模組不應該依賴低層模組。兩者都應該依賴抽象。**這意味著應用程式的功能不應該依賴特定的實現,以便使系統更加靈活並且更容易更新或替換低階實現。 2. **抽像不應該依賴細節。細節應該取決於抽象。**這鼓勵設計專注於實際需要什麼操作,而不是如何實現這些操作。 ### 違反 讓我們來看一個展示依賴倒置原則 (DIP) 違規的範例。 `MessageProcessor` (高階模組)緊密耦合並直接依賴`FileLogger` (低階模組),違反了原則,因為它不依賴抽象層,而是依賴特定的類別實作。 **額外獎勵:**這也違反了開閉原則(OCP)。如果我們想要更改日誌記錄機制以寫入資料庫而不是文件,我們將被迫直接修改`MessageProcessor`函數。 ``` import fs from 'fs'; // Low Level Module class FileLogger { logMessage(message: string): void { fs.writeFileSync('somefile.txt', message); } } // High Level Module class MessageProcessor { // DIP Violation: This high-level module is is tightly coupled with the low-level module (FileLogger), making the system less flexible and harder to maintain or extend. private logger = new FileLogger(); processMessage(message: string): void { this.logger.logMessage(message); } } ``` ### 解決 以下重構的程式碼表示為了遵守依賴倒置原則 (DIP) 所需進行的變更。與前面的範例相反,高階類別`MessageProcessor`持有特定低階類別`FileLogger`的私有屬性,現在它持有`Logger`類型的私有屬性(表示抽象層的介面)。 這種更好的方法減少了類別之間的依賴關係,從而使程式碼更具可擴展性和可維護性。 #### 聲明: ``` import fs from 'fs'; // Abstraction Layer interface Logger { logMessage(message: string): void; } // Low Level Module #1 class FileLogger implements Logger { logMessage(message: string): void { fs.writeFileSync('somefile.txt', message); } } // Low Level Module #2 class ConsoleLogger implements Logger { logMessage(message: string): void { console.log(message); } } // High Level Module class MessageProcessor { // Resolved: The high level module is now loosely coupled with the low level logger modules. private _logger: Logger; constructor (logger: Logger) { this._logger = logger; } processMessage (message: string): void { this._logger.logMessage(message); } } ``` #### 用法: ``` const fileLogger = new FileLogger(); const consoleLogger = new ConsoleLogger(); // Now the logging mechanism can be easily replaced const messageProcessor = new MessageProcessor(consoleLogger); messageProcessor.processMessage('Hello'); ``` #### DIP 之前和之後: ![重構前後的依賴倒置原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kju8qfpie60b104vuz90.png) 結論 -- 透過遵循 SOLID 原則,開發人員在開發或維護任何規模的軟體系統時,可以避免常見的陷阱,例如緊密耦合、缺乏靈活性、程式碼可重複使用性差以及一般維護困難。掌握這些原則是成為更好的軟體工程師的又一步。 --- 原文出處:https://dev.to/idanref/solid-the-5-golden-rules-to-level-up-your-coding-skills-2p82