SSRずプログレッシブ゚ンハンスメントを備えた同圢RealWorldアプリケヌションの開発。 パヌト4-コンポヌネントず構成

チュヌトリアルの前の郚分で、同圢ルヌティング、ナビゲヌション、フェッチ、およびデヌタの初期状態の問題を解決したした。 その結果、同型アプリケヌションのかなり単玔で簡朔な基盀であるこずが刀明したした。これは、別のリポゞトリractive-isomorphic-starterkitにも割り圓おたした。 このパヌトでは、 RealWorldアプリケヌションの䜜成を開始したすが、最初にそれを分解したす。 行こう
画像

埓来のオフトピック


興味深い結果は、 状態ベヌスのルヌティングに関するアンケヌトで瀺されたした。 このアむデアに投祚した人の半数は、期埅されたものに感謝したせんでした。 それでも、アむデアの半分はいただに興味がありたした。

興味深い事実は、最近、フロント゚ンドでYa。Subbotnikを蚪れたこずです。 Yandexのメンバヌは、 状態ベヌスのルヌティングのアむデアを味わったようです。 このトピックに觊れたスピヌカヌは、アフィリ゚むトむンタヌフェヌスのチヌムからのものであり、明らかに次のように芋えたす。

<Match strict state={{page}} params={{status: 'NEW', query: 'Yandex'}} > <App>...</App> </Match> <Switch> ... </Switch> 

実際、これは原則ずしお私がやるこずず同じです

 {{#if $route.match(page) && status === 'NEW' && query === 'Yandex'}} <App>...</App> {{/if}} 

豚のコンポヌネントを䜜成する以倖に、他の衚珟手段を持たないReactを䜿甚するずいう事実がない限り、これがそのスタむルです。

レポヌトの終わりに、私は特にこの郚分に再び焊点を合わせたした。 それから、スピヌカヌは圌のプロゞェクトの詳现に深く没頭し、倚くの人が圌のアむデアを完党に理解できなかったように思えたした。

分解ず合成



非垞に簡単に蚀えば、フロント゚ンドのコンテキストでは、分解はモノリシックアプリケヌションをコンポヌネントに分割するプロセスであり、それぞれが問題の䞀郚を解決したす。 分解の䞻な目的は、経隓的な耇雑さを軜枛し、 DRY原則を実装するこずです。

これは、最新のフロント゚ンドを支配するコンポヌネントアプロヌチであり、党䜓ずしおモゞュラヌアプロヌチの䞀郚です。 ぀たり、コヌドをモゞュヌルに分割し、むンタヌフェむスをコンポヌネントに分割したす。 繰り返したすが、フロント゚ンドのコンテキストでは、コンポヌネントはUI、ビゞネスロゞックなどの䞀郚をカプセル化する特定のナヌザヌ芁玠です。 コンポヌネントを理解する䞊で重芁なコンポヌネントは、分解が発生する原理です。

分解には倚くのアプロヌチがありたす。 たずえば、 Reactは「すべおがコンポヌネントである」ずいう原則を䜿甚しおおり、他のフレヌムワヌクはSOLIDなどのコンテキストでコンポヌネントを考慮しおいたす。 しかし、䞀般に、それはすべお、「 高凝集 、 疎結合 」ず呌ばれるコンポヌネントを䜜成したいずいう願望に垰着したす。

圌らは皆これを理解しおいるように芋えるずいう事実にもかかわらず、この理解は非垞に頻繁に異なる方法で実装されおいたす。 2人の開発者を連れお行く堎合、高い確率で、圌らはさたざたな方法でアプリケヌションを分解したす。 私自身が固守する分解の原則のみを説明したす。

率盎に蚀っお、コンポヌネントを「粉砕」するのは正しいずは思わず、 Reactコンポヌネントを粉砕する原理は私にはあたり䌌おいたせん。 原則ずしお、次のルヌルに埓っおコンポヌネントを遞択したす。

  1. 再利甚 -アプリケヌションの異なる郚分で同じ機胜を䜿甚したす。
  2. 機胜的な目的 -明確に定矩された機胜ず、別個のラむフサむクルず条件を持぀ビゞネスプロセス
  3. 構造的機胜 -構造を改善し、コヌドの可読性を高める

アプリケヌションの䞀郚を別のコンポヌネントに割り圓おる他の理由はないず思われたす。 さらに、逆に過床の分解は、プロゞェクトずそのサポヌトの理解を耇雑にする可胜性がありたす。 蚀い換えれば、この問題ではバランスが非垞に重芁です。

Reactに぀いおの完党な真実気匱な人向けではない
私はカルマの゚ントリヌで嫌いを぀かもうず思いたすが、それでも私は玔粋に䞻芳的な意芋を衚明したす  Reactのいく぀かの偎面に぀いお。 むしろ、 Reactが説埗するアプロヌチずその信者が説教したす。

私はJSXに぀いお、そしおそれがどれほどひどいのかに぀いおは泣きたせん。これに぀いおはすでに䜕千回も蚀われおいたす。 そしお、コヌドずマヌクアップを混合するずいう考えは、reactに属しおいるのではなく、むしろPHPに属しおいるので、そこからはreactに移行したした。 郚分的に。

ここでは、 Reactが分解する原理ず、 Reduxやその他のFluxのようなものに察する盎接的な圱響に぀いお説明したす。 驚くかもしれたせんが、これが、反応がもたらした「革呜的な」アむデアのすべおの理由であるず断蚀したす。 同時に、プログラミングの原則を䜕十幎にもわたっお開発しおきたベストプラクティスをすべお、その道を切り開きたす。

「さお、塩は䜕ですか なんずいう塩、1 .... 反応 »

よくあるこずですが、 すべおは玠晎らしいアむデアから始たったず思いたす。 「すべおがコンポヌネントです」 。 このアむデアは非垞にシンプルで理解しやすいため、人々の心を捉えるこずができたす。 この単玔さに察する過床の熱意は、実際、 Reactが他に䜕もできないずいう事実に぀ながりたした。 コンポヌネントずその構成を䜜成する以倖の衚珟手段はありたせんここでは、 仮想DOMやその他の゚ンゞンフヌドは、アヌキテクチャの芳点からはそれほど重芁ではないため、ここでは特に考慮したせん。

他の最初のシンプルで理想的なアむデアず同様に、反応のアむデアにも珟実がありたした。 しかし、 Reactの䜜成者はこのアむデアに非垞に熱心で、フレヌムワヌク内ではなく他の人がそうであるように過床の耇雑さに耐え始めたしたが、アプリケヌション内に残し、頭脳の芋かけのシンプルさを保ちたす。 たた、 Reactは垞に同じように答えたす。アプリケヌション内のすべおがコンポヌネントであるため、コンポヌネントを䜜成するだけです。

コミュニティがこれらの原則に順応し、さらにはそれらを愛しおいるこずは明らかです。 それでも、それはそのアプリケヌションを過床に耇雑にし始めたしたが、フレヌムワヌクではなく衛星ラむブラリヌでした。 そのため、原則ずしお、 Reactのみでアプリケヌションを䜜成するこずはありたせん。 圌の背埌には、あらゆる皮類のアドオンず束葉杖の列車が確実に䌞びおいたす。

それずは別に、 Angularのような「オヌルむンワン」゜リュヌションの倧ファンでもないこずに泚意しおください。 フレヌムワヌクの特暩は、アプリケヌションのアヌキテクチャの問題、分解ず構成の問題、コンポヌネント間の通信に関連しおいるすべおだず思いたす。 しかし、http-requestなどの送信に関する質問はありたせん。 私にずっお、 Angularは倚すぎ、 Reactは少なすぎたす。

しかし、分解ず「別のコンポヌネントを䜜成する」ずいう原則に戻りたす。 その結果、これらすべおの玠晎らしい、本質的に、アむデアは2぀の䞻なものに぀ながりたした。

  1. コンポヌネントを䜜成するこずで問題が解決されるため、コンポヌネントがたくさんありたすが、それらは小さいので、マヌクアップずコヌドを混ぜおも䞋品に芋えたせん。
  2. アプリケヌションのコンポヌネントぞの匷力な断片化により、コンポヌネントの構成が䞍必芁に耇雑になり、異なるレベルのコンポヌネント間で通信するこずが難しくなりたす。 特に「䞀方向のデヌタフロヌ」の原則ず組み合わせお。 これは、最も明らかな解決策がグロヌバルな状態を介したコミュニケヌションであるずいう事実に぀ながりたす。

したがっお、 「すべおがコンポヌネントである」ずいう原則のman順な遵守ず、 Reactが最初に制埡されない分解を匕き起こし、次にコンポヌネントの構成を耇雑にし、その埌草さえ成長しなかったのは、他のツヌルの欠劂です。 コヌド分​​離ずマヌクアップの䞀般に受け入れられおいる原則を無芖し、すべおがヒヌプ内にあるずき、それがクヌルであるず信者に確信させるこずができたす。 グロヌバル状態の䜿甚に進むこずができたすが、長幎にわたっおこれらの状態を分離しおカプセル化しようずしたした。 芁するに、基盀を揺るぎないものに保぀ために、あらゆる皮類の狂気をしおください。

あなたが私の意芋に同意しない堎合、たたは䜕かで私を修正したい堎合-コメントを歓迎したす。 䞀般に、掻気のある議論は、カルマの無蚀のvy隒よりもはるかに生産的なものだず思いたす。 前もっお感謝したす。 私自身も、誰かを怒らせたり怒らせたりしたくありたせんでした。

たず、メむンペヌゞずナヌザヌプロファむルペヌゞを分解しおみたしょう。 たずそれらを実珟したいです。

メむンペヌゞ




ここで、察応するコンポヌネントを色付きのフレヌムで匷調衚瀺したした。


たた、メむンペヌゞには、スクリヌンショットに収たらない蚘事のリスト甚のペヌゞネヌションコンポヌネントが埋め蟌たれおいたす。

タグコンポヌネントは明らかに再利甚可胜であるこずに泚意しおください。 同様に、お気に入りに远加するコンポヌネント。

ナヌザヌプロファむル




蚘事、タグ、お気に入りのリストのコンポヌネントもありたす。 新しいここから


ペヌゞネヌションコンポヌネントもスクリヌンショットに収たりたせんでしたが、ナヌザヌの蚘事のリストが長くなる可胜性があるため、ここで怜蚎する䟡倀がありたす。 蚘事リストコンポヌネントも再利甚可胜であるこずが明らかになりたす。

このようなコンポヌネントぞの分離はバランスが取れおおり、分解の目暙を達成するのに最䜎限必芁であるず思いたす。 同時に、コンポヌネントの構成は非垞にシンプルで管理しやすいたたです。

コンポヌネントの皮類




私には、3぀の䞻芁なタむプのコンポヌネントがあるように思えたす

  1. 玔粋なコンポヌネントは単玔なコンポヌネントであり、その結果は入力パラメヌタヌに完党に䟝存したす「玔粋な」関数のタむプによる。 完党に再利甚され、他のコンポヌネントずの合成でうたく機胜したす。
  2. 自埋コンポヌネントは、ある皮の分離された機胜を実装し、 SOLIDの原則を実装する耇雑なコンポヌネントです。 原則ずしお、このようなコンポヌネントは、「玔粋な」コンポヌネントずの組み合わせで䜿甚され、特定のビゞネスロゞックを実装し、デヌタを収集したす。
  3. ラップコンポヌネントは、テンプレヌトの構造の改善、パラメヌタの受け枡しなどに最もよく䜿甚される分離されたコンポヌネントではありたせん。

私が䜕床も蚀ったように、珟実の䞖界ではすべおがそれほど明確ではないため、コンポヌネントにはしばしば特城が混圚しおおり、これは正垞です。

コヌドを曞く


ルヌトコンポヌネント


ルヌトコンポヌネントたたはアプリケヌションコンポヌネントは、. /src/app.jsで蚭定および䜜成したRactiveむンスタンスです。 たた、䞀般的なアプリケヌションレむアりトレむアりトを実装し、画面䞊に垞に存圚する芁玠ヘッダヌずフッタヌず、ルヌティングを含むアプリケヌション党䜓のレむアりトを含みたす。

テンプレヌトの構造を改善し、䞀般的なレむアりトをより小さな郚分に分割するために、前のセクションで説明したラッパヌコンポヌネントを䜿甚できたす。 Ractiveでは 、単玔なプロパティを蚭定するこずにより、コンポヌネントを非分離にできたす。

 { isolated: false } 

ただし、コンポヌネント自䜓は「安䟡」ではありたせん。これらのすべおのリアクティブおよび蚈算されたプロパティ、オブザヌバヌ、ラむフサむクルなどが含たれおいるためです。 実際、 Ractiveコンポヌネントは組み蟌み状態を持ち、䜕らかの機胜を実装するクラスです。 ラッパヌがこれをすべおの圢匏で必芁ずせず、テンプレヌトを単玔化するために蚭蚈された構造芁玠にすぎない堎合、別の組み蟌み分解メカニズムであるpartialを䜿甚するのは非垞に「安く」なりたす。

前の蚘事のパヌシャルで垜子ず地䞋宀をすでに取り出したした。 同様に、「すべおがコンポヌネントではない」ため、コンポヌネントの特性を満たさないレむアりトの他の郚分を実装したす。 ;-)

したがっお、この段階では、ルヌトアプリケヌションテンプレヌトは次のようになりたす。

./src/templates/app.html

 {{>navbar}} {{#with @shared.$route as $route, {delay: 500} as fadeIn, {duration: 200} as fadeOut }} <div class="page"> {{#if $route.match('/login') }} <div class="auth-page" fade-in="fadeIn" fade-out="fadeOut"> Login page </div> {{elseif $route.match('/register') }} <div class="auth-page" fade-in="fadeIn" fade-out="fadeOut"> Register page </div> {{elseif $route.match('/profile/:username/:section?') }} <div class="profile-page" fade-in="fadeIn" fade-out="fadeOut"> Profile page </div> {{elseif $route.match('/') }} <div class="home-page" fade-in="fadeIn" fade-out="fadeOut"> {{>homepage}} </div> {{else}} <div class="notfound-page" fade-in="fadeIn" fade-out="fadeOut"> {{>notfound}} </div> {{/if}} </div> {{/with}} {{>footer}} 

以䞋、 RealWorldプロゞェクトのルヌティングのガむドラむンを䜿甚したす 。 ガむドに特定の掚奚事項が含たれおいない堎所では、正しいず思われるアプロヌチを䜿甚したす。 私たちは同圢のアプリケヌションを曞いおいるので、ハッシュルヌティングの代わりにHistory APIルヌティングも䜿甚したす。ご存知のように、URLフラグメントはサヌバヌに移動したせん。

さらに、メむンペヌゞのレむアりト甚ず404ペヌゞ甚の2​​぀のパヌシャルを匷調したした。

./src/templates/partials/homepage.html

 <div class="banner"> <div class="container"> <h1 class="logo-font">conduit</h1> <p>A place to share your knowledge.</p> </div> </div> <div class="container page"> <div class="row"> <div class="col-md-9"> <div class="feed-toggle"> <ul class="nav nav-pills outline-active"> <li class="nav-item"> <a href="/" class-active="$route.pathname === '/'" class="nav-link"> Global Feed </a> </li> </ul> </div> Articles list </div> <div class="col-md-3"> <div class="sidebar"> <p>Popular Tags</p> Tags list </div> </div> </div> </div> 

./src/templates/partials/notfound.html

 <div class="banner"> <div class="container"> <h1 class="logo-font">conduit</h1> <p>404 - Not found</p> </div> </div> 

次に、ルヌトコンポヌネントの蚭定にそれらを登録する必芁がありたす。

./src/app.js

  partials: { ... homepage: require('./templates/parsed/homepage'), notfound: require('./templates/parsed/notfound') }, 

完党なコヌド./src/app.js
 const Ractive = require('ractive'); Ractive.DEBUG = (process.env.NODE_ENV === 'development'); Ractive.DEBUG_PROMISES = Ractive.DEBUG; Ractive.defaults.enhance = true; Ractive.defaults.lazy = true; Ractive.defaults.sanitize = true; Ractive.defaults.data.formatDate = require('./helpers/formatDate'); Ractive.defaults.data.errors = null; Ractive.partials.errors = require('./templates/parsed/errors'); Ractive.use(require('ractive-ready')()); Ractive.use(require('ractive-page')({ meta: require('../config/meta.json') })); const api = require('./services/api'); const options = { el: '#app', template: require('./templates/parsed/app'), partials: { navbar: require('./templates/parsed/navbar'), footer: require('./templates/parsed/footer'), homepage: require('./templates/parsed/homepage'), notfound: require('./templates/parsed/notfound') }, transitions: { fade: require('ractive-transitions-fade'), } }; module.exports = () => new Ractive(options); 


たた、トランゞションアニメヌションの蚭定も少し詊したした。

ペヌゞネヌションコンポヌネント


このコンポヌネントは、玔粋なコンポヌネントの明るい代衚です。 圌の仕事の結果は、入力パラメヌタ-コンポヌネント属性に完党に基づいおいたす。



芖芚的には、このコンポヌネントは完党に暙準に芋えたすが、どの副䜜甚を生成し、この副䜜甚をどの皋床正確に制埡するかを決定するこずが重芁です。 同型のアプリケヌションのコンテキストでは、挞進的な改善により、この質問に察する答えは明癜です-URLを倉曎したす。

JSが無効になっおいおも、ペヌゞ間を移動できる必芁があるこずを垞に芚えおおく必芁がありたす。 ぀たり、各ペヌゞは独自のURLリンクで衚される必芁がありたす。 さらに、ブラりザでペヌゞをリロヌドするずき、遞択したリストペヌゞにずどたる必芁がありたす SSRの完党サポヌト。

ガむドラむンには、ペヌゞネヌションがURLにどのように反映されるべきか、それがたったくないかどうかに関する掚奚事項がないため、リストにオフセットを含むURL Queryパラメヌタヌoffsetを䜿甚したす 。 なぜペヌゞではなくオフセットですか これはAPIでペヌゞネヌションが機胜する方法なので、これは簡単です。

 ?offset=20 

さお、最初のRactiveコンポヌネントを䜜成したす。 これを行うために、 Ractiveコンストラクタヌは静的なextendメ゜ッドを提䟛したす。これにより、コンストラクタヌを新しいプロパティで拡匵し、既存のプロパティを䞊曞きしお、結果ずしお新しいコンストラクタヌを取埗できたす。 簡単に蚀えば、これは継承です。

./src/components/Pagination.js

 const Ractive = require('ractive'); module.exports = Ractive.extend({ template: require('../templates/parsed/pagination'), attributes: { required: ['total'], optional: ['offset', 'limit'] }, data: () => ({ total: 0, limit: 10, offset: 0, isCurrent(page) { let limit = parseInt(this.get('limit')), offset = parseInt(this.get('offset')); return offset === ((page * limit) - limit); }, getOffset(page) { return (page - 1) * parseInt(this.get('limit')); } }), computed: { pages() { let length = Math.ceil(parseInt(this.get('total')) / parseInt(this.get('limit'))); return Array.apply(null, { length }).map((p, i) => ++i);; } } }); 

このコンポヌネントは、属性total リスト内の芁玠の総数、 limit ペヌゞ䞊の芁玠の数、およびoffset リスト内の珟圚のオフセットを受け入れたす。 これらのプロパティに基づいお、コンポヌネントはペヌゞのリストを生成したす。これは、 ペヌゞの蚈算されたプロパティずしお実装されたす 。 さらに、䟝存プロパティのいずれかが時間ずずもに倉化するず、蚈算されたプロパティが自動的に再蚈算されたす。 䟿利に。

./src/templates/pagination.html

 {{#if total > 0 && pages.length > 1}} <nav> <ul class="pagination"> {{#each pages as page}} <li class-active="isCurrent(page)" class="page-item"> <a href="?{{ @shared.$route.join('offset', getOffset(page)) }}" class="page-link"> {{ page }} </a> </li> {{/each}} </ul> </nav> {{/if}} 

テンプレヌトでは、このリストをリンクずしお衚瀺するだけです。 ルヌタヌでの特別なjoinメ゜ッドの䜿甚に泚意しおください。 このメ゜ッドは、枡されたパラメヌタヌずその倀を珟圚のク゚リURLずマヌゞしたす。その結果、既存のク゚リ文字列を取埗し、そこに存圚するパラメヌタヌを考慮したす。 い぀ものように、ルヌタヌ自䜓がリンク凊理に関するすべおの䜜業を匕き受けるため、心配する必芁はありたせん。

結果はかなり小さくシンプルなコンポヌネントであり、その唯䞀の副䜜甚はURLパラメヌタの倉曎です。 これにより、任意のリストを含むコンポゞションでこのコンポヌネントを䜿甚できたす。 リストを実装するコンポヌネントは、察応するURLパラメヌタヌの倉曎をサブスクラむブし、この倀をAPIリク゚ストずデヌタ出力に䜿甚したす。

コンポヌネントタグ


このコンポヌネントも玔粋です。 ただし、 ペヌゞネヌションずは異なり、これには異なる前提がありたす。



この写真は、 Tagsコンポヌネントがアプリケヌションの倚くの堎所で実際に䜿甚されおいるこずを瀺しおいたす。 このコンポヌネントが特定のタグのリストで機胜するこずも明らかです。 しかし、最も重芁なこずは、タグのリストがコンポヌネントが実行されるコンテキストに䟝存するこずがすぐに明らかになるこずです。 メむンペヌゞ-これは蚘事のリスト内の人気のあるタグのリストです-これらは特定の蚘事のタグなどです。 そのため、このコンポヌネントは単玔に自埋的ではなく、䜿甚されおいるコンテキストから蚘事のリストを転送する必芁がありたす。

./src/components/Tags.js

 const Ractive = require('ractive'); module.exports = Ractive.extend({ template: require('../templates/parsed/tags'), attributes: { required: ['tags'], optional: ['skin'] }, data: () => ({ tags: [], skin: 'outline' }) }); 

./src/templates/tags.html

 {{#await tags}} <p>Loading...</p> {{then tags}} <ul class="tag-list"> {{#each tags as tag}} <li> <a href="/?tag={{ tag }}" class="tag-pill tag-default tag-{{~/skin}}"> {{ tag }} </a> </li> {{/each}} </ul> {{catch errors}} {{>errors}} {{else}} <p>No tags</p> {{/await}} 

このコンポヌネントはさらにシンプルです。 タグタグのリストず远加のパラメヌタスキン -スタむルタグ outlineおよびfillを受け入れたす。

タグは、タグのリストを配列たたはプロミスずしお受け入れ、独立しおそれをタグリストに解決できたす。 Paginationず同じ副䜜甚を生成したす-ク゚リパラメヌタタグを倉曎したすここでも、ガむドラむンに掚奚事項はありたせん。 䟝存関係はなく、アプリケヌションのどこでも䜿甚できたす。

副䜜甚に぀いお
ペヌゞネヌションコンポヌネントずは異なり、このコンポヌネントは倉数パラメヌタをク゚リ文字列の残りの郚分にマヌゞせず、完党に曎新するこずに特に泚意する䟡倀がありたす。 実際には、タグをクリックするず、ナヌザヌはこのタグに䞀臎する曎新された蚘事のリストを芋るこずができたす。 したがっお、ペヌゞナビゲヌションなど、前のリストで可胜な操䜜はれロにリセットする必芁がありたす。 この堎合、ペヌゞ線集コンポヌネントは既存のク゚リ文字列に察するパラメヌタヌを維持するため、 ペヌゞ線集はタグの改良ず連携しお機胜したす。

メむンペヌゞでこのコンポヌネントを䜿甚しおみたしょう。 たず、APIから人気のあるタグのリストを取埗する必芁がありたす。 このため、APIで動䜜するサヌビスには既に既補の呌び出しがあり、この芁求を実装するコヌドを蚘述するだけです。たた、前の蚘事で説明したSSRおよび他の同圢の郚分のサポヌトを芚えおおくこずも非垞に重芁です。これが興味深いポむントです。ほずんどの堎合、泚意を払っお蚈算されたコンポヌネントのプロパティの助けを借りお、このようなデヌタ取埗芁求を実装したす



画像


なんでArticlesおよびProfileコンポヌネントの䟋でこれを理解できるず確信しおいたす。䞀蚀で蚀えば-それはナッツに行くのは玠晎らしいです

そのため、倀を返すだけの単玔な関数を䜜成しおいたす./src / computed /

tags.js

 const api = require('../services/api'); module.exports = function() { const key = 'tagsList', keychain = `${this.snapshot}${this.keychain()}.${key}`; let tags = this.get(keychain); if ( ! tags) { tags = api.tags.fetchAll().then(data => data.tags); this.wait(tags, key); } return tags; }; 

ご芧のずおり、耇雑なこずは䜕もありたせん。蚈算されたプロパティの関数は、接続先のコンポヌネントのコンテキストで実行されたす。ここで起こるすべおは前の郚分ですでに説明されたした。さらに、最終的にractive-readyプラグむンのkeychainメ゜ッドを䜿甚したした。このメ゜ッドは、この蚈算されたプロパティを接続したコンポヌネントのネストレベルに応じお、デヌタオブゞェクト内の正しいパスを単に返したす。次に、このプロパティをRootコンポヌネントに接続し、Tagsコンポヌネントを接続しお、このプロパティを属性ずしお枡したす。./src/app.js





 ... Ractive.defaults.snapshot = '@global.__DATA__'; ... components: { tags: require('./components/Tags'), }, computed: { tags: require('./computed/tags') }, ... 

完党なコヌド./src/app.js
 const Ractive = require('ractive'); Ractive.DEBUG = (process.env.NODE_ENV === 'development'); Ractive.DEBUG_PROMISES = Ractive.DEBUG; Ractive.defaults.enhance = true; Ractive.defaults.lazy = true; Ractive.defaults.sanitize = true; Ractive.defaults.snapshot = '@global.__DATA__'; Ractive.defaults.data.formatDate = require('./helpers/formatDate'); Ractive.defaults.data.errors = null; Ractive.partials.errors = require('./templates/parsed/errors'); Ractive.use(require('ractive-ready')()); Ractive.use(require('ractive-page')({ meta: require('../config/meta.json') })); const api = require('./services/api'); const options = { el: '#app', template: require('./templates/parsed/app'), partials: { navbar: require('./templates/parsed/navbar'), footer: require('./templates/parsed/footer'), homepage: require('./templates/parsed/homepage'), notfound: require('./templates/parsed/notfound') }, transitions: { fade: require('ractive-transitions-fade'), }, components: { tags: require('./components/Tags'), }, computed: { tags: require('./computed/tags') } }; module.exports = () => new Ractive(options); 


./src/templates/partials/homepage.html

 ... <div class="sidebar"> <p>Popular Tags</p> <tags tags="{{ tags }}" skin="filled" /> </div> ... 

完党なコヌド./src/templates/partials/homepage.html
 <div class="banner"> <div class="container"> <h1 class="logo-font">conduit</h1> <p>A place to share your knowledge.</p> </div> </div> <div class="container page"> <div class="row"> <div class="col-md-9"> <div class="feed-toggle"> <ul class="nav nav-pills outline-active"> <li class="nav-item"> <a href="/" class-active="$route.pathname === '/'" class="nav-link"> Global Feed </a> </li> </ul> </div> Articles list </div> <div class="col-md-3"> <div class="sidebar"> <p>Popular Tags</p> <tags tags="{{ tags }}" skin="filled" /> </div> </div> </div> </div> 


それがすべおであり、最も重芁なこずは、結果が目に心地よいこずです。


タグはサヌバヌにロヌドされ、TagsコンポヌネントはSSRの間にレンダリングされたす。タグのリストには初期デヌタ状態が含たれるため、コンポヌネントはAPIぞの2番目のリク゚ストなしでクラむアント䞊で正垞に氎和されたす。シャむン

コンポヌネント蚘事


スタンドアロンコンポヌネントずクリヌンコンポヌネントの䞻な違いは、スタンドアロンコンポヌネントが機胜するためには、芪コンポヌネントにプラグむンし、適切なタグをマヌクアップに远加するだけです。必芁に応じお、属性を介しおいく぀かの蚭定を枡すこずができたす。そのようなコンポヌネントずそれに関連するすべおの機胜を削陀たたは無効にする必芁がある堎合も同じこずが機胜したす。テンプレヌトでの䜿甚を停止するか、芪コンポヌネントから削陀するだけです。

アプリケヌションのこのようなコンポヌネントの1぀はArticlesコンポヌネントです。このコンポヌネントは、蚘事のリストのたったく別の機胜を実装し、TagsやPaginationなどの他のコンポヌネントを内郚的に䜿甚したす。



コンポヌネント蚘事 アプリケヌションの少なくずも2ペヌゞメむンおよびプロファむルで䜿甚され、転送されるパラメヌタヌに応じお5皮類の蚘事リストを衚瀺できたす。

  1. 蚘事の䞀般的なリスト
  2. 珟圚のナヌザヌのサブスクリプションに基づく蚘事の個人的なリスト
  3. 䜕らかのタグにフィルタヌされた蚘事のリスト
  4. 任意のナヌザヌが䜜成した蚘事のリスト
  5. 任意のナヌザヌがお気に入りに远加した蚘事のリスト

わあさらに、これらのリストタむプはすべお、ペヌゞネヌションをサポヌトし、同圢であり、JSなしで機胜する必芁がありたす。

実際、このような状況では、スタンドアロンコンポヌネントが優れた゜リュヌションです。必芁なすべおのマヌクアップずロゞックをコンポヌネント内にカプセル化しお、必芁なむンタヌフェむスのみを公開できたす。これにより、アプリケヌションのさたざたな郚分でコンポヌネントを䜿甚する際の危険な副䜜甚がなくなりたす。

デヌタの取埗から始めたしょう./src / computed /

articles.js

 const api = require('../services/api'); module.exports = function() { const type = this.get('type'), params = this.get('params'); const key = 'articlesList', keychain = `${this.snapshot}${this.keychain()}.${key}`; let articles = this.get(keychain); if (articles) { this.set(keychain, null); } else { articles = api.articles.fetchAll(type, params); this.wait(articles, key); } return articles; }; 

ご芧のずおり、蚈算プロパティ甚の関数を再床䜜成したしたが、タグの堎合ず同じです。ネタバレの䞋で蚈算されたプロパティの利点に぀いお自発的にお読みください。

アプロヌチの利点
, , . ?

-, , :
 //     life cycle    - oninit () { const foo = fetch('/foo').then(res => res.json()); this.set('foo', foo); } //   ,     computed: { bar() { return fetch('/bar').then(res => res.json()); } } 

-, «» :

 <!--    ,     --> {{#if baz}} {{foo}} {{/if}} <!--   ,    --> {{#if baz}} {{bar}} {{/if}} 

-, :

 //  , -     oninit () { this.observe('qux', (val) => { const foo = fetch(`/foo?qux=${val}`).then(res => res.json()); this.set('foo', foo); }); } //       computed: { bar() { const qux = this.get('qux'); return fetch(`/bar?qux=${qux}`).then(res => res.json()); } } 

-, ( ) . . :

 computed: { foo: require('./computed/baz'), bar: require('./computed/baz'), } 

, , , . , 10 - Articles 
 ?

次に、コンポヌネント自䜓に぀いお説明したす./src/ components/

Articles.js

 const Ractive = require('ractive'); module.exports = Ractive.extend({ template: require('../templates/parsed/articles'), components: { pagination: require('./Pagination'), tags: require('./Tags'), }, computed: { articles: require('../computed/articles') }, attributes: { optional: ['type', 'params'] }, data: () => ({ type: '', params: null }) }); 

ここで、ネストされたコンポヌネント、蚈算されたプロパティを接続し、コンポヌネントむンタヌフェむスを決定したした。オプションの2぀の属性のみを受け入れたすtypeリストタむプ、空の文字列たたは 'feed'のいずれかずparamsフィルタヌパラメヌタヌを持぀オブゞェクト。実際にはコンポヌネントが小さくないため、テンプレヌトは少し耇雑になりたした./src / templates /

articles.html

 <div class="articles-list"> {{#await articles}} <div class="article-preview"> <p>Loading articles...</p> </div> {{then data}} {{#each data.articles as article}} <div class="article-preview"> <div class="article-meta"> <a href="/profile/{{ article.author.username }}"> <img src="{{ article.author.image }}" /> </a> <div class="info"> <a href="/profile/{{ article.author.username }}" class="author"> {{ article.author.username }} </a> <span class="date">{{ formatDate(article.createdAt) }}</span> </div> </div> <a href="/article/{{ article.slug }}" class="preview-link"> <h1>{{ article.title }}</h1> <p>{{ article.description }}</p> <span>Read more...</span> <tags tags="{{ article.tagList }}"/> </a> </div> {{else}} <div class="article-preview"> <p>No articles are here... yet.</p> </div> {{/each}} <pagination total="{{ data.articlesCount }}" offset="{{ @shared.$route.query.offset || 0 }}" limit="20" /> {{catch errors}} <div class="article-preview"> {{>errors}} </div> {{else}} <div class="article-preview"> <p>No articles are here... yet.</p> </div> {{/await}} </div> 

さお、メむンペヌゞでそれを吐きたしょう。

./src/app.js

  components: { ... articles: require('./components/Articles'), }, 

完党なコヌド./src/app.js
 const Ractive = require('ractive'); Ractive.DEBUG = (process.env.NODE_ENV === 'development'); Ractive.DEBUG_PROMISES = Ractive.DEBUG; Ractive.defaults.enhance = true; Ractive.defaults.lazy = true; Ractive.defaults.sanitize = true; Ractive.defaults.snapshot = '@global.__DATA__'; Ractive.defaults.data.formatDate = require('./helpers/formatDate'); Ractive.defaults.data.errors = null; Ractive.partials.errors = require('./templates/parsed/errors'); Ractive.use(require('ractive-ready')()); Ractive.use(require('ractive-page')({ meta: require('../config/meta.json') })); const api = require('./services/api'); const options = { el: '#app', template: require('./templates/parsed/app'), partials: { navbar: require('./templates/parsed/navbar'), footer: require('./templates/parsed/footer'), homepage: require('./templates/parsed/homepage'), notfound: require('./templates/parsed/notfound') }, transitions: { fade: require('ractive-transitions-fade'), }, components: { tags: require('./components/Tags'), articles: require('./components/Articles'), }, computed: { tags: require('./computed/tags') } }; module.exports = () => new Ractive(options); 


./src/templates/partials/homepage.html

 ... <li class="nav-item"> <a href="/" class-active="$route.pathname === '/' && ! $route.query.tag" class="nav-link"> Global Feed </a> </li> {{#if $route.query.tag }} <li class="nav-item"> <a class="nav-link active"> # {{ $route.query.tag }} </a> </li> {{/if}} ... <articles params="{{ $route.query }}"/> ... 

完党なコヌド./src/templates/partials/homepage.html
 <div class="banner"> <div class="container"> <h1 class="logo-font">conduit</h1> <p>A place to share your knowledge.</p> </div> </div> <div class="container page"> <div class="row"> <div class="col-md-9"> <div class="feed-toggle"> <ul class="nav nav-pills outline-active"> <li class="nav-item"> <a href="/" class-active="$route.pathname === '/' && ! $route.query.tag" class="nav-link"> Global Feed </a> </li> {{#if $route.query.tag }} <li class="nav-item"> <a class="nav-link active"> # {{ $route.query.tag }} </a> </li> {{/if}} </ul> </div> <articles params="{{ $route.query }}"/> </div> <div class="col-md-3"> <div class="sidebar"> <p>Popular Tags</p> <tags tags="{{ tags }}" skin="filled" /> </div> </div> </div> </div> 


フィルタリングが実装されるタグの名前を持぀タブを远加する実装がどのように実装されるかに泚目しおください。ナヌザヌがタグコンポヌネントからタグをクリックするず蚘事たたは人気のタグのリストからは関係ありたせん、蚘事のリストはこのタグによっおフィルタヌされるだけでなく、芖芚的に匷調するためにタグ名のタブも远加されたす。

䞀般に、これは次のように機胜し、私の意芋では、ひどく刀明したせんでした


そしおもちろん、すべおが同型であり、最初の読み蟌みはクラむアントでの単䞀のajaxリク゚ストなしで発生したす。ブラりザの履歎は完党に機胜し、JSをオフにするず、すべおが正垞に機胜したす。芁するに、さらに先に進みたす。

プロファむルコンポヌネント


このコンポヌネントは傑出したものになるでしょうが、そうではありたせん。これは、Articlesず同じスタンドアロンコンポヌネントのプラスたたはマむナスであり、プラスたたはマむナスでも機胜したす。実際、1ペヌゞでしか䜿甚されないため、さらに退屈です。



実際、圌はこのペヌゞです。別の蚀い方を知りたせん。

./src/components/Profile.js

 const Ractive = require('ractive'); module.exports = Ractive.extend({ template: require('../templates/parsed/profile'), components: { articles: require('./Articles') }, computed: { profile: require('../computed/profile') }, attributes: { required: ['username'], optional: ['section'] }, data: () => ({ username: '', section: '' }) }); 

ただし、埓来の蚈算プロパティはただ少し耇雑です./src / computed /

profile.js

 const api = require('../services/api'); let _profile; module.exports = function() { const username = this.get('username'); const key = 'profileData', keychain = `${this.root.snapshot}${this.keychain()}.${key}`; let profile = this.get(keychain); if (profile) { this.set(keychain, null); _profile = profile; } else if (_profile && _profile.username === username) { profile = _profile; } else if (username) { profile = api.profiles.fetch(username).then(data => (_profile = data.profile, _profile)); this.wait(profile, key); } return profile; }; 

お気に入りの蚘事のサブルヌトに切り替えるずき、たたはその逆に切り替えるずきにナヌザヌプロファむルを再床芁求したくないので、ここでクロヌゞャヌのナヌザヌプロファむル_profileを「キャッシュ」しおいたす。難しくはなく、高䟡でもありたせんが、うたく機胜したす。たずえば、React / Reduxの実装では、この問題は解決されないため、「My Articles」ず「Favorited Articles」を切り替えるたびにプロファむルが取埗されたす。すぐに圌らが詊みなかったこずは明らかです。

ここで、テンプレヌトでこの゚コノミヌをすべお䜿甚したす./src/ templates/

profile.html

 <div class="profile"> {{#await profile}} {{then profile}} <div class="user-info"> <div class="container"> <div class="row"> <div class="col-xs-12 col-md-10 offset-md-1"> <img src="{{ profile.image }}" class="user-img" /> <h4>{{ profile.username }}</h4> <p>{{ profile.bio }}</p> </div> </div> </div> </div> {{catch errors}} {{>errors}} {{/await}} {{#if username}} <div class="container"> <div class="row"> <div class="col-xs-12 col-md-10 offset-md-1"> <div class="articles-toggle"> <ul class="nav nav-pills outline-active"> <li class="nav-item"> <a href="/profile/{{ username }}" class-active="! section" class="nav-link"> My Articles </a> </li> <li class="nav-item"> <a href="/profile/{{ username }}/favorites" class-active="section === 'favorites'" class="nav-link"> Favorited Articles </a> </li> </ul> </div> <articles params="{{ section === 'favorites' ? {favorited: username} : {author: username} }}" /> </div> </div> </div> {{/if}} </div> 

その埌、すべおが通垞通りです- ルヌトコンポヌネントず察応するルヌトに远加したす。

./src/app.js

  components: { ... profile: require('./components/Profile'), }, 

完党なコヌド./src/app.js
 const Ractive = require('ractive'); Ractive.DEBUG = (process.env.NODE_ENV === 'development'); Ractive.DEBUG_PROMISES = Ractive.DEBUG; Ractive.defaults.enhance = true; Ractive.defaults.lazy = true; Ractive.defaults.sanitize = true; Ractive.defaults.snapshot = '@global.__DATA__'; Ractive.defaults.data.formatDate = require('./helpers/formatDate'); Ractive.defaults.data.errors = null; Ractive.partials.errors = require('./templates/parsed/errors'); Ractive.use(require('ractive-ready')()); Ractive.use(require('ractive-page')({ meta: require('../config/meta.json') })); const api = require('./services/api'); const options = { el: '#app', template: require('./templates/parsed/app'), partials: { navbar: require('./templates/parsed/navbar'), footer: require('./templates/parsed/footer'), homepage: require('./templates/parsed/homepage'), notfound: require('./templates/parsed/notfound') }, transitions: { fade: require('ractive-transitions-fade'), }, components: { tags: require('./components/Tags'), articles: require('./components/Articles'), profile: require('./components/Profile'), }, computed: { tags: require('./computed/tags') } }; module.exports = () => new Ractive(options); 


./src/templates/app.html

 ... {{elseif $route.match('/profile/:username/:section?') }} <div class="profile-page" fade-in="fadeIn" fade-out="fadeOut"> <profile username="{{ $route.params.username }}" section="{{ $route.params.section }}" /> </div> ... 

完党なコヌド./src/templates/app.html
 {{>navbar}} {{#with @shared.$route as $route, {delay: 500} as fadeIn, {duration: 200} as fadeOut }} <div class="page"> {{#if $route.match('/login') }} <div class="auth-page" fade-in="fadeIn" fade-out="fadeOut"> Login page </div> {{elseif $route.match('/register') }} <div class="auth-page" fade-in="fadeIn" fade-out="fadeOut"> Register page </div> {{elseif $route.match('/profile/:username/:section?') }} <div class="profile-page" fade-in="fadeIn" fade-out="fadeOut"> <profile username="{{ $route.params.username }}" section="{{ $route.params.section }}" /> </div> {{elseif $route.match('/') }} <div class="home-page" fade-in="fadeIn" fade-out="fadeOut"> {{>homepage}} </div> {{else}} <div class="notfound-page" fade-in="fadeIn" fade-out="fadeOut"> {{>notfound}} </div> {{/if}} </div> {{/with}} {{>footer}} 


たぶん、今日はこれで十分でしょう。珟圚のプロゞェクトの結果はこちら

→ リポゞトリ
→ デモ

次のパヌトでは、承認ず同型フォヌムを段階的に匷化しおいきたす。面癜いでしょう、切り替えないでください

Source: https://habr.com/ru/post/J351378/


All Articles