JSとReactにおけるメモ化
社内勉強会のメモ。
What is メモ化
メモ化(英: Memoization)とは、プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。
キャッシュはより広範な用語であり、メモ化はキャッシュの限定的な形態を指す用語である。
メモ化(英: Memoization)とは、プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。
キャッシュはより広範な用語であり、メモ化はキャッシュの限定的な形態を指す用語である。
by wikipedia
つまり、 キャッシュ ⊃ メモ化
例
わかりやすいので、Rubyで頻繁に使うメモ化の例
def hoge
@hoge ||= something_heavy_subroutine
end
def fuga
@fuga ||=
begin
result1 = something_heavy_subroutine1
result2 = something_heavy_subroutine2(result1)
something_heavy_subroutine3(result2)
end
end
余談
ちなみにメモって言葉、Rubyのinject
(reduce
)にも使われています。
inject.rbenum.inject {|memo, item| block } enum.inject(init) {|memo, item| block }
jsのreduce
と同じですが、jsのreduce
はaccumulator
という単語を使ってる。
reducer.jsconst reducer = (accumulator, currentValue) => accumulator + currentValue;
じゃあjsでメモ化のコード書くとどうなるの
memo_ex.js// a simple memoized function to add something const memoizedAdd = () => { let cache = {}; return (n) => { // クロージャなので一回リターンされたこの関数はcacheのデータを持ち続ける if (n in cache) { // キャッシュにあるかどうか見てる console.log('Fetching from cache'); return cache[n]; // キャッシュに存在すればそれを返す } else { // 存在しない場合は普通に計算して、キャッシュに保存しておく console.log('Calculating result'); let result = n + 10; cache[n] = result; return result; } } } // returned function from memoizedAdd const newAdd = memoizedAdd(); console.log(newAdd(9)); // calculated console.log(newAdd(9)); // cached
ただし、前提として、Rubyはクラスを使うのでインスタンス変数に保存するけど、
- jsではクラスをなるべく使わない雰囲気がある(特にReactとか使ってるとそう)ので、関数単位でメモ化ができて欲しい
- クロージャ使えるから関数単位でも簡単にメモ化を実現できる
という背景がある。
つまり、クラスを使うならjsのメモ化だってこれで良い。
class Hoge {
constructor() {
this._heavy_calculate_result;
}
heavy_calculate() {
if (this._heavy_calculate_result != undefined) return this._heavy_calculate_result;
const result = // something calculate
this._heavy_calculate_result = result;
return result;
}
}
要は、メモ化自体は概念であって、特定の実装に依存するものではない。
クロージャを使うと関数単体でメモ化できるから、jsではそれが適してる時が多いかもね、っていう話。
計算がなんども走る場合は気にしてメモ化しておこう。
React Hooksにおけるメモ化
React Hooksが用意しているメモ化のためのフックが二つある。
- React.useMemo
- React.useCallback
Reactのコンポーネントの場合、renderは割と高頻度で呼ばれるので、renderの中で重い処理を繰り返すとパフォーマンスが落ちるので、適切にメモ化していきたい。
useMemo
usememo.jsconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback
callback.jsconst memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
useCallback(fn, deps) は useMemo(() => fn, deps) と等価です。
このuseCallbackによる関数のメモ化は、計算処理を緩和するという意味はほとんどない。
関数の生成コストと前の値との比較コストが、なんなら前者の方が少ない可能性もある。
ただし、子のコンポーネントがPureComponent、もしくはReact.memoを使用したコンポーネントだった場合、本来なら再レンダリングが走らないべきところでレンダリングが走ってしまう可能性がある。
const Component = (props: Props) => {
const onChange = () => {
props.value; // なんかpropsを使った処理
};
return <HogePureComponent onChange={onChange}>
};
useCallbackを使用しない場合、renderが走るたびに関数が生成されるわけだが、そうなるとPureComponent(もしくはReact.memoなコンポーネント)に渡る関数の参照が毎回変わるため、意図としては再レンダリングしないはずの場所で再レンダリングが走ってしまう。
(子のコンポーネントがPureでない場合は関係なく毎回走る。)
const Component = (props: Props) => {
const memoizedOnChange = React.useCallback(() => {
props.value; // なんかpropsを使った処理
}, [props.value]);
return <HogePureComponent onChange={memoizedOnChange}>
};
こうすると、props.valueが変わらない限り参照が毎回同じになるため、不要なレンダリングが防がれる。
ただしこの辺り、deps(Dependency List)(第二引数の配列、他のフックと同じ)をちゃんと書かないと値を更新したのに表示が更新されないみたいな事が起こり得るので、実装時は要注意。
Author And Source
この問題について(JSとReactにおけるメモ化), 我々は、より多くの情報をここで見つけました https://qiita.com/ryokkkke/items/a5177b45c4a61c593d2b著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .