阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!

使用 prisma 操作 db 會像這樣

Create

export async function action({ request }) {
  const formData = await request.formData();
  const title = formData.get("title");
  const badge_text = formData.get("badge_text");

  const errors = {};
  if (!title) {
    errors.title = "Title is required.";
  }

  if (errors && Object.keys(errors).length > 0) {
    return json(errors, { status: 400 });
  }

  const { session } = await authenticate.admin(request);

  const shop = session.shop;

  const randomId = [...crypto.getRandomValues(new Uint8Array(10))]
    .map((x) => (x % 36).toString(36))
    .join("");

  await prisma.recommendationSet.create({
    data: {
      id: randomId,
      shop,
      title,
      createdAt: new Date(),
      updatedAt: new Date(),
    },
  });

  return redirect("/app?success=1");
}

Read

export async function loader({ request }) {
  const { session } = await authenticate.admin(request);

  const shop = session.shop;

  const sets = await prisma.recommendationSet.findMany({
    where: { shop },
    orderBy: { createdAt: "desc" },
  });

  return {
    plan: "free",
    sets: sets.map((set) => ({
      id: set.id,
      title: set.title,
      badge_text: "tbd",
      count: 0,
      createdAt: set.createdAt,
      updatedAt: set.updatedAt,
    })),
  };
}

Update

export async function loader({ params, request }) {
  const { id } = params;

  await authenticate.admin(request);

  const { session } = await authenticate.admin(request);

  const shop = session.shop;

  const set = await prisma.recommendationSet.findUnique({
    where: { shop, id },
  });

  return set;
}

export async function action({ request, params }) {
  const formData = await request.formData();
  const title = formData.get("title");
  const badge_text = formData.get("badge_text");
  const id = params.id;

  const errors = {};
  if (!title) {
    errors.title = "Title is required.";
  }

  if (errors && Object.keys(errors).length > 0) {
    return json(errors, { status: 400 });
  }

  const { session } = await authenticate.admin(request);

  const shop = session.shop;

  await prisma.recommendationSet.update({
    data: {
      title,
    },
    where: {
      id,
      shop,
    },
  });

  return redirect("/app?success=1");
}

Delete

export async function action({ request }) {
  const formData = await request.formData();
  const id = formData.get("id");

  await authenticate.admin(request);
  const { session } = await authenticate.admin(request);

  await prisma.recommendationSet.delete({
    where: {
      id,
      shop: session.shop,
    },
  });

  return redirect("/app?success=1");
}

操作 prisma 是還好 跟常見的 ORM 差不多就是那樣

比較需要思考的是 remix 是用 loader + action 來整合 ssr + csr 流程的開發體驗

是還不錯 但就是多一層抽象化

因此 雖然 action 是接收表單送出 但不是原生 html form 也不是 polaris form

而是有額外做好一些參數與設定的 remix react form

import { Form } from "@remix-run/react";

再來 如果不是全頁送出、會讓網址改變的 http post form submit

而是部份送出 類似 ajax 又想觸發 action 的話 要用 fetcher

const fetcher = useFetcher();
<RecommendationList
  sets={sets}
  onEdit={(id) => navigate(`/app/sets/${id}`)}
  onDelete={(id) => {
    fetcher.submit({ id }, { method: "post" });
  }}
/>

因為是 remix 獨創的 ssr + csr 解決方案 所以傳統的 http get / http post / form submit / ajax 觀念不夠用

需要多學 remix 的 action / loader / fetcher 觀念

屬於必要之惡吧!用多一層抽象化來解決

還算優雅 但就是要學習成本 多一點認知負擔


除此之外 在做連結的時候 還需要小心別搞錯這兩個

import { Link } from "@shopify/polaris";
import { Link } from "@remix-run/react";

因為 admin panel 的 app 是包在 iframe 裡面

會有 hmac host id_token locale session shop timestamp 等等參數

基本上想在 iframe 裡面點連結 去影響整個 admin panel 這很複雜

經典意義上的 a 元素不可能自己做 要支援「右鍵開新分頁」更困難

我決定都先用 navigate() 來做導航就好了 先求簡單、先求穩

import { useNavigate } from "@remix-run/react";
const navigate = useNavigate();

最後就是 我做了簡單的 loading spinner 或稱 pending state 或稱 busy state

因為我對這部分的 ux 很在意

要多學 remix 的 navigation 與 fetcher 觀念

再次強調

總之就是 remix 獨創的 ssr + csr 解決方案

所以傳統的 http get / http post / form submit / ajax 觀念不夠用

export default function App() {
  const { apiKey } = useLoaderData();

  const navigation = useNavigation();
  const fetchers = useFetchers();

  const isNavigationLoading = navigation.state !== "idle";

  const isFetcherLoading = fetchers.some(
    (f) => f.state !== "idle" && f.state !== "uninitialized",
  );

  const isLoading = isNavigationLoading || isFetcherLoading;

  return (
    <AppProvider isEmbeddedApp apiKey={apiKey}>
      {isLoading && (
        <>
          <Backdrop />
          <div
            style={{
              position: "fixed",
              top: 0,
              left: 0,
              width: "100vw",
              height: "100vh",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              zIndex: 9999,
              backgroundColor: "rgba(255,255,255,0.3)", // 淡淡遮罩
            }}
          >
            <Spinner size="large" />
          </div>
        </>
      )}

      <NavMenu>
        <Link to="/app" rel="home">
          Home
        </Link>
        <Link to="/app/additional">Additional page</Link>
        <Link to="/app/contact">Contact Me</Link>
      </NavMenu>
      <Outlet />
    </AppProvider>
  );
}

簡單做一下,效果非常好!


老話一句

shopify app 開發

我覺得門檻真的很高~


共有 0 則留言


👉 身份:資深全端工程師、指導過無數人半路出家轉職 👉 使命:打造 CodeLove 成為優質新手村,讓非本科也有地方自學&討論

阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!