React 系列 - 写出优雅的路由

前言

自前端框架风靡以来,路由一词在前端的热度与日俱增,他是几乎所有前端框架的核心功能点。不同于后端,前端的路由往往需要表达更多的业务功能,例如与菜单耦合、与标题耦合、与“面包屑”耦合等等,因此很少有拆箱即用的完整方案,多多少少得二次加工一下。

1. UmiJS 简述

优秀的框架可以缩短 90% 以上的无效开发时间,蚂蚁的 UmiJS 是我见过最优雅的 React 应用框架,或者可以直接说是最优雅的前端解决方案(欢迎挑战),本系列将逐步展开在其之上的应用,本文重点为“路由”,其余部分后续系列继续深入。

2. 需求概述

动码之前先构想下本次我们要实现哪些功能:

  1. 路由需要耦合菜单,且需要对菜单的空节点自动往下补齐;
  2. 路由中总要体现模板的概念,即不同的路由允许使用不用的模板组件;
  3. 模板与页面的关系完全交由路由组合,不再体现于组件中;
  4. 需要实现从路由中获取当前页面的轨迹,即“面包屑”的功能;
  5. 实现从路由中获取页面标题;

上述每一点的功能都不复杂,若不追求极致,其实默认的约定式路由基本能够满足需求(详情查询官方文档,此处不做展开)。

3. 开码

3.1 菜单

先从菜单出发,以下应当是一个最简洁的目录结构:

const menu = [
  {
    name: '父节点',
    path: 'parent',
    children: [{
      name: '子页面',
      path: 'child'
    }]
  }
];

使用递归补齐 child 路径:

const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w][email protected])?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w][email protected])[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
const formatMenu = (data, parentPath = `${define.BASE_PATH}/`) => {
  return data.map((item) => {
    let { path } = item;
    if (!reg.test(path)) {
      path = parentPath + item.path;
    }
    const result = {
      ...item,
      path
    };
    if (item.children) {
      result.children = formatMenu(item.children, `${parentPath}${item.path}/`);
    }

    return result;
  });
}

菜单的子节点才是真正的页面,所以若当前路径是父节点,我们期望的是能够自动跳转到父节点写的第一个或者特定的页面:

const redirectData = [];
const formatRedirect = item => {
  if (item && item.children) {
    if (item.children[0] && item.children[0].path) {
      redirectData.push({
        path: `${item.path}`,
        redirect: `${item.children[0].path}`
      });
      item.children.forEach(children => {
        formatRedirect(children);
      });
    }
  }
};
const getRedirectData = (menuData) => {
  menuData.forEach(formatRedirect);
  return redirectData
};

3.2 路由组装

而后便是将自动跳转的路径组装入路由节点:

const routes = [
  ...redirect,
  {
    path: define.BASE_PATH,
    component: '../layouts/BasicLayout',
    routes: [
      {
        path: `${define.BASE_PATH}/parent`,
        routes: [
          {
            title: '子页面',
            path: 'child',
            component: './parent/child',
          }
        ],
      },
      {
        component: './404',
      }
    ]
  }
];

路由配置最后需要注入配置文件 .umirc.js:

import { plugins } from './config/plugins';
import { routes } from './config/routes';

export default {
  plugins,
  routes
}

3.3 模板页

import { Layout } from 'antd';
import React, { PureComponent, Fragment } from 'react';
import { ContainerQuery } from 'react-container-query';
import DocumentTitle from 'react-document-title';

import { query } from '@/utils/layout';
import Footer from './Footer';
import Context from './MenuContext';

const { Content } = Layout;

class BasicLayout extends PureComponent {

  render() {
    const {
      children,
      location: { pathname }
    } = this.props;
    const layout = (
      <Layout>
        <Layout>
          <Content>
            {children}
          </Content>
          <Footer />
        </Layout>
      </Layout>
    );
    return (
      <Fragment>
        <DocumentTitle title={this.getPageTitle(pathname)}>
          <ContainerQuery query={query}>
            {params => (
              <Context.Provider>
                {layout}
              </Context.Provider>
            )}
          </ContainerQuery>
        </DocumentTitle>
      </Fragment>
    );
  }
}

export default BasicLayout;

结合路由与菜单获取面包屑:

getBreadcrumbNameMap() {
  const routerMap = {};
  let path = this.props.location.pathname;
  if (path.endsWith('/')) {
    path = path.slice(0, path.length - 1);
  }

  const mergeRoute = (path) => {
    if (path.lastIndexOf('/') > 0) {
      const title = this.getPageTitle(path);
      if (title) {
        routerMap[path] = {
          name: title,
          path: path
        };
      }
      mergeRoute(path.slice(0, path.lastIndexOf('/')));
    }
  };
  const mergeMenu = data => {
    data.forEach(menuItem => {
      if (menuItem.children) {
        mergeMenu(menuItem.children);
      }
      routerMap[menuItem.path] = {
        isMenu: true,
        ...menuItem
      };
    });
  };
  mergeRoute(path);
  mergeMenu(this.state.menuData);
  return routerMap;
}

从路由中获取 PageTitle:

getPageTitle = (path) => {
  if (path.endsWith('/')) {
    path = path.slice(0, path.length - 1);
  }
  let title;
  this.props.route.routes[0].routes.forEach(route => {
    if (route.path === path) {
      title = route.title;
      return;
    }
  })
  return title;
};

结语

此篇随笔比较混乱,写作脉络不对,还是应该简述下在 umijs 之上的架构设计,再往下深入探讨应用点,缺的部分会在后续系列中补上~ 请关注公众号:

原文地址:https://www.cnblogs.com/youclk/p/10094745.html

时间: 2024-08-02 16:50:30

React 系列 - 写出优雅的路由的相关文章

[译] 如何在React中写出更优秀的代码

目录 我们先来看 Linting 利用组件模块性.复用性和组合性 propTypes 和 defaultProps 知道何时创建新组件 组件 vs 纯组件 vs 无状态函数组件 无状态函数组件 纯组件 使用 React 开发工具 使用内联条件语句 尽可能使用代码片段库 React 本质 - 学习 React 是如何工作的 快速回顾 在React中写出更好代码的9条建议:学习关于 Linting, propTypes, PureComponent 等. Rajat S · 2018 年 4 月 1

如何写出优雅的CSS代码 ?(转)

对于同样的项目或者是一个网页,尽管最终每个前端开发工程师都可以实现相同的效果,但是他们所写的代码一定是不同的.有的优雅,看起来清晰易懂,代码具有可拓展性,这样的代码有利于团队合作和后期的维护:而有的混乱,虽然表达出了最终的效果,然而却晦涩难懂,显然团队成员在读这样的代码时就显得无从下手,更不利于后期的维护了.那么如何写出优雅的代码呢?下面我将以一个很小的项目就以下几个方面简单的表达一下自己的看法,如有不妥,望批评指正. 如何整理一个项目. 如何写出清晰易懂的HTML代码. 如何写出优雅的css代

【知识点】如何写出优雅的CSS代码 ?

对于同样的项目或者是一个网页,尽管最终每个前端开发工程师都可以实现相同的效果,但是他们所写的代码一定是不同的.有的优雅,看起来清晰易懂,代码具有可拓展性,这样的代码有利于团队合作和后期的维护:而有的混乱,虽然表达出了最终的效果,然而却晦涩难懂,显然团队成员在读这样的代码时就显得无从下手,更不利于后期的维护了.那么如何写出优雅的代码呢?下面我将以一个很小的项目就以下几个方面简单的表达一下自己的看法,如有不妥,望批评指正. 如何整理一个项目. 如何写出清晰易懂的HTML代码. 如何写出优雅的css代

如何写出优雅的css代码?

如何写出优雅的css代码? 对于同样的项目或者是一个网页,尽管最终每个前端开发工程师都可以实现相同的效果,但是他们所写的代码一定是不同的.有的优雅,看起来井井有条,清晰易懂,这样的代码有利于团队合作和后期的维护:而有的混乱,虽然表达出了最终的效果,然而却晦涩难懂,显然团队成员在读这样的代码时就显得无从下手,更不利于后期的维护了.那么如何写出优雅的代码呢?下面我将以一个很小的项目就以下几个方面简单的表达一下自己的看法,如有不妥,望批评指正. 如何整理一个项目. 如何写出清晰易懂的HTML代码. 如

【原创】怎样才能写出优雅的 Java 代码?这篇文章告诉你答案!

本文已经收录自 JavaGuide (59k+ Star):[Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识. 本文比较简短,基本就是推荐一些对于写好代码非常有用的文章或者资源.讲真的,下面推荐的文章或者资源强烈建议阅读 3 遍以上. 团队 阿里巴巴Java开发手册(详尽版) https://github.com/alibaba/p3c/blob/master/阿里巴巴Java开发手册(华山版).pdf Google Java编程风格指南: http://hawste

如何写出优雅兼备可读性的javascript代码

即或是最简单的需求,不同的程序员也会写出不一样的代码: 需求:充值程序过虑不符合条件的充值金额,即只能充入100.200.500.1000金额,其它过虑: 1.菜鸟程序员可能会这样写,虽然可读性强,代码啰嗦,不够优雅. $(function () { var recharge = 100; if (100 == recharge || 200 == recharge || 500 == recharge || recharge == 1000) { //to do some thing! } }

如何写出优雅的Python(二)

Print sorted list: colors=['red','green','blue','yellow'] for color in sorted(colors): print color for color in sorted(colors,reverse=True): print color Customer sort order: Bad: colors=['red','green','blue','yellow'] def compare_length(c1,c2): if le

如何写出优雅的Python之设置class缺省值

今天有个需求时需要为某个类设置缺省值 最开始的代码如下: Class myClass def __init__(self,datalen=None,times=None): if datalen == None : self.datalen = 1024 else : self.datalen = datalen if times == None: slef.times = 1024*1024 else : self.times = times 但这也太不Python了,简单修改如下,是不是看起

如何写出优雅的Python

Looping over a range of numbers Bad: for i in [0,1,2,3,4,5]: print i**2 Good: for i in range(6): print i**2 Looping over a collection: Bad: colors = [ 'red','green','blue','yellow'] for i in range(len(colors)): print colors[i] Good: for i in colors: