🧩 一、设计目标
封装一个“历史记录管理组件”,
能让筛选框、表单、搜索输入框等组件:
- 自动记录用户的输入历史;
- 支持查看、删除、清空;
- 数据持久化(localStorage);
- 与业务组件无侵入式适配。
✅ 典型交互场景:
比如一个搜索框👇
- 用户输入内容 → 自动存入历史;
- 点击搜索 → 历史记录更新;
- 点击输入框 → 展示历史选项;
- 点击历史记录 → 自动填入。
🧠 二、设计目标拆解
| 模块 | 职责 |
|---|---|
| 存储层 | 提供历史记录增删查封装(基于 localStorage) |
| 逻辑层 Hook / Service | 管理状态、封装增删逻辑、与组件交互 |
| UI 层组件 | 渲染历史记录下拉框、清空按钮等 |
| 适配层 | 与不同组件(输入框、筛选表单)无缝整合 |
🧱 三、核心设计思路(模块化架构)
┌─────────────────────────────┐
│ UI组件层:HistoryList、Input、Select │
├─────────────────────────────┤
│ 逻辑层 Hook:useHistory(key) │
├─────────────────────────────┤
│ 存储层:LocalHistoryService │
└─────────────────────────────┘
⚙️ 四、核心实现步骤
1️⃣ 封装一个 LocalStorage 工具类
class LocalHistoryService<T = string> {
private key: string;
private limit: number;
constructor(key: string, limit = 10) {
this.key = key;
this.limit = limit;
}
get(): T[] {
const data = localStorage.getItem(this.key);
return data ? JSON.parse(data) : [];
}
add(item: T) {
const list = this.get();
// 去重 + 截断
const newList = [item, ...list.filter(v => v !== item)].slice(0, this.limit);
localStorage.setItem(this.key, JSON.stringify(newList));
return newList;
}
remove(item: T) {
const list = this.get().filter(v => v !== item);
localStorage.setItem(this.key, JSON.stringify(list));
return list;
}
clear() {
localStorage.removeItem(this.key);
}
}
✅ 功能:
- 自动去重;
- 控制历史条数;
- 封装增删改查。
2️⃣ 封装逻辑层 Hook(以 React 为例)
import { useState } from "react";
export function useHistory(key: string, limit = 10) {
const service = new LocalHistoryService<string>(key, limit);
const [records, setRecords] = useState(service.get());
const add = (item: string) => setRecords(service.add(item));
const remove = (item: string) => setRecords(service.remove(item));
const clear = () => {
service.clear();
setRecords([]);
};
return { records, add, remove, clear };
}
✅ 封装优点:
- 任何组件都能复用;
- 内部与 localStorage 同步;
- 状态变化即触发重新渲染。
3️⃣ UI层组件(搜索框 + 历史展示)
import React, { useState } from "react";
import { useHistory } from "./useHistory";
export function SearchWithHistory() {
const { records, add, remove, clear } = useHistory("searchHistory", 8);
const [value, setValue] = useState("");
const handleSearch = () => {
if (value.trim()) add(value.trim());
};
return (
<div className="search-box">
<input
value={value}
onChange={e => setValue(e.target.value)}
onFocus={() => console.log("显示历史记录")}
placeholder="搜索内容"
/>
<button onClick={handleSearch}>搜索</button>
{records.length > 0 && (
<ul className="history-list">
{records.map(item => (
<li key={item} onClick={() => setValue(item)}>
{item}
<button onClick={() => remove(item)}>x</button>
</li>
))}
<li onClick={clear} className="clear">
清空历史
</li>
</ul>
)}
</div>
);
}
4️⃣ 通用适配(表单 / 筛选框)
任何组件都可以接入这个 Hook:
const Filter = () => {
const { records, add } = useHistory("filterHistory");
const [filter, setFilter] = useState("");
return (
<select
value={filter}
onChange={(e) => {
const val = e.target.value;
setFilter(val);
add(val);
}}
>
<option value="">选择分类</option>
<option value="A">类型 A</option>
<option value="B">类型 B</option>
</select>
);
};
或者在 Form 组件中统一注册:
function FormExample() {
const { add } = useHistory("formHistory");
const handleSubmit = (values: Record<string, string>) => {
add(JSON.stringify(values));
};
return <YourFormComponent onFinish={handleSubmit} />;
}
🧩 五、扩展设计
| 功能 | 实现方式 |
|---|---|
| ✅ 支持多用户 | 在 key 前加 userId 前缀 |
| ✅ 支持过期时间 | 存储结构 {value, expire},校验过期 |
| ✅ 支持模糊搜索 | records.filter(v => v.includes(keyword)) |
| ✅ 支持全局共享 | 用 React Context / Vue Provide 注入 service |
| ✅ 支持跨页面同步 | 监听 window.storage 事件更新 UI |
💡 六、工程化优化建议
-
防抖写入: 避免频繁操作 localStorage
→ 使用lodash.debounce封装 add。 -
封装通用接口:
interface HistoryStorage<T> { get(): T[]; add(item: T): T[]; remove(item: T): T[]; clear(): void; }可以替换存储方式(localStorage / indexedDB)。
-
泛型支持: 支持非 string 历史记录(比如对象型表单)。
-
UI 与逻辑解耦: Hook 负责逻辑,UI 由业务组件决定。
🧠 七、整体架构总结
用户交互
↓
useHistory Hook —— 封装状态与逻辑
↓
LocalHistoryService —— 操作 localStorage
↓
localStorage —— 持久化存储
✅ 八、一句话总结
TypeScript + localStorage + Hook 封装
实现一个轻量级“历史记录中间层”,
让表单、筛选框、搜索框都能无缝复用历史功能,
并具备去重、持久化、可清除等工程化特性。