admin 发表于 2022-12-7 23:05:24

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

#动态路由参数
我们现在来创建一个实际的路由来进行帖子的查看,我们应该让以下URL可用
/posts/my-first-post
/posts/90s-mixtape
我们可以在url中使用路径地址匹配,而不是为每一个动态路径创建一个路由地址,Remix将解析并且传递给我们内容,方便我们访问动态地址可以阅览帖子。
我们现在访问到app/routes/posts/创建一个$slug.tsx
然后贴入以下内容
```js
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](data/attachment/forum/202212/07/213134rxzxkuekeukx2xkx.png)
然后添加loader函数的代码来获取参数
注意,这里的loader是服务器的函数,只由服务端来执行
如果在服务端执行渲染,则不会发送请求
如果在客户端执行渲染,则会发送到服务端请求对应的参数
另外我们可以观察一下laoder函数的签名LoaderFunction
```js
/**
* 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
```js
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"'hasnoexportedmember'Post'.,尝试重新启动编辑器

我们接下来继续尝试编写slug.tsc代码
```js
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](data/attachment/forum/202212/07/215208q2xtutto2hzg2kxt.png)
现在我们补充一下Typescript的类型
我们修改一下$slug的代码
注意要在app\models\post.server.ts中导出一下Post的类型
```js
export type { Post } from "@prisma/client";
```
然后就可以
```js
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是为了判断是否目前条件为真,我们可以看一下他的代码
可以看到就是判断一下条件,如果不符合就thrownewError一下,触发异常,这时候返回的页面就也会显示一个异常的页面了
```js
    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](data/attachment/forum/202212/07/220024ja31969qmea61u6i.png)
但是为什么加上这个函数Ts就不报错了?
我们去看一下他的定义
```js
export default function invariant(condition: any, message?: string | (() => string)): asserts condition;
```
可以看到有一个assertscondition
这个是TS的类型条件断言
如果condition传入的条件为真,则继续执行,并且类型会判断为通过该条件的类型,如果为假则报出异常
我们在这里首先params.slug的类型为string|undefined,通过第一个的时候则完全变成了string,然后开始搜寻文章,通过第二个断言的时候判断是否存在返回内容,如果不存在则也进行断言~
其次是useLoaderData必须使用泛型传参,不再可以根据教程里使用as,如果使用as则必须通过asanyasxxx做双重断言才可以正确设置类型,具体原因涉及类型推断,理解使用即可。
```
const { post } = useLoaderData<LoaderData>();
```
我们已经使用了invariant来处理post,以后我们将更好的处理404页面,让我们继续
我们现在将Markdown解析为html,有很多mk解析器,这里我们选择了较为上手的marked
输入
```
npm add marked
如果需要Ts类型提醒则再安装
npm add @types/marked-D
```
然后重新运行npmrundev
编写slug.tsx代码
还是比较简单的,就不叙述了
这里的dangerouslySetInnerHTML是innerHTML的替代品
为了提醒开发者这个属性只能传入安全的数据,防止XSS注入

```js
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.slugisrequired`);

const post = await getPost(params.slug);
invariant(post, `Postnotfound:${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-automax-w-4xl">
      <h1 className="my-6border-b-2text-centertext-3xl">{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </main>
);
}


```
那么我们就完成了最基本的博客创建
#结语
撒花~
页: [1]
查看完整版本: 【Remix教程翻译】开发者博客(四)