For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt, and this page is available as Markdown at /zh/guides/basic-features/data/data-write.md.
  • 简体中文
  • 数据写入

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

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

    在 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 = async ({ 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 函数有两个入参,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.text() 等。具体使用哪个 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。
    Info

    更多 API 的信息可参考相关文档:useSubmit、useFetcher。

    数据类型

    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 是在浏览器端执行的。