聊聊 React 里的 Memoize
Posted 奴心
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊 React 里的 Memoize相关的知识,希望对你有一定的参考价值。
本文将阐述 React 里何时何处应该使用 memoize,希望您读后能明白放心地丢掉多余的 useCallback
,让 React Hooks 用的更自然。
1
UI 是稳定的
React 这条路之所以能走通,都是基于这一条假设:UI 整体上是稳定的。原话记不得了,大体这个意思,翻了一下 React 文档也暂时没有找到,有知道的朋友可以告诉我。
网上找不到关于这个假设的详细解释,这里谈一下个人的理解,因为这个概念对后面的理解很重要。我们都知道 io 是计算机里最慢的部分,而 io 里最慢的,估计就是人机交互(人人交互也很慢 )了。UI 的稳定全靠这个 U 了,同时对一个页面的各个部分发起高频点击应该不是一般 U 可以做到的。UI稳定就代表这 React 的 state 是稳定的,也就是说 render
的调用“大多”是低频的。
整体稳定不代表局部是稳定的,你还是可以在一个输入框里“啪啪啪……”不停的(按键盘 )。如果这个“啪啪啪”的位置在组件树的叶子上还好,但如果在根部就麻烦了,整棵树都会“晃”的。
2
如何处理频繁更新的局部状态?
最直接的办法是把这个不安分分子踢出组件树,到 dom 层去处理。但这么做有几点弊端,一是没了 vdom 的跨平台,另一点是太麻烦。还有就是可能关联的节点太多,不可能都拿掉,这时候就需要 memoize 来救场了。
3
memoize 的位置
通常,我们不应该 export
一个 memoize 的组件,原因是组件本身通常不知道自身被使用时的环境,也就是不知道传过来的参数是否有 memoize 的价值。而且基于前面的假设,我们应该认为传过来的参数是稳定的。所以这个 memoize 应该由使用方来做的。useCallback
和 memo
成对出现的关系也可以侧面印证这个观点。如果 memoize 被组件提供方封装了,使用方不了解组件有没有被 memoize,根本无法判断要不要 useCallback
。(注:这里讲的和 React 官方文档里 memo 示例是有出入的,请读者自行思考验证。)
另外一点,memoize 应该尽量把变化隔离在更小的影响范围内,也就是越早越好。这个位置就是那个“啪啪啪”状态所在的组件内部。
function Container() {
// 假设 value 常变
const [value, setValue] = useState();
// foo 代表不经常变的一批状态
const [foo, setFoo] = useState();
const other = useMemo(() => <Other value={foo} />, [foo]);
return <div>
<input onChange={e => setValue(e.target.value)} placeholder="在这里“啪啪啪”" type="text" />
<Display text={value} foo={foo} />
{/* 这里应该对 Other 进行 memoize */}
{/* <Other value={foo} /> */}
{ other }
</div>;
}
4
频繁变更状态的使用方内部是否需要 memoize
考虑下面 Display 实现:
export function Display({text, foo}) {
return <div>
<TextRender value={text} />
{/* 这里要 memoize Foo 吗?*/}
<Foo foo={foo} />
</div>;
}
在 Display
内部看来,是不知道 text
和 foo
哪个会频繁更新的,所以不应该做任何 memoize 的优化的。因为下一个调用者可能就会频繁更新 foo
而不更新 text
,也可能两个都不频繁更新(大概率,基于 UI 稳定的假设)。
在不破坏封装性的情况下,Container
是没办法知道内部组件如何使用 text
和 foo
的,所以 Container
只能假设 Display
是一个原子的轻量组件。那么很可能会遇到性能问题,至少我们可以看到 Foo
可以被 memoize 优化。
这时候就遇到一个取舍问题了,我们是应该维持 Display
的通用性,还是针对场景做性能优化?我觉得在没遇到性能问题时首先保持 Display
的通用性,如果真的遇到问题,再去选择或实现 Display
特定场景的版本。
比如 text
被深层次传递了,Display
可以和 Container
【约定】 通过 context 来传递。
5
memo vs useMemo
因为 memo
是组件的使用方来做的,所以 memo
的使用场景还是比较窄的。举例:
import React, {memo} from 'react';
import Foo from 'foo';
import Bar from 'bar';
const MemoFoo = memo(Foo);
function Baz() {
// ...
return <div>
<MemoFoo {...} />
<Bar {...} />
<MemoFoo {...} />
</div>;
}
所以,大多数情况用 useMemo
就可以满足使用场景。
6
总结
大多数情况下不必考虑 rerender 的问题, 除非发现一个频繁变更的状态确实影响了体验。
如果需要使用 memoization 优化渲染:
找到频繁变更的状态所在的组件
找到这个组件里这个状态的消费者
对这个消费者之外的其它子组件用
useMemo
进行状态隔离如果消费者内部还有性能问题,使用一个特殊优化的组件代替
忘掉 useCallback
和 memo
API 吧!等你遇到钉子的时候再去找你的锤子。
-- end --
往期推荐
以上是关于聊聊 React 里的 Memoize的主要内容,如果未能解决你的问题,请参考以下文章
javascript 用于在节点#nodejs #javascript内设置react app的代码片段
ModuleNotFoundError:没有名为“memoize”的模块