数据写入

数据获取章节中,介绍了 Modern.js 获取数据的方式,你可能会遇到两个问题:

  1. 如何更新 Data Loader 返回的数据?
  2. 如何将新的数据传递到服务器?

在 Modern.js 中,可以通过 Data Action 解决和实现。

什么是 Data Action

Modern.js 推荐使用约定式路由做路由的管理,每个路由组件(layout.tspage.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

action 函数

loader 函数一样,action 函数有两个入参,paramsrequest

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

区别

你可以通过 useSubmituseFetcher 调用 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。
INFO

更多 API 的信息可参考相关文档:useSubmituseFetcher

数据类型

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

而在 Modern.js 中,我们希望通过 Loader 和 Action 帮助开发者自动的同步浏览器端和服务端的状态:

state manage

如果项目中组件共享的数据主要来自于服务端的状态,则无需在项目引入浏览器端状态管理库。可以使用 Data Loader 请求数据,通过 useRouteLoaderData 在子组件中共享数据,使用 Data Action 修改和同步服务端的状态。

CSR 和 SSR

与 Data Loader 一样,SSR 项目中,Data Action 是在服务端执行的(框架会自动发请求触发 Data Action),而在 CSR 项目中,Data Action 是在浏览器端执行的。