伺服器操作的出現是為了減少客戶端程式碼和簡化需要與伺服器通訊的互動。這是一個優秀的解決方案,可以讓開發人員編寫更少的程式碼。然而,在其他框架中實施它存在一些不容忽視的挑戰。
在本文中,我們將討論這些問題以及如何在 Brisa 中找到解決方案。
要了解伺服器操作提供的內容,回顧過去與伺服器的通訊方式很有用。您可能習慣於每次與伺服器互動時執行以下操作:
這七個動作每次互動都會重複。例如,如果您的頁面有 10 個不同的交互,您將重複非常相似的代碼 10 次,僅更改請求類型、URL、發送的資料和客戶狀態等詳細資訊。
一個熟悉的例子是
一:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
在伺服器中:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
增加客戶端包大小......以及開發人員的挫折感。
伺服器操作將這些操作封裝在遠端過程調用(RPC)中,它管理客戶端-伺服器通信,減少客戶端上的程式碼並將邏輯集中在伺服器上:
這裡的一切都是由 Brisa RPC 為您完成的。
這將是來自伺服器元件的程式碼:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
在這裡,開發人員不編寫客戶端程式碼,因為它是伺服器元件。 onInput 事件在反跳後接收,由客戶端 RPC 處理,而伺服器 RPC 使用「操作訊號」來觸發已向該儲存屬性註冊訊號的 Web 元件。
如您所見,這顯著減少了伺服器程式碼,最重要的是,客戶端上的程式碼大小不會隨著每次互動而增加。 RPC 用戶端程式碼佔用固定的 2 KB,無論您有 10 個還是 1000 個這樣的互動。這意味著客戶端包大小增加 0 位元組,換句話說,不會增加。
此外,在需要重新渲染的情況下,這是在伺服器上完成的,並以HTML 流的形式返回,使用戶比傳統方式更早地看到更改,在傳統方式中,您必須在客戶端上完成此工作伺服器回應。
這樣:
在 React 等其他框架中,他們只關注 操作作為 form onSubmit 的一部分,而不是任何事件。
這是一個問題,因為有許多非表單事件也應該從伺服器元件處理,而無需添加客戶端程式碼。例如,輸入的onInput 用於執行自動建議,onScroll 用於載入無限滾動,onMouseOver 進行,onMouseOver
進行,onMouseOver
應用程式比預期更具互動性HTMX 想法
3. 關注點分離
當伺服器操作在 React 中引入時,出現了新的範式轉變,許多開發人員在使用它們時必須更改心理晶片。
我們希望使其
盡可能熟悉Web平台
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />事件屬性
都是可互動的。這是一個範例重設表單: 在此範例中,根本沒有客戶端程式碼,在伺服器操作期間,您可以使用CSS
使用指示器停用提交按鈕,以便表單無法提交兩次,並且在伺服器上執行操作後的同一時間,使用e.formData 存取表單資料,然後使用事件的相同API 重設表單。
在精神上,這與使用網路平台非常相似。唯一的區別是所有伺服器元件的所有事件都是伺服器操作。 這樣,就實現了真正的關注點分離,沒有必要將
「用戶伺服器」或「使用客戶端」放入您的元件不再。請記住一切都只在伺服器上運行。唯一的例外是在客戶端上運行的src/web-components資料夾,並且事件正常。
在 Brisa 中,伺服器操作在伺服器元件之間傳播,就好像它們是 DOM 事件一樣。也就是說,從一個Server Action中,你可以呼叫一個Server Component的prop的事件,然後執行父Server Component的Server Action,等等
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
在這種情況下,onAfterMyAction 事件在父元件上執行,並且可以在伺服器上執行操作。這對於在伺服器上執行影響多個伺服器元件的操作非常有用。
特別是在過去的幾周里,在 X(以前的 Twitter)上進行了多次討論之後,Web 元件變得有點不受歡迎。然而,作為HTML的一部分,它是與伺服器操作互動的最佳方式,原因如下:
在 Web Components 中使用屬性需要序列化,就像不使用 Web Components 將資料從伺服器傳輸到客戶端一樣,因此,使用兩者,無需管理額外的序列化。
注意:如果您有興趣,我會在另一篇文章中解釋流式 HTML 並使用比較演算法對其進行處理。
在 Brisa 中,我們加入了一個新概念,為伺服器操作提供更多功能,這個概念稱為 「操作訊號」。 「行動訊號」的想法是,您有2 個商店,一個位於伺服器,一個位於客戶端。
為什麼是2家店?
預設伺服器儲存 僅在請求等級存在。且您可以共享對客戶端不可見的資料。例如,您可以讓中間件設定使用者並有權存取任何伺服器元件中的敏感使用者資料。透過生活在請求級別,不同請求之間不可能發生衝突,因為每個請求都有其自己的存儲並且不存儲在任何數據庫中,當請求完成,預設消亡。
另一方面,在客戶端商店中,它是一個商店,每個屬性在消費時都是一個信號,也就是說,如果它已更新,正在偵聽該訊號的Web 元件會做出反應。
但是,「操作訊號」的新概念是我們可以將伺服器儲存的生命週期延長到請求之外。為此,需要使用以下程式碼:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
此transferToClient方法,將伺服器資料共用到客戶端儲存並轉換為訊號。透過這種方式,很多時候不需要從伺服器進行任何重新渲染,您可以簡單地透過伺服器操作對正在偵聽該訊號的 Web 元件的訊號做出反應。
這次商店轉移使伺服器商店的生命現在:
渲染初始伺服器元件→客戶端→伺服器操作→客戶端→伺服器操作...
因此它從僅在請求級別生存到永久生存,兼容頁面之間的導航。
範例:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
在此範例中,我們延長了錯誤儲存屬性的壽命,不是在客戶端上使用,而是在伺服器操作中重複使用,然後最終在伺服器操作的重新渲染中重複使用。在這種情況下,作為非敏感數據,沒有必要對其進行加密。此範例程式碼全部發生在伺服器上,甚至重新渲染,用戶將在伺服器上渲染後看到錯誤,其中伺服器RPC 將在流中發送HTML 區塊,客戶端RPC 將對其進行處理以進行比較並顯示向用戶反饋錯誤。
如果在伺服器操作中使用了渲染級別存在的某個變量,則在安全級別,許多框架(如Next.js 14)所做的就是加密此資料以創建在以下位置使用的資料的快照渲染的時間。這或多或少沒問題,但是始終加密資料會產生相關的計算成本並且它並不總是敏感資料。
在 Brisa 中,為了解決這個問題,有不同的請求,在初始渲染中它有一個值,並且在伺服器操作中您可以捕獲它在此請求中具有的值。
<input debounceInput={300} onInput={async (e) => { // All this code only runs on the server const data = await search(e.target.value); store.set("query", data); store.transferToClient(["query"]); }} />
這在某些情況下很有用,但並不總是有用,例如,如果您執行 Math.random,初始渲染和伺服器操作執行之間肯定會有所不同。
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
這就是為什麼我們創建了「行動訊號」概念,以將資料從伺服器儲存傳送到客戶端儲存 ,並且開發者可以隨意決定是否加密它或不。
有時,您可能希望傳輸初始渲染中已存在的數據,而不是從伺服器操作查詢資料庫,即使它需要關聯的加密。為此,您只需使用:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
當你這樣做時:
<input debounceInput={300} onInput={async (e) => { // All this code only runs on the server const data = await search(e.target.value); store.set("query", data); store.transferToClient(["query"]); }} />
Web 元件(客戶端)內部總是會加密,但在伺服器上總是會解密。
注意:Brisa 使用 aes-256-cbc 進行加密,這是 OpenSSL 推薦的用於安全加密資訊的加密演算法組合。加密金鑰是在專案建置過程中產生的。
在 Brisa 中,雖然我們喜歡支援輕鬆編寫 Web 元件,但目標是能夠在沒有客戶端程式碼的情況下製作 SPA,並且僅在純客戶端互動或必須觸及 Web API 時才使用 Web 元件。這就是為什麼伺服器操作如此重要,因為它們允許與伺服器互動而無需編寫客戶端程式碼。
我們鼓勵您嘗試 Brisa,您只需在終端機中執行此命令:bun create brisa,或嘗試一些範例來看看它是如何工作的。
以上是伺服器操作已修復的詳細內容。更多資訊請關注PHP中文網其他相關文章!