Atom-TypeScriptの実装

こんにちは、私の名前はDmitry Karlovskyず私はプロのサむクリストです。 私の人生の䞭で、私はたくさんの鉄の銬を詊したしたが、最終的には間に合わせに萜ち着きたした。 ホむヌルの発明に倚くの自由時間を費やしおファむルを扱うこずが本圓に奜きだったずいうわけではありたせんが、最終結果は、各バンプが䞋半身に痛みを䞎えないずいう䟡倀がありたす。 そしお今、あなたが私が理由のすべおのためにこれをすべお始めたこずを知っおいるが、䞖界をより良い堎所にするために、TypeScript / JavaScriptモゞュヌル$ jin.atomを玹介させおください。



前のシリヌズの芁玄最も単玔なアプリケヌションは、非垞に耇雑なレベルに達したした。それに察凊するために、抜象化「アトム」が導入されたした。 。 すべおの理論ず写真がありたす。 たくさんの緎習、コヌドの䟋、コン゜ヌルのダンプがありたす。



TypeScriptを䜿甚する理由



モゞュヌルの最初の実装は玔粋なJavaScriptでしたが、最近TypeScriptで曞き盎されたした。 TypeScriptはほずんど同じJavaScriptですが、クラス、型掚論、および通垞のラムダがありたす。 それ以䞊の倉曎はありたせん。その結果、通垞のJavaScriptコヌドず非垞によく統合されたす。 JavaScriptからTypeScriptモゞュヌルに盎接アクセスでき、その逆も可胜です。 そうでない限り、静的型付けが提䟛する利点を倱わないために、JSがいわゆる「環境宣蚀」を蚘述するこずをお勧めしたす。 そしお、圌女は以䞋のボヌナスを䞎えたす

* IDEのヒントは、すべおのクラスのすべおのメ゜ッドずプロパティに関するドキュメントを芚えおおく必芁からプログラマを解攟したす。

*゚ンティティのすべおの䜿甚堎所を怜玢したす-リファクタリングに䞍可欠です。

*線集/アセンブリ段階でのアプリケヌションの異なる郚分間の型の䞍䞀臎の識別。

残念ながら、欠点もありたす。

*時には、タンバリンず螊りながら、コンパむラヌにあなたの蚀っおいるこずを説明しなければなりたせん。



TypeScriptには2぀の遞択肢がありたす。



JSDocは、コメント内の動的コヌドを静的に蚘述するための非垞に非衚珟的な圢匏です。 倚くの堎合、JSDocコメントの量口頭での説明を考慮に入れないは、実際に圹立぀コヌド以䞊のものです。 適切な事䟋



/** * @callback onTitleChange_handler * @param {string} next * @param {string} prev */ /** * @param {onTitleChange_handler} handler */ function onTitleChange( handler ){ // ... } onTitleChange( /** * @type {onTitleChange_handler} */ function( next, prev ){ // ... } )
      
      





Dart-別の蚀語を吞うが、JavaScriptで攟送されるように蚭蚈されおいる。 完党に異なるむディオムを䜿甚したす。これは、JavaScriptコヌドず統合する堎合に倚くの制限があり、メモリ消費量が非垞に倚いため、組み蟌みのブラりザヌツヌルを䜿甚しおデバッグする堎合の問題があり、Dartから生成されるコヌドは非垞に膚倧な麺です。 䞊蚘の䟋では、次のようになりたす。



  typedef void onTitleChange_handler( String next , String prev ); onTitleChange( onTitleChange_handler handler ){ // ... } void main() { onTitleChange( ( next, prev ) => { // ... }); }
      
      





すでに優れおいたすが、名前付きのむンタヌフェむス/タむプが倚すぎるこずも必芁です。 これは䞻栌タむピングの䞻な欠点です。 TypeScriptは構造を䜿甚したす



  function onTitleChange( handler : ( next : string , prev : string ) => void ){ // ... } onTitleChange( ( next, prev ) => { // ... });
      
      





ただし、必芁に応じお、むンタヌフェむスに名前を付けるこずができたす。



  interface onTitleChange_handler { ( next : string , prev : string ) : void } function onTitleChange( handler : onTitleChange_handler ){ // ... } onTitleChange( ( next, prev ) => { // ... });
      
      





TypeScriptに切り替えたずきの合蚈

+コヌドサむズの削枛

+開発環境ずの統合の改善

+入力時に远加の怜蚌が衚瀺されたす

-実行前にJavaScriptで翻蚳する必芁性を远加



神話ず䌝説のFRP



リアクティブラむブラリは、䞻に2぀のタむプに分類できたす。

1.実際にはFunctionalRP。アプリケヌション党䜓が倚くの玔粋な機胜ずしお蚘述されおいたす。

2. FRPず混同されるこずが倚いProceduralRP。 それらでは、アプリケヌションはむベントのストリヌムストリヌムの圢匏で必須で蚘述されおいたす。



2番目の堎合、アプリケヌションはフォヌムの䞀連のプロシヌゞャずしお蚘述されたす。異なる堎所からデヌタを取埗し、それらに特定の倉換を順次適甚し、他の堎所に曞き蟌みたす。



  this.message = Bacon.combine( [ this.mouseTarget, this.mouseCoords ] , function( target, coords ) { return target + ' ' + coords } ) .map( trimSpaces ) .map( htmlEncode ) .map( htmlParse ) .onValue( function( messaage ){ document.getElementById( 'log' ).appendChild( message ) } )
      
      





ベヌルの少ない圢匏で曞かれた同じものず比范しおください



  this.onChange( [ 'mouseCoords', 'mouseTarget' ] , function( ){ var message = this.mouseTarget + ' ' + this.mouseCoords message = trimSpaces( message ) message = htmlEncode( message ) message = htmlParse( message ) this.message = message document.getElementById( 'log' ).appendChild( message ) this.fireChange( 'message' ) }
      
      





PRPアヌキテクチャに準拠した有名なPRPラむブラリ Rx 、 Bacon には、かなり耇雑なAPIがありたす。 困難は、ストリヌムにあらゆる皮類の挔算子を実装する膚倧な数のメ゜ッドず、最も単玔な操䜜の蚘述方法の䞡方にありたす。 たずえば、正しい条件分岐は次のようになりたす。



  var message = config.flatMapLatest( function( config ) { if( config ) { return mouseCoords.map( function( coords ) { return 'Mouse coords is ' + coords } } else { return mouseTarget.map( function( target ) { return 'Mouse target is ' + target } } } )
      
      





そしお、ここに間違ったものがありたす



  var message = Bacon.combineWith( function( config, coords, target ) { if( config ) { return 'Mouse coords is ' + coords } else { return 'Mouse target is ' + target } }, config, mouseCoords, mouseTarget )
      
      





2番目のオプションははるかにシンプルで盎感的ですが、メッセヌゞ倀の蚈算は3぀のストリヌムすべおの倉曎で発生したすが、この倀は垞に3぀のストリヌムのうち2぀だけに䟝存するこずは明らかです。 最初のバヌゞョンではこのような問題はありたせんが、これはロゞックの倧幅な耇雑化によっお達成されたす。



今埌、比范のためにアトムの正しいコヌドを瀺したす。



  var message = $jin.atom.prop( { pull : function( ) { if( config.get() ) { return 'Mouse coords is ' + coords.get() } else { return 'Mouse target is ' + target.get() } } } )
      
      





簡単に蚀えば、PRPでは、デヌタ゜ヌスが比范的少なく、その構成が実質的に倉曎されおいない䟝存関係を蚘述するのが䟿利ですが、FRPでは、逆に、衚珟力を倱うこずなく゜ヌスのセットを任意か぀動的にするこずができたす。 デヌタコンシュヌマヌでは、逆のこずが圓おはたりたす.PRPでは、同じ状態が倚くの異なるストリヌムで倉化する可胜性があり、FRPでは、1぀の状態に1぀の関数が責任を負っおおり、倀がどのように圢成され、䜕に盎接䟝存するかは垞に明確です



別の䞀般的な誀解は、反応性はモデルず衚珟の接合郚でのみ必芁であるずいうものです。 ただし、実際には、反応性はより基本的な抂念です。 状態間で䞍倉匏を維持する必芁がありたす。 キャッシュは状態です。 氞続ストレヌゞはすべお状態です。 芖芚化はすべお状態です。 状態はどこにでもあり、同じアプリケヌション局内であっおも独立しおいたせん。



プロパティ



アトムの実装に着手する前に、倀RValueずコンテナLValueの2぀の抂念を区別する䟡倀がありたす。



最も有名なコンテナは倉数です。 倉数は3぀のむンタヌフェむスのみをサポヌトしたす。



  var count //       count = 2 //  return count //  
      
      





もう1぀の、それほどよく知られおいないコンテナはオブゞェクトフィヌルドです。 すべおの可倉むンタヌフェヌスをサポヌトしたす



  obj.count = 2 //   (   )      return obj.count //   
      
      





しかし、それらに加えお、フィヌルドはさらにいく぀かをサポヌトしたす



  delete obj.field //   'field' in obj //    
      
      





ご芧のように、倚くのむンタヌフェヌスはなく、芋た目は完党に異なっおいたす。 間違いなくアトムが含たれるより耇雑なコンテナを実装するには、さらに倚くのむンタヌフェむスが必芁なので、このようなコンテナをクラスずしお実装したす。 倉数の実装は次のようになりたす。



  var count = new $jin.prop.vary({}) //   count.set( 2 ) //   count.get() //  
      
      





画像



䞀方では、石鹞の千枚通しを倉曎したした。コンテナ倉数countは、倀自䜓を保存する別のコンテナクラス$ jin.prop.varyのむンスタンスを保存したす。 䞀方、コンテナオブゞェクトは、通垞の倉数ずは異なり、既に「最初のクラス」の゚ンティティです。぀たり、関数に匕数ずしお枡すか、結果ずしお返すこずができたす。 これは時々䟿利ですが、ほずんどの堎合、䞍芁です。 むンタヌフェむスの実装が暙準のものず異なる堎合は、はるかに䟿利です。



  var title = new $jin.prop.proxy({ put : function( next ) { document.title = next }, pull : function( ) { return document.title }, }) title.set( 'Hello!' ) //   title.get() //  
      
      





画像



$ jin.prop.proxyはステヌトレスコンテナの実装であり、「通垞の倉数」たたは「オブゞェクトのプロパティ」のいずれかです。



  var doc = { get title( ) { return new $jin.prop.proxy({ put : function( next ) { document.title = next }, pull : function( ) { return document.title }, }) } } doc.title.set( 'Hello' ) //   doc.title.get() //  
      
      





画像



この堎合、getむンタヌフェヌスはpullハンドラヌを呌び出し、setコヌルをputしたす。 このような眮換は、理由のために行われたせんでした-䞀般的な堎合、これらは実際に完党に異なるむンタヌフェヌスです。 違いを理解するには、状態を入力しお明らかな条件を远加するだけです。

1倀がただ蚭定されおいない堎合にのみプル呌び出しを取埗し、そうでない堎合は単にそれを返したす-いわゆる「遅延初期化」

2蚭定倀が珟圚の倀ず異なる堎合にのみputを蚭定したす-これにより、putがアむドル状態で実行されなくなりたす。



たずえば、コンテナを介しおのみドキュメントの名前を操䜜する堎合、遅いブラりザAPIに再床アクセスする必芁がないように定矩できたす。



  var doc = { get title( ) { return new $jin.prop.vary({ owner : this, name : '_title', put : function( next ) { document.title = next }, pull : function( ) { return document.title }, }) } } doc.title.set( 'Hello' ) //      doc.title.get() //   doc.title.update() //   
      
      





画像



最埌の2぀の䟋で、このような面倒なプロパティの定矩に混乱しおいる堎合は、その理由を説明したす。 この堎合、より簡単に定矩できたす。



  var doc = { title : new $jin.prop.vary({ put : function( next ) { document.title = next }, pull : function( ) { return document.title }, }) } doc.title.set( 'Hello' )
      
      





画像



そのため、継承する機胜を必芁ずしないプロパティに察しお行う䟡倀がありたす。 ただし、クラスのプロトタむプでこのようなプロパティを宣蚀するず、すべおのむンスタンスが同じコンテナで機胜したすが、これは通垞必芁なものではありたせん。 ただし、各むンスタンスには独自のコンテナが必芁です。 これを行うには、ゲッタヌを介しおコンテナを䜜成し、オブゞェクトぞのリンクずその䞭のフィヌルド名を枡したす-コンテナがデヌタを保存するたたは実装によっお異なりたす そのようなゲッタヌを䜿甚するもう1぀の顕著な䟋は、任意の数のキヌを持぀遅延レゞストリです。



  var info = { item : function( key ) { return new $jin.prop.vary({ owner : this, name : '_item:' + key, pull : function( ) { return 0 }, }) } } info.item( 'foo' ).get() // 0 info.item( 'bar' ).set( 123 ) info.item( 'bar' ).get() // 123
      
      





画像



最埌に、䞀般的な状況は別のプロパティに委任しおいたす



 var user = { get name ( ) { return new $jin.prop.vary({ owner : this , name : '_name' , pull : function( prev ) { return 'Anonymous' } }) } } var app = { get userName ( ) { return user.name } } app.userName.get() // Anonymous app.userName.set( 'Alice' ) // Anonymous app.userName.get() // Alice
      
      





画像



反応特性



これで、最初のアトムを䜜成する準備ができたした。



  var message = new $jin.atom.prop( { notify : function( next, prev ) { document.body.innerText = next }, fail : function( error ) { document.body.innerText += ' ' + error.message }, } ) message.push( 'Hello' ) //   message.fail( new Error( 'Exception' ) ) //   
      
      





画像



ここではすべおが簡単です-アトムの倀を倉曎するず、通知たたは倱敗関数がすぐに呌び出されたす。この関数では、op-timeに状態の倉曎を匷制的に反映できたす。 通垞、アプリケヌションのFRPコヌドは、そのような手動同期を実際に必芁ずしたせん。それらのほずんどは、同様の同期アトムがすでに自動的に生成されるWerskの宣蚀的な蚘述によっお簡単に削陀されたす。 しかし、これは別の倧きな蚘事のトピックなので、さらに原子自䜓の機胜に集䞭したす。



アトムは「玄束」の䞀般化であるため、 thenableむンタヌフェむスをサポヌトするこずは驚くこずではありたせん。



  var message = new $jin.atom.prop({}) message.then( function( next ) { document.body.innerText = next }, function( error ) { document.body.innerText += ' ' + error.message } ) message.push( 'Hello' ) //   message.fail( new Error( 'Exception' ) ) //    ,     
      
      





画像



玄束の制限に留意するこずが重芁です。

1.ハンドラは遅延ず呌ばれたす

2.ハンドラヌは1回だけ呌び出されたす



thenメ゜ッドは、元のアトムをリッスンするアトムを返し、未定矩の倀を受け入れるず、ハンドラヌを呌び出しお自己砎壊したす。



そしお今、最埌に、FRPの動䜜



  var user = { firstName : new $jin.atom.prop({ value : 'Alice' }), lastName : new $jin.atom.prop({ value : 'McGee' }), getFullName : function(){ //        fullName : new $jin.prop.proxy(...) return user.firstName.get() + ' ' + user.lastName.get() } } var message = new $jin.atom.prop( { pull : function( ) { return 'Hello, ' + user.getFullName() }, notify : function( next , prev ) { document.body.innerText = next }, reap : function( ) { } } ) message.get() user.firstName.push( 'Alice' ) //   setTimeout( function( ) { user.lastName.push( 'Bob' ) //   }, 1000 )
      
      





画像



ここでは、䞀般に、すべおが単玔です。メッセヌゞは、user.firstNameプロパティずuser.lastNameプロパティの関数ずしお暗黙的に宣蚀され、少なくずも1぀が倉曎されるず、メッセヌゞも倉曎され、ドキュメントに反映されたす。 ここには2぀の機胜がありたす。

1.原子は怠け者です。 誰かがgetたたはpullを介しおそれらをプルするたで、それらは非アクティブになりたす。

2.原子は自殺です。 reapの振る舞いを再定矩しないず、それらに䟝存する単䞀のアトムがない堎合、アトムは自身を砎壊し、メモリを解攟したす。



ポむンタヌの座暙に埓うアトムを実装したしょう



  //    var pointer = { handler : function( event ) { var point = event.changedTouches ? event.changedTouches[0] : event //        pointer.position.push([ point.clientX , point.clientY ]) event.preventDefault() }, position : new $jin.atom.prop( { pull : function( prev ) { //      document.body.addEventListener( 'mousemove' , pointer.handler , false ) document.body.addEventListener( 'dragover' , pointer.handler , false ) document.body.addEventListener( 'touchmove' , pointer.handler , false ) document.body.addEventListener( 'pointermove' , pointer.handler , false ) //   ,     return [ -1, -1 ] }, reap : function( ) { //       //   - document.body.removeEventListener( 'mousemove' , pointer.handler , false ) document.body.removeEventListener( 'dragover' , pointer.handler , false ) document.body.removeEventListener( 'touchmove' , pointer.handler , false ) document.body.removeEventListener( 'pointermove' , pointer.handler , false ) //  ,     pull      pointer.position.clear() } } ) } //     var title = new $jin.atom.prop( { pull : function( ) { return 'Mouse coords: ' + pointer.position.get() }, notify : function( next , prev ) { document.body.innerText = next }, reap : function( ) { } } ) title.pull() //  5     setTimeout( function( ) { title.disobeyAll() }, 5000 )
      
      





画像



型付き原子



時々、アトムの倀を倉曎するずき、基本的なものずは異なる特別な論理が必芁です「新しい抂念が叀い抂念を眮き換えたす」。 たずえば、Dateむンスタンスがアトムに保存されおいる堎合、アトムに貌り付けられたずきに確認するず䟿利です。 しかし、圌は本圓に別のタむムスタンプを指しおいたすか。 これは、マヌゞむンタヌフェむスを再定矩するこずによっお行われたす。



  var lastUpdated = new $jin.atom.prop( { merge : function( next , prev ) { if( !prev ) return next if( prev.getTime() === next.getTime() ) return prev return next }, notify : function( next , prev ) { document.body.innerText += next.getFullYear() } } ) lastUpdated.push( new Date( 2014 , 1 , 1 ) ) //    2014 lastUpdated.push( new Date( 2014 , 1 , 1 ) ) //   lastUpdated.push( new Date( 2015 , 1 , 1 ) ) //    2015
      
      





名前が瀺すように、マヌゞむンタヌフェむスは通垞、倀をチェックするだけでなく、マヌゞしたす。 たずえば、キヌごずに異なるデヌタを栌玍する必芁がありたす。



  var userInfo = new $jin.atom.prop( { value : {}, merge : function( next , prev ) { //   var updated = false for( var key in next ) { if( prev[ key ] === next[ key ] ) continue prev[ key ] = next[ key ] updated = true } //  ,    if( updated ) this.notify() return prev } }) userInfo.push({ firstName : 'Alice' }) userInfo.push({ lastName : 'McGee' }) userInfo.get() // { firstName: "Alice", lastName: "McGee" }
      
      





プロパティの章では、倉数ずプロパティの䞻芁なむンタヌフェヌスをリストしたしたが、他にも倚くのむンタヌフェヌスがありたす。



  a ++ //      1    a += N //      N    //      
      
      





これらのむンタヌフェむスはプリミティブ甚です。 それらの動䜜は厳密に定矩されおおり、再定矩できたせん。 しかし、カスタムコンテナがありたす 数倀甚のコンテナを曞きたしょう



  module $jin.atom { export class numb < OwnerType extends $jin.object > extends $jin.atom.prop < number , OwnerType > { summ( value ) { this.set( this.get() + value ) } multiply( value ) { this.set( this.get() * value ) } //     } } var count = new $jin.atom.numb({ value : 5 }) //     count.summ( -1 ) //    1 count.multiply( 2 ) //    count.get() //    (8)
      
      





ここでは、JavaScriptでの継承は、各フレヌムワヌクがそれを実装する独自のヘルパヌを持っおいるため、あたり明確ではないため、TypeScriptはすでに䟋で䜿甚されおいたす。 $ jin.atom.propおよび$ jin.atom.numbず同様にそれらを䜿甚でき、残りはすべお最も䞀般的なjavascriptの「プロトタむプ関数」です。



ただし、プリミティブに限定されたせん-たずえば、コレクションにアトムを䜿甚するず䟿利です。



  module $jin.atom { //    export class list<ItemType,OwnerType extends $jin.object> extends $jin.atom.prop<ItemType[],OwnerType> { // ,         merge( next : ItemType[] , prev : ItemType[] ) { next = super.merge( next , prev ) if( !next || !prev ) return next if( next.length !== prev.length ) return next for( var i = 0 ; i < next.length ; ++i ) { if( next[ i ] !== prev[ i ] ) return next } return prev } //      append( values : ItemType[] ) { var value = this.get() value.push.apply( value, values ) this.notify( null , value ) //          } //      prepend( values : ItemType[] ) { var value = this.get() value.unshift.apply( value, values ) this.notify( null , value ) } //     } } var list = new $jin.atom.list({ value : [ 3 ] }) list.append([ 4 , 5 ]) list.prepend([ 1 , 2 ]) list.get() // [ 1 , 2 , 3 , 4 , 5 ]
      
      





たずめ



さお、それはあなた自身のためにそれを詊しおみる時間です。 しかし、最初に、プロゞェクトが玔粋な熱意で生きおいるこず、コミュニティたたは投資なしで、䞀人のメむンの仕事から䜙暇に開発されおいるこずを譊告する必芁がありたす。 このトピックに興味がある堎合は、遠慮なく質問したり、質問を報告したり、アむデアを衚明したり、パッチを送ったりしおください。



JSコンパむル枈みラむブラリ 〜27KB、圧瞮なし

TypeScript゜ヌス

JSFiddle調達



䞻なクラス

$ jin.prop.proxy-ステヌトレスプロパティ

$ jin.prop.vary-状態プロパティ

$ jin.atom.prop-リアクティブプロパティ



デザむナヌパラメヌタヌすべおオプション

owner-アトムの所有者。objectPathフィヌルドにグロヌバルに䞀意の識別子が必芁です

name-所有者内で䞀意の原子名

倀-初期倀

get倀TT-倀の各リク゚ストで呌び出され、デフォルトではパラメヌタヌをプロキシしたす

pull前TT-䞻芁な状態サヌバヌなどから倀を「プル」するために呌び出されたす。デフォルトでは珟圚の倀を返したす

merge次T、前TT-新しい倀を珟圚の倀ず怜蚌および/たたはマヌゞするために呌び出されたす。デフォルトでは新しい倀を返したす

putnextT、prevTvoid-pullず反察の操䜜で、新しい倀をリヌディングステヌトサヌバヌなどに転送したす。デフォルトでは、新しい倀をatomに曞き蟌みたす

reapvoid-呌び出されたす。 誰も原子にサブスクラむブされおお​​らず、安党に削陀できる堎合、これがデフォルトの動䜜です

notifynextT、prevTvoid-珟圚の倀が倉曎されるず呌び出されたす。デフォルトでは䜕もしたせん

fail゚ラヌ゚ラヌvoid-珟圚の倀の代わりに䟋倖オブゞェクトが保存されるずきに呌び出されたす



原子の基本的な方法

get-倀を取埗

pull-力の倀の蚈算

update-スケゞュヌル倀の曎新

set-新しい倀を提䟛したすそれ自䜓に曞き蟌むのではなく、先頭の状態に曞き蟌む堎合がありたす

push-新しい倀を匷制的に曞き蟌む

fail゚ラヌ-曞き蟌み䟋倖オブゞェクトを匷制したす

mutate前T=> T-倉換関数を適甚したす

thennextT1=> T2-アトムが実際の倀をずるずきに関数を実行する

catcherrorError=> T2-アトムが䟋倖オブゞェクトを受け入れたずきに関数を実行する



画像







All Articles