使用 Redux 构建 React 应用时不要做的 12 件事

2025-06-11

使用 Redux 构建 React 应用时不要做的 12 件事

在Medium上找到我

在构建 React 应用时,小型项目在代码架构方面通常比大型项目更灵活一些。虽然用大型项目​​适用的最佳实践来构建小型应用本身并没有什么不妥,但可能没有必要应用所有重大决策。应用越小,偷懒就越“合适”。

但是,建议将本文中的一些最佳实践应用于任何规模的反应应用程序。

如果您从未有过在生产环境中构建应用程序的经验,那么本文可以帮助您为下一个大型应用程序的构建做好准备。最糟糕的情况莫过于在工作中构建应用程序时,突然意识到必须重构大量代码架构才能提高可扩展性和可维护性——尤其是在缺少单元测试的情况下!

相信我,我经历过这种情况。____ 给了我几个任务,让我完成____。起初,我以为一切都进展顺利,完美无缺。我以为,只要我的 Web 应用程序运行良好,并且速度很快就说明我的代码开发和维护工作做得非常出色。我知道如何使用 Redux,以及如何让 UI 组件正常交互。Reducer 和 Action 对我来说是一个简单的概念。我感觉自己无所不能

直到未来悄然来临

几个月过去了,我开发了超过 15 个功能,但事情开始变得不可控。我使用 redux 的代码变得难以维护。

你可能会问:为什么? ”

你不是无敌的吗?”

嗯,我也这么想。它就像一颗定时炸弹,随时可能引发灾难。Redux 拥有惊人的能力,只要在大型项目中正确使用,就能保持代码的可维护性。

继续阅读,了解如果您计划构建可扩展的 React Web 应用程序,应该避免哪些行为。

1. 将动作和常量放在一起

你可能会看到一些 Redux 教程,将常量和所有操作放在一个地方。然而,随着应用规模的扩大,这很快就会变得麻烦。常量应该放在单独的位置,./src/constants这样就可以在一个地方搜索,而不是在多个地方。

此外,创建一个单独的动作文件来表示它将用于什么用途如何使用,并封装直接相关的动作,这绝对是可以的。如果你正在开发一款新的街机/RPG游戏,引入了战士女巫弓箭手 三个职业,那么像这样放置动作会更易于维护:

src/actions/战士.js
src/actions/女巫.js
src/actions/弓箭手.js

而不是像这样:

src/actions/classes.js

如果应用程序变得非常大,那么采用如下方法可能是一种更好的方法:

src/actions/战士/skills.js
src/actions/女巫/skills.js
src/actions/弓箭手/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/女巫/技能

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,
})
Enter fullscreen mode Exit fullscreen mode

src/actions/女巫/装备

import * as consts from '../constants/sorceress'

export const equipStaff = (staff, enhancements) => {...}

export const removeStaff = (staff) => {...}

export const upgradeStaff = (slot, enhancements) => {
  return (dispatch, getState, { api }) => {
    // Grab the slot in our equipment screen to grab the staff reference
    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 })
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

我们这样做的原因是,总会有新的功能需要添加,并且随着文件变得越来越臃肿,您必须做好准备!

一开始可能会觉得多余,但随着项目越来越大,这些方法将开始发挥作用。

2. 将 Reducer 放在一个地方

当我的减速器开始看起来像这样时:

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
  }
}
Enter fullscreen mode Exit fullscreen mode

这显然会很快造成很大的混乱,因此最好让你的状态结构尽可能简单和扁平,或者尝试组合所有的减速器。

一个巧妙的技巧是创建一个生成减速器的高阶减速器,将每个包装的减速器映射到从动作类型到处理程序的对象映射

3. 变量命名不当

命名变量听起来像是一件简单的事情,但实际上它可能是编写代码时最难做好的事情之一。

它本质上是一种简洁的编码实践……这个术语之所以存在,是因为它在实践中应用非常重要。糟糕的变量命名会让你的团队成员和你自己的未来都吃亏

你是否曾经尝试编辑别人的代码,却发现很难理解代码的用途?你是否曾经运行别人的代码,却发现它的功能与你的预期不同?

我敢打赌,该代码的作者采用了肮脏的代码实践。

在这种情况下最糟糕的情况是必须在大型应用程序中经历这种情况,并且这种情况通常发生在多个领域。

让我给你讲一个我曾经经历过的真实情况:

当时我正在编辑应用程序代码中一个现有的 React Hook,收到一个任务,要求在患者点击医生时添加并显示每位医生的附加信息。当患者选择(点击)一位医生时,医生信息会从表格行中提取出来,以便患者将这些信息附加到下一个发往后端的请求中。

一切都进展顺利,只是当我在代码中搜索该部分的位置时,我花费了比我应该花费的更多时间。

此时,我脑子里一直在寻找诸如infodataToSenddataObject 之类的词语,或者任何与刚刚收集的数据相关的词语。5-10 分钟后,我找到了实现此流程的部分,并将其放入其中的对象命名为paymentObject。当我想到支付对象时,我会想到 CVV、后 4 位数字、邮政编码等。在 11 个属性中,只有三个与支付相关:收费方式付款配置文件 ID优惠券

而且,事后再尝试融入我的变化实在是太尴尬了,这并没有什么帮助

简而言之,请尽量避免像这样命名函数或变量:

import React from 'react'

class App extends React.Component {
  state = { data: null }

  // Notify what?
  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
Enter fullscreen mode Exit fullscreen mode

4. 中途改变数据/类型结构

我犯过的最大错误之一,就是在应用程序已经建立的流程中更改某些数据/类型结构。新的数据结构本来可以大幅提升性能,因为它利用对象查找来获取内存中的数据,而不是通过数组进行映射。但为时已晚。

除非您确实了解所有将受到影响的区域,否则请不要这样做。

会有什么后果?

如果某个数据从数组变为对象,应用程序的多个部分都可能面临无法正常运行的风险。我犯了一个最大的错误,就是以为我已经把应用程序中所有可能受结构化数据更改影响的部分都考虑周全了,但总会有一个地方被遗漏了

6. 不使用代码片段进行开发

我曾经是 Atom 的粉丝,但我转而使用 VScode,因为它比 Atom 快得多——同时仍然支持大量功能,而速度没有明显损失。

如果您正在使用 VSCode,我强烈建议您下载一个名为“项目代码片段”的扩展。此扩展允许您为每个工作区声明自定义代码片段,以供该项目使用。它的工作原理与 VSCode 默认内置的“用户代码片段”功能完全相同,只不过您.vscode/snippets/需要在项目中创建一个文件夹,如下所示:

vscode 项目代码片段工作区 React 项目

7. 忽略单元/端到端/集成测试

随着应用规模的扩大,在没有任何测试的情况下编辑现有代码会变得越来越可怕。你可能最终会编辑位于 src/x/y/z/ 的文件,并决定将更改推送到生产环境。然而,如果更改影响了应用的其他部分而你没有注意到,那么由于你没有任何测试可以提前提醒你,这个 bug 就会一直存在,直到真正的用户在浏览你的页面时发现它。

8. 跳过头脑风暴阶段

开发人员经常会跳过头脑风暴阶段,因为他们没有编写代码,尤其是在他们只有一周的时间来开发某个功能的情况下。然而,根据经验,这是最重要的一步,它将在未来为您和您的团队节省大量时间。

为什么要费心集思广益?

应用程序越复杂,开发人员就越需要管理其中的某些部分。头脑风暴有助于减少重构代码的次数,因为您已经预先规划好了可能出现的问题。很多时候,开发人员几乎没有时间静下心来,将所有巧妙的实践运用到进一步增强应用程序的功能中。

这就是为什么头脑风暴如此重要。你需要思考架构中的所有代码设计以及所需的增强功能,以便从一开始就以战略性的方式解决所有问题。不要养成过度自信并在脑海中规划一切的习惯。如果你这样做,你将无法记住所有事情。一旦你做错了什么,就会像多米诺骨牌效应一样,更多的事情会出错。

头脑风暴也能让你的团队更轻松一些。如果有人在某个任务上遇到困难,他们可以参考一开始的头脑风暴,而且很可能已经找到了答案。

您在集思广益时所做的笔记也可以作为您和您的团队的议程,并有助于在开发应用程序时轻松地提供对当前进度的一致了解。

9. 未事先确定 UI 组件

如果您要开始构建应用,您应该确定应用的外观和风格。有多种工具可以帮助您创建自己的模型。

我经常听到的一个模型工具是Moqups。它速度很快,不需要任何插件,并且是用 HTML5 和 JavaScript 构建的。

完成此步骤非常有助于您获得即将在页面上显示的信息和数据。应用程序开发将变得更加轻松。

10. 没有规划数据流

几乎每个组件都会与某种数据关联。有些组件会使用自己的数据源,但大多数组件的数据都会由组件树中更高层级的位置提供。对于应用程序中需要与多个组件共享数据的部分,最好将这些数据放在组件树中更高层级的位置,以便充当集中式状态树。这时,Redux的强大功能就派上用场了 :)

我建议列出数据在整个应用程序中的流动方式。这将帮助你为应用程序创建更坚实的思维和书面模型。基于这些价值观,你的 Reducer 应该很容易建立。

11.不使用访问器函数

随着应用规模的扩大,组件数量也会随之增加。而当组件数量增加时,使用选择器(react-redux ^v7.1) 或mapStateToProps 的次数也会随之增加。如果您发现组件或钩子在应用程序的多个部分经常选择状态切片,例如useSelector((state) => state.app.user.profile.demographics.languages.main),那么是时候考虑在一个共享位置创建访问器函数,以便组件/钩子可以从中导入和使用。这些访问器函数可以是过滤器、解析器或任何其他数据转换函数。

以下是一些示例:

src/访问器

export const getMainLanguages = (state) =>
  state.app.user.profile.demographics.languages.main
Enter fullscreen mode Exit fullscreen mode

连接版本

src/组件/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)
Enter fullscreen mode Exit fullscreen mode

useSelector版本

src/组件/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
Enter fullscreen mode Exit fullscreen mode

保持这些函数不可变(没有副作用)也非常重要。要了解原因,请点击此处

12. 不使用解构和扩展属性来控制属性流

props.something使用versus有哪些好处something

无需解构

const Display = (props) => <div>{props.something}</div>
Enter fullscreen mode Exit fullscreen mode

使用解构

const Display = ({ something }) => <div>{something}</div>
Enter fullscreen mode Exit fullscreen mode

使用解构,你不仅可以提高代码的可读性,还能直接决定哪些内容可以传入,哪些内容可以传出。将来其他开发者编辑你的代码时,他们无需逐行扫描你的 render 方法,就能找到组件使用的所有 props。

您还可以从一开始就声明默认道具,而无需添加任何其他代码行:

const Display = ({ something = 'apple' }) => <div>{something}</div>
Enter fullscreen mode Exit fullscreen mode

你可能之前见过类似这样的情况:

const Display = (props) => (
  <Agenda {...props}>
    {' '}
    // forward other props to Agenda
    <h2>Today is {props.date}</h2>
    <hr />
    <div>
      <h3>Here your list of todos:</h3>
      {props.children}
    </div>
  </Agenda>
)
Enter fullscreen mode Exit fullscreen mode

这不仅代码可读性稍差,而且组件中还存在一个无意的错误。如果App组件也渲染子元素,那么props.children会被渲染两次,从而导致重复渲染。当你与其他开发团队合作时,这些错误很可能会意外发生,尤其是在他们不够细心的情况下。

通过解构 props,组件可以直接切入正题,并减少出现不必要错误的机会:

const Display = ({ children, date, ...props }) => (
  <Agenda {...props}>
    {' '}
    // forward other props to Agenda
    <h2>Today is {date}</h2>
    <hr />
    <div>
      <h3>Here your list of todos:</h3>
      {children}
    </div>
  </Agenda>
)
Enter fullscreen mode Exit fullscreen mode

结论

好了,各位!希望这些建议对你们有所帮助,如有任何问题或疑虑,欢迎留言!下次再见!

鏂囩珷鏉ユ簮锛�https://dev.to/jsmanifest/12-things-not-to-do-when-building-react-apps-with-redux-n5i
PREV
JavaScript 中函数返回其他函数的强大之处
NEXT
为异步作业构建可扩展、可靠且经济高效的事件调度程序