Rumah >hujung hadapan web >tutorial js >Dari Next.js untuk React Edge dengan Cloudflare Workers: A Liberation Story
Semuanya bermula dengan invois daripada Vercel. Tidak, ia sebenarnya bermula lebih awal - dengan kekecewaan kecil yang timbul. Keperluan untuk membayar ciri asas seperti perlindungan DDoS, log yang lebih terperinci, atau juga tembok api yang baik, membina baris gilir, dsb. Rasa terperangkap dalam lock-in vendor yang semakin mahal.
"Dan yang paling teruk: pengepala SEO berharga kami hanya berhenti dipaparkan pada pelayan dalam aplikasi menggunakan penghala halaman. Sakit kepala sebenar bagi mana-mana pembangun! ?"
Tetapi apa yang benar-benar membuatkan saya memikirkan semula segala-galanya ialah arah yang diambil oleh Next.js. Pengenalan pelanggan penggunaan, pelayan penggunaan - arahan yang dalam teori harus memudahkan pembangunan, tetapi dalam amalan menambah satu lagi lapisan kerumitan untuk diurus. Seolah-olah kami akan kembali ke zaman PHP, menandai fail dengan arahan untuk memberitahu mereka di mana ia harus dijalankan.
Dan ia tidak berhenti di situ. App Router, idea yang menarik, tetapi dilaksanakan dengan cara yang mencipta rangka kerja yang boleh dikatakan baharu dalam Next.js. Tiba-tiba kami mempunyai dua cara yang berbeza untuk melakukan perkara yang sama. Yang 'lama' dan 'baru' - dengan gelagat yang berbeza secara halus dan perangkap tersembunyi.
Ketika itulah saya menyedari: mengapa tidak mengambil kesempatan daripada infrastruktur Cloudflare yang luar biasa dengan Pekerja berjalan di tepi, R2 untuk storan, KV untuk data yang diedarkan... Selain itu, sudah tentu, perlindungan DDoS yang luar biasa, CDN global, firewall, peraturan untuk halaman dan laluan dan segala-galanya yang ditawarkan oleh Cloudflare.
Dan yang terbaik: model harga yang berpatutan, di mana anda membayar untuk apa yang anda gunakan, tanpa kejutan.
Beginilah cara React Edge dilahirkan. Rangka kerja yang tidak cuba mencipta semula roda, sebaliknya memberikan pengalaman pembangunan yang benar-benar mudah dan moden.
Apabila saya mula membangunkan React Edge, saya mempunyai matlamat yang jelas: untuk mencipta rangka kerja yang masuk akal. Tiada lagi bergelut dengan arahan yang mengelirukan, tiada lagi bayaran untuk ciri asas, dan yang paling penting, tiada lagi perlu berurusan dengan kerumitan buatan yang dicipta oleh pemisahan klien/pelayan. Saya mahukan kelajuan, sesuatu yang memberikan prestasi tanpa mengorbankan kesederhanaan. Dengan memanfaatkan pengetahuan saya tentang API React dan bertahun-tahun sebagai pembangun Javascript dan Golang, saya tahu dengan tepat cara mengendalikan strim dan pemultipleksan untuk mengoptimumkan pemaparan dan pengurusan data.
Cloudflare Workers, dengan infrastruktur yang berkuasa dan kehadiran globalnya, menawarkan saya persekitaran yang sempurna untuk meneroka kemungkinan ini. Saya mahukan sesuatu yang benar-benar hibrid, dan gabungan alatan serta pengalaman inilah yang memberi kehidupan kepada React Edge: rangka kerja yang menyelesaikan masalah sebenar dengan penyelesaian moden dan cekap.
React Edge membawa pendekatan revolusioner kepada pembangunan React. Bayangkan anda boleh menulis kelas pada pelayan dan memanggilnya terus daripada klien, dengan menaip penuh dan konfigurasi sifar. Bayangkan sistem caching teragih yang "hanya berfungsi", membenarkan penolakan oleh teg atau awalan. Bayangkan anda boleh berkongsi keadaan antara pelayan dan pelanggan dengan cara yang telus dan selamat. Selain memudahkan pengesahan dan membawa pendekatan pengantarabangsaan yang cekap, CLI dan banyak lagi.
Komunikasi RPC anda sangat semula jadi seolah-olah seperti sihir - anda menulis kaedah dalam kelas dan memanggilnya daripada pelanggan seolah-olah kaedah itu tempatan. Sistem pemultipleksan pintar memastikan bahawa, walaupun berbilang komponen membuat panggilan yang sama, hanya satu permintaan dibuat kepada pelayan. Cache ephemeral mengelakkan permintaan berulang yang tidak perlu, dan semua ini berfungsi pada kedua-dua pelayan dan pelanggan.
Salah satu perkara yang paling berkesan ialah app.useFetch hook, yang menyatukan pengalaman pengambilan data. Pada pelayan, ia pramuat data semasa SSR; pada pelanggan, ia menghidrat secara automatik dengan data ini dan membenarkan kemas kini atas permintaan. Dan dengan sokongan untuk tinjauan automatik dan kereaktifan berasaskan pergantungan, mencipta antara muka dinamik tidak pernah semudah ini.
Tetapi ia tidak berhenti di situ. Rangka kerja ini menawarkan sistem penghalaan yang berkuasa (diilhamkan oleh Hono yang hebat), pengurusan aset yang disepadukan dengan Cloudflare R2, dan cara yang elegan untuk menangani ralat melalui kelas HttpError. Middleware boleh menghantar data kepada pelanggan dengan mudah melalui kedai kongsi dan semuanya dikaburkan secara automatik untuk keselamatan.
Yang paling mengagumkan? Hampir semua kod rangka kerja adalah hibrid. Tiada versi 'klien' dan 'pelayan' - kod yang sama berfungsi dalam kedua-dua persekitaran, secara automatik menyesuaikan diri dengan konteks. Pelanggan hanya menerima apa yang mereka perlukan, menjadikan himpunan terakhir sangat dioptimumkan.
Dan icing pada kek: semua ini berjalan pada infrastruktur tepi Cloudflare Workers, memberikan prestasi luar biasa pada kos yang berpatutan. Tiada kejutan pada rang undang-undang, tiada ciri asas yang tersembunyi di sebalik rancangan perusahaan paksa, hanya rangka kerja yang kukuh yang membolehkan anda memberi tumpuan kepada perkara yang benar-benar penting: mencipta aplikasi yang luar biasa. Selain itu, React Edge memanfaatkan keseluruhan ekosistem Cloudflare, termasuk Baris Gilir, Objek Tahan Lama, Storan KV dan banyak lagi untuk menyediakan asas yang teguh dan berskala untuk aplikasi anda.
Vite digunakan sebagai asas, baik untuk persekitaran pembangunan dan untuk ujian dan binaan. Vite, dengan kelajuan yang mengagumkan dan seni bina moden, membolehkan aliran kerja yang tangkas dan cekap. Ia bukan sahaja mempercepatkan pembangunan, tetapi juga mengoptimumkan proses binaan, memastikan kod disusun dengan cepat dan tepat. Tidak syak lagi, Vite ialah pilihan yang tepat untuk React Edge.
Pernahkah anda terfikir bagaimana rasanya membangunkan aplikasi React tanpa perlu risau tentang halangan pelanggan/pelayan? Tanpa perlu menghafal berpuluh-puluh arahan seperti menggunakan klien atau menggunakan pelayan? Dan lebih baik lagi: bagaimana jika anda boleh memanggil fungsi pelayan seolah-olah ia adalah tempatan, dengan penaipan penuh dan konfigurasi sifar?
Dan bahagian yang terbaik: semua ini berfungsi pada kedua-dua pelayan dan pelanggan, tanpa perlu menandakan apa-apa dengan menggunakan klien atau menggunakan pelayan. Rangka kerja tahu apa yang perlu dilakukan berdasarkan konteks. Boleh kita pergi?
Bayangkan boleh melakukan ini:
// 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 }
Lupakan semua yang anda ketahui tentang pengambilan data dalam React. App.useFetch React Edge membawa pendekatan yang benar-benar baharu dan berkuasa. Bayangkan cangkuk yang:
Mari kita lihat tindakan ini:
// 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' }) ); };
Contoh di atas menyembunyikan ciri berkuasa: pemultipleksan pintar. Apabila anda menggunakan ctx.rpc.batch, React Edge bukan sahaja membatch panggilan - ia secara automatik menyahduplikasi panggilan yang sama:
// 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 }
Salah satu bahagian yang paling mengagumkan ialah cara useFetch mengendalikan 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> ); };
Sistem RPC React Edge telah direka bentuk dengan mengambil kira keselamatan dan enkapsulasi. Tidak semua dalam kelas RPC didedahkan secara automatik kepada pelanggan:
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! };
Salah satu ciri RPC yang paling berkuasa ialah keupayaan untuk menyusun API ke dalam hierarki:
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> ); };
Mengatur API ke dalam hierarki membawa beberapa faedah:
Sistem RPC React Edge menjadikan komunikasi pelanggan-pelayan begitu semula jadi sehingga anda hampir terlupa bahawa anda sedang membuat panggilan jauh. Dan dengan keupayaan untuk menyusun API ke dalam hierarki, anda boleh mencipta struktur yang kompleks sambil memastikan kod anda teratur dan selamat.
React Edge membawakan sistem pengantarabangsaan yang elegan dan fleksibel yang menyokong interpolasi pembolehubah dan pemformatan kompleks tanpa perpustakaan yang berat.
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; };
Penggunaan dalam kod:
// 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 mengesan dan memuatkan terjemahan anda secara automatik dan boleh menyimpan pilihan pengguna dengan mudah dalam kuki. Tetapi anda sudah menjangkakannya, bukan?
// 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' }) ); };
Pengesahan sentiasa menjadi masalah dalam aplikasi web. Menguruskan token JWT, kuki selamat, pengesahan semula - semua ini biasanya memerlukan banyak kod boilerplate. React Edge mengubah ini sepenuhnya.
Lihat betapa mudahnya untuk melaksanakan sistem pengesahan lengkap:
// 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> ); };
Sifar Plat Dandang
Keselamatan secara Lalai
Taip Lengkap
Penyatuan Lancar
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! };
Salah satu ciri React Edge yang paling berkuasa ialah keupayaannya untuk berkongsi keadaan dengan selamat antara pekerja dan pelanggan. Mari lihat cara ini berfungsi:
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> ); };
Sistem penghalaan React Edge diilhamkan oleh Hono, tetapi dengan kuasa besar untuk 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 mempunyai sistem caching yang berkuasa yang berfungsi untuk kedua-dua data JSON dan keseluruhan halaman:
// 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 }
Komponen Pautan ialah penyelesaian pintar dan berprestasi untuk pramuat sumber di sisi pelanggan, memastikan navigasi yang lebih lancar dan lebih pantas untuk pengguna. Fungsi prefetching diaktifkan apabila menuding kursor pada pautan, mengambil kesempatan daripada saat pengguna tidak aktif untuk meminta data destinasi terlebih dahulu.
Bagaimana ia berfungsi?
Prafetch Bersyarat: Atribut prefetch (aktif secara lalai) mengawal sama ada pramuat akan dilakukan.
Smart Cache: Satu Set digunakan untuk menyimpan pautan yang telah dimuat naik, mengelakkan panggilan berlebihan.
Masuk Tetikus: Apabila pengguna menuding kursor pada pautan, fungsi handleMouseEnter menyemak sama ada pramuat perlu dan, jika ya, memulakan permintaan pengambilan ke destinasi.
Ralat Selamat: Sebarang kegagalan dalam permintaan ditindas, memastikan gelagat komponen tidak terjejas oleh ralat rangkaian seketika.
// 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' }) ); };
Apabila pengguna menuding tetikus pada pautan "Perihal Kami", komponen akan mula memuatkan data dari halaman /tentang, memberikan peralihan yang hampir serta-merta. Idea yang bernas, bukan? Tetapi saya melihatnya dalam dokumentasi react.dev.
app.useContext ialah cangkuk asas React Edge, menyediakan akses kepada keseluruhan konteks pekerja:
// 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 }
Kait app.useContext merapatkan jurang antara pekerja dan pelanggan. Ia membolehkan anda membina ciri yang bergantung pada keadaan dikongsi, pengambilan data selamat dan pemaparan kontekstual tanpa kod berulang. Ini memudahkan aplikasi yang kompleks, menjadikannya lebih mudah untuk diselenggara dan lebih cepat untuk dibangunkan.
Kait app.useUrlState memastikan keadaan aplikasi anda disegerakkan dengan parameter URL, memberikan anda kawalan tepat ke atas perkara yang disertakan dalam URL, cara keadaan itu disiri dan bila ia dikemas kini.
// 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> ); };
Keadaan Awal
Pilihan:
Kait app.useStorageState membolehkan anda mengekalkan keadaan dalam penyemak imbas menggunakan localStorage atau sessionStorage dengan sokongan menaip penuh.
// 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' }) ); };
Nyahlantunkan nilai reaktif dengan mudah:
// 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 }
Kekalkan tatasusunan nilai unik dengan menaip:
app.useDistinct ialah cangkuk khusus dalam mengesan apabila nilai sebenarnya telah berubah, dengan sokongan untuk perbandingan mendalam dan nyahlantun:
// 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' }) ); };
Cakuk React Edge direka untuk berfungsi dengan harmoni, memberikan pengalaman pembangunan yang lancar dan ditaip. Menggabungkannya membolehkan anda mencipta antara muka yang kompleks dan reaktif dengan kod yang lebih sedikit.
React Edge CLI direka untuk memudahkan kehidupan pembangun dengan menghimpunkan alatan penting dalam satu antara muka yang intuitif. Sama ada anda seorang pemula atau pakar, CLI memastikan anda boleh mengkonfigurasi, membangun, menguji dan menggunakan projek dengan cekap dan tanpa kerumitan.
Ciri Utama
Saya berbangga untuk berkongsi bahawa aplikasi pengeluaran pertama menggunakan React Edge kini berfungsi! Ini ialah syarikat hartanah Brazil, Lopes Imóveis, yang sudah pun mengambil kesempatan daripada semua prestasi dan fleksibiliti rangka kerja.
Di tapak web agensi hartanah, hartanah dimuatkan dalam cache untuk mengoptimumkan carian dan menawarkan pengalaman yang lebih lancar untuk pengguna. Memandangkan ia adalah tapak web yang sangat dinamik, cache laluan menggunakan TTL selama 10 saat sahaja, digabungkan dengan strategi yang basi sementara pengesahan semula. Ini memastikan bahawa tapak menyampaikan data terkini dengan prestasi yang luar biasa, walaupun semasa pengesahan semula latar belakang.
Selain itu, pengesyoran untuk sifat yang serupa dikira dengan cekap dan sekali-sekala di latar belakang, dan disimpan terus dalam cache Cloudflare, menggunakan sistem cache yang disepadukan ke dalam RPC. Pendekatan ini mengurangkan masa tindak balas pada permintaan berikutnya dan membuat pengesyoran pertanyaan hampir serta-merta. Selain itu, semua imej disimpan pada Cloudflare R2, menawarkan storan berskala dan teragih tanpa bergantung pada pembekal luaran.
Dan tidak lama lagi kami juga akan mengadakan pelancaran projek pemasaran automatik yang besar untuk Easy Auth, seterusnya menunjukkan potensi teknologi ini.
Jadi, pembaca yang dihormati, kami sampai ke penghujung pengembaraan ini melalui alam semesta React Edge! Saya tahu masih terdapat banyak perkara yang luar biasa untuk diterokai, seperti pengesahan paling mudah seperti Asas dan Pembawa, dan rahsia kecil lain yang menjadikan kehidupan harian seorang pembangun lebih bahagia. Tetapi bertenang! Idea ini adalah untuk membawa artikel yang lebih terperinci pada masa hadapan untuk menyelami terlebih dahulu setiap ciri ini.
Dan, spoiler: tidak lama lagi React Edge akan menjadi sumber terbuka dan didokumenkan dengan betul! Mengimbangi pembangunan, kerja, penulisan dan sedikit kehidupan sosial bukanlah mudah, tetapi keseronokan melihat keajaiban ini dalam tindakan, terutamanya dengan kelajuan yang tidak masuk akal yang disediakan oleh infrastruktur Cloudflare, adalah bahan api yang menggerakkan saya. Jadi tahan kebimbangan anda, kerana yang terbaik akan datang! ?
Sementara itu, jika anda ingin mula meneroka dan menguji sekarang, pakej itu kini tersedia di NPM: React Edge pada NPM..
E-mel saya ialah feliperohdee@gmail.com, dan saya sentiasa terbuka untuk maklum balas, ini hanyalah permulaan perjalanan, cadangan dan kritikan membina ini. Jika anda menyukai apa yang anda baca, kongsikannya dengan rakan dan rakan sekerja anda dan nantikan perkara baharu yang akan datang. Terima kasih kerana mengikuti saya sejauh ini, dan sehingga kali seterusnya! ???
Atas ialah kandungan terperinci Dari Next.js untuk React Edge dengan Cloudflare Workers: A Liberation Story. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!