数据写入
在数据获取章节中,介绍了 Modern.js 获取数据的方式,你可能会遇到两个问题:
- 如何更新 Data Loader 返回的数据?
- 如何将新的数据传递到服务器?
在 Modern.js 中,可以通过 Data Action 解决和实现。
什么是 Data Action
Modern.js 推荐使用约定式路由做路由的管理,每个路由组件(layout.ts
,page.ts
或 $.tsx
)都可以有一个同名的 .data
文件。这些文件可以导出一个 action
函数,我们称为 Data Action,开发者可以在合适的时机调用它:
.
└── routes
└── user
├── layout.tsx
└── layout.data.ts
在 routes/user/layout.data.ts
文件中,可以导出一个 Data Action 函数:
routes/user/layout.data.ts
import type { ActionFunction } from '@modern-js/runtime/router';
export const action: ActionFunction = ({ request }) => {
const newUser = await request.json();
const name = newUser.name;
return updateUserProfile(name);
}
在路由组件中,你可以通过 useFetcher
获取对应 submit
函数,执行并触发 Data Action:
routes/user/layout.tsx
import {
useFetcher,
useLoaderData,
useParams,
Outlet
} from '@modern-js/runtime/router';
export default () => {
const userInfo = useLoaderData();
const { submit } = useFetcher();
const editUser = () => {
const newUser = {
name: 'Modern.js'
}
return submit(newUser, {
method: 'post',
encType: 'application/json',
})
}
return (
<div>
<button onClick={editUser}>edit user</button>
<div className="user-profile">
{userInfo}
</div>
<Outlet context={userInfo}></Outlet>
</div>
)
}
当执行 submit
后,会触发定义的 action
函数。在 action
函数中,可以通过入参获取提交的数据,最终发送数据到服务端。
action
函数执行完后,Modern.js 将自动执行 loader
函数代码,并更新对应的数据和视图。
![action flow](https://lf3-static.bytednsdoc.com/obj/eden-cn/ulkl/ljhwZthlaukjlkulzlp/action-flow.png)
action
函数
与 loader
函数一样,action
函数有两个入参,params
和 request
。
params
params
是当路由为动态路由时动态路由片段,会作为参数传入 action
函数:
routes/user/[id]/page.data.ts
import { ActionFunctionArgs } from '@modern-js/runtime/router';
// 访问 /user/123 时,函数的参数为 `{ params: { id: '123' } }`
export const action = async ({ params }: ActionFunctionArgs) => {
const { id } = params;
const res = await fetch(`https://api/user/${id}`);
return res.json();
};
request
request
是一个 Fetch Request 实例。通过 request
,可以在 action 函数中获取到浏览器端提交的数据,如 request.json()
,request.formData()
,request.json()
等。具体使用哪个 API,请参考数据类型。
routes/user/[id]/page.data.ts
import { ActionFunctionArgs } from '@modern-js/runtime/router';
export const action = async ({ request }: ActionFunctionArgs) => {
const newUser = await request.json();
return updateUser(newUser);
};
返回值
action
函数的返回值可以是任何可序列化的内容,也可以是一个 Fetch Response 实例,
可以通过 useActionData
访问 response 中内容。
useSubmit 和 useFetcher
区别
你可以通过 useSubmit
或 useFetcher
调用 Data Action。它们的区别是通过
useSubmit
调用,会触发浏览器的导航,通过 useFetcher
则不会触发浏览器的导航。
useSubmit:
const submit = useSubmit();
submit(null, { method: "post", action: "/logout" });
useFetcher:
const { submit } = useFetcher();
submit(null, { method: "post", action: "/logout" });
submit
函数有两个入参,第一个入参是传递到服务端的 formData
。第二个入参是可选参数,其中
method
相当于表单提交时的 method
,大部分写入数据的场景下,method
可以传入 post
。
action
用来指定触发哪个路由组件的 Data Action,如果未传入 action
入参,默认会触发当前路由组件的 action,即 user/page.tsx
组件或子组件中执行 submit,会触发 user/page.data.ts
中定义的 action。
数据类型
submit
函数的第一个入参,可以接受不同类型的值。
如 FormData
:
let formData = new FormData();
formData.append("cheese", "gouda");
submit(formData);
// In the action, you can get the data by request.json
或 URLSearchParams
类型的值:
let searchParams = new URLSearchParams();
searchParams.append("cheese", "gouda");
submit(searchParams);
// In the action, you can get the data by request.json
或任意 URLSearchParams
构造函数可接受的值:
submit("cheese=gouda&toasted=yes");
submit([
["cheese", "gouda"],
["toasted", "yes"],
]);
// In the action, you can get the data by request.json
默认情况下,如果 submit
函数中的第一个入参是一个对象,对应的数据会被 encode 为 formData
:
submit(
{ key: "value" },
{
method: "post",
encType: "application/x-www-form-urlencoded",
}
);
// In the action, you can get the data by request.formData
你也可以通过第二个参数,指定为编码方式:
submit(
{ key: "value" },
{ method: "post", encType: "application/json" }
);
submit('{"key":"value"}', {
method: "post",
encType: "application/json",
});
// In the action, you can get the data by request.json
或提交纯文本:
submit("value", { method: "post", encType: "text/plain" });
// In the action, you can get the data by request.text
为什么要提供 Data Action
Modern.js 中提供 Data Action 主要是为了使 UI 和服务器的状态能保持同步,通过这种方式可以减少状态管理的负担,
传统的状态管理方式,会在浏览器端和远程分别持有状态:
![traditional state manage](https://lf3-static.bytednsdoc.com/obj/eden-cn/ulkl/ljhwZthlaukjlkulzlp/action-state-manage.png)
而在 Modern.js 中,我们希望通过 Loader 和 Action 帮助开发者自动的同步浏览器端和服务端的状态:
![state manage](https://lf3-static.bytednsdoc.com/obj/eden-cn/ulkl/ljhwZthlaukjlkulzlp/action-state-manage1.png)
如果项目中组件共享的数据主要来自于服务端的状态,则无需在项目引入浏览器端状态管理库。可以使用 Data Loader 请求数据,通过 useRouteLoaderData
在子组件中共享数据,使用 Data Action 修改和同步服务端的状态。
CSR 和 SSR
与 Data Loader 一样,SSR 项目中,Data Action 是在服务端执行的(框架会自动发请求触发 Data Action),而在 CSR 项目中,Data Action 是在浏览器端执行的。