H5 前端状态管理与模块边界
H5 前端状态管理与模块边界
状态管理不是把所有数据放进一个全局仓库。它更像是在回答三个问题:这个状态属于谁、谁能修改它、什么时候应该被重置。边界清晰的状态设计,能让页面更容易理解,也能减少组件之间的隐式依赖。
一、先分清状态类型
页面状态可以分为几类:服务端数据、用户输入、界面状态和路由状态。不同类型的状态生命周期和修改方式不同,不应全部混在一起。
interface ProductState {
products: Product[];
keyword: string;
isSubmitting: boolean;
selectedFilter: string;
}
products 来源于接口,keyword 来源于用户输入,isSubmitting 是界面状态,selectedFilter 更适合由路由承载。把不同生命周期的状态放在一起,会难以判断谁负责重置。
二、组件状态优先于全局状态
如果一个状态只被当前组件及其子组件使用,放在组件内部通常更直接。只有确实需要跨页面或跨层级共享时,再提升到全局。
const keyword = ref('');
过早全局化会让依赖关系变弱,也让排查问题变难。全局状态的每一项都应能说明为什么需要全局。
三、共享状态的修改必须收敛
共享状态如果允许任何组件直接修改,很快就会失去控制。修改逻辑应集中在定义状态的地方。
const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([]);
function changeQuantity(productId: string, quantity: number): void {
const item = items.value.find(
(entry) => entry.productId === productId
);
if (!item) return;
item.quantity = quantity;
}
return { items, changeQuantity };
});
组件调用 changeQuantity 表达业务意图,而不是直接改数组。这样修改入口清晰,也方便记录日志和埋点。
四、异步操作与状态更新
异步请求返回后,页面可能已经切换,或者用户已经触发了新的请求。状态更新前要确认当前操作仍然有效。
let requestToken = 0;
async function loadProducts(keyword: string): Promise<void> {
const token = ++requestToken;
const result = await productApi.list(keyword);
if (token !== requestToken) return;
items.value = result;
}
如果项目已有统一的请求取消机制,应优先复用,而不是在每个状态模块里重复实现一套。
五、派生状态不要冗余存储
总价、总数、是否可提交这类数据可以由基础状态计算得出,不应单独维护一份。
const totalPrice = computed(() =>
items.value.reduce((total, item) => {
return total + item.priceInCent * item.quantity;
}, 0)
);
冗余状态需要手动保持同步,一旦遗漏就出现数据不一致。只有计算成本极高或需要缓存时,才考虑额外存储。
六、服务端状态与客户端状态分开
服务端数据的生命周期通常跟随页面或业务流程,客户端状态更多来自用户交互。把两者放在不同层次管理,会让重置和刷新更简单。
const query = useProductQuery(keyword);
const selection = ref<string[]>([]);
重新拉取数据时只重置服务端数据相关状态,不应该把用户正在编辑的输入一起清空。
七、页面离开时要正确重置
全局状态不会因为组件销毁而自动消失。如果页面依赖上一次留下的状态,用户再次进入时可能看到脏数据。
onUnmounted(() => {
resetPageState();
});
对于需要跨页面保留的状态,例如购物车,应显式说明它的生命周期。不要让“刚好没被清掉”成为一种隐式设计。
八、模块之间只依赖接口
业务模块之间的依赖应尽量通过方法调用或事件传递,而不是直接读取对方内部状态。
const orderStore = useOrderStore();
await orderStore.submitOrder(cartStore.items);
如果订单模块直接大量读取购物车内部结构,购物车字段调整会影响订单逻辑。跨模块数据传递时,优先传递明确的业务参数。
九、不要让状态成为万能容器
状态仓库不是所有逻辑的归宿。请求构建、格式化、权限判断这类纯逻辑,更适合提取为独立方法或领域服务。
function formatPrice(priceInCent: number): string {
return `${(priceInCent / 100).toFixed(2)}`;
}
状态模块负责保存和修改状态,不要把所有工具函数都塞进去,否则状态仓库会变成难以维护的大文件。
十、让状态变化可追踪
调试复杂页面时,如果不知道是谁修改了状态,排查会非常困难。修改逻辑收敛到方法后,还应配合开发工具追踪变化。
store.$subscribe((mutation, state) => {
logger.debug('cart changed', mutation.type);
});
关键业务动作可以在修改入口增加日志,但不要在组件里到处打印,否则日志本身就是新的噪音。
总结
状态管理的重点是边界而不是工具。先区分服务端数据、用户输入、界面状态和路由状态,只在真正需要共享时全局化。修改逻辑收敛到定义处,派生数据用计算属性,页面离开显式重置。模块之间通过明确接口协作,让每一次状态变化都有清晰的责任归属。
- 点赞
- 收藏
- 关注作者
评论(0)