這一切都始於一張 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 應用程式會是什麼樣子?無需記住使用客戶端或使用伺服器等數十條指令?更好的是:如果您可以像本地一樣呼叫伺服器函數,並且具有完整的類型和零配置,會怎麼樣?
最好的部分:所有這些都可以在伺服器和客戶端上無縫運行,而無需將任何內容標記為使用客戶端或使用伺服器。框架根據上下文自動知道要做什麼。我們要潛入水中嗎?
想像一下能夠做到這一點:
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! 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) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // 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]); // ... rest of the component }
忘掉你所知道的關於 React 中資料獲取的一切吧。 React Edge 中的 app.useFetch 鉤子引進了一種全新且強大的方法。想像一個鉤子:
讓我們來看看它的實際效果:
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! 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) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // 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]); // ... rest of the component }
// First, define your API on the server class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Automatic caching for 5 minutes return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Now, on the client, the magic happens const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Reactive search with intelligent debounce const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Re-fetch when filters change deps: [filters], // Wait 300ms of "silence" before fetching depsDebounce: { filters: 300 } } ); // Fetch property details for the found results const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // This looks like multiple calls, but... return ctx.rpc.batch([ // Everything is multiplexed into a single request! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Refresh when searchResults change deps: [searchResults] } ); // A beautiful and responsive interface return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Search error: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
React Edge 中的 RPC 系統在設計時就考慮到了安全性和封裝性。並非 RPC 類別中的所有內容都會自動暴露給客戶端:
const PropertyListingPage = () => { const { data } = app.useFetch(async (ctx) => { // Even if you make 100 identical calls... return ctx.rpc.batch([ ctx.rpc.getProperty('123'), ctx.rpc.getProperty('123'), // same call ctx.rpc.getProperty('456'), ctx.rpc.getProperty('456'), // same call ]); }); // Behind the scenes: // 1. The batch groups all calls into ONE single HTTP request. // 2. Identical calls are deduplicated automatically. // 3. Results are distributed correctly to each position in the array. // 4. Typing is maintained for each individual result! // Actual RPC calls: // 1. getProperty('123') // 2. getProperty('456') // Results are distributed correctly to all callers! };
RPC 最強大的功能之一是將 API 組織成層次結構的能力:
One of the most impressive parts is how useFetch handles SSR: const ProductPage = ({ productId }: Props) => { const { data, loaded, loading, error } = app.useFetch( async (ctx) => ctx.rpc.getProduct(productId), { // Fine-grained control over when to fetch shouldFetch: ({ worker, loaded }) => { // On the worker (SSR): always fetch if (worker) return true; // On the client: fetch only if no data is loaded return !loaded; } } ); // On the server: // 1. `useFetch` makes the RPC call. // 2. Data is serialized and sent to the client. // 3. Component renders with the data. // On the client: // 1. Component hydrates with server data. // 2. No new call is made (shouldFetch returns false). // 3. If necessary, you can re-fetch with `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 { // Properties are never exposed private stripe = new Stripe(process.env.STRIPE_KEY); // Methods starting with $ are private private async $validateCard(card: CardInfo) { return await this.stripe.cards.validate(card); } // Methods starting with _ are also private private async _processPayment(amount: number) { return await this.stripe.charges.create({ amount }); } // This method is public and accessible via RPC async createPayment(orderData: OrderData) { // Internal validation using a private method const validCard = await this.$validateCard(orderData.card); if (!validCard) { throw new HttpError(400, 'Invalid card'); } // Processing using another private method const payment = await this._processPayment(orderData.amount); return payment; } } // On the client: const PaymentForm = () => { const { rpc } = app.useContext<App.Context>(); // ✅ This works const handleSubmit = () => rpc.createPayment(data); // ❌ These do not work - private methods are not exposed const invalid1 = () => rpc.$validateCard(data); const invalid2 = () => rpc._processPayment(100); // ❌ This also does not work - properties are not exposed const invalid3 = () => rpc.stripe; };
程式碼中的用法:
// Nested APIs for better organization class UsersAPI extends Rpc { // Subclass to manage preferences preferences = new UserPreferencesAPI(); // Subclass to manage notifications 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 { // Private methods remain private 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); } } } // On the client: const UserProfile = () => { const { rpc } = app.useContext<App.Context>(); const { data: profile } = app.useFetch( async (ctx) => { // Nested calls are fully typed const [user, theme, notificationSettings] = await ctx.rpc.batch([ // Method from the main class ctx.rpc.getProfile('123'), // Method from the preferences subclass ctx.rpc.preferences.getTheme('123'), // Method from the notifications subclass ctx.rpc.notifications.getSettings('123') ]); return { user, theme, notificationSettings }; } ); // ❌ Private methods remain inaccessible const invalid = () => rpc.notifications.$sendPush('123', 'hello'); };
React Edge 會自動偵測並載入您的翻譯。它甚至可以輕鬆地將用戶偏好保存在 cookie 中。但是,當然,您會期待這一點,對吧?
// translations/fr.ts export default { 'Good Morning, {name}!': 'Bonjour, {name}!', };
身份驗證一直是 Web 應用程式中的痛點。管理 JWT 令牌、安全性 cookie 和重新驗證通常需要大量樣板代碼。 React Edge 徹底改變了這一點。
以下是實現完整身份驗證系統的簡單方法:
const WelcomeMessage = () => { const userName = 'John'; return ( <div> {/* Output: Good Morning, John! */} <h1>{__('Good Morning, {name}!', { name: userName })}</h1> </div> ); };
// worker.ts const handler = { fetch: async (request: Request, env: types.Worker.Env, context: ExecutionContext) => { const url = new URL(request.url); const lang = (() => { const lang = url.searchParams.get('lang') || worker.cookies.get(request.headers, 'lang') || request.headers.get('accept-language') || ''; if (!lang || !i18n[lang]) { return 'en-us'; } return lang; })(); const workerApp = new AppWorkerEntry({ i18n: { en: await import('./translations/en'), pt: await import('./translations/pt'), es: await import('./translations/es') } }); const res = await workerApp.fetch(); if (url.searchParams.has('lang')) { return new Response(res.body, { headers: worker.cookies.set(res.headers, 'lang', lang) }); } return res; } };
零樣板
預設安全性
完整打字
無縫整合
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
React Edge 最強大的功能之一是它能夠在工作執行緒和用戶端之間安全地共享狀態。其工作原理如下:
// pages/api/search.ts export default async handler = (req, res) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // 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]); // ... rest of the component }
React Edge 的路由系統受到 Hono 的啟發,但增強了 SSR 的功能:
// First, define your API on the server class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Automatic caching for 5 minutes return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Now, on the client, the magic happens const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Reactive search with intelligent debounce const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Re-fetch when filters change deps: [filters], // Wait 300ms of "silence" before fetching depsDebounce: { filters: 300 } } ); // Fetch property details for the found results const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // This looks like multiple calls, but... return ctx.rpc.batch([ // Everything is multiplexed into a single request! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Refresh when searchResults change deps: [searchResults] } ); // A beautiful and responsive interface return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Search error: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
React Edge 包含一個強大的快取系統,可以無縫地處理 JSON 資料和整個頁面。此快取系統支援智慧標記和基於前綴的失效,使其適用於廣泛的場景。
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
Link元件是一個智慧且以效能為導向的解決方案,用於預先載入用戶端資源,確保為使用者提供更流暢、更快的導航體驗。當使用者將滑鼠停留在連結上時,會觸發其預取功能,利用空閒時間提前請求目的地資料。
// pages/api/search.ts export default async handler = (req, res) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // 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]); // ... rest of the component }
當使用者將滑鼠懸停在「關於我們」連結上時,該元件將開始預先載入 /about 頁面的數據,確保幾乎即時轉換。天才的想法,不是嗎?受到react.dev文檔的啟發。
app.useContext 鉤子是 React Edge 的基石,允許無縫存取工作人員的整個上下文。它提供了一個強大的介面來管理路由、狀態、RPC 呼叫等。
// First, define your API on the server class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Automatic caching for 5 minutes return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Now, on the client, the magic happens const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Reactive search with intelligent debounce const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Re-fetch when filters change deps: [filters], // Wait 300ms of "silence" before fetching depsDebounce: { filters: 300 } } ); // Fetch property details for the found results const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // This looks like multiple calls, but... return ctx.rpc.batch([ // Everything is multiplexed into a single request! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Refresh when searchResults change deps: [searchResults] } ); // A beautiful and responsive interface return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Search error: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
app.useContext 的主要特性
app.useContext 掛鉤彌合了工作人員和客戶端之間的差距。它允許您建立依賴共享狀態、安全資料擷取和上下文渲染的功能,而無需樣板。這簡化了複雜的應用程序,使它們更容易維護並更快地開發。
app.useUrlState 掛鉤可讓您的應用程式狀態與 URL 查詢參數保持同步,提供對 URL 中包含的內容、狀態如何序列化以及狀態何時更新的細微控制。
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
初始狀態
選項:
app.useStorageState 掛鉤可讓您使用 localStorage 或 sessionStorage 在瀏覽器中保留狀態,並具有完整的 TypeScript 支援。
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! 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) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // 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]); // ... rest of the component }
在保持型別安全的同時保留具有唯一值的陣列。
app.useDistinct 鉤子專門用於檢測值何時真正發生變化,並支援深度比較和反跳:
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
React Edge 中的鉤子旨在協調工作,提供流暢且強類型的開發體驗。它們的組合允許使用更少的程式碼創建複雜且反應式的介面。
React Edge 的 CLI 旨在透過將基本工具收集到直覺的介面中來簡化開發人員的生活。無論您是初學者還是經驗豐富的開發人員,CLI 都能確保您能夠有效率、輕鬆地配置、開發、測試和部署專案。
我很自豪地分享,第一個使用 React Edge 的生產應用程式已經上線!巴西房地產公司 Lopes Imóveis 已經從該框架的性能和靈活性中獲益。
在他們的網站上,屬性被加載到快取中以優化搜尋並提供更流暢的用戶體驗。由於它是一個高度動態的站點,因此路由快取僅使用 10 秒的 TTL,並結合重新驗證時失效策略。這確保了網站即使在後台重新驗證期間也能提供具有卓越效能的更新資料。
此外,類似屬性的建議會在背景高效非同步運算,然後使用整合 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中文網其他相關文章!