热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

React使用中值得优化的7个点

分享几个将改善你的

?

技术分享图片


前言

原文链接:https://dev.to/awnton/7-code-smells-in-react-components-5f66

作者信息:Anton Gunnarsson

翻译许可:


技术分享图片图片

Agree


正文

自从使用?React?后,我见过越来越多可值得优化的点,比如:


  • 大量的?props



  • props?的不兼容性



  • props?复制为?state



  • 返回?JSX?的函数



  • state?的多个状态



  • useState?过多



  • 复杂的?useEffect



在本文中,我想分享几个技巧,这些技巧将改善你的React代码。


大量的 props

如果需要把大量的?props?传递到一个组件中,那么很有可能 该组件可再进一步拆分。

问题来了,“大量” 具体是多少呢?答案是 看情况。

假设你正在开发 一个包含 20 个或更多?props?的组件时,你想再添加一些?props?完善其他功能,这时有两点可以参考 是否应拆分组件:

该组件是否做了多件事?

像函数一样,一个组件应该只做好一件事,所以考虑下 将组件拆分成多个小组件是否会更好。

例如,该组件存在?props?的不兼容性?或?返回?JSX?的函数。

该组件是否可被合成?

开发中,组合是一种很好的模式但经常被忽视。

如果你的组件中存在将不相干逻辑塞到一起的情况,是时候考虑使用组合了。

假设我们有一个表单组件来处理某组织的用户信息:

??user={userData}
??organization={organizationData}
??categories={categoriesData}
??locatiOns={locationsData}
??OnSubmit={handleSubmit}
??OnCancel={handleCancel}
??...
/>

通过该组件的?props?,我们可看到它们都与组件提供的功能密切相关。

该组件看起来并无大碍,但如果将其中的一些?props?分担到子组件,那么数据流就会更清晰。


??
??
??
??


现在,我们已经看到该表单组件只处理提交和取消动作,其他范围内的事情,都交给了对应的子组件。

是否传递了很多有关配置的?props

在某些情况下,将多个有关配置的?props?组合成一个?options?是个不错的实践。

假设我们有一个可显示某种表格的组件:

??data={gridData}
??pagination={false}
??autoSize={true}
??enableSort={true}
??sortOrder="desc"
??disableSelection={true}
??infiniteScroll={true}
??...
/>

我们可以很清楚地看出,该组件除了?data?外其余的?props?都是与配置有关的。

如果将多个配置?props?合成为一个?options?,就可更好地控制组件的选项,规范性也得到提升。

const?options?=?{
??pagination:?false,
??autoSize:?true,
??enableSort:?true,
??sortOrder:?‘desc‘,
??disableSelection:?true,
??infiniteScroll:?true,
??...
}
??data={gridData}
??optiOns={options}
/>


props 的不兼容性

避免组件之间传递不兼容的?props。

假设你的组件库中有一个??组件,而该组件开始时仅用于处理文本,但过了一段时间后,你将它用于电话号码处理。

你的实现可能如下:

function?Input({?value,?isPhoneNumberInput,?autoCapitalize?})?{
??if?(autoCapitalize)?capitalize(value)
??return?
}

问题在于,isPhoneNumberInput?与?autoCapitalize?之间并不存在关联,将一个手机号首字母大写是没有任何意义的。

在这种情况下,我们可以将其分割成多个小组件,来明确具体的职责,如果有共享逻辑,可以将其放到?hooks?中。

function?TextInput({?value,?autoCapitalize?})?{
??if?(autoCapitalize)?capitalize(value)
??useSharedInputLogic()
??return?
}
function?PhoneNumberInput({?value?})?{
??useSharedInputLogic()
??return?
}

虽然上面例子有点勉强,可当发现组件的props存在不兼容性时,是时候考虑拆分组件了。


props 复制为 state

如何更好地将?props?作为?state?的初始值。

有如下组件:

function?Button({?text?})?{
??const?[buttonText]?=?useState(text)
??return?
}

该组件将?text?作为?useState?的初始值,可能会导致意想不到的行为。

实际上该组件已经关掉了?props?的更新通知,如果?text?在上层被更新,它将仍呈现 接受到?text?的第一次值,这更容易使组件出错。

一个更实际场景是,我们想基于?props?通过大量计算来得到新的?state。

在下面的例子中,slowlyFormatText?函数用于格式化?text,注意 需要很长时间才能完成。

function?Button({?text?})?{
??const?[formattedText]?=?useState(()?=>?slowlyFormatText(text))
??return?
}

解决此问题 最好的方案是 使用?useMemo?代替?useState。

function?Button({?text?})?{
??const?formattedText?=?useMemo(()?=>?slowlyFormatText(text),?[text])
??return?
}

现在?slowFormatFormat?仅在?text?更改时运行,并且没有阻断 上层组件更新。

进一步阅读:Writing resilient components by Dan Abramov。


返回 JSX 的函数

不要从组件内部的函数中返回?JSX。

这种模式虽然很少出现,但我还是时不时碰到。

仅举一个例子来说明:

function?Component()?{
??const?topSection?=?()?=>?{
????return?(
??????
????????

Component?header


??????
????)
??}
??const?middleSection?=?()?=>?{
????return?(
??????

????????

Some?text


??????

????)
??}
??const?bottomSection?=?()?=>?{
????return?(
??????

????????

Some?footer?text


??????

????)
??}
??return?(
????

??????{topSection()}
??????{middleSection()}
??????{bottomSection()}
????

??)
}

该例子虽然看起来没什么问题,但其实这会破坏代码的整体性,使维护变得困难。

要么把函数返回的?JSX?直接内联到组件内,要么将其拆分成一个组件。

有一点需要注意,如果你创建了一个新组件,不必将其移动到新文件中的。

如果多个组件紧密耦合,将它们保存在同一个文件中是有意义的。


state 的多个状态

避免使用多个布尔值来表示组件状态。

当编写一个组件并多次迭代后,很容易出现这样一种情况,即内部有多个布尔值来表示 该组件处于哪种状态。

比如下面的例子:

function?Component()?{
??const?[isLoading,?setIsLoading]?=?useState(false)
??const?[isFinished,?setIsFinished]?=?useState(false)
??const?[hasError,?setHasError]?=?useState(false)
??const?fetchSomething?=?()?=>?{
????setIsLoading(true)
????fetch(url)
??????.then(()?=>?{
????????setIsLoading(false)
????????setIsFinished(true)
??????})
??????.catch(()?=>?{
????????setHasError(true)
??????})
??}
??if?(isLoading)?return?
??if?(hasError)?return?
??if?(isFinished)?return?
??return?
}

当按钮被点击时,我们将?isLoading?设置为?true,并通过?fetch?执行网络请求。

如果请求成功,我们将?isLoading?设置为?false,isFinished?设置为?true,如果有错误,将?hasError?设置为?true。

虽然这在技术上是可行的,但很难推断出组件处于什么状态,而且不容易维护。

并且有可能最终处于“不可能的状态”,比如我们不小心同时将?isLoading?和?isFinished?设置为?true。

解决此问题一劳永逸的方案是 使用枚举来管理状态。

在其他语言中,枚举是一种定义变量的方式,该变量只允许设置为预定义的常量值集合,虽然在Javascript?中不存在枚举,但我们可以使用字符串作为枚举:

function?Component()?{
??const?[state,?setState]?=?useState(‘idle‘)
??const?fetchSomething?=?()?=>?{
????setState(‘loading‘)
????fetch(url)
??????.then(()?=>?{
????????setState(‘finished‘)
??????})
??????.catch(()?=>?{
????????setState(‘error‘)
??????})
??}
??if?(state?===?‘loading‘)?return?
??if?(state?===?‘error‘)?return?
??if?(state?===?‘finished‘)?return?
??return?
}

通过这种方式,完全杜绝了出现 不可能状态的情况,并更利用扩展。

如果你使用?TypeScript?开发的话,则可以从定义时就实现枚举:

const?[state,?setState]?=?useState<‘idle‘?|?‘loading‘?|?‘error‘?|?‘finished‘>(‘idle‘)


useState 过多

避免在同一个组件中使用太多的?useState。

一个包含许多?useState?的组件可能会做多件事情,可以考虑是否要拆分它。

当然也存在一些复杂的场景,我们需要在组件中管理一些复杂的状态。

下面是自动输入组件的例子:

function?AutocompleteInput()?{
??const?[isOpen,?setIsOpen]?=?useState(false)
??const?[inputValue,?setInputValue]?=?useState(‘‘)
??const?[items,?setItems]?=?useState([])
??const?[selectedItem,?setSelectedItem]?=?useState(null)
??const?[activeIndex,?setActiveIndex]?=?useState(-1)
??const?reset?=?()?=>?{
????setIsOpen(false)
????setInputValue(‘‘)
????setItems([])
????setSelectedItem(null)
????setActiveIndex(-1)
??}
??const?selectItem?=?(item)?=>?{
????setIsOpen(false)
????setInputValue(item.name)
????setSelectedItem(item)
??}
??...
}

我们有一个?reset?函数,可以重置所有状态,还有一个?selectItem?函数,可更新一些状态。

这些函数都离不开?useState?定义的状态。如果功能继续迭代,那么函数就会越来越多,状态也会随之增加,数据流就会变得模糊不清。

在这种情况下,使用?useReducer?来代替 过多的?useState?是一个不错的选择。

const?initialState?=?{
??isOpen:?false,
??inputValue:?"",
??items:?[],
??selectedItem:?null,
??activeIndex:?-1
}
function?reducer(state,?action)?{
??switch?(action.type)?{
????case?"reset":
??????return?{
????????...initialState
??????}
????case?"selectItem":
??????return?{
????????...state,
????????isOpen:?false,
????????inputValue:?action.payload.name,
????????selectedItem:?action.payload
??????}
????default:
??????throw?Error()
??}
}
function?AutocompleteInput()?{
??const?[state,?dispatch]?=?useReducer(reducer,?initialState)
??const?reset?=?()?=>?{
????dispatch({?type:?‘reset‘?})
??}
??const?selectItem?=?(item)?=>?{
????dispatch({?type:?‘selectItem‘,?payload:?item?})
??}
??...
}

通过使用?reducer,我们封装了管理状态的逻辑,并将复杂的逻辑移出了组件,这使得组件更容易维护。

进一步阅读:state reducer pattern by Kent C. Dodds。


复杂的 useEffect

避免在?useEffect?中做太多事情,它们使代码易于出错,并且难以推理。

下面的例子中 犯了一个很大的错误:

function?Post({?id,?unlisted?})?{
??...
??useEffect(()?=>?{
????fetch(`/posts/${id}`).then(/*?do?something?*/)
????setVisibility(unlisted)
??},?[id,?unlisted])
??...
}

当?unlisted?改变时,即使?id?没有变,也会调用?fetch。

正确的写法应该是 将多个依赖分离:

function?Post({?id,?unlisted?})?{
??...
??useEffect(()?=>?{?//?when?id?changes?fetch?the?post
????fetch(`/posts/${id}`).then(/*?...?*/)
??},?[id])
??useEffect(()?=>?{?//?when?unlisted?changes?update?visibility
????setVisibility(unlisted)
??},?[unlisted])
??...
}



结束语

以上就是我分享的全部。请记住,这些绝不是规则,而是表明某些东西可能是“错误的”。



  1. 技术分享图片


推荐阅读
  • 在Eclipse中提升开发效率,推荐使用Google V8插件以增强Node.js的调试体验。安装方法有两种:一是通过Eclipse Marketplace搜索并安装;二是通过“Help”菜单中的“Install New Software”,在名称栏输入“googleV8”。此插件能够显著改善调试过程中的性能和响应速度,提高开发者的生产力。 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 在 Axublog 1.1.0 版本的 `c_login.php` 文件中发现了一个严重的 SQL 注入漏洞。该漏洞允许攻击者通过操纵登录请求中的参数,注入恶意 SQL 代码,从而可能获取敏感信息或对数据库进行未授权操作。建议用户尽快更新到最新版本并采取相应的安全措施以防止潜在的风险。 ... [详细]
  • POJ 2482 星空中的星星:利用线段树与扫描线算法解决
    在《POJ 2482 星空中的星星》问题中,通过运用线段树和扫描线算法,可以高效地解决星星在窗口内的计数问题。该方法不仅能够快速处理大规模数据,还能确保时间复杂度的最优性,适用于各种复杂的星空模拟场景。 ... [详细]
  • 在 Mac 上查看隐藏文件和文件夹的详细指南。通过终端命令,您可以轻松地显示或隐藏这些文件。具体步骤如下:输入 `defaults write com.apple.finder AppleShowAllFiles -bool true` 以显示所有隐藏文件,或使用 `defaults write com.apple.finder AppleShowAllFiles -bool false` 以重新隐藏它们。此方法适用于各种版本的 macOS,帮助用户更好地管理和访问系统文件。 ... [详细]
  • 本文详细解析了逻辑运算符“与”(&&)和“或”(||)在编程中的应用。通过具体示例,如 `[dehua@teacher~]$[$(id -u) -eq 0] && echo "You are root" || echo "You must be root"`,展示了如何利用这些运算符进行条件判断和命令执行。此外,文章还探讨了这些运算符在不同编程语言中的实现和最佳实践,帮助读者更好地理解和运用逻辑运算符。 ... [详细]
  • ClassList对象学习心得与表单事件非空校验技巧
    ClassList对象学习心得与表单事件非空校验技巧 ... [详细]
  • 在跨线程调用UI控件方法时,通常使用同步调用机制,如 `控件.Invoke(Delegate, 参数)`。这里需要声明并实现一个委托,因为控件本身并不知道如何处理跨线程操作。通过将具体的实现逻辑封装在委托中,控件可以正确地执行这些操作,确保线程安全性和UI的一致性。此外,为了提高性能和可维护性,建议对频繁的跨线程调用进行优化,例如使用异步调用或批量处理请求。 ... [详细]
  • 深入解析Java虚拟机的内存分区与管理机制
    Java虚拟机的内存分区与管理机制复杂且精细。其中,某些内存区域在虚拟机启动时即创建并持续存在,而另一些则随用户线程的生命周期动态创建和销毁。例如,每个线程都拥有一个独立的程序计数器,确保线程切换后能够准确恢复到之前的执行位置。这种设计不仅提高了多线程环境下的执行效率,还增强了系统的稳定性和可靠性。 ... [详细]
  • 本指南介绍了如何在ASP.NET Web应用程序中利用C#和JavaScript实现基于指纹识别的登录系统。通过集成指纹识别技术,用户无需输入传统的登录ID即可完成身份验证,从而提升用户体验和安全性。我们将详细探讨如何配置和部署这一功能,确保系统的稳定性和可靠性。 ... [详细]
  • 题目 E. DeadLee:思维导图与拓扑结构的深度解析问题描述:给定 n 种食物,每种食物的数量由 wi 表示。同时,有 m 位朋友,每位朋友喜欢两种特定的食物 x 和 y。目标是通过合理分配食物,使尽可能多的朋友感到满意。本文将通过思维导图和拓扑排序的方法,对这一问题进行深入分析和求解。 ... [详细]
  • 深入解析Linux内核中的进程上下文切换机制
    在现代操作系统中,进程作为核心概念之一,负责管理和分配系统资源,如CPU和内存。深入了解Linux内核中的进程上下文切换机制,需要首先明确进程与程序的区别。进程是一个动态的执行流,而程序则是静态的数据和指令集合。进程上下文切换涉及保存当前进程的状态信息,并加载下一个进程的状态,以实现多任务处理。这一过程不仅影响系统的性能,还关系到资源的有效利用。通过分析Linux内核中的具体实现,可以更好地理解其背后的原理和技术细节。 ... [详细]
  • 如何在PDF文档中添加新的文本内容?
    在处理PDF文件时,有时需要向其中添加新的文本内容。这是否可以直接实现呢?有哪些简便且免费的方法可供选择?使用极速PDF阅读器打开文档后,可以通过点击左上角的“注释”按钮切换到注释模式,并选择相应的工具进行编辑。此外,还可以利用其他功能丰富的PDF编辑软件,如Adobe Acrobat DC或Foxit PhantomPDF,它们提供了更多高级的编辑选项,能够满足更复杂的需求。 ... [详细]
  • 题目要求解决一个有趣的编程挑战,即计算由四个自然数 \( p, q, r, s \) 组成的分数序列的和。具体来说,需要编写一个 C# 程序来处理这些自然数,并通过特定的数学运算得出最终结果。该任务不仅考验编程技能,还涉及对数学公式的理解和应用。 ... [详细]
  • Nginx 反向代理配置与应用指南
    本文详细介绍了 Nginx 反向代理的配置与应用方法。首先,用户可以从官方下载页面(http://nginx.org/en/download.html)获取最新稳定版 Nginx,推荐使用 1.14.2 版本。下载并解压后,通过双击 `nginx.exe` 文件启动 Nginx 服务。文章进一步探讨了反向代理的基本原理及其在实际应用场景中的配置技巧,包括负载均衡、缓存管理和安全设置等,为用户提供了一套全面的实践指南。 ... [详细]
author-avatar
HIGO
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有