客観的反応型プログラミング

客観的反応型プログラミング



SAPRUNのDmitry Karlovskyが提示する... mmm ...







これは、 FrontendConf'17での同名のパフォーマンスのテキストバージョンです。 記事として読む、プレゼンテーションインターフェイス開く、ビデオを見ることができます


にうんざり PIUはどのように役立ちますか?
...たくさん書きますが、少ししますか? 少し書いて、たくさんやる!
...単純なロジックに何時間も費やしますか? リアクティブルールは一貫性を保証します!
...非同期ですか? 同期コードはノンブロッキングでもあります!
...デフォルトではすべてが愚かだと? PPRはデータフローを自動的に最適化します!
...機能的なパズル? プロパティを持つオブジェクト-どこも簡単ではありません!
...アプリケーションが完全にクラッシュするということですか? その部分を落としてみましょう-それ自体が上昇します!
...待機インジケータを操作しますか? 期待の指標は、必要に応じて表示されます!
...双方向バインディング? 双方向バインディングは正しくクックする必要があります!
...再利用可能なコンポーネントを見ましたか? コンポーネントをデフォルトで再利用可能にしましょう!
...永遠に追いつく? 先に進み、リードしましょう!


商業休憩



サプルーン







みなさんこんにちは、私の名前はドミトリー・カルロフスキーです。 私は、SAPRAN Web開発チームの責任者です。 弊社はロシア最大のSAPインテグレーターですが、最近、自社のソフトウェア製品の開発に積極的に取り組んでいます。


$ mol-骨髄に反応する



それらの1つは、 「$ mol」という名前のレスポンシブインターフェイスを迅速に構築するためのクロスプラットフォームのオープンソースWebフレームワークです。 その中で、Objective Reactive Programmingの機能を最大限に使用します。これについてはさらに説明します...

$ mol







夕方には何をしますか? 小売を勝ち取ろう!



オンラインのおもちゃ屋を開くことにしたと想像してみましょう。 そして、とにかくすべてを行うだけでなく、スタイリッシュ、ファッショナブル、若々しく、迅速、柔軟かつ確実にすべてを行いたい..

toys.hyoo.ru







製品カタログ



おもちゃはたくさんありますが、昨日販売を開始しなければなりませんでした。 したがって、できる限り迅速にすべてを実行したいのですが、ユーザーエクスペリエンスを失うことではありません。



すべてのおもちゃの基本データをクライアントデバイスにダウンロードして、ユーザーがネットワークの遅延なしにカタログをすばやく閲覧できるようにすることができます。

さまざまなおもちゃのカタログ







フィルタリング



私たちのカタログをめくるのはもちろんエキサイティングな娯楽ですが、ユーザーは現時点で特に興味のあるおもちゃのみに選択を制限したいと考えています。 したがって、フィルタリングを追加します。

フィルターカタログ







大量のデータの場合、複雑なフィルターを長時間適用することができるため、応答性を確保するために、必要のないときにもう一度フィルター処理を繰り返すことは望ましくありません。



たとえば、サイズでフィルタリングした場合、レビューの数を変更するときに、再フィルタリングする意味がありません。 しかし、レビューの数でフィルタリングした場合...まあ、あなたは理解しますよね?



結果に実際に影響する条件のみが変更されたときに再起動できるように、フィルタリング結果がどの特定の製品の特定のプロパティに依存するかを知ることが重要です。


仕分け



通常、ユーザーはおもちゃをランダムな順序ではなく、特定の順序で表示することを望みます。 例:価格の昇順または関連性の降順。 したがって、ソートを追加します。 繰り返しますが、それは商品のさまざまな特性に影響を与える可能性があり、大量のデータにとっては非常に困難です。

高度なフィルターと高度な並べ替えを備えたディレクトリ







明らかに、ここでの再ソートは、ソート基準を変更するときにのみ行う必要があります...またはソートした商品のプロパティ。 ただし、商品ではなく、フィルタリング基準を満たす商品のみです。 そして、それに応じて、この基準そのものを変更するとき。 また、フィルタリングする商品のプロパティを変更する場合。 そして...


すべての依存関係の説明



コード内のすべての状態間のすべての依存関係を記述しようとすると、組み合わせの爆発が発生し、手が引き裂かれます。

状態間のすべての依存関係の図







ここでは、変換の2つのステップのみを説明しましたが、実際のアプリケーションでは12を超える場合があります。



この指数関数的に増大する複雑さを抑えるために、リアクティブプログラミングが考案されました。 それがなければ、複雑なアプリケーションを高速、安定、コンパクトにすることができません。


すべてがレンダリングされますか?



準備したすべてのデータを表示すると、レンダリングのアルゴリズムの複雑さはこのデータの量に比例します。 10の製品が1000で即座にレンダリングされます。遅延があり、10000の場合、ユーザーはお茶を飲む時間ができます。

線形進行グラフ







それとも全部ではありませんか?



ユーザーが同時に10個以下の製品しか収まらないような画面を持っている場合、視覚的には違いはありません-1000個すべてをレンダリングするか、10個だけをレンダリングするか、スクロールすると欠落しているものをレンダリングします。 したがって、どんなに速くても 反応する テンプレートエンジンでは、データの量にあまり依存しない遅延アーキテクチャへの応答性が常に失われます。

対数および線形進行グラフ







表示のみ表示



製品カードのサイズがおおよそわかっている場合、ウィンドウの高さを知っていると、どの製品が正確に見えず、どの製品が少なくとも可視領域に入ることができるかが簡単にわかります。

製品リストの表示部分のカットチャート







巨大なリストから最初の要素から10番目の要素を切り取るのは簡単な操作です。 ただし、このリストがキャッシュされたフォームのどこかに保存されている場合のみ。 各フレームで、フィルター処理と並べ替えによってソースデータから取得する場合、スクロールの滑らかさについて話すことはできません。


DOMの変更を試みる



わかりました、データを準備しました、ユーザーにそれらを示すことは残っています。 額の解決策は、古いDOMツリーを削除し、新しいDOMツリーを成長させることです。 これが、すべてのHTMLテンプレートエンジンの機能です。

DOMの再描画が長い







遅いだけでなく、ノードの動的状態のリセットにもつながります。 例:スクロール位置と選択フラグ。 それらの一部はプログラムで復元できますが、すべてではありません。



要するに、現実は頑固に純粋な機能になりたくない。 アプリケーションが正常に機能するには、新しい状態を作成するだけでなく、可能であれば既存の状態を変更する必要があります。 そして、もし勝てないなら、リードしてください!


仮想DOM



ハリネズミにヘビを引っ張る方法は? そうです、アプリケーション全体の新しいDOMツリーを生成して、 反応する 特別なライブラリは、新しいバージョンと古いバージョンを比較し、ユーザーが実際に見るDOMツリーに違いを適用します。

くしゃみごとのバーチャルハウスは遅い







松葉杖のようですね。 モデルの1つで文字列プロパティが変更されたときに、テキストノードの値を変更するためだけに必要な作業量。


直接的な依存関係



そして、最も効果的なソリューションの仕事はどのように見えるでしょうか?

直接の依存関係。より効果的なものは何ですか?







すべてが単純です-ソースデータとその表示の間で直接接続が確立されます。 1つの状態が変化すると、それに依存する状態のみが変化します。 さらに、これはいわゆる「モデル」と「ディスプレイ」の間だけでなく、サーバー上のデータベースへの書き込みから始まり、中間状態の束を経てブラウザのホームノードで終わる依存状態の間にも当てはまります。 これはリアクティブプログラミングの本質であり、すべての会議で販売されている子音の名前を持つテンプレートエンジンではありません。


そしてそれは行く!



そしてそれは行く!







私は誇張すると言うことができます、具体的にはあなたのプロジェクトではそれほど多くのデータとその重い処理はありませんし、あなたのアプリケーションは強力なワークステーションでのみ起動され、省エネモードの脆弱な中国のスリッパでは起動されません。


ReactJSのシェルピンスキートライアングル



しかし、これについては簡単に検討します。 アプリケーションが毎秒60フレームの速度で動作するためには、データの準備からレンダリングまでのすべての操作をわずか16ミリ秒で完了する必要があります。 また、強力なコンピューター上の些細なアプリケーションであっても、それらを超えることは非常に簡単です。

nin-jin.github.io/sierpinski/stack.html







以下は、Facebookのメンバーによって作成された有名なデモで、Reactの複雑なアプリケーションがどれほど馬鹿げているかを示しています。 彼らは現在、いくつかのフレームにわたって計算を曖昧にすることでこれを解決しようとしています。これは、大切な毎秒60フレームを与えますが、視覚的なアーティファクトにつながります。 それらの基本的な問題は変更されていません-仮想DOMでは、くしゃみごとに大量の追加計算が必要です。


PPRを使用したシェルピンスキー三角形



一方、PPRでは、ソースからコンシューマへのデータフローを自動的に最適化することで、計算量を最小限に抑えることができます。

mol.js.org/perf/sierp/







ご覧のように、リアクティブプログラミングを使用した同等の実装は、フレームごとに計算を塗りつぶさなくても、はるかに高いパフォーマンスを示します。


パラダイム



最後にいくつかの理論を追加しましょう...



オブジェクトプログラミングとは その主な機能は、比較的単純なインターフェースであるオブジェクトという1つの抽象化のフレームワーク内でデータと機能を操作するための組み合わせです。



関数型プログラミングとは何ですか? ここでは、プログラム全体が、変更される状態に依存せず、状態自体を変更しない純粋な関数の束として説明されています。



最後に、リアクティブプログラミングとは何ですか? ここでは、1つの状態を変更すると依存関係のカスケード変更につながるように、1つの状態を別の状態から取得するためのルールについて説明します。

客観的、機能的、反応的







多くの場合、リアクティブプログラミングはファンクショナルに強く関連付けられていますが、リアクティブプログラミングの主人公は可変状態であるため、目標にはるかに近いものです。


プッシュ(FRP)



反応性を実装するには、根本的に異なる2つの方法があります。



1つ目は、あらゆる種類のベーコンRX 、および機能的リアクティブプログラミングとしても知られる他のストリームパンクです。 その本質は、状態が依存するいわゆるストリームを明示的に受け取り、新しい値を計算する機能を追加することです。 そして、この方法で得られた値は、すべての依存ストリームに既にプッシュされており、それら自体がこの値で何をするかしないかを決定します。


const FilterSource = new Rx.BehaviorSubject( toy => toy.count > 0 )
const Filter = FilterSource.distinctUntilChanged().debounce( 0 )

const ToysSource = new Rx.BehaviorSubject( [] )
const Toys = ToysSource.distinctUntilChanged().debounce( 0 )

const ToysFiltered = Filter
.select( filter => {
    if( !filter ) return Toys
    return Toys.map( toys => toys.filter( filter ) )
} )
.switch()
.distinctUntilChanged()
.debounce( 0 )
      
      





- , . : , .



. : , . , , .



, . . . . , , , .


()



, , . , , — $mol_mem.


class $my_toys {

    @ $mol_mem
    filter( next ) {
        if( next === undefined ) return toy => toy.count() > 0

        return next
    }

    @ $mol_mem
    toys( next = [] ){ return next }

    @ $mol_mem
    toys_filtered() {
        if( !this.filter() ) return this.toys()

        return this.toys().filter( this.filter() )
    }
}
      
      





ORP , ? , , , , , .



, .




, .


@ $mol_mem
sorter( next ) {
    if( next === undefined ) return ( a , b )=> b.price() - a.price()

    return next
}

@ $mol_mem
toys_sorted() {
    if( !this.sorter() ) return this.toys_filtered()

    return this.toys_filtered().slice().sort( this.sorter() )
}
      
      





, - , .




, . , .


@ $mol_mem
toys_visible() {
    return this.toys_sorted().slice( ... this.view_window() )
}
      
      





children



, , . .


children() {
    return this.toys_visible()
}
      
      







render



, .


@ $mol_mem
render() {
    let node = document.getElementById( this.id() )

    if( !node ) {
        node = document.createElement( 'div' )
        node.id = this.id()
    }

    /// Node updating here

    return node
}
      
      





, DOM-. , . .



. , , , DOM-.


?



. , . , — , .

toys.hyoo.ru/#luck=.9







- , , . , .




DOM- try-catch



, . — .


try {

    /// Node updating here

    node.removeAttribute( 'mol_view_error' )

} catch( error ) {

    console.error( error )

    node.setAttribute( 'mol_view_error' , error.name )
}
      
      







? CSS , , . , .


[mol_view_error] {
    opacity: .5 !important;
    pointer-events: none !important;
}
      
      





:



. , .


namesakes_message() {

    /// Serial
    const user = this.user()
    const count = this.name_count( user.name )

    return this.texts().namesakes_message
    .replace( /\{count\}/g , count )
}
      
      





, , .



, ? : , javascript , .


:



.


namesakes_message() {

    /// Parallel
    return Promise.all([

        /// Serial
        this.user().then( user => this.name_count( user.name ) ,

        this.texts() ,

    ])
    .then( ([ count , texts ])=> {
        return texts.namesakes_message.replace( /\{count\}/g , count )
    } )

}
      
      





, , . , .


:



, , .


/// Serial
async namesakes_count() {
    const user = await this.user()
    return await this.name_count( user.name )
}

async namesakes_message() {

    /// Parallel
    const [ count, texts ] = await Promise.all([
        this.namesakes_count() ,
        this.texts() ,
    ])

    return texts.namesakes_message.replace( /\{count\}/g , count )
}
      
      





"", .



, , .


:



, , ?


@ $mol_mem
namesakes_message() {

    /// Parallel
    const texts = this.texts()
    const user = this.user()

    /// Serial
    const count = this.namesakes_count( user.name )

    return texts.namesakes_message.replace( /\{count\}/g , count )
}
      
      





. , — . , , . , , .


:



, , , , - , — .



, user.name



, .


@ $mol_mem
namesakes_message() {

    /// Parallel
    const texts = this.texts()
    const user = this.user()

    /// Serial
    const count = this.namesakes_count( user.name ) /// <-- first yield

    return texts.namesakes_message.replace( /\{count\}/g , count )
}
      
      





:



, . .


/// Before
@ $mol_mem
toys(){ return [] }

/// After
toys_data() {
    return $mol_http.resource( '/toys.json' ).json()
}

@ $mol_mem
toys() {
    return Object.keys( this.toys_data() ).map( id => this.toy( id ) )
}
      
      





, — , , .




. , DOM-, . , . .


[mol_view_error="$mol_atom_wait"] {
    animation: my_waiting .25s steps(6) infinite;
}
      
      







. , .







, — , . , , . "". " , ".



, , , , , , .



, . , , . , ?




, , , ?







:









:









:









:









, ?



Angular. , watcher-, , - .



, , " , ".




?







… ?









,









Facebook - FLUX, , — — "".



, — . , .



, .


:



, , , , . , , . . ?







:



— . — "". , — .







, .


:



, , .







, . FLUX-, .




class $mol_string {

    hint() { return '' }

    @ $mol_mem
    value( next = '' ) { return next }

    // ...
}
      
      





— . : hint



— , ; value



— . value. , .




. , , hint



.


const Name = new $mol_string

Name.hint = ()=> 'Batman'
      
      







value



, .


let name = 'Jin'

Name.value = ( next = name )=> {
    return name = next
}
      
      





, value



name



, — name



.


?



, , , .







.



, .



, : .




, , , . , , .


    @ $mol_mem
    Name() {
        const Name = new $mol_string

        /// Setup ```Name``` here

        return Name
    }
      
      





, , - , . .




, , , .


        /// Setup ```Name```:

        /// One way binding
        Name.hint =  ()=> this.name_hint()

        /// Two way binding
        Name.value = ( next )=> this.name( next )
      
      





, , , . , , , .




— .







, , .







.







.







— .







— .







: . . — "" , , , — , .



. , , , , . , . .


?



. - , .

: $mol_mem, VueJS, MobX, CellX, KnockOut







: toys.hyoo.ru







: github.com/nin-jin/toys.hyoo.ru







: nin-jin.github.io/slides/orp







: github.com/nin-jin/sierpinski








All Articles