全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-07-06_React 19 新 hook —— useActionState 与 Next.js Server Actions 绝佳搭配

您的位置:首页 >> 新闻 >> 行业资讯

React 19 新 hook —— useActionState 与 Next.js Server Actions 绝佳搭配 ?本文为稀土掘金技术社区首发签约文章 ?点击关注公众号,“技术干货”及时达!前言2024 年 4 月 25 日,React 发布了 React 19 RC 的介绍博客,其中介绍了新 hook —— useActionState。 说是新 hook,其实就是 Canary 版中的 useFormState,只是添加了新功能以及做了重命名。 使用 useActionState,可以替代之前的 useFormState 和 useFormStatus,和 Next.js 的 Server Actions 更是绝佳搭配。 本篇我们就详细介绍下为什么会有 useActionState 这个 hook 以及在 Next.js 项目中如何搭配 Server Actions 使用。 ?本篇已收录到掘金专栏《Next.js 开发指北》(https://juejin.cn/column/7343569488744611849) 系统学习 Next.js,欢迎入手小册《Next.js 开发指南》(https://juejin.cn/book/7307859898316881957?suid=712139234359182&source=pc)。基础篇、实战篇、源码篇、面试篇四大篇章带你系统掌握 Next.js! ?使用 Next.js 15 与 React 19创建一个 Next.js 项目: npxcreate-next-app@latest 目前 Next.js 15 和 React 19 都在 RC 阶段,使用需要升级相关依赖项,运行: npminext@rcreact@rcreact-dom@rc 如果安装时遇到了依赖问题: 那就加个 --force 强制安装: npminext@rcreact@rcreact-dom@rc--force 1. 传统实现方式使用 React 常遇到的场景就是执行数据突变,然后根据响应更新状态。 这样的说法有些“文绉绉”,大白话就是调用接口,然后根据接口返回的数据进行处理,比如更新列表、提示错误信息等。我们写个例子。 涉及的文件和目录如下: app └─form ├─actions.js ├─form.js └─page.js 新建 app/form/page.js,代码如下: import{findToDos}from'./actions'; importAddToDoFormfrom'./form'; exportdefaultasyncfunctionPage(){ consttodos=awaitfindToDos(); return( divclassName="p-4" AddToDoForm/ ulclassName='list-decimallist-inside' {todos.map((todo,i)=likey={i}{todo}/li)} /ul /div ) } 新建 app/form/form.js,代码如下: 'useclient' import{useState}from'react'; import{createToDo}from'./actions'; exportdefaultfunctionAddToDoForm(){ const[todo,setTodo]=useState(""); const[error,setError]=useState(null); const[isPending,setIsPending]=useState(false); consthandleSubmit=async()={ setIsPending(true); consterror=awaitcreateToDo(todo); setIsPending(false); if(error){ setError(error); return; } return( divclassName="flexflex-colgap-y-2mb-2" inputtype="text"name="todo"className="px-4py-2roundedshadow-smring-1ring-insetring-gray-300"value={todo}onChange={(event)=setTodo(event.target.value)}/ buttontype="submit"disabled={isPending}className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-white"onClick={handleSubmit} {isPending?'Adding':'Add'} /button {errorp{error}/p} /div ) } 在这段代码中,我们声明了 3 个状态,一个状态用于获取输入框的值,一个状态用于处理挂起状态,一个状态用于处理错误。这就体现了「传统实现方式的弊端:开发者需要手动处理挂起状态、错误状态等。」 新建 app/form/actions.js,代码如下: 'useserver' import{revalidatePath}from"next/cache"; constsleep=ms=newPromise(r=setTimeout(r,ms)); letdata=['阅读','写作','冥想'] exportasyncfunctionfindToDos(){ returndata } exportasyncfunctioncreateToDo(todo){ awaitsleep(500) if(Math.random()0.5){ return'创建失败' } data.push(todo) revalidatePath("/form"); } 在这段代码中,我们模拟了创建失败的情况,失败的时候会返回一个错误信息。 这就是一个最基本的 Next.js Server Actions 的例子。浏览器效果如下: 在上图中,前两次添加都创建成功,列表得到更新。第三次添加的时候触发错误,显示了错误信息。 而且你还会发现(图中没有体现出来),如果继续添加,哪怕添加成功,之前的错误信息还会继续显示。如果要去除之前的错误信息,还需要开发者手动进行处理。 实际开发中也是如此,手动维护多个状态,如果某个边缘 Case 没有处理,很可能就会导致错误。 2. 过时的 useFormState 与 useFormStatus为了优化这个过程,React 提供了 useFormState 和 useFormStatus 这两个 hook 用来自动处理挂起状态、错误等。 涉及的文件和目录如下: app └─form2 ├─actions.js ├─form.js └─page.js 新建 app/form2/page.js,代码如下: import{findToDos}from'./actions'; importAddToDoFormfrom'./form'; exportdefaultasyncfunctionPage(){ consttodos=awaitfindToDos(); return( divclassName="p-4" AddToDoForm/ ulclassName='list-decimallist-inside' {todos.map((todo,i)=likey={i}{todo}/li)} /ul /div ) } 新建 app/form2/form.js ,代码如下: 'useclient' import{useFormState,useFormStatus}from'react-dom' import{createToDo}from'./actions'; functionSubmitButton(){ const{pending}=useFormStatus() return( buttontype="submit"disabled={pending}className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-white" {pending?'Adding':'Add'} /button ) } exportdefaultfunctionAddToDoForm(){ const[state,formAction]=useFormState(createToDo,'') return( formaction={formAction}className="flexflex-colgap-y-2mb-2" inputtype="text"name="todo"className="px-4py-2roundedshadow-smring-1ring-insetring-gray-300"/ SubmitButton/ {statep{state}/p} /form ) } 可以看到使用 useFormState 和 useFormStatus 后,整体代码量下降,而且不需要手动维护状态。 注意:useFormStatus 需要写在单独的组件中,所以我们才单独声明了一个 SubmitButton 组件。useFormState 返回的 formAction 可与 form集成,当 Action 成功时,React 会自动重置表单的不受控制组件。 新建 app/form2/actions.js,代码如下: 'useserver' import{revalidatePath}from"next/cache"; constsleep=ms=newPromise(r=setTimeout(r,ms)); letdata=['阅读','写作','冥想'] exportasyncfunctionfindToDos(){ returndata } exportasyncfunctioncreateToDo(prevState,formData){ awaitsleep(500) if(Math.random()0.5){ return'创建失败' } consttodo=formData.get('todo') data.push(todo) revalidatePath("/form2"); } 浏览器效果如下: 在上图中,前两次添加的时候都失败了,显示了错误信息。第三次添加的时候终于成功了,之前的错误信息也自动去除,而且表单自动重置。 3. React 19 useActionState查看第二个例子中的浏览器控制台,其实会发现有报错: useFormState 在 React 19 RC 中已经更名为 useActionState: const[error,submitAction,isPending]=useActionState( async(previousState,newName)={ //... }, null, ); 注意:在 React 官方的例子里,第一个变量被命名为 error,这是因为成功的时候往往不需要返回什么,失败的时候却需要返回错误信息(Next.js 中成功的时候使用 revalidatePath,失败的时候才返回信息),其实更准确的表达应该是 state,这个字段不一定用来处理错误信息。 相比之前的 useFormState,useActionState 还多了一个 isPending,这正是 useFormStatus 返回的 isPending 状态,所以使用 useActionState,不需要再使用 useFormState 和 useFormStatus。 涉及的文件和目录如下: app └─form3 ├─actions.js ├─form.js └─page.js 新建 app/form3/page.js代码如下: import{findToDos}from'./actions'; importAddToDoFormfrom'./form'; exportdefaultasyncfunctionPage(){ consttodos=awaitfindToDos(); return( divclassName="p-4" AddToDoForm/ ulclassName='list-decimallist-inside' {todos.map((todo,i)=likey={i}{todo}/li)} /ul /div ) } 新建 app/form3/form.js,代码如下: 'useclient' import{useActionState}from"react"; import{createToDo}from'./actions'; exportdefaultfunctionAddToDoForm(){ const[state,formAction,isPending]=useActionState(createToDo,'') return( formaction={formAction}className="flexflex-colgap-y-2mb-2" inputtype="text"name="todo"className="px-4py-2roundedshadow-smring-1ring-insetring-gray-300"/ buttontype="submit"disabled={isPending}className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-white" {isPending?'Adding':'Add'} /button {statep{state}/p} /form ) } 新建 app/form3/actions.js,代码如下: 'useserver' import{revalidatePath}from"next/cache"; constsleep=ms=newPromise(r=setTimeout(r,ms)); letdata=['阅读','写作','冥想'] exportasyncfunctionfindToDos(){ returndata } exportasyncfunctioncreateToDo(prevState,formData){ awaitsleep(500) if(Math.random()0.5){ return'创建失败' } consttodo=formData.get('todo') data.push(todo) revalidatePath("/form3"); } 浏览器效果如下: 这里的效果同使用 useFormState 和 useFormStatus的时候。 4. React 19 useFormStatus注意:虽然 useFormState 被废弃并更名为 useActionState,但 useFormStatus 依然可以正常使用。 因为有的时候,组件需要访问 form的信息,虽然可以使用 useActionState 获取 pending 状态然后将该状态通过 props 一层一层传递,但其实也可以通过 Context 完成,为了处理更加方便,React 添加了 useFormStatus: import{useFormStatus}from'react-dom'; functionDesignButton(){ const{pending}=useFormStatus(); returnbuttontype="submit"disabled={pending}/ } 简单的来说,useFormStatus 会自动读取父 form 的状态,而无须通过 props 传递。所以依然可以用,组件层级过深的时候,建议使用 useFormStatus。 5. useActionState 不仅用于表单目前我们举的例子都是用在表单上,这可能会让初学者以为 useActionState 就是用来处理表单的,其实不然。 回顾下 useActionState 的用法: const[state,formAction,isPending]=useActionState(fn,initialState); 虽然 form 元素有一个 action 属性,但此 action 非彼 action。「在 React 中,按照惯例,使用异步转换的函数被称之为“Action”」。useActionState 中的 Action 表达的其实是异步函数,而非用在 form action 的意思。 我们举个将 useActionState 用在其他元素的例子: 涉及的文件和目录如下: app └─form4 ├─actions.js ├─form.js ├─delbtn.js └─page.js 新建 app/form4/page.js,代码如下: import{findToDos}from'./actions'; importAddToDoFormfrom'./form'; importDeleteBtnfrom'./delbtn'; exportdefaultasyncfunctionPage(){ consttodos=awaitfindToDos(); return( divclassName="p-4" AddToDoForm/ ulclassName='list-decimallist-inside' {todos.map((todo,i)=likey={i}className='mb-2'{todo}DeleteBtnid={i}//li)} /ul /div ) } 我们在列表后又添加了一个删除按钮。 新建 app/form4/form.js,代码同 form3,代码如下: 'useclient' import{useActionState}from"react"; import{createToDo}from'./actions'; exportdefaultfunctionAddToDoForm(){ const[state,formAction,isPending]=useActionState(createToDo,'') return( formaction={formAction}className="flexflex-colgap-y-2mb-2" inputtype="text"name="todo"className="px-4py-2roundedshadow-smring-1ring-insetring-gray-300"/ buttontype="submit"disabled={isPending}className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-white" {isPending?'Adding':'Add'} /button {statep{state}/p} /form ) } 新建 app/form4/delbtn.js,代码如下: 'useclient' import{useActionState}from"react"; import{deleteTodo}from'./actions'; exportdefaultfunctionDeleteBtn({id}){ const[state,action,isPending]=useActionState(deleteTodo,null) return( button disabled={isPending} onClick={()={ action(id) }} className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-whitepx-2ml-2" {isPending?'Deleting':'Delete'}{state} /button ) } 这里我们使用了 useActionState,并将 action 函数用在了按钮元素的 onClick 事件中。 新建 app/form4/actions.js,代码如下: 'useserver' import{revalidatePath}from"next/cache"; constsleep=ms=newPromise(r=setTimeout(r,ms)); letdata=['阅读','写作','冥想'] exportasyncfunctionfindToDos(){ returndata } exportasyncfunctioncreateToDo(prevState,formData){ awaitsleep(500) if(Math.random()0.5){ return'创建失败' } consttodo=formData.get('todo') data.push(todo) revalidatePath("/form4"); } exportasyncfunctiondeleteTodo(id){ awaitsleep(500) if(Math.random()0.5){ return'删除失败' } data.splice(id,1) revalidatePath("/form4"); } 浏览器效果如下: 点击删除按钮的时候,如果删除失败,会渲染返回的 state 信息。 如果经常写 Server Actions,你可能会这样错误使用 useActionState: 'useclient' import{useActionState}from"react"; import{deleteTodo}from'./actions'; exportdefaultfunctionDeleteBtn({id}){ const[state,action,isPending]=useActionState(deleteTodo,null) return( button disabled={isPending} onClick={async()={ //这样写是错误的 constresponse=awaitaction(id) if(response)alert(response) }} className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-whitepx-2ml-2" {isPending?'Deleting':'Delete'}{state} /button ) } 这样写是无效的,response 是一直是 undefiend。使用 useActionState,返回的信息要使用 state 获取。 如果直接使用 Server Actions,你可以这样写: 'useclient' import{useActionState}from"react"; import{deleteTodo}from'./actions'; exportdefaultfunctionDeleteBtn({id}){ const[state,action,isPending]=useActionState(deleteTodo,null) return( button disabled={isPending} onClick={async()={ constresponse=awaitdeleteTodo(id) if(response)alert(response) }} className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-whitepx-2ml-2" {isPending?'Deleting':'Delete'}{state} /button ) } 总结一下就是,使用 Server Actions,一种是直接使用: 'useclient' import{deleteTodo}from'./actions'; exportdefaultfunctionDeleteBtn({id}){ return( button onClick={async()={ constresponse=awaitdeleteTodo(id) if(response)alert(response) }} className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-whitepx-2ml-2" Delete /button ) } 使用这种方式你可以通过 async/await 获取 Server Actions 返回的结果。 一种是结合 useActionState: 'useclient' import{useActionState}from"react"; import{deleteTodo}from'./actions'; exportdefaultfunctionDeleteBtn({id}){ const[state,action,isPending]=useActionState(deleteTodo,null) return( button disabled={isPending} onClick={()={ action(id) }} className="bg-indigo-600disabled:bg-gray-500py-2roundedtext-whitepx-2ml-2" {isPending?'Deleting':'Delete'}{state} /button ) } 使用这种方式,Server Actions 的返回结果需要从 state 中获取。 总结本篇我们讲解了 React/Next.js 如何处理数据突变(data mutation)场景,从传统实现到 useFormState + useFormStatus 再到现在的 useActionState,目的在于自动管理挂起状态、错误、Form 等。相信大家通过 4 个实例的代码变化,更能深刻理解使用 useActionState 的好处。 在 Next.js 项目使用 Server Actions 时应该多搭配 useActionState 一起使用。 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2025-04-06_一场跨越时空的女性史诗—多维度解析时下全球爆火韩剧《苦尽柑来遇见你》 下一篇:2020-09-29_杜蕾斯出月饼,文案怎么抄了5年前的自己?

TAG标签:

14
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价