Your cart is currently empty!
2024 為什麼我開始學 REMIX 而不是 NEXT.JS?
這篇會介紹 Remix 的主要特色和入門知識,包含 Loader、Action、Outlet。
Reference: https://remix.run | https://nextjs.org
Why Remix?
Remix 的特點就是一個檔案就可以處理全端資料,Remix 會根據 function 名稱幫你決定他該在前端還是後端。以往建立一個 Application,我們必須建立 HTML/CSS/JavaScript 的前端頁面,不論是用 React/Vue/Angular 或其他函式庫或框架,然後另外寫後端 API 讓前端 Fetch,JavaScript 提供 Node.js,Python 有 Flask/Django,可能你也聽過 Java、Ruby、PHP,整個搞得好像很複雜需要會很多東西,但說到底就是一個人類軀殼(前端),每個動作都要腦袋(後端)去思考應該丟出什麼資料。哦不過 Remix 後端其實底子是 Express,我覺得他更像是幫你整合一些常用功能的東東。
以下可以看到一般進到網頁後會跑一些 Fetch 去取得資料,而 Remix 直接把整包丟給前端,全部一起出現,所以省略了前後端互動的等待時間。一般進入頁面會需要「前端觸發 > 後端回覆」,Remix 主要就是省去前端觸發的部分。
Server-Side Rendering
Remix 第一個優勢,所有的資料都是在後端處理好後直接一次丟給使用者,所以他們進到網頁後可以看到完整的畫面,在 SEO 部分也會加分,因為網頁更完整。當然也可以在前端 Fetch。
具體來說,Remix 的一個檔案會包含:
- Outlet(整個頁面 / Component 該長怎麼樣)
- Loader(取得資料之後丟給 Outlet)
- Action(處理使用者點擊 Form 觸發的 Post)
Outlet
每個 route 裡的檔案可以看成一個 React Component,必須要 export default function YourThing() {},在建立的時候他會被丟到 root.tsx 的 <Outlet />,或是他的上層,這個會在前端頁面顯示,如果有在寫 React 應該會看得很習慣。
// ./my-remix-app/app/route/home.tsx
// The default export is the component that will be
// rendered when a route matches the URL. This runs
// both on the server and the client
export default function Projects() {
return (
<div>
{/* outlets render the nested child routes
that match the URL deeper than this route,
allowing each layout to co-locate the UI and
controller code in the same file */}
<Outlet />
</div>
);
}
Loader
Loader 會處理跟後端互動的東西,然後 return 資料,在 function YourThing() {} 裡面可以呼叫 useLoaderData() 取得他 return 的東西。這邊可以直接寫跟資料庫相關的程式碼,而不需要另外寫個 API Route 或文件,不過通常還是會做一個完整的 DB 互動程式碼文件統一管理,只是它就不會是一個 Route,而只是一個 Function 的統整文件。
// ./my-remix-app/app/route/home.tsx
// Loaders only run on the server and provide data
// to your component on GET requests
// 這個 loader function 會在後端執行
export async function loader() {
return json(await db.projects.findAll());
}
export default function Projects() {
const projects = useLoaderData<typeof loader>();
return (
<div>
{projects.map((project) => (
<Link key={project.slug} to={project.slug}>
{project.title}
</Link>
))}
// ...Outlet 的東西
</div>
);
}
Action
Action 其實就是處理 Post 來的資料,跟 Loader 一樣是在後端執行,return 的東東可以在 function YourThing() {} 裡面可以呼叫 useActionData()。他跟基本的 HTML Form 其實是一樣的,所以即使無法使用 JavaScript,頁面還是可以正常執行。
// ./my-remix-app/app/route/home.tsx
export default function Projects() {
const actionData = useActionData<typeof action>();
return (
<div>
// ...Loader 的東東
<Form method="post">
<input name="title" />
<button type="submit">Create New Project</button>
</Form>
{actionData?.errors ? (
<ErrorMessages errors={actionData.errors} />
) : null}
// Outlet 的東東
</div>
);
}
// Actions only run on the server and handle POST
// PUT, PATCH, and DELETE. They can also provide data
// to the component
export async function action({
request,
}: ActionFunctionArgs) {
const form = await request.formData();
const errors = validate(form);
if (errors) {
return json({ errors });
}
await createProject({ title: form.get("title") });
return json({ ok: true });
}
Conclusion
雖然這個真的讓之前的前後端東東變得很方便簡單,不過目前缺點就是相關的資料跟生態圈很小,而且是真的蠻簡單的,可能會少了一些可玩性,所以如果你害怕看 Documents 或擔心求職的話,建議還是直接學 Next 哦!
整個檔案就長這樣(https://remix.run/docs/en/main/discussion/introduction#http-handler-and-adapters):
// ./my-remix-app/app/route/home.tsx
// Loaders only run on the server and provide data
// to your component on GET requests
export async function loader() {
return json(await db.projects.findAll());
}
// The default export is the component that will be
// rendered when a route matches the URL. This runs
// both on the server and the client
export default function Projects() {
const projects = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
return (
<div>
{projects.map((project) => (
<Link key={project.slug} to={project.slug}>
{project.title}
</Link>
))}
<Form method="post">
<input name="title" />
<button type="submit">Create New Project</button>
</Form>
{actionData?.errors ? (
<ErrorMessages errors={actionData.errors} />
) : null}
{/* outlets render the nested child routes
that match the URL deeper than this route,
allowing each layout to co-locate the UI and
controller code in the same file */}
<Outlet />
</div>
);
}
// Actions only run on the server and handle POST
// PUT, PATCH, and DELETE. They can also provide data
// to the component
export async function action({
request,
}: ActionFunctionArgs) {
const form = await request.formData();
const errors = validate(form);
if (errors) {
return json({ errors });
}
await createProject({ title: form.get("title") });
return json({ ok: true });
}