🔍 搜尋結果:signal

🔍 搜尋結果:signal

Ruby on Rails 之父:我怎麼學會寫程式的?

DHH,本名David Heinemeier Hansson,Basecamp合夥人、Google 2005年度駭客、業餘賽車手,他最知名的身份是網路開發框架Ruby on Rails的發明者。 DHH曾在部落格發表他學寫程式的心得: https://signalvnoise.com/posts/2582-how-do-i-learn-to-program 看過之後覺得獲益良多,特此翻譯出來和大家分享。 --- ## 我怎麼學會寫程式的? 我花了二十幾年才真的學會怎麼寫程式。不是因為我嘗試的不夠,而是因為我一直用錯誤的方法。我以前都看著那些教材、範本照做,寫出一些我根本沒興趣繼續往下寫的東西。這是為了學習而學習。 我知道這方法對有些人管用,這些人就是很樂意學習新東西。我真羨慕你們。但這對我不管用,而且我知道很多人也沒辦法用這種方式學習。 為了解決鳥事而寫程式———對我來說這才管用。寫程式是因為非這麼做不可。寫程式是因為我他媽的在乎自己所寫的東西,而且我希望它越快完成越好。 這就是我學寫Ruby的方法。我把目標定為,用Ruby把Basecamp寫出來。當你因為一個目標而去學習,每件事的先後順序就變得非常清楚。我到底該做什麼才能讓這幾個訊息順利在Ruby執行?哦,我需要在這裡加一個迴圈。哦,我需要在哪裡抓些資料庫的資料出來。 決定目標的當下,你的點子就完成一半了、而且你還知道了該怎麼做。 簡單來說,就是從一些確實的東西開始做起,接著讓時鐘發出「滴」聲,然後讓它發出「答」聲。

現代前端框架的背後觀念:新手必讀基本功

框架背後有什麼必學觀念的?這篇文章簡單整理如下 原文出處:https://dev.to/lexlohr/concepts-behind-modern-frameworks-4m1g --- 很多初學者會問“我應該學哪個框架?”和“學一個框架之前需要學多少JS或TS?” - 無數自以為是的文章都在宣傳作者首選框架或庫的優勢,而不是向讀者展示其背後的概念、教他們如何做出明智的決定。讓我們先解決第二個問題: ## “在學習框架之前要學多少 JS/TS?” 盡可能多地理解它們的基本概念。您將需要了解基本資料類型、函數、基本運算符和文檔對像模型 (DOM),這是 HTML 和 CSS 在 JS 中的基礎。雖然先學一點當然沒關係,但沒必要先精通框架或庫。 如果您是一個完全的初學者,[JS for cats](http://jsforcats.com/) 可能是您第一步的好資源。繼續前進,直到您感到自信為止,然後繼續前進,直到您開始感到自信不足。那就是你了解足夠的 JS/TS 並可以開始學框架的時間。其餘的你可以邊走邊學。 ## “你指的是什麼概念?” - 狀態 - 效果 - 記憶化 - 模板和渲染 所有現代框架都從這些概念中衍伸出它們的功能。 ### 狀態 狀態只是讓您的應用程式跑起來的資料。它可能在全局級別上,適用於應用程式的較大部分,或適用於單個元件。讓我們以一個簡單的計數器為例。它保留的計數是狀態。我們可以讀取狀態並寫入狀態以增加計數。 最簡單的表示通常是一個變數,其中包含我們的狀態所包含的資料: ``` let count = 0; const increment = () => { count++; }; const button = document.createElement('button'); button.textContent = count; button.addEventListener('click', increment); document.body.appendChild(button); ``` 但是這段程式碼有一個問題:對 count 的更改,就像對 increment 所做的更改一樣,不會更新按鈕的文本內容。我們可以手動更新所有內容,但這對於更複雜的用例來說並不能很好地擴展。 `count` 更新其用戶的能力稱為*反應性*。這是通過訂閱並重新執行應用程式的訂閱部分來更新的。 幾乎每個現代前端框架和庫都有一種響應式管理狀態的方法。解決方案分為三部分,至少採用其中之一或混合使用: - Observables / Signals - Reconciliation of immutable updates - Transpilation #### Observables / Signals Observables 基本上是允許藉由訂閱閱讀器的函數來進行讀取的結構。然後訂閱者在更新時重新執行: ``` const state = (initialValue) => ({ _value: initialValue, get: function() { /* subscribe */; return this._value; }, set: function(value) { this._value = value; /* re-run subscribers */; } }); ``` 這個概念的第一個用途之一是在 [knockout](https://knockoutjs.com/) 中,它使用相同的函數,帶和不帶參數進行寫/讀存取。 這種模式目前正在以「信號」的形式復興,例如在 [Solid.js](https://www.solidjs.com/docs/latest/api#createsignal) 和 [preact signals](https://preactjs.com /guide/v10/signals/),但在 [Vue](https://vuejs.org/) 和 [Svelte](https://svelte.dev/) 的底層使用了相同的模式。 [RxJS](https://rxjs.dev/) 為 [Angular](https://angular.io/) 的反應層提供動力,是這一原則的延伸,超越了簡單狀態,但有人可能會爭辯說它模擬複雜性的能力可能反而綁手綁腳。 [Solid.js](https://www.solidjs.com/) 還以儲存(可以通過 setter 操作的物件)和可變(可以像平常一樣使用的物件)的形式進一步抽象這些信號 JS 物件或 [Vue](https://vuejs.org/) 中的狀態來處理巢狀狀態物件。 #### Reconciliation of immutable states 不變性意味著如果一個物件的屬性發生變化,整個物件引用必須改變,所以簡單的引用比較可以很容易地檢測到是否有變化,這就是協調器所做的。 ``` const state1 = { todos: [{ text: 'understand immutability', complete: false }], currentText: '' }; // updating the current text: const state2 = { todos: state1.todos, currentText: 'understand reconciliation' }; // adding a to-do: const state3 = { todos: [ state.todos[0], { text: 'understand reconciliation', complete: true } ], currentText: '' }; // this breaks immutability: state3.currentText = 'I am not immutable!'; ``` 如您所見,未更改專案的引用被重新使用。如果協調器檢測到不同的物件引用,它會再次使用狀態(props, memos, effects, context)來重跑所有元件。由於讀取存取是被動的,這需要手動指定對反應值的依賴性。 顯然,您不是以這種方式定義狀態。您可以從現有屬性建置它,也可以使用所謂的 reducer。reducer 是一個函數,它接受一個狀態並返回另一個狀態。 [react](https://reactjs.org/) 和 [preact](https://preactjs.com/) 使用此模式。它適合與 vDOM 一起使用,我們將在稍後描述模板時探討它。 並非每個框架都使用其 vDOM 來使狀態完全響應。 例如 [Mithril.JS](https://mithril.js.org/components.html#state),元件會在設置的事件後變化後更新狀態;否則你必須手動觸發 `m.redraw()`。 #### Transpilation Transpilation 是一個建置步驟,它重寫我們的程式碼以使其在舊瀏覽器上執行或賦予它額外的能力;在這種情況下,該技術用於將簡單變數更改為反應系統的一部分。 [Svelte](https://svelte.dev/) 基於一個轉譯器,該轉譯器還通過看似簡單的變數宣告和存取為其反應式系統提供動力。 順便說一句,[Solid.js](https://solidjs.com) 使用轉譯,但不是針對它的狀態,只是針對模板。 ### 效果 在大多數情況下,我們需要對反應狀態做更多的事情,而不是從中衍伸並渲染到 DOM 中。我們必須管理副作用,這些都是由於視圖更新之外的狀態更改而發生的所有事情(儘管 [Solid.js](https://solidjs.com) 等一些框架也將視圖更改視為效果)。 還記得第一個例子中,訂閱處理被故意遺漏的狀態嗎?讓我們完成這個處理效果,來作為對更新的反應: ``` const context = []; const state = (initialValue) => ({ _subscribers: new Set(), _value: initialValue, get: function() { const current = context.at(-1); if (current) { this._subscribers.add(current); } return this._value; }, set: function(value) { if (this._value === value) { return; } this._value = value; this._subscribers.forEach(sub => sub()); } }); const effect = (fn) => { const execute = () => { context.push(execute); try { fn(); } finally { context.pop(); } }; execute(); }; ``` 這基本上是 [preact signals](https://preactjs.com/guide/v10/signals/) 或 [Solid.js](https://solidjs.com) 中反應狀態的簡化,沒有錯誤處理和狀態突變模式(使用接收前一個值並返回下一個值的函數),但這很容易加入。 它允許我們使前面的範例具有反應性: ``` const count = state(0); const increment = () => count.set(count.get() + 1); const button = document.createElement('button'); effect(() => { button.textContent = count.get(); }); button.addEventListener('click', increment); document.body.appendChild(button); ``` > ☝ 使用您的開發人員工具在 [空白頁面](about:blank) 中嘗試上述兩個程式碼塊。 在大多數情況下,框架允許不同的時間安排,讓效果在渲染 DOM 之前、期間或之後執行。 ### 記憶化 Memoization 意味著緩存從狀態計算的值,它會從狀態衍伸的變化更新時更新。它基本上是一種回傳衍伸狀態的效果。 在重新執行元件功能的框架中,例如 [react](https://reactjs.org/) 和 [preact](https://preactjs.com/),這讓某些複雜計算不需要每次都重複計算。 對於其他框架,情況恰恰相反:它允許您選擇部分組件進行響應式更新,同時緩存之前的計算。 對於我們簡單的反應式系統,memo 看起來像這樣: ``` const memo = (fn) => { let memoized; effect(() => { if (memoized) { memoized.set(fn()); } else { memoized = state(fn()); } }); return memoized.get; }; ``` ### 模板化和渲染 現在我們有了純的、衍伸的和緩存形式的狀態,我們想把它展示給用戶。在我們的範例中,我們直接使用 DOM 來加入按鈕並更新其文本內容。 為了對開發人員更加友好,幾乎所有現代框架都支持一些特定領域的語言來編寫類似於程式碼中所需輸出的內容。儘管有不同的風格,比如 `.jsx`、`.vue` 或 `.svelte` 文件,但它們都歸結為用類似於 HTML 的程式碼表示 DOM,因此基本上 ``` <div>Hello, World</div> // in your JS // becomes in your HTML: <div>Hello, World</div> ``` 你可能會問“我要把狀態放在哪裡?”。很好的問題。在大多數情況下,`{}` 用於表示屬性和節點周圍的動態內容。 最常用的 JS 模板語言擴展無疑是 JSX。對於 [react](https://reactjs.org),它被編譯為純 JavaScript,其方式允許它建立 DOM 的虛擬表示,一種稱為虛擬文檔對像模型或簡稱 vDOM 的內部視圖狀態。 這樣設計的原因是:建立物件比存取 DOM 快得多,所以如果你能用當前的替換後者,你可以節省時間。但是,如果您在任何情況下都有大量 DOM 更改或建立無數物件而沒有更改,則此解決方案的優點就變成必須通過「記憶化」來規避的缺點。 ``` // original code <div>Hello, {name}</div> // transpiled to js createElement("div", null, "Hello, ", name); // executed js { "$$typeof": Symbol(react.element), "type": "div", "key": null, "ref": null, "props": { "children": "Hello, World" }, "_owner": null } // rendered vdom /* HTMLDivElement */<div>Hello, World</div> ``` 不過,JSX 不僅限於 react。例如,Solid 使用其轉譯器更徹底地更改程式碼: ``` // 1. original code <div>Hello, {name()}</div> // 2. transpiled to js const _tmpl$ = /*#__PURE__*/_$template(`<div>Hello, </div>`, 2); (() => { const _el$ = _tmpl$.cloneNode(true), _el$2 = _el$.firstChild; _$insert(_el$, name, null); return _el$; })(); // 3. executed js code /* HTMLDivElement */<div>Hello, World</div> ``` 雖然轉譯後的程式碼乍看可能令人望而生畏,但解釋這裡發生的事情卻相當簡單。首先,建立包含所有靜態部分的模板,然後複製它以建立其內容的新實體,並加入動態部分並連接以根據狀態更改進行更新。 Svelte 走得更遠,不僅可以轉換模板,還可以轉換狀態。 ``` // 1. original code <script> let name = 'World'; setTimeout(() => { name = 'you'; }, 1000); </script> <div>Hello, {name}</div> // 2. transpiled to js /* generated by Svelte v3.55.0 */ import { SvelteComponent, append, detach, element, init, insert, noop, safe_not_equal, set_data, text } from "svelte/internal"; function create_fragment(ctx) { let div; let t0; let t1; return { c() { div = element("div"); t0 = text("Hello, "); t1 = text(/*name*/ ctx[0]); }, m(target, anchor) { insert(target, div, anchor); append(div, t0); append(div, t1); }, p(ctx, [dirty]) { if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div); } }; } function instance($$self, $$props, $$invalidate) { let name = 'World'; setTimeout( () => { $$invalidate(0, name = 'you'); }, 1000 ); return [name]; } class Component extends SvelteComponent { constructor(options) { super(); init(this, options, instance, create_fragment, safe_not_equal, {}); } } export default Component; // 3. executed JS code /* HTMLDivElement */<div>Hello, World</div> ``` 也有例外。例如,在 [Mithril.js](https://mithril.js.org/) 中,雖然可以使用 JSX,但我們鼓勵您編寫 JS: ``` // 1. original JS code const Hello = { name: 'World', oninit: () => setTimeout(() => { Hello.name = 'you'; m.redraw(); }, 1000), view: () => m('div', 'Hello, ' + Hello.name + '!') }; // 2. executed JS code /* HTMLDivElement */<div>Hello, World</div> ``` 雖然大多數人會發現開發人員缺乏經驗,但其他人更喜歡完全控制他們的程式碼。根據他們主要想解決的問題,缺少轉譯步驟甚至可能是有益的。 儘管很少有人這樣推薦,許多其他框架都允許在不進行轉譯的情況下使用。 ## “我現在應該學習什麼框架或庫?” 我有一些好訊息和一些壞訊息要告訴你。 壞訊息是:沒有萬靈丹。沒有哪個框架在每個方面都比其他框架好得多。他們每個人都有自己的優勢和妥協。 [React](https://reactjs.org/) 有它的鉤子規則,[Angular](https://angular.io/) 缺乏簡單的信號,[Vue](https://vuejs.org/)缺乏向後兼容性,[Svelte](https://svelte.dev/) 不能很好地擴展,[Solid.js](https://www.solidjs.com/) 禁止解構,[Mithril.js]( https://mithril.js.org/) 並不是真正的反應式,僅舉幾例。 好訊息是:沒有錯誤的選擇——至少,除非專案的要求真的很有限,無論是在 bundle 大小還是性能方面。每個框架都會完成它的工作。有些人可能需要配合團隊的設計決策,這可能會使您的速度變慢,但無論如何您都應該能夠獲得可行的結果。 話雖這麼說,沒有框架也可能是一個可行的選擇。許多專案都被過度使用 JavaScript 破壞了。其實帶有一些互動性的靜態頁面就可以完成這項工作。 現在您已經了解了這些框架和庫應用的概念,請選擇最適合您當前任務的概念。不要害怕在下一個專案中切換框架。沒有必要學習所有這些。 如果你嘗試一個新的框架,我發現最有幫助的事情之一就是跟它的社群有所連結,無論是在社群媒體、discord、github 還是其他地方。他們可以告訴您哪些方法適合他們的框架,這將幫助您更快地獲得更好的解決方案。 ## “拜託,你*總是*有個人喜好吧!” 如果你的主要目標是就業,我建議學習 [react](https://reactjs.org/)。如果您想要輕鬆的性能和控制體驗,請嘗試 [Solid.js](https://solidjs.com);你可能會在 Solid 的 [Discord](https://discord.com/invite/solidjs) 上見到我。 但請記住,所有其他選擇都同樣有效。你不應該因為我這麼說就選擇一個框架,而應該使用最適合你的框架。

如何在 React 中漂亮地 render 一個列表:四點注意事項

面試的時候,常常會被問到這題。看似簡單,其實有一些進階注意事項,此篇與您分享。 - 原文出處:https://dev.to/andyrewlee/stand-out-in-a-react-interview-by-rendering-a-list-like-a-pro-1cn5 # 基本做法 以下是最基本做法。根據陣列內容,直接渲染成一個列表。 ``` import { useState, useEffect } from "react"; const App = () => { const [posts, setPosts] = useState([]); const [currentPost, setCurrentPost] = useState(undefined); useEffect(() => { const initialize = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/posts"); const json = await res.json(); setPosts(json); }; initialize(); }, []); const onPostClick = (post) => { setCurrentPost(post); }; return ( <div> {currentPost && <h1>{currentPost.title}</h1>} <PostList posts={posts} onPostClick={onPostClick} /> </div> ); }; const PostList = ({ posts, onPostClick }) => { return ( <div> {posts.map((post) => ( <Post post={post} onPostClick={onPostClick} /> ))} </div> ); }; const Post = ({ post, onPostClick }) => { const onClick = () => { onPostClick(post); }; return <div onClick={onClick}>{post.title}</div>; }; export default App; ``` # 改進方法 以下是四個可以改進的方向,以及背後的原因。 ## 1. 指定 key 替每個元件提供一個 key 屬性,可以幫助後續 React 渲染時改善效能。要注意 key 屬性要是唯一值,不要直接用陣列索引當成 key。 ``` {posts.map((post) => ( <Post key={post.id} post={post} onPostClick={onPostClick} /> ))} ``` ## 2. 優化渲染 每次點擊列表項目時,都會重新渲染 `PostList` 和每個 `Post`。 ``` const Post = ({ post, onPostClick }) => { console.log("post rendered"); const onClick = () => { onPostClick(post); }; return <div onClick={onClick}>{post}</div>; }; ``` 可以使用 React 提供的 `memo` 功能來優化 `PostList` 元件。用 `memo` 包裝元件時,等於告訴 React:除非 props 改變,否則不要重新渲染這個元件。 ``` import { useState, useEffect, memo } from "react"; const PostList = memo(({ posts, onPostClick }) => { return ( <div> {posts.map((post) => ( <Post post={post} onPostClick={onPostClick} /> ))} </div> ); }); ``` 不過,實際跑下去,會發現還是重新渲染了。因為每次 currentPost 改變時,App 都會重新渲染。每次重新渲染都會重新建立 onPostClick 函數。當一個函數被重新建立時(即使函式內容一樣),都算是新的實體。所以技術上來說,props 確實發生了變化,所以 `PostList` 會重新渲染。 ``` const fn1 = () => {}; const fn2 = () => {}; fn1 === fn2; // => false ``` 這種狀況,就可以使用 useCallback hook 來告訴 React 不要重新建立函數。 ``` const onPostClick = useCallback((post) => { setCurrentPost(post); }, []); ``` 上面的例子中,使用 useCallback 沒問題,可以避免全部貼文被重新渲染。但要知道的是,並非什麼函式都適合包在 useCallback 裡面。 ``` const Post = ({ post, onPostClick }) => { const useCalllback(onClick = () => { onPostClick(post); }, []); return <div onClick={onClick}>{post.title}</div>; }; ``` 比方說,在 Post 元件使用 useCallback 就不太有意義。因為這個元件是輕量級的。應該只在有意義的情況下使用 useCallback(可以用 profiling 工具分析效能確認)。`useCallback` 有缺點:它增加了程式碼的複雜性。使用 useCallback 會在每次渲染時運行的額外程式碼。 ## 3. 元件卸載時清理乾淨 目前的元件沒有在卸載時進行清理。例如,如果在收到 URL 回應結果之前就離開頁面怎麼辦?這時應該要取消請求。 ``` useEffect(() => { const initialize = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/posts"); const json = await res.json(); setPosts(json); }; initialize(); }, []); ``` `useEffect` 可以分為兩部分:掛載時運行的程式碼、卸載時運行的程式碼: ``` useEffect(() => { // When component mounts what code should I run? return () => { // When component unmounts what code should I run (clean up)? }; }, []); ``` 我們可以使用 AbortController ,並在清理時呼叫 controller.abort() 來取消請求。 ``` useEffect(() => { const controller = new AbortController(); const signal = controller.signal; const initialize = async () => { try { const res = await fetch("https://jsonplaceholder.typicode.com/posts", { signal }); const json = await res.json(); setPosts(json); } catch (err) { console.log(err); } }; initialize(); return () => { controller.abort(); }; }, []); ``` ## 4. 增加無障礙輔助功能 以上範例太過簡單,沒有太多無障礙功能可以加入。一旦應用程式變複雜,絕對應該要開始考慮這點。 有個簡單的無障礙測試:可以單獨只使用鍵盤就操作一個應用程式嗎? 關於這點,快速解法是:把每個項目都轉成按鈕,用鍵盤時就可以在元件之間切換。 ``` const Post = ({ post, onPostClick }) => { const onClick = () => { onPostClick(post); }; return <button onClick={onClick}>{post}</button>; }; ``` # 結論 在 React 中渲染列表,乍看之下,只是個簡單的面試問題。但其實可深可淺。下次面試時遇到這問題,可以思考一下此篇文章談到的各個面向。