当组件里出现两个必须保持一致的 state,先停下来问一句:其中一个是否可以由另一个算出来?这往往是简化组件最直接的切入点。
找到真正会变化的输入
文章列表的输入是文章集合和搜索词。筛选结果可以在渲染时计算,不需要再保存一份。
import { useState } from 'react';
export function ArticleSearch({ articles }) {
const [query, setQuery] = useState('');
const normalizedQuery = query.trim().toLowerCase();
const visibleArticles = articles.filter(article =>
article.title.toLowerCase().includes(normalizedQuery)
);
return (
<section>
<label>
搜索文章
<input
value={query}
onChange={event => setQuery(event.target.value)}
/>
</label>
<ul>
{visibleArticles.map(article => (
<li key={article.id}>{article.title}</li>
))}
</ul>
</section>
);
}这个组件只有一份用户输入。文章集合更新或搜索词改变时,筛选结果自然会随渲染重新计算,不需要额外 Effect 来同步。
Effect 留给外部同步
Effect 适合把组件与外部系统同步,例如注册浏览器事件、建立连接或控制非 React 组件。单纯从现有 props 和 state 得到另一个值,通常不需要 Effect。
用 Effect 更新派生状态,会先产生一次携带旧结果的渲染,再触发额外的更新;更麻烦的是依赖列表遗漏时,数据会悄悄变旧。
useMemo 是优化工具
如果筛选规模确实很大,可以测量耗时,再考虑用 useMemo 缓存计算。它不应该成为保证业务正确性的前提,也不是所有数组操作的必备包装。
先保证代码直接、数据流单向,再依据性能记录优化。少一份状态,就少一条需要人工维护的同步关系。
一个实用检查
新增 state 前检查三件事:它是否来自用户输入或外部系统?它是否能由已有数据推导?如果已有数据改变,它是否还需要手动同步?最后一个问题经常能暴露重复状态。
读到这里,不妨动手试一试。
把一个概念变成能运行的小实验。