案例背景

react-router-dom 更新到v6着实是一次大改,很多之前重要的组件和特性都改掉了,最明显的就是替换了Switch,Route里的Component属性等等。当然,对此次发现的问题的元凶是,更新删除了路由组件(class)默认自带的三个属性--match, history, location。不仅让编程式路由导航的常用写法失效了,antd动态生成menu的view也受到了影响。同时react-router-dom在大力推行函数式组件的使用,所以可以通过一些hook来获取match,history和location,但类式组件就没那么幸运啦,本文主要针对router的更新对类式组件的影响讨论。

缘由是之前Menu中的selectedKeys属性需要通过this.props.location.pathname,从当前路由组件获取现在的path,router更新后当然就只能另求他法了。

这里先要说明一下为什么不将Menu中的defaultSelectedKeys设置为'/home':如果用户是从'/'被重定向到'/home',此时会出现问题,因为页面会渲染两次导致第二次无法使用default。

老版本的动态生成menu的方法如下:

export default class LeftNav extends Component {
    state = {
        collapsed: false,
    };

    onCollapse = collapsed => {
        this.setState({ collapsed });
    };

    createMenuNodes = (menuList) => {
        return menuList.map(item => {
            return item.children ?
            (
                <SubMenu key={item.key} icon={item.icon} title={item.title}>
                    {this.createMenuNodes(item.children)}
                </SubMenu>
            ) :
            (
                <Menu.Item key={item.key} icon={item.icon}>
                    <Link to={item.key}>{item.title}</Link>
                </Menu.Item>
            )
        })
    }   

    render() {
        const { collapsed } = this.state
        const path = this.props.location.pathname
        return (
            <Sider collapsible collapsed={collapsed} onCollapse={this.onCollapse}>
                <div className="logo" style={collapsed === true ? { opacity: 0 } : { opacity: 1, transition: 'all 0.7s' }}>商品后台管理系统</div>
                <Menu theme="dark" selectedKeys={[path]} mode="inline">
                    {
                        this.createMenuNodes(menuList)
                    }
                </Menu>
            </Sider>
        )
    }
}

解决方案

最初想到的方法使用浏览器自带的window.location.pathname获取,但是点击导航时却并没有触发高亮变化,原因应该是react的diffing算法并不能监测到URL的变化,所以没有触发render。目前我只想到一个不太聪明的方法,通过更新state获取path,但会多出很多条代码来解决路径异常时,state更新到home的问题,所以还要先判断当前path是不是有效的path。很笨重,个人愚见,如果有大佬能想到更好的优化方法请多指教。(最好的优化就是不用class组件~)

export default class LeftNav extends Component {
    state = {
        collapsed: false,
        path: ''
    };

    onCollapse = collapsed => {
        this.setState({ collapsed });
    };

    componentDidMount(){
        const curPath = window.location.pathname
        const effectivePaths = this.findEffectivePaths(menuList).flat() //扁平化数组
        if (effectivePaths.indexOf(curPath) === -1) {
            this.setState({path: '/home'}) //如果不是有效path,就指向home
        } else {
            this.setState({path: curPath})
        }
    }

    //统计有效的path
    findEffectivePaths = (menuList) => {
        return menuList.map(item => {
            if (item.children) {
                return this.findEffectivePaths(item.children)
            } else {
                return item.key
            }
        })
    }

    //动态生成Menu,map+递归 (也可以通过reduce+递归)
    createMenuNodes = (menuList) => {
        return menuList.map(item => {
            return item.children ?
            (
                <SubMenu key={item.key} icon={item.icon} title={item.title}>
                    {this.createMenuNodes(item.children)}
                </SubMenu>
            ) :
            (
                <Menu.Item key={item.key} icon={item.icon}>
                    <Link to={item.key} onClick={() => {this.setState({path: item.key})}}>{item.title}</Link>
                </Menu.Item>
            )
        })
    }   

    render() {
        const { collapsed, path } = this.state
        return (
            <Sider collapsible collapsed={collapsed} onCollapse={this.onCollapse}>
                <div className="logo" style={collapsed === true ? { opacity: 0 } : { opacity: 1, transition: 'all 0.7s' }}>商品后台管理系统</div>
                <Menu theme="dark" selectedKeys={[path]} mode="inline">
                    {
                        this.createMenuNodes(menuList)
                    }
                </Menu>
            </Sider>
        )
    }
}

RyanWu
29 声望2 粉丝

目前只会React,热衷于探索前端未来的方向,node/区块链/图形等