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

阅读时长 16 分钟读完

在一个复杂的前端应用程序中,经常需要处理多级菜单的展示和操作,而 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

纠错
反馈