給習慣寫 CSR 服務的 remix 入門注意事項
提示:這不是一篇關於 remix 的手把手教學筆記,而是一個過去只寫過 client side render 服務的前端工程師在改用 remix 開發後的心得分享。如果你完全不知道 remix 是什麼,請先參考此框架的新手教學。
大前提
推薦使用 remix 的大前提是你能控制伺服器,你寫好的服務可以部署到後端。如果你的服務只能做成 client side render 的形式,請改用 remix 的 SPA Mode 開發。
via official document: from the beginning, Remix’s opinion has always been that you own your server architecture. While we believe that having a server provides the best UX/Performance/SEO/etc. for most apps, it is also undeniable that there exist plenty of valid use cases for a Single Page Application in the real world.
套件
如果你需要安裝第三方套件,請確認該套件是否能在對應的環境中執行。舉例:如果你需要在 loader()
/action()
裡呼叫某個套件,該套件就要能在 Node.js 環境中運作。另一方面,任何會用到 window
/document
的套件與功能(比如 localStorage
),請一律在 React.useEffect
中執行。
關於 GA4
一開始我參考 google-analytics 在 root.tsx
的 Layout()
設定了 GA4 的 script 標籤,但測試後確定事件都沒有被派送(明明 window.dataLayer
跟 window.gtag
都不是 undefined
🥲)。
目前的解決辦法:在 React.useEffect
中透過套件 react-ga4 來啟動 GA4。
資料流
大原則:從
loader()
取資料,在元件(畫面)中使用loader()
提供的資料;觸發action()
後(比如使用者送出訂單),再根據action()
造成的改變更新元件(畫面)。細節可參考官方文件 Fullstack Data Flow。
所有「取得資料」的工作(呼叫 api/DB 等等)都由 loader()
負責,詳細可參考官方文件 Data Loading。
如果需要全域變數,可以從 root.tsx
的 loader()
傳入,再搭配 useRouteLoaderData
於特定部位取用。但要注意 loader()
吐出來的資料都是序列化的內容。需要全域功能或任何要「全域共用,但不能被序列化的資料」可以考慮用 <Outlet>
的 props.context
搭配 useOutletContext
來實現。
環境變數請根據你部署服務的後端來設定,參考 Environment Variables。這部分比傳統的 client side render 服務簡單很多。
remix 有提供 meta 功能方便開發者設定每一條路由的 <meta>
資訊。要注意的是:如果 meta()
需要用到 Node.js 環境的資料,要從 loader()
餵進去;另外,此功能雖然不在後端,但也無法操作 window
/document
等瀏覽器物件 🌚
網路狀態
非常簡單易懂,只有三種:idle
/ submitting
/ loading
。發生的順序可參考以下整理自 useNavigation — navigation.state 的懶人包:
- HTTP
GET
與一般瀏覽的狀態切換為idle
→loading
→idle
- HTTP
POST
/PUT
/PATCH
/DELETE
的狀態切換為idle
→submitting
→loading
→idle
以下是自用的 custom hook:
import { useNavigation } from '@remix-run/react';
import { useMemo } from 'react';
type NavState = {
/** Means NO navigation pending */
isIdle: boolean;
/** A route action is triggered by <Form> submission using `POST/PUT/PATCH/DELETE` */
isSubmitting: boolean;
/** The loaders for the next routes are called, next page is going to be rendered */
isLoading: boolean;
};
export default function useNavState() {
const { state } = useNavigation();
return useMemo<NavState>(
() => ({
isIdle: state === 'idle',
isSubmitting: state === 'submitting',
isLoading: state === 'loading',
}),
[state]
);
}
打包
只要你能將服務部署到後端上,用 remix 打包就很簡單,直接跑 remix vite:build
就好。