国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務器之家 - 編程語言 - JavaScript - Next.js 強勁對手來了! Remix 正式宣布開源

Next.js 強勁對手來了! Remix 正式宣布開源

2021-11-29 23:34程序員巴士一只圖雀 JavaScript

近期,由 React Router 原班團隊打造,基于 TypeScript 與 React,內建 React Router V6 特性的全棧 Web 框架 Remix 正式開源。目前占據 Github 趨勢總榜前 3.

Next.js 強勁對手來了! Remix 正式宣布開源

大家好,我是皮湯。周五翻 Github 趨勢榜看到了 Remix 這個內容,覺得挺有發展前景的,初步了解了一下具體的特性,分享給大家。

Next.js 強勁對手來了! Remix 正式宣布開源

近期,由 React Router 原班團隊打造,基于 TypeScript 與 React,內建 React Router V6 特性的全棧 Web 框架 Remix 正式開源。目前占據 Github 趨勢總榜前 3,Github 標星 5K+ Star:

Next.js 強勁對手來了! Remix 正式宣布開源

Remix 開源之后可以說是在 React 全??蚣茴I域激起千層浪,絕對可以算是 Next.js 的強勁對手。Remix 的特性如下:

  • 追求速度,然后是用戶體驗(UX),支持任何 SSR/SSG 等
  • 基于 Web 基礎技術,如 HTML/CSS 與 HTTP 以及 Web Fecth API,在絕大部分情況可以不依賴于 JavaScript 運行,所以可以運行在任何環境下,如 Web Browser、Cloudflare Workers、Serverless 或者 Node.js 等
  • 客戶端與服務端一致的開發體驗,客戶端代碼與服務端代碼寫在一個文件里,無縫進行數據交互,同時基于 TypeScript,類型定義可以跨客戶端與服務端共用
  • 內建文件即路由、動態路由、嵌套路由、資源路由等
  • 干掉 Loading、骨架屏等任何加載狀態,頁面中所有資源都可以預加載(Prefetch),頁面幾乎可以立即加載
  • 告別以往瀑布式(Waterfall)的數據獲取方式,數據獲取在服務端并行(Parallel)獲取,生成完整 HTML 文檔,類似 React 的并發特性
  • 提供開發網頁需要所有狀態,開箱即用;提供所有需要使用的組件,包括 <Links> 、<Link>、 <Meta> 、<Form> 、<Script/> ,用于處理元信息、腳本、CSS、路由和表單相關的內容
  • 內建錯誤處理,針對非預期錯誤處理的 <ErrorBoundary> 和開發者拋出錯誤處理的 <CatchBoundary>

特性這么多?不明覺厲!接下來我們就嘗試一一來展示這些 Remix 的特性。

一致的開發體驗

Remix 提供基于文件的路由,將讀取數據、操作數據和渲染數據的邏輯都寫在同一個路由文件里,方便一致性處理,這樣可以跨客戶端和服務端邏輯共享同一套類型定義。

看一段官網的代碼:

  1. import type { Post } from "~/post"
  2. import { Outlet, Link, useLoaderData, useTransition } from "remix"
  3.  
  4. let postsPath = path.join(__dirname, "..""posts"); 
  5.  
  6. async function getPosts() { 
  7.   let dir = await fs.readdir(postsPath); 
  8.   return Promise.all
  9.     dir.map(async (filename) => { 
  10.       let file = await fs.readFile(path.join(postsPath, filename)); 
  11.       let { attributes } = parseFrontMatter(file.toString()); 
  12.       invariant( 
  13.         isValidPostAttributes(attributes), 
  14.         `${filename} has bad meta data!` 
  15.       ); 
  16.       return { 
  17.         slug: filename.replace(/.md$/, ""), 
  18.         title: attributes.title, 
  19.       }; 
  20.     }) 
  21.   ); 
  22.  
  23. async function createPost(post: Post) { 
  24.   let md = `---\ntitle: ${post.title}\n---\n\n${post.markdown}`; 
  25.   await fs.writeFile(path.join(postsPath, post.slug + ".md"), md); 
  26.   return getPost(post.slug); 
  27.  
  28. export async function loader({ request }) { 
  29.   return getProjects(); 
  30.  
  31. export async function action({ request }) { 
  32.   let form = await request.formData(); 
  33.   const post = createPost({ title: form.get("title") }); 
  34.   return redirect(`/posts/${post.id}`); 
  35.  
  36. export default function Projects() { 
  37.   let posts = useLoaderData<Post[]>(); 
  38.   let { state } = useTransition(); 
  39.   let busy = state === "submitting"
  40.  
  41.   return ( 
  42.     <div> 
  43.       {posts.map((post) => ( 
  44.         <Link to={post.slug}>{post.title}</Link> 
  45.       ))} 
  46.  
  47.       <Form method="post"
  48.         <input name="title" /> 
  49.         <button type="submit" disabled={busy}> 
  50.           {busy ? "Creating..." : "Create New Post"
  51.         </button> 
  52.       </Form> 
  53.        
  54.       <Outlet /> 
  55.     </div> 
  56.   ); 

上述是一個路由文件,如果它是 src/routes/posts/index.tsx 文件,那么我們開啟服務器,通過 localhost:3000/posts 就可以訪問到這個文件,這就是文件即路由,而默認導出的 Projects 函數,即為一個 React 函數式組件,此函數的返回模板則為訪問這個路由的 HTML 文檔。

  • 每個路由函數,如 Projects 可以定義一個 loader 函數,類似處理 GET 請求的服務端函數,可以獲取到路由信息,為初次服務端渲提供數據,在這個函數中可以獲取文件系統、請求數據庫、進行其他網絡請求,然后返回數據,在我們的 Projects 組件里,可以通過 Remix 提供的 useLoaderData 鉤子拿到 loader 函數獲取到的數據。
  • 每個路由函數也可以定義一個 action 函數,用于進行實際的操作,類似處理非 GET 請求,如 POST/PUT/PATCH/DELETE 的操作的函數,它可以操作修改數據庫、寫入文件系統等,同時其返回的結果可能是實際的數據或是重定向到某個新頁面,如 redirect("/admin")。當 action 函數返回數據或錯誤信息時,我們可以通過 Remix 提供的 useActionData 鉤子拿到這個返回的錯誤信息,進行前端的展示等。

值得注意的是,action 函數是在 <Form method="post"> 表單里,用戶點擊提交按鈕之后自動調用,Remix 通過 Fetch API 的形式去調用,然后在前端不斷的輪詢獲取調用結果,且自動處理用戶多次點擊時的競爭情況。

你的瀏覽器網絡面板將呈現如下情況,自動 Remix 發起 POST 請求,然后處理重定向到 /post/${post.id} ,同時加載對應的 /posts 和 /posts/${post.id} 對應的路由頁面內容。

Next.js 強勁對手來了! Remix 正式宣布開源

通過 Remix 提供的 useTransition 鉤子,我們可以拿到表單提交的狀態,當請求還未返回結果時,我們可以通過這個狀態 state 判斷是否要展示一個加載狀態,提示用戶當前的請求進展。

Next.js 強勁對手來了! Remix 正式宣布開源

同時 Post 類型在 useLoaderData

有同學可能注意到了,上面我們整個頁面渲染、到發起創建 Post 請求、到后臺創建 Post,到重定向到 Post 詳情,這整個過程,我們無需在前端使用任何 JavaScript 相關的內容,僅僅通過 HTML 與 HTTP 就完成了這個交互,所以 Remix 的網站在 Disbaled JavaScript 運行環境下也可以正常工作。

Next.js 強勁對手來了! Remix 正式宣布開源

通過上圖我們可以看到,即使 JavaScript 已經關閉了,我們的網站依然可以正常運行。

 強大的嵌套路由體系

基于文件即路由的理念,我們無需集中的維護一套路由定義,當我們創建了對應的文件之后,Remix 就為我們注冊了對應的路由。

而 Remix 最具特色的功能之一就是嵌套路由。在 Remix 中,一個頁面通常包含多層級頁面,每個子頁面控制自身的 UI 展現,而且獨立控制自身的數據加載和代碼分割。

拿官網的例子來看如下:

Next.js 強勁對手來了! Remix 正式宣布開源

Next.js 強勁對手來了! Remix 正式宣布開源

上述頁面的對應關系如下:

  • 整個頁面模塊為 / 、而對應到 /sales 則是右邊的整塊天藍色內容、/sales/invoices 對應到黃色的部分、/sales/invoices/102000 則對應到右下角的紅色部分

整個路由分層,對應到整個頁面的分層視圖,而每個分層下的代碼都是獨立編寫,視圖渲染獨立渲染,數據獨立獲取,錯誤獨立展示。

來看一個實際例子:

  1. // src/root.tsx 
  2. import { 
  3.   Outlet, 
  4.    
  5. export default function App() { 
  6.   return ( 
  7.     <Document> 
  8.       <Layout> 
  9.         <Outlet /> 
  10.       </Layout> 
  11.     </Document> 
  12.   ); 
  13.  
  14. function Document() {} 
  15. function Layout() {} 
  1. // src/routes/admin.tsx 
  2. import { Outlet, Link, useLoaderData } from "remix"
  3. import { getPosts } from "~/post"
  4. import type { Post } from "~/post"
  5. import adminStyles from "~/styles/admin.css"
  6.  
  7. export let links = () => { 
  8.   return [{ rel: "stylesheet", href: adminStyles }]; 
  9. }; 
  10.  
  11. export let loader = () => { 
  12.   return getPosts(); 
  13. }; 
  14.  
  15. export default function Admin() { 
  16.   let posts = useLoaderData<Post[]>(); 
  17.   return ( 
  18.     <div className="admin"
  19.       <nav> 
  20.         <h1>Admin</h1> 
  21.         <ul> 
  22.           {posts.map((post) => ( 
  23.             <li key={post.slug}> 
  24.               <Link to={post.slug}>{post.title}</Link> 
  25.             </li> 
  26.           ))} 
  27.         </ul> 
  28.       </nav> 
  29.       <main> 
  30.         <Outlet /> 
  31.       </main> 
  32.     </div> 
  33.   ); 
  1. // src/routes/admin/index.tsx 
  2. import { Link } from "remix"
  3.  
  4. export default function AdminIndex() { 
  5.   return ( 
  6.     <p> 
  7.       <Link to="new">Create a New Post</Link> 
  8.     </p> 
  9.   ); 
  1. // src/routes/admin/new.tsx 
  2. import { useTransition, useActionData, redirect, Form } from "remix"
  3. import type { ActionFunction } from "remix"
  4. import { createPost } from "~/post"
  5. import invariant from "tiny-invariant"
  6.  
  7. export let action: ActionFunction = async ({ request }) => { 
  8.   await new Promise((res) => setTimeout(res, 1000)); 
  9.   let formData = await request.formData(); 
  10.  
  11.   let title = formData.get("title"); 
  12.   let slug = formData.get("slug"); 
  13.   let markdown = formData.get("markdown"); 
  14.  
  15.   let errors = {}; 
  16.   if (!title) errors.title = true
  17.   if (!slug) errors.slug = true
  18.   if (!markdown) errors.markdown = true
  19.  
  20.   if (Object.keys(errors).length) { 
  21.     return errors; 
  22.   } 
  23.  
  24.   await createPost({ title, slug, markdown }); 
  25.  
  26.   return redirect("/admin"); 
  27. }; 
  28.  
  29. export default function NewPost() { 
  30.   let errors = useActionData(); 
  31.   let transition = useTransition(); 
  32.  
  33.   return ( 
  34.     <Form method="post"
  35.       <p> 
  36.         <label> 
  37.           Post Title: {errors?.title && <em>Title is required</em>} 
  38.           <input type="text" name="title" /> 
  39.         </label> 
  40.       </p> 
  41.       <p> 
  42.         <label> 
  43.           Post Slug: {errors?.slug && <em>Slug is required</em>}{" "
  44.           <input type="text" name="slug" /> 
  45.         </label> 
  46.       </p> 
  47.       <p> 
  48.         <label htmlFor="markdown">Markdown:</label>{" "
  49.         {errors?.markdown && <em>Markdown is required</em>} 
  50.         <br /> 
  51.         <textarea rows={20} name="markdown" /> 
  52.       </p> 
  53.       <p> 
  54.         <button type="submit"
  55.           {transition.submission ? "Create..." : "Create Post"
  56.         </button> 
  57.       </p> 
  58.     </Form> 
  59.   ); 

上述代碼渲染的頁面如下:

Next.js 強勁對手來了! Remix 正式宣布開源

整個 App 網站是由 <Document> 嵌套 <Layout> 組成,其中 <Outlet> 是路由的填充處,即上圖中綠色的部分。當我們訪問 localhost:3000/ 時,其中填充的內容為 src/routes/index.tsx 路由文件對應的渲染內容,而當我們訪問 localhost:3000/admin 時,對應的是 src/routes/admin.tsx 路由文件對應的渲染內容。

而我們在 的 src/routes/admin.tsx 繼續提供了 <Outlet> 路由顯然組件,意味著當我們繼續添加分級(嵌套)路由時,如訪問 http://localhost:3000/admin/new 那么這個 <Outlet> 會渲染 src/routes/admin/new.tsx 對應路由文件的渲染內容,而訪問 http://localhost:3000/admin 時,<Outlet> 部分會渲染 src/routes/admin/index.tsx 對應路由文件的渲染內容,見下圖:

Next.js 強勁對手來了! Remix 正式宣布開源

而這種嵌套路由是自動發生的,當你創建了一個 src/routes/admin.tsx 之后,又創建了一個同名的文件夾,并在文件夾下建立了其它文件,那么這些文件的文件名會被注冊為下一級的嵌套路由名:

  • localhost:3000/admin 同時注冊 src/routes/admin.tsx 和 src/routes/admin/index.tsx
  • localhost:3000/admin/new 注冊 src/routes/admin/new.tsx

通過這種文件即路由,同名文件夾下文件即嵌套路由的方式,然后通過在父頁面里面通過 的方式渲染根據子路由渲染子頁面內容,極大的增加了靈活性,且每個子路由對應獨立的路由文件,具有獨立的數據處理邏輯、內容渲染邏輯、錯誤處理邏輯。

上述嵌套路由一個顯而易見的優點就是,某個部分如果報錯了,結合后續會提到的 ErrorBoundary 和 CatchBoundary 這個部分可以顯示錯誤的頁面,而用戶仍然可以操作其他部分,而不需要刷新整個頁面以重新加載使用,極大提高網站容錯性。

 再見,加載狀態

通過嵌套路由,Remix 可以干掉幾乎所有的加載狀態、骨架屏,現在很多應用都是在前端組件里進行數據獲取,獲取前置數據之后,然后用前置數據去獲取后置的數據,形成了一個瀑布式的獲取形式,當數據量大的時候,頁面加載就需要很長時間,所以絕大部分網站都會放一個加載的狀態,如小菊花轉圈圈,或者體驗更好一點的骨架屏,如下:

Next.js 強勁對手來了! Remix 正式宣布開源

這是因為這些應用缺乏類似 Remix 這樣的嵌套路由的概念,訪問某個路由時,就是訪問這個路由對應的頁面,只有這個頁面加載出來之后,里面的子組件渲染時,再進行數據的獲取,再加載子組件,如此往復,就呈現瀑布流式的加載,帶來了很多中間的加載狀態。

而 Remix 提供了嵌套路由,當訪問路由 localhost:3000/admin/new 時,會加載三級路由,同時這三個路由對應的頁面獨立、并行加載,獨立、并行獲取數據,最后發送給客戶端的是一個完整的 HTML 文檔,如下過程:

Next.js 強勁對手來了! Remix 正式宣布開源

可見雖然我們首屏拿到內容可能會慢一點,但是再也不需要加載狀態,再見,菊花圖,再見,骨架屏。

Next.js 強勁對手來了! Remix 正式宣布開源

同時借助嵌套路由,當我們鼠標 Hover 到某個鏈接準備點擊切換某個子路由時,Remix 提供了預獲取(Prefetch)功能,可以提前并行獲取子路由文檔和各種資源,包括 CSS、圖片、相關數據等,這樣當我們實際點擊這個鏈接切換子路由時,頁面可以立即呈現出來:

Next.js 強勁對手來了! Remix 正式宣布開源

 完善的錯誤處理

我們的網站經常會遇到問題,使用其他框架編寫時,網站遇到問題可能用戶就需要重新刷新網站,而對于 Remix 來說,基于嵌套路由的理念,則無需重新刷新,只需要在對應的錯誤的子路由展示錯誤信息,而頁面的其他部分仍然可以正常工作:

Next.js 強勁對手來了! Remix 正式宣布開源

比如我們上圖的右下角子路由出現了問題,那么這塊會展示出問題時的錯誤頁面,而其他頁面部分仍然展示正常的信息。

正因為錯誤經常發生,且處理錯誤異常困難,包含客戶端、服務端的各種錯誤,包含預期的、非預期的錯誤等,所以 Remix 內建了完善的錯誤處理機制,提供了類似 React 的 ErrorBoundary 的理念。

在 Remix 中,每個路由函數對應一個 ErrorBoundary 函數:

  1. export default function RouteFunction() {} 
  2.  
  3. export function ErrorBoundary({ error }) { 
  4.   console.error(error); 
  5.   return ( 
  6.     <div> 
  7.       <h2>Oh snap!</h2> 
  8.       <p> 
  9.         There was a problem loading this invoice 
  10.       </p> 
  11.     </div> 
  12.   ); 

ErrorBoundary 函數代表處理那些來自 loader 和 action,客戶端或服務端的非預期的錯誤,當出現這些非預期的錯誤時,就會激活這個函數,顯示對應函數的表示錯誤信息的 UI。

同時每個路由函數對應著一個 CatchBoundary 函數:

  1. import { useCatch } from "remix"
  2.  
  3. export function CatchBoundary() { 
  4.   let caught = useCatch(); 
  5.  
  6.   return ( 
  7.     <div> 
  8.       <h1>Caught</h1> 
  9.       <p>Status: {caught.status}</p> 
  10.       <pre> 
  11.         <code>{JSON.stringify(caught.data, null, 2)}</code> 
  12.       </pre> 
  13.     </div> 
  14.   ); 

CatchBoundary 函數對應著預期的錯誤,即你在 loader、action 函數中,在客戶端或服務端,手動拋出的 Response 錯誤,這些錯誤的路徑是可預期的,在 CatchBoundary 中,通過 useCatch 鉤子獲取這些拋出的 Response 錯誤,然后展示對于的錯誤信息的 UI。

當我們沒有在子路由中添加 ErrorBoundary 或 CatchBoundary 函數時,一旦遇到錯誤,這些錯誤就會向更上一級的路由冒泡,直至最頂層的路由頁面,所以你只最好在最頂層的路由文件里聲明一個 ErrorBoundary 和 CatchBoundary 函數,用于捕獲所有可能的錯誤,然后在代碼審查( Code Review)時及時排查出來。

Next.js 強勁對手來了! Remix 正式宣布開源

 基于 Web 基礎技術

Remix 專注于用 Web 基礎技術,HTML/CSS + HTTP 等解決問題,同時提供了在 Web 全棧開發框架中所需要的所有狀態和所有基礎組件。

其中相關狀態包含:

  1. // 加載數據的狀態 
  2. useLoaderData() 
  3.  
  4. // 更新數據的狀態 
  5. useActionData() 
  6.  
  7. // 提交表單等相關狀態 
  8. useFormAction() 
  9. useSubmit() 
  10.  
  11. // 統一的加載狀態 
  12. useTransition() 
  13.  
  14. // 錯誤抓取狀態等 
  15. useCatch() 

以及 Web 網站組成的基礎組件:

  • <Meta> 用于動態的設置網頁的元信息,方便 SEO
  • <Script> 用于告知 Remix 是否需要在加載網頁時導入相關 JS,因為大部分情況下 Remix 編寫的頁面無需 JS 也能正常工作
  • <Form> 用于替代原生的 <form> 方便在客戶端和服務端進行表單操作,接管提交時的相應功能,使用 Fetch API 發起請求等,以及處理多次重復提交的競爭狀態等

同時在路由函數所在文件里,可以通過聲明 link 、meta 、links 、headers 等函數來聲明對應的功能:

  • links 變量函數:表示此頁面需要加載的資源,如 CSS、圖片等
  1. import type { LinksFunction } from "remix"
  2. import stylesHref from "../styles/something.css"
  3.  
  4. export let links: LinksFunction = () => { 
  5.   return [ 
  6.     // add a favicon 
  7.     { 
  8.       rel: "icon"
  9.       href: "/favicon.png"
  10.       type: "image/png" 
  11.     }, 
  12.  
  13.     // add an external stylesheet 
  14.     { 
  15.       rel: "stylesheet"
  16.       href: "https://example.com/some/styles.css"
  17.       crossOrigin: "true" 
  18.     }, 
  19.  
  20.     // add a local stylesheet, remix will fingerprint the file name for 
  21.     // production caching 
  22.     { rel: "stylesheet", href: stylesHref }, 
  23.  
  24.     // prefetch an image into the browser cache that the user is likely to see 
  25.     // as they interact with this page, perhaps they click a button to reveal in 
  26.     // a summary/details element 
  27.     { 
  28.       rel: "prefetch"
  29.       as"image"
  30.       href: "/img/bunny.jpg" 
  31.     }, 
  32.  
  33.     // only prefetch it if they're on a bigger screen 
  34.     { 
  35.       rel: "prefetch"
  36.       as"image"
  37.       href: "/img/bunny.jpg"
  38.       media: "(min-width: 1000px)" 
  39.     } 
  40.   ]; 
  41. }; 
  • links 函數:聲明需要 Prefetch 的頁面,當用戶點擊之前就加載好資源
  1. export function links() { 
  2.   return [{ page: "/posts/public" }]; 
  • meta 函數:與 組件類似,聲明頁面需要的元信息
  1. import type { MetaFunction } from "remix"
  2.  
  3. export let meta: MetaFunction = () => { 
  4.   return { 
  5.     title: "Josie's Shake Shack", // <title>Josie's Shake Shack</title> 
  6.     description: "Delicious shakes", // <meta name="description" content="Delicious shakes"
  7.     "og:image""https://josiesshakeshack.com/logo.jpg" // <meta property="og:image" content="https://josiesshakeshack.com/logo.jpg"
  8.   }; 
  9. }; 
  • headers 函數:定義此頁面發送 HTTP 請求時,帶上的請求頭信息
  1. export function headers({ loaderHeaders, parentHeaders }) { 
  2.   return { 
  3.     "X-Stretchy-Pants""its for fun"
  4.     "Cache-Control""max-age=300, s-maxage=3600" 
  5.   }; 

由此可見,Remix 提供了整個全棧 Web 開發生命周期所需要的幾乎的一切內容,且內置最佳實踐,確保你付出很少的努力就能開發出性能卓越、體驗優秀的網站!

當然這篇文章并不能包含所有 Remix 的特性,看到這里仍然對 Remix 感興趣的同學可以訪問官網(https://remix.run/)詳細了解哦~ 官網提供了非常詳細的實戰教程幫助你使用 Remix 開發實際的應用。

了解了 Remix 的特性之后,你對 Remix 有什么看法呢?你覺得它能超過 Next.js?

【編輯推薦】https://mp.weixin.qq.com/s/_LHjkkupb-ZMLEI5RhANvg

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲精品一 | 思热99re视热频这里只精品 | 91精品国产高清一区二区三区 | 伊人亚洲| 欧美在线免费 | 99亚洲精品 | 亚洲综合色成在线播放 | 综合精品| 天天爱天天草 | 在线视频一区二区 | 亚洲国产精品一区二区久久 | 国产在线在线 | 99精品视频在线免费观看 | 国产毛片黄色片 | 国产精品初高中精品久久 | 国产精品欧美一区二区三区不卡 | 在线中文字幕第一页 | 欧美在线网站 | 久久高清片 | 91久久久久久久久久久久久 | 日韩中文字幕在线播放 | 国产成人精品一区二区 | 亚洲激情欧美 | 国产一区二区在线免费观看 | 91视频免费在线看 | 国产日韩一区二区三区 | 在线免费黄色网址 | 亚洲视频一区二区 | 欧美影| 精品在线91 | 欧美精品日韩精品 | 91春色 | 精品免费国产 | 日韩国产欧美亚洲 | 91av免费在线观看 | 久久蜜桃av一区二区天堂 | 天天操狠狠操 | 男人的天堂亚洲 | 亚洲国产aⅴ成人精品无吗 久久久91 | 亚洲一区二区在线免费观看 | 福利在线播放 |