关于算法优化在前端工程实践中运用的思考

三葉Leaves Author

在做算法题的时候,我们经常用 “空间换时间” 的方法,把 O(n) 的算法优化到 O(1) ,并“引以为傲”。但今天碰到的这个场景,让我开始重新思考这件事:

在前端领域,“空间换时间”有时候不一定是一件好事“

案例展示

我们有一个大大的路由表,现在,我们需要处理这个路由表,把它变成面包屑菜单(Breadcrumb)组件可用的样子。

也就是对于每一个页面 URL,我们都要有一个对应的链路,来指导组件怎么渲染出面包屑菜单。

例如:

对于 http://localhost:xxxx/dashboard/committee/create

渲染出 Mypaper => Dashboard => Committee => Create Committee

案例源码在下面,有兴趣的可以看看。

如果考虑到最极致的优化:

我可以将所有 pathname 对应的 SideBarItem[] 在组件顶部就计算出来并存到 hash map,这样每次 pathname 变化时只需要进行一次 O(1) 的查找即可。

然而,从 nav-items.ts 得到 hash map 的这样的过程,还是要交给用户的浏览器(考虑到 RSC ,也有可能是部署 node 项目的服务器)计算。

不过显而易见的,由于 nav-items.ts 不可能动态改变,所以其实在编译期就应该已经可以计算出来了,也就是编译的时候计算一次,之后就不用让每个用户或者每台服务器都去跑一遍 nav-items.ts 到 hash map 的计算。

工程架构与性能的思考

nextjs, vite 等等框架都有办法在编译期就执行某些脚本或者代码,能计算出咱们需要的静态 map 数据,如此一来确实能实现上面的优化了。

但是实际工程开发中,我考虑到了下面的两点:

1. 是否值得牺牲灵活性、增加复杂度和维护难度?

要实现上面的想法,我们需要改 build 时候的配置。将来逻辑变了,我们可能还要到处改,带来额外的复杂度和维护难度。

我们写的是前端网页,这个场景本身就不会处理大量数据。这不是百万并发的 GO 服务,也不是跑在用户 PC 上的 Rust 应用,在前端领域工程开发速度和迭代时候的维护难度,相比这一点性能提升会重要的多。

2. ”空间换时间“行为本身的问题?

这点甚至比第一点更致命。

在提升算法性能上,这笔账可能会值。但是在前端领域,如果真做到了上面的优化,最后也需要传一个大大的 json map 给用户的浏览器。

浏览器算出一个 map 往往很快,但是让用户等待体积巨大的 json map 传过来,往往对用户体验的影响更明显,尤其在网络不佳的移动设备场景等等。

案例源码

frontend\src\app\dashboard\components\site-header.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"use client";

import { useMemo } from "react";
import { usePathname } from "next/navigation";
import { SidebarIcon } from "lucide-react";
import { Link } from "next-view-transitions";

import { SearchForm } from "@/app/dashboard/components/search-form";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { useSidebar } from "@/components/ui/sidebar";

import {
NAV_ITEMS,
type SideBarItem,
} from "@/app/dashboard/constants/nav-items";

// 辅助函数:在树中查找路径
function findBreadcrumbPath(
items: SideBarItem[],
pathname: string,
): SideBarItem[] | null {
for (const item of items) {
// 1. 如果当前项匹配 URL,返回包含当前项的数组
if (item.url === pathname) {
return [item];
}

// 2. 如果有子项,递归查找
if (item.children) {
const childPath = findBreadcrumbPath(item.children, pathname);
// 如果在子项中找到了,将当前项加到路径头部并返回
if (childPath) {
return [item, ...childPath];
}
}
}
return null;
}
const allNavItems = NAV_ITEMS.flatMap((item) => item.items);

export function SiteHeader() {
const { toggleSidebar } = useSidebar();
const pathname = usePathname();

// 计算面包屑数据
const breadcrumbs = useMemo(() => {
// 1. 扁平化所有菜单项为 SideBarItem 列表

// 2. 递归的查找匹配当前 pathname 的完整链路
const rawPath = findBreadcrumbPath(allNavItems, pathname);

// 3. 过滤掉没有 url 的父级节点
return rawPath ? rawPath.filter((item) => item.url) : [];
}, [pathname]);

return (
<header className="sticky top-0 z-50 flex w-full items-center border-b bg-background">
<div className="flex h-(--header-height) w-full items-center gap-2 px-4">
<Button
className="h-8 w-8"
variant="ghost"
size="icon"
onClick={toggleSidebar}
>
<SidebarIcon />
</Button>
<Separator orientation="vertical" className="mr-2 h-4" />

<Breadcrumb className="hidden sm:block">
<BreadcrumbList>
{/* 静态起点:Mypapers */}
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/">Mypapers</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />

{/* 静态起点:Dashboard */}
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/dashboard">Dashboard</Link>
</BreadcrumbLink>
</BreadcrumbItem>

{/* 动态生成的面包屑部分 */}
{breadcrumbs.map((item, index) => {
const isLast = index === breadcrumbs.length - 1;

return (
<div
key={item.url || item.title}
className="flex items-center gap-2"
>
<BreadcrumbSeparator />
<BreadcrumbItem>
{isLast ? (
// 最后一个节点显示为 Page (当前页,不可点)
<BreadcrumbPage>{item.title}</BreadcrumbPage>
) : (
// 中间节点显示为 Link
<BreadcrumbLink asChild>
<Link href={item.url!}>{item.title}</Link>
</BreadcrumbLink>
)}
</BreadcrumbItem>
</div>
);
})}
</BreadcrumbList>
</Breadcrumb>

<SearchForm className="w-full sm:ml-auto sm:w-auto" />
</div>
</header>
);
}

frontend\src\app\dashboard\constants\nav-items.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import {
Calendar,
Medal,
Newspaper,
NotebookText,
NotepadTextDashed,
PackagePlus,
ShoppingCart,
UsersRound,
type LucideIcon,
} from "lucide-react";

export interface SideBarList {
label: string;
items: SideBarItem[];
}

export type SideBarItem = {
title: string;
icon?: LucideIcon;
/**
* 是否在 SideBar 中隐藏(但可用于面包屑/路由层级关系)
* @default false
*/
hideInSideBarMenu?: boolean;
/**
* 控制进入页面时是否展开子菜单,默认不展开
* @default false
*/
isActive?: boolean;
/**
* 最顶层的 URL 在 SideBar 中不会生效,只用于面包屑/路由层级关系
*/
url?: string;
/**
* 子菜单可以嵌套任意层级,但 SideBar 至多渲染到第 2 层
*/
children?: SideBarItem[];
};

const PLATFORM_ITEMS: SideBarList = {
label: "Platform",
items: [
{
title: "Committee",
url: "/dashboard/committee",
icon: UsersRound,
isActive: true,
children: [
{
title: "Create Committee",
url: "/dashboard/committee/create",
hideInSideBarMenu: true,
},
],
},
{
title: "Conference",
icon: Calendar,
children: [
{
title: "Manage Conference",
url: "/dashboard/conference",
children: [
{
title: "Create Conference",
url: "/dashboard/conference/create",
hideInSideBarMenu: true,
},
],
},
{
title: "Conference Issues",
url: "/dashboard/conference/issues",
},
],
},
{
title: "Journal",
icon: Newspaper,
children: [
{
title: "Manage Journal",
url: "/dashboard/journal",
children: [
{
title: "Create Journal",
url: "/dashboard/journal/create",
hideInSideBarMenu: true,
},
],
},
{
title: "Journal Issues",
url: "/dashboard/journal/issues",
},
],
},
],
};

const PAPERS_ITEMS: SideBarList = {
//...
};
const NFT_ITEMS: SideBarList = {
//...
};

export const NAV_ITEMS: SideBarList[] = [
PAPERS_ITEMS,
NFT_ITEMS,
PLATFORM_ITEMS,
];
  • 标题: 关于算法优化在前端工程实践中运用的思考
  • 作者: 三葉Leaves
  • 创建于 : 2025-12-24 00:00:00
  • 更新于 : 2026-03-16 12:05:05
  • 链接: https://blog.oksanye.com/d6bcf10210cb/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论