将 React PureComponent 重构为基于钩子的功能组件
Refactoring a React PureComponent to a hooks based functional component
我有一个工作 class based implementation of an Accordion component which I'm trying to refactor to use the new hooks api。
我的主要挑战是找到一种方法来仅重新渲染切换的 <AccordionSection />
,同时防止所有其他 <AccordionSection/>
组件在每次父级状态 [=13] 时重新渲染=](跟踪其状态的打开部分)已更新。
在基于 class 的实现中,我设法通过将 <AccordionSection />
设为 PureComponent
,传递 isOpen
和 onClick
来实现这一点通过使用 context
API、 和 的高阶组件回调它,方法是将这些回调保存在父 <Accordion/>
组件的状态中如下:
this.state = {
/.../
onClick: this.onClick,
isOpen: this.isOpen
};
据我所知,它保留了对它们的引用,从而防止它们在每次 <Accordion />
更新时被创建为新实例。
但是,我似乎无法让它与基于挂钩的实现一起使用。
有些我已经尝试过但没有成功的事情:
用 memo
包装 Accordion 部分 - 在第二个回调参数上包括各种渲染条件。
用 useCallback
包装 onClick
和 isOpen
回调(似乎不起作用,因为它们有依赖项,每个 <Accordion/>
都会更新渲染)
将 onClick
和 isOpen
保存到这样的状态:const [callbacks] = useState({onClick, isOpen})
然后将 callbacks
对象作为 ContextProvider
value
。 (似乎是错误的,并没有奏效)
以下是对我基于 class 的工作实现的引用:
https://codesandbox.io/s/4pyqoxoz9
我的钩子重构尝试:
https://codesandbox.io/s/lxp8xz80z7
我将日志保存在 <AccordionSection/>
渲染上,以展示我试图阻止的重新渲染。
任何输入将不胜感激。
所以我在追了太多兔子之后最后添加了这个小金块..
const cache = {};
const AccordionSection = memo(({ children, sectionSlug, onClick, isOpen }) => {
if (cache[sectionSlug]) {
console.log({
children: children === cache[sectionSlug].children,
sectionSlug: sectionSlug === cache[sectionSlug].sectionSlug,
onClick: onClick === cache[sectionSlug].onClick,
isOpen: isOpen === cache[sectionSlug].isOpen
});
}
cache[sectionSlug] = { children, sectionSlug, onClick, isOpen };
这表明 onClick
正在发生变化。这看起来很明显,因为 Accordion 组件正在渲染并创建一个新的 onClick
.
用 useCallback
包装他 onClick
的创作可以解决这个问题。
const onClick = useCallback(
sectionSlug =>
setOpenSections({
...(exclusive ? {} : openSections),
[sectionSlug]: !openSections[sectionSlug]
}),
[]
);
虽然我似乎确实在这个过程中遇到了问题 exclusive
因为它现在总是启用的..
https://codesandbox.io/s/1o08p08m27
哦,我确实在那里移动了其他一些可能有助于修复的部分..
更新
重构为使用 useReducer
并将所有逻辑移至那里,以便我们可以提供稳定的 onClick
更新
他们说睡眠很好,但对我来说只是想入睡..
我知道我遗漏了什么.. 昨晚意识到我们不需要减速器,只需要 setState
的函数形式,它允许我们从内部访问最新状态useCallback
记忆功能。在此处转换了@itaydafna 的优化 https://codesandbox.io/s/8490v55029
我有一个工作 class based implementation of an Accordion component which I'm trying to refactor to use the new hooks api。
我的主要挑战是找到一种方法来仅重新渲染切换的 <AccordionSection />
,同时防止所有其他 <AccordionSection/>
组件在每次父级状态 [=13] 时重新渲染=](跟踪其状态的打开部分)已更新。
在基于 class 的实现中,我设法通过将 <AccordionSection />
设为 PureComponent
,传递 isOpen
和 onClick
来实现这一点通过使用 context
API、 和 的高阶组件回调它,方法是将这些回调保存在父 <Accordion/>
组件的状态中如下:
this.state = {
/.../
onClick: this.onClick,
isOpen: this.isOpen
};
据我所知,它保留了对它们的引用,从而防止它们在每次 <Accordion />
更新时被创建为新实例。
但是,我似乎无法让它与基于挂钩的实现一起使用。
有些我已经尝试过但没有成功的事情:
用
memo
包装 Accordion 部分 - 在第二个回调参数上包括各种渲染条件。用
useCallback
包装onClick
和isOpen
回调(似乎不起作用,因为它们有依赖项,每个<Accordion/>
都会更新渲染)将
onClick
和isOpen
保存到这样的状态:const [callbacks] = useState({onClick, isOpen})
然后将callbacks
对象作为ContextProvider
value
。 (似乎是错误的,并没有奏效)
以下是对我基于 class 的工作实现的引用:
https://codesandbox.io/s/4pyqoxoz9
我的钩子重构尝试:
https://codesandbox.io/s/lxp8xz80z7
我将日志保存在 <AccordionSection/>
渲染上,以展示我试图阻止的重新渲染。
任何输入将不胜感激。
所以我在追了太多兔子之后最后添加了这个小金块..
const cache = {};
const AccordionSection = memo(({ children, sectionSlug, onClick, isOpen }) => {
if (cache[sectionSlug]) {
console.log({
children: children === cache[sectionSlug].children,
sectionSlug: sectionSlug === cache[sectionSlug].sectionSlug,
onClick: onClick === cache[sectionSlug].onClick,
isOpen: isOpen === cache[sectionSlug].isOpen
});
}
cache[sectionSlug] = { children, sectionSlug, onClick, isOpen };
这表明 onClick
正在发生变化。这看起来很明显,因为 Accordion 组件正在渲染并创建一个新的 onClick
.
用 useCallback
包装他 onClick
的创作可以解决这个问题。
const onClick = useCallback(
sectionSlug =>
setOpenSections({
...(exclusive ? {} : openSections),
[sectionSlug]: !openSections[sectionSlug]
}),
[]
);
虽然我似乎确实在这个过程中遇到了问题 exclusive
因为它现在总是启用的..
https://codesandbox.io/s/1o08p08m27
哦,我确实在那里移动了其他一些可能有助于修复的部分..
更新
重构为使用 useReducer
并将所有逻辑移至那里,以便我们可以提供稳定的 onClick
更新
他们说睡眠很好,但对我来说只是想入睡..
我知道我遗漏了什么.. 昨晚意识到我们不需要减速器,只需要 setState
的函数形式,它允许我们从内部访问最新状态useCallback
记忆功能。在此处转换了@itaydafna 的优化 https://codesandbox.io/s/8490v55029