這一切都始於 Vercel 的發票。不,它實際上開始得更早——伴隨著一些小小的挫折。需要為 DDoS 防護、更詳細的日誌、甚至像樣的防火牆、建置佇列等基本功能付費。被困在日益昂貴的供應商鎖定中的感覺。
「最糟糕的是:我們寶貴的 SEO 標頭根本就停止在使用頁面路由器的應用程式中的伺服器上呈現。這對任何開發人員來說都是一個真正的頭痛!?」
但真正讓我重新思考一切的是 Next.js 的發展方向。使用客戶端、使用伺服器的引入 - 理論上應該簡化開發的指令,但實際上增加了另一層管理的複雜性。就好像我們回到了 PHP 時代,用指令標記檔案來告訴它們應該在哪裡運行。
而且它還不止於此。 App Router,一個有趣的想法,但實作方式是在 Next.js 中創建一個幾乎全新的框架。突然之間,我們有兩種完全不同的方式來做同一件事。 「舊」和「新」 - 具有微妙不同的行為和隱藏的陷阱。
那時我意識到:為什麼不利用Cloudflare 令人難以置信的基礎設施,其中Workers 在邊緣運行,R2 用於存儲,KV 用於分佈式數據......當然,還有令人難以置信的DDoS 防護、全球CDN、防火牆、規則了解頁面和路線以及Cloudflare 提供的所有其他內容。
最好的:公平的價格模式,以使用量付費,不會出現意外。
這就是 React Edge 的誕生。一個不會嘗試重新發明輪子的框架,而是提供真正簡單且現代的開發體驗。
當我開始開發 React Edge 時,我有一個明確的目標:創建一個有意義的框架。不再需要與令人困惑的指令作鬥爭,不再需要為基本功能付出高昂的代價,最重要的是,不再需要處理客戶端/伺服器分離所造成的人為複雜性。我想要速度,即在不犧牲簡單性的情況下提供性能的東西。憑藉我對 React API 的了解以及多年作為 Javascript 和 Golang 開發人員的知識,我確切地知道如何處理流和多路復用以優化渲染和資料管理。
Cloudflare Workers 憑藉其強大的基礎設施和全球影響力,為我提供了探索這些可能性的完美環境。我想要真正混合的東西,而工具和經驗的結合賦予了 React Edge 生命:一個用現代高效的解決方案解決實際問題的框架。
React Edge 為 React 開發帶來了革命性的方法。想像一下,能夠在伺服器上編寫一個類別並直接從客戶端呼叫它,並且具有完整的類型和零配置。想像一個「正常工作」的分散式快取系統,允許透過標籤或前綴使其失效。想像一下能夠以透明且安全的方式在伺服器和客戶端之間共享狀態。除了簡化身分驗證並帶來高效的國際化方法、CLI 等之外。
您的 RPC 通訊是如此自然,看起來就像魔法一樣 - 您在類別中編寫方法並從客戶端呼叫它們,就好像它們是本地的一樣。智慧多路復用系統確保即使多個元件進行相同的調用,也只向伺服器發出一個請求。臨時快取避免了不必要的重複請求,所有這些都適用於伺服器和用戶端。
最強大的點之一是 app.useFetch 鉤子,它統一了資料擷取體驗。在伺服器上,它在SSR期間預先載入資料;在客戶端,它會自動合併這些資料並允許按需更新。憑藉對自動輪詢和基於依賴關係的反應性的支持,創建動態介面變得前所未有的簡單。
但它不止於此。該框架提供了強大的路由系統(受到出色的 Hono 的啟發)、與 Cloudflare R2 整合的資產管理,以及透過 HttpError 類別處理錯誤的優雅方法。中間件可以透過共享儲存輕鬆地將資料傳送到客戶端,為了安全起見,所有內容都會自動混淆。
印象最深刻的是?幾乎所有框架的程式碼都是混合的。沒有“客戶端”和“伺服器”版本 - 相同的程式碼可以在兩種環境中工作,自動適應上下文。客戶只收到他們需要的東西,使最終的捆綁包得到了極大的優化。
錦上添花:所有這些都在 Cloudflare Workers 邊緣基礎設施上運行,以合理的成本提供卓越的效能。帳單上沒有意外,沒有隱藏在強制企業計劃背後的基本功能,只有一個堅實的框架,可以讓您專注於真正重要的事情:創建令人難以置信的應用程式。此外,React Edge 利用整個 Cloudflare 生態系統,包括佇列、持久化物件、KV 儲存等,為您的應用程式提供強大、可擴展的基礎。
Vite 被用作開發環境以及測試和建置的基礎。 Vite 以其令人印象深刻的速度和現代化的架構,實現了敏捷高效的工作流程。它不僅加快了開發速度,還優化了建置過程,確保快速且準確地編譯程式碼。毫無疑問,Vite 是 React Edge 的完美選擇。
你有沒有想過在不擔心客戶端/伺服器障礙的情況下開發 React 應用程式會是什麼樣子?無需記住數十個指令,例如使用客戶端或使用伺服器?甚至更好:如果您可以像本地一樣呼叫伺服器函數,並且具有完整的類型和零配置,會怎麼樣?
最好的部分:所有這些都可以在伺服器和用戶端上運行,而無需使用 use client 或 use server 來標記任何內容。框架知道根據上下文做什麼。我們走吧?
想像一下能夠做到這一點:
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... resto do componente }
忘掉你所知道的關於 React 中資料獲取的一切吧。 React Edge 的 app.useFetch 帶來了一種全新且強大的方法。想像一個鉤子:
讓我們來看看實際效果:
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
上面的範例隱藏了一個強大的功能:智慧復用。當您使用 ctx.rpc.batch 時,React Edge 不僅僅對呼叫進行批次處理 - 它會自動刪除重複的相同呼叫:
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... resto do componente }
最令人印象深刻的部分之一是 useFetch 如何處理 SSR:
// Primeiro, definimos nossa API no servidor class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Cache automático por 5 minutos return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Agora, no cliente, a mágica acontece const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Busca reativa com debounce inteligente const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Quando filters muda, refaz a busca deps: [filters], // Mas espera 300ms de 'silêncio' antes de buscar depsDebounce: { filters: 300 } } ); // Agora, vamos buscar os detalhes das propriedades encontradas const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // Isso parece fazer múltiplas chamadas, mas... return ctx.rpc.batch([ // Na verdade, tudo é multiplexado em uma única requisição! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Atualiza sempre que searchResults mudar deps: [searchResults] } ); // Interface bonita e responsiva return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Erro na busca: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
React Edge 的 RPC 系統在設計時就考慮到了安全性和封裝性。並非 RPC 類別中的所有內容都會自動暴露給客戶端:
const PropertyListingPage = () => { const { data } = app.useFetch(async (ctx) => { // Mesmo que você faça 100 chamadas idênticas... return ctx.rpc.batch([ ctx.rpc.getProperty('123'), ctx.rpc.getProperty('123'), // mesma chamada ctx.rpc.getProperty('456'), ctx.rpc.getProperty('456'), // mesma chamada ]); }); // Mas na realidade: // 1. O batch agrupa todas as chamadas em UMA única requisição HTTP // 2. Chamadas idênticas são deduplicas automaticamente // 3. O resultado é distribuído corretamente para cada posição do array // 4. A tipagem é mantida para cada resultado individual! // Entao.. // 1. getProperty('123') // 2. getProperty('456') // E os resultados são distribuídos para todos os chamadores! };
RPC 最強大的功能之一是將 API 組織成層次結構的能力:
const ProductPage = ({ productId }: Props) => { const { data, loaded, loading, error } = app.useFetch( async (ctx) => ctx.rpc.getProduct(productId), { // Controle fino de quando executar shouldFetch: ({ worker, loaded }) => { // No worker (SSR): sempre busca if (worker) return true; // No cliente: só busca se não tiver dados return !loaded; } } ); // No servidor: // 1. useFetch faz a chamada RPC // 2. Dados são serializados e enviados ao cliente // 3. Componente renderiza com os dados // No cliente: // 1. Componente hidrata com os dados do servidor // 2. Não faz nova chamada (shouldFetch retorna false) // 3. Se necessário, pode refazer a chamada com data.fetch() return ( <Suspense fallback={<ProductSkeleton />}> <ProductView product={data} loading={loading} error={error} /> </Suspense> ); };
將 API 組織成層次結構可以帶來多種好處:
React Edge 的 RPC 系統使客戶端-伺服器通訊變得如此自然,以至於您幾乎忘記自己正在進行遠端呼叫。透過將 API 組織成層次結構的能力,您可以建立複雜的結構,同時保持程式碼的組織性和安全性。
React Edge 帶來了一個優雅且靈活的國際化系統,無需繁重的庫即可支援變數插值和複雜的格式設定。
class PaymentsAPI extends Rpc { // Propriedades nunca são expostas private stripe = new Stripe(process.env.STRIPE_KEY); // Métodos começando com $ são privados private async $validateCard(card: CardInfo) { return await this.stripe.cards.validate(card); } // Métodos começando com _ também são privados private async _processPayment(amount: number) { return await this.stripe.charges.create({ amount }); } // Este método é público e acessível via RPC async createPayment(orderData: OrderData) { // Validação interna usando método privado const validCard = await this.$validateCard(orderData.card); if (!validCard) { throw new HttpError(400, 'Invalid card'); } // Processamento usando outro método privado const payment = await this._processPayment(orderData.amount); return payment; } } // No cliente: const PaymentForm = () => { const { rpc } = app.useContext<App.Context>(); // ✅ Isso funciona const handleSubmit = () => rpc.createPayment(data); // ❌ Isso não é possível - métodos privados não são expostos const invalid1 = () => rpc.$validateCard(data); const invalid2 = () => rpc._processPayment(100); // ❌ Isso também não funciona - propriedades não são expostas const invalid3 = () => rpc.stripe; };
程式碼中的用法:
// APIs aninhadas para melhor organização class UsersAPI extends Rpc { // Subclasse para gerenciar preferences preferences = new UserPreferencesAPI(); // Subclasse para gerenciar notificações notifications = new UserNotificationsAPI(); async getProfile(id: string) { return this.db.users.findById(id); } } class UserPreferencesAPI extends Rpc { async getTheme(userId: string) { return this.db.preferences.getTheme(userId); } async setTheme(userId: string, theme: Theme) { return this.db.preferences.setTheme(userId, theme); } } class UserNotificationsAPI extends Rpc { // Métodos privados continuam privados private async $sendPush(userId: string, message: string) { await this.pushService.send(userId, message); } async getSettings(userId: string) { return this.db.notifications.getSettings(userId); } async notify(userId: string, notification: Notification) { const settings = await this.getSettings(userId); if (settings.pushEnabled) { await this.$sendPush(userId, notification.message); } } } // No cliente: const UserProfile = () => { const { rpc } = app.useContext<App.Context>(); const { data: profile } = app.useFetch( async (ctx) => { // Chamadas aninhadas são totalmente tipadas const [user, theme, notificationSettings] = await ctx.rpc.batch([ // Método da classe principal ctx.rpc.getProfile('123'), // Método da subclasse de preferências ctx.rpc.preferences.getTheme('123'), // Método da subclasse de notificações ctx.rpc.notifications.getSettings('123') ]); return { user, theme, notificationSettings }; } ); // ❌ Métodos privados continuam inacessíveis const invalid = () => rpc.notifications.$sendPush('123', 'hello'); };
React Edge 會自動偵測並載入您的翻譯,並且可以輕鬆地將使用者首選項保存在 cookie 中。但你已經預料到了,對吧?
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
身份驗證一直是 Web 應用程式中的痛點。管理 JWT 令牌、安全性 cookie、重新驗證 - 所有這些通常都需要大量樣板程式碼。 React Edge 徹底改變了這一點。
看看實現完整的身份驗證系統是多麼簡單:
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... resto do componente }
// Primeiro, definimos nossa API no servidor class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Cache automático por 5 minutos return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Agora, no cliente, a mágica acontece const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Busca reativa com debounce inteligente const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Quando filters muda, refaz a busca deps: [filters], // Mas espera 300ms de 'silêncio' antes de buscar depsDebounce: { filters: 300 } } ); // Agora, vamos buscar os detalhes das propriedades encontradas const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // Isso parece fazer múltiplas chamadas, mas... return ctx.rpc.batch([ // Na verdade, tudo é multiplexado em uma única requisição! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Atualiza sempre que searchResults mudar deps: [searchResults] } ); // Interface bonita e responsiva return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Erro na busca: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
零樣板
預設安全性
完成打字
無縫整合
const PropertyListingPage = () => { const { data } = app.useFetch(async (ctx) => { // Mesmo que você faça 100 chamadas idênticas... return ctx.rpc.batch([ ctx.rpc.getProperty('123'), ctx.rpc.getProperty('123'), // mesma chamada ctx.rpc.getProperty('456'), ctx.rpc.getProperty('456'), // mesma chamada ]); }); // Mas na realidade: // 1. O batch agrupa todas as chamadas em UMA única requisição HTTP // 2. Chamadas idênticas são deduplicas automaticamente // 3. O resultado é distribuído corretamente para cada posição do array // 4. A tipagem é mantida para cada resultado individual! // Entao.. // 1. getProperty('123') // 2. getProperty('456') // E os resultados são distribuídos para todos os chamadores! };
React Edge 最強大的功能之一是它能夠在工作執行緒和用戶端之間安全地共享狀態。讓我們看看它是如何運作的:
const ProductPage = ({ productId }: Props) => { const { data, loaded, loading, error } = app.useFetch( async (ctx) => ctx.rpc.getProduct(productId), { // Controle fino de quando executar shouldFetch: ({ worker, loaded }) => { // No worker (SSR): sempre busca if (worker) return true; // No cliente: só busca se não tiver dados return !loaded; } } ); // No servidor: // 1. useFetch faz a chamada RPC // 2. Dados são serializados e enviados ao cliente // 3. Componente renderiza com os dados // No cliente: // 1. Componente hidrata com os dados do servidor // 2. Não faz nova chamada (shouldFetch retorna false) // 3. Se necessário, pode refazer a chamada com data.fetch() return ( <Suspense fallback={<ProductSkeleton />}> <ProductView product={data} loading={loading} error={error} /> </Suspense> ); };
React Edge 的路由系統受到 Hono 的啟發,但具有 SSR 的超能力:
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
React Edge 擁有強大的快取系統,適用於 JSON 資料和整個頁面:
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... resto do componente }
Link 元件是一個智慧且高效能的解決方案,用於在用戶端預先載入資源,確保使用者更流暢、更快速的導覽。當遊標懸停在連結上時,其預取功能會被激活,利用用戶不活動的時刻提前請求目標資料。
它是如何運作的?
Conditional Prefetch:prefetch 屬性(預設為活動狀態)控制是否執行預先載入。
智慧型快取:Set用於儲存已預先載入的鏈接,避免冗餘呼叫。
Mouse Enter:當使用者將遊標懸停在連結上時,handleMouseEnter 函數會檢查是否需要預先加載,如果需要,則向目的地發起獲取請求。
錯誤安全:抑制請求中的任何失敗,確保組件的行為不受瞬時網路錯誤的影響。
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
當使用者將滑鼠懸停在「關於我們」連結上時,該元件將開始從 /about 頁面預先載入數據,提供幾乎瞬時的轉換。好主意,對吧?但我在react.dev文檔中看到了它。
app.useContext 是 React Edge 的基本鉤子,提供對整個工作上下文的存取:
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... resto do componente }
app.useContext 掛鉤彌合了工作人員和客戶端之間的差距。它允許您建立依賴共享狀態、安全資料擷取和上下文渲染的功能,而無需重複程式碼。這簡化了複雜的應用程序,使它們更容易維護並更快地開發。
app.useUrlState 掛鉤可讓應用程式的狀態與 URL 參數保持同步,讓您可以精確控制 URL 中包含的內容、狀態的序列化方式以及更新時間。
// Primeiro, definimos nossa API no servidor class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Cache automático por 5 minutos return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Agora, no cliente, a mágica acontece const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Busca reativa com debounce inteligente const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Quando filters muda, refaz a busca deps: [filters], // Mas espera 300ms de 'silêncio' antes de buscar depsDebounce: { filters: 300 } } ); // Agora, vamos buscar os detalhes das propriedades encontradas const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // Isso parece fazer múltiplas chamadas, mas... return ctx.rpc.batch([ // Na verdade, tudo é multiplexado em uma única requisição! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Atualiza sempre que searchResults mudar deps: [searchResults] } ); // Interface bonita e responsiva return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Erro na busca: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
初始狀態
選項:
app.useStorageState 掛鉤可讓您使用 localStorage 或 sessionStorage 並具有完整的類型支援來在瀏覽器中保留狀態。
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
輕鬆消除無功值:
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... resto do componente }
透過輸入維護唯一值的陣列:
app.useDistinct 是一個專門用於檢測值何時實際更改的鉤子,支援深度比較和反跳:
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
React Edge hooks 旨在協調工作,提供流暢且類型化的開發體驗。將它們組合起來可以讓您用更少的程式碼創建複雜且反應式的介面。
React Edge CLI 旨在透過將基本工具集中在一個直覺的介面中來簡化開發人員的生活。無論您是初學者還是專家,CLI 都確保您可以有效率、輕鬆地配置、開發、測試和部署專案。
主要特點
我很自豪地分享,第一個使用 React Edge 的生產應用程式現在正在運行!這是一家巴西房地產公司 Lopes Imóveis,該公司已經利用了該框架的所有性能和靈活性。
在房地產仲介的網站上,房產會載入到快取中,以優化搜尋並為使用者提供更流暢的體驗。由於它是一個極其動態的網站,因此路由快取使用僅 10 秒的 TTL,並結合 stale-while-revalidate 策略。這確保了即使在後台重新驗證期間,網站也能提供具有卓越效能的最新數據。
此外,使用整合到 RPC 的快取系統,偶爾會在後台高效地計算類似屬性的推薦,並直接保存在 Cloudflare 的快取中。這種方法減少了後續請求的回應時間,並使查詢建議幾乎是即時的。此外,所有映像都儲存在 Cloudflare R2 上,提供可擴展的分散式存儲,無需依賴外部提供者。
很快我們還將啟動一個龐大的 Easy Auth 自動化行銷項目,進一步展示這項技術的潛力。
所以,親愛的讀者,我們透過 React Edge 宇宙的冒險結束了!我知道還有大量令人難以置信的事情需要探索,例如像 Basic 和 Bearer 這樣最簡單的身份驗證,以及其他讓開發人員的日常生活更加快樂的小秘密。但冷靜點!我們的想法是在未來推出更詳細的文章,以深入探討這些功能中的每一個。
並且,劇透:很快 React Edge 將開源並正確記錄!平衡開發、工作、寫作和一點社交生活並不容易,但看到這一奇蹟付諸實踐的興奮感,尤其是 Cloudflare 基礎設施提供的荒謬速度,是推動我前進的動力。所以請抑制住你的焦慮,因為最好的尚未到來! ?
同時,如果您想立即開始探索和測試,該軟體包現已在 NPM 上提供:React Edge on NPM..
我的電子郵件是feliperohdee@gmail.com,我總是樂於接受回饋,這只是這趟旅程的開始,建議和建設性批評。如果您喜歡所讀內容,請與您的朋友和同事分享,並留意即將推出的新內容。感謝您關注我到目前為止,直到下一次! ???
以上是從 Next.js 到使用 Cloudflare Workers 的 React Edge:一個解放故事的詳細內容。更多資訊請關注PHP中文網其他相關文章!