组件边界应围绕职责和变化原因。把每十行代码拆成一个组件,不会自然得到清晰架构;把请求、校验、列表、弹窗和权限都塞进一个页面,也会让变化互相牵连。先画出数据从哪里来、谁能修改、哪些界面共同使用,再决定边界。
同一条数据只选一个权威来源
| 状态 | 常见归属 | 示例 |
|---|---|---|
| 短暂交互 | 最近的共同组件 | 输入草稿、弹窗是否打开 |
| 可分享条件 | URL | 查询词、筛选、页码 |
| 服务端记录 | 查询与缓存层 | 课程、账号资料 |
| 可推导值 | 计算函数 | 待学数量、过滤后列表 |
| 本地偏好 | 持久化适配层 | 主题、显示密度 |
把“全部记录”和“过滤后记录”都保存成可独立写入的状态,容易只改一份。保存原始记录与筛选条件,每次推导展示结果,能减少同步工作。
type RecordItem = { id: string; title: string; done: boolean };
type Filter = "all" | "pending";
function visible(items: RecordItem[], filter: Filter, query: string) {
const word = query.trim().toLocaleLowerCase();
return items.filter(item =>
(filter === "all" || !item.done) &&
item.title.toLocaleLowerCase().includes(word));
}
选择全局状态库应由共享范围、调试需求和更新模式决定,不应把所有输入值都提升到全局。服务端数据还需要过期、重试、去重与失效策略,和一个全局布尔变量不同。
把副作用放到边界
可按页面/业务模块组织,再在需要时分离纯函数、请求适配与展示组件。纯函数容易验证;适配层处理浏览器存储、HTTP 和平台差异;组件负责交互与反馈。小项目不必先搭多层抽象,但接口返回格式不应散落在每一个按钮回调中。
路由既是导航也是状态入口。能复制、刷新、返回的条件放入 URL;一次击键是否产生新的 history 记录应有明确规则,通常输入过程中替换,确认导航时新增。部署 SPA 时必须检查深链接刷新;服务器不能把 JS、图片缺失也一律返回 HTML。
CSR、SSR 与 SSG
CSR 在浏览器执行代码生成主要界面;SSR 在请求时生成 HTML;SSG 在构建时生成 HTML。应用可以按路由混用。SSR 仍需要处理加载、权限和缓存;需要交互时还可能进行水合,把客户端事件行为接到已有标记上。
水合要求服务端和客户端初始结果保持一致。直接在初次渲染读取随机数、时间或仅浏览器存在的数据,可能造成不一致。服务端模块中的全局可变用户数据还可能跨请求泄漏;缓存也必须区分公共与私人结果。采用 SSR 应基于首屏、索引、部署和数据新鲜度需求,而不是认为它总比 CSR 快。
练习:为学习记录应用列出状态归属,要求刷新后恢复筛选,但不恢复一半填写的临时弹窗。再说明离线、多个标签页和跨设备编辑分别需要额外处理什么。
面试自查:什么时候拆组件?派生状态为什么可能产生同步错误?SSR 是否自动改善所有性能?前端隐藏按钮能否代替服务端权限检查?
参考:React 状态结构、Vue 状态管理、渲染模式。