06 - Mutating Data - Deep Dive

06 - Mutating Data - Deep Dive
06 - Mutating Data - Deep Dive
type
Post
status
Published
date
Dec 22, 2025
slug
nextjs-006
summary
06 - Mutating Data - Deep Dive
tags
Next.js
category
编程学习
icon
password
😀

001 Module Introduction

  • 回顾前序章节数据获取内容:提及在之前的章节中,学习者已掌握使用Next.js获取数据的方法,包括通过服务器端发送HTTP请求从外部源获取数据,以及通过服务器端代码直接从数据库获取数据这两种方式。
  • 本章节核心内容介绍:本章节将聚焦数据的修改,探索如何更改数据并存储新数据,因为多数网络应用程序不仅需要获取数据,还需在特定时刻更改或添加数据。
  • 本章节学习目标:学习者将了解使用Next.js时操作数据的不同选项,其中最重要的是学习如何使用“服务器操作”功能,在Next.js中实现数据的操作和更新。

002 Starting Project & Analyzing Mutation Options

一、项目初始准备与环境搭建

  1. 项目获取与启动
      • 下载后需执行npm install安装所有依赖项,再通过npm run dev启动开发服务器。
      • 启动后将得到一个简易应用,支持查看帖子与添加新帖子,核心功能聚焦“添加帖子”的数据突变操作。
  1. 初始项目结构与数据库配置
      • 项目包含lib文件夹,其中posts.js文件不仅有新帖子存储到数据库的代码,还包含数据库初始化逻辑,每次启动应用会自动运行,确保所需表(用户表、帖子表、喜欢表)存在。
      • 使用SQLite数据库存储数据,初始设置两个假用户供本节课程使用,身份验证与用户管理功能将在后续课程讲解。

二、表单提交与数据突变方案分析

(一)三种核心方案对比

方案
实现方式
适用场景
优缺点
外部API方案
设置独立后端API,接收外域数据请求并存储到数据库
适用于多客户端(如移动应用、网页端)需共享数据源的场景
优点:符合传统前后端分离架构;缺点:需额外维护独立项目,本课程场景下冗余,不推荐
路由处理程序方案
app文件夹下创建api文件夹(如api/posts),添加route.js文件并导出post函数,处理对/api/posts的POST请求
需为非Next.js客户端(如第三方应用)提供数据提交接口时
优点:可灵活对接多客户端;缺点:仅Next.js应用使用时无需额外创建,非最优解
服务器行动(推荐)
直接在Next.js应用中集成表单提交与数据存储逻辑,无需额外API或项目
仅构建Next.js应用,需高效处理数据提交场景
优点:Next.js核心功能,契合课程定位,无需额外维护成本,集成度高;缺点:仅限Next.js生态内使用

(二)推荐方案:服务器行动核心优势

  1. 无需脱离Next.js应用架构,直接将数据处理逻辑嵌入项目,减少开发与维护成本。
  1. 后续课程将基于此方案展开,配合useFormStatus(管理提交状态)、useFormState(处理表单状态与验证)等Hooks实现完整功能。

003 Setting Up A Form Action - 表单处理

一、核心概念辨析:表单操作(Form Action)vs 服务器操作(Server Action)

1. 归属与依赖关系

特性
表单操作(Form Action)
服务器操作(Server Action)
底层归属
React 内置功能(录制时未稳定,后续将通用)
React 内置功能,但需 Next.js 等框架“解锁”使用
适用环境
未来支持所有 React 项目(含纯客户端项目)
仅支持 Next.js 等框架环境,纯客户端 React 不适用
核心定位
客户端表单提交处理基础
基于表单操作,拓展服务器端数据处理能力

2. 核心差异

  • 表单操作:仅在客户端执行,收集数据后需手动通过 fetch 发送到后端(如 API 路由),适合简单客户端交互。
  • 服务器操作:在服务器端执行,可直接操作数据库(如 SQLite、MongoDB),无需手动转发数据,是修改数据源的“最佳实践”。

二、表单操作(Form Action)核心用法

1. 基础实现步骤

  1. 定义表单处理函数:函数名自定义(如 createPost),自动接收 FormData 对象作为参数。
    1. 绑定到表单 action 属性:将函数作为值传递给 <form>action 属性,不执行函数(不加括号)

      2. 关键注意点

      • FormData 对象用法
        • 网页标准对象(非 React 特有),通过 get(name) 提取输入值,依赖输入项的 name 属性(必须设置,否则无法获取数据)。
        • 支持文件类型,可直接提取 input[type="file"] 选择的文件,后续可结合服务器操作上传存储。
      • 浏览器默认行为拦截: React 接管 action 属性后,会阻止浏览器“自动发送请求到 URL”的默认行为,转而触发自定义函数。
      • 兼容性问题:录制视频时,React 表单操作功能尚未稳定,部分版本可能报错,需注意版本匹配(官方文档示例可参考,但实际需以项目 React 版本为准)。

      三、从表单操作到服务器操作的过渡

      1. 客户端表单操作的局限性

      • 仅能在客户端收集数据,若需修改数据库(如存储表单内容),仍需手动通过 fetch 发送请求到后端(如 Next.js 的 API 路由或外部服务)。
      • 数据传输过程需额外处理,安全性和效率不如直接在服务器端操作。

      2. 服务器操作的优势与意义

      • 直接操作数据源:在服务器端执行函数,可直接连接数据库(如 SQLite、MongoDB),无需客户端转发,减少数据传输环节。
      • 简化流程:基于表单操作的基础逻辑,仅需调整函数执行环境(从客户端到服务器端),无需重构表单结构。
      • 后续扩展方向:视频后续将讲解服务器操作的具体实现,包括文件上传到安全存储(如 AWS S3)、服务器端输入验证等。

      四、常见问题与注意事项

      1. 报错处理:若保存表单操作代码后出现“需使用服务器功能”的错误,大概率是 React 版本不支持(录制时功能未稳定),可升级 React 或直接过渡到服务器操作。
      1. name 属性必要性:所有需提取值的输入项(inputtextareaselect 等)必须设置 name 属性,否则 FormData.get(name) 无法获取数据。
      1. 函数绑定误区:绑定 action 时需传递函数引用(如 action={createPost}),不可写成 action={createPost()}(会立即执行函数,而非提交时触发)。

      004 Creating a Server Action

      一、核心概念与作用

      • 定义:Server Action 是 Next.js 中用于在服务器端处理表单提交、数据修改等操作的函数,仅在服务器执行,避免客户端暴露敏感逻辑(如数据验证、数据库操作)。
      • 核心价值:替代传统客户端表单提交(需手动发请求、处理数据),由 Next.js 自动管理请求发送、表单数据传递,简化全栈开发流程。

      二、创建服务器动作的关键步骤

      1. 标记 use server 指令(必做)

      • 位置:必须在函数内部顶部添加(非函数外部)。
      • 作用:告知 React 和 Next.js 该函数是服务器操作,而非客户端函数(类似 use client 标记客户端组件,但服务器组件是默认状态,无需额外标记)。
      • 错误提示:若遗漏该指令,函数会被默认当作客户端函数,执行时会触发错误。

      2. 声明为异步函数(必做)

      • 要求:必须在函数前添加 async 关键字,使函数返回 Promise。
      • 原因:服务器操作涉及网络请求(如表单提交到服务器)、数据库交互等异步任务,Next.js 要求其为异步函数以处理异步逻辑。
      • 错误提示:若未加 async,刷新页面会报错“服务器操作必须是异步函数”。

      三、Next.js 对服务器动作的自动处理

      1. 请求触发:表单提交时,Next.js 自动在后台向服务器发送请求,触发对应的 Server Action 函数,无需开发者手动写 fetchaxios 请求。
      1. 数据传递:自动将表单数据(如输入框内容、上传文件)附加到请求中,开发者可直接在 Server Action 中提取使用。
      1. 执行环境:Server Action 仅在服务器端执行,客户端无法获取函数内部代码,保障数据安全(如数据库密钥、验证逻辑不会暴露)。

      四、验证服务器动作的执行效果

      1. 无错误启动:添加 use serverasync 后,重新加载页面无报错,说明服务器动作配置正确。
      1. 终端查看输出:提交表单后(无 UI 反馈,需手动验证),在启动开发服务器的终端中可看到表单数据(如输入文本、上传文件信息)的打印结果,证明服务器动作已执行且数据提取成功。
      1. 核心依据:终端输出属于服务器端日志,仅当代码在服务器执行时才会显示,可作为 Server Action 生效的直接证据。

      五、后续扩展方向

      • 当前示例仅实现“提取表单数据并打印到控制台”,后续需补充数据存储逻辑(如将表单数据存入数据库,如视频中提及的 SQLite 等),完成完整的表单提交-数据持久化流程。

      005 Storing Data in Databases - 数据库数据存储

      一、核心功能与数据要求

      1. 存储帖子函数:项目内已创建storePost函数(位于lib/posts.js),用于将数据存入数据库,调用时需传入包含以下4个必要字段的帖子对象:
          • 图像URL(image URL)
          • 标题(title)
          • 内容(content)
          • 用户ID(user ID)
      1. 函数调用场景:在新帖子页面的JS文件中,需调用storePost函数传递帖子对象,替代原有的控制台输出数据逻辑。

      二、关键数据的临时处理方案

      数据字段
      现状问题
      临时解决方案
      后续计划
      图像URL
      未实现图片存储,无法获取存储后的文件链接
      空字符串作为回退值,当前帖子无图片显示
      后续补充图片存储功能,获取真实文件链接
      用户ID
      未开发登录机制,无法关联真实用户
      固定使用用户ID=1创建帖子,预留用户ID=2用于后续点赞功能
      课程后期添加真实身份验证系统

      三、数据存储验证与原理

      1. 验证方式:提交表单后,无即时反馈,但可通过feed页面查看结果——页面会展示除图片外的所有帖子数据(标题、内容等),证明数据已存入数据库。
      1. 数据获取逻辑feed文件夹的page.js文件通过async/await调用getPosts方法(同样来自lib/posts.js),该方法连接数据库并获取数据,实现数据库数据的前端渲染。

      四、后续优化方向

      1. 表单提交反馈:添加加载状态(隐藏按钮+显示加载文本),避免用户重复提交。
      1. 自动导航:数据提交成功后,自动跳转至feed页面,提升用户体验。

      006 Providing User Feedback with The useFormStatus Hook - useFormStatus 钩子应用

      一、核心功能目标

      在表单交互中实现两大关键功能:1. 表单提交完成后重定向用户;2. 提交过程中显示加载反馈(替换提交按钮)。

      二、表单提交后重定向实现

      1. 等待异步操作:确保等待存储数据的 POST 操作完成,该操作含模拟慢服务器的延迟(1秒后解析承诺),避免重定向提前触发。
      1. 导入并使用 redirect 函数
          • 来源:从 next/navigation 包导入,不可从其他路径导入。
          • 用法:在服务器操作中调用,传入目标路径字符串(如 /feed),即可实现重定向。

      三、useFormStatus 钩子核心应用

      1. 钩子基础信息

      • 归属:由 react-dom 包提供(非 Next.js 专属),用于获取表单提交状态。
      • 依赖条件:使用该钩子的组件必须嵌套在 <form> 标签内部,否则无法生效。

      2. 组件创建与配置

      • 单独组件文件:在 components 文件夹新建组件(如 FormSubmit.js),避免整体页面变为客户端组件。
      • 标记客户端组件:在组件顶部添加 'use client' 指令,因 useFormStatus 仅支持客户端组件,不添加会报错。
      • 组件内容迁移:将原表单中的提交按钮剪切到新组件,用 fragment<>)包裹(适配多兄弟元素场景),再在原表单中引入该组件替代按钮。

      3. 状态对象与提交反馈

      • 获取状态对象:在组件函数中调用 useFormStatus(),返回包含表单提交状态的对象,核心属性为 pending(布尔值)。
      • pending 属性作用true 表示表单正在提交,false 表示未提交或提交完成。
      • 条件渲染逻辑:根据 pending 值切换显示内容——提交中(pending: true)显示加载文本(如“创建帖子”),非提交状态(pending: false)显示原提交按钮,实现提交过程的用户反馈。

      四、关键注意事项

      1. useFormStatus 组件必须在 <form> 标签内使用,否则无法获取表单状态。
      1. 重定向函数需等待 POST 异步操作完成,防止数据未存储就跳转。
      1. 客户端组件需明确添加 'use client' 指令,避免钩子使用报错。

      007 Using useFormState Hook

      In the next lecture, we'll use a React DOM Hook called useFormState.在下一讲中,我们将使用一个名为useFormState的React DOM钩子。
      For some unknown reason, the React team decided to suddenly change this Hook's name to useActionState. In addition, it now must be imported from react instead of react-dom.出于某种未知原因,React 团队决定突然将这个 Hook 的名称改为 useActionState。此外,现在它必须从 react 而不是 react-dom 中导入。
      So when I write this code in the next lecture所以当我在下一讲中编写这段代码时
      you should write this code (and then use useActionState() instead of useFormState() in any other place where I use it):你应该编写这段代码(然后在我使用它的任何其他地方用useActionState()代替useFormState()):

      008 Validating User Input With Help Of The useFormState Hook -实现用户输入验证 - 核心笔记

      一、表单验证的必要性与分层验证逻辑

      1. 前端基础验证(辅助性,非绝对安全)

      • 实现方式:为表单元素(输入框、文本域等)添加required属性,浏览器会自动检测空字段并显示错误提示。
      • 作用:快速提升用户体验,初步拦截无效提交,无需额外代码即可实现基础校验。
      • 局限性:用户可通过浏览器开发工具删除required属性,绕过验证提交空表单,因此仅作为辅助手段,不能替代核心验证。

      2. 服务器端验证(核心安全层,必须实现)

      • 验证逻辑:在服务器操作(如表单提交的处理函数)中,针对关键字段(如标题、内容、图片)做严格校验:
        • 检查字段是否存在(如undefined);
        • 检查字段内容是否为空(如修剪空格后长度为0)。
      • 错误处理
        • 创建错误消息数组,符合校验失败条件时添加对应提示(例:“标题是必需的”“内容为必填”“图片为必填”);
        • 若错误数组长度>0,返回包含该数组的对象到客户端,用于更新DOM展示错误。
      • 核心原因:服务器是用户无法篡改代码的安全区域,是拦截无效数据的最终保障。

      二、useFormState Hook 核心用法

      1. 基本介绍与导入

      • 功能:React 提供的表单状态管理钩子,用于接收服务器操作返回的数据(如错误信息),并更新表单相关UI,实现“提交-校验-反馈”的闭环。
      • 导入路径:从react-dom导入(非react),代码示例:import { useFormState } from 'react-dom'

      2. 调用场景与参数

      • 调用位置:在直接渲染表单的组件中调用,不可在嵌套组件(如表单内部子组件)中使用。
      • 两个核心参数
        • 参数顺序
          参数类型
          说明
          第一个
          表单动作函数
          表单提交时触发的服务器操作(如createPost),仅传递函数引用,不执行
          第二个
          初始表单状态
          表单未提交时的默认状态,示例中为{}(空对象),因初始无错误需展示

      3. 返回值与使用

      • 返回值结构:与useState类似,返回包含两个元素的数组:
          1. 当前表单状态:初始为传递的初始状态,表单提交后更新为服务器操作返回的数据(如错误数组);
          1. 更新后的表单动作:React 监听该动作,自动捕获其返回数据并更新表单状态,需作为表单action属性的值。
      • 关键代码逻辑示例

        三、使用注意事项

        1. 客户端组件限制useFormState仅能在客户端组件中使用,需在组件文件顶部添加'use client'指令声明。
        1. 避免服务器代码冲突:客户端组件中不能使用带useServer装饰的服务器代码(会导致冲突),需通过代码重构解决(如将服务器操作抽离到单独文件)。
        1. 状态用途:示例中仅用该Hook处理错误状态,若表单提交成功会直接导航到其他页面,无需返回额外状态。
         

        009 Adjusting Server Actions for useFormState

        一、核心场景与目标

        解决 Next.js 项目中表单状态管理(useFormState)与 Server Action 交互的兼容性问题,实现表单逻辑解耦、正确参数传递及错误反馈,确保服务器端验证有效落地。

        二、关键操作步骤与原理

        1. 组件拆分:提取独立表单组件

        • 操作目的:将表单状态逻辑与页面渲染逻辑解耦,避免服务器组件与客户端组件混用冲突
        • 具体步骤
            1. 新建独立组件文件(如 PostForm.js),从原页面文件(如 new-post.js)中迁移以下内容:
                • 表单渲染相关 JSX 代码
                • useFormState 钩子及表单状态管理逻辑
            1. 补充依赖:在新组件顶部添加缺失的导入(useFormState、表单提交组件)及 "use client" 指令(标记为客户端组件)

        2. Server Action 传递规则

        • 核心原则:Server Action 不可在客户端组件中定义,但可从服务器组件通过 props 传递给客户端组件
        • 实现方式
            1. PostForm 组件中定义 action prop,用于接收 Server Action
            1. 在 useFormState 钩子中使用该 prop(useFormState(action, initialState)
            1. 原页面文件(服务器组件)中,渲染 PostForm 时传入 Server Action(如 action={createPostAction}

        3. Server Action 参数顺序调整(关键修复点)

        • 问题现象:表单提交时报错“外来的数据获取不是函数”
        • 根本原因:使用 useFormState 后,Server Action 参数顺序改变:
          • 未使用 useFormState
            使用 useFormState
            第一个参数:表单数据
            第一个参数:之前的表单状态
            -
            第二个参数:提交的表单数据
        • 修复代码逻辑

          4. 表单错误反馈实现

          • 问题点:Server 端返回的错误未在页面显示,因未使用 useFormState 管理的 state 对象
          • 解决方案
              1. 利用 useFormState 返回的 state 对象(内置 errors 属性,存储错误数组)
              1. 在表单底部添加动态错误渲染逻辑:

            5. 图片字段额外验证

            • 特殊场景:未选择图片时无错误提示,因图片字段即使空值也会返回空对象(而非 undefined
            • 验证逻辑补充

              三、核心知识点总结

              知识点分类
              关键内容
              组件划分规则
              客户端组件需添加 "use client" 指令,负责表单交互;服务器组件定义 Server Action,避免客户端直接定义
              Server Action 传递
              仅支持“服务器组件定义 → 客户端组件通过 props 接收”的单向传递
              useFormState 特性
              1. 管理表单状态与 Server Action 交互<br>2. 改变 Server Action 参数顺序(prevState → formData)<br>3. 返回包含 errors 的状态对象,用于错误反馈
              服务器端验证
              需覆盖特殊字段(如图片)的边界情况,确保验证逻辑完整性

              010 Storing Server Actions In Separate Files - 服务器动作(Server Actions)单独文件存储

              一、核心背景与优势

              1. 现有方案局限:在组件函数内直接定义服务器动作本身无语法错误,但当组件需使用"use client"指令(如实现客户端交互功能)时,需拆分出独立组件(如帖子表单组件),增加代码冗余。
              1. 单独存储优势:将服务器动作剥离到独立文件,可避免不必要的组件拆分,简化代码结构,同时提升复杂服务器动作的可维护性(尤其代码量增多时)。

              二、关键操作步骤

              1. 搭建文件结构

              • 创建文件夹:在 Next.js 项目根目录新建文件夹(名称、位置无强制要求,推荐命名为actions,便于识别)。
              • 创建文件:在actions文件夹内新建文件(如posts.js,文件名自定义),核心要求:在文件顶部添加"use server"指令(区别于组件内定义时在函数内部添加)。

              2. 迁移服务器动作

              操作要点
              具体说明
              移除旧指令
              删除原组件函数中服务器动作内的"use server"指令(因文件顶部已全局声明)。
              增加导出关键字
              为迁移到新文件的服务器动作函数添加export,确保外部组件可导入使用(如export async function createPost(...))。
              迁移依赖导入
              将原组件中服务器动作依赖的导入语句(如重定向函数redirect、数据存储相关模块),剪切到新文件的"use server"指令下方,保证函数正常运行。

              3. 多动作支持

              单个独立文件(如posts.js)可存放多个服务器动作,无需按动作拆分文件,降低文件管理成本。

              三、组件适配与效果

              1. 组件简化:从actions文件夹导入目标服务器动作(如createPost)后,原页面组件(如新帖子页面)代码会更简洁,可将此前拆分的表单标记、逻辑重新整合回该组件。
              1. 组件类型转换:因页面组件不再内置服务器动作,可按需添加"use client"指令转换为客户端组件(满足更多客户端交互需求,视频中未实际操作,但技术上可行)。
              1. 功能一致性:代码结构调整后,表单提交等核心功能与原方案完全一致,仅优化代码组织方式,无功能损耗。

              四、适用场景

              1. 服务器动作逻辑复杂、代码量较大时,独立文件存储便于维护和修改。
              1. 组件需作为客户端组件(需"use client"),但又依赖服务器动作时,避免组件拆分的最优方案。

              011 use server Does Not Guarantee Server-side Execution!

              In the previous lectures, you saw a lot of usage of the "use server" directive.在之前的课程中,你看到了很多`"use server"`指令的用法。
              What's very important to understand about this directive is the following: It's just "telling" NextJS that something should become a server action. I.e., that NextJS should send requests to that function behind the scenes (kind of).关于这个指令,有一点非常重要需要理解:它只是“告诉”NextJS某样东西应该成为一个服务器操作。也就是说,NextJS应该在后台(某种程度上)向该函数发送请求。
              "use server" does not mean or guarantee that the code will only execute on the server! Whilst that will be the case for server actions, you can't rely on the usage of "use server" to "hide code" from the client!"use server"并不意味着或保证代码只会在服务器上执行!虽然服务器操作确实是这种情况,但你不能依赖"use server"来向客户端“隐藏代码”
              If you have code that must never end up on the client-side (no matter if it's a server action or not), you should instead use the server-only package as described here.如果你有绝对不能出现在客户端的代码(无论它是不是服务器操作),你应该改用server-only包,如此处所述。

              012 Uploading & Storing Images - 图片上传与存储(基于Cloudinary)

              一、核心前提:图片存储方案选择

              1. Next.js本地存储局限性:项目公共文件夹(public)仅在开发环境可临时存储图片,生产环境下图片无法访问,因此必须依赖外部存储服务。
              1. 推荐存储服务:优先选择支持免费计划的服务,如Cloudinary(本案例使用)、AWS等,满足轻量开发需求且无需额外付费。

              二、前期准备:环境与依赖配置

              1. Cloudinary账户准备

              • 免费注册Cloudinary账户(官网链接),后续需从账户中获取关键配置信息。

              2. 安装依赖包

              停止当前Next.js开发服务器,在项目根目录执行命令:
              作用:引入Cloudinary官方SDK,实现图片上传功能。

              3. 核心文件创建与配置

              文件路径
              作用
              关键细节
              lib/cloudinary.js
              封装图片上传到Cloudinary的逻辑
              1. 默认上传至“next js课程突变”文件夹(可自定义名称) 2. 需读取环境变量完成账户关联
              根目录.env.local
              存储Cloudinary敏感配置(Next.js会自动识别该文件中的环境变量)
              需配置3个变量: 1. CLOUDINARY_CLOUD_NAME(云名称) 2. CLOUDINARY_API_KEY(API密钥) 3. CLOUDINARY_API_SECRET(API密钥)

              4. 环境变量值获取

              登录Cloudinary账户,在“控制台-账户设置”中找到对应值,替换.env.local中的占位符,确保使用个人账户信息(避免使用演示者的默认值)。

              5. 重启服务

              配置完成后重启Next.js开发服务器,确保Cloudinary相关代码可正常加载。

              三、核心逻辑:集成图片上传到业务流程

              1. 代码集成位置

              posts.js动作文件中,将帖子存储到数据库之前,插入图片上传逻辑(确保图片上传成功后再存储帖子数据)。

              2. 关键步骤

              1. 导入上传函数:从lib/cloudinary.js中导入封装好的图片上传函数。
              1. 调用上传函数:将前端获取的图片文件传递给上传函数,该函数返回Promise,最终 resolve 为图片的可访问URL(用于页面显示)。
              1. 错误处理:用try-catch包裹上传过程,捕获上传失败异常,抛出自定义错误: 错误会通过Next.js的error.js文件在前端页面显示,提升用户体验。
                1. 存储图片URL到数据库
                    • try块中定义的imageUrl变量改为块外定义(避免块级作用域限制),确保上传成功后可传递给storePost函数。
                    • 调用storePost时,将imageUrl与帖子标题、内容等数据一起存入数据库。

                四、功能验证:测试与确认

                1. 清除旧数据

                删除项目中的post.db文件(清除历史帖子数据),刷新页面后Next.js会自动重建空数据库,确保测试环境干净。

                2. 前端测试流程

                1. 前端表单选择图片,输入帖子标题(如“美食分享”)和内容(如“在Pit Masters餐厅享受的美味的炸薯条”)。
                1. 点击“发布帖子”,页面会因图片上传耗时短暂加载,完成后重定向到帖子列表页。
                1. 列表页中若能正常显示上传的图片,说明前端渲染与数据库存储均正常。

                3. Cloudinary后台验证

                登录Cloudinary账户,进入“媒体库-next js课程突变”文件夹(自定义名称则对应修改),可看到自动生成唯一文件名的上传图片,确认图片已成功存储。

                五、关键注意事项

                1. 环境变量安全.env.local包含敏感信息,需添加到.gitignore中,避免提交到代码仓库。
                1. 错误处理完整性:除图片上传外,可对storePost(存储帖子到数据库)也添加try-catch,处理数据库存储失败场景。
                1. 生产环境适配:Cloudinary免费计划满足开发与轻量生产需求,若用户量增长,需根据需求升级Cloudinary套餐或切换其他存储服务。

                013 Alternative Ways of Using, Configuring & Triggering Server Actions - 帖子点赞功能开发

                一、功能核心目标与数据库设计

                1. 功能目标:实现帖子点赞/取消点赞功能,点击按钮可标记或取消标记帖子为“喜欢”,且每个用户对单篇帖子仅能点赞一次
                1. 数据库设计
                    • 已预设likes表,存储user_id(用户ID)与post_id(帖子ID)的组合,通过唯一约束确保“单用户单帖单次点赞”规则
                    • 帖子查询逻辑:检索帖子时,会判断特定用户是否已点赞该帖子,为返回数据添加isLiked布尔属性(true表示已点赞,false表示未点赞)
                    • 临时处理:因未实现用户登录功能,暂硬编码用户ID为2,后续会补充用户认证相关逻辑

                二、Server Action 创建(核心业务逻辑)

                1. 文件位置:在actions文件夹下的posts.js文件中定义Server Action
                1. 函数实现
                  1. 作用:作为客户端与数据库的中间层,处理点赞状态变更的服务器端逻辑,避免客户端直接操作数据库

                  三、Server Action 触发方式(两种方案)

                  触发方案
                  实现步骤
                  适用场景
                  方案1:按钮绑定formAction
                  1. 在点赞按钮元素上设置formAction属性 2. 在Post组件中传递togglePostLikeStatus函数给按钮 3. 点击按钮时直接触发Server Action
                  单个按钮独立触发操作,无需包裹表单
                  方案2:表单包裹按钮(推荐)
                  1. 用<form>标签包裹点赞按钮 2. 在表单上设置action属性,绑定togglePostLikeStatus函数 3. 点击按钮时提交表单,间接触发Server Action
                  与现有表单逻辑(如新建帖子表单)保持一致,兼容性更强

                  四、关键问题解决:传递帖子ID参数

                  1. 问题:直接将togglePostLikeStatus作为表单action时,函数无法自动获取帖子ID,导致数据库操作缺少关键参数
                  1. 解决方案:使用JavaScript的bind方法预配置参数
                    1. 原理bind方法会返回新函数,预填充第一个参数为帖子ID,后续表单提交时函数可直接使用该参数

                    五、UI状态同步:条件样式显示

                    1. 需求:根据isLiked属性(帖子是否已点赞)改变按钮外观,提升用户体验
                    1. 实现步骤
                        • 基于post.isLiked属性,为表单或按钮添加条件CSS类:若已点赞,添加liked类;否则不添加
                          • 样式支持:起始项目的全局CSS中已预设liked类对应的样式规则(如改变按钮颜色、图标等)

                      六、当前功能不足与优化方向

                      1. 现存问题:点击点赞/取消点赞后,页面不会实时更新isLiked状态,需手动刷新页面才能看到变化,用户体验较差
                      1. 优化方向(后续需实现)
                          • 方案1:使用useFormStateuseFormStatus钩子,监听Server Action执行状态,手动更新客户端UI
                          • 方案2:采用“乐观更新”策略,点击按钮时先更新本地UI,再等待Server Action返回结果,失败时回滚状态
                          • 方案3:通过revalidatePathrevalidateTag触发页面数据重新获取,实现状态同步

                      014 Revalidating Data To Avoid Caching Problems - 数据重新验证(避免缓存问题)

                      一、核心背景:Next.js 默认缓存机制的问题

                      1. 缓存特性:Next.js 默认会“积极缓存”页面数据,以提升加载性能
                      1. 核心缺陷:无法自动检测数据库中数据的变更,即使数据库内容更新,页面仍会默认展示基于旧数据的缓存版本,导致用户无法实时看到最新数据
                      1. 场景示例:用户操作(如切换帖子点赞状态)后,数据库已更新,但页面不刷新则无法显示新状态

                      二、关键解决方案:revalidatePath 函数

                      1. 基础信息

                      • 作用:主动告知 Next.js 特定页面数据已变更,触发页面重新验证(删除旧缓存、生成更新版本)
                      • 导入要求:必须从 next/cache 模块导入,语法为 import { revalidatePath } from 'next/cache'
                      • 核心目标:解决“缓存页面与数据库数据不同步”的问题,确保用户访问时能获取最新数据

                      2. 配置与使用方式

                      使用场景
                      配置方式
                      效果说明
                      指定单个/多个路由
                      传入具体路由路径(如 revalidatePath('/feed')
                      用户访问该路由(如 /feed)时,Next.js 后台自动更新页面并展示最新版本
                      全量页面重新验证
                      传入根路径 '/' + 第二个参数 { type: 'layout' }(即 revalidatePath('/', { type: 'layout' })
                      所有由“根布局”包裹的页面(通常是整个应用的页面)均被重新验证
                      动态路由/复杂场景
                      参考 Next.js 官方文档配置
                      支持动态路由段、嵌套路由等复杂路径的精准重新验证

                      3. 工作机制

                      1. 调用 revalidatePath 并指定路径后,Next.js 会标记该路径下的页面为“需重新验证”
                      1. 当用户访问被标记的页面时,Next.js 会在后台重新获取最新数据、生成新页面
                      1. 无需用户手动刷新,页面会自动展示更新后的内容

                      三、当前方案的局限性

                      • 存在延迟:即使使用 revalidatePath,从“数据变更”到“页面展示新数据”仍有时间差,无法实现“实时同步”
                      • 非终极方案:该函数仅解决“缓存不更新”问题,还需结合其他优化手段(如乐观更新)进一步提升用户体验

                      四、补充建议

                      1. 文档参考:配置细节(如动态路由、嵌套路径的重新验证)需查阅 Next.js 官方 revalidatePath 文档
                      1. 使用时机:数据变更场景(如添加新帖子、修改用户信息、切换状态)后,必须调用该函数触发重新验证
                      1. 后续优化:可结合“乐观更新”(先在前端临时展示新状态,再同步后端)减少用户感知的延迟

                      015 Performing Optimistic Updates With NextJS - 乐观更新(Optimistic Updates)

                      一、核心概念:乐观更新

                      1. 定义:一种提升用户体验的前端更新模式,核心是“先更新UI,后同步服务器”——假设服务器更新会成功,在发起服务器请求前先立即更新客户端页面状态,待服务器处理完成后再同步最终结果;若服务器更新失败,则回滚客户端状态。
                      1. 价值:避免用户等待服务器响应时的“无反馈”状态,让操作(如点赞、切换状态)即时生效,提升交互流畅度。
                      1. 技术依赖:Next.js 中借助 React 内置钩子 useOptimistic 实现,无需额外引入第三方库。

                      二、核心技术:useOptimistic 钩子使用步骤

                      1. 前置准备:导入钩子与声明客户端组件

                      • 导入钩子:在使用乐观更新的组件文件(如 posts.js)顶部,从 React 导入 useOptimistic
                        • 声明客户端组件:因 useOptimistic 仅支持客户端组件,需在文件顶部添加 "use client" 指令(若服务器动作定义在单独文件中,此声明不影响服务器逻辑):

                          2. 调用 useOptimistic:参数与返回值

                          useOptimistic 接收 2个输入参数,返回 2个输出值,格式如下:
                          类型
                          名称
                          说明
                          输入参数1
                          initialState
                          初始状态(从数据库获取的原始数据,如帖子列表 posts 数组)
                          输入参数2
                          updateFn
                          客户端更新逻辑函数,接收 oldState(旧状态)和 updateData(更新所需数据,如帖子ID),返回新的乐观状态
                          返回值1
                          optimisticState
                          乐观更新后的状态(如 optimisticPosts,用于渲染UI,替代原始 posts
                          返回值2
                          triggerOptimisticUpdate
                          触发乐观更新的函数,调用时传入 updateData(如帖子ID),触发 updateFn 执行

                          3. 编写 updateFn:客户端更新逻辑

                          以“切换帖子点赞状态”为例,updateFn 需实现以下逻辑:
                          1. 找到目标数据索引:通过 findIndex 从旧帖子数组中匹配需要更新的帖子(根据帖子ID);
                          1. 处理“未找到数据”场景:若索引为 1(未找到帖子),返回旧数组,避免报错;
                          1. 不可变更新数据:通过“复制旧对象+修改属性”的方式更新目标帖子(避免直接修改原数组,符合 React 状态不可变原则);
                          1. 返回新状态:创建包含更新后帖子的新数组,作为乐观状态返回。
                          代码示例:

                          4. 触发乐观更新:绑定动作函数

                          创建实际触发更新的函数(如 updatePost),关联“乐观更新”与“服务器动作”:
                          1. 调用 triggerOptimisticUpdate 触发客户端即时更新;
                          1. 调用服务器动作(如 togglePostLike)同步数据库状态;
                          1. 添加 async 关键字支持 await(Next.js 允许 async 函数作为客户端操作)。
                          代码示例:

                          5. 渲染乐观状态:替换原始数据

                          在组件渲染时,使用 optimisticState(如 optimisticPosts)替代原始的 initialState(如 posts),确保UI展示即时更新后的状态:

                          三、关键注意事项

                          1. 客户端组件限制useOptimistic 仅在客户端组件中生效,必须添加 "use client" 指令,否则会报错;
                          1. 状态回滚机制:若服务器动作执行失败,Next.js 会自动将 optimisticState 回滚到 initialState,无需手动处理;
                          1. 不可变更新updateFn 中必须通过“复制+修改”的方式更新状态,不可直接修改 oldState(否则会导致状态混乱);
                          1. 服务器动作分离:建议将服务器逻辑(如 togglePostLike)定义在单独文件中,避免客户端组件中混入服务器代码,保持代码分层清晰。

                          016 Caching Differences Development vs Production - 开发与生产模式缓存差异

                          一、Next.js 生产模式构建与启动

                          1. 构建流程:部署前需执行npm run build,触发Next.js构建过程,完成优化并生成可部署的Next文件夹,为应用上线做准备。
                          1. 本地生产模式测试:构建后运行npm start启动生产服务器,服务器仍可通过localhost:3000访问;生产模式下页面切换无加载文本,数据加载速度更快,但存在缓存相关问题。

                          二、生产模式缓存问题

                          1. 问题表现:开发模式中数据更新(如创建新帖子)可正常显示,生产模式下创建新帖子后,页面重定向后新内容不显示,刷新起始页也无法看到更新数据。
                          1. 问题根源:Next.js在生产模式构建时,会预渲染所有页面并进行缓存;若应用无动态页面和动态段,缓存的页面不会重新渲染,导致数据更改无法被检测,页面始终展示旧数据。

                          三、缓存问题解决方案

                          1. 核心函数:revalidatePath:通过revalidatePath函数告知Next.js,当数据发生变化时需重新渲染指定页面,该函数会将目标页面从缓存中移除并重新创建,确保页面获取最新数据。
                          1. 正确使用场景:在创建帖子的服务器操作中、执行页面重定向之前调用revalidatePath,无需依赖“点赞”等其他操作触发,直接保证数据更新后页面能同步展示最新内容。

                          四、方案验证步骤

                          1. 停止当前运行的生产服务器。
                          1. 重新执行构建命令(npm run build),获取包含revalidatePath配置的最新代码。
                          1. 再次启动生产服务器(npm start),重新加载页面并创建新帖子,验证页面重定向后是否能直接显示新数据,确认缓存问题已解决。

                          五、关键注意点

                          1. 起始页若仅展示最新两条帖子,是代码中“仅获取最新两条数据”的设定,属于正常逻辑,非缓存或功能错误。
                          1. 课程核心收获:掌握Next.js应用中数据突变的实现方式、通过服务器操作处理数据交互,以及利用revalidatePath等工具解决生产环境缓存问题的实用技巧。

                          📎 参考文章

                          • 一些引用
                          • 引用文章
                           
                          💡
                          欢迎您在底部评论区留言,一起交流~
                          上一篇
                          07 - Understanding & Configuring Caching
                          下一篇
                          05 - Data Fetching - Deep Dive
                          Loading...
                          0%