问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

这些hook更优雅的管理你的状态

发布网友 发布时间:2024-10-03 06:24

我来回答

1个回答

热心网友 时间:2024-10-13 04:42

本文是深入浅出ahooks源码系列文章的第十二篇,这个系列的目标主要有以下几点:

加深对Reacthooks的理解。

学习如何抽象自定义hooks。构建属于自己的Reacthooks工具库。

培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。

今天我们来聊聊ahooks中那些可以帮助我们更优雅管理我们state(状态)的那些hook。一些比较特殊的,比如cookie/localStorage/sessionStorage,useUrlState等,我们已经单独拿出来细讲了,感兴趣可以看看笔者的历史文章。

useSetState

管理object类型state的Hooks,用法与class组件的this.setState基本一致。

先来了解一下可变数据和不可变数据的含义和区别如下:

可变数据(mutable)即一个数据被创建之后,可以随时进行修改,修改之后会影响到原值。

不可变数据(Immutable)就是一旦创建,就不能再被更改的数据。对Immutable对象的任何修改或添加删除操作都会返回一个新的Immutable对象。

我们知道,ReactFunctionComponents中的State是不可变数据。所以我们经常需要写类似如下的代码:

setObj((prev)=>({...prev,name:'Gopal',others:{...prev.others,age:'27',}}));

通过useSetState,可以省去对象扩展运算符操作这个步骤,即:

setObj((prev)=>({name:'Gopal',others:{age:'27',}}));

其内部实现也比较简单,如下所示:

调用设置值方法的时候,会根据传入的值是否为函数。如果是函数,则入参为旧状态,输出新的状态。否则直接作为新状态。这个符合setState的使用方法。

使用对象拓展运算符,返回新的对象,保证原有数据不可变。

constuseSetState=<SextendsRecord<string,any>>(initialState:S|(()=>S),):[S,SetState<S>]=>{const[state,setState]=useState<S>(initialState);//合并操作,并返回一个全新的值constsetMergeState=useCallback((patch)=>{setState((prevState)=>{//新状态constnewState=isFunction(patch)?patch(prevState):patch;//也可以通过类似Object.assign的方式合并//对象拓展运算符,返回新的对象,保证原有数据不可变returnnewState?{...prevState,...newState}:prevState;});},[]);return[state,setMergeState];};

可以看到,其实就是将对象拓展运算符的操作封装到内部。

还有其他更优雅的方式?我们可以使用use-immer

useImmer(initialState)非常类似于useState。该函数返回一个元组,元组的第一个值是当前状态,第二个是updater函数,它接受一个immerprocer函数或一个值作为参数。

使用如下:

const[person,updatePerson]=useImmer({name:"Michel",age:33});functionupdateName(name){updatePerson(draft=>{draft.name=name;});}functionbecomeOlder(){updatePerson(draft=>{draft.age++;});}

当向更新函数传递一个函数的时候,draft参数可以自由地改变,直到procer函数结束,所做的改变将是不可变的,并成为下一个状态。这更符合我们的使用习惯,可以通过draft.xx.yy的方式更新我们对象的值。

useBoolean和useToggle

这两个都是特殊情况下的值管理。

useBoolean,优雅的管理boolean状态的Hook。

useToggle,用于在两个状态值间切换的Hook。

实际上,useBoolean又是useToggle的一个特殊使用场景。

先看useToggle。

这里使用了typescript函数重载声明入参和出参类型,根据不同的入参会返回不同的结果。比如第一个入参为boolean布尔值,则返回一个元组,第一项为boolean值,第二个为更新函数。优先级从上到下依次变低。

入参可能有两个值,第一个为默认值(认为是左值),第二个是取反之后的值(认为是右值),可以不传,不传的时候,则直接根据默认值取反!defaultValue。

toggle函数。切换值,也就是上面的左值和右值的转换。

set。直接设置值。

setLeft。设置默认值(左值)。

setRight。如果传入了reverseValue,则设置为reverseValue。否则设置为defautValue的取反值。

//TS函数重载的使用functionuseToggle<T=boolean>():[boolean,Actions<T>];functionuseToggle<T>(defaultValue:T):[T,Actions<T>];functionuseToggle<T,U>(defaultValue:T,reverseValue:U):[T|U,Actions<T|U>];functionuseToggle<D,R>(//默认值defaultValue:D=falseasunknownasD,//取反reverseValue?:R,){const[state,setState]=useState<D|R>(defaultValue);constactions=useMemo(()=>{constreverseValueOrigin=(reverseValue===undefined?!defaultValue:reverseValue)asD|R;//切换stateconsttoggle=()=>setState((s)=>(s===defaultValue?reverseValueOrigin:defaultValue));//修改stateconstset=(value:D|R)=>setState(value);//设置为defaultValueconstsetLeft=()=>setState(defaultValue);//如果传入了reverseValue,则设置为reverseValue。否则设置为defautValue的反值constsetRight=()=>setState(reverseValueOrigin);return{toggle,set,setLeft,setRight,};//useToggleignorevaluechange//},[defaultValue,reverseValue]);},[]);return[state,actions];}

而useBoolean是对useToggle的一个使用。如下,比较简单,不细说

exportdefaultfunctionuseBoolean(defaultValue=false):[boolean,Actions]{const[state,{toggle,set}]=useToggle(defaultValue);constactions:Actions=useMemo(()=>{constsetTrue=()=>set(true);constsetFalse=()=>set(false);return{toggle,set:(v)=>set(!!v),setTrue,setFalse,};},[]);return[state,actions];}usePrevious

保存上一次状态的Hook。

其原理,是每次状态变更的时候,比较值有没有发生变化,变更状态:

维护两个状态prevRef(保存上一次的状态)和curRef(保存当前状态)。

状态变更的时候,使用shouldUpdate判断是否发生变化,默认通过Object.is判断。开发者可以自定义shouldUpdate函数,并决定什么时候记录上一次状态。

状态发生变化,更新prevRef的值为上一个curRef,并更新curRef为当前的状态。

constdefaultShouldUpdate=<T>(a?:T,b?:T)=>!Object.is(a,b);functionusePrevious<T>(state:T,shouldUpdate:ShouldUpdateFunc<T>=defaultShouldUpdate,):T|undefined{//使用了useRef的特性,一直保持引用不变//保存上一次值constprevRef=useRef<T>();//当前值constcurRef=useRef<T>();//自定义是否更新上一次的值if(shouldUpdate(curRef.current,state)){prevRef.current=curRef.current;curRef.current=state;}returnprevRef.current;}useRafState

只在requestAnimationFramecallback时更新state,一般用于性能优化。

window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

假如你的操作是比较频繁的,就可以通过这个hook进行性能优化。

重点看setRafState方法,它执行的时候,会取消上一次的setRafState操作。重新通过requestAnimationFrame去控制setState的执行时机。

另外在页面卸载的时候,会直接取消操作,避免内存泄露。

functionuseRafState<S>(initialState?:S|(()=>S)){constref=useRef(0);const[state,setState]=useState(initialState);constsetRafState=useCallback((value:S|((prevState:S)=>S))=>{cancelAnimationFrame(ref.current);ref.current=requestAnimationFrame(()=>{setState(value);});},[]);//unMount的时候,去除监听useUnmount(()=>{cancelAnimationFrame(ref.current);});return[state,setRafState]asconst;}useSafeState

用法与React.useState完全一样,但是在组件卸载后异步回调内的setState不再执行,避免因组件卸载后更新状态而导致的内存泄漏。

代码如下:

在更新的时候,通过useUnmountedRef判断如果组件卸载,则停止更新。

functionuseSafeState<S>(initialState?:S|(()=>S)){//判断是否卸载constunmountedRef=useUnmountedRef();const[state,setState]=useState(initialState);constsetCurrentState=useCallback((currentState)=>{//如果组件卸载,则停止更新if(unmountedRef.current)return;setState(currentState);},[]);return[state,setCurrentState]asconst;}

useUnmountedRef这个我们之前提过,简单回顾下,其实就是在hook的返回值中标记组件为已卸载。

constuseUnmountedRef=()=>{constunmountedRef=useRef(false);useEffect(()=>{unmountedRef.current=false;//如果已经卸载,则会执行return中的逻辑return()=>{unmountedRef.current=true;};},[]);returnunmountedRef;};useGetState

给React.useState增加了一个getter方法,以获取当前最新值。

其实现如下:

其实就是通过useRef记录最新的state的值,并暴露一个getState方法获取到最新的。

setObj((prev)=>({name:'Gopal',others:{age:'27',}}));0

这在某一些情况下,可以避免React的闭包陷阱。如官网例子:

setObj((prev)=>({name:'Gopal',others:{age:'27',}}));1

假如这里不使用getCount(),而是直接使用count,是获取不到最新的值的。

总结与思考

React的functionComponent的状态管理还是比较灵活,我们可以针对一些场景进行封装和优化,从而更优雅的管理我们的state状态,希望ahooks这些封装能对你有所帮助。

原文:https://juejin.cn/post/7111610143913017358
这些hook更优雅的管理你的状态

useBoolean,优雅的管理boolean状态的Hook。 useToggle,用于在两个状态值间切换的Hook。 实际上,useBoolean又是useToggle的一个特殊使用场景。 先看useToggle。 这里使用了typescript函数重载声明入参和出参类型,根据不同的入参会返回不同的结果。比如第一个入参为boolean布尔值,则返回一个元组,第一项为boolean值,第二...

简述环境影响评价的程序与基本内容是什么

第二种是环境影响报告表,指的是对建设项目本身可能产生或是能周围环境造成较大污染和影响的; 第三种是环境影响登记表,指的是造成的污染或影响较轻。 国家对这三种形式的认定有个专门的目录,叫《建设项目环境影响分类管理目录》,对所有类别的项目...

Vue3中Hook函数,解锁新技能!

Vue3中的Hook与Vue2的mixins相似,但更灵活,能处理更多生命周期和响应式问题。尽管有优点如代码复用和清晰逻辑,但缺点包括可能的命名冲突和对参数传递的限制。掌握Hook的书写规范,能让你在Vue3开发中更加游刃有余。总的来说,通过理解和使用Vue3的Hook,我们能更优雅地处理组件状态和视图更新,是提升...

Recoil 这个状态管理库,用起来可能是最爽的

在这个领域,状态管理库众多,Redux和Mobx两大阵营争论不休。Redux坚持函数式思维,严谨而优雅;Mobx则以高效、自动化的特点著称,无需担心性能优化。然而,随着Hooks的兴起,状态管理的需求发生了变化。人们追求更简洁的样板代码和更高的效率,新一代状态管理方案应运而生。它们需要支持分布式定义、状态原子...

别再用kill-9了,这才是微服务上下线的正确姿势!

目前大多数应用的部署模式不管是jetty部署模式还是docker部署模式(同样使用jetty镜像),本质上用的都是外置容器。那么这个情况就比较困难了,至少在应用层面无法观察到外部容器的运行状态,并且容器本身没有提供什么hook给你实现。那么和优雅上线一样,需要RPC框架提供优雅上线接口来初始化整个应用的生命周期,并...

React-query

React-query是一个用于管理前端应用程序中服务端状态的库。它提供了优雅的API来处理数据获取、缓存、失效、重试等数据管理任务,帮助开发者简化复杂的数据处理逻辑。React Query与React集成,通过``组件提供全局状态管理,并通过`useQuery`等Hook函数来简化数据操作。React-query的实现依赖于创建一个全局`Query...

服务优雅下线

Spring Boot 应用提供了/pause端点,利用该端点可实现优雅下线。使用方式:在想下线应用的application.yml中添加配置,从而启用并暴露/pause端点:发送 POST 请求到/actuator/pause端点:curl -X POST http://你想停止的服务实例地址/actuator/pause 该应用在 Eureka Server 上的状态会被标记为DOWN,但是...

Kubernetes 发布服务的时候发生 5xx? 搞懂 Pod 如何优雅退出

解决方法是在程序退出时等待一段时间再结束,例如通过设置一个合理的超时时间,比如15秒,以允许kube-proxy、Ingress控制器和CoreDNS等组件传播endpoint删除信息。使用preStop hook进行优雅退出是一种替代方法。它在程序退出时执行,但此时程序并不知道将要退出,因此不会接收到退出信号。预退出时间被计算在...

如何在项目中优雅的使用对话框?

如果需要给对话框(C)传递参数,一般情况我们会使用props传入,意味着状态的管理必须也是子组件(A、B)的父组件或者更高一级进行管理和维护,但是其实这些状态可能只需要在子组件A或者B中维护。这种情况下,我们就需要自定义事件,将状态进行回传,比较麻烦。const?MySalesOrders:?React.FC?=?()?=&gt;?{??const?[visible...

React.js是什么?React.js教程?

状态管理:确保业务流程的精准把控Redux和MobX是两种流行的状态管理工具,它们分别以事件驱动和简洁易用见长。Redux提供高度可预测的状态存储,适用于大型项目,而MobX则更适合快速原型开发。其他如Hookstate,作为useState的扩展,提供了更丰富的状态管理功能。基础工具包:构建完整应用的基石从Package.json的元...

...initRender 、生命周期的调用 callHook 、异常处理机制

调用callHook函数触发beforeCreate生命周期,该函数会遍历队列中的每个任务,并以当前组件实例为上下文执行这些函数。值得一提的是,在调用生命周期钩子时,Vue会暂时禁用依赖收集,以避免不必要的渲染操作。这一机制通过pushTarget和popTarget函数实现,确保在执行钩子函数后,状态能正确恢复。异常处理机制Vue具有...

技术状态管理的目的 技术状态管理的意义 管理状态 为什么需要状态管理 技术状态管理 设备状态管理 技术状态管理标准 hook的意思 chorus和hook区别
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
...9300的手机,拍照的时候因为屏幕很大自拍的时候按不到开关~前面的... 帮我的作文取个好题目 我的作文是仿写《生命 生命》这篇文章的。_百度... 40 多亿美金BD背后的超前押注者 40 多亿美金BD背后的超前押注者 ...中间有条金色拉链,上面是稍微肥点的怎么搭配外套和里衣 仿照课文生命 生命写一篇作文 要写3件事,380字左右. 如何在微信上面创建一个微信号? 东安黑豹股份有限公司公司简介 2022励志句子精选10句 自信的生命最美丽 梦幻模拟战手游兰迪乌斯转职攻略:兰迪乌斯转职选择推荐 为什么我的QQ帐号老是被冻结!解冻之后没多久又被冻结!聊着聊着对方就... 湘菜嘅钙骨汤,求了解渗透,以及做法 从根上理解ReactHooks的闭包陷阱 为什么申请交行信用卡老被拒? 魔兽世界自从出熊猫人之谜之后官网怎么找不到冲点卡的选项了呢?我是... 为什么有时候PPT放音乐没有声音? 魔兽世界熊猫人,进入界面之后,又开始加载,过了很久都还停留在1.几%... 魔兽世界更新熊猫人的时候到第二阶段能进入游戏的时候进不去,然后就... 我是1978年正月十九日辰时生,请高人帮我算算近5年运气。谢谢 77年5月4号辰时出生女,有几个子女谢 吃薄冰打旋子的乐趣无穷,是哪篇文章 澳洲国立大学应用统计专业介绍 ...农历生日是4月19号 谁能帮我看一下我是什么星座的,谢啦! 柠檬酸钨是络合剂吗? 磷钨杂多酸与磷钨杂多酸盐在环氧化反应中的的催化机理是不是一样的... 钨酸钠理化性质 磷钨杂多酸磷钨杂多酸 如何为自己的宝宝制定成长计划呢? 小孩子不愿走路,聪明家长怎样做让他爱上走路 为什么将各个最高级奖项设置为诺贝尔奖 注册安全工程师考哪个专业更吃香 如何在项目中优雅的使用对话框? 如何输入邮箱符号@? 我的QQ老被冻结,注册一个没一个,用短信验证码解冻后,过一会就又... 高级鼠标垫都有哪些著名品牌? 注册安全工程师报哪个领域好 显卡错误87怎么解决? ...文件后 立即出现蓝屏 重启后explorer.exe 应用程序错误 冬季洗澡的13个禁忌 沱茶怎么弄开,沱茶的正确打开方式 八个时间洗澡会短命 如何开普洱沱茶的方法 普洱茶沱茶怎么泡 起床哪些习惯注定短命?起床应该如何做比较好? 普洱沱茶生茶怎么泡 普洱生小沱茶要怎么泡 今晚天天向上节目中,江珊史可出场时候放的歌曲是什么? 敲门贾平凹散文集 日常消费大家是喜欢刷卡还是用现金啊? 现金还是刷卡漫画