1 - Introduction

image-20251027094752289

image-20251027094857937

2 - Form Setup

创建一个简单的form表单,内容如下:

image-20251027095049460

使用vite来创建项目,步骤如下:image-20251027095210419

如果使用create-react-app命令来创建项目,步骤如下:

image-20251027095507125

创建文件src/components/YoutubeForm.tsx,里面就是一个简单的表单:

修改App.cssindex.css里面的样式,直接copy老师的文件即可。然后在App.tsx里面使用表单:

效果:

image-20251027101020562

3 - useForm Hook

安装依赖npm i react-hook-form,在YoutubeForm里面使用。

useForm是一个hook函数,所以按照函数的方式来使用它,它会返回一个对象。

这节课就学习react-hook-form最基本的使用方法。

4 - Managing Form State

什么是form state呢?就是form里面可以变化的部分:

image-20251027103020258

在代码中,我们可以这样表示form state:

image-20251027103439944

但是这些对象都要追踪的话,太复杂了,所以需要使用react hook form来帮助我们解决。

useForm()返回的对象form里面,包含一个register方法,register 方法的作用是将表单的输入元素(如 <input><select><textarea> 等)与 React Hook Form 的表单状态管理绑定起来,从而实现表单数据的收集、验证和提交。

使用register的方法如下:

这种使用方法是react hook form帮助我们真正实现的代码,但是在使用的时候,可以使用spread operator来传递参数:

那么,现在react hook form就接管了form state,但是我怎么知道接管了呢?下节课会介绍。

5 - DevTools Visualization

这节课我们来学习react hook form是否真的在manage form state。项目里面安装依赖npm i -D @hookform/devtools,在YoutubeForm里面使用这个工具。

然后在页面右上角可以看到一个小工具:

image-20251027110325441

点击可以看到:

image-20251027110352812

value表示当前值,touched表示是否交互过,dirty表示字段是否已更改。

6 - Form State and Rerenders

这节课是说明react hook form管理form state,不会造成页面的rerender,这一点很棒。react hook form遵循非受控组件的输入行为。

为了说明react hook form在管理form state的时候不会造成页面的rerender,那么可以定义一个数字变量,看form交互的时候,数字是否会变化。

可以看到,在表单交互的时候,数字并没有变化,这就说明了react hook form管理form state,不会造成页面重新渲染。

 

为什么react hook form能够做到监听值的变化,而不引起页面的重新渲染?

🧠 背景:普通表单为什么会频繁 re-render?

在传统 React 表单中,你可能会这样写:

每次输入一个字母,setUsername 都会触发组件的 重新渲染 如果表单有很多字段,这种模式就会造成大量 无意义的 re-render


⚙️ React Hook Form 的不同之处

React Hook Form 的核心设计目标是:

让表单状态的变更不强制触发 React 组件的重新渲染。

它是通过下面这三个机制实现的:


① 使用独立的 “subscription” 模型(发布-订阅)

📦 类似这样:

因此,不会造成整页刷新或整个表单 re-render。


register() 不会把字段变成受控组件

在普通 React 中:

这种模式叫“受控组件”,会导致每次输入都触发渲染。

而 RHF 的:

其实是非受控组件(uncontrolled)。 RHF 通过 ref 直接读取 DOM 的值,而不是通过 React 的 state。

👉 也就是说,输入框的值存在 DOM 里,而不是存在 React state 里。 所以 React 不需要重新渲染来同步输入。


watch()useWatch() 提供选择性订阅

RHF 提供两个监听值变化的方式:

例如:

这个组件只会在 username 变化时更新,而不是整个表单。


🧩 小总结

机制说明好处
独立 store + subscription字段独立订阅自己的变化避免全局 re-render
非受控组件(uncontrolled input)直接通过 DOM ref 管理值不依赖 React state
useWatch精准订阅特定字段局部更新

⚡ 总结一句话

React Hook Form 用 “非受控组件 + 订阅模型” 替代 “受控 state 模型”,从而避免不必要的 React re-render。

 

需要注意的是,设置了校验规则之后,如果不断触发input的校验报错,那么会引起页面的rerender。为什么会这样呢?

 

非常棒的问题!👏 你观察得很细,也确实抓到了 React Hook Form(RHF)性能机制的一个关键细节

这个现象其实 不矛盾,而是 RHF 有意为之 的一部分设计。我们来细讲。


🎯 结论先说:

RHF 的“无重渲染”特性只在 字段值变化(value change) 时生效。 一旦涉及到 校验状态(error state)变化,它会 触发必要的 re-render,但只更新受影响的组件,而非整个表单。


🧩 为什么?

让我们回忆一下 RHF 的内部状态分层:

层级存的是什么是否引发重新渲染
field value字段的实际输入值❌ 不引发(非受控)
field statetouched, dirty, error 等状态✅ 可能引发(受控)
form state全局状态,如 isValid, isSubmitting✅ 可能引发(订阅更新)

当你只输入内容时(没校验或没报错):

但当你设置了校验规则,例如:

每当输入内容使得校验状态变化(比如从无错误 → 有错误 / 错误内容不同):


⚙️ 总结成一句话:

✅ 值变化 → 只更新 store,不 re-render ⚠️ 校验状态变化 → 更新订阅的字段视图(必要的 re-render)

这不矛盾,而是 React Hook Form 的精细化优化策略。


🧠 额外技巧:减少验证引发的 re-render

如果你觉得验证导致的重渲染太频繁,可以做两件事:

  1. 调整验证时机

    👉 只在失焦时校验,不在每次输入时触发。

  2. 延迟验证(自定义触发) trigger("fieldName") 手动触发验证,而不是在输入中实时执行。

 

我好像有点害怕页面的rerender,因为我写vue的缘故,当页面rerender之后,里面使用v-model绑定的值会丢失,那么vue里面有rerender吗?我从来没有注意过这个问题。

问:vue里面有类似react里面的rerender吗?

答:

简而言之:

Vue 中没有直接对应 React 中“组件 re-render (重新渲染)”的概念和机制。

 

1. React 的机制:Re-render (重新渲染)

在 React 中,组件的渲染流程是基于组件函数重新执行的:

  1. 触发:当组件的 StateProps 发生变化时。
  2. 过程整个组件函数会重新执行,生成一个新的虚拟 DOM (Virtual DOM) 树
  3. 对比:React 将新的 V-DOM 树与旧的 V-DOM 树进行 Diff(对比/调和/Reconciliation)
  4. 更新:只有发现差异的部分,React 才会去更新真实的 DOM

这种机制的特点是:默认情况下,组件内部的代码(包括 JSX)会完整地重新执行。为了优化性能,开发者需要手动使用 memouseCallbackuseMemoshouldComponentUpdate阻止不必要的重新渲染。

2. Vue 的机制:细粒度响应式 (Fine-Grained Reactivity)

在 Vue 中,组件的更新流程是基于响应式数据依赖追踪的:

  1. 创建 (Tracking):在组件首次渲染时,Vue 的响应式系统会“监听”你在模板中使用了哪些 refreactive 数据。这个过程被称为依赖收集
  2. 触发 (Triggering):只有当你修改了被模板使用的那个特定的响应式数据时,Vue 的响应式系统才会收到通知。
  3. 更新:Vue 会找到唯一需要更新的那个 Reactive Effect(通常是一个 Watcher 或一个渲染函数)。它只会重新运行依赖于这个变化数据的代码块,并生成局部的 V-DOM 树。
  4. 局部修补 (Patch):Vue 的编译器和运行时优化使其能够非常高效地只修补 (Patch) 真实 DOM 中发生变化的那一小部分

这种机制的特点是:Vue 的更新是“自动且精准”的。你不需要担心整个组件函数会重新执行,也不需要手动阻止不必要的重新渲染。

总结对比

特性React (re-render)Vue (响应式更新)
触发机制StateProps 变化被使用的响应式数据变化
执行范围整个组件函数重新执行,生成完整 V-DOM只执行依赖于变化数据的代码块,生成局部 V-DOM
优化方式开发者手动使用 memouseMemoshouldComponentUpdate阻止重新渲染。框架自动优化,更新的粒度非常细,通常不需要手动干预。
性能理念默认为不精确(宽泛),开发者手动优化。默认为精确(细粒度),框架自动追踪。

因此,当 Vue 中的响应式数据发生变化时,它对应的行为是“自动和最小化的更新”“重新运行其依赖的副作用函数(Effect)”,而不是 React 那种广义上的“组件重新渲染”。

 

其实我理解的rerender和实际上的rerender不是一回事,我理解的rerender很可能是page reload这种东西,那么react rerender到底是什么呢?

很多人用 React 一段时间后发现“页面卡”、“动一下全刷新”,根本原因通常都在于对 rerender(重新渲染)机制 理解不深。 我们来系统地讲一遍 👇


🧠 一、先说什么是“rerender”

在 React 中,当组件的 state 或 props 发生变化 时,React 会触发组件函数再次执行,这就是一次 rerender(重新渲染)。

⚠️ 注意:

“rerender” ≠ “DOM 更新”。 React 的重新渲染是 虚拟 DOM(Virtual DOM)层面 的重新计算,只有当新旧虚拟 DOM 不同时,才会导致真实 DOM 更新。


⚙️ 二、什么会引发 rerender?

触发原因示例说明
useState 更新setCount(count + 1)本组件 rerender
父组件 rerender父组件执行,子组件也 rerender(除非被 memo) 
useReducer dispatchdispatch 改变 state,会 rerender 
useContext 的值改变所有消费该 context 的组件都会 rerender 
props 引用变化即使内容相同,只要引用不同也会 rerender(对象/函数) 

💥 三、rerender 的影响(你该警惕的地方)

1️⃣ 性能影响:多次 render 会浪费计算资源

React 每次 rerender 都会重新执行组件函数:

  • 重新计算 JSX;
  • 重新绑定事件;
  • 重新计算 Hook 调用;
  • 如果有昂贵逻辑(如数据处理、复杂渲染),性能会迅速下降。

👉 解决办法

  • useMemo 缓存计算结果;
  • useCallback 缓存函数;
  • React.memo() 包裹组件。

2️⃣ 问题:不必要的 rerender

父组件更新时,所有子组件默认都会重新渲染,即使 props 没变。

例子:

✅ 解决方案:


3️⃣ 对异步副作用的影响

如果你的组件里有异步逻辑,例如:

一旦 rerender 逻辑不当,容易导致:

  • 重复请求;
  • 闭包陷阱(拿到旧 state);
  • 状态被覆盖。

⚠️ 所以要确保:

  • useEffect 的依赖正确;
  • 使用 useCallback 稳定函数引用;
  • 在异步中用 isMounted 或 abortController 控制请求。

4️⃣ 对 React Hook Form、React Query、Redux 等库的影响

像 React Hook Form 之所以性能好,就是因为它避免了无意义的 rerender

  • 如果你在表单里频繁触发 setValue / setError
  • 或者 React Query 每次请求完都让整个页面刷新; 👉 性能就会下降。

解决方法是“订阅局部变化”:

  • RHF → Controller + useWatch
  • React Query → 拆分 Query、select
  • Redux → 使用 useSelector 时拆分 state。

⚡ 四、优化 rerender 的核心思路

场景优化手段原理
父子组件频繁刷新React.memo跳过 props 未变化的重新渲染
函数作为 propsuseCallback保持函数引用稳定
大计算量渲染useMemo缓存计算结果
Context 改变太频繁拆分 Context / 使用 selector减少订阅范围
表单状态频繁变非受控组件 / useWatch精细订阅

🎯 五、最重要的理解:Rerender 不是坏事

React 的 rerender 并不一定慢,它只是重新执行函数。 真正影响性能的,是“无意义的 rerender”。

也就是说:

  • 小组件 rerender 没问题;
  • 关键是 不要让它们级联触发(parent → child → grandchild...)。

✅ 总结一句话

React rerender 的本质是组件函数重新执行。 合理控制 rerender,可以极大提升性能与可维护性。

 

可以从上面看到,react的rerender机制还是非常复杂的,所以产生了nextjs这样的框架,那么nextjs框架对于react的rerender有什么优化吗?为什么我在学习nextjs的时候,没有怎么使用useMemouseCallback这些函数呢?

非常好的问题,这说明你已经从“怎么用 React”上升到了“React 框架是怎么优化性能的”这个层面。👏

其实你观察得非常准确 —— 在 Next.js(尤其是 v13+ 的 App Router 架构) 中, 你确实会发现: 👉 很多地方根本不需要手动用 useMemouseCallback 来优化渲染性能。

这并不是因为 Next.js 不会发生 rerender,而是因为 Next.js 把大部分渲染优化提前到了“服务器端” 我们来分层分析一下为什么。👇


🧩 一、React 自身 rerender 的痛点

在纯 React(前端渲染)项目中:

  • 所有状态、组件、数据请求都在 浏览器端运行
  • 页面渲染逻辑全靠客户端 React 控制;
  • 因此频繁的 setStateuseEffect、函数重建、props 传递都会导致 rerender。

你就需要用:

  • React.memo 避免子组件重复渲染;
  • useCallback 缓存函数;
  • useMemo 缓存复杂计算。

⚙️ 二、Next.js 的重大不同点:默认是服务端渲染(Server Components)

Next.js 13 (App Router) 开始,引入了 React 官方的 Server Components 模式 这彻底改变了渲染逻辑 👇

对比传统 ReactNext.js App Router
组件执行位置浏览器端默认在服务器端
状态管理前端状态服务端计算后传结果
网络请求浏览器 fetch服务器直接 fetch(更快)
Rerender 范围前端全量渲染仅 Client Components rerender
优化需求必须用 useMemo/useCallback大部分情况下不需要

🧠 三、深入理解:Server Components 避免了很多“rerender 场景”

举个例子👇

这里的 PageServer Component

  • 它只在服务端执行一次;
  • React 直接把渲染好的 HTML 发给浏览器;
  • 浏览器拿到后只负责展示;
  • 不存在 “state 改变 → rerender” 这个过程。

💡 所以你根本不需要 useMemouseCallback —— 组件根本不会在客户端重新执行。


🧭 四、那 Next.js 里什么时候会重新渲染?

只有这两种情况:

  1. 使用 “Client Component” 时

    🔸 这个组件才是传统的 React 客户端逻辑。 在这种情况下,useMemouseCallback 才有意义。

  2. 当 props 变化时

    • Next.js 的 Server Components 会智能 diff;
    • 只更新需要重新渲染的部分;
    • 所以 rerender 的范围非常小,性能损耗远小于纯 React SPA。

🧠 五、Next.js 的其他隐形优化手段

Next.js 在底层做了很多性能优化,让你不必手写优化

优化点说明
🔹 Server Components避免前端 rerender,直接在服务器端计算
🔹 Streaming + Partial Rendering页面分块流式渲染,减少阻塞
🔹 缓存 (Route Cache / Data Cache)自动缓存数据请求和组件树
🔹 React Suspense + Lazy Loading自动分块渲染组件,减少初始负担
🔹 Selective Hydration只激活需要交互的组件部分
🔹 静态优化 (Static Rendering)无需请求的页面直接预生成 HTML

👉 因此,在 Next.js 里你几乎感觉不到 “性能调优的负担”。


🧩 六、简单理解一句话:

在传统 React 里,组件是渲染的最小单位 在 Next.js(App Router)里,Server Component 树是渲染的最小单位

也就是说,Next.js 在“更高一层”就把 rerender 控制住了。 你写的组件大多只渲染一次,因此根本不需要频繁用 useMemo/useCallback


✅ 七、总结对比表

特性纯 React SPANext.js (App Router)
渲染位置浏览器服务端(默认)
rerender 频率高频低频
性能瓶颈React diff + 组件重建网络/SSR 流式传输
优化手段useMemo / useCallback / memo默认启用缓存 + Streaming
开发体验手动优化自动优化

💬 简短总结一句话:

你在 Next.js 里很少用 useMemouseCallback 并不是它们没用了,而是 Next.js 通过 Server Components + Cache + Streaming,把大部分 rerender 问题在“框架层”就解决掉了

 

 

7 - Form Submission

这节课学习form提交,有三步来完成。

1、定义一个提交函数,里面写提交的逻辑

2、从useForm()的返回结果,解构出handleSubmit方法

3、在<form>上绑定onSubmit事件,使用onSubmit={handleSubmit(onSubmit)},这样做了之后,在onSubmit函数上,可以接收到form里面的数据。之后就可以利用这个数据进行逻辑操作。注意react hook form要求typescript类型,所以使用useForm时要定义ts类型,接收的data参数也要定义类型。

效果:

8 - Form Validation

之前我们已经学习了管理form state,提交form data,接下来我们学习表单校验和校验结果反馈。

image-20251027113754171

react hook form支持各种HTML校验规则,比如说:必填、最小程度和最大长度、最小值和最大值、设置自定义校验模式。

image-20251027113818364

这节课学习最简单的required规则。在form标签上需要使用noValidate属性,这个属性将阻止浏览器的校验,让react hook form的校验接管。

校验规则写在register的第二个参数,是一个对象,key:value,key表示校验规则,value表示校验出错误时显示的文字信息。

默认情况下,validations只在表单提交的时候触发,当然后面会讨论到其他情况。先看最简单的必填校验。

效果:

可以看到,在devtool里面,出现了校验相关的信息。不过这种提示信息不是我所熟悉的在输入框下面出现的红色字体提示信息,说明我要使用返回的Error TypeMESSAGE来展示,这部分后面会讲到。

下面讲一下pattern校验方式怎么做。register的第二个参数对象,key就是pattern,value是一个对象,里面有两个参数,value表示正则规则,message表示校验错误时的提示信息。

效果:

实际上required规则也可以写成对象的形式:

需要注意的是,当submit提交触发了校验之后,在有错误的field里面再次输入,会触发校验,此时不需要submit来触发校验,这一点要注意。

9 - Display Error Messages

上节课学习了怎么设置校验规则,这节课来讲解怎么展示校验规则。

useForm()中解构出formState,这个参数包含很多信息,这里只使用它的errors来展示错误信息。

修改了一些样式,如下:

image-20251027123130307

展示错误信息很简单,因为有了react hook form的帮助,只需要在有校验规则的form-item下面直接展示errors相关信息即可,<p className="error">{errors.email?.message}</p>

效果:

 

老师展示错误信息是这样写的<p className="error">{errors.username?.message}</p>,但是我经常看到是这样写的:

两种都可以。

10 - Custom Validation

这节课讲解怎么添加自定义校验规则,比如说在email输入框里面,我们想阻止用户输入admin@example.com这个邮箱,怎么做?

通过向register的第二个参数,添加第二个参数的方式来实现,key的名称为validate,value是一个函数。函数接收当前字段值为参数。

custom validation规则与pattern规则是分开的,互不影响,当然我的规则要设置的互不影响。

validate基本用法

return返回true就表示校验通过,就不会显示提供的错误信息,否则就显示错误信息。那么这里的return返回值的意思是:当fieldValue等于admin@example.com时,展示错误信息。

效果:

validate的值也可以是多个规则的对象

validate的值如果是一个对象,那么里面的key表示的就是规则的名称,这个可以自定义;value表示校验规则,是一个函数,与基本用法里面的函数一致。

效果:

11 - Enhancing React Hook Form

之前我们学习了react hook form的基本用法,如下:

image-20251027130602093

接下来,我们会更加深入的学习react hook form的相关概念,学习更多的特性:

image-20251027130712725

12 - Default Values

这节课学习如何为form里面的field设置默认值。

基本用法

useForm()中传入一个对象,对象里面有一个属性defaultValues,在这里设置。

效果:

image-20251027131416915

数据回显

比如说编辑时,进入这个表单之后,需要将保存的数据回显出来,怎么做?

使用https://jsonplaceholder.typicode.com/users/1提供的数据,回显里面的email。

image-20251027131655586

将defaultValues改为一个函数,在里面请求数据,返回数据对象。

效果:

13 - Nested Objects

有时候我们想将某些字段分组在一起,这时候可以将这些字段放到一个对象里面,

1、定义typescript类型

2、定义默认值

3、添加jsx代码,注册fieldname。重点是注册的方式{...register("social.twitter")},真的是很方便啊。

可以看到,提交的数据里面有social相关数据。

14 - Arrays

将表单空间的值作为数组来管理,也是很常见的,比如说多选框。react hook form也支持管理数组。

案例:收集用户的主要和次要手机号,不需要区分它们,只需要手机即可。(这里其实我不是很认同,因为主要和次要手机号都是要回显的,这里可以把它当作用户的爱好这类的信息)

1、定义typescript类型

2、设置默认值

3、添加jsx,注册fieldname。重点就是注册方式{...register("phoneNumbers.0")},注意这种方式,是react hook form定义的方式,这里不能使用方括号。

可以看到输出结果里面phoneNumbers数组,数据是正常的。

课后练习,为social和phoneNumbers相关的field添加required校验。

required还是正常设置,重点是错误提示信息,怎么展示。nested object这个简单,但是数组里面,怎么做?要这样做errors.phoneNumbers?.[1]?.message,是不是没有见过?其实应该这么看:

?.[1]:

效果:

image-20251027141611672

如果是一个table里面,每一行有多个输入框,table可以CRUD,这时候应该怎么做呢?

因为数组长度是不固定的,此时可以使用useFieldArray来处理。

15 - Dynamic Fields

上节课学习了arrays,但有时候用户需要输入的手机号、爱好等等数量是不固定的,这时候需要添加或删除字段,怎么办?

使用useFieldArray()

1、引入useFieldArray,定义typescript类型

2、设置默认值

3、使用useFieldArray

4、添加jsx

5、添加新增、删除按钮,使用useFieldArray提供的方法来处理

https://react-hook-form.com/docs/usefieldarray

image-20251027144136085

效果:

16 - Numeric and Date Values

这节课学习处理数字和日期类型的数据。

numeric values

1、定义typescript类型

2、设置默认值

3、添加jsx

填写所有信息,提交后查看控制台输出,会发现age的值是string类型:

为了能让这个值变为数字类型,可以使用react hook form提供的valueAsNumber: number

image-20251027150328509

再看输出结果,会发现变为数字类型了。

Date values

1、定义typescript类型

2、设置默认值

3、添加jsx

可以看到,提交的数据里面,dob是字符串类型。

有时候后端不需要这种类型,需要的是原始的Date类型,这时候可以使用valueAsDate: true来将数据转为Date类型。

image-20251027151534614

可以看到,dob转为了Date类型:

需要注意的是,数据类型转换发生在校验之前,所以校验规则需要根据实际数据类型来编写。

17 - Watch Field Values

这节课学习怎么watch组件中使用的fieldvalue。

https://react-hook-form.com/docs/useform/watch

image-20251027152120596

watch是从useForm解构出来的,可以监听一个或多个fieldvalues。

1、监听一个fieldvalue

效果:

2、监听多个fieldvalues

效果:

3、监听整个表单

watch不传参就表示监听整个表单,返回值是一个对象。

效果:

4、执行watch的副作用

需要结合useEffect一起使用。

可以看到,在监听的时候,renderCount的值没有发生变化,说明页面并没有rerender,这样的监听对性能非常友好。

注意:

1、2、3的监听方法,都触发了页面的rerender,使用的时候需要注意。

4的监听方法,没有触发页面的rerender,但是监听的是整个form表单。