MST

星途 面试题库

面试题:React嵌套列表的复杂交互与渲染优化结合

对于一个高度嵌套且具有复杂交互的列表场景,如多级菜单,每个菜单项可能有不同的权限控制(显示或隐藏),同时在不同设备上有不同的布局方式(移动端折叠,PC端展开)。请设计一个完整的React架构方案来处理这种情况,包括状态管理、组件设计、渲染优化策略等,并详细说明各部分的设计思路和实现要点。
18.8万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

状态管理

  1. 使用 Redux 或 MobX
    • 设计思路:高度嵌套且复杂交互的场景,状态变化较多且相互关联。Redux 通过集中式存储管理应用的所有状态,以 action 描述状态变化,reducer 处理变化逻辑,便于追踪和调试。MobX 使用响应式编程,自动追踪状态变化,简化状态管理代码。
    • 实现要点
      • Redux:定义清晰的 action types,如 SHOW_MENU_ITEMHIDE_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;
};
  1. 本地状态(useState 或 useReducer)
    • 设计思路:对于一些只影响局部组件的状态,如某个菜单项的展开/折叠状态,可以使用 React 内置的 useStateuseReduceruseState 简单易用,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);

组件设计

  1. 菜单组件分层
    • 设计思路:将菜单拆分为不同层级的组件,如顶级菜单组件、子菜单组件等,便于复用和维护。
    • 实现要点
      • 顶级菜单组件(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>
    );
};
  1. 权限控制组件
    • 设计思路:创建一个高阶组件或自定义 Hook 来处理权限控制,使得权限控制逻辑可复用。
    • 实现要点
      • 高阶组件(HOC)
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;
};

渲染优化策略

  1. Memoization
    • 设计思路:使用 React.memo 包裹纯展示组件,避免不必要的重新渲染。
    • 实现要点:对 MenuItemSubMenu 等组件使用 React.memo,例如 export default React.memo(MenuItem);。对于有复杂 props 的组件,可以使用自定义比较函数,如 React.memo(SubMenu, (prevProps, nextProps) => prevProps.subMenuItems === nextProps.subMenuItems && prevProps.deviceType === nextProps.deviceType);
  2. Virtualization
    • 设计思路:如果菜单列表非常长,使用虚拟化技术,如 react - virtualizedreact - 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}
        />
    );
};
  1. 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>
    );
};