LoginSignup
1
0

More than 5 years have passed since last update.

Hyperapp の重要なテクニックhypercraft(Memoization 編)

Last updated at Posted at 2018-10-25

1. はじめに

Hyperappでアプリを開発中、課題解決の為に、
loteooさんにzacenoさんが書かれた素晴らしいページを教えてもらいました。
いつでも読み返せるように翻訳します。

本記事では原文サイトの中の、
Memoization [訳: メモ化] について翻訳したものを記述します。
完璧な翻訳ではないので、齟齬が無いよう原文とパラレルで記述します。

以下原文ページのリンク

2. 翻訳

Memoization

メモ化

An optimization technique for faster renders and making sure onupdate is only called when something actually changed.

高速な描画、また実際に何かが変更された場合のみにonupdateをするテクニックです。

The general idea with memoization, is to avoid expensive calculations in functions by keeping a memo of the result of previous calls. Whenever you call a memoized function with the same arguments as a previous time, we skip over the actual calculation and simply return the previously calculated result

一般的にメモ化は、前回の呼び出しの処理結果を保持しておき、コストがかかる計算処理を避けるアイデアです。メモ化された関数を、
前回呼び出しの際と同じ引数で呼び出す場合に、実際の計算処理をスキップし単純に前回の計算結果をリターンします。

So memoization is best used only for pure functions. Functions who'se only output/effect is the returned value of a calculation, which is only based on input arguments. Also memoization is not free. There is a cost involved in comparing the input values to previous ones, and to storing the old results. You should chose a method appropriate to the types of input values you'll have, and the calculations involved.

なので、メモ化は、ピュアな関数のみで最大限に活用できます。
出力/作用だけする関数はインプットの引数に基づいた計算結果の値です。
また、メモ化は、引数に関して前回のものと比較するコストがかかり、前回結果をストアしておく必要があります。
引数の型、計算処理に適した手法を選択する必要があります。

Memoizing views

Viewsのメモ化

If you're composing your main view from sub-views, as described here (or similar), then your sub-views are pure functions which take a part of the state tree under a namespace: const fooView = fooModule.view(state.foo, actions.foo))

もし、ここで触れたように、サブビューでメインビューを構成している場合、サブビューは名前空間の中にあるstate treeの一部を利用するピュアファンクションですね: const fooView = fooModule.view(state.foo, actions.foo))

Because of the way hyperapp's state-management works, state.foo will be a new instance in the view only if some state has changed under state.foo -- otherwise, it will be the same object instance as the last time the view was called. This offers a perfect opportunity for memoization: the subview represents a significant chunk of the UI, and checking whether the input values have changed is dirt cheap: it's simply a matter of checking if prevState === state.

hyperappのstate管理の仕組みは、state.fooの中身で何か変更があった場合のみ、
state.fooをビューの中で新しいインスタンスとして扱います。
それ以外の場合は最後にビューが呼ばれた時と同じオブジェクトです。これは、完全にメモ化のしどころですね。
サブビューはUIに関して重要な表示を行なっており、インプットの値が変更されたかどうかチェックするのは楽勝です。単純に、prevState === state かどうかをチェックするだけの事です。

var prevState
var memo
const memoizeView = view => (state, actions) => {
    if (prevState === state) return memo
    prevState = state
    memo = view(state, actions)
    return memo
}

...

fooModule.view = memoizeView(fooModule.view)

Memoizing Components

Componentsのメモ化

Memoizing components is a bit more involved than the example above, because

コンポーネントのメモ化をviewのそれと比べるともう一手間必要です。なぜなら、

  • components take an input argument wich is an object of attributes, and unlike the state of subviews, you'll typically need to check the properties of the input object, to determine if the arguments have changed

  • unlike subviews, a component is typically called many times every render. This means it's not enough to remember the input from just the previous call. You need to keep track of all the previous inputs and results to reap the benefits of memoization.

  • コンポーネントは属性のオブジェクトを使うので、サブビューのstateとは違い、inputのプロパティが変更したかどうか知る為に典型的なチェックをする必要があります

  • サブビューとは違い、コンポーネントは描画の度に毎回呼ばれます。これは前回呼び出しの際のinputを記憶するだけでは不十分という事です。過去の全てのinputの値と結果を持っておく必要があります

Thankfully are plenty of memoization implementations out there which take care of this for you, so you don't need to write it yourself. A popular one is moize

ありがたい事に、それを解決しているメモ化の実装はたくさんあります。
なので、ご自分で実装する必要はありません。人気があるやつで、moizeがあります。

Speeds up rendering
The main reason you'd want to memoize components, is because during the patch operation (where hyperapp is busy comparing the old virtual-DOM to the new one, in order to figure out how to change the DOM), whenever a virtual node is encounterd which is the same instance as the previous one, it simply skips over that entire branch of the virtual-dom-tree. (See the discussion here).

描画の高速化
コンポーネントのメモ化を行う大きな理由は、patch処理(patch処理では、hyperappはDOMをどのように変更するかを見極める為に、古いvirtual-DOMと新しいvirtualDomの比較という重い処理を行う)の処理時間にあると思います。
virtual nodeが前回と同じ場合は、それ以下の階層の全てのdomの比較チェックをスキップします。(ここで議論しています。)

So, for a memoized component, not only do you not have to recreate the virtual nodes again -- but you also spare the patch algorithm from processing that part of the virtual tree again. For big, complicated components this might mean significantly faster rendering.

なので、コンポーネントのメモ化はvirtual nodeの再生成が不要になるだけではなく(patchアルゴリズムではvirtual treeの部分的な処理をさせない事できますが)大きく複雑なコンポーネントの場合、描画がかなり高速化する事もあります。

Onupdate only when component-attributes have changed
A common pitfall/surprise beginning Hyperapp:ers encounter is that if you have a component that produces a vnode with an onupdate lifecycle-event-handler, they often expect it will only run when one of the components attributes has changed. But this is not the case of course. onupdate handlers are run every render.

component-attributes が変更した時だけOnupdate
Hyperappを使い始めた際のよくある落とし穴は、ライフサイクルイベントハンドラのonupdateを持つvnodeを生成するコンポーネントを扱う場合、しばしば、一つのコンポーネントの属性が変更された場合のみにonupdateが実行されるものだと思っている事です。しかしこれは違います。

But since that happens as part of the patch process, if you memoize the component, you get the expected behavior, because patch will only process the vnodes (and call lifecycle events) if something has actually changed.

しかし、これはpatch関数で処理されていて、patch関数は実際に何かが変更された場合にのみ、vnode(および呼び出しライフサイクルイベント)を処理するため、コンポーネントをメモ化すると期待どおりの動作が得られます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0