一、开始之前
💛本文适合对 Remix 感兴趣的同学
- Remix 基于服务端渲染的多页面,数据流与前后端分离的 spa 数据流有所不同。
- Remix 开发基于全栈开发,数据真实的修改操作基本通过表单在服务端完成。
- 基于 axios 等库,前后端分离项目,axios 在与服务器交互的过程中产生了重要的作用。
二、axios 的数据流
- 基于 axios 封装请求 api 请求函数。
- axios 获取数据一般在组件生命后周期的挂载完毕之后的阶段。
- axios 基于拦截器,在请求前和请求后对请求进行拦截处理,这些操作在前后端分离项目中都十分实用。
简单的封装:
ts
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://localhost:3000/api,
timeout: 5000,
});
instance.interceptors.request.use(
(config) => {
// 在请求发送之前做些什么,例如添加 headers、转换数据等
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => {
// 对响应数据做些什么,例如提取数据或处理错误等
return response.data;
},
(error) => {
// 对响应错误做些什么
return Promise.reject(error);
}
);
// 每个 API 对应一个函数
export const getPost = async (postId) => {
try {
const response = await instance.get(`/posts/${postId}`);
return response;
} catch (error) {
throw new Error(error.message);
}
};
export const getUser = async (userId) => {
try {
const response = await instance.get(`/users/${userId}`);
return response;
} catch (error) {
throw new Error(error.message);
}
};
以 React function component 为例:
ts
import React, { useEffect, useState } from 'react';
import { getPost, getUser } from './api';
const YourComponent = () => {
const [postData, setPostData] = useState(null);
const [userData, setUserData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const postResult = await getPost(1);
setPostData(postResult);
const userResult = await getUser(1);
setUserData(userResult);
} catch (err) {
setError(err.message);
}
};
fetchData();
}, []);
return (
<div>
{error && <p>发生错误: {error}</p>}
{postData && (
<div>
<h2>Post: {postData.title}</h2>
<p>{postData.body}</p>
</div>
)}
{userData && (
<div>
<h2>User: {userData.name}</h2>
<p>Email: {userData.email}</p>
</div>
)}
</div>
);
};
export default YourComponent;
三、Remix 数据流
Remix 提供的数据流,基于服务端渲染和表单提交:
- loader.useLoaderData 获取页面输出数据
- action/useActionData 用于表单提交
Remix 是一个基于 React SSR 的多页面全栈框架,可以在页面渲染之前获取数据,然后再提供给前端渲染页面内容,这样就不再需要 xhr/fetch/axios 等工具在 React 获取数据,并页面刷新 useLoaderData 会重新获取数据。
四、Remix 录入数据
Remix 提供了强大的表单能力, Remix 中表单需要包裹在 form 相关的组件中
表单组件
- 原生 form 组件
- Remix 内置 Form 组件
ts
import type { ActionFunctionArgs } from "@remix-run/node";
import { useFetcher } from "@remix-run/react";
import { Form } from "@remix-run/react";
import { json } from "@remix-run/node";
export const action = async ({ request }: ActionFunctionArgs) => {
const method = request.method;
const data = await request.formData();
const title = formData.get('title')
return json({title})
}
function NewEvent() {
return (
<Form action="/events" method="post">
<input name="title" type="text" />
</Form>
);
}
是 form 元素的包装器,在 url 修改和向历史堆栈中添加数据非常有用。
- useFetchers 的 Form 组件
ts
import type { ActionFunctionArgs } from "@remix-run/node";
export const action = async ({ request }: ActionFunctionArgs) => {
const method = request.method;
const data = await request.formData();
const data1 = formData.get('key')
return json({data1})
}
function SomeComponent() {
const fetcher = useFetcher();
return (
<fetcher.Form method="post" action="/some/route">
<input type="text" />
</fetcher.Form>
);
}
特点:没有导航的行为。
- 第三方 Form 组件
也可以在 antd Form/ProForm 中配合 onFinish 函数直接使用。
表单提交
- 默认提交
大多数情况下,对于简单的表单,可以直接提交 formData 即可,不需要额外的 js 代码吗,在 action 函数解析 POST 方法和 POST 对应的表单数据。
- useFetcher 提交
ts
import type { ActionFunctionArgs } from "@remix-run/node";
import { useFetcher } from "@remix-run/react";
import { Form } from "@remix-run/react";
import { json } from "@remix-run/node";
export const action = async ({ request }: ActionFunctionArgs) => {
const method = request.method;
const data = await request.formData();
const title = formData.get('title')
return json({title})
}
export function SomeComponent() {
const fetcher = useFetcher();
return <fetcher.Form method="post" action="/some/route">
<input name="title" type="text" />
</fetcher.Form>
}
- useSubmit 提交
ts
import { useSubmit } from "@remix-run/react";
function SomeComponent() {
const submit = useSubmit();
return (
<Form
onChange={(event) => {
submit(event.currentTarget);
}}
/>
);
}
提交 json
提交表单有一个重要的缺陷:如果数据有数组。获取到的表单数据解析需要额外的编译。
ts
import { useSubmit } from "@remix-run/react";
export const action = async ({ request }: ActionFunctionArgs) => {
const method = request.method;
const dataJson = await request.json();
return json({})
}
function SomeComponent() {
const submit = useSubmit();
return (
<Form
onChange={async (event) => {
await submit(values, { method: "POST", encType: "multipart/form-data" });
}}
/>
);
}
submit 函数中,需要配置 encType
为 multipart/form-data
。
五、小结
axios 符合单页面的操作流,但是在 Remix 依然可以用,Remix 中更加符合多页面的服务端渲染流,同时也减少了请求的封装。
这里绝对不是否定 Axios 的价值,axios 是一个前后端通用的请求库,其拦截器功能更是巧妙设计,在基于 Token 的授权设计中使用更为常见。
在 Remix 中对于路由的数据流有自己的处理方式,符合服务端渲染和表单提交的 web 规范。Remix 提交表单时存在深层数组的问题,此时 action 中获取表单数据并不好处理,提交时可以指定为 json 模式, 在 action 解析时,可以直接获取 json 数据,简化了表单的 formData 的获取等问题。