本文总结并摘录了笔者在实际工作经验中使用React的一些错误方式,希望能帮助大家改掉这些相同的错误。1.Props透传props透传是将单个props从父组件向下传递到多个层的做法。理想情况下,道具不应超过两层。当我们选择多层交付时,会造成一些性能问题,这也让React官方很头疼。通过道具可能会导致不必要的重新渲染。因为当props改变时,React组件总是会被重新渲染,而那些不需要props而只提供传递函数的中间层组件将被渲染。除了性能问题,props透传会导致数据难以追踪,对于很多想看懂代码的人来说也是一个很大的挑战。constA=()=>{const[title,setTitle]=useState('')return
{title}}解决这个问题的方法有很多,比如ReactContextHook,或者像Redux这样的库。但是使用Redux需要一些额外的编码,它更适合单一状态改变很多东西的复杂场景。使用ContextHook的简单项目选择是更好的选择。2.导入的代码超出实际代码React是一个前端框架,代码量较大。当我们编写React程序时,我们应该避免导入许多未使用的模块。因为它们也会被打包成运行时代码,发送给用户的客户端/浏览器/移动设备。额外的依赖会导致应用体积膨胀,增加用户的加载时间,拖慢网页速度,降低用户体验。import_from'lodash'//导入整个包import_mapfrom'lodash/map'//只导入需要的包为了保证良好的用户体验,我们应该将FCP保持在1.8秒以内,所以我们需要简化代码大小。现代打包器具有tree-shaking功能,并使用各种方法来缩小和压缩我们用于生产的代码,例如webpack。但在某些情况下它并不能很好地去除无用代码,最好知道应该打包哪些代码,而不是依赖打包工具来尝试修复我们的代码问题。当前的JavaScript已经经历了很多次重大的更新,拥有了很多的新特性。以前我们需要借助lodash这样的库来实现这些功能,但是现在lodash的优势正在慢慢减弱。当然,这取决于您的用户使用的浏览器和JavaScript版本。但是我们大多数人使用babel或类似的转译器来处理这个问题。现在几乎每个人都在使用Chrome,对吧?其他图书馆也是如此。3.不要把业务逻辑和组件逻辑分开过去,很多人认为React组件应该包含逻辑,逻辑是组件的一部分。但放在今天来看,这种观点是有问题的。constExample=()=>{const[data,setData]=useState([])useEffect(()=>{fetch('...').then(res=>res.json()).then(data=>{constfilteredData=data.filter(item=>item.status===ture)setData(filteredData)})},[])returnclickme}正确的做法是:useEffect的回调函数应该返回一个解除绑定的函数。useEffect应该提供第二个参数,它是一个空数组,并且保证只运行一次。importReact,{useMemo}from'react'constUseEffectBadExample=()=>{useEffect(()=>{constclickHandler=e=>console.log('e:',e)document.getElementById('btn').addEventListener('click',clickHandler)return()=>document.getElementById('btn').removeEventListener('click',clickHandler)},[])return点击我}6.useState使用不当useState也是React中使用频率最高的两个Hooks之一。但让很多人感到困惑的是,useState可能并没有达到预期的效果。例如,一个图像压缩组件:[...files]//这里的文件总是[]cloneFiles.map(//一些逻辑...)setFiles(cloneFiles)})}return }应该更改为:functionCompress(){const[files,setFiles]=useState([])consthandleChange=(newFiles)=>{api(newFiles).then((res)=>{setFiles((oldFiles)=>{constcloneFiles=[...files]//这是最新的文件returncloneFiles.map(//一些逻辑...)})})}return }原因是函数是基于当前闭包使用的状态。但是状态更新后,会触发渲染并创建新的上下文,而不会影响之前的闭包。所以要使程序按预期执行,必须使用如下语法:setFiles(oldFiles=>[...oldFiles,...res.data])7.布尔运算符的错误使用大多数时候我们会使用布尔运算符values来控制页面上某些元素的渲染,这是很正常的事情。还有一些其他方法可以处理此逻辑,最常见的是&&运算符,这也完全是JavaScript的一项功能,但有时会产生意想不到的后果。consttotal=0constComponent=()=>total&&`产品总数:${total}`当我们需要显示产品数量时,如果数量为0,则只显示0而不显示总数产品数量:0。原因是JavaScript对待0,所以最好不要依赖JavaScript的布尔真假比较。详细参考前端高级面试题正确回答方式:consttotal=0constComponent=()=>{consthasItem=total>0returnhasItem&&`商品总数:${total}`}8.处处使用三元表达式三元表达式是一种非常简洁的语法,在短代码中非常令人满意。所以很多人喜欢在React中使用三元表达式来渲染组件。但它的问题是难以扩展。最简单的三元表达式没有问题,但是多个三元表达式一旦组合在一起,就形成了一个非常大的组件,难以阅读。importReact,{useMemo}from'react'constVIPExample=({vipLevel})=>{return( ...<-->/>}constVIPExample=({vipLevel})=>{return(}当不使用TypeScript或不定义propsTypes在的情况下,我们可以自由使用props.xxx来访问道具。为了解决这个问题,我们可以选择使用TypeScript来声明组件props的类型。如果您不使用TypeScript,则可以使用propTypes。还建议以破坏性的方式使用道具。constExample=({title,content})=>{return}例子。propTypes={title:PropTypes.string.isRequired,content:PropTypes.string.isRequired}我们可以一眼看出组件需要哪些props。当我们尝试访问props上不存在的属性时,我们会收到警告。10.不要拆分大型应用程序的代码大型应用程序意味着大量的组件。这时候我们就应该使用代码拆分,将应用拆分成多个js文件,在文件使用的时候加载。这使应用程序的初始包大小保持较小,并允许用户更快地启动网页。react-loadable是专门处理这个问题的第三方库。使用它,我们可以很好的拆分组件。importLoadablefrom'react-loadable'importLoadingfrom'loading'constLoadableComponent=Loadable({loader:()=>import('./component'),loading:Loading})exportdefault()=> 总结React为我们提供了强大的开发生态系统和开发工具集,我们可以比以往更轻松地创建Web应用程序。然而,它是一套工具,工具可能会被滥用。只有按预期使用工具,并以JavaScript优先的方式,才能让我们创建更干净、更强大、性能更高的代码。作为开发者,不断完善自己的代码,让用户用起来舒服,让其他开发者读起来舒服,是我们应该努力的方向和目标。我的10条建议可以作为你用好React的一个起点,希望能帮助你避免很多在开发过程中容易出现的错误。
...
}把组件和逻辑放在一起,组件变得复杂,在修改或增加业务逻辑时,对开发者来说更??加复杂,理解整个过程也更具挑战性。constExample=()=>{const{data,error}=useData()return...
}分离组件和逻辑有两个好处:关注点分离。重用业务逻辑。4.每次渲染重复工作即使你是一个经验丰富的React老手,你可能仍然没有完全理解渲染这个东西。渲染很频繁,而且常常出乎意料。这是使用React编写组件的核心原则之一,在编写React组件时应牢记于心。这也意味着当组件被渲染时,一些逻辑将被重新执行。React提供了两个Hook,useMemo和useCallback。如果使用得当,这些Hooks可以缓存计算结果或函数,减少不必要的重复渲染,最终提升性能。importReact,{useMemo}from'react'constMemoExample=({items,filter})=>{constfilteredItems=useMemo(()=>{returnitems.filter(filter)},[filter,items])returnfilteredItems.map(item=>{item}
)}上面的例子是item列表的展示,需要通过一定的条件过滤,最后展示给用户。这种数据过滤在前端是不可避免的,所以我们可以使用useMemo来缓存过滤数据的过程,这样只有当items和filter发生变化时才会重新渲染。5、useEffect使用不当useEffect是React中使用最多的Hooks之一。在class组件时代,componentDidMount是一个通用的生命周期函数,用来做一些数据请求,事件绑定等,在Hooks时代,useEffect已经取代了它。但是不正确地使用useEffect最终可能会创建多个事件绑定。以下是错误的用法。importReact,{useMemo}from'react'constuseEffectBadExample=()=>{useEffect(()=>{constclickHandler=e=>console.log('e:',e)document.getElementById('btn').addEventListener('click',clickHandler)})return会员系统{vipLevel===0?():vipLevel===1?(
)}这种代码没有功能性错误,但可读性差。有两种方法可以解决。第一种是使用条件判断代替三元表达式。importReact,{useMemo}from'react'constVIPDetail=(vipLevel)=>{if(vipLevel===0)returnif(vipLevel===1)return亲爱的青铜VIP,您有3个特权:...
):vipLevel===2?(...
):...
}Respect青铜VIP,你有3个特权:...
//...}constVIPExample=({vipLevel})=>{return(会员系统{VIPDetail(vipLevel)}
)如果每个分支中的组件都比较复杂,我们更进一步,我们使用抽象来封装组件。importReact,{useMemo}from'react'constVIPZeroDetail=({vipLevel})=>{if(vipLevel!==0)returnnullreturn}constVIPOneDetail=({vipLevel})=>{if(vipLevel!==1)returnnullreturn亲爱的青铜VIP,您有3个特权:...
}//...constVIP=({vipLevel})=>{返回<>会员系统
)}在大多数情况下,条件判断的方法就足够了。使用抽象封装组件的缺点是组件过于分散,同步逻辑麻烦。9.不定义propTypes或不解构propsReact中的大部分内容与JavaScript几乎相同。Reactprops在JavaScript中也只是对象,这意味着我们可以在对象中传递许多不同的值,组件很难知道它们。这使得组件使用props更加麻烦。许多人喜欢通过这种方式访问??道具。constExample=(props)=>{return{props.title}
{props.content}
{title}
{content}