zoukankan      html  css  js  c++  java
  • 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]+@)?[A-Za-z0-9.-]+(?::d+)?|(?:www.|[-;:&=+$,w]+@)[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 之上的架构设计,再往下深入探讨应用点,缺的部分会在后续系列中补上~ 请关注公众号:

    作者:捷义
    出处:http://www.cnblogs.com/youclk/
    说明:转载请标明来源和作者
  • 相关阅读:
    WPF鼠标拖放功能(拖放图片,文本)
    PHP封装属性
    vs 启动时报错:未能加载文件或程序集 SharpGit
    C#.NET Winform承载WCF RESTful API (硬编码配置)
    使用edge浏览器时,怎么让alt+tab不切换他的子标签页而只在程序间切换?
    C#.NET Winform使用线程承载WCF (硬编码配置)
    AnkhSVN For Visual Studio 2022
    ASP.NET MVC 查询加分页
    C#.NET Winform承载WCF RESTful API (App.config 方式)
    C#.NET Windows服务承载WCF
  • 原文地址:https://www.cnblogs.com/youclk/p/10094745.html
Copyright © 2011-2022 走看看