当前位置: 首页 > 科技观察

一篇让我们学习实践React的文章

时间:2023-03-12 16:07:58 科技观察

每天都在写业务代码,但是在写业务代码的时候,经常会觉得自己写的一些代码有点坑爹,但又不知道坑爹在哪里.在今天的文章中,我整理了项目中用到的一些小技巧点。状态逻辑复用在使用ReactHooks之前,我们一般都是复用组件,没有办法复用组件的内部状态。ReactHooks的引入很好的解决了状态逻辑复用,而在我们日常开发中可以实现什么样的状态逻辑复用呢?下面我列举几个我目前在项目中常用的状态复用。为什么useRequest要封装这个hook呢?加载数据时,有很多点可以提取到共享逻辑的加载状态。复用和异常处理constuseRequest=()=>{const[loading,setLoading]=useState(false);const[error,setError]=useState();construn=useCallback(async(...fns)=>{setLoading(true);try{awaitPromise.all(fns.map((fn)=>{if(typeoffn==='function'){returnfn();}returnfn;}));}catch(错误){setError(错误);}finally{setLoading(false);}},[]);return{loading,error,run};};functionApp(){const{loading,error,run}=useRequest();useEffect(()=>{run(newPromise((resolve)=>{setTimeout(()=>{resolve());},2000);}));},[]);return(

);}usePagination我们在使用表格的时候,一般都会使用分页。通过将分页封装成hooks,一是可以引入前端代码量,二是统一了前后端分页的参数,对后端也有帮助。对端口接口的约束。constusePagination=(initPage={total:0,current:1,pageSize:10,})=>{const[pagination,setPagination]=useState(initPage);//接口查询数据的请求参数constqueryPagination=useMemo(()=>({limit:pagination.pageSize,offset:pagination.current-1}),[pagination.current,pagination.pageSize]);consttablePagination=useMemo(()=>{return{...pagination,onChange:(页面,pageSize)=>{setPagination({...pagination,current:page,pageSize,});},};},[pagination]);constsetTotal=useCallback((total)=>{setPagination((prev)=>({...prev,total,}));},[]);constsetCurrent=useCallback((current)=>{setPagination((prev)=>({...prev,current,}));},[]);return{//对antd表使用分页:tablePagination,//对接口查询数据使用queryPagination,setTotal,setCurrent,};};除了上面例子中的两个钩子,其实定义钩子可以无处不在。只要有可以复用的公共逻辑,都可以定义为一个独立的钩子,然后在多个页面或组件中使用。我们在使用redux和react-router的时候,也会使用到它们提供的hooks。在合适的场景中给useState传入一个函数我们在使用useState的setState的时候,大部分时候都会给setState传入一个值,但实际上setState不仅可以传入普通数据,还可以传入一个函数。下面的极端代码描述了几个传入函数的例子。3秒后以下代码输出什么?如下代码所示,还有两个按钮,一个按钮点击后会延时三秒然后给count+1,第二个按钮点击后会直接给count+1,那么如果我先点击延时按钮,然后多次点击非延时按钮,三秒后count的值是多少?import{useState,useEffect}from'react';functionApp(){const[count,setCount]=useState(0);functionhandleClick(){setTimeout(()=>{setCount(count+1);},3000);}functionhandleClickSync(){setCount(count+1);}return(
count:{count}
延迟加一加一
);}exportdefaultApp;我们知道React的功能组件会在其内部状态或外部props发生变化时重新渲染。其实这个重新渲染就是重新执行这个功能组件。当我们点击延迟按钮时,因为三秒后count的值会发生变化,所以此时不??会重新渲染。然后点击直接加一按钮,计数值从1变为2,需要重新渲染。这里需要注意的是,虽然重新渲染了组件,但是在上一次渲染时调用了setTimeout,也就是说setTimeout中的count值是组件第一次渲染时的值。所以即使多次添加第二个按钮,三秒后,执行setTimeout回调时,引用的count值仍然初始化为0,所以三秒后count+1的值为1。如何延迟上面的代码三秒然后输出正确的值?这时候就需要使用setState的方法传入函数,代码如下:import{useState,useEffect}from'react';functionApp(){const[count,setCount]=useState(0);functionhandleClick(){setTimeout(()=>{setCount((prevCount)=>prevCount+1);},3000);}functionhandleClickSync(){setCount(count+1);}return(
count:{count}
延迟加一加一
);}exportdefaultApp;从上面的代码可以看出,setCount(count+1)被改成了setCount((prevCount)=>prevCount+1)。我们传递一个函数给setCount,setCount会调用这个函数,并将之前的状态值作为参数传递给函数,这样我们就可以在setTimeout中得到正确的值了。也可以在useState初始化的时候传入一个函数。请参见以下示例。我们有一个getColumns函数,它将返回表的所有列。同时还有一个计数状态,每秒递增一次。functionApp(){constcolumns=getColumns();const[count,setCount]=useState(0);useEffect(()=>{setInterval(()=>{setCount((prevCount)=>prevCount+1);},1000);},[]);useEffect(()=>{console.log('columnshaschanged');},[columns]);return(
count:{count}
);}上面的代码执行完后,你会发现每次count变化的时候,都会打印出columns发生了变化,而列A变化意味着表格的属性发生了变化,表格将被重新渲染。这时候如果表数据不大,没有复杂的处理逻辑,还好,但是如果表有性能问题,就会导致整个页面的体验很差?其实这个时候有很多解决方案时间。看看怎么用useState来解决?//把columns改成下面的代码const[columns]=useState(()=>getColumns());此时columns的值正在初始化,之后不会再有变化。有人建议我也可以这样写useState(getColumns())。其实这样写也是可以的,但是如果getColumns这个函数本身有复杂的计算,那么实际上虽然useState本身只会初始化一次,但是每次组件重新渲染的时候getColumn还是会被初始化。上面的代码也可以简化为const[columns]=useState(getColumns);理解钩子比较算法的原理constuseColumns=(options)=>{const{isEdit,isDelete}=options;returnuseMemo(()=>{return[{title:'title',dataIndex:'title',key:'title',},{title:'operation',dataIndex:'action',key:'action',render(){return(<>{isEdit&&}{isDelete&&});},},];},[选项]);};functionApp(){constcolumns=useColumns({isEdit:true,isDelete:false});const[count,setCount]=useState(1);useEffect(()=>{console.log('列已更改');},[columns]);return(
setCount(count+1)}>修改计数:{count}
);}如上面的代码,当我们点击修改count的按钮时,我们期望改变的只是count的值,但实际上这个值的列也发生变化。要理解列为什么会变化,我们先来了解一下react比较算法的原理。React比较算法的底层是使用Object.is来比较传入的状态。语法:Object.is(value1,value2);下面的代码是Object.is在比较不同数据类型的数据时的返回值:Object.is('foo','foo');//trueObject.is(window,window);//trueObject.is('foo','bar');//falseObject.is([],[]);//falsevarfoo={a:1};varbar={a:1};Object.is(foo,foo);//trueObject.is(foo,bar);//falseObject.is(null,null);//true//特例Object.is(0,-0);//falseObject.is(0,+0);//trueObject.is(-0,-0);//trueObject.is(NaN,0/0);//true从上面的代码可以看出,Object.is比较的是对象的引用地址,而不是值,所以Object.is([],[]),Object.is({},{})都是假的。对于基本类型,需要注意末尾的四个特殊列,与===不同。回到上面的代码示例,useColumns将传入的options作为useMemo的第二个参数,options是一个对象。当组件的计数状态发生变化时,整个函数组件会重新执行。这时候会调用useColumns,传入{isEdit:true,isDelete:false},这是一个新创建的对象,和上次渲染创建的一样,虽然options的内容是一样的,但是Object.is比较结果仍然为false,因此将重新创建并返回列的结果。通过对标准化组件的二次封装,我们在项目中使用antd作为组件库。虽然antd可以满足大部分的开发需求,但是在某些地方,antd的二次封装不仅可以减少开发代码量,还可以起到页面交互的作用。达到标准化效果。看看下面的场景。我们在开发数据表的时候,一般会用到哪些函数呢?表格可以分为页面。表格的最后一列将有操作按钮。表格顶部将有一个搜索区域。表格顶部可能会有操作按钮等一系列功能,这些功能在系统中会大量使用,它们的实现方法基本相同。这时候,如果能够将这些功能集成并封装成一个标准的组件,不仅可以减少代码量,还可以让页面显示更加统一。以封装表操作列为例,我们一般使用操作列来封装constcolumns=[{title:'operation',dataIndex:'action',key:'action',width:'10%',align:'center',render:(_,row)=>{return(<>handleEdit(row)}>编辑handleDelete(row)}>Delete/>);}}]我们期望的是操作栏可以也可以像表的列一样通过配置生成而不是写jsx。看看怎么封装吧?//定义动作按钮exportinterfaceIActionextendsOmit{//自定义按钮渲染render?:(row:any,index:number)=>React.ReactNode;onClick?:(row:any,index:number)=>void;//是否有确认提示confirm?:boolean;//提示文字confirmText?:boolean;//按钮显示文字text:string;}//定义表格columnexportinterfaceIColumnextendsColumnType{actions?:IAction[];}//然后我们可以定义一个钩子,专门用来修改表的列,添加操作列constuseActionButtons=(columns:IColumn[],actions:IAction[]|undefined):IColumn[]=>{returnuseMemo(()=>{if(!actions||actions.length===0){returncolumns;}return[...列,{align:'center',title:'Operation',key:'__action',dataIndex:'__action',width:Math.max(120,actions.length*85),render(value:any,row:any,index:number){returnactions.map((item)=>{if(item.render){returnitem.render(row,index);}if(item.confirm){returnitem.onClick?.(row,index)}>{item.text}}return(item.onClick?.(row,index)}>{item.text});});}}];},[columns,actions,actionFixed]);};//最后再对表进行一次封装constCustomTable:React.FC=({actions,columns,...props})=>{constactionColumns=useActionColumns(columns,actions)//rendertable}通过上面的封装,当我们再次使用table时,可以这样写compactions:IAction[]=[{text:'Edit',onClick:handleModifyRecord,},];return本文转载自微信公众号《前端有戏》,可以关注转载通过以下二维码获取本文,请联系前端某玩公众号。