在一个复杂的前端应用程序中,经常需要处理多级菜单的展示和操作,而 Redux 是一种很好的状态管理工具,可以帮助开发人员更好地处理这些需求。在本文中,我们将通过一个实际的场景来介绍如何在 Redux 中处理多级菜单的展示和操作。
场景描述
假设我们正在开发一个在线商城应用程序,其中一个页面需要展示多级分类菜单。这个菜单具有以下特性:
- 菜单支持无限级别的子菜单;
- 选中一个菜单项时,应该高亮该项,并且显示与该项关联的商品列表;
- 当用户在菜单项上悬停时,应该能够打开该菜单项的子菜单;
- 当用户点击一个已经打开的菜单项时,应该收起该菜单项的子菜单。
Redux 中的菜单状态
在 Redux 中,将菜单的状态单独存储在一个状态树上是很有意义的。这个状态树应该包含以下属性:
-- -------------------- ---- ------- - ------------------- ------- -- ----------- -- ---------------- --------- -- ------------- --------- ---------- - - --- ------- -- ---------- -- ----- ------- -- --------- --------- ------- -- ------ ----------- ---- --------- - -- ------ - --- ------- -- ----- -- ----- ------- -- ----------- --------- ----- -- ------- -- --- - -- --- - --
以上是菜单状态的大致结构。关键点包括:
- 在菜单结构中,每个菜单项都应该有唯一的 ID;
- 为了方便处理闭合状态,状态中应该维护所有已经打开的菜单项的 ID,而不是单独为每个菜单项存储一个“是否已经打开”的标志;
- 为了便于展示和操作,菜单状态应该按照层次结构进行组织。对于每个菜单项,应该存放它的所有子菜单项。
处理菜单展示的 Redux Action
为了方便后续调用,我们将所有与菜单相关的 Redux Action 存储在一个独立的文件 menuActions.js
中。
首先,我们要定义两个最简单的 Action Creator:
-- -------------------- ---- ------- ------ ----- -------------- - ---- -- - ------ - ----- ------------------- -- -- -- ------ ----- ------------ - ---- -- - ------ - ----- ----------------- -- -- --
这两个 Action Creator 分别用于选中一个菜单项和展开一个菜单项。它们的实现也很简单,只需要返回一个包含 type
和 payload
属性的对象即可。
接下来,我们需要实现一个更复杂的 Action Creator hoverMenuItem
。当用户将鼠标移到一个菜单项上时,我们需要知道这个菜单项的 ID,并将它展开。而且,如果这个菜单项已经打开了,应该不做任何操作。
Action Creator 的实现如下:
export const hoverMenuItem = (id) => { return (dispatch, getState) => { const { openedMenuItems } = getState().menu; if (openedMenuItems.indexOf(id) < 0) { dispatch(openMenuItem(id)); } } };
这个 Action Creator 接受一个菜单项 ID,返回一个函数,这个函数接受两个参数:dispatch
和 getState
。dispatch
是 Redux 的分发函数,用于分发 Action;getState
是 Redux 的状态函数,用于获取当前状态。
在函数内部,我们首先获取当前所有已经打开的菜单项的 ID,然后检查当前传入的菜单项 ID 是否已经被打开。如果没有,我们会分发一个 openMenuItem
Action 将其打开。否则,不做任何操作。
处理菜单操作的 Redux Reducer
有了上面的 Action Creator,我们就可以开发针对菜单操作的 Reducer 了。
首先,我们考虑两个最简单的菜单操作:
-- -------------------- ---- ------- ----- ----------- - ------ - ------------- ------- -- - ------ ------------- - ---- ------------------- ------ - --------- ------------------- --------- -- ---- ----------------- ------ - --------- ---------------- -------------------------- ---------- -- -------- ------ ------ - --
上述 Reducer 中的代码很简单,针对 SELECT_MENU_ITEM
和 OPEN_MENU_ITEM
两个类型的 Action,它们分别更新菜单状态中的 selectedMenuItemId
和 openedMenuItems
字段。
接下来,我们需要实现一个处理菜单关闭操作的 Reducer。每个菜单项的状态结构中,我们应该存储该菜单项是否在关闭状态。如果在关闭状态,则不应该显示它的子菜单项。因此,这个 Reducer 需要查找被关闭的菜单项的所有子孙菜单项,并将它们的关闭状态都设为 true
。
实现代码如下:
-- -------------------- ---- ------- ----- ----------- - ------ - ------------- ------- -- - ------ ------------- - ---- ------------------- ------ - --------- ------------------- --------- -- ---- ----------------- ------ - --------- ---------------- -------------------------- ---------- -- ---- ------------------ ------ - --------- ---------- ------------------------------ ---------- -- -------- ------ ------ - -- ----- ------------- - ----------- --- -- - ------ ------------------ -- - -- -------- --- --- - ------ - -------- ------- ---- -- - ---- -- --------------- - ------ - -------- --------- ---------------------------- --- -- - ---- - ------ ----- - --- --
为了方便起见,我们将递归操作封装成了一个独立的 closeMenuItem
函数。它接受两个输入,第一个是一个菜单项结构的数组,第二个是目标菜单项的 ID。这个函数的作用是,将菜单结构中所有 ID 为目标 ID 的菜单项和它的后代菜单项的 closed
属性设为 true
,表示这些菜单项已经处于关闭状态。
处理子菜单的展示和操作
到目前为止,我们已经能够实现基本的菜单展示和操作了。下一步,我们需要考虑更复杂的情况:如何处理子菜单的展示和操作?
针对这个问题,我们需要借助 React 中的事件系统。对于每一个菜单项,我们需要为它添加以下事件:
onClick
:选中该菜单项;onMouseEnter
:悬停该菜单项,打开子菜单;onMouseLeave
:离开该菜单项,关闭子菜单。
实现代码如下:
-- -------------------- ---- ------- ----- -------- - -- ---- -- -- - ----- - ------------------- --------------- - - ----------------- -- ------------ ----- -------- - -------------- ----- - --- ----- --------- --------- ------ - - ----- ----- ----------- - -- -- - ----------------------------- -- ----- ---------------- - -- -- - -- --------- -- --------------- - -- - ---------------------------- - -- ----- ---------------- - -- -- - -- ---------------------------- -- -- - ---------------------------- - -- ------ - --- ----------------------- --------- -- --- ------------------- ------- ------- -- --------------------------- -- - --- ------------------------------- -------------------------------- -- -------- -------------------------------- --------- -- --------------- - - -- - --- -------------------- ------------------- -- - --------- -------------- ------------ -- --- ----- -- ----- -- --
上述代码中的 MenuItem
组件负责渲染每个菜单项。它使用了 useSelector
和 useDispatch
钩子获取 Redux 状态和分发 Dispatch,然后为菜单项添加了 onClick
、onMouseEnter
和 onMouseLeave
事件。
在 onMouseEnter
和 onMouseLeave
事件的处理函数中,我们分别分发了 hoverMenuItem
和 closeMenuItem
Action。closeMenuItem
Action 的处理代码已经在前文中介绍过,这里不再赘述。
最后,我们需要将 MenuItem
组件以及顶层的菜单组件和 Redux Store 连接起来。完整的示例代码如下:
-- -------------------- ---- ------- ------ ------ - -------- - ---- -------- ------ - ------------ ----------- - ---- -------------- ------ ---------- ---- ------------- ------ - --------------- ------------- -------------- ------------- - ---- ---------------- ------ - --------- - ---- ------------- ----- ---- - -- -- - ----- ---------------- ------------------ - ---------------- ----- - ------------------ - - ----------------- -- ------------ ----- ----------- - - --- -------------- ------------ ------------------- -- - --------- ------------- ----------- -- --- ----- -- ----- ---------- - - ---- ----------------------- ------------------ ----- ----------- --------------- ---- --- -------------- ------------ ------------------- -- - --------- ------------- ----------- -- --- ----- ------ -- ----- -------- - -------------- ----- ------------ - -- -- - ----------------------------------- -- ----- ----------------- - ---- -- - ------------------------- ----------------------------- -- ------ - ---- ----------------- ---------------- ---- ---------------------------- ---- -------------------------- ------- ------------- ------------------------ ---------- ----------------------- ----- -------------------------- ----------------- ----- ---------------------------- ----- ---------------------------- ----- ---------------------------- --------- -- ------------------------ --------------- -------- ------ ------------- ------------ ---- -------------------------- ---------- --- -------------- ---------- -------------- ------ ---------------------- ----- ------ ------ ------ -- -- ----- -------- - -- ---- -- -- - ----- - ------------------- --------------- - - ----------------- -- ------------ ----- -------- - -------------- ----- - --- ----- --------- --------- ------ - - ----- ----- ----------- - -- -- - ----------------------------- -- ----- ---------------- - -- -- - -- --------- -- --------------- - -- - ---------------------------- - -- ----- ---------------- - -- -- - -- ---------------------------- -- -- - ---------------------------- - -- ------ - --- ----------------------- --------- -- --- ------------------- ------- ------- -- --------------------------- -- - --- ------------------------------- -------------------------------- -- -------- -------------------------------- --------- -- --------------- - - -- - --- -------------------- ------------------- -- - --------- -------------- ------------ -- --- ----- -- ----- -- -- ----- ------------ - - ------------------- ----- ---------------- --- --------- -- ----- ----------- - ------ - ------------- ------- -- - ------ ------------- - ---- ------------------- ------ - --------- ------------------- --------- -- ---- ----------------- ------ - --------- ---------------- -------------------------- ---------- -- ---- ------------------ ------ - --------- ---------- ------------------------------ ---------- -- -------- ------ ------ - -- ----- ------------- - ----------- --- -- - ------ ------------------ -- - -- -------- --- --- - ------ - -------- ------- ---- -- - ---- -- --------------- - ------ - -------- --------- ---------------------------- --- -- - ---- - ------ ----- - --- -- ------ - ----- ----------- --
总结
本文介绍了如何在 Redux 中更好地处理多级分类菜单的展示和操作。可供开发人员参考和借鉴。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/649ed12948841e9894b54dae