🔍 搜尋結果:Line

🔍 搜尋結果:Line

CSS 行高的工作原理和最佳實踐

CSS 屬性`line-height`定義兩個內嵌元素之間的間距。典型用途是分隔文字。您可以看到人們將其與“前導”進行比較,“前導”是印刷術中使用的術語,指兩行文本的基線之間的空間。 `line-height`工作方式不同。它在文字上方和下方加入了空間。 ![行距與行高](https://thepracticaldev.s3.amazonaws.com/i/lsrnzx1hin6mkqg6fagw.png) 左:行距,右:行高 用法 -- 您可以使用具有不同值的`line-height`如下所示: ``` body { line-height: normal; /* default */ line-height: 2; line-height: 1em; line-height: 1rem; line-height: 200%; line-height: 20px; } ``` 噢,孩子😧!好多啊。讓我們一一分析一下👍。 ### 預設值和無單位值 如果您不將其設定為不同的值,則「正常」是預設值。通常,這意味著它設定為`1.2` ,這取決於瀏覽器供應商。那麼只有一個數字值而沒有任何單位是什麼意思呢?它實際上是一個乘數。它採用`font-size`值並將其乘以`1.2` 。讓我們用下面的例子來計算一條線的高度。 ``` body { font-size: 16px; line-height: 1.5; } ``` 我們只需要做以下計算:16 \* 1.5 = 24px。現在我們知道文字的最小高度為 24 像素。因此它將在文字下方和上方加入 4 個像素。酷就這麼簡單😎! ### em 和 rem 下一個是`em`和`rem` 。 `rem`相對於根元素的`font-size` , `em`相對於目前元素的 font-size。這是一個例子 ``` html { font-size: 12px; } .remExample { font-size: 16px; line-height: 1.5rem; /* line-height will be 12 * 1.5 = 18px */ } .emExample { font-size: 16px; line-height: 1.5em; /* line-height will be 16 * 1.5 = 24px */ } ``` ### 百分比 `%`值有點難以閱讀。 100%意味著乘以1。再次舉例來說明這一點。 ``` body { font-size: 12px; } .percentage { font-size: 16px; line-height: 150%; /* line-height will be 16 * 1.5 = 24px */ } ``` ### 像素(px) 對我來說最簡單也是最令人困惑的是`px`值。將其設為任何像素值都會將其精確地設定為該值。因此,如果您的`font-size`為 16px,並且您將`line-height`設為 12px,您的字體將比它所包裝的容器更大。一般來說,您應該盡量避免在行高中使用`px`值! ``` body { font-size: 16px; } .pixel { line-height: 12px; } ``` ### 一些最佳實踐 一般來說,我先將`body`元素中的`font-size`和`line-height`設定為以下值。 ``` body { font-size: 16px; line-height: 1.5; } ``` 由此,您可以建立所有其他樣式。我會盡量避免使用無單位數字以外的任何東西。另外,嘗試使用易於分割的`font-size`值,例如 16 或 12。這將有助於您在設計中保持平衡。您也可以在`margin`和`padding`中使用它。在腦中計算 16 \* 1.5 比 13 \* 1.5 更容易。然後您將永遠知道實際價值是多少。 ``` body { font-size: 16px; line-height: 1.5; } h1, h2, h3, h4, ul, ol { margin-bottom: 15rem; } button { display: inline-block; padding: 0.75rem 1.5rem; } ``` 當然,你可以嘗試一下,這些規則也會有例外,但我總是這樣開始。 資源 -- - http://www.indesignskills.com/tutorials/leading-typography/ - https://developer.mozilla.org/en-US/docs/Web/CSS/line-height - https://www.w3schools.com/cssref/pr\_dim\_line-height.asp - https://css-tricks.com/almanac/properties/l/line-height/ - https://ux.stackexchange.com/questions/35270/is-there-an-optimal-font-size-line-height-ratio **謝謝閱讀!** --- 原文出處:https://dev.to/lampewebdev/css-line-height-jjp

Git Good:分支命名和 commit 訊息的最佳實踐

開發者們大家好,我將分享一些更有效地使用 Git 的最佳實踐。**Git?**是的,就是你已經熟悉的 git。程式碼夥伴是您的守護天使,可確保您的程式設計冒險順利進行,讓開發人員能夠輕鬆地進行專案協作。 你是那個建立分支然後忘記它們存在的原因的人嗎?總是需要尋找*文件變更*才能理解提交?這裡有一些建議供您參考。 我為什麼要遵循標準? ---------- - **清晰和理解** - **協作與團隊合作** - **易於導航和維護** - **文件和知識轉移** - **工程品質** - **自動化變更日誌** - **優化 CI/CD** 在你讀之前 ----- - **分支命名約定**和**提交訊息**約定下的每個 seb 部分分別按**基本**、**中階**和**進階規則**排序。 - 您可以根據用例和相關性遵循任何層級的規則,無論如何,建議遵循**中間規則**層級的約定。 - 以下內容根據參考部分提供的資源進行改編和組織。 --- 分支命名約定 ------ ### 基本 1. **描述性名稱**:一個命名良好的分支可以為其目的提供直接的上下文。選擇清晰的名稱,而不是通用名稱。 例如: `feature/login` 、 `bugfix/navbar-overflow` 2. **使用連字號**:使用連字號分隔分支名稱中的單字(或短橫線),這確保了可讀性。 例如, `bugfix/fix-login-issue`比`bugfix/fixLoginIssue`或`bugfix/fix_login_issue`更具可讀性。 3. **小寫字母數字字元**:僅使用小寫字母數字字元(az、0–9)和連字號。盡可能避免標點符號、空格、底線或任何特殊字元。 4. **避免不必要的連字符**:避免不必要的連字符,例如後續或尾隨的連字符。 例如, `feat/new--login-`是一種不好的做法。 5. **簡短有效**:保持分支名稱簡單。在描述性的同時,它們也應該足夠簡潔,以便一目了然地傳達目標。 ### 前綴或類型 - 為分支加入前綴有助於根據其用途來組織它們。這不僅提高了清晰度,還有助於自動化工作流程。 - 一些常見的分支類型前綴及其用例包括: - `feature/` :用於開發新功能, - `bugfix/` :修復程式碼中的錯誤。通常是與某個問題相關的。 - `hotfix/` :修復生產中的關鍵錯誤。 - `release/` :準備新版本,通常用於執行最後修改和修訂等任務。 - `docs/` :用於撰寫、修改或修正文件。 - 例如, `feature/new-feature`或`release/version-1.0.0` 。 ### 包括票號 如果您的專案使用 Jira 等問題追蹤系統,或者它根據 github 問題或其他類似工具進行修改。在分支名稱中包含問題的令牌將使追蹤變得簡單。範例: `feature/PROJ-123-footer-links` --- 提交訊息約定 ------ - 提交訊息的最終格式: ``` <type>([optional scope]): <description> # subject [optional body] [optional footer(s)] ``` ### 資訊主題 - **命令式**:以命令式建立提交訊息。以動詞開頭來指示提交的作用。 例如:使用`fix: Fix bug #67`比`fix: Fixed bug #67` - **簡短總結**:嘗試將主題行控制在 50 個字元以內,以確保訊息在各種 Git 工具中可讀,例如使用`git log --oneline`時。避免尾隨句點和不必要的單字/符號。 - **將描述大寫**:這聽起來很簡單。主題行以大寫字母開頭。 ### 類型和訊息正文 - 主題行中的`type`前綴可用於表示提交中包含的變更的類型。一些常用的類型是: - `feat:` :總結程式碼庫中的新功能。 - `fix:` :修復程式碼庫。 - `build:` 、 `chore:` 、 `ci:` 、 `style:` 、 `refactor:`是其他一些例子。 - 可以將`scope`加入到提交的類型中以提供額外的上下文訊息,並將其括在括號中,例如`feat(parser): Add the ability to parse arrays` - 可以將`body`新增至訊息中,以在提交中包含詳細說明。 - 在主題行後面留空行來加入正文。 - 將正文包裹在 72 個字元處,即。使用多行正文,每行不超過 72 個字元。 頁腳和擴充訊息正文 --------- - `footer`用於傳達有關提交的附加訊息,例如審查者、批准者等。例如: - `Signed-off-by: Alice <[email protected]>` - **重大變更**: `BREAKING CHANGE`是指程式碼庫中相當重要的重大變更。可以透過加`!`來表示。在類型/範圍之後和/或透過將其加入到`body`或作為`footer` ``` chore!: drop support for Node 6 BREAKING CHANGE: use JavaScript features not available in Node 6. ``` - **多段落正文**:在某些情況下,多個段落有助於更詳細地解釋提交的目標。範例:描述提交更改的內容、原因以及方式。 #### 例子 可以[在此處](https://www.conventionalcommits.org/en/v1.0.0/#examples)找到提交訊息的各種用例範例。 結論 -- 遵守 Git 約定類似於說通用語言。然而,很明顯,這些標準或約定並沒有被任何系統強制執行;因此,這些標準的調整和擴展使用完全取決於我們。 養成這些習慣肯定會改善您的 Git 體驗並鼓勵協作編碼環境。儘管我們不能一下子就遵循這些,但逐漸適應它們無疑會有所不同。 我計劃寫一篇關於**使用 Husky 實現 Git 約定的文章**,透過反應和評論來表達您的支持。快樂編碼! 參考 -- - [Git 分支的命名約定 — 備忘單,作者:Abhay Amin](https://medium.com/@abhay.pixolo/naming-conventions-for-git-branches-a-cheatsheet-8549feca2534) - [常規提交](https://www.conventionalcommits.org/en/v1.0.0) - [Git 約定](https://se-education.org/guides/conventions/git.html) - [如何透過 CBEAMS 撰寫 Git 提交訊息](https://cbea.ms/git-commit) - [Git 預告片](https://git-scm.com/docs/git-interpret-trailers) --- 原文出處:https://dev.to/shinjithdev/git-good-best-practices-for-branch-naming-and-commit-messages-oj4

css 表格樣式指南

我最近注意到一個小悖論:很多年前——在 CSS 網格出現之前——我們使用`<table>`來模擬網格佈局。現在我們*有了*網格佈局,我們用它們來模擬表格!這是錯誤的。表格用於**表格**資料;在一堆`<div>`中呈現表格資料是沒有意義的。 造成這種弊端的原因可能是因為表格的樣式可能**有點**棘手,而且大多數 CSS 框架使用`border-collapse: collapse`作為預設的表格樣式。正如我們將在本教程中看到的,折疊邊框對於表格樣式並不總是有用。 讓我們研究一下`<table>`的元素,然後了解如何建立它們並設定它們的樣式。 元素 -- 除了`<table>`元素本身之外,您只需要這 3 個標籤來建立基本表格: |標籤 |描述 | | ---- | ---| | `td` |表資料單元 | | `th` |表格標題儲存格 | | `tr` |表行| *例子:* ``` <table> <tr><th>Header</th></tr> <tr><td>Content</td></tr> </table> ``` 但是,為了更好地構造表,我們可以將行封裝在: |標籤 |描述 | | -------- | ---| | `thead` |表頭 | | `tbody` |桌體| | `tfoot` |表頁腳| 最後,我們可以在表格中新增`<caption>` ,並在`<colgroup>`內的`<col>`標籤中定義列。 *例子:* ``` <table> <caption>Super Heroes</caption> <colgroup><col><col><col><col></colgroup> <thead> <tr><th>First Name</th><th>Last Name</th><th>Known As</th><th>Place</th></tr> </thead> <tbody> <tr><td>Bruce</td><td>Wayne</td><td>Batman</td><td>Gotham City</td></tr> <tr><td>Clark</td><td>Kent</td><td>Superman</td><td>Metropolis</td></tr> <tr><td>Tony</td><td>Stark</td><td>Iron Man</td><td>Malibu</td></tr> <tr><td>Peter</td><td>Parker</td><td>Spider-Man</td><td>New York City</td></tr> <tr><td>Matt</td><td>Murdock</td><td>Daredevil</td><td>New York City</td></tr> </tbody> </table> ``` 如果沒有任何樣式,您的瀏覽器將呈現以下內容: ![基本表格瀏覽器樣式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iq375md00ma4g8bikaqj.png) 預設的用戶代理樣式是: ``` table { border-collapse: separate; text-indent: initial; border-spacing: 2px; } ``` 現在,如果我們加入一個超級簡單的規則: ``` :is(td,th) { border-style: solid; } ``` 我們得到: ![實心邊框基本表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9o8sa2igklyxz78ec2ij.png) 注意單獨的邊框。看起來不太好看... 因此,為了了解折疊邊框的流行(以及更好的字體!),如果我們簡單地加入: ``` table { border-collapse: collapse; font-family: system-ui; } ``` ……我們得到: ![border-collapse 設定為折疊](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/beia4yujksm8c5945o7s.png) 如果我們然後將`padding: .5ch 1ch`加入我們的`:is(td,th)`選擇器並將`margin-block: 1rlh`加入`<caption>` ,我們會得到: ![基本表格樣式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6tai5jys59g9q0sg3t5w.png) 回顧一下,我們**需要**得到上述樣式的是: ``` table { border-collapse: collapse; font-family: system-ui; & caption { margin-block: 1rlh; } &:is(td, th) { border-style: solid; padding: .5ch 1ch; } } ``` 若要將`<caption>`放置在表格*下方*,請使用: ``` table { caption-side: bottom; } ``` --- 斑馬條紋 ---- 要為**columns**加入奇數/偶數斑馬條紋,我們可以簡單地設定`<col>`標籤的樣式: ``` col:nth-of-type(even) { background: #F2F2F2; } ``` ![斑馬山口](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mlnajfbyvxsgzlgbntge.png) 對於行,它是類似的: ``` tr:nth-of-type(odd) { background: #F2F2F2; } ``` ![斑馬行](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p47mwf3x6dp8n5qit72d.png) --- 圓角 -- 圓角有點棘手。您不能只將`border-radius`新增至`<table>` ,因此我們必須定位**第一行**和**最後**一行的**第**一個和**最後**一個儲存格: ``` th { &:first-of-type { border-start-start-radius: .5em } &:last-of-type { border-start-end-radius: .5em } } tr { &:last-of-type { & td { &:first-of-type { border-end-start-radius: .5em } &:last-of-type { border-end-end-radius: .5em } } } } ``` ……但仍然沒有發生任何事情!那是因為: > 如果您的表格有折疊邊框,則**無法**新增`border-radius` 。 因此,我們必須使用**單獨的**邊框,並*模仿*折疊的邊框: ``` table { border-spacing: 0; } :is(td, th) { border-block-width: 1px 0; border-inline-width: 1px 0; &:last-of-type { border-inline-end-width: 1px } } ``` **現在**我們有了圓角: ![圓角](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/172o5rqyidiqisopyrx6.png) --- 拆分列 --- 讓我們*保留*單獨的列,並使用`border-spacing`屬性在列之間新增間隙: ``` table { border-spacing: 2ch 0; & :is(td, th) { border-inline-width: 1px; } } ``` ![拆分列](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uplqw0uurk0yk7ot2k1k.png) 我們甚至可以加入`border-radius` : ![邊界半徑](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldecau6tm4elj68hz7id.png) 這仍然只是一個`<table>` ,但如果用作“比較表”,則更具可讀性。 --- 分割行 --- 對於分割行,我們只需要更新`border-spacing`屬性的第二部分(y 軸): ``` table { border-spacing: 0 2ch; & :is(td, th) { border-block-width: 1px; } } ``` ![分割行](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q118cu966qesqqyir6gc.png) --- 懸停和焦點 ----- 對於大桌子,準確了解您所在的*位置*非常重要。為此,我們需要`:hover` ,並且 - 如果您使用的是鍵盤可導航的表格 - `:focus-visble` -styles。 在此範例中,懸停樣式會套用於`<col>` 、 `<tr>`和`<td>` : ![表格懸停範例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zy9wrlzfvt5us63ip8ae.png) 懸停行和單元格很簡單: ``` td:hover { background: #666666; } tr:hover { background: #E6E6E6; } ``` 將滑鼠懸停在`<col>`上有點複雜。 您可以新增一條規則: ``` col:hover { background: #E6E6E6; } ``` ...但這不起作用。奇怪的是,如果您在開發工具中選擇一個 col-element 並為其啟用`:hover` ,它會起作用 - 但在 IRL 中不起作用。 相反,我們需要使用`:has`捕獲單元格的懸停,*然後*設定`<col>`元素的樣式: ``` table { &:has(:is(td,th):nth-child(1):hover col:nth-child(1) { background: #E6E6E6; } ``` 發生什麼事了? 讓我們來分解一下: 如果我們的表格**有**一個`<td>`*或*`<th>` ,它是`nth-child(1)`並且當前*懸停*,**則**選擇具有**相同**`nth-child`選擇器的`<col>` ,並設定它的`background` 。 唷! ……**並且**您需要為每一列重複此程式碼: `nth-child(2)` 、 `nth-child(3)`等。 --- 概要 -- 在懸停時顯示輪廓也很簡單,單元格和行也是如此。您需要從偏移量中*扣除*寬度: ``` :is(td, th, tr):hover { outline: 2px solid #666; outline-offset: -2px; } ``` ![表格懸停:輪廓](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4ump8dctc5q6yrpdqif.png) ### 列輪廓 概述一列*非常*棘手,但看起來不錯: ![表格懸停:大綱列](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o8om2x4gtsizjl7bwj30.png) 如果單元格的`border-width`為`1px` ,您可以在懸停時將`<col>`的`border-width`設為`2px` ,但隨後整個表格會發生變化。 Álvaro Montoro 建議在`<col>`上使用背景漸變來模擬邊框,如果表格單元格是透明的,效果很好。 為了使其與`border-radius`一起工作並保留單元格可能具有的任何背景,我最終為每個單元格使用了一個偽元素: ``` :is(td,th) { position: relative; &::after { border-inline: 2px solid transparent; border-radius: inherit; content: ''; inset: -2px 0 0 0; position: absolute; } } tr:first-of-type th::after { border-block-start: 2px solid transparent; } tr:last-of-type td::after { border-block-end: 2px solid transparent; } ``` ……然後,與我們對 col-hover 所做的類似,在懸停時將所有單元格定位為具有相同的“col-index”: ``` :has(:is(td,th):nth-child(1):hover :is(td,th):nth-child(1) { border-color: #666; } ``` 對所有列重複此操作。 --- 對齊文字 ---- 在舊規範中,您可以為`<col>`元素新增`align`屬性。那不再起作用了。 範例:您想要將第二列中的文字置中並右對齊第四列中的文字: ![表:對齊文字](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/659ffi0cieppfs3p9xvf.png) 我們可以在表本身中新增*每*列的資料屬性,而不是在每個單元格中新增一個類別: ``` <table data-c2="center" data-c4="end"> ``` 然後,在 CSS 中: ``` [data-c2~="center"] tr > *:nth-of-type(2) { text-align: center; } [data-c4~="end"] tr > *:nth-of-type(4) { text-align: end; } ``` 對所有列重複此操作。 --- 結論 -- 表格樣式指南到此結束。 我沒有介紹`colspan` 、 `rowspan` 、 `scope`和`span` 。如果您想更深入地了解這些內容,我建議您閱讀[有關表的 MDN 頁面](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)。 示範 -- 我在這裡製作了一個包含大量演示的 CodePen: https://codepen.io/stoumann/pen/RwdVxJM --- 原文出處:https://dev.to/madsstoumann/a-guide-to-styling-tables-28d2

C/C++ 中的雙指標

新 C 程式設計師的困惑來源之一是指標。乍一看,使用它們似乎沒有多大用處。但是,理解指標至關重要,因為它是一個有用的工具,任何比「Hello, World」程式更大的專案都會有指標。一旦初學者開始掌握指針的概念和適用性,另一波恐怖浪潮就會深入他們的內心:雙指針。 在繼續之前,讓我們回顧一下指標是什麼。 在 C 和其他語言(如 C++)中,指標是保存物件記憶體位址的東西。它們是一個數值,當輸出到控制台時,它們通常以十六進位顯示。這意味著,本質上,指針只是奇特的整數。 現在回到雙指針。 當初學者看到雙指針時,就會開始不舒服地移動,汗水從額頭一直流到下巴。就像生活中其他一切新鮮或陌生的事物一樣,人們一想到要面對以前從未遇到過的事物,就會感到尷尬和不安,即使只是輕微的。但隨著你了解的越多,你開始對自己對想要征服的任何事物的新理解充滿信心。雙指針也是如此。 那麼,什麼是雙指針呢?那麼,如果常規指標要引用記憶體中的物件,那麼雙指標就是指向另一個指標的變數,而另一個指標又指向記憶體中的物件。 例子: ``` #include <stdio.h> int main(void) { int value = 100; int *value_ptr = &value; int **value_double_ptr = &value_ptr; printf("Value: %d\n", value); printf("Pointer to value: %d\n", *value_ptr); printf("Double pointer to value: %d\n", **value_double_ptr); } ``` 輸出: ``` ~/Desktop ➜ clang main.c ~/Desktop ➜ ./a.out Value: 100 Pointer to value: 100 Doublue pointer to value: 100 ~/Desktop ➜ ``` 當取消引用雙指標時,我們不會得到最終物件,而是得到預期的結果:必須再次取消引用才能檢索最終值的指標。它類似於下面的程式碼。 ``` int *ptr = *value_double_ptr; int final_value = *ptr; ``` 現在我確信大多數剛接觸雙指標的人一定會問自己這樣的問題:「我將在哪裡使用它?」。也許展示雙指標有用性的最佳方法是記住常規指標的實際用途之一。這就是說,常規指標可以用作函數中的輸出參數。如果您不知道的話,輸出參數是一個填充結果的參數。為什麼要使用輸出參數取決於幾個因素,並且超出了本文的範圍,但我覺得這就是可以利用雙指標的地方。 我們將要了解的一個函數是 POSIX `getline()`函數。其目的是從文件中讀取一行。當讀取該行時,人們可能會懷疑它被返回,但是,情況並非如此,因為返回值用於不同的用途。相反,第一個參數採用雙指針,填入包含該行的緩衝區。 為什麼需要雙指針?因此,它可以分配足夠的記憶體來保存整行加上空終止符。如果記憶體分配和讀取一行成功,則提供的指標將被修改為指向新的且已填入的緩衝區。只有使用雙指針,他們才能實現這一目標。 將任何內容傳遞給函數時,傳遞的是副本而不是實際物件。指針也是如此。如果我們傳遞一個常規指針,我們只能修改該指針指向的內容。如果我們更改指標本身,則更改不會反映在函數外部,因為它是副本。 如果我們想要改變指標指向的位置,我們必須新增另一個間接定址並採用雙指標。 使用`getline()`的範例如下。 ``` #include <stdio.h> int main(void) { FILE *file = fopen("test", "r"); char *line = 0; int line_length = 0; getline(&line, &line_length, file); printf("%s\n", line); printf("Line length: %d\n", line_length); } ``` `getline()`有兩個輸出參數:一個用於緩衝區,另一個用於讀入的行的長度。 概括 -- 雙指針對許多初學者來說是一個挑戰。當您對雙指標的概念越來越熟悉並在自己的程式碼中必要時使用它們時,您開始認為曾經害怕雙指標是多麼愚蠢。 現在我將帶著你的新知識送你走! --- 原文出處:https://dev.to/noah11012/double-pointers-in-cc-2n96

堅實的原則:它們堅如磐石是有充分理由的!

剛開始接觸**物件導向編程**,對**SOLID**感到有點迷失?不用擔心,在本文中,我將向您解釋它並提供如何在程式碼開發中使用它的範例。 什麼是固體? ------ 在物件導向程式設計中, **SOLID**是五個設計原則的縮寫,旨在增強對軟體的理解、開發和維護。 透過應用這組原則,您應該注意到錯誤的減少、程式碼品質的提高、程式碼組織性更強、耦合性降低、重構增強以及程式碼重用的鼓勵。讓我們來看看他們。 1. S-單一職責原則 ----------- ![建議零售價](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v564d0p0s36fwo6imofo.png) > SRP - 單一職責原則 這確實很簡單,但非常重要:**一個類別應該有一個且只有一個改變的理由。** 不再建立具有多種功能和職責的類,是嗎?您可能遇到甚至建立了一個可以完成所有操作的類,即所謂的*God Class* 。目前看起來似乎沒問題,但是當您需要更改該類別的邏輯時,肯定會出現問題。 > 上帝類別:在OOP中,這是一個`do`或`knows`太多事情的類別。 ``` class ProfileManager { authenticateUser(username: string, password: string): boolean { // Authenticate logic } showUserProfile(username: string): UserProfile { // Show user profile logic } updateUserProfile(username: string): UserProfile { // Update user profile logic } setUserPermissions(username: string): void { // Set permission logic } } ``` 此**ProfileManager**類別執行**四個**不同的任務,違反了 SRP 原則。它正在驗證和更新資料、進行演示,最重要的是,它正在設定權限,所有這些都是同時進行的。 ### 這可能導致的問題 - `Lack of cohesion -`一個類別不應該承擔不屬於它自己的責任; - `Too much information in one place -`你的類別最終會產生許多依賴性並且難以進行更改; - `Challenges in implementing automated tests -`很難模擬這樣的類別。 現在,將**SRP**應用到`ProfileManager`類別中,讓我們來看看這個原則可以帶來的改進: ``` class AuthenticationManager { authenticateUser(username: string, password: string): boolean { // Authenticate logic } } class UserProfileManager { showUserProfile(username: string): UserProfile { // Show user profile logic } updateUserProfile(username: string): UserProfile { // Update user profile logic } } class PermissionManager { setUserPermissions(username: string): void { // Set permission logic } } ``` 您可能想知道, `can I apply this only to classes?`答案是:**完全不是**。您也可以(並且應該)將其應用於方法和函數。 ``` // ❌ function processTasks(taskList: Task[]): void { taskList.forEach((task) => { // Processing logic involving multiple responsibilities updateTaskStatus(task); displayTaskDetails(task); validateTaskCompletion(task); verifyTaskExistence(task); }); } // ✅ function updateTaskStatus(task: Task): Task { // Logic for updating task status return { ...task, completed: true }; } function displayTaskDetails(task: Task): void { // Logic for displaying task details console.log(`Task ID: ${task.id}, Description: ${task.description}`); } function validateTaskCompletion(task: Task): boolean { // Logic for validating task completion return task.completed; } function verifyTaskExistence(task: Task): boolean { // Logic for verifying task existence return tasks.some((t) => t.id === task.id); } ``` 美麗、優雅、有組織的程式碼。這個原則是其他原則的基礎;透過應用它,您應該建立高品質、可讀且可維護的程式碼。 2. O——開閉原則 ---------- ![OCP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/epfur4p9r55iwbk9i4yy.png) > OCP-開閉原則 **物件或實體應該對擴充開放,但對修改關閉。**如果您需要加入功能,最好擴展而不是修改原始程式碼。 想像一下,您需要一個類別來計算某些多邊形的面積。 ``` class Circle { radius: number; constructor(radius: number) { this.radius = radius; } area(): number { return Math.PI * this.radius ** 2; } } class Square { sideLength: number; constructor(sideLength: number) { this.sideLength = sideLength; } calculateArea(): number { return this.sideLength ** 2; } } class areaCalculator { totalArea(shapes: Shape[]): number { let total = 0; shapes.forEach((shape) => { if (shape instanceof Square) { total += (shape as any).calculateArea(); } else { total += shape.area(); } }); return total; } } ``` `areaCalculator`類別的任務是計算不同多邊形的面積,每個多邊形都有自己的面積邏輯。如果您,'lil dev,需要加入新形狀,例如三角形或矩形,您會發現自己**需要**更改此類來進行更改,對吧?這就是你遇到問題的地方,違反了`Open-Closed Principle` 。 我想到了什麼解決方案?可能會在類別中加入另一個方法並完成,問題解決了🤩。不完全是,年輕學徒😓,這就是問題所在! **修改現有類別以新增行為會帶來嚴重的風險,可能會將錯誤引入到已執行的內容中。** > 請記住:OCP 堅持認為類別應該對修改關閉,對擴展開放。 看看重構程式碼帶來的美妙之處: ``` interface Shape { area(): number; } class Circle implements Shape { radius: number; constructor(radius: number) { this.radius = radius; } area(): number { return Math.PI * this.radius ** 2; } } class Square implements Shape { sideLength: number; constructor(sideLength: number) { this.sideLength = sideLength; } area(): number { return this.sideLength ** 2; } } class AreaCalculator { totalArea(shapes: Shape[]): number { let total = 0; shapes.forEach((shape) => { total += shape.area(); }); return total; } } ``` 查看`AreaCalculator`類別:它不再需要知道要呼叫哪些方法來註冊該類別。它可以透過呼叫介面強加的契約來正確地呼叫區域方法,這是它唯一需要的。 > 只要它們實作了 Shape 接口,一切就可以正常運作。 &lt;br/&gt; > 分離介面背後的可擴展行為並反轉依賴關係。 &gt; > [鮑伯叔叔](https://en.wikipedia.org/wiki/Robert_C._Martin) - `Open for extension:`您可以為類別新增功能或行為,而無需變更其原始程式碼。 - `Closed for modification:`如果您的類別已經具有可以正常工作的功能或行為,請不要更改其原始程式碼以加入新內容。 3. L——里氏代換原理 ------------ ![LSP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62uow23fsa5zk2wz5fz5.png) > LSP - 里氏替換原理 里氏替換原則指出**衍生類別必須可替換其基底類別。** 這個原則由 Barbara Liskov 在 1987 年提出,閱讀她的解釋可能會有點複雜。不過,不用擔心,我將提供另一個解釋和範例來幫助您理解。 > 如果對於 S 類型的每個物件 o1 都有一個 T 類型的物件 o2,使得對於所有用 T 定義的程式 P,當 o1 取代 o2 時 P 的行為保持不變,則 S 是 T 的子類型。 &gt; > 芭芭拉‧利斯科夫,1987 你做對了?不,可能不是。是的,我第一次讀時不明白(接下來的一百遍也不明白),但等等,還有另一種解釋: > 如果 S 是 T 的子類型,則程式中類型 T 的物件可以用類型 S 的物件替換,而不改變該程式的屬性。 &gt; > [維基百科](https://en.wikipedia.org/wiki/Liskov_substitution_principle) 如果您更喜歡視覺學習者,請不要擔心,這裡有一個例子: ``` class Person { speakName() { return "I am a person!"; } } class Child extends Person { speakName() { return "I am a child!"; } } const person = new Person(); const child = new Child(); function printName(message: string) { console.log(message); } printName(person.speakName()); // I am a person! printName(child.speakName()); // I am a child! ``` 父類別和衍生類別作為參數傳遞,程式碼繼續按預期工作。魔法?是的,這就是我們的朋友倒鉤的魔力。 ### 違規行為範例: - 重寫/實作一個不執行任何操作的方法; - 從基類傳回不同類型的值。 - 拋出意外的異常; 4. I——介面隔離原則 ------------ ![網際網路服務供應商](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qbdmugukrcacuqthho5x.png) > ISP-介面隔離原則 這句話說**不應該強迫一個類別實作它不使用的介面和方法。**建立更具體的介面比建立大而通用的介面更好。 在下面的範例中,建立一個**Book**介面來抽象化書籍行為,然後類別實作該介面: ``` interface Book { read(): void; download(): void; } class OnlineBook implements Book { read(): void { // does something } download(): void { // does something } } class PhysicalBook implements Book { read(): void { // does something } download(): void { // This implementation doesn't make sense for a book // it violates the Interface Segregation Principle } } ``` 通用`Book`介面迫使`PhysicalBook`類別做出毫無意義的行為(*或者我們在 Matrix 中下載實體書籍?* )並且違反了**ISP**和**LSP**原則。 使用**ISP**解決此問題: ``` interface Readable { read(): void; } interface Downloadable { download(): void; } class OnlineBook implements Readable, Downloadable { read(): void { // does something } download(): void { // does something } } class PhysicalBook implements Readable { read(): void { // does something } } ``` 現在好多了。我們從`Book`介面中刪除了`download()`方法,並將其加入到派生介面`Downloadable`中。這樣,行為就可以在我們的上下文中正確隔離,並且我們仍然尊重**介面隔離原則**。 5.D-依賴倒置原則 ---------- ![沾](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/42aomc36xq804pyldlis.png) > DIP - 依賴倒置原理 這個是這樣的:**依賴抽象而不是實現。** > 高層模組不應該依賴低層模組。兩者都應該依賴抽象。 &gt; > 抽像不應該依賴細節。細節應該取決於抽象。 &gt; > 鮑伯叔叔 現在我將展示一個簡單的程式碼來說明 DIP。在此範例中,有一個從資料庫取得使用者的服務。首先,讓我們建立一個與資料庫連接的具體類別: ``` // Low-level module class MySQLDatabase { getUserData(id: number): string { // Logic to fetch user data from MySQL database } } ``` 現在,讓我們建立一個取決於具體實作的服務類別: ``` // High-level module class UserService { private database: MySQLDatabase; constructor() { this.database = new MySQLDatabase(); } getUser(id: number): string { return this.database.getUserData(id); } } ``` 在上面的範例中, `UserService`直接依賴`MySQLDatabase`的具體實作。這違反了**DIP** ,因為**高級**類別 UserService 直接依賴**低階**類別。 如果我們想要切換到不同的資料庫系統(例如PostgreSQL),我們需要修改UserService類,這`AWFUL`了! 讓我們使用**DIP**修復此程式碼。高級類別`UserService`不應依賴特定實現,而應依賴抽象。讓我們建立一個`Database`介面作為抽象: ``` // Abstract interface (abstraction) for the low-level module interface Database { getUserData(id: number): string; } ``` 現在, `MySQLDatabase`和`PostgreSQLDatabase`的具體實作應該要實作這個介面: ``` class MySQLDatabase implements Database { getUserData(id: number): string { // Logic to fetch user data from MySQL database } } // Another low-level module implementing the Database interface class PostgreSQLDatabase implements Database { getUserData(id: number): string { // Logic to fetch user data from PostgreSQL database } } ``` 最後,UserService 類別可以依賴`Database`抽象: ``` class UserService { private database: Database; constructor(database: Database) { this.database = database; } getUser(id: number): string { return this.database.getUserData(id); } } ``` 這樣, `UserService`類別依賴`Database`抽象,而不是具體實現,滿足**依賴倒置原則**。 結論 -- 透過採用這些原則,開發人員可以建立更能適應變化的系統,使維護變得更容易,並隨著時間的推移提高程式碼品質。 本文的全部內容源自各種其他文章、我的個人筆記以及我**在深入研究物件導向程式設計 (OOP) 領域**時遇到的數十個線上影片。範例中使用的程式碼片段是基於我對這些原則的解釋和理解而建立的。我真的希望,我的小學徒,我能為促進你的理解和學習進步做出貢獻。 衷心希望您喜歡這篇文章,別忘了關注! 註:圖片取自[本文](https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898) --- 原文出處:https://dev.to/lukeskw/solid-principles-theyre-rock-solid-for-good-reason-31hn

🪄與您的簡歷製作者聊天 - 使用 Next.js、OpenAI 和 CopilotKit 📑✨

#TL;博士 在本文中,您將了解如何使用 Nextjs、CopilotKit 和 OpenAI 建立人工智慧驅動的簡歷產生器應用程式。 我們將涵蓋: - 利用 ChatGPT 撰寫履歷和求職信 📑 - 透過與履歷聊天逐漸完善你的履歷💬 - 將您的履歷和求職信匯出為 PDF 🖨️ ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jxzcx6jqet2anmr2pu6c.gif) --- ## CopilotKit:建構深度整合的應用內人工智慧聊天機器人 💬 只是簡單介紹一下我們的背景。 CopilotKit 是[開源 AI 副駕駛平台。](https://github.com/CopilotKit/CopilotKit) 我們可以輕鬆地將強大的 AI 聊天機器人整合到您的 React 應用程式中。完全可定制和深度集成。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wf9zcyvtu9q293uej2n.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} 現在回到文章。 --- ## **先決條件** 要開始學習本教程,您需要在電腦上安裝以下軟體: - 文字編輯器(例如 Visual Studio Code) - 節點.js - 套件管理器 ## **使用 NextJS 建立簡歷應用程式前端** **步驟 1:** 開啟命令提示字元並執行下列命令。 ``` npx create-next-app@latest ``` --- **第 2 步:** 系統將提示您選擇一些選項,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvk0mgct4ypra7ao9u18.png) **步驟 3:** 使用您選擇的文字編輯器開啟新建立的 Nextjs 專案。然後,在命令列上執行以下命令,以使用 Tailwind CSS 安裝帶有 NextJS 的 Preline UI。依照[本指南](https://preline.co/docs/frameworks-nextjs.html)完成 Preline 設定。 ``` npm install preline ``` --- **步驟4:** 在resume/app/page.tsx檔案中,新增以下程式碼內容。 ``` export default function Home() { return ( <> <header className="flex flex-wrap sm:justify-start sm:flex-nowrap z-50 w-full bg-slate-900 bg-gradient-to-b from-violet-600/[.15] via-transparent text-sm py-3 sm:py-0 dark:bg-gray-800 dark:border-gray-700"> <nav className="relative max-w-7xl w-full mx-auto px-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8 " aria-label="Global"> <div className="flex items-center justify-between"> <a className="flex-none text-xl text-gray-200 font-semibold dark:text-white py-8" href="#" aria-label="Brand"> ResumeBuilder </a> </div> </nav> </header> {/* <!-- Hero --> */} <div className="bg-slate-900 h-screen"> <div className="bg-gradient-to-b from-violet-600/[.15] via-transparent"> <div className="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 py-24 space-y-8"> {/* <!-- Title --> */} <div className="max-w-3xl text-center mx-auto pt-10"> <h1 className="block font-medium text-gray-200 text-4xl sm:text-5xl md:text-6xl lg:text-7xl"> Craft A Compelling Resume With AI Resume Builder </h1> </div> {/* <!-- End Title --> */} <div className="max-w-3xl text-center mx-auto"> <p className="text-lg text-gray-400"> ResumeBuilder helps you create a resume that effectively highlights your skills and experience. </p> </div> {/* <!-- Buttons --> */} <div className="text-center"> <a className="inline-flex justify-center items-center gap-x-3 text-center bg-gradient-to-tl from-blue-600 to-violet-600 shadow-lg shadow-transparent hover:shadow-blue-700/50 border border-transparent text-white text-sm font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 focus:ring-offset-white py-3 px-6 dark:focus:ring-offset-gray-800" href="#"> Get started <svg className="flex-shrink-0 w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="m9 18 6-6-6-6" /> </svg> </a> </div> {/* <!-- End Buttons --> */} </div> </div> </div> {/* <!-- End Hero --> */} </> ); } ``` **步驟 5:** 在命令列上執行命令 *npm run dev*。導航至 http://localhost:3000/,您應該會看到新建立的 NextJS 專案。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56ymnb9iir7z14bx4ofm.png) --- ## 使用 GitHub GraphQL 從 GitHub 取得履歷資料 **步驟 1:** 使用下列命令安裝 Axios HTTP 用戶端。 ``` npm i axios ``` **步驟 2:** 在應用程式資料夾中,建立一個名為 API 的資料夾。然後,在 API 資料夾中建立一個名為 GitHub 的資料夾。在GitHub資料夾中建立一個名為route.ts的檔案並加入以下程式碼。 ``` import { NextResponse } from "next/server"; import axios from "axios"; // Environment variables for GitHub API token and user details const GITHUB_TOKEN = "Your GitHub personal access token"; const GITHUB_USERNAME = "Your GitHub account username"; // Axios instance for GitHub GraphQL API const githubApi = axios.create({ baseURL: "https://api.github.com/graphql", headers: { Authorization: `bearer ${GITHUB_TOKEN}`, "Content-Type": "application/json", }, }); // GraphQL query to fetch user and repository data const getUserAndReposQuery = ` query { user(login: "${GITHUB_USERNAME}") { name email company bio repositories(first: 3, orderBy: {field: CREATED_AT, direction: DESC}) { edges { node { name url description createdAt ... on Repository { primaryLanguage{ name } stargazers { totalCount } } } } } } } `; // API route to handle resume data fetching export async function GET(request: any) { try { // Fetch data from GitHub const response = await githubApi.post("", { query: getUserAndReposQuery }); const userData = response.data.data.user; // Format resume data const resumeData = { name: userData.name, email: userData.email, company: userData.company, bio: userData.bio, repositories: userData.repositories.edges.map((repo: any) => ({ name: repo.node.name, url: repo.node.url, created: repo.node.createdAt, description: repo.node.description, language: repo.node.primaryLanguage.name, stars: repo.node.stargazers.totalCount, })), }; // Return formatted resume data return NextResponse.json(resumeData); } catch (error) { console.error("Error fetching data from GitHub:", error); return NextResponse.json({ message: "Internal Server Error" }); } } ``` **步驟 3:** 在應用程式資料夾中,建立一個名為 Components 的資料夾。然後,在元件資料夾中建立一個名為 githubdata.tsx 的檔案並新增以下程式碼。 ``` "use client"; import React, { useEffect, useState } from "react"; import axios from "axios"; // Resume data interface interface ResumeData { name: string; email: string; company: string; bio: string; repositories: { name: string; url: string; created: string; description: string; language: string; stars: number; }[]; } export const useGithubData = () => { const [resumeData, setResumeData] = useState<ResumeData | null>(null); // Fetch resume data from API useEffect(() => { axios .get("/api/github") .then((response) => { setResumeData(response.data); }) }, []); return { resumeData, }; } ``` --- ## 建立求職信和履歷功能 **步驟 1:** 透過在命令列上執行以下命令來安裝 CopilotKit 前端軟體包。 ``` npm i @copilotkit/react-core @copilotkit/react-ui @copilotkit/react-textarea ``` **步驟2:** 在元件資料夾中建立一個名為resume.tsx 的檔案。然後在檔案頂端匯入 useMakeCopilotReadable、useMakeCopilotActionable 和 useGithubData 自訂掛鉤,如下所示。 ``` import React, { useState } from "react"; import { useGithubData } from "./githubdata"; import { useMakeCopilotReadable, useMakeCopilotActionable, } from "@copilotkit/react-core"; ``` **第 3 步:** 建立一個名為 CoverLetterAndResume 的元件。在元件內部,使用 useGithubData 掛鉤檢索從 GitHub 取得的資料。然後,宣告一個名為 createCoverLetterAndResume 的狀態變數和一個用於更新它的名為 setCreateCoverLetterAndResume 的函數。使用包含 letter 和 resume 兩個屬性的物件初始化 useState,如下所示。 ``` export const CoverLetterAndResume = () => { const {resumeData } = useGithubData(); const [createCoverLetterAndResume, setCreateCoverLetterAndResume] = useState({ letter: "", resume: "" }); } ``` **步驟 4:** 使用 useMakeCopilotReadable 掛鉤將從 GitHub 取得的資料新增為應用程式內聊天機器人的上下文。 ``` useMakeCopilotReadable(JSON.stringify(resumeData)); ``` **步驟 5:** 使用 useMakeCopilotActionable 掛鉤設定一個名為 createCoverLetterAndResume 的操作,其中包含描述和實作函數,該函數使用提供的求職信和簡歷更新 createCoverLetterAndResume 狀態,如下所示。 ``` useMakeCopilotActionable( { name: "createCoverLetterAndResume", description: "Create a cover letter and resume for a software developer job application.", argumentAnnotations: [ { name: "coverLetterMarkdown", type: "string", description: "Markdown text for a cover letter to introduce yourself and briefly summarize your professional background as a software developer.", required: true, }, { name: "resumeMarkdown", type: "string", description: "Markdown text for a resume that displays your professional background and relevant skills.", required: true, }, ], implementation: async (coverLetterMarkdown, resumeMarkdown) => { setCreateCoverLetterAndResume((prevState) => ({ ...prevState, letter: coverLetterMarkdown, resume: resumeMarkdown, })); }, }, [] ); ``` **步驟 6:** 在 CoverLetterAndResume 元件外部,建立一個名為 CoverLetterResume 的元件,用於在 Web 應用程式 UI 上顯示求職信和履歷。 ``` type CoverLetterResumeProps = { letter: string; resume: string; }; const CoverLetterResume = ({ letter, resume }: CoverLetterResumeProps) => { return ( <div className="px-4 sm:px-6 lg:px-8 bg-slate-50 py-4"> <div className="sm:flex sm:items-center"> <div className="sm:flex-auto"> <h1 className="text-3xl font-semibold leading-6 text-gray-900"> ResumeBuilder </h1> </div> </div> {/* Cover Letter Start */} <div className="mt-8 flow-root bg-slate-200"> <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> <div> <h2 className="text-lg font-semibold leading-6 text-gray-900 mb-4 p-2"> Cover Letter </h2> <div className="min-w-full divide-y divide-gray-300 p-2"> {/* <Thead /> */} <div className="divide-y divide-gray-200 bg-white p-2"> <ReactMarkdown>{letter}</ReactMarkdown> </div> </div> </div> </div> </div> </div> {/* Cover Letter End */} {/* Cover Letter Preview Start */} <div className="mt-8 flow-root bg-slate-200"> <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> <div> <h2 className="text-lg font-semibold leading-6 text-gray-900 mb-4 p-2"> Cover Letter Preview </h2> <div className="min-w-full divide-y divide-gray-300"> {/* <Thead /> */} <div className="divide-y divide-gray-200 bg-white"> <textarea className="p-2" id="coverLetter" value={letter} rows={20} cols={113} /> </div> </div> </div> </div> </div> </div> {/* Cover Letter Preview End */} {/* Resume Start */} <div className="mt-8 flow-root bg-slate-200"> <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> <h2 className="text-lg font-semibold leading-6 text-gray-900 mb-4 p-2"> Resume </h2> <div className="min-w-full divide-y divide-gray-300"> {/* <Thead /> */} <div className="divide-y divide-gray-200 bg-white"> <ReactMarkdown>{resume}</ReactMarkdown> </div> </div> </div> </div> </div> {/* Resume End */} {/* Cover Letter Preview Start */} <div className="mt-8 flow-root bg-slate-200"> <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> <div> <h2 className="text-lg font-semibold leading-6 text-gray-900 mb-4 p-2"> Cover Letter Preview </h2> <div className="min-w-full divide-y divide-gray-300"> {/* <Thead /> */} <div className="divide-y divide-gray-200 bg-white"> {/* {letter} */} {/* <ReactMarkdown>{letter}</ReactMarkdown> */} <textarea className="p-2" id="resume" value={resume} rows={20} cols={113} /> </div> </div> </div> </div> </div> </div> {/* Cover Letter Preview End */} </div> ); }; ``` **第7步:**然後返回CoverLetterAndResume元件內的CoverLetterResume元件,如下圖所示。 ``` return <CoverLetterResume {...createCoverLetterAndResume}/>; ``` **第8步:** 在應用程式資料夾中建立一個名為resumeandcoverletter的資料夾。然後,建立一個名為 page.tsx 的檔案並新增以下程式碼。 ``` "use client"; import { CopilotProvider } from "@copilotkit/react-core"; import { CopilotSidebarUIProvider } from "@copilotkit/react-ui"; import "@copilotkit/react-textarea/styles.css"; // also import this if you want to use the CopilotTextarea component import "@copilotkit/react-ui/styles.css"; // also import this if you want to use the chatbot component import React, { useEffect, useState } from "react"; import { CoverLetterAndResume } from "../components/resume"; function buildResume () { return ( <CopilotProvider chatApiEndpoint="./../api/copilotkit/chat"> <CopilotSidebarUIProvider> <CoverLetterAndResume /> </CopilotSidebarUIProvider> </CopilotProvider> ); } export default buildResume; ``` **步驟 9:** 使用下列指令安裝 openai 軟體套件。 ``` npm i openai ``` **步驟 10:** 在應用程式資料夾中,建立一個名為 API 的資料夾。然後,在 API 資料夾中建立一個名為 copilotkit 的資料夾。在 copilotkit 資料夾中,建立一個名為 chat 的資料夾。然後,在聊天資料夾中建立一個名為route.ts的檔案並新增以下程式碼。 ``` import OpenAI from "openai"; const openai = new OpenAI({ apiKey: "Your ChatGPT API key", }); export const runtime = "edge"; export async function POST(req: Request): Promise<Response> { try { const forwardedProps = await req.json(); const stream = openai.beta.chat.completions .stream({ model: "gpt-4-1106-preview", ...forwardedProps, stream: true, }) .toReadableStream(); return new Response(stream); } catch (error: any) { return new Response("", { status: 500, statusText: error.error.message }); } } ``` **步驟 11:** 在應用程式資料夾中的 page.tsx 檔案中,在「開始」按鈕中新增一個連結,用於導航到簡歷和求職信頁面,如下所示。 ``` <div className="text-center"> <Link className="inline-flex justify-center items-center gap-x-3 text-center bg-gradient-to-tl from-blue-600 to-violet-600 shadow-lg shadow-transparent hover:shadow-blue-700/50 border border-transparent text-white text-sm font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 focus:ring-offset-white py-3 px-6 dark:focus:ring-offset-gray-800" href="/resumeandcoverletter"> Get started <svg className="flex-shrink-0 w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="m9 18 6-6-6-6" /> </svg> </Link> </div> ``` **第12步:**導航至http://localhost:3000/,點擊「開始」按鈕,您將被重新導向到與聊天機器人整合的履歷和求職信頁面,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqfjykc75pherkjxut4p.png) **第 13 步:** 向右側的聊天機器人發出諸如“建立求職信和簡歷”之類的提示。聊天機器人將開始產生回應,完成後,它將在頁面左側顯示產生的求職信和履歷,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7muhhi4a85ol0ddyi1l.png) --- ## 建立更新求職信功能 **第 1 步:** 宣告一個名為 updateLetter 的變數,用於保存先前產生的求職信。 ``` const updateLetter = createCoverLetterAndResume.letter; ``` **步驟 2:** 使用 useMakeCopilotReadable 掛鉤新增 updateLetter 作為應用程式內聊天機器人的上下文。 ``` useMakeCopilotReadable("Cover Letter:" + JSON.stringify(updateLetter)); ``` **步驟 3:** 使用 useMakeCopilotActionable 掛鉤設定一個名為 updateCoverLetter 的操作,其中包含描述和實作函數,該函數使用提供的求職信更新來更新 createCoverLetterAndResume 狀態,如下所示。 ``` useMakeCopilotActionable( { name: "updateCoverLetter", description: "Update cover letter for a software developer job application.", argumentAnnotations: [ { name: "updateCoverLetterMarkdown", type: "string", description: "Update markdown text for a cover letter to introduce yourself and briefly summarize your professional background as a software developer.", required: true, }, { name: "resumeMarkdown", type: "string", description: "Markdown text for a resume that displays your professional background and relevant skills.", required: true, }, ], implementation: async (updatedCoverLetterMarkdown) => { setCreateCoverLetterAndResume((prevState) => ({ ...prevState, letter: updatedCoverLetterMarkdown, })); }, }, [] ); ``` ** 步驟 4:** 給聊天機器人一個提示,例如“更新求職信並加入我正在申請 CopilotKit 的技術寫作職位。”如下圖所示,您可以看到求職信已更新。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4dkm8zacgbmn19j9qtw6.png) --- ## 建立更新復原功能 **第 1 步:** 宣告一個名為 updateResume 的變數,用於保存先前產生的求職信。 ``` const updateResume = createCoverLetterAndResume.resume; ``` **步驟 2:** 使用 useMakeCopilotReadable 掛鉤新增 updateResume 作為應用程式內聊天機器人的上下文。 ``` useMakeCopilotReadable("Resume:" + JSON.stringify(updateResume)); ``` **步驟 3:** 使用 useMakeCopilotActionable 掛鉤設定一個名為 updateResume 的操作,其中包含描述和實作函數,該函數使用提供的求職信更新來更新 createCoverLetterAndResume 狀態,如下所示。 ``` useMakeCopilotActionable( { name: "updateResume", description: "Update resume for a software developer job application.", argumentAnnotations: [ { name: "updateResumeMarkdown", type: "string", description: "Update markdown text for a resume that displays your professional background and relevant skills.", required: true, }, ], implementation: async (updatedResumeMarkdown) => { setCreateCoverLetterAndResume((prevState) => ({ ...prevState, resume: updatedResumeMarkdown, })); }, }, [] ); ``` **第 4 步:** 向聊天機器人發出提示,例如「更新履歷並將我的姓名加入為 John Doe,將我的電子郵件加入為 [email protected]」。如下圖所示,可以看到履歷已更新。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2d9y6pmfynxwzff8be86.png) --- ## 建立下載求職信和履歷表 Pdfs 功能 **第 1 步:** 安裝 jsPDF,一個用 JavaScript 產生 PDF 的函式庫。 ``` npm i jspdf ``` **步驟 2:** 在 CoverLetterAndResume 元件內,使用 useMakeCopilotActionable 掛鉤設定一個名為“downloadPdfs”的操作,其中包含描述和實現函數,該函數使用 jsPDF 庫為求職信和簡歷建立 PDF,然後保存它們, 如下所示。 ``` function addTextToPDF(doc: any, text: any, x: any, y: any, maxWidth: any) { // Split the text into lines const lines = doc.splitTextToSize(text, maxWidth); // Add lines to the document doc.text(lines, x, y); } useMakeCopilotActionable( { name: "downloadPdfs", description: "Download pdfs of the cover letter and resume.", argumentAnnotations: [ { name: "coverLetterPdfA4", type: "string", description: "A Pdf that contains the cover letter converted from markdown text and fits A4 paper.", required: true, }, { name: "resumePdfA4Paper", type: "string", description: "A Pdf that contains the resume converted from markdown text and fits A4 paper.", required: true, }, ], implementation: async () => { const marginLeft = 10; const marginTop = 10; const maxWidth = 180; const coverLetterDoc = new jsPDF(); addTextToPDF( coverLetterDoc, createCoverLetterAndResume.letter, marginLeft, marginTop, maxWidth ); coverLetterDoc.save("coverLetter.pdf"); const resumeDoc = new jsPDF(); addTextToPDF( resumeDoc, createCoverLetterAndResume.resume, marginLeft, marginTop, maxWidth ); resumeDoc.save("resume.pdf"); }, }, [createCoverLetterAndResume] ); ``` **第 3 步:** 返回網頁應用程式中的聊天機器人,並提示「下載求職信和簡歷的 pdf 檔案」。 PDF 將開始下載,如果您開啟 coverLetter.pdf,您應該會看到產生的求職信,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4p853urbqn43jh6454at.png) --- ## 結論 總而言之,您可以使用 CopilotKit 建立應用內 AI 聊天機器人,該機器人可以查看當前應用程式狀態並在應用程式內執行操作。 AI 聊天機器人可以與您的應用程式前端、後端和第三方服務對話。 對於完整的源程式碼: https://github.com/TheGreatBonnie/AIPoweredResumeBuilder --- 原文出處:https://dev.to/copilotkit/how-to-build-the-with-nextjs-openai-1mhb

讓我們加密:使用 Certbot 的通配符憑證

--- 標題:讓我們加密:使用 Certbot 的通配符憑證 發表:真實 說明:Let's Encrypt 使用 DNS-01 質詢透過 ACMEv2 支援通配符憑證,該質詢於 2018 年 3 月 13 日開始。其用戶端 Certbot 提供 --manual 選項來執行此操作。我寫瞭如何使用 Certbot 產生通配符憑證。 標籤: LetsEncrypt、 certbot、 憑證、 安全 封面圖片:https://thepracticaldev.s3.amazonaws.com/i/wxi7s3zr664nv3u5d2ig.jpg 系列:OpenBSD TLS 連接 --- *\* 封面圖片最初由 [OpenClipart-Vectors](https://pixabay.com/users/OpenClipart-Vectors-30363/) 製作並經過精心編輯。* --- ## 介紹 [Let's Encrypt](https://letsencrypt.org/) 透過 ACMEv2 支援[通配符憑證](https://letsencrypt.org/docs/faq/#does-let-s-encrypt-issue-wildcard-certificates) [ DNS-01 挑戰](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge),[於2018 年3 月13 日開始](https://letsencrypt.org/2017/ 07 /06/wildcard-certificates-coming-jan-2018.html)。 [Certbot](https://certbot.eff.org/),其客戶端,提供「--manual」選項來[執行](https://certbot.eff.org/faq/#does-let- s -加密-頒發-通配符-憑證)。 我真誠地感謝他們。 我寫瞭如何使用 Certbot 產生通配符憑證。 這是一個非常簡單的任務:) 此外,所需要做的就是將 Certbot 指定的 TXT 記錄新增至 DNS 伺服器。 \* 注意:[有必要每 90 天更新一次 Let's Encrypt 的憑證](https://letsencrypt.org/docs/faq/#what-is-the-lifetime-for-let-s-encrypt-certificates- for -how-long-are-they-valid),[每次續約時都需要新的TXT 記錄](https://community.letsencrypt.org/t/how-to-renew-wildcard-cert-with- cert -機器人自動/60723)。 ## 身體 #### 環境 - [OpenBSD](https://www.openbsd.org/): [6.5](https://www.openbsd.org/65.html) - 憑證機器人:0.32.0 #### 教程 命令是這樣的: ``` # certbot certonly --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory --manual-public-ip-logging-ok -d '*.<your.domain>' -d <your.domain> ``` 確保引用 `*.<your.domain>` 以避免錯誤「未找到符合專案:*.\<your.domain\>」。 *\* 注意:`-d *.<your.domain>` 和 `-d <your.domain>` 的順序似乎不重要。* 以下是「certonly」選項與單一憑證的比較: ``` - --webroot -w <dir-path> + --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory --manual-public-ip-logging-ok ``` 此外,其命令列選項的詳細資訊請參閱[此處](https://certbot.eff.org/docs/using.html#certbot-command-line-options)。 然後,命令回覆: ``` Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator manual, Installer None Obtaining a new certificate Performing the following challenges: dns-01 challenge for <your.domain> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please deploy a DNS TXT record under the name _acme-challenge.<your.domain> with the following value: <acme-challenge-value> Before continuing, verify the record is deployed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Press Enter to Continue ``` 配置 DNS 伺服器以將記錄新增至您的網域。 記錄是這樣的: |哪裡 |什麼 | | -----| --- | |類型 |文字 | |名稱 | `_acme-挑戰` | |價值| \<acme-challenge-value\> 指令顯示如上 | *\* 注意:您可能會在這裡等待一段時間以進行 DNS 傳播。* 然後按 Enter 鍵。 ``` Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/<your.domain>/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/<your.domain>/privkey.pem Your cert will expire on 2019-08-01. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le ``` 完畢 : ) ## 結論 因此,我獲得了通配符憑證並應用於我的幾台伺服器。 今天它運作良好,我的工作量減少了,安全性幾乎相同。 如果您正在尋找如何將 Let's Encrypt 的憑證應用到 OpenBSD httpd 伺服器,我在這裡寫道: {% 連結 nabbisen/lets-encrypt-certbot-for-openbsds-httpd-3ofd %} 感謝您的閱讀。 快樂安全。 --- 原文出處:https://dev.to/nabbisen/let-s-encrypt-wildcard-certificate-with-certbot-plo

使用 Next.js、Resend 和 Trigger.dev 建立後台電子郵件通知

## 您會在本文中找到什麼? 電子郵件通知是讓使用者了解應用程式所執行操作的最常用方法。典型的通知包括:有人追蹤您、有人喜歡您的貼文、有人查看了您的內容。在這篇文章中,我們將探索如何使用 Next.js、Resend 和 Trigger.dev 建立一個簡單的非同步電子郵件通知系統。 我們將使用 Next.js 作為框架來建立我們的應用程式。我們將使用 Resend 發送電子郵件,並使用 Trigger.dev 非同步卸載和發送電子郵件。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jwlb0t41kg2s3djcb072.gif) ## Papermark - 開源 DocSend 替代品。 在我們開始之前,讓我與您分享 Papermark。它是 DocSend 的開源替代方案,可幫助您安全地共享文件並從查看者那裡獲取即時的逐頁分析。全部都是開源的! 如果您能給我們一顆星星,我會非常高興!別忘了在留言區分享你的想法❤️ [https://github.com/mfts/papermark](https://github.com/mfts/papermark) [![Papermark 應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igzk8cdssbmla9uf1544.png)](https://github.com/mfts/papermark) ## 設定專案 讓我們繼續為我們的電子郵件後台通知系統設定專案環境。我們將建立一個 Next.js 應用程式,並設定為重新發送,最重要的是,設定觸發器來處理非同步電子郵件通知。 ### 使用 TypeScript 和 Tailwindcss 設定 Next.js 我們將使用「create-next-app」產生一個新的 Next.js 專案。我們還將使用 TypeScript 和 Tailwind CSS,因此請確保在出現提示時選擇這些選項。 ``` npx create-next-app # --- # you'll be asked the following prompts What is your project named? my-app Would you like to add TypeScript with this project? Y/N # select `Y` for typescript Would you like to use ESLint with this project? Y/N # select `Y` for ESLint Would you like to use Tailwind CSS with this project? Y/N # select `Y` for Tailwind CSS Would you like to use the `src/ directory` with this project? Y/N # select `N` for `src/` directory What import alias would you like configured? `@/*` # enter `@/*` for import alias ``` ### 安裝重新傳送和 React-Email Resend 是開發人員優先的事務性電子郵件服務。我們將使用它向我們的用戶發送電子郵件。 `react-email` 是一個 React 元件庫,可以輕鬆建立漂亮的電子郵件。 ``` npm install resend react-email ``` ### 安裝觸發器 Trigger 是 TypeScript 的後台作業框架。它允許您從主應用程式中卸載長時間執行的任務並非同步執行它們。我們將使用它非同步發送電子郵件。 觸發器 CLI 是在新的或現有的 Next.js 專案中設定觸發器的最簡單方法。有關更多訊息,請查看[他們的文件](https://trigger.dev/docs/documentation/quickstarts/nextjs)。 ``` npx @trigger.dev/cli@latest init ``` ## 建立應用程式 現在我們已經完成了設置,我們準備開始建立我們的應用程式。我們將介紹的主要功能是: - 設定重新發送電子郵件 - 編寫API路由來發送電子郵件 - 新增觸發器作業以使電子郵件發送非同步 ### #1 設定重新傳送電子郵件 首先,我們需要設定重新發送來發送電子郵件。我們將在專案中建立一個新檔案「resend-notification.ts」並新增以下程式碼。 ``` // lib/emails/resend-notification.ts import { Resend } from "resend"; import { NotificationEmail } from "@/components/emails/notification"; const resend = new Resend(process.env.RESEND_API_KEY!); export async function sendNotificationEmail({ name, email, }: { name: string | null | undefined; email: string | null | undefined; }) { const emailTemplate = NotificationEmail({ name }); try { // Send the email using the Resend API await resend.emails.send({ from: "Marc from Papermark <[email protected]>", to: email as string, subject: "You have a new view on your document!", react: emailTemplate, }); } catch (error) { // Log any errors and re-throw the error console.log({ error }); throw error; } } ``` 使用「react-email」的通知電子郵件範本將如下所示: ``` // components/emails/notification.tsx import React from "react"; import { Body, Button, Container, Head, Heading, Html, Preview, Section, Text, Tailwind, } from "@react-email/components"; export default function ViewedDocument({ name, }: { name: string | null | undefined; }) { return ( <Html> <Head /> <Preview>See who visited your document</Preview> <Tailwind> <Body className="bg-white my-auto mx-auto font-sans"> <Container className="my-10 mx-auto p-5 w-[465px]"> <Heading className="text-2xl font-normal text-center p-0 mt-4 mb-8 mx-0"> <span className="font-bold tracking-tighter">Papermark</span> </Heading> <Heading className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black"> New Document Visitor </Heading> <Text className="text-sm leading-6 text-black"> Your document was just viewed by someone. </Text> <Text className="text-sm leading-6 text-black"> You can get the detailed engagement insights like time-spent per page and total duration for this document on Papermark. </Text> <Section className="my-8 text-center"> <Button className="bg-black rounded text-white text-xs font-semibold no-underline text-center" href={`${process.env.NEXT_PUBLIC_BASE_URL}/documents`} style={{ padding: "12px 20px" }}> See my document insights </Button> </Section> <Text className="text-sm"> Cheers, <br /> The Papermark Team </Text> </Container> </Body> </Tailwind> </Html> ); } ``` ### #2 撰寫API路由發送電子郵件 現在,我們已經準備好了電子郵件範本。我們可以使用它向我們的用戶發送電子郵件。我們將建立一個無伺服器函數,該函數會取得使用者的“姓名”和“電子郵件”,並使用我們之前建立的“sendNotificationEmail”函數向他們發送電子郵件。 ``` // pages/api/send-notification.ts import { NextApiRequest, NextApiResponse } from "next"; import prisma from "@/lib/prisma"; import { sendViewedDocumentEmail } from "@/lib/emails/resend-notification"; export const config = { maxDuration: 60, }; export default async function handle( req: NextApiRequest, res: NextApiResponse ) { // We only allow POST requests if (req.method !== "POST") { res.status(405).json({ message: "Method Not Allowed" }); return; } // POST /api/send-notification try { const { viewId } = req.body as { viewId: string; }; // Fetch the link to verify the settings const view = await prisma.view.findUnique({ where: { id: viewId, }, select: { document: { select: { owner: { select: { email: true, name: true, }, }, }, }, }, }); if (!view) { res.status(404).json({ message: "View / Document not found." }); return; } // send email to document owner that document await sendViewedDocumentEmail({ email: view.document.owner.email as string, name: view.document.owner.name as string, }); res.status(200).json({ message: "Successfully sent notification", viewId }); return; } catch (error) { console.log("Error:", error); return res.status(500).json({ message: (error as Error).message }); } } ``` ### #3 新增觸發器作業,使電子郵件傳送非同步 我們的電子郵件發送功能已準備就緒,但我們不想同步發送電子郵件,因此要等到電子郵件發送後應用程式才會回應使用者。我們希望將電子郵件傳送任務轉移到後台作業。我們將使用觸發器來做到這一點。 在設定中,Trigger CLI 在我們的專案中建立了一個「jobs」目錄。我們將在該目錄中建立一個新檔案“notification-job.ts”並新增以下程式碼。 ``` // jobs/notification-job.ts import { client } from "@/trigger"; import { eventTrigger, retry } from "@trigger.dev/sdk"; import { z } from "zod"; client.defineJob({ id: "send-notification", name: "Send Notification", version: "0.0.1", trigger: eventTrigger({ name: "link.viewed", schema: z.object({ viewId: z.string(), }), }), run: async (payload, io, ctx) => { const { viewId } = payload; // get file url from document version const notification = await io.runTask( "send-notification", async () => { const response = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/send-notification`, { method: "POST", body: JSON.stringify({ viewId }), headers: { "Content-Type": "application/json", }, } ); if (!response.ok) { await io.logger.error("Failed to send notification", { payload }); return; } const { message } = (await response.json()) as { message: string; }; await io.logger.info("Notification sent", { message, payload }); return { message }; }, { retry: retry.standardBackoff } ); return { success: true, message: "Successfully sent notification", }; }, }); ``` 將匯出新增至作業索引文件,否則觸發器將不知道該作業。雖然是小細節,但連我都忘記了這一點,並花了一個小時尋找錯誤。 ``` // jobs/index.ts export * from "./notification-job"; ``` ### 獎勵:防止惡意存取 API 路由 我們已準備好 API 路由,但我們不想允許任何人存取它。我們希望確保只有我們的應用程式可以存取它。我們將使用一個簡單的標頭身份驗證金鑰來做到這一點。 在觸發器作業中,我們將標頭加入到請求中: ``` // jobs/notification-job.ts .. ... const response = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/jobs/send-notification`, { method: "POST", body: JSON.stringify({ viewId }), headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.INTERNAL_API_KEY}`, // <- add the authenication header with a local env variable }, }, ); ... .. ``` 在 API 路由中,我們將在「try {} catch {}」區塊之前檢查 API 金鑰是否符合: ``` // pages/api/send-notification.ts .. ... // Extract the API Key from the Authorization header const authHeader = req.headers.authorization; const token = authHeader?.split(" ")[1]; // Assuming the format is "Bearer [token]" // Check if the API Key matches if (token !== process.env.INTERNAL_API_KEY) { res.status(401).json({ message: "Unauthorized" }); return; } ... .. ``` 確保將“INTERNAL_API_KEY”新增至“.env”檔案中。 ``` # .env INTERNAL_API_KEY="YOUR_API_KEY" ``` ## 結論 瞧!我們已經準備好非同步電子郵件通知系統。我們現在可以非同步向用戶發送電子郵件,而不會影響用戶等待時間。我們還可以使用觸發器從主應用程式中卸載許多我們不希望用戶等待的其他任務。 感謝您的閱讀。我是 Marc,開源倡導者。我正在建立 [papermark.io](https://www.papermark.io) - DocSend 的開源替代品。 繼續編碼! ## 幫幫我! 如果您覺得這篇文章有幫助,並且對觸發器和後台任務有了更好的理解,如果您能給我們一顆星,我將非常高興!別忘了在評論中分享你的想法❤️ [https://github.com/mfts/papermark](https://github.com/mfts/papermark) ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nk9c8ktyv1tf3n6jgbxh.gif) --- 原文出處:https://dev.to/mfts/building-background-email-notifications-with-nextjs-resend-and-triggerdev-4cem

2024 年 7 個學習 Python 的最佳場所 [網站 + 平台]

--- 標題:2024 年學習 Python 的 7 個最佳地點 [網站 + 平台] 發表:真實 描述:如果您想知道 2024 年在哪裡學習 Python,那麼請查看這 8 個排名前列的網站和免費教程,以在 2024 年免費在線學習 Python 編程。 標籤: python, 程式設計, 編碼, 開發 封面圖片:https://thepracticaldev.s3.amazonaws.com/i/60y4jb5e98udezhppwt8.png --- *披露:這篇文章包含附屬連結;如果您透過本文提供的不同連結購買產品或服務,我可能會收到補償。* 不管你相信與否,Python 已經激勵了許多人學習編碼,而且它還在不斷激勵他們。我認識一些人因為不同的原因學習 Python,從 [web 開發](https://javarevisited.blogspot.com/2019/04/top-5-python-web-development-frameworks.html) 到[機器學習](https://javarevisited.blogspot.com/2019/08/top-5-python-books-for-data-science-and-machine-learning.html)。 我看過新人學習 Python,使用 Django 編寫 Web 應用程式,使用 Python 建立機器學習模型,然後編寫一些方便的腳本來自動執行無聊的工作。 Python 目前是世界上**#1 的程式語言**,並且由於資料科學和機器學習以及出色的 [Python 庫](https://javarevisited.blogspot.com/2018/10/ top-8-python- libraries-for-data-science-machine-learning.html)如Pandas、NumPy 和[TensorFlow](https://hackernoon.com/top-5-tensorflow-and-ml-courses-對於程式設計師-8b30111cad2c)。 因此,如果您也考慮在 2024 年學習 Python,或者已經開始使用 Python 進行編碼,但仍在尋找一些免費資源,那麼您來對地方了。 過去,我分享了許多有用的免費Python資源,例如[書籍](https://javarevisited.blogspot.com/2019/07/top-5-books-to-learn-python-in-2019.html )和[免費課程](https://javarevisited.blogspot.com/2018/12/10-free-python-courses-for-programmers.html)。今天,我將分享一些可以免費學習 Python 的網站、免費教學和入口網站。 從免費資源中學習真是太棒了,因為您不需要信用卡,也不需要支付課程費用。您所需要的只是時間和學習的渴望。 然而,這並不容易,因為有很多[免費的Python資源](https://medium.com/swlh/5-free-python-courses-for-beginners-to-learn-online-e1ca90687caf)可用,選擇正確的一個是一項艱鉅的任務。這就像大海撈針一樣,這就是本文將為您提供幫助的地方。 順便說一句,如果你不介意花幾塊錢來學習像Python 這樣有價值和有用的東西,那麼我還建議你看看Josh Portilla 的**[完整的Python 3 Bootcamp](http://bit. ly/complete Udemy 上的 -python3-bootcamp)**。您將以更結構化的方式快速學習 Python,並且您可以在 Udemy 的促銷活動中僅花費 10 美元購買本課程。 ##2024 年 8 個初學者的熱門平台和免費 Python 教程 在這裡,您將找到一些免費學習 Python 的最佳地點,我與幾位 Python 專家一起精心挑選了這些資源。我有目的地選擇盡可能少的資源,但我仍然有一些選擇。如果您有任何其他免費教授 Python 開發的有用 Python 網站,請隨時推薦它們。 ###**1\. [Coursera](https://coursera.pxf.io/c/3294490/1164545/14726?u=https%3A%2F%2Fwww.coursera.org%2F)** 如果您想在不支付一分錢的情況下向世界一流大學學習,那麼 Coursera 就是您的最佳選擇。它提供史丹佛大學、歐洲工商管理學院、新加坡國立大學(新加坡國立大學)等知名大學教授的線上課程。 最重要的是,它還有最受歡迎的免費課程之一來學習*Python - 適合所有人的程式設計*(Python 入門)。 本課程將從零開始教您 Python 3。您不需要任何程式設計經驗,因為您將在課程中學習。超過 1250,000 名學生已經註冊了這門課程並學習如何編程,現在是您從中受益的機會。 課程也是 [**Python forEverybody 專業化**](https://coursera.pxf.io/c/3294490/1164545/14726?u=https%3A%2F%2Fwww.coursera.org% Coursera 上的2Fspecializations %2Fpython),其中包含另外4 個深入學習Python 的課程,例如: 1.【Python資料結構】( https://coursera.pxf.io/c/3294490/1164545/14726?u=https%3A%2F%2Fwww.coursera.org%2Flearn%2Fpython-data%3Fspecialization%3Dpython) 2.【使用Python存取Web資料】( https://coursera.pxf.io/c/3294490/1164545/14726?u=https%3A%2F%2Fwww.coursera.org%2Flearn%2Fpython-network-data%3Fspecialization%3Dpython) 3. [透過Python使用資料庫]( https://coursera.pxf.io/c/3294490/1164545/14726?u=https%3A%2F%2Fwww.coursera.org%2Flearn%2Fpython-databases%3Fspecialization%3Dpython) 4. [Capstone 專案:使用 Python 檢索、處理和視覺化資料]( https://coursera.pxf.io/c/3294490/1164545/14726?u=https%3A%2F%2Fwww.coursera.org%2Flearn%2Fpython-data-visualization) 所有課程都可以免費旁聽,這意味著您可以免費加入並學習。但是,您無法參加作業和測驗,並且在付款之前不會獲得任何認證。 [![Python 適合所有人Coursera 免費課程](https://1.bp.blogspot.com/-9iz43SzwsfY/XX4691bTMjI/AAAAAAAAaYE/Xk960951xbALk2Y7eCIqfIgL94pOq5vDQCA51xbALk207eCIqfIgL94pOq5vDQA2057%/57%/A5%/B5%/B55%/B5/M5%/B5%/M5%/AM5%/B5%/Mr. 2Bfree。 .jpg) ]( https://dev.to/javinpaul/7-python-online-courses-for-beginners-and-intermediate-programmers-1h4k) 如果您想要所有這些和認證,那麼您需要註冊不是免費的專業化課程。如果您負擔得起並欣賞該課程,無論如何,您應該訂閱,它完全值得您的時間和金錢。 我建議加入 [Coursera Plus],這是 Coursera 的訂閱計劃,可以無限制地存取許多課程、認證和專案。如果您想參加 Coursera 中的多個課程或認證,這可能是最好的學習方式,不僅包括 Python,還包括資料科學和雲端。 ------ ###**2\. [Udemy](https://click.linksynergy.com/deeplink?id=JVFxdTr9V80&mid=39197&murl=https%3A%2F%2Fwww.udemy.com%2F)** 這是另一個流行的線上課程平台,它可能擁有地球上最大的線上課程集合。我喜歡 Udemy,因為你幾乎可以找到任何你想學的課程,而且還是免費的。 與 [Codecademy](https://bit.ly/codecademypro) 不同,您不需要任何訂閱,只需建立免費帳戶,然後您就可以註冊免費的 Python 課程。大多數講師在首次推出課程時都會免費提供課程,以便獲得一些關注、評論和社會認同。 但是,也有一些完全免費的優質 Python 課程。您可以加入他們來學習 Python 3。以下是我最喜歡的一些免費課程,可幫助您深入學習 Python。 Udemy 的優點是您可以向專家學習,但它的互動性不如 CodeCademy。不過,如果您喜歡透過影片學習,那麼沒有比 Udemy 更好的地方了。 如果你能負擔得起一些錢,你還可以以10 美元左右的一次性價格獲得很棒的訓練營風格的課程,例如**[The Complete Python 3 Bootcamp](http://bit.ly/complete -python3-bootcamp)**關於他們的閃購。 [![5 個免費學習 Python 的網站](https://1.bp.blogspot.com/-D0BNTzFrdGo/XX44gcl_gII/AAAAAAAAaXk/yIXkYvrcO60EB9lgyHTQiCDp8nUoaAzSQCLcBGAsYvrcO60EB9lgyHTQiCDp8nUoaAzSQCLcBGAsY7/s400/The% ://bit.ly/complete-python3-bootcamp) -------- ###**3 [透過 Educative 從頭開始學習 Python](https://www.educative.io/courses/learn-python-from-scratch?affiliate_id=5073518643380224)** 如果您不知道 Educative.io 是一個基於文字的互動式平台,可讓您透過瀏覽器進行學習和編碼。您可以學習概念並只需在下一行中編寫程式碼,而無需擔心下載必要的軟體和設定開發環境。對於任何學習任何程式語言的初學者來說,這是最大的優勢,因為他們中的大多數人都陷入了這個設定部分。 如果你想[在2024 年學習Python](https://www.java67.com/2020/05/top-5-courses-to-learn-python-in-depth.html) 那麼這門課是完美的地方開始。本課程首先探索基本建置塊,然後再討論函數和循環等更高層次的概念。有趣的測驗和程式設計挑戰將伴隨您一路前行,幫助您強化課程中涵蓋的所有概念。 在課程結束時,您將熟悉[資料結構](https://becoming human.ai/6-courses-python-programmers-can-join-to-learn-data-structs-and -algorithms-c1a37284938e) 和Python中的函數式程式設計。這是一門免費課程,因此您無需支付任何費用即可加入,您只需建立 Educative.io 帳戶即可存取課程。 [![深入 Python 的免費教學](https://thepracticaldev.s3.amazonaws.com/i/2bwr9gfk1yxgn89j3kjy.png)](https://www.educative.io/courses/learn-python-from-從頭開始?affiliate_id=5073518643380224) ---- ### 4\. [DataCamp 的免費 Python 入門課程](https://datacamp.pxf.io/c/1193463/1012793/13294?u=https%3A%2F%2Fwww.datacamp.com%2Fcourses%2Fintro-to-python-for -資料科學) DataCamp 的「Python 程式設計簡介」課程對於想要踏上 Python 世界之旅的初學者來說是一個極好的資源。此免費課程具有用戶友好的介面和全面的內容,適合剛接觸程式設計或希望鞏固基礎 Python 技能的學習者。 它涵蓋了變數、資料類型、控制結構、函數等關鍵概念,確保學生充分掌握 Python 基礎知識。透過實踐方法,參與者參與實際練習和編碼挑戰,以增強他們的理解。 無論您是想進入資料科學、Web 開發或任何 Python 相關領域,本課程都會對該語言的語法和功能進行紮實的介紹,為更高級的學習奠定基礎。 說到社會認同,超過 500 萬人參加了這門課程,這是任何線上 Python 課程的記錄,它的平均評分為 4.7,簡直令人驚嘆。 順便說一下,如果您喜歡Datacamp的線上學習平台及其課程,可以考慮付費訂閱。他們有不同的計劃,如標準、專業和高級,允許存取所有專案。我推薦 **[標準計劃](https://datacamp.pxf.io/c/1193463/1012793/13294?u=https%3A%2F%2Fwww.datacamp.com%2Fpricing)** 因為它是正確的-價格合理,您可以獲得提高資料技能的所有必需品。 [![適合初學者的最佳免費Python課程](https://blogger.googleusercontent.com/img/a/AVvXsEhZ1TxSJhyjGvc8q5E6AE4d8YXovCdy4RuU75uZWx2ISudjW64QLyZgNCiERoA0Ee3HYjmW64QLyZgNCiERoA0Ee3HYjmW64QLDgNCiERo YAx yjX5tcmFxfSsiOOFfnIG6ta66ZtpgGFbs-m2KQpxFHdNxvlFyLbwk0hBfD-MhIWXo0fDjmbXsyli9EzQ=w395-h250)](https://datacampc. /c/1193463 /1012793/13294?u=https%3A%2F%2Fwww.datacamp.com%2Fcourses%2Fintro-to-python-for-data-science) -------- ###**5\. [CodeCademy](https://bit.ly/codecademypro)** 如果您喜歡互動式學習,那麼沒有比 CodeCademy 更好的地方了。他們首先用盡可能少的單字教你理論,然後要求你使用該概念在線上編寫程式碼。最好的事情是您不需要進行任何設置,例如在電腦上安裝 Python。 您可以直接從瀏覽器執行 Python 程式碼。另一個好處是,在準備好之前,您不需要編寫完整的程式。您需要進行一些小的更改並執行它們。這是學習 Python 程式設計的一種很棒且有趣的方式。 我使用他們的互動式平台學習了 JavaScript、Java、Python 和 Linux。早些時候,他們是完全免費的,但現在他們有免費增值模式,其中一些課程或課程僅對付費會員開放。 目前,他們的[**學習Python 2**](https://bit.ly/learnpython2withcodecademy)課程是免費的,而[**Python 3課程**](https://bit.ly/learnpython3codecademy)只是免費的可供付費會員使用。如果您負擔得起並欣賞 CodeCademy,請務必訂閱,但如果您無法從他們的 Python 2 課程開始,那麼它非常適合沒有編碼經驗的初學者。 [![線上學習Python的免費互動課程](https://1.bp.blogspot.com/-lSHI8IMKqGA/XX46dg1vjwI/AAAAAAAAaX8/4O1-n9jcl5YT47zh02TOYIdGA87j3AOxQCLcBGAsYhon/T47zh02TOYIdGA87j3AOxQCLcBGAsYhon/s4007% PG )](https://bit.ly/codecademypro) ----- ###**6\. [Google 的 Python 課程](https://developers.google.com/edu/python/)** 如果你不知道,Google 也為初學者提供了一套優秀的 Python 教程,稱為 Google 的 Python 課程。這是一個免費課程,適合有一點程式設計經驗並且想要學習 Python 的人。課程包括書面教程、講座影片和大量練習 Python 編碼的程式碼練習。 第一個練習涉及字串和清單等基本 Python 概念,為下一個練習奠定基礎,下一個練習是處理文字檔案、進程和 http 連接的完整程序。 Google 本身的許多專案都使用 [Python](https://javarevisited.blogspot.com/2018/05/10-reasons-to-learn-python-programming.html)。而且,這些材料通常在 Google 內部用於向剛開始編碼或幾乎沒有程式設計經驗的人教授 Python。 本材料最好的部分是 YouTube 上提供講座影片。因此您不需要任何其他帳戶。它還教您設定自己的[Python開發環境](https://medium.com/better-programming/top-5-courses-to-learn-python-in-2018-best-of-lot-26644a99e7ec ),這確實帶來了最初的挑戰,但從長遠來看是很好的。 ------ ###**7\. [微軟的免費Python課程](https://www.awin1.com/cread.php?awinmid=6798&awinaffid=631878&clickref=&p=%5B%5Bhttps%3A%2F%2Fwww.edx.org%2Fcourse%2Fintroduction-to - python-absolute-beginner-4%5D%5D)** 如果谷歌也有Python課程,微軟怎麼會落後呢?嗯,它還在 Edx(另一個流行的免費教育線上入口網站)上提供免費的 Python 課程。本課程名為“Python 簡介:絕對初學者”,是一門學習 Python 的免費課程,由高級內容開發人員 Eric Camplin 教授。 本課程將在Jupyter Notebooks 中教您Python,這是一個基於瀏覽器的線上[Python] 編碼編輯器(https://hackernoon.com/top-5-courses-to-learn-python-in-2018- best-of- lot-26644a99e7ec),這表示你不需要安裝Python。這是一個為期 5 週的課程,每週學習 3 到 4 個小時。 本課程也是 Microsoft 入門級軟體開發專業計劃的一部分,該課程也是免費的。您只需在需要認證時付費。您可以將其新增至您的履歷或 LinkedIn 個人資料中,如下所示: [![免費最佳Python認證課程](https://1.bp.blogspot.com/-1U2amxNH280/XX47hDYf-3I/AAAAAAAAaYk/Xa8Se2JHrmca1AbXy81ILDQofQW4KyAzwCLcBGAsAsYHQ/s400/IntrodBPY free % 2B課程.png)](https://javarevisited.blogspot.com/2018/03/top-5-courses-to-learn-python-in-2018.html) ------ ###8。 [學習 Python - FreeCodeCamp 的初學者完整課程 [教程]](https://www.youtube.com/watch?v=rfscVS0vtbw) 本課程將向您全面介紹 Python 中的所有核心概念。跟著影片一起學習,您很快就會成為 Python 程式設計師!您可以在 YouTube 上免費觀看,這是目錄 ⭐️內容⭐ ⌨️ (0:00) 簡介 ⌨️ (1:45) 安裝 Python 和 PyCharm ⌨️ (6:40) 設定和 Hello World ⌨️ (10:23) 繪製形狀 ⌨️ (15:06) 變數和資料類型 ⌨️ (27:03) 使用字串 ⌨️ (38:18) 處理數字 ⌨️ (48:26) 取得使用者的意見 ⌨️ (52:37) 建構一個基本計算器 ⌨️ (58:27) 瘋狂自由遊戲 ⌨️ (1:03:10) 列表 ⌨️ (1:10:44) 列表函數 ⌨️ (1:18:57) 元組 ⌨️ (1:24:15) 功能 ⌨️ (1:34:11) 退貨聲明 ⌨️ (1:40:06) If 語句 ⌨️ (1:54:07) If 語句與比較 ⌨️ (2:00:37) 建構一個更好的計算器 ⌨️ (2:07:17) 字典 ⌨️ (2:14:13) While 循環 ⌨️ (2:20:21) 建構一個猜謎遊戲 ⌨️ (2:32:44) For 循環 ⌨️ (2:41:20) 指數函數 ⌨️ (2:47:13) 2D 清單和嵌套循環 ⌨️ (2:52:41) 建構翻譯器 ⌨️ (3:00:18) 評論 ⌨️ (3:04:17) 嘗試/例外 ⌨️ (3:12:41) 讀取文件 ⌨️ (3:21:26) 寫入文件 ⌨️ (3:28:13) 模組和 Pip ⌨️ (3:43:56) 類別與物件 ⌨️ (3:57:37) 建構多項選擇測驗 ⌨️ (4:08:28) 物件函數 ⌨️ (4:12:37) 繼承 ⌨️ (4:20:43) Python 解譯器 ![免費 Python 教學與平台](https://thepracticaldev.s3.amazonaws.com/i/zu01vymg8iberknzdeb8.png) ---- 這就是一些 **您可以免費學習 Python 的網站**。所有這些都是很棒的資源,您可以選擇您喜歡的資源。您不需要註冊所有這些,這將是荒謬且耗時的。相反,選擇最適合您的學習風格的一種。 例如,如果您喜歡互動式學習,請選擇[CodeCademy](https://javarevisited.blogspot.com/2019/09/codecademy-vs-udemy-vs-onemonth-which-is-better-for-learning-code . html),如果您喜歡非正規影片課程,那麼選擇 Udemy;如果您喜歡大學和學校等結構化教育,那麼選擇 Coursera。 而且,如果您喜歡基於文字的學習,請記住閱讀比觀看影片更快,那麼 Google 的 Python 課程是最好的。 除此之外,Scrimba 是另一個免費學習 Python 程式設計的好地方。 您可能喜歡的其他 **Python 文章和資源** - [2024年學習Python的10個原因](https://javarevisited.blogspot.com/2018/05/10-reasons-to-learn-python-programming.html) - [Python初學者學習Python的5大課程](https://javarevisited.blogspot.com/2018/03/top-5-courses-to-learn-python-in-2018.html) - [Python 開發者最喜歡的 5 個 Web 開發框架](https://javarevisited.blogspot.com/2019/04/top-5-python-web-development-frameworks.html) - [Python 與 JavaScript - 哪個更好開始?](https://javarevisited.blogspot.com/2019/05/python-vs-javascript-which-programming-language-beginners-should-learn.html) - [10門免費線上課程深入學習Python](https://javarevisited.blogspot.com/2018/12/10-free-python-courses-for-programmers.html) - [資料科學與機器學習的 8 個最佳 Python 函式庫](https://javarevisited.blogspot.com/2018/10/top-8-python-libraries-for-data-science-machine-learning.html) - [Python vs Java - 初學者應該學習哪種程式語言?](https://javarevisited.blogspot.com/2018/06/java-vs-python-which-programming-language-to-learn-first.html ) - [5 Python 資料科學與機器學習課程](https://javarevisited.blogspot.com/2018/03/top-5-data-science-and-machine-learning-online-courses-to-learn-online . html) - [完整的 Web 開發者路線圖](https://hackernoon.com/the-2019-web-developer-roadmap-ab89ac3c380e) - [10本針對程式設計師的免費Python程式設計書籍](http://www.java67.com/2017/05/top-7-free-python-programming-books-pdf-online-download.html) - [資料科學的 5 本 Python 書籍](https://javarevisited.blogspot.com/2019/08/top-5-python-books-for-data-science-and-machine-learning.html) 感謝您到目前為止閱讀這篇文章。如果您喜歡這些網站,請與您的朋友和同事分享。如果您有任何問題或回饋,請留言。 一切順利。 **P。 S. -** 如果你此刻只想做一件事來開啟你的 Python 程式設計之旅,那就加入 **[完整的 Python 3 Bootcamp](http://bit.ly/complete-python3-bootcamp ) ** Jose Portilla 在Udemy 上的課程。您將快速學習 Python 並且永遠不會後悔您的決定。 --- 原文出處:https://dev.to/javinpaul/top-5-places-to-learn-python-programming-for-free-m4c

創作 RawJS 之後,我再也沒有碰過 React。

早在 2012 年 10 月,TypeScript 0.8 就發布了。當時我正在開發一個中型 JavaScript 專案。它發布的那天,我閱讀了最初的規範,在玩了大約 10 分鐘後,我確信這將是未來,因此我開始用 TypeScript 重寫我的整個應用程式。相對於標準無型別 JavaScript 的好處是巨大的。 我對 [RawJS](https://www.squaresapp.org/rawjs/) 也有同樣的感覺。 [RawJS 是一個小型函式庫](https://github.com/squaresapp/rawjs),它使普通 JavaScript 應用程式開發更符合人體工學。它不僅僅是當今最新的 Web 框架,可以與 React、Vue、Svelte 或其他框架競爭。 RawJS 是不同的。 RawJS 直觀地說明了為什麼框架本身的整個前提可能有點誤導。它表明大多數應用程式最好使用普通 JS 並採用某些程式模式。 我知道這是一個非常大膽的聲明。但我懇請您研究一下 RawJS 誕生背後的心態和想法。 ## React 往往會破壞專案的複雜性 我不認為我說 React 太複雜是太過分了。畢竟,Svelte 正是因此而專門建立的。 React 會導致應用程式臃腫。舉個例子——我最近接到了一項任務,負責監督一個 React 應用程式的開發,該應用程式的複雜性已經失控。我最終扔掉了整個應用程式,並使用 RawJS 重建了整個應用程式,使用了一個從未接觸過 RawJS 的團隊,甚至根本沒有做過很多直接的 DOM 操作。幾週之內,團隊就加快了速度,現在該應用程式比以前的 React 應用程式小了約 90%。不,這不是一個錯字。 我們現在已經到了這樣一個階段:React 是「沒有人會因為購買 IBM 而被解僱」的選擇。問題是——人們「應該」因為購買 IBM 而被解僱。這是一個隱喻,指的是那些懶得做充分的需求分析、默認從眾心態、出於恐懼和懶惰而盲目跟隨大多數其他人正在使用的東西的人。 一旦您有幸使用 RawJS 加速的普通 JavaScript 開發應用程式,React 的過度複雜程度就會變得更加清晰。 RawJS 對 props、state、hooks、JSX、從特定基礎強制繼承(React.Component)、虛擬 DOM、自動資料綁定以及 React 所做的一切的必要性提出了質疑。 React 的膨脹及其施加的限制(例如禁止您直接編輯 DOM)都集中在試圖維護其虛擬 DOM 系統的完整性,我認為這是針對特定領域問題的解決方案對於facebook.com,那些精心建置的應用程式根本不具備。 所謂的直接 DOM 操作的效能劣勢被嚴重誇大了。事實上,如果做得正確,直接 DOM 操作通常會提高效能。這是因為您能夠精確控制 DOM 的更新方式。它允許您根據需要使用手術刀更新 DOM 的微小區域。虛擬 DOM 與此相反。它是一個生硬的工具,在大量 DOM 子樹上執行複雜的比較演算法,以便自動計算需要更新的內容。 自動資料綁定和反應性的有用性也被誇大了。假設您的程式碼組織良好,那麼建立您的應用程式以便 DOM 由於資料變更而更新似乎不會比僅建置應用程式以在必要時明確更新 DOM 所需的程式碼少。但與前者的區別在於,它給你強加了一個巨大的難以除錯的黑盒子,並迫使你遵守他們的官僚機構層。除非您組裝了一個精心建置的普通 JS 應用程式(例如使用 RawJS!),否則很難體會到擺脫這種情況的好處。 ## 為什麼沒有人談論匿名控制器類別? 匿名控制器類別(ACC)是一種需要引起更多關注的模式。它們是將普通 JavaScript 應用程式從雜亂無章轉變為連貫且美觀的關鍵想法之一。 ACC 的基本前提是建立一個與 DOM 中單一元素鬆散連接的物件,並且其垃圾收集生命週期等於所連接元素的生命週期。這是對繼承 HTMLElement 的一個進步,HTMLElement 是另一種選擇(但我不喜歡這種選擇,原因我將在另一篇文章中討論)。 考慮以下程式碼: ``` class SomeComponent { readonly head; constructor() { this.head = document.createElement("div"); this.head.addEventListener("click', () => this.click()); // Probably do some other stuff to this.head } private handleClick() { alert("Clicked!") } } ``` ACC 是建立單一 .head 元素(可能還有其他巢狀元素)、連接事件偵聽器、分配樣式等的類別。它們具有通常是事件處理程序或其他輔助方法的方法。然後實例化該元件,並將元件的 .head 元素加入 DOM: ``` const component = new SomeComponent(); document.body.append(component.head); ``` 該類別被視為“匿名”,因為一旦元件實例附加到 DOM,您就可以丟棄該實例。一旦元素從 DOM 中刪除並被垃圾回收,該類別的實例就會被垃圾回收,因為 DOM 是唯一擁有對它的引用的東西。例如: ``` class SomeComponent { readonly head; constructor() { this.head = document.createElement("div"); this.head.addEventListener("click', () => this.remove()); // Probably do some other stuff to this.head } private remove() { // Remove the component's .head element from the DOM, // which will by extension garbage collect this instance of // SomeComponent. this.head.remove(); } } ``` ACC 的優點在於它們基本上不會施加任何限制。他們可以繼承任何東西(或什麼都不繼承)。它們只是一個想法——您可以將它們塑造成您喜歡的樣子。 當然,在許多情況下您可能想要取得與特定元素關聯的 ACC。例如,想像一下迭代 this.head 元素的祖先元素,並取得與其關聯的 ACC 以呼叫某些公共方法。有一個名為 [HatJS](https://github.com/squaresapp/hatjs) 的輕量級程式庫,旨在改善使用 ACC 的人體工學。 **編輯:我是 HatJS 的作者。 「匿名控制器類別」是我發明的一個術語。這是在實驗過程中出現的模式,儘管我懷疑我是第一個發現它的人,因為這個概念非常明顯。就像 JSON 之前有名字一樣。您不需要將 HatJS 與 RawJS 一起使用。許多人正在建立普通的JavaScript 應用程式(或者對於迂腐的人來說是「普通的TypeScript 應用程式」),並且僅僅透過建立繼承自HTMLElement 的自訂元素,有效地將元素和控制器合併到同一個實體中,就取得了巨大的成功。我已經用這種方法建置了一些應用程式,並認為 ACC 更好,原因我會在以後的文章中介紹。** ## 改進 document.createElement() 具有令人驚訝的強大影響 儘管本文試圖為直接使用 DOM API 提供最有力的案例,但這些 API 絕對失敗的一個領域是使用屬性、樣式和事件附件來建立複雜的 DOM 層次結構。 DOM API 的這一部分非常冗長,如果沒有一些外部幫助,您的程式碼將比所需的長度長約 10 倍。這就是 RawJS 的用武之地。 RawJS 的設計正是為了一個目的。它使 document.createElement() 的人體工學性能提高了 10 倍。呼叫函數並取得 HTMLElement 實例的層次結構。它沒有任何其他作用。沒有奇怪的背景魔法。您可能不認為這聽起來很有影響力。但你的評估是錯的。 事實證明,在過去 15 年圍繞框架模式的構思中,我們不需要虛擬 DOM、反應性、資料綁定預編譯器或任何其他野生科學專案。我們需要匿名控制器類別模式和更好的方法來建立 HTMLElement 實例。 使用這兩種技術,我可以肯定地說,我永遠不會再故意使用 React 或任何其他競爭框架。這類框架根本無法提供超出 JavaScript 已經可以完成的功能,無法保證它們所施加的巨大權重和官僚作風。 那麼 RawJS 程式碼是什麼樣的呢?對 RawJS 建立者函數的呼叫遵循以下形式: ``` const htmlElement = raw.div(...parameters); ``` 強大的人體工學來自於可接受的參數的廣度(在 RawJS 中輸入為“Raw.Param”)。 參數可以是字串、數字、布林值、陣列、函數、DOM 節點實例、對 `raw.on("event", ...)` 的呼叫(建立可移植事件附件)以及幾乎任何其他內容。 RawJS 總是做你所期望的。 我不會重申使用 RawJS 來建立層次結構可以做的很棒的事情。快速入門對此進行了詳細介紹。 主要想法是,因為幾乎任何東西都可以是 Raw.Param,所以您可以建立迷你函數庫來產生 Raw.Params 列表並返回它們。由此可實現的程式碼重用水準是前所未有的。再說一次,除非你真正使用過它,否則很難欣賞它。我討厭與 LISP / 閉包進行比較,但還是有相似之處。 ## 我見過的最好的 CSS-in-JS 解決方案 如果不建構對應的 CSS,HTML 元素層次結建置構器有什麼用呢? RawJS 還擁有一流的 CSS-in-JS 解決方案,它可以完成我在任何其他解決方案中未見過的功能。例如,RawJS 完全支援僅限於特定元素的 CSS。 ``` const anchor = raw.a( // This constructs CSS within a global style sheet, // and the rules below will be scoped to the containing // anchor element. raw.css( ":focus", { outline: 0 }, ":visited": { color: "red" } ), raw.text("Hyperlink!") ); ``` 使用 RawJS 的 CSS-in-JS 解決方案還可以做很多其他事情。本文並不是 RawJS 教程,但如果您正在尋找此類內容,這裡有一個快速入門和一個演示應用程式。 ## 使用 DOM 作為狀態管理器實際上很好。 我們收到的一個常見的反對意見是,您將應用程式狀態儲存在哪裡?答案是使用 DOM 作為狀態管理器。 在你對此感到不寒而慄之前,請記住tailwind 如何率先提出在HTML 元素上刪除數百萬個類名的想法,有效地重新建立內聯CSS 的等價物,多年來,內聯CSS 一直被認為是一種反模式,但開發人員堅持它其實是很好,現在大家都在做嗎?同樣的想法也適用於使用 DOM 作為狀態管理器。 如今,每個人決定建立應用程式的方式都是從某種被視為「真相來源」的資料結構開始,然後您需要以某種方式將其笨拙地投影到 UI 中。以另一種方式做這件事被認為是“天真的”,甚至是反模式。但是,我想建議擁有兩個需要保持同步的獨立表示本身就是一種反模式。 嘗試使用某種框架以宣告方式將資料對應到 DOM 會導致複雜度大幅增加。這種技術的問題在於,對同一件事物有兩種不同的表示自然會比假設的只有一種這種表示的替代技術更加臃腫。 事實證明,如果您讓 ACC 接受輸入資料以便自行渲染,效果實際上會好得多。然後,您可以使用某種保存函數來檢查 DOM 的狀態以產生可保存的資料塊。這樣,您的事實來源不必與 DOM 同步,因為您的事實來源就是 DOM。 檢查以下程式碼範例: ``` class FormComponent { readonly head; private readonly firstNameInput; private readonly lastNameInput; constructor(firstName: string, lastName: string) { this.head = raw.form( this.firstNameInput = raw.input({ type: "text", value: firstName }), this.lastNameInput = raw.input({ type: "text", value: lastName }), raw.button( raw.text("Save"), raw.on("submit", () => this.save()) ) ); } private save() { const firstName = this.firstNameInput.value; const lastName = this.lastNameInput.value; SomeDatabaseSomewhere.save({ firstName, lastName }); alert("Saved!"); } } ``` 看?您只需將值儲存在 DOM 中即可。在本例中,我們使用文字輸入的值,但您也可以將資料儲存為 HTML 屬性、類別名稱或任何有意義的內容。 當然,在某些情況下,您需要儲存無法分解為字串、數字和布林值的狀態。我還看到一些狀態儲存在 ACC 內的屬性中的情況。做任何對你有用的事。 ## 元件之間的通信 在某些情況下,您可能需要更新多個元件以回應一項操作,或更簡單地在 ACC 之間發送訊息。 HatJS 可以幫助解決這個問題。 請記住,ACC 建立了一種隱藏的控制器層次結構。您擁有典型的 DOM 元素層次結構,但只有某些元素是 ACC 的頭元素。因此,這將建立自己的 ACC 層次結構,它是整個 DOM 元素層次結構的嚴格子集。 [HatJS](https://github.com/squaresapp/hatjs) 具有遍歷 ACC 層次結構的功能,並快速擷取可能位於或不位於元素後面的 ACC 實例。 ``` class ParentComponent { readonly head; constructor() { this.head = raw.div( new ChildComponent().head ); // Call Hat.wear() to define the object as a "hat" // and make it discoverable by HatJS Hat.wear(this); } callAlert() { alert("Hello!"); } } class ChildComponent { readonly head; constructor() { this.head = raw.div( raw.on("click", () => { // Hat.over finds the "Hat" (or the ACC) that exists // above the specified element in the hierarchy. // And passing ParentComponent gives you type-safe // tells HatJS what kind of component you're looking // for, and also gives you type-safe access to it. Hat.over(this, ParentComponent).callAlert() }) ); } } ``` 除了「Hat.over()」之外,還有「Hat.under()」、「Hat.nearest()」等方法來尋找 DOM 相對中可能存在或不存在的特定類型的其他 ACC到指定的元素。 ## 興奮起來! 那麼,我是否說服您啟動您的 React 應用程式並使用 RawJS 來重建您一生的工作?如果您想開始使用,請造訪 [這裡是 RawJS 網站](https://www.squaresapp.org/rawjs/)。 RawJS 的儲存庫是[此處](https://github.com/squaresapp/rawjs),HatJS 的儲存庫是[此處](https://github.com/squaresapp/hatjs) --- 原文出處:https://dev.to/paulgordon/after-using-rawjs-im-never-touching-react-again-or-any-framework-vanilla-javascript-is-the-future-3ac1

寫給年輕單身男性軟體工程師:一些感情&自我成長建議

最近我留意到一件事,在我們工程師閒聊群組 https://line.me/ti/g2/nipkjq2WoZPKX5dTn9tE9266aEOt6EOICFGa1g 常常會出現感情方面的困擾&討論,令我非常吃驚,因為這是討論 coding 的群組 但是這個話題出現的頻率相當高,我發現這似乎是「年輕單身男性軟體工程師」相當關心的一個話題 站長畢竟年紀稍大一些,比群組內很多人多活了幾年,是有一些建議,雖然跟 coding 無關,但還是跟各位開發者簡單分享 ## 姿態永遠不要放太低 這點就跟業務在做銷售時一樣,你心裡再怎麼想成交,也絕對不能用超低姿態、苦苦哀求拜託對方成交 過低的姿態會釋放「很缺」的訊號,任何人都會心想「這產品很沒價值,所以業務才姿態這麼低,千萬不能買」 同理,「年輕單身男性軟體工程師」似乎在感情上有成為「舔狗」的傾向,請避免這樣做,絕對只有壞處、沒有好處 如果你真的很想疼女生、找一個女生對她好,我建議你多帶你媽出門走走,週五下班幫你媽多買一份點心、宵夜、禮物,有空多陪你媽聊天即可,就不用擔心都沒有女生陪你聊天 ## 注意力放在自己身上 下一個問題是,姿態很低,背後是源自於缺乏自信 這點我們先談一件事,在網路創業圈,有一句話叫做「Attention Is The New Currency」,注意力就是網路世界的現金 你把注意力放在社群網站,這些網站企業就更有錢;你把注意力放在 IG 網美,這些 IG 網美就更有錢 你把注意力放在哪,會決定誰將過得越來越好 所以,建議把注意力放在自己身上,你就會越來越優質 簡單建議,分三個層面 **知識層面** 工程師應該很擅長吸收新知,這點不難。學有趣知識、學習投資、專精工作、更有智慧、賺更多,都不是壞事 **肉體層面** 叫大家都進健身房,很難、又花錢,不切實際。所以我建議各位路過公園就去拉一下單槓,一週兩次,一次20分鐘就可,免費 上網找一些分難度的教學即可,例如這個 https://www.youtube.com/watch?v=kxdNy86hG5I 你就拉個三個月,工程師打電腦的駝背、姿態、儀態,都會改善,然後你會變強壯 **精神層面** 現代人普遍忽略這點,不過,如果你掌握「發呆」的技術、藝術,也就是所謂的「冥想」、「靜坐」 其實在冷靜程度、自信程度、鎮定程度,會有顯著變化,這比較宗教、靈性,需要一些年紀體會 隨便找一個適合你的靈性練習、或宗教,都可以。如果沒頭緒,我建議逛一逛這個印度阿北的頻道 https://www.youtube.com/@sadhgurutraditionalchinese ## 面對挫折感 人生不如意,十之八九,不要太玻璃心 被拒絕了、丟臉了、意想不到情況發生了,就認了,慢慢消化 回家休息,繼續做你該做的事情就好 千萬不要糾纏、不要挽回、不要當恐怖情人 瀟灑一點,繼續做上面三個層面談到的事情就是了 ## trial and error,你就多練習 這跟工程師學新語言、新套件的情況一樣 出錯很正常,多練習就是,你想在什麼方面進步呢?那就 trial and error,你就多練習 想改善穿搭,多逛街、消費就是。簡單從黑灰白開始配色就是,跟前端 css 色彩配色技巧一樣 想改善談吐,在一些小聚、適合的大眾場合、甚至咖啡廳酒吧,你就去搭訕兩句,尬聊兩句 大多數情況下就是被拒絕、很丟臉,別擔心,不要搞到「讓對方感到害怕」的程度即可,丟人現眼 OK 的,被婉拒就禮貌離開即可 我留意到近幾年有一些昂貴的課程在賺這種錢,其實錢省下來買電玩比較爽,不要被商人隨意利用你的焦慮感 反正就是練習交新朋友、networking 而已,臉皮厚一點,越練習臉皮越厚,然後目的性不需要總是那麼強 熟能生巧,上手了社交技巧,在職場也很有用 ## 關於打電動&看動漫 爽爽打電動,爽爽看動漫,一點問題都沒有,都是很健康的興趣 不要越打越沒自信、越看越空虛就好,要找到真正喜歡的電玩、動漫並且投入 有人傳訊息你就不要理,專心打電動&看動漫,有空再回就好,享受你的興趣就對了 呼應到前面幾點,這種人,會讓很多人覺得「好像是一個很忙的人」,然後,不知怎的,就是比較有魅力 (雖然根本不知道你只是看動畫懶得回而已) ## 結論 這些也是我比較年輕時候,身為「年輕單身男性軟體工程師」的一些觀察&經驗談 簡單分享,希望對大家有幫助

Docker 絕對初學者

Docker 是一個工具,允許開發人員將他們的應用程式及其所有依賴項打包到一個容器中。然後,這個容器就可以輕鬆地在任何安裝了 Docker 的機器上傳輸和執行,而不必擔心環境的差異。這就像是打包和執行軟體的標準化方式。 **容器是什麼?** ![Docker 容器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/imsgbstga86vnxjwgebr.png) 容器就像一個小包,其中包含程式執行所需的一切,可以輕鬆地在不同電腦上移動和執行,而不會造成任何麻煩。 最酷的部分是這個迷你電腦(容器)就像一個披著斗篷的超級英雄。它可以在任何電腦上執行,無論它們有多麼不同,因為它自帶特殊的環境。這是一種保持軟體井然有序的方式,並確保它無論在哪裡都能以相同的方式運作。 ![容器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1z5zcued8ya2onerpzwt.png) **為什麼我們需要 Docker?** 1. **一致性:** Docker 確保軟體在您的電腦、您朋友的電腦或任何電腦上以相同的方式運作。它使事情保持一致。 2. **可移植性:** 您可以將您的軟體及其朋友打包到 Docker 容器中,並且它可以移動到任何地方。這就像將您的遊戲及其所有規則放在手提箱中並在朋友家中玩。 3. **隔離:** Docker 容器就像小氣泡。氣泡內發生的事只會留在氣泡內。這意味著容器中的一個程式不會幹擾容器外的另一個程式。 4. **效率:** Docker有助於節省電腦資源。您可以讓許多容器在同一台電腦上執行,而不會相互妨礙,而不是讓一整台電腦只用於一個程式。 5. **速度:** Docker 讓啟動、停止和共享軟體變得快速、輕鬆。這就像打開和關閉遊戲機一樣 - 快速而簡單。 **什麼是 Docker 映像?** ![Docker 映像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/817t1rsad728snnighkj.png) Docker 映像像是程式及其運作所需的所有內容的快照。它是一個打包版本,包括程式碼、工具和設置,就像包含所有成分的餅乾食譜的快照一樣。 **圖像是配方,容器是當您按照該配方實際製作和執行程序時所得到的。** **一些基本的 Docker 命令。** ![基本 Docker 指令](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xq1pwt8896lvster3ppl.png) 1. **`docker執行nginx`** - 此命令告訴 Docker 使用「nginx」映像執行容器。這就像告訴 Docker 啟動一個預製程式的新實例(nginx,它是一個 Web 伺服器)。 2. **`docker ps`** - 顯示正在執行的容器的清單。這就像檢查當前正在執行哪些程式。 3. **`docker ps -a`** - 顯示所有容器的列表,包括已停止的容器。這就像檢查您執行過的所有程式的歷史記錄。 4. **`docker stopsilly_sammet'** - 停止名為「silly_sammet」的正在運作的容器。這就像關閉當前正在執行的程式。 5. **`docker rmsilly_sammet'** - 刪除名為「silly_sammet」的已停止容器。這就像丟掉你不再需要的程式的指令一樣。 6. **`docker 映像`** - 列出您擁有的所有 Docker 映像。這就像查看您可以執行的所有不同程式的選單一樣。 7. **`docker rmi nginx`** - 刪除“nginx”圖像。這就像刪除您不想再使用的程式的配方。 8. **`docker拉nginx`** - 從網路下載「nginx」映像。這就像從食譜中獲取新食譜一樣。 9. **`docker 執行 ubuntu sleep 5`** - 使用「ubuntu」映像檔執行容器並使其休眠 5 秒。這就像啟動一個程序,只是等待一小會兒,然後就停止了。 10. **`docker exectracted_mcclintock cat /etc/hosts`** - 在名為「distracted_mcclintock」的正在執行的容器內執行命令。這就像在食譜書中偷看特定頁面一樣。 11. **`docker run -d kodekloud/simple-webapp`** - 從「kodekloud/simple-webapp」鏡像以分離模式執行容器。這就像啟動一個程式並讓它在背景執行。 12. **`docker Attach a043d`** - 將您的終端附加到 ID 為「a043d」的正在執行的容器。這就像跳入正在執行的程式來查看發生了什麼。 **一些 Docker 概念:** 1. **使用標籤執行:** - 標籤就像程式的版本。它指定您要執行哪個版本。 - 範例程式碼:`docker run nginx:latest` - 這將執行最新版本的 Nginx 程式。 2. **使用標準輸入執行:** - STDIN 就像在鍵盤上打字一樣。有些程式需要您的輸入。 - 範例程式碼:`docker run -i -t ubuntu` - 這會在 Ubuntu 容器內執行互動終端,讓您可以鍵入命令。 3. **使用連接埠映射執行:** - 連接埠就像門。程式使用它們與外界進行通訊。 - 範例程式碼:`docker run -p 8080:80 nginx` - 這將執行 Nginx,並打開電腦連接埠 8080 上的門,將其連接到容器的連接埠 80。 4. **使用磁碟區映射執行:** - 磁碟區就像共用資料夾。它們讓您可以將東西存放在容器之外。 - 範例程式碼:`docker run -v /your/local/folder:/container/folder nginx` - 這將執行 Nginx 並將電腦上的資料夾連接到容器內的資料夾。 5. **檢查容器:** - 檢查就像仔細檢查正在執行的程式。 - 範例程式碼:`docker檢查container_name` - 這為您提供有關正在執行或已停止的容器的詳細資訊。 6. **容器日誌:** - 日誌就像日記。他們記錄程式正在做什麼。 - 範例程式碼:“docker 日誌容器名稱” - 這會向您顯示特定容器的日誌或活動。 ##環境變數 環境變數就像程式用來尋找重要資訊的便利筆記,有點像是程式可以理解和更好工作的秘密訊息! 1. **Python腳本(app.py)中的環境變數:** - 假設您有一個用 Python 寫的程式 (app.py)。您可能想要在不更改程式碼的情況下自訂它。您可以使用環境變數。 - 範例程式碼(app.py): ``` import os app_color = os.getenv("APP_COLOR", "default_color") print(f"The app color is {app_color}") ``` - 正常運作腳本:`python app.py` - 以特定顏色執行:`export APP_COLOR=blue; python 應用程式.py` 2. **在 Docker 中使用 ENV 變數:** - Docker 容器也可以使用環境變數。這就像是向容器內的程式發出指令。 - 範例程式碼: - `docker run -e APP_COLOR=green simple-webapp-color` - 這會執行 Docker 容器(`simple-webapp-color`)並將環境變數 `APP_COLOR` 設為「綠色」。 3. **檢查環境變數:** - 有時,您會想要檢查正在執行的容器正在使用哪些環境變數。 - 範例程式碼:`docker檢查blissful_hopper` - 此命令提供有關名為“blissful_hopper”的容器的詳細訊息,包括其環境變數。 簡單來說,環境變數就像程式(或 Docker 容器)可以讀取以了解如何行為的小註釋。您可以在執行程式之前設定這些註釋,程式將使用它們來自訂自身。第二個範例中的「export」指令就像在執行程式之前寫一條註釋,告訴它如何運作。 “docker Inspect”指令就像是在容器內部查看它有什麼註解。 ## Docker 映像 **Docker 檔案:** Dockerfile 就像是 Docker 建立映像的一組指令。這就像是烤蛋糕的食譜。 ``` # Use the Ubuntu base image FROM Ubuntu # Update apt repository RUN apt-get update # Install dependencies using apt RUN apt-get install -y python # Install Python dependencies using pip RUN pip install flask RUN pip install flask-mysql # Copy source code to /opt folder COPY . /opt/source-code # Set the working directory WORKDIR /opt/source-code # Specify entry point to run the web server ENTRYPOINT ["flask", "run"] ``` **建立自己的圖像的步驟:** 1. 使用上述內容建立一個名為「Dockerfile」的檔案。 2. 將其保存在與原始碼相同的目錄中。 **建置 Docker 映像:** 在終端機中執行以下命令: ``` docker build -t your-image-name . ``` 此命令告訴 Docker 使用目前目錄中的 Dockerfile (`.`) 建置映像,並使用您選擇的名稱對其進行標記 (`-t your-image-name`)。 **分層架構:** ![分層架構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9i91e79dg61wnbbfp62m.png) - 將 Docker 映像視為一個分層蛋糕。 Dockerfile 中的每個指令都會在映像上新增一層。 - 圖層可重複使用。如果您變更程式碼中的某些內容,Docker 只會重建受影響的層,從而提高效率。 **Docker 建置輸出:** - 當您建置映像檔時,Docker 會顯示流程中的每個步驟。如果發生故障,它會給您錯誤訊息。 **你可以容器化什麼?** - 幾乎所有東西!應用程式、服務、資料庫、網站,基本上任何軟體都可以容器化。 - 這就像將您的軟體放入一個盒子中,以便它可以在任何地方執行而不會造成麻煩。 ## 什麼是 Docker CMD 與 ENTRYPOINT **Docker 中的`CMD`:** - 將 CMD 視為啟動容器時程式執行的預設操作。 - 這就像說,“嘿,當你執行這個容器時,默認執行此操作。” - 範例:`CMD ["flask", "run"]` 表示當容器啟動時,它會自動執行 Flask Web 伺服器。 **CMD 範例:** ``` FROM alpine CMD ["sleep", "5"] ``` 在此範例中,當您使用此映像執行容器時,它會自動休眠 5 秒。 **Docker 中的`ENTRYPOINT`:** - 將 ENTRYPOINT 視為容器所做的主要事情。就好像boss的命令一樣。 - 它設定一個預設應用程式在容器啟動時執行,但您仍然可以根據需要覆蓋它。 - 範例:`ENTRYPOINT ["flask", "run"]` 表示容器主要用於執行 Flask Web 伺服器,但如果需要,您仍可新增更多指令。 **入口點範例:** ``` FROM alpine ENTRYPOINT ["sleep"] CMD ["5"] ``` 在這裡,主要目的是睡眠,如果您願意,您仍然可以覆蓋睡眠持續時間。 在這兩種情況下,容器在啟動時只會休眠幾秒鐘。主要區別在於如何提供參數以及它們是否可以輕鬆覆蓋。 CMD 就像在說,“這是默認要做的事情”,而 ENTRYPOINT 就像在說,“這是主要要做的事情,但如果你願意,你可以稍微調整一下。”它們都有助於定義容器啟動時執行的操作。 ## Docker 中的網路: Docker 網路幫助容器(程式)相互通信,確保它們可以順利地協同工作。 **預設網路:** - Docker 建立預設網路供容器通訊。 - 範例程式碼:`docker run ubuntu --network=host` - 這使用主機網路執行 Ubuntu 容器,這意味著它與主機共享網路命名空間。 **使用者定義的網路:** - 您可以建立自己的網路以更好地組織和控制。 - 範例程式碼: ``` docker network create --driver=bridge --subnet=182.18.0.0/16 custom-isolated-network ``` - 這將建立一個名為「custom-isolated-network」的使用者定義的橋接網絡,具有特定的子網。 **上市網路:** - 您可以查看您擁有的所有網路。 - 範例程式碼:`docker network ls` **檢查網路:** - 您可以檢查特定網路的詳細資訊。 - 範例程式碼:`docker網路檢查blissful_hopper` - 這顯示有關名為「blissful_hopper」的網路的詳細資訊。 **嵌入式 DNS:** - Docker 有一個內建的 DNS 系統,供容器透過名稱相互查找。 - 範例程式碼:`mysql.connect(mysql)` - 這可能是程式碼中的一行,其中名為「mysql」的服務使用 Docker 的 DNS 連接到另一個名為「mysql」的服務。 ## Docker 儲存: ![Docker 儲存](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7i54a6m0o1tb0812xbjk.jpg) Docker 儲存就像使用容器時決定將資料保存在哪裡一樣。您可以將它們保留在容器內,使用磁碟區在容器之間共用它們,或將它們儲存在容器外部以妥善保管。 **Docker中的檔案系統:** - Docker 使用分層架構來建立映像。 Dockerfile 中的每個指令都會在檔案系統中新增一個新圖層。 ``` # Dockerfile FROM Ubuntu RUN apt-get update && apt-get install -y python RUN pip install flask flask-mysql COPY . /opt/source-code WORKDIR /opt/source-code ENTRYPOINT ["flask", "run"] ``` - Dockerfile 中的層: - 第 1 層:Ubuntu 基礎層 - 第 2 層:apt 軟體包的更改 - 第 3 層:pip 套件的變化 - 第 4 層:原始碼 - 第 5 層:使用「flask」指令更新入口點 - 第 6 層:容器層 **影像圖層:** - 當您建立 Docker 映像時,它由唯讀層組成。每一層代表影像的變化或加入。 - 第 1 層:Ubuntu 基礎層 - 第 2 層:apt 軟體包的更改 - 第 3 層:pip 套件的變化 - 第 4 層:原始碼 - 第 5 層:使用「flask」指令更新入口點 ``` # Build the Docker image docker build -t mmumshad/my-custom-app . ``` **容器層:** - 當您執行 Docker 容器時,會在唯讀映像層上方新增一個讀寫層。該層特定於正在執行的容器。 - 第 6 層. 容器層 ``` # Run the Docker container docker run mmumshad/my-custom-app ``` **數量:** - 卷是一種在容器外部保存資料的方法。它們就像外部記憶體。 ``` # Create a Docker volume docker volume create data_volume # Use the volume in a container docker run -v data_volume:/var/mysql mysql ``` - 您也可以使用「-v」將特定目錄從主機掛載到容器: ``` # Mount a host directory to a container directory docker run -v /path/on/host:/var/mysql/mysql -d mysql ``` - docker run --mount 指令用於將主機上的特定目錄或檔案掛載到正在執行的 Docker 容器中。 ``` docker run --mount type=bind,source=/mysql,target=/var/mysql mysql ``` **儲存驅動程式:** - Docker 使用儲存驅動程式來管理資料的儲存和存取方式。一些常見的儲存驅動程式包括 AUFS、ZFS、BTRFS、Device Mapper、Overlay 和 Overlay2。 [在 Docker 管理資料](https://docs.docker.com/storage/) [關於儲存驅動程式](https://docs.docker.com/storage/storagedriver/) [卷](https://docs.docker.com/storage/volumes/) ## Docker 組合 ![Docker Compose](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjyro6o2844s2or1b83c.jpeg) Docker Compose 是一個方便的工具,可幫助您輕鬆執行和連接不同的軟體服務,就好像它們都是同一事件的一部分一樣。 **Docker Compose 基礎:** 1. **執行單一容器:** - 通常,您可以像這樣執行單獨的 Docker 容器: ``` docker run mmumshad/simple-webapp docker run mongodb docker run redis:alpine docker run ansible ``` 2. **Docker 撰寫文件(`docker-compose.yml`):** - Docker Compose 允許您在一個簡單的檔案中定義所有這些服務: ``` # docker-compose.yml version: '3' services: web: image: 'mmumshad/simple-webapp' database: image: 'mongodb' messaging: image: 'redis:alpine' orchestration: image: 'ansible' ``` - 此檔案描述您要執行的服務(「web」、「database」、「messaging」、「orchestration」)、它們各自的映像以及任何其他配置。 3. **使用 Docker Compose 執行:** - 要一起啟動所有這些服務: ``` docker-compose up ``` - Docker Compose 負責啟動「docker-compose.yml」檔案中定義的所有容器。 4. **使用 Docker Compose 建置:** - 您也可以使用 Docker Compose 建置映像: ``` docker-compose build ``` - 此指令建置「docker-compose.yml」檔案中指定的映像。 **執行連結容器:** - 如果您要透過連結執行單一容器: ``` docker run -d --name redis redis docker run --name voting-app -p 5000:80 --link redis:redis voting-app docker run --name result-app -p 5001:80 --link db:db result-app docker run -d --name worker --link db:db --link redis:redis worker ``` - 在 Docker 中撰寫: ``` # docker-compose.yml version: '3' services: vote: image: 'voting-app' ports: - '5000:80' links: - 'redis:redis' result: image: 'result-app' ports: - '5001:80' links: - 'db:db' worker: image: 'worker' links: - 'db:db' - 'redis:redis' db: image: 'db' redis: image: 'redis' ``` Docker Compose 可讓您在單一檔案中描述整個應用程式堆疊,從而輕鬆管理、執行和連接不同的服務。這就像在一份計劃中寫下活動的所有任務,然後 Docker Compose 為您處理設定。 [Docker Compose 概述](https://docs.docker.com/compose/) [Docker 撰寫文件](https://docs.docker.com/engine/reference/commandline/compose/) ## Docker 註冊表 ![Docker 註冊表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzflp82qyg36y8fcf8k8.png) Docker 註冊表是人們儲存和分享 Docker 映像的地方,使其他人可以輕鬆使用和執行他們的軟體。它就像一個大型線上程式庫,可以輕鬆下載並在不同電腦上使用。 **Docker 註冊表基礎知識:** 1. **公共登記處:** - Docker 映像可以在 Docker Hub 等公共註冊表中儲存和共用。 - 例: ``` docker pull nginx ``` 2. **私人登記處:** - 有時,您可能希望將圖像保存在您自己的私人註冊表中。 - 例: - 登入私人註冊表: ``` bash docker login private-registry.io ``` - 從私有註冊表中的映像執行容器: ``` docker run private-registry.io/apps/internal-app ``` 3. **部署您自己的私有註冊表:** - 您可以為您的團隊或公司部署自己的私人註冊表。 - 例: - 在您的電腦上執行私有註冊表: ``` docker run -d -p 5000:5000 --name registry registry:2 ``` - 為私人註冊表標記您的圖像: ``` bash docker image tag my-image localhost:5000/my-image ``` - 將映像推送到您的私人註冊表: ``` bash docker push localhost:5000/my-image ``` - 從您的私人註冊表中提取映像: ``` bash docker pull localhost:5000/my-image ``` 4. **從遠端私有註冊表中提取:** - 您也可以使用 IP 位址或網域從遠端私有註冊表中提取映像。 - 例: ``` docker pull 192.168.56.100:5000/my-image ``` Docker 註冊表就像一個儲存空間,人們在其中保存和共享 Docker 映像。您可以將公用註冊表用於廣泛使用的映像,也可以根據您的特定需求設定自己的私人註冊表。它就像一個用於共享和儲存軟體藍圖(圖像)的特殊庫。 ## Docker 引擎 ![Docker 引擎](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hc6r4dvo9kg9xqgwsrbb.jpg) 想像一下,你有一個魔盒(Docker Engine),可以為你執行和管理各種程式(容器)。 Docker Engine 就像是這個魔盒的大腦。 1. **Docker 守護程式:** - 守護程式就像魔法盒的看門人。它始終在那裡,隨時準備接受指示並確保一切順利進行。 2. **REST API:** - 將 REST API 視為一組允許您與魔盒對話的規則。它就像你和守護程式用來溝通的語言。你告訴守護程式要做什麼,它會理解,因為你們說的是同一種語言。 3. **Docker CLI(命令列介面):** - Docker CLI 就像是用來命令守護程式的魔杖。您輸入指令,守護程式就會按照您的指示進行操作。這就像說「Abracadabra」就能讓事情發生。 **連線到遠端 Docker 引擎:** 連接到遠端 Docker 引擎可讓您控制另一台機器上的容器,且設定約束可確保容器僅使用指定的資源。 1. **Docker主機IP:** - 您可以使用 IP 位址和連接埠連接到不同電腦上的 Docker 引擎。 - 例: ``` docker -H=remote-docker-engine:2375 run nginx ``` - 這告訴您的本機 Docker CLI 與遠端 Docker 引擎進行通訊。 2. **有約束地執行容器:** - Docker 允許您設定容器的資源限制,例如 CPU 和記憶體限制。 - 例: ``` docker run --cpus=0.5 ubuntu docker run --memory=100m ubuntu ``` - 這些指令限制容器僅使用半個 CPU 核心和 100 MB 記憶體。 當然,讓我們簡化一下PID命名空間的概念: **命名空間PID:** PID 命名空間可讓您為容器中的進程(如程式或任務)建立單獨的區域,因此它們有自己的一組「票號」(進程 ID),不會與容器外的進程發生衝突。 **範例程式碼:** 1. **使用主機 PID 命名空間執行容器:** - 這表示容器與主機共用相同的「票號」。 ``` docker run --pid=host ubuntu ``` 2. **執行具有隔離 PID 命名空間的容器:** - 這表示容器有自己的一組獨立於主機的「票號」。 ``` docker run --pid=container ubuntu ``` 在第一個範例中,容器與進程交互,就好像它與主機位於同一空間中一樣。在第二個範例中,容器有自己的進程隔離空間。這就像在大型活動中擁有一個私人區域,您的團隊有自己的一套票號,讓您可以獨立於活動的其餘部分進行操作。 **容器化概念:** 1. **進程 ID 命名空間:** - 容器有自己獨立的流程 ID (PID) 空間,因此容器內的流程與容器外的流程是分開的。 - 例: ``` docker run --pid=host ubuntu ``` - 此指令使用主機的 PID 命名空間來執行容器,因此它共用相同的程序。 2. **網路命名空間:** - 容器也有自己獨立的網路命名空間,這意味著它們可以有自己的網路配置。 - 例: ``` docker run --net=host nginx ``` - 此指令使用主機的網路命名空間來執行容器。 3. **Unix分時命名空間:** - 此命名空間允許容器擁有自己的時間視圖,與主機和其他容器分開。 - 例: ``` docker run --uts=host ubuntu ``` - 此指令使用主機的 Unix 時間共用命名空間來執行容器。 4. **進程間掛載命名空間:** - Mount命名空間隔離檔案系統,讓容器擁有自己的檔案系統視圖。 - 例: ``` docker run --mount=type=bind,source=/host/folder,target=/container/folder ubuntu ``` - 此指令將主機中的資料夾安裝到容器中。 當然!我們來簡化一下cgroup的概念: **C組:** cgroup(控制組的縮寫)可協助在不同進程或容器之間管理和分配系統資源,例如 CPU 和記憶體。它們確保沒有任何一個進程或容器耗盡所有可用資源,從而保持一切平衡。 **範例程式碼:** 1. **使用 Cgroup 設定 CPU 限制:** - 這就像說聚會上的每位客人只能吃一定數量的食物。 ``` docker run --cpus=0.5 ubuntu ``` - 這限制容器僅使用一半的 CPU 核心。 2. **使用 Cgroup 設定記憶體限制:** - 這就像說每位客人只能在舞池上佔據一定的空間。 ``` docker run --memory=100m ubuntu ``` - 這限制容器僅使用 100 MB 記憶體。 [Docker 引擎概述](https://docs.docker.com/engine/) [使用 Docker Engine API 進行開發](https://docs.docker.com/engine/api/) [執行時指標](https://docs.docker.com/config/containers/runmetrics/#control-groups) ## Linux容器與Windows容器的概念: **Linux 容器(預設):** Linux 容器是一種打包和執行軟體及其所需一切的方法,它們最適合執行 Linux 的電腦。 **Windows 容器:** Windows 容器是一種打包和執行軟體的方式,就像 Linux 容器一樣,但它們設計用於執行 Windows 的電腦。 **Windows 容器基礎:** 1. **集裝箱類型:** - Windows 容器有兩種主要類型:Windows Server Core 和 Nano Server。 - **Windows Server Core:** 將其視為功能更齊全的容器,適合各種應用程式。 - **Nano Server:** 將其視為一個輕量級容器,專為特定的、簡約的用例而設計。 2. **基礎鏡像:** - 基礎映像就像是建立容器時開始使用的空白畫布。 - 例: ``` docker pull mcr.microsoft.com/windows/servercore:ltsc2019 ``` - 此指令擷取 Windows Server Core 基礎映像。 - 例: ``` docker pull mcr.microsoft.com/windows/nanoserver:ltsc2019 ``` - 此命令提取 Nano Server 基礎映像。 3. **支援的環境:** - Windows 容器可以在特定版本的 Windows 作業系統上運作。 - 例: - 您可以在 Windows Server 2016 上執行 Windows 容器。 - 例: - 您可以在 Windows 10 專業版和企業版上執行 Windows 容器,並使用 Hyper-V 隔離容器進行額外隔離。 ## 容器編排 ![容器編排](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lyleybojw3xmr2dgfep.png) 容器編排是一種管理和協調多個容器的方法,確保它們無縫協作來執行應用程式,就像一個超級智能的管理器確保所有機器人一起工作來建置完美的塔一樣。 **為什麼要編曲?** 1. **多項任務,一名經理:** - 想像一下您有許多機器人(容器)執行不同的工作。編排就像有一位超級聰明的經理(編排者),他告訴每個機器人該做什麼,並確保一切順利進行。 2. **一致性:** - 編排確保所有任務每次都以相同的方式完成。這就像為您的機器人提供了一套要遵循的指令,以確保其行為的一致性。 3. **效率:** - 編排有助於優化任務,確保資源(如時間和材料)有效利用。這就像經理確保所有機器人一起工作而不浪費能源。 4. **縮放比例:** - 當您需要完成更多工作時,編排可以輕鬆建立額外的機器人(容器)。這就像當有很多事情需要完成時神奇地召喚更多機器人來提供幫助。 5. **可靠性:** - 編排確保任務可靠地完成,即使機器人(容器)出現故障。這就像製定備份計劃來確保無論如何都能完成工作。 6. **協調:** - 編排協調任務,確保機器人無縫協作。這就像經理確保每個機器人都知道自己的角色並協作以實現總體目標。 **容器編排程式碼:** ``` # Create a Docker service with 100 replicas (instances) of a Node.js application docker service create --replicas 100 --name my-nodejs-app nodejs ``` 在這個例子中: - `docker service create`:該指令告訴 Docker 建立一個服務,該服務是一組正在執行的容器。 - `--replicas 100`:此標誌指定您需要 100 個服務實例(副本)。 - `--name my-nodejs-app`:此標誌為您的服務提供名稱,在本例中為「my-nodejs-app」。 - `nodejs`:這是 Node.js 應用程式的圖片或配方。這就像是烘焙紙杯蛋糕的藍圖。 因此,這段簡單的程式碼就像告訴您神奇的廚師助手 (Docker Swarm) 建立 Node.js 應用程式的 100 個副本,確保您有大量容器正在執行並準備好提供服務。 ## Docker 群 ![Docker Swarm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5noonjo2ikyrp90kc38.png) Docker Swarm 是一個工具,可以幫助協調和管理一組電腦(節點)作為一個機器人團隊一起工作,使它們能夠以協調的方式部署和執行多個容器。這就像有一個首席機器人經理,確保所有單一機器人一起建造出偉大而令人驚嘆的東西。 **設定 Docker Swarm:** 1. **群組管理器:** - 想像你有一個首席機器人(Swarm Manager)來領導團隊。主機器人決定需要做什麼,並指導其他機器人(節點)如何協同工作。 ``` # Initiate Docker Swarm on the Swarm Manager docker swarm init ``` 2. **節點工作人員:** - 現在,您的工作機器人(節點工作人員)已準備好加入團隊。 Swarm Manager 共享一個特殊的程式碼(令牌)來邀請他們一起工作。 ``` # Join a Node Worker to the Docker Swarm docker swarm join --token <token> <Swarm Manager IP> ``` **Docker Swarm 服務:** 現在您已經有了一個協調的團隊,您想要建立一項服務,例如與您的機器人團隊一起建造塔: ``` # Create a Docker service (a group of containers) with 3 replicas (instances) docker service create --replicas 3 --network frontend --name my-web-server my-web-image ``` - `--replicas 3`:此標誌告訴 Docker 建立服務的三個實例(副本)。 - `--network frontend`:此標誌指定您的服務屬於名為「frontend」的網路。 - `--name my-web-server`:這會為您的服務命名,在本例中為「my-web-server」。 - `my-web-image`:這是您的網頁伺服器的圖片或藍圖。這就像建造塔樓的配方。 您建立了一個由隊長(Swarm Manager)和工作機器人(Node Workers)組成的機器人團隊。然後,您指示他們建立一個執行您的 Web 伺服器應用程式的服務(容器群組)。主機器人確保建立 Web 伺服器的三個副本並將其連接到「前端」網路。這就像有一個首席機器人經理在工作機器人的幫助下監督多個塔(貨櫃)的建造。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytitz2wt4jsufejox9yx.gif) **好的,這就是本文的內容。** 另外,如果您對此或其他任何問題有任何疑問,請隨時在下面的評論中或在 [Instagram](https://www.instagram.com/_abhixsh/) 、[Facebook](https://www.facebook.com/abhi.haththakage/) 或[Twitter](https://twitter.com/abhixsh)。 感謝您閱讀這篇文章,我們下一篇再見! ❤️ --- 原文出處:https://dev.to/abhixsh/docker-for-the-absolute-beginner-3h1p

iosbase的register_callback突然跳過一行

問題描述: 在testfn函數中,switch imbue的部分會被filestr.imbue(std::cout.getloc());觸發, 接下來理論上會cout, 接著LOG(我自己寫的輸出封裝函數), 但是cout後就離開switch, 觸發流刪除事件了,LOG沒有觸發。 如果先LOG再cout則沒問題, LOG和cout都會被輸出到console。 像這樣,提供出stream.imbue_event非常多的信息。 ``` LOG [enum std::ios_base::event] stream.imbue_event = 1At testfn ,line 00219 imbue_event erase_event ``` 出事代碼 C++ ``` #include <iostream> //std::ios, std::cout, std::cerr, std::clog, std::left #include <iomanip> #include <fstream> // ofstream #include <locale> // std::locale #include <typeinfo> #include <chrono> #include <thread> #include <ctime> #include <sstream> template <typename T> void Log(const char* varName, T var, int line, const char* functionName) { std::stringstream ss; ss << "[" << typeid(var).name() << "] " << varName << " = " << var; std::ios_base::fmtflags originalFlags = std::cout.flags();/*FORMAT FLAG*/\ /*auto now = std::chrono::system_clock::now();*//*TIME*/\ /*std::time_t timestamp = std::chrono::system_clock::to_time_t(now);*//*TIME*/\ /* std::cout << "\nTime taken at Total : " << duration.count() / 1000.0 << " millisecond" << std::endl;*//*TIME*/ std::cout.width(0); std::cout << std::setfill(' ') << std::left << "LOG "\ << std::setw(50) << ss.str()/*TYPE*//*VARIABLE*//*VALUE*/\ << "At " << std::setw(15) << functionName /*FUNCTION*/\ << std::dec << ",line " << std::setw(5) << std::setfill('0') << std::right << line /*LINE*/\ /*<< "File: " << __FILE__ */ /*FILE*/\ /*<< ", Thread ID: " << std::this_thread::get_id() << ", "*//*THREAD*/\ << std::endl; \ std::cout.flags(originalFlags); } //register_callback, imbue_event, erase_event void testfn(std::ios::event ev, std::ios_base& stream, int index) { switch (ev) { case stream.copyfmt_event: std::cout << "copyfmt_event\n"; break; case stream.imbue_event: std::cout << "imbue_event\n"; break; Log("stream.imbue_event", stream.imbue_event, 219, __FUNCTION__); case stream.erase_event: std::cout << "erase_event\n"; break; } } void funcPart6() { /*register_callback*/std::cout << "//---//\n"; std::ofstream filestr; filestr.register_callback(testfn, 0);/*imbue_event*//*erase_event*/ filestr.imbue(std::cout.getloc()); /* * 使用register_callback來設定事件 如 copyfmt_event imbue_event erase_event * filestr.register_callback(testfn, 0); * /* * 筆記: * 試圖複製或複製但是構造了一個 std::basic_ofstream 對象,該類的複製構造函數是被刪除的(deleted)。 * 這是因為 std::basic_ofstream 對象通常代表一個文件流,而文件流的複製構造函數不是允許的, * 因為一個檔案流在複製時可能會引起一些不可預測的行為。在這種情況下,應該避免直接複製 std::basic_ofstream 物件。 */ } ```

在沒有伺服器的情況下在視窗之間共享狀態

最近,社群網路上流行一張 gif 動圖,展示了一件 [Bjorn Staal 製作的令人驚嘆的藝術品](https://twitter.com/_nonfigurativ_/status/1727322594570027343)。 ![Bjorn Staal 藝術作品](https://cdn-images-1.medium.com/max/2000/1*vCKb_XLed3eD9y4h-yjdKQ.gif) 我想重新建立它,但缺乏球體、粒子和物理的 3D 技能,我的目標是了解如何讓一個視窗對另一個視窗的位置做出反應。 本質上,在多個視窗之間共享狀態,我發現這是 Bjorn 專案中最酷的方面之一! 由於無法找到有關該主題的好文章或教程,我決定與您分享我的發現。 > 讓我們嘗試根據 Bjorn 的工作建立一個簡化的概念驗證 (POC)! ![我們將嘗試創造什麼(ofc 它比 Bjorn 的作品沒那麼性感)](https://cdn-images-1.medium.com/max/2000/1*KJHO9DmEDcTISWuCcvDpMQ.gif) 我做的第一件事就是列出我所知道的在多個客戶端之間共享資訊的所有方法: ## 呃:伺服器 顯然,擁有伺服器(帶有輪詢或 Websocket)可以簡化問題。然而,由於 Bjorn 在沒有使用伺服器的情況下實現了他的結果,所以這是不可能的。 ## 本機存儲 本地存儲本質上是瀏覽器鍵值存儲,通常用於在瀏覽器會話之間保存資訊。雖然通常用於儲存身份驗證令牌或重定向 URL,但它可以儲存任何可序列化的內容。 [您可以在這裡了解更多](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)。 我最近發現了一些有趣的本地儲存 API,包括 *`storage`* 事件,每當同一網站的另一個會話更改本地儲存時就會觸發該事件。 ![儲存事件如何運作(當然是簡化的)](https://cdn-images-1.medium.com/max/4048/1*otw7fDvd-XFjj9yVBxn5zg.png) 我們可以透過將每個視窗的狀態儲存在本地儲存中來利用這一點。每當一個視窗改變其狀態時,其他視窗將透過儲存事件進行更新。 這是我最初的想法,這似乎是Bjorn 選擇的解決方案,因為他分享了他的LocalStorage 管理器程式碼以及與ThreeJs 一起使用的範例[此處](https://github.com/bgstaal/multipleWindow3dScene) 。 但是當我發現有程式碼可以解決這個問題時,我想看看是否有其他方法…劇透警告:是的,有! ## 共享工作者 這個華而不實的術語背後是一個令人著迷的概念——WebWorkers 的概念。 簡單來說,工作執行緒本質上是在另一個執行緒上執行的第二個腳本。雖然它們無法存取 DOM(因為它們存在於 HTML 文件之外),但它們仍然可以與您的主腳本進行通訊。 它們主要用於透過處理背景作業來卸載主腳本,例如預取資訊或處理不太關鍵的任務(例如流日誌和輪詢)。 ![腳本與worker之間通訊機制的簡單解釋](https://cdn-images-1.medium.com/max/3548/1*izcnWc_p13m8pZy5d49mVw.png) 共享工作線程是一種特殊類型的 WebWorkers,它可以與同一腳本的多個實例進行通信,這使得它們對我們的用例很有趣!好吧,讓我們直接進入程式碼! ![共享工作人員可以將資訊傳送到同一腳本的多個會話](https://cdn-images-1.medium.com/max/5428/1*A7ObCM2OjojgfFP57ankyw.png) ### 設定工人 如前所述,工作人員是具有自己的入口點的「第二腳本」。根據您的設定(TypeScript、捆綁程式、開發伺服器),您可能需要調整 tsconfig、新增指令或使用特定的匯入語法。 我無法涵蓋所有使用 Web Worker 的可能方法,但您可以在 MDN 或網路上找到資訊。 如果需要,我很樂意為本文撰寫前傳,詳細介紹設定它們的所有方法! 就我而言,我使用的是 Vite 和 TypeScript,因此我需要一個「worker.ts」檔案並將「@types/sharedworker」安裝為開發依賴項。我們可以使用以下語法在主腳本中建立連結: ``` new SharedWorker(new URL("worker.ts", import.meta.url)); ``` 基本上,我們需要: * 辨識每個視窗 * 追蹤所有視窗狀態 * 一旦視窗改變狀態,提醒其他視窗重繪 我們的狀態將非常簡單: ``` type WindowState = { screenX: number; // window.screenX screenY: number; // window.screenY width: number; // window.innerWidth height: number; // window.innerHeight }; ``` 當然,最重要的訊息是“window.screenX”和“window.screenY”,因為它們告訴我們視窗相對於顯示器左上角的位置。 我們將有兩種類型的訊息: * 每個窗口,無論何時改變其狀態,都會發布一個帶有新狀態的“windowStateChangedmessage”。 * 工作人員將向所有其他視窗發送更新,以提醒他們其中一個視窗已更改。工作人員將發送包含所有視窗狀態的「syncmessage」。 我們可以從一個看起來有點像這樣的普通工人開始: ``` // worker.ts let windows: { windowState: WindowState; id: number; port: MessagePort }[] = []; onconnect = ({ ports }) => { const port = ports[0]; port.onmessage = function (event: MessageEvent<WorkerMessage>) { console.log("We'll do something"); }; }; ``` 我們與 SharedWorker 的基本連結如下所示。我有一些基本函數可以產生 id,並計算當前視窗狀態,我還對我們可以使用的稱為 WorkerMessage 的訊息類型進行了一些輸入: ``` // main.ts import { WorkerMessage } from "./types"; import { generateId, getCurrentWindowState, } from "./windowState"; const sharedWorker = new SharedWorker(new URL("worker.ts", import.meta.url)); let currentWindow = getCurrentWindowState(); let id = generateId(); ``` 一旦我們啟動應用程式,我們應該提醒工作人員有一個新窗口,因此我們立即發送一條訊息: ``` // main.ts sharedWorker.port.postMessage({ action: "windowStateChanged", payload: { id, newWindow: currentWindow, }, } satisfies WorkerMessage); ``` 我們可以在工作端監聽此訊息並相應地更改 onmessage。基本上,一旦工作人員收到 windowStateChanged 訊息,要么它是一個新窗口,我們將其附加到狀態,要么它是一個已更改的舊窗口。然後我們應該提醒大家狀態已經改變: ``` // worker.ts port.onmessage = function (event: MessageEvent<WorkerMessage>) { const msg = event.data; switch (msg.action) { case "windowStateChanged": { const { id, newWindow } = msg.payload; const oldWindowIndex = windows.findIndex((w) => w.id === id); if (oldWindowIndex !== -1) { // old one changed windows[oldWindowIndex].windowState = newWindow; } else { // new window windows.push({ id, windowState: newWindow, port }); } windows.forEach((w) => // send sync here ); break; } } }; ``` 為了發送同步,我實際上需要一些技巧,因為“port”屬性無法序列化,所以我將其字串化並解析回來。因為我很懶,所以我不只是將視窗映射到更可序列化的陣列: ``` w.port.postMessage({ action: "sync", payload: { allWindows: JSON.parse(JSON.stringify(windows)) }, } satisfies WorkerMessage); ``` 現在是時候畫東西了! ## 有趣的部分:繪畫! 當然,我們不會做複雜的 3D 球體:我們只會在每個視窗的中心畫一個圓,並在球體之間畫一條線! 我將使用 HTML Canvas 的基本 2D 上下文進行繪製,但您可以使用任何您想要的內容。畫一個圓,非常簡單: ``` const drawCenterCircle = (ctx: CanvasRenderingContext2D, center: Coordinates) => { const { x, y } = center; ctx.strokeStyle = "#eeeeee"; ctx.lineWidth = 10; ctx.beginPath(); ctx.arc(x, y, 100, 0, Math.PI * 2, false); ctx.stroke(); ctx.closePath(); }; ``` 為了繪製線條,我們需要做一些數學運算(我保證,這不是很多🤓),並將另一個視窗中心的相對位置轉換為目前視窗上的座標。 基本上,我們正在改變基地。我用一點數學來做到這一點。首先,我們將更改底座以在顯示器上具有座標,並透過目前視窗 screenX/screenY 進行偏移 ![基本上我們正在尋找鹼基變化後的目標位置](https://cdn-images-1.medium.com/max/5056/1*Zg_z1aZxUE1WP-uOk1owdw.png) ``` const baseChange = ({ currentWindowOffset, targetWindowOffset, targetPosition, }: { currentWindowOffset: Coordinates; targetWindowOffset: Coordinates; targetPosition: Coordinates; }) => { const monitorCoordinate = { x: targetPosition.x + targetWindowOffset.x, y: targetPosition.y + targetWindowOffset.y, }; const currentWindowCoordinate = { x: monitorCoordinate.x - currentWindowOffset.x, y: monitorCoordinate.y - currentWindowOffset.y, }; return currentWindowCoordinate; }; ``` 如您所知,現在我們在同一相對座標系上有兩個點,我們現在可以畫線了! ``` const drawConnectingLine = ({ ctx, hostWindow, targetWindow, }: { ctx: CanvasRenderingContext2D; hostWindow: WindowState; targetWindow: WindowState; }) => { ctx.strokeStyle = "#ff0000"; ctx.lineCap = "round"; const currentWindowOffset: Coordinates = { x: hostWindow.screenX, y: hostWindow.screenY, }; const targetWindowOffset: Coordinates = { x: targetWindow.screenX, y: targetWindow.screenY, }; const origin = getWindowCenter(hostWindow); const target = getWindowCenter(targetWindow); const targetWithBaseChange = baseChange({ currentWindowOffset, targetWindowOffset, targetPosition: target, }); ctx.strokeStyle = "#ff0000"; ctx.lineCap = "round"; ctx.beginPath(); ctx.moveTo(origin.x, origin.y); ctx.lineTo(targetWithBaseChange.x, targetWithBaseChange.y); ctx.stroke(); ctx.closePath(); }; ``` 現在,我們只需要對狀態變化做出反應。 ``` // main.ts sharedWorker.port.onmessage = (event: MessageEvent<WorkerMessage>) => { const msg = event.data; switch (msg.action) { case "sync": { const windows = msg.payload.allWindows; ctx.reset(); drawMainCircle(ctx, center); windows .forEach(({ windowState: targetWindow }) => { drawConnectingLine({ ctx, hostWindow: currentWindow, targetWindow, }); }); } } }; ``` 最後一步,我們只需要定期檢查視窗是否發生變化,如果發生變化則發送訊息 ``` setInterval(() => { const newWindow = getCurrentWindowState(); if ( didWindowChange({ newWindow, oldWindow: currentWindow, }) ) { sharedWorker.port.postMessage({ action: "windowStateChanged", payload: { id, newWindow, }, } satisfies WorkerMessage); currentWindow = newWindow; } }, 100); ``` [您可以在此儲存庫中找到完整的程式碼](https://github.com/achrafl0/multi-window-article)。實際上,我用它做了很多實驗,使它變得更加抽象,但其要點是相同的。 如果您在多個視窗上執行它,希望您能得到與此相同的結果! ![完整結果](https://cdn-images-1.medium.com/max/2000/1*KJHO9DmEDcTISWuCcvDpMQ.gif) 謝謝閱讀 ! 如果您發現這篇文章有幫助、有趣或只是有趣,您可以將其分享給您的朋友/同事/社區 [您也可以訂閱我的電子報](https://notachraf.substack.com/)它是免費的! --- 原文出處:https://dev.to/notachraf/sharing-a-state-between-windows-without-a-serve-23an

📚 前 1% 的 React 開發者使用的 8 個儲存庫 🏆

你好👋 今天,讓我們來看看**前 1% 的開發人員使用**的 8 個 React 儲存庫(以及那些您可能從未聽說過的儲存庫)。 準備好? ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5yehweju0i54ov2n6bwt.gif) --- # 我們如何找到前 1% 的開發人員使用的儲存庫? 🔦 我們如何找到最好的開發人員使用的東西背後的故事植根於大量的資料探勘和一些重要的建模。 現在,在 Quine,我們根據開發人員的**[DevRank](https://docs.quine.sh/for-developers/devrank)** 對開發人員進行排名。 簡單來說,DevRank 使用 [Google 的 PageRank 演算法](https://en.wikipedia.org/wiki/PageRank) 根據開發人員對開源儲存庫的貢獻來衡量開發人員在開源領域的重要性。 為了建立此列表,我們查看了前 1% 已加星號的儲存庫。 🌟 然後,我們計算了前 1% 的開發者會為回購加註星標的可能性,與後 50% 的開發者不支持的可能性進行比較。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/miugcnqpataix1fsq6hb.png) 最後,經過一番挑選,我們找到了以下 8 個儲存庫。 :向下點: 當您想要建立很酷的網頁應用程式時,這些儲存庫將特別有用。** 如果您有興趣建立小型應用程式,並且喜歡應用人工智慧方面,我們建議您查看 Creator Quests,這是一項**開源挑戰,獎勵開發人員使用 ChatGPT、Claude、Gemini 建立酷炫的 GenerativeAI 應用程式**和更多。 :upside_down_face: 💰 最新的 Creator Quest 挑戰您使用生成式 AI 建立開發人員工具。要參與,只需註冊 [Quine](https://quine.sh/?utm_source=devto&utm_campaign=best_react_repos) 並前往 _Quests_。 **目前獎金池為$2028**,並且隨著更多參與者的加入,獎金池將會增加!點擊下面的圖片並嘗試! ⬇️ [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/akiuhk62zctvf3b9gilx.png)](https://quine.sh/?utm_source=devto&utm_campaign=best_react_repos) --- # jsxstyle/jsxstyle **不再有 JS 到 CSS 的跳躍** [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h75mskqja5bcwst05e93.png)](https://github.com/jsxstyle/jsxstyle) **為什麼要關心?** 在 Web 開發中,使用 React 或 Preact,您必須設定元件的樣式(如按鈕、選單等)。傳統上,這是使用單獨的 CSS 檔案或複雜的樣式系統來完成的,這可能非常耗時且管理起來很麻煩。 jsxstyle 可讓您直接在 JavaScript 程式碼中以及元件中定義樣式,從而簡化了此過程。換句話說,這意味著您不再需要在 JS 和 CSS 檔案之間跳躍。 **設定**:`npm install jsxstyle` **範例用例**:您的程式碼可以如下所示。 👇 ``` <Row padding={15}> <Block backgroundColor="#EEE" boxShadow="inset 0 0 0 1px rgba(0,0,0,0.15)" borderRadius={5} height={64} width={64} marginRight={15} backgroundSize="contain" backgroundImage="url(http://graph.facebook.com/justinbieber/picture?type=large)" /> <Col fontFamily="sans-serif" fontSize={16} lineHeight="24px"> <Block fontWeight={600}>Justin Bieber</Block> <Block fontStyle="italic">Canadian</Block> </Col> </Row> ``` [https://github.com/jsxstyle/jsxstyle](https://github.com/jsxstyle/jsxstyle) --- # 💨 alangpierce/蔗糖酶 **Babel 的超快替代品** [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rk9ceq6mlw8ya0f2icb8.png)](https://github.com/alangpierce/sucrase) **為什麼你應該關心?** Babel 是 Web 開發中廣泛使用的工具,可將現代 JavaScript 程式碼轉換為舊瀏覽器可以理解的格式。 Sucrase 是 Babel 更快的替代品。 **設定**: ``` yarn add --dev sucrase # Or npm install --save-dev sucrase node -r sucrase/register main.ts ``` **用例範例**:Sucrase 可以直接從 JS 呼叫: ``` import {transform} from "sucrase"; const compiledCode = transform(code, {transforms: ["typescript", "imports"]}).code; ``` [https://github.com/alangpierce/sucrase](https://github.com/alangpierce/sucrase) --- # 🎨 woorm/折射鏡 **我為您的網頁程式碼著色,讓您的生活更輕鬆** [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzwpgi5t47o93kvcbtdq.png)](https://github.com/wooorm/refractor) **為什麼你應該關心?** Refractor 很重要,因為它允許您加入突出顯示,從而增強專案的可讀性;尤其是當您將程式碼片段新增至 Web 應用程式時。它允許您用 270 多種程式語言表達程式碼,並且在傳統的基於 HTML 的突出顯示不理想的領域(例如 CLI 表單)特別有用。 **設定**:`npm install refractor` **用例範例**: ``` import {refractor} from 'refractor' const tree = refractor.highlight('"use strict";', 'js') console.log(tree) ``` **產量**: ``` { type: 'root', children: [ { type: 'element', tagName: 'span', properties: {className: ['token', 'string']}, children: [{type: 'text', value: '"use strict"'}] }, { type: 'element', tagName: 'span', properties: {className: ['token', 'punctuation']}, children: [{type: 'text', value: ';'}] } ] } ``` [https://github.com/wooorm/refractor](https://github.com/wooorm/refractor) --- # 🐦 react-static-tweets **您在網站上加入推文的最佳選擇。** [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1lvul78znx84ph479fa1.png)](https://github.com/transitive-bullshit/react-static-tweets) **為什麼你應該關心?** 將推文加入到您的網站是您在許多登陸頁面上看到的一項很酷的功能。 React Static Tweets 很重要,因為它提供了一種在 Web 專案中嵌入推文的高效方法,與 Twitter 的標準嵌入方法相比,提供更快的載入時間和更好的效能。 **設定**: ``` npm install react-static-tweets static-tweets date-fns # or yarn add react-static-tweets static-tweets date-fns ``` **用例範例:** ``` import React from 'react' import { fetchTweetAst } from 'static-tweets' import { Tweet } from 'react-static-tweets' const tweetId = '1358199505280262150' export const getStaticProps = async () => { try { const tweetAst = await fetchTweetAst(tweetId) return { props: { tweetAst }, revalidate: 10 } } catch (err) { console.error('error fetching tweet', err) throw err } } export default function Example({ tweetAst }) { return <Tweet ast={tweetAst} /> } ``` [https://github.com/transitive-bullshit/react-static-tweets](https://github.com/transitive-bullshit/react-static-tweets) --- # 🖨️ preactjs/preact-render-to-string **以 HTML 形式呈現您的元件** [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7djwj6w7nqwfnifc43c.png)](https://github.com/preactjs/preact-render-to-string) **為什麼要關心?** 「preact-render-to-string」是一個工具,可以幫助網站更快地載入並在搜尋引擎中更好地顯示。使用 Preact 等 JS 框架建立的網站需要一段時間才能顯示內容,因為瀏覽器必須先執行 JavaScript。此儲存庫透過將元件轉換為現成的 HTML 來完成伺服器端的繁重工作。因此,當有人造訪該網站時,即使網路速度很慢,他們也會立即看到內容。 **設定**:`npm install preact-render-to-string` **用例範例:** ``` import { render } from 'preact-render-to-string'; import { h, Component } from 'preact'; /** @jsx h */ // Classical components work class Fox extends Component { render({ name }) { return <span class="fox">{name}</span>; } } // ... and so do pure functional components: const Box = ({ type, children }) => ( <div class={`box box-${type}`}>{children}</div> ); let html = render( <Box type="open"> <Fox name="Finn" /> </Box> ); console.log(html); // <div class="box box-open"><span class="fox">Finn</span></div> ``` [https://github.com/preactjs/preact-render-to-string](https://github.com/preactjs/preact-render-to-string) --- # 🏆 自行車刮鬍/曲柄 **唯一的 JavaScript 框架** [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l8hp1ex1qh1sv6isksaq.png)](https://github.com/bikeshaving/crank) **為什麼要關心?** 在像 React 這樣的傳統 Web 框架中,Web 元件配置一次,僅在明確指定時才更改。它們看起來像是需要手動更新的靜態影像。 Crank.js 透過允許小部件更新自身以回應新資料來改變這一點,類似於用新新聞刷新的新聞收報機。這對於管理即時資料(例如即時體育賽事比分或產品更新)的 Web 應用程式尤其有用。 這個倉庫需要更多的人遷移到這裡才能獲得關注,但它仍然是一個非常酷的倉庫,值得關注。 👀 **設定**:`$ npm i @b9g/crank` **用例範例**: ``` import {renderer} from "@b9g/crank/dom"; function Greeting({name = "World"}) { return ( <div>Hello {name}</div> ); } renderer.render(<Greeting />, document.body); ``` [https://github.com/bikeshaving/crank](https://github.com/bikeshaving/crank) --- # 🎯 evoluhq/evolu **我是一個本地第一的人** [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k15m25pi0w9pk0g54zrn.png)](https://github.com/evoluhq/evolu) **為什麼要關心?** Web 應用程式通常依賴在伺服器上儲存使用者資料,這需要持續的網路連接,並引起對隱私和資料安全的擔憂。這種基於伺服器的方法也意味著如果伺服器發生故障或公司停止運營,效能會降低,並且可能會遺失資料。 Evolu 引入了「本地優先」方法,其中資料直接儲存在使用者的裝置上。這意味著您的應用程式可以離線工作,更快地存取資料,並提供增強的隱私和安全性。如果您正在建立離線 Chrome/瀏覽器應用程式,這將非常有用。 **設定**:` npm install @evolu/react` 要開始使用它,您可以在[此處](https://www.evolu.dev/docs/quickstart)找到這個很棒的指南。 [https://github.com/evoluhq/evolu](https://github.com/evoluhq/evolu) --- # 📸 笑話社群/快照差異 **我比較你們的元件並突出顯示差異** [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hy76comkwkqkt0d5qn8z.png)](https://github.com/jest-community/snapshot-diff) **為什麼要關心?** 在測試 React 元件或其他 JavaScript 值時,開發人員通常會比較整個狀態或輸出。這意味著處理大量資料,查找特定變更就像大海撈針一樣。 Snapshot-diff 是重點比較工具,可讓您取得元件的兩種不同狀態(或任兩個 JavaScript 值)並直接比較它們,僅將差異突出顯示。 這對於測試 React 元件特別有幫助,因為它可以準確指出兩種狀態之間發生的變化,從而更容易辨識和理解程式碼變更的影響。 **設定**:`yarn add --dev snapshot-diff` **範例用例:** 預設笑話匹配器 ``` const snapshotDiff = require('snapshot-diff'); test('snapshot difference between 2 strings', () => { expect(snapshotDiff(a, b)).toMatchSnapshot(); }); const React = require('react'); const Component = require('./Component'); test('snapshot difference between 2 React components state', () => { expect( snapshotDiff(<Component test="say" />, <Component test="my name" />) ).toMatchSnapshot(); }); ``` [https://github.com/jest-community/snapshot-diff](https://github.com/jest-community/snapshot-diff) --- **我希望這些發現對您有價值,並將有助於建立更強大的 React 工具包!⚒️** 如果您今天想利用這些工具來獲得獎勵,我們剛剛發起了一項使用生成式人工智慧建立開發人員工具的挑戰。 如果對此有興趣,請登入 [Quine](https://quine.sh/?utm_source=devto&utm_campaign=best_react_repos) 並發現任務! 💰 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5drisgbolxfnzfvtwzw.gif) 最後,請**考慮透過加星號來支持這些專案。 ⭐️** PS:我們與他們沒有任何關係。我們只是認為偉大的專案值得高度認可。 下週見, 您的開發夥伴💚 巴普 --- 原文出處:https://dev.to/quine/8-repos-used-by-the-top-1-of-react-devs-2758

Pipeline 🛠️Repos 對決⚔️:Python 🐍 版

## **簡介** 在不斷發展的資料工程和自動化領域,Python 已經出現了多種工作流程編排器。在本文中,我將介紹 6 個 Python 函式庫及其一些主要功能。 ![介紹 GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d09jxm34esmawmnv93lt.gif) --- ## 1. [Taipy](https://github.com/Avaiga/taipy) Taipy 是一個開源 Python 庫,用於建立生產就緒的應用程式前端和後端。 對於 Python 開發人員來說,Taipy 是建立管道最簡單的框架之一,這要歸功於它的管道圖形編輯器 (Taipy Studio)。 然後,您可以透過 Python 腳本輕鬆執行和編排管道。一個非常酷的核心功能是每個管道執行都是註冊的。 這可以輕鬆實現假設分析、KPI 監控、資料沿襲等。 🔑特點: - 圖形管道編輯器 - 與 Taipy 前端功能整合以實現端到端部署 - 日程安排 - 管道的版本控制 - 快取等智慧功能 ![太比圖解](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28kg5av8pk2sdrvi3bl8.png) --- ![QueenB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovcc5kqd8xr9uihrggbb.gif) 您的支持意義重大🌱,並且在許多方面為我們帶來了很大的幫助,例如寫文章! 🙏 --- ## 2. [Kedro](https://github.com/kedro-org/kedro) Kedro 是一個開源 Python 框架。 它為生產就緒的資料科學管道提供了一個工具箱。 事實上,Kedro 可以輕鬆地與成熟的 Python ML 庫集成,並提供統一的方法來實現端到端框架。 🔑特點: - 資料目錄 - 筆記本集成 - 專案模板 - 有自己的觀點,因為它強制執行特定的約定 ![Kedro 圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5zfgz36ogrixa1mfqre.png) --- ## 3. [Airflow](https://github.com/apache/airflow) 十多年來,Airflow 一直是管道領域的知名參與者。 Airbnb 建立了 Airflow 來解決資料處理和工作流程需求的內部挑戰。 眾所周知,這個強大的開源平台具有陡峭的學習曲線,但具有廣泛的功能。 該平台允許您透過建立 DAG(有向無環圖)來建立和管理工作流程。 🔑特點: - 基於DAG的定義 - 豐富的基於 Web 的監控 UI:DAG、故障、重試的可視化… - 各種集成 - 動態任務執行和調度 - 由於其以 Python 為中心的特性而具有靈活性。 - 強大的社區 ![氣流圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i7a5qgpcnawim32gudhd.png) --- ## 4. [Prefect](https://github.com/PrefectHQ/prefect) Prefect 是一個資料管道開發框架。 Prefect 在策略上將自己定位在與 Airflow 的直接競爭中,以基於簡單性、用戶友好性和靈活性的獨特身份脫穎而出。 如果您想要一個具有各種功能但比 Airflow 更容易學習的成熟產品,Prefect 是一個很好的中間產品。 🔑特點: - 控制面板 - 快取 - 基於流程的結構 - 動態參數化與依賴管理 - 混合執行(本地/雲端) ![完美插畫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejmhma39rkmpjg1lwu6z.png) --- ## 5. [Dagster](https://github.com/dagster-io/dagster) Dagster 是本次編譯中較新的函式庫之一,它是一個雲端原生資料管道編排,旨在統一資料整合、工作流程編排和監控。 與其他工具相比,Dagster 強調工作流程建立和管理的 DataOps 面向。 🔑特點: - 聲明式管道設置 - 固執己見的結構 - 版本控制 - 與 Hadoop 集成 - 全面的元資料跟踪 ![Dagster 圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/soebsxs4wfwxayzql8wr.png) --- ## 6. [Luigi](https://github.com/spotify/luigi) Luigi 提供了一個資料處理管道框架。 Spotify 與 Airflow 大約在同一時間開發了這個程式庫,以解決其複雜的資料工作流程和管道。 Luigi 專為管理批次作業的複雜通道而設計。如果您正在尋找簡單的東西並且必須快速上手,Luigi 是一個不錯的選擇。 🔑特點: - 內建 Hadoop 支援 - 基於任務的工作流程定義 - 用於依賴管理的中央調度程序 - 任務依賴關係視覺化 ![Luigi 插圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxx0ty0vsldbk8unyhqv.png) --- ##結論 隨著 Python 工作流程編排環境的不斷發展,這些工具展示了主要的共同特徵和特定的差異化因素。 所有這些工具都有不同程度的複雜性,了解您的專案和團隊的需求至關重要。 我建議使用非常簡單的範例來測試一些選項,以獲得對每個框架可用性的第一手了解。 --- 希望您喜歡這篇文章! 我是一名新手作家,歡迎任何改進建議! ![新秀圖片](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25oebh36ha622u74kpli.gif) 如果您有任何疑問,請隨時與我們聯繫。 --- 原文出處:https://dev.to/taipy/the-pipeline-repos-showdown-python-edition-39i5

為 2023 年準備好你自己的 DEV 🎁

隨著每個人和他們的貓為他們的應用程式建立一個“2023 Wrapped”,我無法阻止,不得不為這個很棒的 dev.to 社區建立一個小型開源應用程式 🥰 造訪[devto-wrapped.sliplane.app](https://devto-wrapped.sliplane.app/?username=code42cate),輸入您的用戶名,看看您作為dev.to 的作者在2023 年取得了什麼成就! **無需 API 金鑰或登入!** 這是我在 dev.to 的第一年的經驗: ![我的包裹](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c4zst6ibuahiq6wtk0e1.png) PS:在評論中分享你的截圖,我會隨機挑選一個人,給他們發送一些免費的開發者貼紙作為提前的聖誕禮物🎅🎁 不管怎樣,你來這裡是為了學習一些東西,所以讓我們深入研究程式碼吧! ## 教程 建立這個小應用程式的速度對我來說至關重要,因此我決定使用我最近使用的自己的[Hackathon Starter Template](https://dev.to/code42cate/how-to-win-any-hackathon -3i99)寫了關於。我剝離了一些我不需要的功能,從而產生了一個非常精簡的 monorepo: 1.Next.js + Tailwind 2. ShadcnUI 你可以在這個[Github儲存庫](https://github.com/Code42Cate/devto-wrapped)中看到所有內容 ### 設定 如果您想長期關注並親自嘗試一下,請按照以下步驟操作: ``` # Clone repository git clone https://github.com/Code42Cate/devto-wrapped.git # Install dependencies pnpm install # Start app pnpm run dev --filter web ``` 該應用程式現在應該從 http://localhost:3000 啟動。如果它不起作用,請在評論中告訴我! ### 存取 dev.to 資料 這個小應用程式最有趣的部分可能是我們如何存取 dev.to 資料。雖然有幾種方法可以解決這個問題,但我有一些要求幫助我決定前進的方向: 1. 不抓取 - 花費太長時間,我希望資料可用 <1 秒 2. 僅公開資料 - 我不想向使用者詢問 API 金鑰或使用我自己的 3.不需要資料庫-我很懶,想避免無用的複雜性 這為我們提供了兩種可能的獲取資料的方式: 1. [記錄和未經驗證的 API 呼叫](https://developers.forem.com/api/v1) 2. 即使您未登錄,dev.to 網站也會進行未記錄的公開 API 呼叫 考慮到這兩種獲取資料的方式,我們基本上可以獲得 3 類資料: 1.使用API公開使用者資訊:`dev.to/api/users/by_username` 2. 使用 `dev.to/search/feed_content` API 和 `class_name=Article` 發布帖子 3. 包含 `dev.to/search/feed_content` 和 `class_name=Comment&search_fields=xyz` 的搜尋查詢的評論 這些 API 呼叫都是在伺服器端進行的,以加快請求速度,可以在「/apps/web/actions/api.ts」中找到。由於這只是組合在一起,因此功能相當簡單,錯誤處理也非常少: ``` export async function getUserdata(username: string): Promise<User | undefined> { const res = await fetch( `https://dev.to/api/users/by_username?url=${username}`, ); if (!res.ok) { return undefined; } const data = await res.json(); return data as User; } ``` 對於這個用例來說,這很好,但如果您不希望用戶發生意外崩潰,請記住正確捕獲異常並驗證您的類型😵 ### 計算統計資料 計算統計資料出奇地容易,主要是因為我們的資料非常小。即使你每天發帖,我們只會瀏覽 365 個帖子。迭代 365 個專案的陣列幾乎不需要時間,這給了我們很大的空間來完成工作,而無需關心效能!您在頁面上看到的每個統計資料都是在單一函數中計算的。以「總反應」為例: ``` const reactionsCount = posts?.reduce( (acc: number, post: Article) => acc + post.public_reactions_count, 0, ); ``` 我們需要做的就是檢查帖子陣列並總結每個帖子的“public_reactions_count”數量。田田,完成! 即使對於更複雜的,它也只不過是一個嵌套循環: ``` const postsPerTag: Record<string, number> = posts?.reduce( (acc: Record<string, number>, post: Article) => { post.tag_list.forEach((tag) => { acc[tag] = acc[tag] ? acc[tag] + 1 : 1; }); return acc; }, {} as Record<string, number>, ); ``` ### 前端 由於這是使用 Next.js 建構的,因此所有內容都可以在「/apps/web/app/page.tsx」檔案中找到。 在元件的頂部,您可以先看到我們如何取得資料並檢查使用者是否存在或是否有足夠的資料來顯示任何內容: ``` const user = await getUserdata(username); if (!user) { return <EmptyUser message="This user could not be found 🫠" />; } const stats = await getStats(user.id.toString()); const mentionsCount = await getMentionedCommentCount(user.username); if (stats.postCount === 0) { return <EmptyUser message="This user has no posts 🫠" />; } ``` 不同的統計資料都是它們自己的元件,它們是 CSS 網格的一部分,看起來像這樣(縮短) ``` <div className="grid grid-cols-2 gap-2 w-full text-sm text-gray-800"> <PublishedPostsCard count={stats.postCount} /> <ReactionsCard count={stats.reactionsCount} /> <BusiestMonthCard busiestMonth={stats.busiestMonth} postsPerMonth={stats.postsPerMonth} /> <CommentsCard count={stats.commentsCount} /> <ReadingTimeCard readingTime={stats.readingTime} totalEstimatedReadingTime={stats.totalEstimatedReadingTime} /> </div> ``` 這些元件都是「啞」的,這意味著它們只負責顯示資料。他們不獲取或計算任何東西。其中大多數都非常簡單,就像這張「最佳貼文」卡: ``` import Image from "next/image"; import { Article } from "@/actions/api"; export default function BestPostCard({ post, coverImage, }: { post: Article; coverImage: string; }) { return ( <div className="flex w-full flex-col justify-between gap-2 rounded-xl border border-gray-300 bg-white p-4 shadow-md"> Your fans really loved this post: <br /> <Image src={coverImage} alt={post.title} width={500} height={500} className="rounded-md border border-gray-300" /> <a className="font-semibold underline-offset-2" href={`https://dev.to${post.path}`} > {post.title} </a> </div> ); } ``` ### 部署 為了部署我們的應用程式,我們將對其進行dockerize,然後使用Sliplane(稍微有偏見,我是聯合創始人!)將其託管在我們自己的[Hetzner Cloud](https://www.hetzner.com /cloud) 伺服器上。我在[上一篇部落格文章](https://dev.to/sliplane/understanding-nextjs-docker-images-2g08)中介紹瞭如何對Next.js 應用程式進行docker 化,這基本上是相同的,只是做了一些小的更改適應我的 Turborepo 設定:) ``` # src Dockerfile: https://github.com/vercel/turbo/blob/main/examples/with-docker/apps/web/Dockerfile FROM node:18-alpine AS alpine # setup pnpm on the alpine base FROM alpine as base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable RUN pnpm install turbo --global FROM base AS builder # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat RUN apk update # Set working directory WORKDIR /app COPY . . RUN turbo prune --scope=web --docker # Add lockfile and package.json's of isolated subworkspace FROM base AS installer RUN apk add --no-cache libc6-compat RUN apk update WORKDIR /app # First install the dependencies (as they change less often) COPY .gitignore .gitignore COPY --from=builder /app/out/json/ . COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml COPY --from=builder /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml RUN pnpm install # Build the project COPY --from=builder /app/out/full/ . COPY turbo.json turbo.json RUN turbo run build --filter=web # use alpine as the thinest image FROM alpine AS runner WORKDIR /app # Don't run production as root RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs USER nextjs COPY --from=installer /app/apps/web/next.config.js . COPY --from=installer /app/apps/web/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public CMD node apps/web/server.js ``` 在 Docker 化並推送到 Github 儲存庫後,我們需要做的就是在 Sliplane 中建立一個新服務並選擇我們想要託管的伺服器。我已經有一台伺服器,在上面執行一些小型專案,所以我只使用該伺服器: ![Sliplane 建立服務](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2r1wfded0cy9vhw103dx.png) 點擊「部署」後,需要幾分鐘時間來建置並啟動我們的 Docker 映像。可以在日誌檢視器中監視進度: ![Sliplane 日誌檢視器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpmxb1jlp540qvblxmoa.png) 第一次成功部署後,我們將獲得一個可以存取我們的應用程式的免費子網域,或者我們可以加入自己的自訂網域: ![網域](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tc7h2eu1ctw8o5xeq9xp.png) 就是這樣!我們的應用程式在線,世界上每個人都可以存取,並且不會產生令人驚訝的無伺服器帳單 🤑 感謝您到目前為止的閱讀,不要忘記用您的截圖進行評論,以_可能_贏得一些貼紙😊 乾杯,喬納斯 --- 原文出處:https://dev.to/code42cate/devto-wrapped-2023-13o

✨23 個開源函式庫,用於將您的作品集發射到月球🚀🚀

為優秀的開源庫做出貢獻是建立作品集的好方法。 我已經編譯了 23 個優秀的開源程式庫和一些很好的入門問題。 不要忘記加星號並支持這些🌟 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi3dd111pv2948ya21w2.gif) --- #產品中的人工智慧: ### 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 應用內 AI 聊天機器人與 AI 文字區域 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ox3mv8nmqzot6m4kvkdh.png) 開源平台,用於使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。 CopilotPortal:應用程式內人工智慧聊天機器人,可以「查看」當前應用程式狀態並採取行動。 CopilotTextarea:AI 驅動的 <textarea /'> 替換。具有自動完成、插入和生成功能。 ###[好第一期:](https://github.com/CopilotKit/CopilotKit/issues/62) ``` Support bold and italicized text in CopilotTextarea Proposal: Add support for bold and italicized text in CopilotTextarea CopilotTextarea uses slate-js under the hood. Lots of examples for adding bold/italicized support Initially only add programatic support. UI support will be added separately in [TODO add issue] Implementation tips: changes will be made to render-element.tsx and base-copilot-textarea.tsx custom-editor.tsx structures may also require changes ``` {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- ###2.[Tavily GPT 研究員](https://github.com/assafelovic/gpt-researcher){% embed https://github.com/assafelovic/gpt-researcher no-readme %} ###3.[Pezzo.ai](https://github.com/pezzolabs/pezzo){% 嵌入 https://github.com/pezzolabs/pezzo no-readme %} ###4.[Weaviate](https://github.com/weaviate/weaviate){% 嵌入 https://github.com/weaviate/weaviate no-readme %} ###5.[LangChain](https://github.com/langchain-ai/langchain){% 嵌入 https://github.com/langchain-ai/langchain no-readme %} --- &nbsp; #🛜網頁開發: ### 6. [Wasp](https://github.com/wasp-lang/wasp) - 使用 React 和 Node.js 開發全端 Web 應用程式 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54jp6j6r8ils6we97i0f.png) 使用 React 和 Node.js 進行快速全端 Web 應用程式開發。 Wasp 提供了一種建立現代 Web 應用程式的簡化方法,將前端的 React 和後端的 Node.js 結合在一個緊密結合的框架中。 ###[好第一期:](https://github.com/wasp-lang/wasp/issues/874) ``` Add images (or link to the example app) of auth UI helpers Wasp provides At this point in docs (also in the tutorial if we're using it), it would be nice to add an image of UI helpers for Auth (login/signup form, Google/GitHub button, ...) so developers can immediately see what they are getting and how nice it looks. ``` {% cta https://github.com/wasp-lang/wasp %} 星黃蜂 ⭐️ {% endcta %} --- ###7.[ClickVote](https://github.com/clickvote/clickvote) {% 嵌入 https://github.com/clickvote/clickvote no-readme %} ###8.[ReactFlow](https://github.com/xyflow/xyflow) {% 嵌入 https://github.com/xyflow/xyflow no-readme %} ###9.[Trigger.dev](https://github.com/triggerdotdev/trigger.dev) {% 嵌入 https://github.com/triggerdotdev/trigger.dev no-readme %} ###10.[Novu](https://github.com/novuhq/novu) {% 嵌入 https://github.com/novuhq/novu no-readme %} --- &nbsp; #🧑‍💻DevOps: ### 11. [Logstash](https://github.com/elastic/logstash) - 由 elastic 傳輸和處理日誌和事件。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0nhya28nmwby9lurtrta.png) 輕鬆將點讚、按讚和評論加入到您的網路應用程式中。 用於加入這些元件的簡單反應程式碼。 ### [第一期好](https://github.com/elastic/logstash/issues/15561) ``` Allow comments in pipeline config between hash entries Currently it seems not allowed to make comments between hash entries, this is a feature request to allow it. ``` {% cta https://github.com/elastic/logstash %} 明星 Logstash ⭐️ {% endcta %} --- ###12.[Odigos](https://github.com/keyval-dev/odigos) {% 嵌入 https://github.com/keyval-dev/odigos no-readme %} ###13.[Glasskube](https://github.com/glasskube/operator) {% 嵌入 https://github.com/glasskube/operator no-readme %} ###14.[鏡像](https://github.com/metalbear-co/mirrord){% 嵌入 https://github.com/metalbear-co/mirrord no-readme %} ###15.[挖土機](https://github.com/diggerhq/digger) {% 嵌入 https://github.com/diggerhq/digger no-readme %} --- &nbsp; #💽資料庫: ### 16. [Supabase](https://github.com/supabase/supabase) - 開源 Rirebase 替代品 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4xnzrefrjaaywu4b49p.png) 使用託管 Postgres、身份驗證和即時功能建立現代資料驅動應用程式 ###[第一期好:](https://github.com/supabase/supabase/issues/19396) ``` Horizontal Scroll for CodeBlocks Currently when reading the dcs, it's not possible to view all of the code for alot of the samples. Is this the Component rendered across all of the web properties, if so I'll be happy to throw on a horizontal scroll bar that matches supabase branding. ``` {% cta https://github.com/supabase/supabase %} 明星 Supabase ⭐️ {% endcta %} --- ###17.[Appwrite](https://github.com/appwrite/appwrite){% 嵌入 https://github.com/appwrite/appwrite no-readme %} ###18.[Superduperdb] (https://github.com/SuperDuperDB/superduperdb){% 嵌入 https://github.com/SuperDuperDB/superduperdb no-readme %} ###19.[Milvus](https://github.com/milvus-io/milvus) {% 嵌入 https://github.com/milvus-io/milvus no-readme %} --- &nbsp; #👾其他: ### 21. [Snapify](https://github.com/MarconLP/snapify) - 開源螢幕錄製 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89h8mjriix6hdihcrfr8.png) 螢幕錄製,但免費、開源,您負責自己的資料。 ###[好第一期:](https://github.com/MarconLP/snapify/issues/18) ``` Ability to create GIFs and take screenshots to also store in S3 ``` {% cta https://github.com/MarconLP/snapify %} 明星 Snapify ⭐️ {% endcta %} --- ###22.[ReactAgent](https://github.com/eylonmiz/react-agent){% 嵌入 https://github.com/eylonmiz/react-agent no-readme %} ###23.[對初學者來說很棒](https://github.com/MunGell/awesome-for-beginners){% embed https://github.com/MunGell/awesome-for-beginners no -readme %} --- #就是這樣,夥計們! ## 別忘了按讚、留言和收藏🫡 --- 原文出處:https://dev.to/copilotkit/23-open-source-libraries-to-launch-your-portfolio-to-the-moon-fe

keepHQ 如何獲得前 2,000 顆星!

我很高興與 [Keep](https://github.com/keephq/keep) 的執行長兼聯合創始人 Tal 交談。 它最初是一個 CLI 工具,隨著時間的推移,變成了一個警報聚合工具。 如今,他們擁有近 3,000 顆星。 **您可以在這裡觀看完整影片:** {% 嵌入 https://www.youtube.com/watch?v=eykb1zbDwQo %} <小時/> 開始 ------------ 他們製作了一個非常基本的警報 CLI 工具並將其發佈在 Hackernews 上 - **“顯示 HN:”** 他們乘坐飛機,當他們著陸時,**他們看到了 600 顆星星**。 [Hackernews](https://hackernews.com?utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars)是一個有趣的網站。它非常醜陋,牽引力很大,而且很難進入。 我在 Hackernews 上看到並推出了很多產品。雖然在 Hackernews 上發表一篇文章很困難,但 **「Show HN」** 的文章要容易得多。 這通常是一個秘密武器,因為你也許可以每年做一次 - 最好與更多管道合作,以獲得更好的機會在 GitHub 上流行。 他們創造了更多工具,例如[gnip](https://www.gnip.io/?utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars),以及雖然這個工具今天沒有帶來大量流量,但在發布之日就帶來了許多流量。 您會發現,對於您建立的每個副專案,您都可以在 Hackernews (Show HN) 和 Product Hunt 中啟動它。 我的建議? **目標是每月發布一次新專案。** **HACK:** 最近,一些隨機的人在 Hackernews 上發布了 **Novu** 組織頁面。我不知道它是如何被接受的,但它為我們帶來了 400 顆星。 [查看此處的貼文](https://news.ycombinator.com/item?id=38419513&utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars) <小時/> ![](https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/7eda5059-74b8-4b7a- 9468-d77cce002b2c/image.png?t=1701432843) 與社區一起尋找產品 ------------------------------------------- 雖然 Hackernews 對開源社群(許多早期採用者)非常友好,但 Product Hunt 感覺更像是獨立駭客/非開發人員的工具。 **目標是獲得盡可能多的讚成票並達到列表的頂部。** 您通常應該將 Product Hunt 與其他管道結合。 如果您剛開始並想要星星 - 將您的 GitHub 作為您的“存取”URL。 如果您是一家更知名的公司並且希望獲得更多潛在客戶並預訂會議,請加入您的**網站 URL。** **對於第一次啟動,我通常會瞄準 GitHub 存儲庫。** Keep 的社群很小,但仍然佔據了第一天的份額。 最好的創始人不會讓運氣引導他們。 **這是他們所做的:** * 他們在社交媒體上發布了有關其發布的訊息(像大多數人一樣) * 他們嘗試聯繫盡可能多的人來幫助他們。 * 他們建造了一個秘密武器工具! [Slackline](https://github.com/talboren/slackline?utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars) <小時/> ![](https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1ff5c353-9447-45cb- 946c-2a4a3f3e29a6/image.png?t=1701433047) 鬆弛的線路 ------------------- 塔爾不久前向我展示了這個工具,從那時起我就一直使用它。 當您是一家小型新創公司時,您必須完成不可能的任務,從 0 到 1。**使用您擁有的所有可能的選項。** Slackline 正是為此而設計的。您可以加入任何 Slack 群組,並向頻道中的每個可能的人發送 DM。 有幾個選項: 1. 您可以在 Slack 頻道上使用它來推動對您的產品的回饋或從社群成員那裡獲得幫助來完成不同的事情。 2. 在 Product Hunt 上向人們尋求協助。 雖然數字 2 聽起來有點冒犯,但它確實有效 - 他們獲得了當天的第一名以及名譽和榮耀🎩 <小時/> ![](https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2ac46256-fc15-4bfe- a2bb-724e305e20b9/image.png?t=1701433220) 使用賞金 -------------- 您可以對 [Algora](https://algora.io?utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars) 的問題進行懸賞,告訴您這會擴大你的社區——可能不會,但會給你帶來更多的可信度。 如果您剛開始,Algora 可以幫助您獲得更多貢獻者(在貢獻者清單中)並使您看起來更可信。 然而,請記住,為了錢而來的貢獻者通常不會免費做同樣的事情(並非總是如此)。 {% cta https://github.com/keephq/keep %}在 GitHub 上加星 Keep{% endcta %} --- ## 我邀請您註冊我的電子報。 若符合以下條件,本通訊對您有好處: - 您正在考慮開源您的產品(或建立新產品)。 - 您正在考慮開啟一個副產品並將其開源(以反映您的主要產品)。 - 您從事科技業,希望在沒有明星/沒有 GitHub 趨勢的情況下實現成長。 這是一份 100% 免費的時事通訊(並且永遠如此)。請隨時註冊: [https://gitroom.com](https://gitroom.com/) ![技術](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86ywkzncq6erg44d6xy9.gif) --- 原文出處:https://dev.to/github20k/how-keephq-got-their-first-2000-stars-l7i