这一切都始于一张 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中文网其他相关文章!