Redux 中如何更好的处理多级菜单的展示和操作

在一个复杂的前端应用程序中,经常需要处理多级菜单的展示和操作,而 Redux 是一种很好的状态管理工具,可以帮助开发人员更好地处理这些需求。在本文中,我们将通过一个实际的场景来介绍如何在 Redux 中处理多级菜单的展示和操作。

场景描述

假设我们正在开发一个在线商城应用程序,其中一个页面需要展示多级分类菜单。这个菜单具有以下特性:

  • 菜单支持无限级别的子菜单;
  • 选中一个菜单项时,应该高亮该项,并且显示与该项关联的商品列表;
  • 当用户在菜单项上悬停时,应该能够打开该菜单项的子菜单;
  • 当用户点击一个已经打开的菜单项时,应该收起该菜单项的子菜单。

Redux 中的菜单状态

在 Redux 中,将菜单的状态单独存储在一个状态树上是很有意义的。这个状态树应该包含以下属性:

-
  ------------------- ------- -- ----------- --
  ---------------- --------- -- ------------- ---------
  ---------- -
    -
      --- ------- -- ---------- --
      ----- ------- -- ---------
      --------- ------- -- ------ ----------- ----
      --------- - -- ------
        -
          --- ------- -- ----- --
          ----- ------- -- -----------
          --------- ----- -- -------
        --
        ---
      -
    --
    ---
  -
--

以上是菜单状态的大致结构。关键点包括:

  • 在菜单结构中,每个菜单项都应该有唯一的 ID;
  • 为了方便处理闭合状态,状态中应该维护所有已经打开的菜单项的 ID,而不是单独为每个菜单项存储一个“是否已经打开”的标志;
  • 为了便于展示和操作,菜单状态应该按照层次结构进行组织。对于每个菜单项,应该存放它的所有子菜单项。

处理菜单展示的 Redux Action

为了方便后续调用,我们将所有与菜单相关的 Redux Action 存储在一个独立的文件 menuActions.js 中。

首先,我们要定义两个最简单的 Action Creator:

------ ----- -------------- - ---- -- -
  ------ -
    ----- -------------------
    --
  --
--

------ ----- ------------ - ---- -- -
  ------ -
    ----- -----------------
    --
  --
--

这两个 Action Creator 分别用于选中一个菜单项和展开一个菜单项。它们的实现也很简单,只需要返回一个包含 typepayload 属性的对象即可。

接下来,我们需要实现一个更复杂的 Action Creator hoverMenuItem。当用户将鼠标移到一个菜单项上时,我们需要知道这个菜单项的 ID,并将它展开。而且,如果这个菜单项已经打开了,应该不做任何操作。

Action Creator 的实现如下:

------ ----- ------------- - ---- -- -
  ------ ---------- --------- -- -
    ----- - --------------- - - ----------------
    -- ---------------------------- - -- -
      ---------------------------
    -
  -
--

这个 Action Creator 接受一个菜单项 ID,返回一个函数,这个函数接受两个参数:dispatchgetStatedispatch 是 Redux 的分发函数,用于分发 Action;getState 是 Redux 的状态函数,用于获取当前状态。

在函数内部,我们首先获取当前所有已经打开的菜单项的 ID,然后检查当前传入的菜单项 ID 是否已经被打开。如果没有,我们会分发一个 openMenuItem Action 将其打开。否则,不做任何操作。

处理菜单操作的 Redux Reducer

有了上面的 Action Creator,我们就可以开发针对菜单操作的 Reducer 了。

首先,我们考虑两个最简单的菜单操作:

----- ----------- - ------ - ------------- ------- -- -
  ------ ------------- -
    ---- -------------------
      ------ -
        ---------
        ------------------- ---------
      --
    ---- -----------------
      ------ -
        ---------
        ---------------- -------------------------- ----------
      --
    --------
      ------ ------
  -
--

上述 Reducer 中的代码很简单,针对 SELECT_MENU_ITEMOPEN_MENU_ITEM 两个类型的 Action,它们分别更新菜单状态中的 selectedMenuItemIdopenedMenuItems 字段。

接下来,我们需要实现一个处理菜单关闭操作的 Reducer。每个菜单项的状态结构中,我们应该存储该菜单项是否在关闭状态。如果在关闭状态,则不应该显示它的子菜单项。因此,这个 Reducer 需要查找被关闭的菜单项的所有子孙菜单项,并将它们的关闭状态都设为 true

实现代码如下:

----- ----------- - ------ - ------------- ------- -- -
  ------ ------------- -
    ---- -------------------
      ------ -
        ---------
        ------------------- ---------
      --
    ---- -----------------
      ------ -
        ---------
        ---------------- -------------------------- ----------
      --
    ---- ------------------
      ------ -
        ---------
        ---------- ------------------------------ ----------
      --
    --------
      ------ ------
  -
--

----- ------------- - ----------- --- -- -
  ------ ------------------ -- -
    -- -------- --- --- -
      ------ -
        --------
        ------- ----
      --
    - ---- -- --------------- -
      ------ -
        --------
        --------- ---------------------------- ---
      --
    - ---- -
      ------ -----
    -
  ---
--

为了方便起见,我们将递归操作封装成了一个独立的 closeMenuItem 函数。它接受两个输入,第一个是一个菜单项结构的数组,第二个是目标菜单项的 ID。这个函数的作用是,将菜单结构中所有 ID 为目标 ID 的菜单项和它的后代菜单项的 closed 属性设为 true,表示这些菜单项已经处于关闭状态。

处理子菜单的展示和操作

到目前为止,我们已经能够实现基本的菜单展示和操作了。下一步,我们需要考虑更复杂的情况:如何处理子菜单的展示和操作?

针对这个问题,我们需要借助 React 中的事件系统。对于每一个菜单项,我们需要为它添加以下事件:

  • onClick:选中该菜单项;
  • onMouseEnter:悬停该菜单项,打开子菜单;
  • onMouseLeave:离开该菜单项,关闭子菜单。

实现代码如下:

----- -------- - -- ---- -- -- -
  ----- - ------------------- --------------- - - ----------------- -- ------------
  ----- -------- - --------------
  ----- - --- ----- --------- --------- ------ - - -----
  
  ----- ----------- - -- -- -
    -----------------------------
  --
  
  ----- ---------------- - -- -- -
    -- --------- -- --------------- - -- -
      ----------------------------
    -
  --
  
  ----- ---------------- - -- -- -
    -- ---------------------------- -- -- -
      ----------------------------
    -
  --
  
  ------ -
    ---
      -----------------------
        --------- -- --- -------------------
        ------- ------- -- --------------------------- -- -
      ---
      -------------------------------
      --------------------------------
      -- -------- --------------------------------
      --------- -- --------------- - - -- -
        --- --------------------
          ------------------- -- -
            --------- -------------- ------------ --
          ---
        -----
      --
    -----
  --
--

上述代码中的 MenuItem 组件负责渲染每个菜单项。它使用了 useSelectoruseDispatch 钩子获取 Redux 状态和分发 Dispatch,然后为菜单项添加了 onClickonMouseEnteronMouseLeave 事件。

onMouseEnteronMouseLeave 事件的处理函数中,我们分别分发了 hoverMenuItemcloseMenuItem Action。closeMenuItem Action 的处理代码已经在前文中介绍过,这里不再赘述。

最后,我们需要将 MenuItem 组件以及顶层的菜单组件和 Redux Store 连接起来。完整的示例代码如下:

------ ------ - -------- - ---- --------
------ - ------------ ----------- - ---- --------------
------ ---------- ---- -------------
------ - --------------- ------------- -------------- ------------- - ---- ----------------
------ - --------- - ---- -------------

----- ---- - -- -- -
  ----- ---------------- ------------------ - ----------------
  ----- - ------------------ - - ----------------- -- ------------
  
  ----- ----------- - -
    --- -------------- ------------
      ------------------- -- -
        --------- ------------- ----------- --
      ---
    -----
  --
  
  ----- ---------- - -
    ---- -----------------------
      ------------------ -----
      ----------- ---------------
    ----
      --- -------------- ------------
        ------------------- -- -
          --------- ------------- ----------- --
        ---
      -----
    ------
  --
  
  ----- -------- - --------------
  
  ----- ------------ - -- -- -
    -----------------------------------
  --
  
  ----- ----------------- - ---- -- -
    -------------------------
    -----------------------------
  --
  
  ------ -
    ---- ----------------- ----------------
      ---- ----------------------------
        ---- --------------------------
          ------- ------------- ------------------------ ---------- -----------------------
            ----- -------------------------- -----------------
            ----- ----------------------------
            ----- ----------------------------
            ----- ----------------------------
          ---------
          -- ------------------------ --------------- --------
        ------

        -------------

        ------------

        ---- -------------------------- ----------
          --- -------------- ---------- --------------
            ------ ----------------------
          -----
        ------
      ------
    ------
  --
--

----- -------- - -- ---- -- -- -
  ----- - ------------------- --------------- - - ----------------- -- ------------
  ----- -------- - --------------
  ----- - --- ----- --------- --------- ------ - - -----
  
  ----- ----------- - -- -- -
    -----------------------------
  --
  
  ----- ---------------- - -- -- -
    -- --------- -- --------------- - -- -
      ----------------------------
    -
  --
  
  ----- ---------------- - -- -- -
    -- ---------------------------- -- -- -
      ----------------------------
    -
  --
  
  ------ -
    ---
      -----------------------
        --------- -- --- -------------------
        ------- ------- -- --------------------------- -- -
      ---
      -------------------------------
      --------------------------------
      -- -------- --------------------------------
      --------- -- --------------- - - -- -
        --- --------------------
          ------------------- -- -
            --------- -------------- ------------ --
          ---
        -----
      --
    -----
  --
--

----- ------------ - -
  ------------------- -----
  ---------------- ---
  ---------
--

----- ----------- - ------ - ------------- ------- -- -
  ------ ------------- -
    ---- -------------------
      ------ -
        ---------
        ------------------- ---------
      --
    ---- -----------------
      ------ -
        ---------
        ---------------- -------------------------- ----------
      --
    ---- ------------------
      ------ -
        ---------
        ---------- ------------------------------ ----------
      --
    --------
      ------ ------
  -
--

----- ------------- - ----------- --- -- -
  ------ ------------------ -- -
    -- -------- --- --- -
      ------ -
        --------
        ------- ----
      --
    - ---- -- --------------- -
      ------ -
        --------
        --------- ---------------------------- ---
      --
    - ---- -
      ------ -----
    -
  ---
--

------ - ----- ----------- --

总结

本文介绍了如何在 Redux 中更好地处理多级分类菜单的展示和操作。可供开发人员参考和借鉴。

来源:JavaScript中文网 ,转载请联系管理员! 本文地址:https://www.javascriptcn.com/post/649ed12948841e9894b54dae


猜你喜欢

  • 从 Deno 到 Preact 的路程

    前言 作为一名前端开发者,我们需要不断升级自己的技术,学习新的工具和框架以应对不断变化的市场需求。本文将会介绍从 Deno 到 Preact 的学习路程,探究其特性以及如何在实际项目中应用。

    1 年前
  • Kubernetes 集群监控中的 Prometheus 详解

    随着云计算和容器化技术的发展,Kubernetes 已经成为了互联网公司中最主流的容器管理平台之一,它能够自动扩缩容,定期备份和自动恢复服务等等。Kubernetes 作为快速开发的利器,但是在实际生...

    1 年前
  • 在 AngularJS 的 SPA 中使用 ui-router 的最佳实践

    在 AngularJS 的 SPA 中使用 ui-router 的最佳实践 随着 Web 应用程序的复杂性不断增加,Web 应用程序框架也在不断地提供更好的工具来满足需求。

    1 年前
  • Node.js 中如何使用 WebSocket 实现 WebRTC?

    前言 WebRTC (Web Real-Time Communication) 是现代 Web 技术中非常重要的一部分,它可以在浏览器中实现高质量的实时音视频通信。

    1 年前
  • 解析 ES2021 新特性中的 Promise.any

    ES2021 引入了一个新的 Promise 方法:Promise.any。这个方法可以接受一个数组作为参数,其中的 Promise 对象只要有一个 resolve,整个 Promise.any 就会...

    1 年前
  • 使用 Fastify 和 Redis 构建数据缓存

    近年来,随着互联网的发展和用户需求的不断增加,数据量和处理数据的速度越来越成为关键问题。对于前端开发人员而言,如何提高系统的响应速度,避免重复计算、提高资源利用率等都是需要考虑的问题。

    1 年前
  • CSS Flexbox 在实现网站主体布局中的最佳实践

    Flexbox 是一种强大的 CSS 布局模式,它可以让我们轻松地创建响应式且灵活的布局。在本文中,我们将讨论如何使用 Flexbox 在实现网站主体布局中的最佳实践。

    1 年前
  • Chai 库中如何判断一个变量是否为 null 或 undefined?

    在 JavaScript 中,经常需要判断一个变量是否为 null 或 undefined,这是一种基本的防御性编程方法。如果不进行判断,当调用这个变量的方法时,有可能会产生错误。

    1 年前
  • 如何使用 Enzyme 测试 React 组件的 render 方法?

    引言 随着前端技术的不断发展,React 组件已经成为了前端开发的重要部分。为了保证 React 组件的质量,我们需要不断地进行测试。而 Enzyme 是一个流行的 React 测试工具,可以帮助我们...

    1 年前
  • 如何通过 LESS 实现字体图标的配色方案

    介绍 在网站开发中,字体图标早已成为不容忽视的一部分。它们可以用来解决图像图片因体积而降低加载速度等问题,也能使网页设计更具灵活性。自定义字体图标不仅满足了各种设计和排版的需求,而且在多个设备间的显示...

    1 年前
  • Mocha + SuperTest 实现 RESTful API 自动化测试

    RESTful API 是现代应用开发的基础,自动化测试是代码质量保证的必要手段。本文介绍了如何使用 Mocha 和 SuperTest 实现 RESTful API 自动化测试。

    1 年前
  • 如何为无障碍用户提供更好的文字描述

    随着互联网的发展,在网页和应用中使用的图片、图表、视频等多媒体越来越多,然而这些多媒体有时会给视障用户带来困扰,因为视障用户无法获得图像的信息。为了解决这一难题,我们需要更好的文字描述来给视障用户提供...

    1 年前
  • RxJS 加强版:使用 Operator 操作符实现更高效的数据缓存

    什么是 RxJS? RxJS 是一个基于观察者模式的库,用于构建基于事件的异步和基于事件的程序。它提供了一种使用可观察序列来简化异步代码的方法。RxJS 扩展了核心观察者模式,以支持其他模式,例如流、...

    1 年前
  • 详解:优化 Babel7 的 Plugin 使用

    随着前端技术的发展,Babel7 作为一个十分流行的 JavaScript 编译器,无疑是我们的必备工具之一。而在前端开发中,使用 Babel7 的 Plugin 可以使代码编译得更加高效和精准。

    1 年前
  • Material Design 风格下实现圆形 ImageView 的方法

    Material Design 是 Google 推出的基于平面设计的新一代设计语言,注重简单、直观、有目的性的设计,受到了广泛的认可。其中一个重要的设计元素就是圆形头像。

    1 年前
  • Redux 学习笔记(二):Redux 核心原理

    在上一篇 Redux 学习笔记中,我们介绍了 Redux 的概念、作用、和基本使用方法。本篇将更深入地介绍 Redux 的核心原理,包括数据流动、reducer、store、Action 和中间件等。

    1 年前
  • TypeScript 高阶组件:组件的复用与 Mixins

    在前端开发中,我们经常会遇到需要复用某些组件功能的情况,这时候高阶组件就可以大显身手了。而在 TypeScript 中,我们还可以通过 Mixins 的方式来实现更加灵活和可配置的复用。

    1 年前
  • ES7 中的变量定义语句

    在前端开发中,变量定义语句是非常常见的。在 ES7 中,新增了一些变量定义语句,让变量的定义更加方便,同时也更加易懂和清晰。本文将介绍 ES7 中新增的变量定义语句,并提供示例代码和使用方法,帮助你更...

    1 年前
  • Vue.js 中 v-for 指令的几种使用方法

    Vue.js 是一个快速、高效且灵活的 JavaScript 框架,它广泛应用于前端开发中。其中 v-for 指令是 Vue.js 中的重要组成部分,可以用来循环渲染数组或对象,实现动态数据绑定。

    1 年前
  • Promise 中 then 方法如何控制执行顺序?

    在前端开发中,异步编程是一个比较常见的任务。我们通常会使用 Promise 管理异步任务的执行顺序。在 Promise 中,then 方法被广泛使用,本文将探讨如何使用 then 方法来控制异步任务的...

    1 年前

相关推荐

    暂无文章