如何更新Recoil atom不触发重渲染 解决大量手风琴点击卡顿问题
问题根因
你当前将所有accordion的选中状态存在同一个_SelectedCategories atom中,所有accordion组件都直接订阅了这个完整atom,只要atom值更新,所有依赖它的组件都会触发重渲染,250个accordion每个嵌套15+子组件的情况下,渲染压力自然会很大。另外你当前使用defaultExpanded属于非受控属性,仅在组件初始化时生效,后续atom更新不会同步修改accordion的展开状态,本身就存在逻辑问题。
最优方案:使用atomFamily拆分状态
Recoil的atomFamily可以为每个accordion根据唯一标识propKey生成独立的atom,只有对应标识的atom更新时,才会触发对应的accordion组件重渲染,其余组件完全不受影响。
代码实现:
- 定义atomFamily
import { atomFamily } from 'recoil'; // 每个accordion对应一个独立的atom,入参为propKey export const selectedCategoryState = atomFamily({ key: 'selectedCategoryState', default: false, });
- 单个accordion组件内使用
// 只订阅当前propKey对应的atom状态 const isExpanded = useRecoilValue(selectedCategoryState(propKey)); const setIsExpanded = useSetRecoilState(selectedCategoryState(propKey)); const openAccordion = () => { setIsExpanded(prev => !prev); }; // 注意要用受控属性expanded,不要用defaultExpanded return ( <Accordion expanded={isExpanded} id={propKey}> <AccordionSummary onClick={openAccordion}> { /* Accordion Summary Header */ } </AccordionSummary> <AccordionDetails> { /* Accordion Details Here */ } </AccordionDetails> </Accordion> );
兼容方案:不拆分atom的情况下用selectorFamily按需订阅
如果不想改动原有atom的结构,可以用selectorFamily实现按需订阅,每个组件只取和自己相关的状态值,只有当这个值发生变化时才会触发重渲染:
代码实现:
- 定义selectorFamily
import { selectorFamily } from 'recoil'; import { _SelectedCategories } from './your-atom-path'; export const categoryExpandedState = selectorFamily({ key: 'categoryExpandedState', get: (propKey) => ({ get }) => { const selected = get(_SelectedCategories); return selected?.category === propKey; }, set: (propKey) => ({ set }, newValue) => { set(_SelectedCategories, prev => { return { ...prev, category: newValue ? propKey : '' }; }); } });
- 组件内使用和上面atomFamily的用法完全一致,不需要其他修改。
额外优化点
- 给Accordion组件包裹
React.memo,避免无关的父组件重渲染传递到子组件:
const AccordionItem = React.memo(({ propKey }) => { // 组件内部逻辑 });
- 避免使用
JSON.parse(JSON.stringify)做深拷贝,对于单层对象直接用展开运算符{...prev}即可,性能更好,也不会有序列化丢失问题。
内容的提问来源于stack exchange,提问作者tjg92
相关产品推荐
相关产品推荐

