开发React应用程序时使用Redux的11个技巧

在开发React应用程序时,就代码体系结构而言,小型项目通常比大型项目更灵活。 使用针对大型应用程序的实用指南创建此类项目没有错。 但是,对于小型项目而言,所有这些可能根本就没有必要。 应用程序越小,它表示在其中使用简单的解决方案就越“居高临下”,可能不是最优的,但不需要花费很多时间来实施。



尽管如此,我想指出的是,本材料中将给出的一些建议针对任何规模的React应用。

如果您从未创建过生产应用程序,那么本文可以帮助您为大规模解决方案的开发做准备。 这样的事情很可能成为您的下一个项目之一。 程序员可能遇到的最糟糕的事情是,当他在一个项目上工作并意识到他需要重构大量的代码以提高应用程序的可伸缩性和可维护性时。 如果重构前项目中没有任何单元测试,那么一切看起来都会更糟。

该材料的作者请读者相信这一点。 他也遇到过类似情况。 因此,他得到了一些需要在一定时间内解决的任务。 起初,他认为自己所做的一切都很出色。 产生这种想法的原因是,他的Web应用程序进行了更改之后,可以继续工作,同时又可以继续快速工作。 他知道如何使用Redux,如何在用户界面组件之间建立正常的交互。 在他看来,他深刻理解了减速器和动作的概念。 他感到无懈可击。

但是这里的前途一片光明。

经过几个月的研究,该应用程序已添加了15多个新功能。 之后,该项目失控了。 使用Redux库的代码已变得很难维护。 为什么会这样呢? 刚开始时,该项目似乎没有漫长而无云的寿命吗?

文章的作者说,通过问类似的问题,他意识到自己已经用自己的双手在项目中植入了定时炸弹。

如果在大型项目中正确使用Redux库,则随着此类项目的增长,它有助于将其代码保持在受支持的状态。

对于那些想要使用Redux开发可伸缩React应用程序的人,这里有11个技巧。

1.不要将动作代码和常量放在一个地方


您可能会遇到一些Redux教程,其中常量和所有操作都放在同一位置。 但是,随着应用程序的增长,这种方法会很快导致问题。 常量需要单独存储,例如,在./src/constants 。 结果,要搜索常量,您只需要查看一个文件夹,而不要查看多个文件夹。

此外,创建单独的文件存储操作看起来完全正常。 这些文件封装了彼此直接相关的动作。 例如,单个文件中的操作可能在使用方式和方式方面具有相似性。

假设您正在开发一款街机游戏或角色扮演游戏,并创建了warrior (战士), sorceress (女巫)和archer (archer)类。 在这种情况下,您可以通过组织以下操作来获得高级别的代码支持:

 src/actions/warrior.js src/actions/sorceress.js src/actions/archer.js 

如果所有内容都归入一个文件,那就更糟了:

 src/actions/classes.js 

如果应用程序变得非常大,那么最好使用大约以下代码拆分为文件的结构:

 src/actions/warrior/skills.js src/actions/sorceress/skills.js src/actions/archer/skills.js 

在此仅显示这种结构的一小部分。 如果您更广泛地考虑并始终如一地使用此方法,那么最终将得到以下文件集:

 src/actions/warrior/skills.js src/actions/warrior/quests.js src/actions/warrior/equipping.js src/actions/sorceress/skills.js src/actions/sorceress/quests.js src/actions/sorceress/equipping.js src/actions/archer/skills.js src/actions/archer/quests.js src/actions/archer/equipping.js 

这是来自src/actions/sorceress/skills对象的src/actions/sorceress/skills文件中的操作的样子:

 import { CAST_FIRE_TORNADO, CAST_LIGHTNING_BOLT } from '../constants/sorceress' export const castFireTornado = (target) => ({ type: CAST_FIRE_TORNADO, target, }) export const castLightningBolt = (target) => ({ type: CAST_LIGHTNING_BOLT, target, }) 

这是src/actions/sorceress/equipping

 import * as consts from '../constants/sorceress' export const equipStaff = (staff, enhancements) => {...} export const removeStaff = (staff) => {...} export const upgradeStaff = (slot, enhancements) => { return (dispatch, getState, { api }) => {   //                 const state = getState()   const currentEquipment = state.classes.sorceress.equipment.current   const staff = currentEquipment[slot]   const isMax = staff.level >= 9   if (isMax) {     return   }   dispatch({ type: consts.UPGRADING_STAFF, slot })   api.upgradeEquipment({     type: 'staff',     id: currentEquipment.id,     enhancements,   })   .then((newStaff) => {     dispatch({ type: consts.UPGRADED_STAFF, slot, staff: newStaff })   })   .catch((error) => {     dispatch({ type: consts.UPGRADE_STAFF_FAILED, error })   }) } } 

我们以这种方式组织代码的原因是,新功能不断地添加到项目中。 这意味着我们需要为它们的外观做好准备,同时努力确保文件不会被代码超载。

在项目工作的开始,这似乎是不必要的。 但是项目规模越大,这种方法的力量就会越强。

2.不要将减速器代码放在一个地方


当我发现减速器的代码变成了类似于以下所示的代码时,我了解到我需要进行一些更改。

 const equipmentReducers = (state, action) => { switch (action.type) {   case consts.UPGRADING_STAFF:     return {       ...state,       classes: {         ...state.classes,         sorceress: {           ...state.classes.sorceress,           equipment: {             ...state.classes.sorceress.equipment,             isUpgrading: action.slot,           },         },       },     }   case consts.UPGRADED_STAFF:     return {       ...state,       classes: {         ...state.classes,         sorceress: {           ...state.classes.sorceress,           equipment: {             ...state.classes.sorceress.equipment,             isUpgrading: null,             current: {               ...state.classes.sorceress.equipment.current,               [action.slot]: action.staff,             },           },         },       },     }   case consts.UPGRADE_STAFF_FAILED:     return {       ...state,       classes: {         ...state.classes,         sorceress: {           ...state.classes.sorceress,           equipment: {             ...state.classes.sorceress.equipment,             isUpgrading: null,           },         },       },     }   default:     return state } } 

毫无疑问,这样的代码会很快导致很多混乱。 因此,最好以最简单的形式保持状态的工作结构,以使其嵌套的最小级别为目标。 相反,您可以尝试使用减速器的组成。

使用减速器的一个有用技巧是创建其他减速器生成的高阶减速器。 在此处了解更多信息。

3.使用信息性变量名称


乍看之下,命名变量似乎是一项基本任务。 但实际上,此任务可能是最困难的一项。

变量名的选择通常与编写简洁代码的实用准则有关。 之所以总有这样一个“变量名”,是因为代码开发的这一方面在实践中起着非常重要的作用。 选择变量名不成功是将来损害您自己和团队成员的肯定方法。

您是否曾经尝试编辑其他人的代码,同时又在理解此代码的确切功能时遇到困难? 您是否曾经运行过外部程序并发现它无法按预期运行?

我想证明在这种情况下,您遇到了所谓的“脏代码”。

如果您必须在大型应用程序中处理类似的代码,那么这只是一场噩梦。 不幸的是,这种情况经常发生。

这是生活中的一种情况。 我从一个应用程序编辑了Re​​act钩子代码,那时他们向我发送了一个任务。 这是为了在应用程序中实现显示有关医生的其他信息的能力。 该信息应该已经显示给单击医生个人资料图片的患者。 有必要从表中获取它,它必须在处理对服务器的下一个请求后才能到达客户端。

这项任务并不困难,我遇到的主要问题是我不得不花太多时间查找项目代码中确切所需的位置。

我在代码中搜索了单词infodataToSenddataObject和其他我dataObject与从服务器接收的数据相关联的单词。 5-10分钟后,我设法找到负责处理所需数据的代码。 他们最终paymentObject的对象称为paymentObject 。 我认为,与付款相关的对象可能包含CVV码,信用卡号,付款人邮政编码和其他类似信息。 我发现的对象具有11个属性。 其中只有三个与付款有关:付款方式,付款资料标识符和优惠券代码列表。

情况也没有改善,因为我必须对此对象进行更改以解决我面前的任务。

简而言之,建议您不要对函数和变量使用晦涩的名称。 这是一个示例代码,其中notify函数的名称未显示其含义:

 import React from 'react' class App extends React.Component { state = { data: null } //  -? notify = () => {   if (this.props.user.loaded) {     if (this.props.user.profileIsReady) {       toast.alert(         'You are not approved. Please come back in 15 minutes or you will be deleted.',         {           position: 'bottom-right',           timeout: 15000,         },       )     }   } } render() {   return this.props.render({     ...this.state,     notify: this.notify,   }) } } export default App 

4.不要更改已配置的应用程序数据流中的数据结构或类型


我曾经犯过的最大错误之一就是更改已经配置的应用程序数据流中的数据结构。 新的数据结构将带来巨大的性能提升,因为它使用快速方法来搜索存储在内存中的对象中的数据,而不是遍历数组。 但是为时已晚。

我请你不要这样做。 也许这样的事情只能提供给确切知道这会影响应用程序哪些部分的人。

这样的步骤会有什么后果? 例如,如果某物首先是数组,然后成为对象,则这可能会中断应用程序许多部分的操作。 我犯了一个巨大的错误,因为我能够记住代码中所有可能受结构化数据表示方式更改影响的地方。 但是,在这种情况下,总是有一些代码会受到更改的影响,而且没人记得。

5.使用摘要


我曾经是Atom编辑器的粉丝,但是由于与Atom相比,该编辑器的运行速度非常快,因此切换到VS Code。 而且他以自己的速度支持大量不同的可能性。

如果您还使用VS Code,建议您安装Project Snippets扩展 。 此扩展允许程序员为项目中使用的每个工作区创建自定义片段。 此扩展的工作方式与VS Code中内置的“使用摘要”机制相同。 区别在于,在使用项目片段时,将在项目中创建.vscode/snippets/文件夹。 如下图所示。


.vscode / snippets /文件夹的内容

6.创建单元,端到端和集成测试


随着应用程序大小的增长,程序员编辑测试未涵盖的代码变得更加可怕。 例如,可能发生某人编辑了存储在src/x/y/z/的代码,并决定将其发送到生产环境的情况。 如果同时进行的更改影响了程序员没有想到的那些部分,那么一切都可能以真实用户会遇到的错误结尾。 如果项目中有测试,程序员将在代码投入生产之前很早就知道该错误。

7.头脑风暴


在向项目中引入新功能的过程中,程序员经常拒绝集思广益。 发生这种情况是因为此类活动与编写代码无关。 当为任务分配很少的时间时,这种情况尤其经常发生。

顺便说一句,为什么在开发应用程序时需要集思广益?

事实是,应用程序越复杂,程序员对它的各个部分的关注就越多。 头脑风暴有助于减少重构代码所需的时间。 举行之后,程序员将掌握在项目完成期间可能出问题的知识。 通常,程序员在开发应用程序时,甚至不会费心去思考如何以最佳方式做所有事情。

这就是为什么集思广益非常重要的原因。 在这样的事件中,程序员可以考虑代码的体系结构,考虑如何对程序进行必要的更改,跟踪这些更改的生命周期,并制定使用这些更改的策略。 养成仅将所有计划掌握在自己脑海中的习惯是不值得的。 程序员对此过于自信。 但是,绝对记住所有事情根本是不可能的。 而且,一旦做错了事,问题就会陆续出现。 这就是多米诺骨牌行动的原理。

集思广益在团队中也很有用。 例如,如果某人在工作过程中遇到问题,他可以转向集思广益会议的材料,因为与他有关的问题很可能已经被考虑了。 在头脑风暴会议上做的笔记可能很好地发挥了解决问题的计划的作用。 该计划使您可以清楚地评估执行的工作量。

8.创建应用程序模型


如果您要开始开发该应用程序,则需要决定其外观以及用户如何与之交互。 这意味着您将需要创建一个应用程序布局。 您可以为此使用各种工具。

Moqups是我经常听说的应用程序模型工具之一。 这是使用HTML5和JavaScript创建的快速工具,对系统没有特殊要求。

创建模拟应用程序可以大大简化并加快开发过程。 该布局为开发人员提供了有关应用程序各个部分之间的关​​系以及将在其页面上显示哪种数据的信息。

9.计划应用程序中的数据流


您的应用程序几乎每个组件都将与一些数据相关联。 某些组件将使用自己的数据源,但是大多数组件从组件层次结构中位于其上方的实体接收数据。 对于应用程序中由多个组件共享相同数据的那些部分,提供一些位于层次结构顶层的集中式信息存储很有用。 在这种情况下, Redux库可以为开发人员提供宝贵的帮助。

我建议在处理应用程序时,绘制一个图表,显示数据在此应用程序中的移动方式。 这将有助于创建清晰的应用程序模型,此外,我们正在谈论程序员的代码和对应用程序的看法。 此外,这种模型将有助于创建减速器。

10.使用数据访问功能


随着应用程序大小的增长,其组件数量也随之增加。 随着组件数量的增加,使用选择器(react-redux ^ v7.1)或mapStateToProps的频率也会发生相同的事情。 假设您发现组件或挂钩经常使用诸如useSelector((state) => state.app.user.profile.demographics.languages.main)类的结构访问应用程序不同部分的状态片段。 如果是这样,则意味着您需要考虑创建数据访问功能。 具有此类功能的文件应存储在公共场所,组件和挂钩可以从中导入它们。 类似的功能可以是过滤器,解析器或任何其他用于数据转换的功能。

这里有一些例子。

例如, src/accessors可能包含以下代码:

 export const getMainLanguages = (state) => state.app.user.profile.demographics.languages.main 

这是使用connect的版本,可以沿着src/components/ViewUserLanguages

 import React from 'react' import { connect } from 'react-redux' import { getMainLanguages } from '../accessors' const ViewUserLanguages = ({ mainLanguages }) => ( <div>   <h1>Good Morning.</h1>   <small>Here are your main languages:</small>   <hr />   {mainLanguages.map((lang) => (     <div>{lang}</div>   ))} </div> ) export default connect((state) => ({ mainLanguages: getMainLanguages(state), }))(ViewUserLanguages) 

这是使用位于src/components/ViewUserLanguages

 import React from 'react' import { useSelector } from 'react-redux' import { getMainLanguages } from '../accessors' const ViewUserLanguages = ({ mainLanguages }) => { const mainLanguages = useSelector(getMainLanguages) return (   <div>     <h1>Good Morning.</h1>     <small>Here are your main languages:</small>     <hr />     {mainLanguages.map((lang) => (       <div>{lang}</div>     ))}   </div> ) } export default ViewUserLanguages 

此外,努力确保这些功能是不变的,没有副作用。 在这里找出为什么我给出这样的建议。

11.使用解构和传播语法控制属性中的数据流


使用props.something构造比something构造有什么好处?

这是不使用解构的情况:

 const Display = (props) => <div>{props.something}</div> 

这是相同的,但是使用了解构:

 const Display = ({ something }) => <div>{something}</div> 

使用解构可以提高代码的可读性。 但这并不限制他对项目的积极影响。 使用解构,程序员必须决定组件接收的确切内容和输出的确切内容。 这样一来,无需编辑其他人的代码的任何人都不必在查找组件使用的所有属性时查看render方法的每一行。

此外,此方法提供了一个有用的机会来设置默认属性值。 这是在组件代码的最开始处完成的,无需在组件主体中编写其他代码:

 const Display = ({ something = 'apple' }) => <div>{something}</div> 

您之前可能已经看过类似以下示例的内容:

 const Display = (props) => ( <Agenda {...props}>   {' '}   //     Agenda   <h2><font color="#3AC1EF">Today is {props.date}</font></h2>   <hr />   <div>     <h3><font color="#3AC1EF">▍Here your list of todos:</font></h3>     {props.children}   </div> </Agenda> ) 

这样的结构不容易阅读,但这不是它们的唯一问题。 因此,有一个错误。 如果应用程序还显示子组件,则props.children将在屏幕上显示两次。 如果在团队中进行项目工作,并且团队成员不够谨慎,则发生此类错误的可能性就很高。

如果改为破坏属性,则组件代码将更清晰,并且出错的可能性将降低:

 const Display = ({ children, date, ...props }) => ( <Agenda {...props}>   {' '}   //     Agenda   <h2><font color="#3AC1EF">Today is {date}</font></h2>   <hr />   <div>     <h3><font color="#3AC1EF">▍Here your list of todos:</font></h3>     {children}   </div> </Agenda> ) 

总结


在本文中,我们为使用Redux开发React应用程序的人员回顾了12条建议。 我们希望您在这里找到对您有用的东西。

亲爱的读者们! 您将在本文中添加哪些技巧?



Source: https://habr.com/ru/post/zh-CN456336/


All Articles