赞
踩
本文涉及的技术包括:react、react-router(4.0及其以上)、femo(数据管理库)。这些技术都能找到对应的替代。
在大型前端系统里面,业务场景复杂,系统复杂度高,开发人员协同难度大。这样的系统里面需要做许多设计来降低系统的复杂度,提高开发效率,适应多变的使用场景。其中路由设计是核心之一。
什么是大型前端系统?笔者认为(比较主观)直观地以代码量和团队规模来衡量:代码量超过5万,前端人员规模超过5人,二者达其一。
笔者经历过前端系统代码20万+,前端开发人员10+的项目,也经历过前端系统代码4万+,只有一个前端开发人员的项目。基于原则:降低系统的复杂度,提高开发效率,适应多变的使用场景,对路由设计做了一些初潜地总结。
为什么是数据驱动?
定义路由数据结构
- interface Road {
- name: string; // 名字
- path: string; // 路径(同层唯一)
- component: React.ComponentType; // 对应的组件
- subRoads?: Road[]; // 下级路由
- permissions?: Set<string> | string[]; // 路由所需要的权限码。规定:如果是数组,权限码之间是且关系;如果是Set则是或关系。二者可互相嵌套,来表达复杂的逻辑关系。
- fallback?: (props: RouteComponentProps) => any; // 遇到无权限时,回退渲染的视图逻辑
- hasHeader?: boolean; // 是否有页面header
- hasSider?: boolean; // 是否有侧边菜单
- visible?: boolean; // 是否可见
- }
上面的数据结构经过简化,实际的项目中要复杂很多。
实际上路由数据不单单会给路由组件消费,路由周边的组件也会消费。比如页面的菜单栏、页面头部、页面文档的标题、面包屑导航、以及其他有导航功能的地方,一些页面区域的显示隐藏也和路由数据相关。这些页面的功能区域,可以根据同一份路由数据来实现。相反的,如果这些页面功能区需要的数据没有,那么要在路由数据结构里面新增。
定义一个路由
- import { gluer } from 'femo';
- import { Road } from '../interface';
- import Homepage from './Homepage';
- import Cart from './Cart';
-
- const initRoad: Road = {
- name: '商城系统',
- path: '/shop',
- component: () => <Redirect to='/shop/homepage'/>,
- permissions: [], // 表示不需要权限
- hasHeader: true, // 页面头部展示 默认展示
- hasSider: true, // 页面菜单展示 默认展示
- visible: true, // 菜单项展示 默认展示
- subRoads: [{
- name: '首页',
- path: '/homepage',
- component: Homepage,
- }, {
- name: '购物车',
- path: '/cart',
- component: Cart,
- }]
- }
-
- const shopRoad = gluer(initRoad);
-
- export default shopRoad;
-
将路由集中
- import { gluer } from 'femo';
- import { Road } from './interface';
- import shopRoad from './Shop/road';
-
- const roadMap = gluer({
- shop: shopRoad(),
- })
-
- // 设置依赖
- // shopRoad的变化会同步到roadMap中
- roadMap.relyOn([shopRoad], (roads, state) => {
- return {
- ...state,
- shop: roads[0] as Road,
- }
- });
-
- export default roadMap;
文件目录结构如下
效果如下
知乎视频www.zhihu.com场景一
产品小王提出:用户进入“首页”时,页面的头部隐藏掉。此时前端开发小李快速地敲了几下键盘就搞定了,代码如下:
- import { gluer } from 'femo';
- import { Road } from '../interface';
- import Homepage from './Homepage';
- import Cart from './Cart';
-
- const initRoad: Road = {
- name: '商城系统',
- path: '/shop',
- component: () => <Redirect to='/shop/homepage'/>,
- permissions: [], // 表示不需要权限
- hasHeader: true, // 页面头部展示 默认展示
- hasSider: true, // 页面菜单展示 默认展示
- visible: true, // 菜单项展示 默认展示
- subRoads: [{
- name: '首页',
- path: '/homepage',
- component: Homepage,
- hasHeader: false, // 隐藏页面头部(改了这里)
- }, {
- name: '购物车',
- path: '/cart',
- component: Cart,
- }]
- }
-
- const shopRoad = gluer(initRoad);
-
- export default shopRoad;
效果如下:
知乎视频www.zhihu.com场景二
产品小王隔了几天,突发奇想:希望用户进入购物车的时候,在左侧导航栏增加一个菜单项“历史记录”,但这个“历史记录”菜单项在“首页”的时候又没有。前端小李表示很奇怪,这是什么需求!但最终还是做了,过了大概几分钟,实现了如下效果。产品小王满意地点了点头。
效果如下:
知乎视频www.zhihu.com核心改动如下:
在 购物车 挂载时,新增一个history的路由配置
- useEffect(() => {
- // 挂载的时候去新增一个history路由配置
- shopRoad((_data, state) => {
- const subPaths: Road[] = [...(getSafe(state, 'subPaths') || [])];
- const target = subPaths.find((item) => getSafe(item, 'path') === '/history');
- if (target) return state;
- const history: Road = {
- name: '历史记录',
- path: '/history',
- component: History,
- };
- subPaths.push(history);
- const tmpState = { ...state };
- tmpState.subPaths = subPaths;
- return tmpState;
- });
- }, []);
在 首页 挂载时,从路由配置中删除history
- useEffect(() => {
- // 挂载的时候去删除history路由配置
- shopRoad((_data, state) => {
- const subPaths: Road[] = [...(getSafe(state, 'subPaths') || [])];
- subPaths.find((item, i) => {
- const flag = getSafe(item, 'path') === '/history';
- if (flag) {
- subPaths.splice(i, 1);
- }
- return flag;
- });
- const tmpState = { ...state };
- tmpState.subPaths = subPaths;
- return tmpState;
- });
- }, []);
场景三
又没过几天,产品小王又来找前端小李,没错又是一个新需求。产品小王希望在一个商品详情的页面,加一个“返回”按钮。这个返回按钮是一个下拉菜单,里面的菜单项与商城系统的菜单项一致。前端小李想了想,共用一下路由数据,生成一个下拉菜单就行了,小意思。
一会就有了下面的效果
知乎视频www.zhihu.com核心代码如下:
- const [road] = useModel<Road>(shopRoad);
-
- const pathPrefix = getSafe(road, 'path');
- const subPaths: Road[] = getSafe(road, 'subPaths') || [];
- const overlay = (
- <Menu>
- {
- subPaths.map((s) => {
- const path = getSafe(s, 'path');
- const name = getSafe(s, 'name');
- const visible = getSafe(s, 'visible');
- if (visible === false) return null;
- return (
- <Menu.Item key={path}>
- <Link to={`${pathPrefix}${path}`}> {name} </Link>
- </Menu.Item>
- );
- })
- }
- </Menu>
- );
在今后的日子里,类似上面的场景还会不断在产品小王和前端小李之间发生。
从上面的几个例子中可以看到一些数据驱动好处:操作简单、适用性广。数据本身也具有比组件更好的弹性:增加属性或者改变结构来适配业务。
上面的例子并没有详细说明,侧边菜单栏、页面顶部、路由组件是如何消费数据的,因为篇幅限制加上这部分实现的方式比较灵活。
除了上面的例子,还有许多对页面访问相关的需求,比如不同入口进来看见不同菜单和页面、对灰度的要求、对用户不同身份看见的页面不同等。这些需求一般在系统入口处做,属于比较上层。原理都一样,就是操作路由数据。
结合业务场景和业务开发,可以总结出一些数据模型,这些数据模型具有某些业务通用性。而透过总结的这些数据模型,可以体会到数据是核心,一切展示和交互都是数据变化和流动。进而能引发思考:如何管理好数据。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。