上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

【Remix教程翻译】开发者博客(四)

[复制链接]
  • TA的每日心情
    开心
    2023-6-5 00:59
  • 签到天数: 4 天

    [LV.2]偶尔看看I

    9

    主题

    42

    回帖

    233

    积分

    管理员

    积分
    233

    管理员油中3周年

    发表于 2022-12-7 23:05:24 | 显示全部楼层 | 阅读模式

    动态路由参数

    我们现在来创建一个实际的路由来进行帖子的查看,我们应该让以下URL可用
    /posts/my-first-post
    /posts/90s-mixtape
    我们可以在url中使用路径地址匹配,而不是为每一个动态路径创建一个路由地址,Remix将解析并且传递给我们内容,方便我们访问动态地址可以阅览帖子。
    我们现在访问到app/routes/posts/创建一个$slug.tsx
    然后贴入以下内容

    export default function PostSlug() {
      return (
        <main className="mx-auto max-w-4xl">
          <h1 className="my-6 border-b-2 text-center text-3xl">
            Some Post
          </h1>
        </main>
      );
    }

    可以看到目前已经有了内容
    图片.png
    然后添加loader函数的代码来获取参数
    注意,这里的loader是服务器的函数,只由服务端来执行
    如果在服务端执行渲染,则不会发送请求
    如果在客户端执行渲染,则会发送到服务端请求对应的参数
    另外我们可以观察一下laoder函数的签名LoaderFunction

    /**
     * A function that loads data for a route.
     */
    export interface LoaderFunction {
        (args: DataFunctionArgs): Promise<Response> | Response | Promise<AppData> | AppData;
    }
    export interface DataFunctionArgs {
        request: Request;
        context: AppLoadContext;
        params: Params;
    }

    可以看到,函数参数是一个对象,包含了请求,上下文以及参数,而返回推荐是一个Promise,Appdata是any类型,代表我们也可以返回一个任意类型。
    现在我们通过slug来获取帖子的内容
    在post文件里增加一个getPost函数
    修改app/models/post.server.ts

    import { prisma } from "~/db.server";
    
    export async function getPosts() {
      return prisma.post.findMany();
    }
    export async function getPost(slug: string) {
      return prisma.post.findUnique({ where: { slug } });
    }
    

    tips

    如果看到 TypeScript 警告,例如TS2305: Module '"@prisma/client"' has no exported member 'Post'.,尝试重新启动编辑器

    我们接下来继续尝试编写slug.tsc代码

    import type { LoaderFunction } from "@remix-run/node";
    import { json } from "@remix-run/node";
    import { useLoaderData } from "@remix-run/react";
    import { getPost } from "~/models/post.server";
    export const loader: LoaderFunction = async ({ params }) => {
      const post = await getPost(params.slug!);
      return json({ post });
    };
    export default function PostSlug() {
      const { post } = useLoaderData();
      return (
        <main className="mx-auto max-w-4xl">
          <h1 className="my-6 border-b-2 text-center text-3xl">{post.title}</h1>
        </main>
      );
    }
    

    这个时候已经可以从服务器来拉取数据了
    图片.png
    现在我们补充一下Typescript的类型
    我们修改一下$slug的代码
    注意要在app\models\post.server.ts中导出一下Post的类型

    export type { Post } from "@prisma/client";

    然后就可以

    import type { LoaderFunction } from "@remix-run/node";
    import { json } from "@remix-run/node";
    import { useLoaderData } from "@remix-run/react";
    import invariant from "tiny-invariant";
    
    import type { Post } from "~/models/post.server";
    import { getPost } from "~/models/post.server";
    
    type LoaderData = { post: Post };
    
    export const loader: LoaderFunction = async ({ params }) => {
      invariant(params.slug, `params.slug is required`);
    
      const post = await getPost(params.slug);
      invariant(post, `Post not found: ${params.slug}`);
    
      return json<LoaderData>({ post });
    };
    
    export default function PostSlug() {
      const { post } = useLoaderData<LoaderData>();
      return (
        <main className="mx-auto max-w-4xl">
          <h1 className="my-6 border-b-2 text-center text-3xl">{post.title}</h1>
        </main>
      );
    }
    

    有几点需要注意
    invariant是为了判断是否目前条件为真,我们可以看一下他的代码
    可以看到就是判断一下条件,如果不符合就throw new Error一下,触发异常,这时候返回的页面就也会显示一个异常的页面了

        var isProduction = process.env.NODE_ENV === 'production';
        var prefix = 'Invariant failed';
        function invariant(condition, message) {
            if (condition) {
                return;
            }
            if (isProduction) {
                throw new Error(prefix);
            }
            var provided = typeof message === 'function' ? message() : message;
            var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
            throw new Error(value);
        }

    图片.png
    但是为什么加上这个函数Ts就不报错了?
    我们去看一下他的定义

    export default function invariant(condition: any, message?: string | (() => string)): asserts condition;

    可以看到有一个asserts condition
    这个是TS的类型条件断言
    如果condition传入的条件为真,则继续执行,并且类型会判断为通过该条件的类型,如果为假则报出异常
    我们在这里首先params.slug的类型为string|undefined,通过第一个的时候则完全变成了string,然后开始搜寻文章,通过第二个断言的时候判断是否存在返回内容,如果不存在则也进行断言~
    其次是useLoaderData必须使用泛型传参,不再可以根据教程里使用as,如果使用as则必须通过as any as xxx做双重断言才可以正确设置类型,具体原因涉及类型推断,理解使用即可。

    const { post } = useLoaderData<LoaderData>();

    我们已经使用了invariant来处理post,以后我们将更好的处理404页面,让我们继续
    我们现在将Markdown解析为html,有很多mk解析器,这里我们选择了较为上手的marked
    输入

    npm add marked
    如果需要Ts类型提醒则再安装
    npm add @types/marked  -D

    然后重新运行npm run dev
    编写slug.tsx代码
    还是比较简单的,就不叙述了
    这里的dangerouslySetInnerHTML是innerHTML的替代品
    为了提醒开发者这个属性只能传入安全的数据,防止XSS注入

    import type { LoaderFunction } from "@remix-run/node";
    import { marked } from "marked";
    import { json } from "@remix-run/node";
    import { useLoaderData } from "@remix-run/react";
    import invariant from "tiny-invariant";
    
    import type { Post } from "~/models/post.server";
    import { getPost } from "~/models/post.server";
    
    type LoaderData = { post: Post; html: string };
    
    export const loader: LoaderFunction = async ({ params }) => {
      invariant(params.slug, `params.slug  is  required`);
    
      const post = await getPost(params.slug);
      invariant(post, `Post  not  found:  ${params.slug}`);
      const html = marked(post.markdown);
    
      return json<LoaderData>({ post, html });
    };
    
    export default function PostSlug() {
      const { post, html } = useLoaderData<LoaderData>();
      return (
        <main className="mx-auto  max-w-4xl">
          <h1 className="my-6  border-b-2  text-center  text-3xl">{post.title}</h1>
          <div dangerouslySetInnerHTML={{ __html: html }} />
        </main>
      );
    }
    

    那么我们就完成了最基本的博客创建

    结语

    撒花~

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表