你無法這樣做 😅。在客戶端轉換期間,為了讓你的應用程式盡可能快速,Remix 會並行呼叫你的所有載入器,在獨立的 fetch 請求中進行。它們每一個都需要有自己的身份驗證檢查。
這可能與你使用 Remix 之前所做的沒有什麼不同,現在可能只是更明顯了。在 Remix 之外,當你對你的「API 路由」進行多次 fetch 時,每個端點都需要驗證使用者會話。換句話說,Remix 路由載入器是它們自己的「API 路由」,必須這樣對待它們。
我們建議你建立一個驗證使用者會話的函式,可以將其添加到任何需要它的路由中。
import {
createCookieSessionStorage,
redirect,
} from "@remix-run/node"; // or cloudflare/deno
// somewhere you've got a session storage
const { getSession } = createCookieSessionStorage();
export async function requireUserSession(request) {
// get the session
const cookie = request.headers.get("cookie");
const session = await getSession(cookie);
// validate the session, `userId` is just an example, use whatever value you
// put in the session when the user authenticated
if (!session.has("userId")) {
// if there is no user session, redirect to login
throw redirect("/login");
}
return session;
}
現在,在任何需要使用者會話的載入器或動作中,你都可以呼叫該函式。
export async function loader({
request,
}: LoaderFunctionArgs) {
// if the user isn't authenticated, this will redirect to login
const session = await requireUserSession(request);
// otherwise the code continues to execute
const projects = await fakeDb.projects.scan({
userId: session.get("userId"),
});
return json(projects);
}
即使你不需要會話資訊,該函式仍然會保護路由
export async function loader({
request,
}: LoaderFunctionArgs) {
await requireUserSession(request);
// continue
}
在 HTML 中,表單可以使用 action 屬性發送到任何 URL,並且應用程式將導航到那裡
<Form action="/some/where" />
在 Remix 中,動作預設為呈現表單的路由,使其易於共同放置 UI 和處理它的伺服器程式碼。開發人員經常想知道如何在這種情況下處理多個動作。你有兩個選擇
我們發現選項 (1) 最簡單,因為你無需為了將驗證錯誤傳回 UI 而使用會話。
HTML 按鈕可以發送一個值,因此這是實現此目的最簡單的方法
export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const intent = formData.get("intent");
switch (intent) {
case "update": {
// do your update
return updateProjectName(formData.get("name"));
}
case "delete": {
// do your delete
return deleteStuff(formData);
}
default: {
throw new Error("Unexpected action");
}
}
}
export default function Projects() {
const project = useLoaderData<typeof loader>();
return (
<>
<h2>Update Project</h2>
<Form method="post">
<label>
Project name:{" "}
<input
type="text"
name="name"
defaultValue={project.name}
/>
</label>
<button type="submit" name="intent" value="update">
Update
</button>
</Form>
<Form method="post">
<button type="submit" name="intent" value="delete">
Delete
</button>
</Form>
</>
);
}
如果你習慣使用 application/json
的內容類型執行 fetch,你可能會想知道表單如何適應這個。 FormData
與 JSON 有點不同。
如果你只是想發送結構化資料以發佈陣列,你可以在多個輸入上使用相同的鍵
<Form method="post">
<p>Select the categories for this video:</p>
<label>
<input type="checkbox" name="category" value="comedy" />{" "}
Comedy
</label>
<label>
<input type="checkbox" name="category" value="music" />{" "}
Music
</label>
<label>
<input type="checkbox" name="category" value="howto" />{" "}
How-To
</label>
</Form>
每個核取方塊都有名稱:「category」。由於 FormData
可以在同一個鍵上有多個值,因此你不需要 JSON 來執行此操作。在你的動作中使用 formData.getAll()
存取核取方塊的值。
export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const categories = formData.getAll("category");
// ["comedy", "music"]
}
使用相同的輸入名稱和 formData.getAll()
可以涵蓋大多數在表單中提交結構化資料的情況。
如果您仍然想提交巢狀結構,可以使用非標準的表單欄位命名慣例,以及來自 npm 的 query-string
套件。
<>
// arrays with []
<input name="category[]" value="comedy" />
<input name="category[]" value="comedy" />
// nested structures parentKey[childKey]
<input name="user[name]" value="Ryan" />
</>
然後在您的 action 中
import queryString from "query-string";
// in your action:
export async function action({
request,
}: ActionFunctionArgs) {
// use `request.text()`, not `request.formData` to get the form data as a url
// encoded form query string
const formQueryString = await request.text();
// parse it into an object
const obj = queryString.parse(formQueryString);
}
有些人甚至會將他們的 JSON 傾印到一個隱藏的欄位中。請注意,這種方法不適用於漸進式增強。如果這對您的應用程式來說不重要,這是一種發送結構化資料的簡單方法。
<input
type="hidden"
name="json"
value={JSON.stringify(obj)}
/>
然後在 action 中解析它
export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const obj = JSON.parse(formData.get("json"));
}
再次強調,formData.getAll()
通常已足夠您使用,我們鼓勵您嘗試看看!