Help us understand the problem. What is going on with this article?

JSとReactにおけるメモ化

JSとReactにおけるメモ化

by ryokkkke
1 / 17

社内勉強会のメモ。


What is メモ化

メモ化(英: Memoization)とは、プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。

キャッシュはより広範な用語であり、メモ化はキャッシュの限定的な形態を指す用語である。

by wikipedia

つまり、 キャッシュ ⊃ メモ化


わかりやすいので、Rubyで頻繁に使うメモ化の例

hoge.rb
def hoge
  @hoge ||= something_heavy_subroutine
end
fuga.rb
def fuga
  @fuga ||=
    begin
      result1 = something_heavy_subroutine1
      result2 = something_heavy_subroutine2(result1)
      something_heavy_subroutine3(result2)
    end
end

余談
ちなみにメモって言葉、Rubyのinject(reduce)にも使われています。

inject.rb
enum.inject {|memo, item| block }
enum.inject(init) {|memo, item| block }

https://ref.xaio.jp/ruby/classes/enumerable/inject


jsのreduceと同じですが、jsのreduceaccumulatorという単語を使ってる。

reducer.js
const reducer = (accumulator, currentValue) => accumulator + currentValue;

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce


じゃあ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

https://www.freecodecamp.org/news/understanding-memoize-in-javascript-51d07d19430e/


ただし、前提として、Rubyはクラスを使うのでインスタンス変数に保存するけど、

  • jsではクラスをなるべく使わない雰囲気がある(特にReactとか使ってるとそう)ので、関数単位でメモ化ができて欲しい
  • クロージャ使えるから関数単位でも簡単にメモ化を実現できる

という背景がある。


つまり、クラスを使うならjsのメモ化だってこれで良い。

hoge_class.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の中で重い処理を繰り返すとパフォーマンスが落ちるので、適切にメモ化していきたい。

https://ja.reactjs.org/docs/hooks-reference.html#usememo


useMemo

usememo.js
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useCallback

callback.js
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallback(fn, deps) は useMemo(() => fn, deps) と等価です。


このuseCallbackによる関数のメモ化は、計算処理を緩和するという意味はほとんどない。
関数の生成コストと前の値との比較コストが、なんなら前者の方が少ない可能性もある。

ただし、子のコンポーネントがPureComponent、もしくはReact.memoを使用したコンポーネントだった場合、本来なら再レンダリングが走らないべきところでレンダリングが走ってしまう可能性がある。


re-rendering.js
const Component = (props: Props) => {
  const onChange = () => {
    props.value; // なんかpropsを使った処理
  };

  return <HogePureComponent onChange={onChange}>
};

useCallbackを使用しない場合、renderが走るたびに関数が生成されるわけだが、そうなるとPureComponent(もしくはReact.memoなコンポーネント)に渡る関数の参照が毎回変わるため、意図としては再レンダリングしないはずの場所で再レンダリングが走ってしまう。

(子のコンポーネントがPureでない場合は関係なく毎回走る。)


re-rendering.js
const Component = (props: Props) => {
  const memoizedOnChange = React.useCallback(() => {
    props.value; // なんかpropsを使った処理
  }, [props.value]);

  return <HogePureComponent onChange={memoizedOnChange}>
};

こうすると、props.valueが変わらない限り参照が毎回同じになるため、不要なレンダリングが防がれる。


ただしこの辺り、deps(Dependency List)(第二引数の配列、他のフックと同じ)をちゃんと書かないと値を更新したのに表示が更新されないみたいな事が起こり得るので、実装時は要注意。

ryokkkke
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした