解决了与应用路由相结合的问题,具体到布局组件内部,其中最重要的两部分就是页面的页眉和页脚部分,而页眉又可以分为应用页眉与页面页眉两部分。
应用页眉指的是整个应用层面的页眉,与具体的页面无关,一般来说会包含用户头像、通知栏、搜索框、多语言切换等这些应用级别的信息与操作。页面页眉则一般来讲会包含页面标题、面包屑导航、页面通用操作等与具体页面相关的内容。
在以往的项目中,尤其是在项目初期许多开发者因为对项目本身还没有一个整体的认识,很多时候会倾向于将应用页眉做成一个展示型组件并在不同的页面中直接调用。这样做当然有其方便之处,比如说页面与布局之间的数据同步环节就被省略掉了,每个页面都可以直接向页眉传递自己内部的数据。
但从理想的项目架构角度来讲这样做却是一个 反模式(anti-pattern) 。因为应用页眉实际是一个应用级别的组件,但按照上述做法的话却变成了一个页面级别的组件,伪代码如下:
<App> <BasicLayout> <PageA> <AppHeader title="Page A" /> </PageA> </BasicLayout> <BasicLayout> <PageB> <AppHeader title="Page B" /> </PageB> </BasicLayout> </App>
从应用数据流的角度来讲也存在着同样的问题,那就是应用页眉应该是向不同的页面去传递数据的,而不是反过来去接收来自页面的数据。这导致应用页眉丧失了控制自己何时 rerender(重绘) 的机会,作为一个纯展示型组件,一旦接收到的 props 发生变化页眉就需要进行一次重绘。
另一方面,除了通用的应用页眉外,页面页眉与页面路由之间是有着严格的一一对应的关系的,那么我们能不能将页面页眉部分的配置也做到路由配置中去,以达到新增加一个页面时只需要在 config/routes.js 中多配置一个路由对象就可以完成页面页眉部分的创建呢?理想情况下的伪代码如下:
<App> <BasicLayout> // with app & page header already <PageA /> </BasicLayout> <BasicLayout> <PageB /> </BasicLayout> </App>
1、配置优于代码
在过去关于组件库的讨论中我们曾经得出过代码优于配置的结论,即需要使用者自定义的部分,应该尽量抛出回调函数让使用者可以使用代码去控制自定义的需求。这是因为组件作为极细粒度上的抽象,配置式的使用模式往往很难满足使用者多变的需求。但在企业管理系统中,作为一个应用级别的解决方案,能使用配置项解决的问题我们都应该尽量避免让使用者编写代码。
配置项(配置文件)天然就是一种集中式的管理模式,可以极大地降低应用复杂度。以页眉为例来说,如果我们每个页面文件中都调用了页眉组件,那么一旦页眉组件出现问题我们就需要修改所有用到页眉组件页面的代码。除去 debug 的情况外,哪怕只是修改一个页面标题这样简单的需求,开发者也需要先找到这个页面相对应的文件,并在其 render 函数中进行修改。这些隐性成本都是我们在设计企业管理系统解决方案时需要注意的,因为就是这样一个个的小细节造成了本身并不复杂的企业管理系统在维护、迭代了一段时间后应用复杂度陡增。理想情况下,一个优秀的企业管理系统解决方案应该可以做到 80% 以上非功能性需求变更都可以使用修改配置文件的方式解决。
2、配置式页眉
import { matchRoutes } from 'react-router-config'; // routes config const routes = [{ path: '/outlets', exact: true, permissions: ['admin', 'user'], component: Outlets, unauthorized: Unauthorized, pageTitle: '门店管理', breadcrumb: ['/outlets'], }, { path: '/outlets/:id', exact: true, permissions: ['admin', 'user'], component: OutletDetail, unauthorized: Unauthorized, pageTitle: '门店详情', breadcrumb: ['/outlets', '/outlets/:id'], }]; // find current route object const pathname = get(state, 'router.location.pathname', ''); const { route } = head((matchRoutes(routes, pathname)));
基于这样一种思路,我们可以在通用的布局组件中根据当前页面的 pathname 使用 react-router-config 提供的 matchRoutes 方法来获取到当前页面 route 对象的所有配置项,也就意味着我们可以对所有的这些配置项做统一的处理。这不仅为处理通用逻辑带来了方便,同时对于编写页面代码的同事来说也是一种约束,能够让不同开发者写出的代码带有更少的个人色彩,方便对于代码库的整体管理。
3、页面标题