logo
  • 指南
  • 配置
  • 插件
  • API
  • 示例
  • 社区
  • Modern.js 2.x 文档
  • 简体中文
    • 简体中文
    • English
    • 开始
      介绍
      快速上手
      版本升级
      名词解释
      技术栈
      核心概念
      页面入口
      构建工具
      Web 服务器
      基础功能
      路由
      路由基础
      配置式路由
      数据管理
      数据获取
      数据写入
      数据缓存
      渲染
      服务端渲染(SSR)
      服务端流式渲染(Streaming SSR)
      渲染缓存
      静态站点生成(SSG)
      渲染预处理 (Render Preprocessing)
      样式开发
      引入 CSS
      使用 CSS Modules
      使用 CSS-in-JS
      使用 Tailwind CSS
      HTML 模板
      引用静态资源
      引用 JSON 文件
      引用 SVG 资源
      引用 Wasm 资源
      调试
      数据模拟(Mock)
      网络代理
      使用 Rsdoctor
      使用 Storybook
      测试
      Playwright
      Vitest
      Jest
      Cypress
      路径别名
      环境变量
      构建产物目录
      部署应用
      进阶功能
      使用 Rspack
      使用 BFF
      基础用法
      运行时框架
      扩展 BFF Server
      扩展一体化调用 SDK
      文件上传
      跨项目调用
      优化页面性能
      代码分割
      静态资源内联
      产物体积优化
      React Compiler
      提升构建性能
      浏览器兼容性
      配置底层工具
      源码构建模式
      服务端监控
      Monitors
      日志事件
      指标事件
      国际化
      基础概念
      快速开始
      配置说明
      语言检测
      资源加载
      路由集成
      API 参考
      高级用法
      最佳实践
      自定义 Web Server
      专题详解
      模块联邦
      简介
      开始使用
      应用级别模块
      服务端渲染
      部署
      集成国际化能力
      常见问题
      依赖安装问题
      命令行问题
      构建相关问题
      热更新问题
      已下线功能
      📝 编辑此页面
      上一页数据获取下一页数据缓存

      #数据写入

      在数据获取章节中,介绍了 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 = ({ 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.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。
      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 是在浏览器端执行的。