状态管理
- 使用 Redux 或 MobX:
- 设计思路:高度嵌套且复杂交互的场景,状态变化较多且相互关联。Redux 通过集中式存储管理应用的所有状态,以 action 描述状态变化,reducer 处理变化逻辑,便于追踪和调试。MobX 使用响应式编程,自动追踪状态变化,简化状态管理代码。
- 实现要点:
- Redux:定义清晰的 action types,如
SHOW_MENU_ITEM
、HIDE_MENU_ITEM
等。reducer 根据 action type 更新状态,例如:
const menuReducer = (state = initialMenuState, action) => {
switch (action.type) {
case 'SHOW_MENU_ITEM':
return {
...state,
[action.menuItemId]: {
...state[action.menuItemId],
visible: true
}
};
case 'HIDE_MENU_ITEM':
return {
...state,
[action.menuItemId]: {
...state[action.menuItemId],
visible: false
}
};
default:
return state;
}
};
- **MobX**:定义可观察状态,如 `observable({ menuItems: {} })`,使用 action 函数修改状态,例如:
const showMenuItem = (menuItemId) => {
menuStore.menuItems[menuItemId].visible = true;
};
- 本地状态(useState 或 useReducer):
- 设计思路:对于一些只影响局部组件的状态,如某个菜单项的展开/折叠状态,可以使用 React 内置的
useState
或 useReducer
。useState
简单易用,useReducer
适用于更复杂的状态更新逻辑。
- 实现要点:
- useState:在组件内使用
const [isExpanded, setIsExpanded] = useState(false);
来管理菜单项的展开状态。
- useReducer:定义 reducer 函数和初始状态,如
const initialState = { isExpanded: false }; const menuItemReducer = (state, action) => { switch (action.type) { case 'EXPAND': return { isExpanded: true }; case 'COLLAPSE': return { isExpanded: false }; default: return state; } }; const [menuItemState, dispatch] = useReducer(menuItemReducer, initialState);
组件设计
- 菜单组件分层:
- 设计思路:将菜单拆分为不同层级的组件,如顶级菜单组件、子菜单组件等,便于复用和维护。
- 实现要点:
- 顶级菜单组件(Menu):负责整体布局和获取权限相关状态。接收权限数据和设备类型作为 props,例如
<Menu permissions={permissions} deviceType={deviceType} />
。
- 菜单项组件(MenuItem):展示单个菜单项,接收自身的权限、可见性等状态作为 props。根据权限和可见性决定是否渲染,例如:
const MenuItem = ({ menuItem, isVisible, onClick }) => {
if (!isVisible) return null;
return (
<div onClick={onClick}>
{menuItem.label}
</div>
);
};
- **子菜单组件(SubMenu)**:处理子菜单的展开折叠逻辑,接收子菜单项数据和设备类型作为 props。在移动端根据设备类型折叠或展开,例如:
const SubMenu = ({ subMenuItems, deviceType }) => {
const [isExpanded, setIsExpanded] = useState(deviceType === 'pc');
return (
<div>
{deviceType ==='mobile' && <button onClick={() => setIsExpanded(!isExpanded)}>{isExpanded? '收起' : '展开'}</button>}
{isExpanded && subMenuItems.map((subMenuItem) => <MenuItem key={subMenuItem.id} menuItem={subMenuItem} />)}
</div>
);
};
- 权限控制组件:
- 设计思路:创建一个高阶组件或自定义 Hook 来处理权限控制,使得权限控制逻辑可复用。
- 实现要点:
const withPermission = (permission) => {
return (WrappedComponent) => {
return (props) => {
const hasPermission = checkPermission(permission); // 假设的权限检查函数
if (!hasPermission) return null;
return <WrappedComponent {...props} />;
};
};
};
- **自定义 Hook**:
const usePermission = (permission) => {
const hasPermission = checkPermission(permission); // 假设的权限检查函数
return hasPermission;
};
渲染优化策略
- Memoization:
- 设计思路:使用
React.memo
包裹纯展示组件,避免不必要的重新渲染。
- 实现要点:对
MenuItem
、SubMenu
等组件使用 React.memo
,例如 export default React.memo(MenuItem);
。对于有复杂 props 的组件,可以使用自定义比较函数,如 React.memo(SubMenu, (prevProps, nextProps) => prevProps.subMenuItems === nextProps.subMenuItems && prevProps.deviceType === nextProps.deviceType);
- Virtualization:
- 设计思路:如果菜单列表非常长,使用虚拟化技术,如
react - virtualized
或 react - window
,只渲染可见区域的菜单项,提高性能。
- 实现要点:以
react - virtualized
为例,导入 List
组件,将菜单项数据传递给 List
,并定义 renderRow
函数来渲染每个菜单项,例如:
import { List } from'react - virtualized';
const MenuList = ({ menuItems }) => {
const rowRenderer = ({ index, key, style }) => {
const menuItem = menuItems[index];
return (
<div key={key} style={style}>
<MenuItem menuItem={menuItem} />
</div>
);
};
return (
<List
height={400}
rowCount={menuItems.length}
rowHeight={30}
rowRenderer={rowRenderer}
width={200}
/>
);
};
- Lazy Loading:
- 设计思路:对于深层嵌套的子菜单,可以采用懒加载的方式,只有在父菜单展开时才加载子菜单数据,减少初始渲染的开销。
- 实现要点:在父菜单组件中,当展开时触发加载子菜单数据的函数,例如:
const ParentMenu = ({ parentMenuItem }) => {
const [subMenuData, setSubMenuData] = useState(null);
const [isExpanded, setIsExpanded] = useState(false);
const loadSubMenuData = () => {
// 异步加载子菜单数据
fetch(`/api/sub - menu/${parentMenuItem.id}`)
.then(response => response.json())
.then(data => setSubMenuData(data));
};
return (
<div>
<button onClick={() => { setIsExpanded(!isExpanded); if (!subMenuData) loadSubMenuData(); }}>{isExpanded? '收起' : '展开'}</button>
{isExpanded && subMenuData && <SubMenu subMenuItems={subMenuData} />}
</div>
);
};