ホームページ > ウェブフロントエンド > htmlチュートリアル > 2015 年のフロントエンド コンポーネント フレームワークへの道_html/css_WEB-ITnose

2015 年のフロントエンド コンポーネント フレームワークへの道_html/css_WEB-ITnose

WBOY
リリース: 2016-06-24 11:45:55
オリジナル
1216 人が閲覧しました

https://github.com/xufei/blog/issues/19

1. コンポーネント化はなぜ難しいのですか?

Web アプリケーションのコンポーネント化は非常に複雑なトピックです。

大規模なソフトウェアでは、コンポーネント化によって開発効率が向上する一方、メンテナンスコストが削減されることが一般的です。しかし、Webフロントエンドの分野では、あまり共通したコンポーネントモデルが存在せず、誰もが納得できる実装方法が存在しないため、多くのフレームワーク/ライブラリが独自のコンポーネント化方法を実装しています。

ホイール作りに最も熱心なフロントエンドサークルは、これほど混沌と繁栄した分野は他にはありません。これは、フロントエンド分野の創造性が非常に高いことを示す一方で、インフラが不完全であることを示しています。

私はかつて、特定のプログラミング テクノロジーとその生態学的発展の段階を説明するために、次のような例え話をしたことがあります。

当初、人々はさまざまな API を完成させるのに忙しくしていました。つまり、彼らが持っているものはまだ不足しており、開発する必要がありました。言語とインフラストラクチャは改善され続けています
  • その後、さまざまなモデルが始まり、それらが行うことは徐々に大きく、より複雑になり、より適切な組織化が必要であることが示されました
  • そして、さまざまな種類の階層型 MVC、MVP、MVVM などがあり、視覚化 開発、自動テスト、チームコラボレーションシステムなどは、いわゆるエンジニアリングである生産効率を重視していることを示しています
  • そこで、これら 3 つの段階を比較し、これら 3 つのことに注意を払っている人の数を見てみましょう。 Webの発展は進んだと思いますか?

    細かいところではおそらくモジュール化やコンポーネント化の標準が(良いか悪いかは別として)大規模に実装されようとしていて、ようやく各種APIも大まかに準備が整いつつあると思われます。古いブラウザの影響を考慮しなければ、さまざまなフレームワークが数年以内に非常に強力になるでしょう。このシャッフル プロセスは大幅に加速され、そのとき初めて Web フロントエンドの生産能力が解放されます。

    しかし、普及しようとしているこれらの標準の多くは、以前の研究に変更をもたらすことに注意する必要があります。産業システムの発展の歴史と比較すると、現在、フロントエンド分野は蒸気機関が発明される前の段階にあり、主に比較的原始的な動力と材料を備えた初期の機械(「ムーラン・シー」の機械など)です。 )が人気になっています。

    したがって、この観点から、多くのフレームワーク/ライブラリは廃止され(モジュール式 AMD および CMD 関連ライブラリに特化し、一部のライブラリは標準化された DOM セレクターに焦点を当てています)、一部は革新される必要があり、一部はあまり影響を受けないでしょう(データ視覚化など)関連する方向)、独自の方向に進化し続ける機会を得るでしょう。

    2. 規格の変更

    この種の場合、幅広い大衆層を獲得するには、将来の規格にどれだけ対応できるかが鍵となります。フロントエンド プログラミングに大きな影響を与える可能性のある標準は次のとおりです:

    module
  • Web Components
  • class
  • observe
  • promise
  • モジュール問題は、JavaScript の言語モジュール メカニズムを理解するのが簡単です。 Web コンポーネントは、汎 HTML システムに基づいてコンポーネント ライブラリを構築する方法について初めて合意しました。Observe は、現在最も人気のある非同期プログラミング方法を提供します。フロントエンド。

    回避できないものは 2 つだけです。1 つはモジュール、もう 1 つは Web コンポーネントです。前者はモジュール化の基礎であり、後者はコンポーネント化の基礎です。

    モジュールの標準化は主に一部の AMD/CMD ロードと関連する管理システムに影響を与えます。seajs チームの @afc163 が述べたように、AMD と CMD は両方とも時代遅れです。

    モジュール化は移行が比較的簡単です。AMD や CMD と比較すると、パッケージ化の形式は変わりましたが、コンポーネント化はより困難な問題です。

    Web コンポーネントは、具体的には次のような推奨されるコンポーネント化方法を提供します。

    Shadow DOM を使用してコンポーネントの内部構造をカプセル化する
  • カスタム要素を使用してコンポーネントのラベルを外部に提供する
  • テンプレートを使用してコンポーネントの HTML テンプレートを定義する要素
  • HTML インポートによるコンポーネントの依存関係の読み込みの制御
  • これらは、さまざまな既存のフロントエンド フレームワーク/ライブラリに大きな影響を与えます:

  • Shadow DOM の出現により、コンポーネントの内部実装はより適切に隠蔽され、各コンポーネントはより独立しています。ただし、これにより CSS は非常に断片化され、LESS や SASS などのスタイル フレームワークは大きな課題に直面しています。
  • コンポーネントの分離により、各コンポーネント内のDOMの複雑さが軽減されるため、ほとんどの場合、セレクターをコンポーネント内に制限することができ、従来のセレクターの複雑さが軽減され、依存関係の減少につながります。 jQuery について。
  • コンポーネントの分離が強化されているため、フロントエンド コンポーネント開発用のさまざまなフレームワーク/ライブラリ (Polymer を除く) の確立、独自のコンポーネント実装メソッドと標準 Web コンポーネントの組み合わせ、コンポーネント間のデータ モデルの同期に取り組んでいます。他の問題でも珍しい課題に直面しました。
  • HTML インポートと新しいコンポーネントのカプセル化メソッドの使用により、これまで一般的に使用されていた JavaScript ベースのコンポーネント定義メソッドは、その依存関係と読み込みが新たな課題に直面し、グローバル スコープの弱体化によりリクエストのマージが発生します。はさらに難しくなります。
  • 3. 現時点で最もファッショナブルなフロントエンド コンポーネント フレームワーク/ライブラリ

    2015 年初頭のこの時点では、フロントエンド分野で流行をリードしている 3 つのフレームワーク/ライブラリ、つまり Angular、Polymer、 React (最初の文字に従ってランク付け)。Hu の記事: 2014 年末に人気のある Web 開発テクノロジは何ですか?ここで私はいくつかの点に大まかに答えましたが、他の数人の友人の答えも読む価値があります。これら 3 つの詳細な分析に関しては、

    Hou Zhenyu の記事が非常に優れています: 2015 年にフロントエンド フレームワークはどこへ行くでしょうか? Polymer はその中心的なコンセプトに基づいているため、この点で固有の利点があることがわかります。つまり、基本的に現状の問題をどう解決するかということは考えず、未来をそのまま開発の方向性としています。

    React のプログラミング モデルは、実際には Web 標準を特に考慮する必要はありません。その実装メカニズムにより、UI 層の実装がブロックされるため、ネイティブおよびキャンバスでの使用がわかります。これは DOM ベースのプログラミングとは大きく異なるため、タグをカプセル化するときに Web コンポーネントの互換性の問題を解決する必要があります。タグはとにかく事前にカプセル化する必要があります。

    Angular 1.x バージョンは、同時代のほとんどのフレームワーク/ライブラリと同じであると言えますが、基本的には将来の標準との互換性を考慮していませんでした。しかし、再設計後の 2.0 バージョンには多くのトレードオフがあります。は急激な変化となり、突然未来のものになりました。

    これら 3 つはそれぞれに独自のメリットがありますが、今後数年のうちに、これらよりも人気のある新しいフレームワークが出現する可能性があります。

    さらに、オリジナルの Angular 2.0 のメンバーである Rob Eyesenberg は、Angular 2.0 の強力な競合相手となる独自の新世代フレームワーク aurelia を作成しました。

    4. フロントエンドコンポーネントの再利用性

    いくつかの既存のものを確認した後、フロントエンドコンポーネント化のいくつかの概念について大まかに説明します。ブラウザーにネイティブな、または何らかのフレームワーク/ライブラリによって実装された規約にかかわらず、ある種の基礎となるコンポーネント メカニズムがあり、それを使用して大規模な Web アプリケーションを構築する予定があるとします。どうすればよいでしょうか。

    いわゆるコンポーネント化の中心的な意味は、再利用のために真に価値のあるものを抽出することです。では、再利用価値のあるものにはどのようなものがあるのでしょうか?

    コントロール

  • 基本的なロジック関数
  • パブリック スタイル
  • 安定したビジネス ロジック
  • コントロールは真の汎用関数であり、比較的独立しているため、基本的にコントロールの再利用性については議論の余地はありません。
  • 基本的な論理関数は主に、アンダースコアなどの補助ライブラリや検証などの純粋な論理関数など、インターフェイスとは関係のないものを指します。

    パブリック スタイルの再利用性も比較的簡単に認識できるため、ブートストラップ、ファウンデーション、セマンティックなども人気になるでしょう。ただし、これらは純粋なスタイル ライブラリではなく、いくつかの小さなロジックのカプセル化も備えています。

    最後の部分はビジネス ロジックです。この部分の再利用については多くの議論がありますが、一方では、ビジネス ロジックもコンポーネント化する必要があるということに同意していない人もいます。その一方で、これをどのようにコンポーネント化するかについても考える必要があります。

    上記のものに加えて、これは明らかに再利用性が非常に低く、基本的に再利用性がありませんが、それらを「コンポーネント化」するソリューションはまだたくさんあります。再利用可能なコンポーネント」。なぜこのようなことが起こるのでしょうか?

    コンポーネント化の本質的な目的は必ずしも再利用性ではなく、保守性を向上させることです。これはオブジェクト指向言語と同じで、Java は例外を許可しないため、C++ よりも純粋です。したがって、Java は純粋なオブジェクト指向言語ですが、C++ はそうではありません。

    私たちの場合、コンポーネント化は完全コンポーネント化と部分コンポーネント化にも分けることができます。これら 2 つの違いをどう理解すればよいでしょうか? 一般的に、一致が強いものをフレームワークと呼び、一致が緩やかなものをライブラリと呼びます。多くのフレームワークは完全にコンポーネント化された概念を持っています。たとえば、何年も前に登場した ExtJS は完全にコンポーネント化されたフレームワークですが、jQuery とそのプラグイン システムは部分的にコンポーネント化されています。なので、ExtJSで何かを書く場合は、何を書いても書き方はほぼ同じで、jQueryを使う場合は、違う部分があれば、その箇所でプラグインを呼び出して作るだけです。専門分野。

    ある程度の規模のWebアプリケーションであれば、すべてを「コンポーネント化」することで管理がより便利になります。例を挙げてみましょう。コードを書くときは、長いコードよりも短いコードの方が読みやすいのは明らかです。そのため、多くの言語では、「メソッドは通常、特定の行数を超えないようにする必要があります。」一定の行数を超えます。」 Web フロントエンド システムでは、JavaScript は比較的よくできています。現在、入門レベルの人が大量の js を作成することはほとんどありません。 CSS は、SASS や LESS などのフレームワークの指導のもと、モジュール化に向けて徐々に発展しています。そうでない場合、ブートストラップのような CSS を直接記述するのは非常に困難です。

    この時点で、テンプレートやその他のテクノロジの使用を考慮しない場合、いくつかのインターフェイスのレイアウト コードは非常に多くのコードを記述する必要があります。たとえば、いくつかのフォームは階層化する必要があります。複雑なレイアウトのものはもちろん、単純なフォーム要素が多く含まれるレイヤーごとに作成します。特にシステム全体が 1 つのページに変換された後は、ヘッダー、フッター、さまざまなナビゲーション、またはインターフェイスの脇部分がある程度複雑になる可能性があります。これらのコードがセグメント化されていない場合、メイン インターフェイスの HTML は間違いなく醜くなります。

    特定のテンプレートを使用するか、Angular でインクルードを使用するか、Polymer や React でタグを使用するか、ネイティブ Web コンポーネントを直接使用するかなど、分割方法は気にしません。つまり、各部分を分解する必要があります。それからそれを含めます。こうやって取り除いたものは部品のようなものですが、再利用性という観点から見ると、ほとんどの物は一か所でしか使われておらず、全く再利用性がありません。これは、プロジェクト全体の管理と保守を容易にするために純粋に削除されました。

    このとき、さまざまなフレームワーク/ライブラリが UI 層のコンポーネント化をどのように処理するかに注目すると、テンプレートと関数の 2 つのタイプがあることがわかります。

    テンプレートは、HTML 文字列を使用してインターフェイスの元の構造を表現し、データを置き換えることによって実際のインターフェイスを生成するものや、さまざまなイベントの自動バインディングを生成するものもあります。前者は静的テンプレート、後者は動的テンプレートです。

    さらに、初期の ExtJS や現在の React (依然として内部でテンプレートを使用し、コンポーネント作成インターフェース??jsx のさらなるカプセル化を提供する可能性があります) など、関数型ロジックを使用してインターフェースを生成することを好むフレームワーク/ライブラリがいくつかあります。この実装テクノロジの利点は、プログラミング エクスペリエンスがさまざまなプラットフォームで一貫しており、呼び出し元が Web およびさまざまなネイティブ プラットフォームで使用できるコードを簡単に作成できることです。しかし、この方法はインターフェースの調整が比較的面倒であるという面倒な側面もある。

    この記事の前半で Hou Zhenyu が引用した記事の中で、彼は次のような質問を提起しました:

    コンポーネントをより再利用しやすくするにはどうすればよいですか? より具体的には:

  • コンポーネントを使用するときにコンポーネント内の要素を再調整する必要があります。
  • コンポーネント内の特定の要素を削除したい場合はどうすればよいですか? より具体的には:
  • ビジネス側から追加を要求され続けた場合はどうすればよいですか?
  • このため、この点については私は異なる意見を持っています。

    ビジネスインターフェースをコンポーネントに分割する方法を見てみましょう。

    このような単純なシナリオがあります。従業員リストのインターフェイスには、従業員フォームと従業員情報を入力するフォームの 2 つの部分が含まれています。このシナリオにはどのようなコンポーネントが存在しますか?

    この問題に関しては、主に 2 つの傾向があります。1 つは「コントロール」とより一般的なもののみをコンポーネントにカプセル化すること、もう 1 つはアプリケーション全体をコンポーネント化することです。

    前者の方法では、データテーブルなどのコンポーネントは 1 つだけです。
    後者の方法には、データ テーブル、従業員フォーム、さらには従業員リスト インターフェイスなどのより大きなコンポーネントが含まれる場合があります。

    これら 2 つの方法は、以前は「部分コンポーネント化」と「完全コンポーネント化」と呼ばれていたものです。

    完全コンポーネント化には管理上の利点があると述べました。たとえば、先ほどのビジネス シナリオは、最終的には次のように記述されます。

    <Employee-Panel>    <Employee-List></Employee-List>    <Employee-Form></Employee-Form></Employee-Panel>
    ログイン後にコピー

    UI レイヤーの場合、最適なコンポーネント化方法はラベル付けです。たとえば、上記のコードでは、3 つのラベルがインターフェイス全体を表しています。しかし、私自身はラベルの乱用には断固反対します。あらゆるものをできるだけカプセル化することが必ずしも良いというわけではありません。

    完全なタグ付けの主な問題は次のとおりです:

    まず、セマンティクスのコストが高すぎます。タグを使用する限り、タグには適切なセマンティクス、つまり名前を付ける必要があります。しかし、実際に使用する場合は、おそらく、HTML の束を単純化するだけでしょう。その単純化されたものを何と呼ぶべきでしょうか?たとえば、従業員が管理するフォームの場合、このフォームにはヘッダーやフッターはあるのか、折りたたむことはできるのかなど、他の人が一目でわかるような名前を考えるのは難しいです。長すぎるでしょう。これは比較的単純です。完全にコンポーネント化されているため、複数のものを組み合わせたより複雑なインターフェイスになる可能性があります。よく考えても名前を付けることができなかったので、次のように書きました。誇張しているかもしれませんが、多くの場合、プロジェクトの規模が十分に大きいため、そのような複雑な名前を付ける余裕はなく、最終的には同様の機能を持つコンポーネントと区別できない可能性があります。コンポーネントはすべて同じ名前空間に存在します。インターフェースの断片として組み込まれているだけであれば、そのような心理的な負担はありません。

    たとえば、Angular では次のようになります:

    <Panel-With-Department-Panel-On-The-Left-And-Employee-Panel-On-The-Right></Panel-With-Department-Panel-On-The-Left-And-Employee-Panel-On-The-Right>
    ログイン後にコピー

    名前を付けずに、直接含めてファイル パスを使用して区別します。このフラグメントの役割は、論理名ではなく物理名によって識別されるディレクトリ構造によって説明できます。ディレクトリ階層は適切な名前空間として機能します。

    Knockout、Angular、avalon、vue など、現在主流の MVVM フレームワークの一部には「インターフェイス テンプレート」がありますが、このテンプレートは単なるテンプレートではなく、設定ファイルとみなすことができます。特定のインターフェイス テンプレートは、データ モデルとの関係を記述し、解析された後、さまざまな設定に従ってデータに関連付けられ、対応するビューが更新されます。

    ビジネスロジックのないUI(またはビジネスロジックが分離されたUI)は、たとえロジックが変更されていないとしても、インターフェースの改訂の可能性が多すぎるため、基本的にコンポーネントとして扱うには適していません。例えば、新しい CSS 実装を float レイアウトから flex レイアウトに変更した場合でも、DOM 構造を div 数層削減することが可能です。そのため、テンプレートを使用するスキームでは、インターフェイス層は次のように考えることができます。設定ファイルをコンポーネントとして扱うことはできません。これを行うと、はるかに簡単になります。

    軍隊が行軍しているとき、「山に出会ったら道を開き、水に出会ったら橋を架ける」ことに注意を払います。この文の重要な点は、特定の地形に到達したときにのみ道路を開いて橋を架けるということです。 MVVM などを使用することで解決されるモデルはほとんどが平坦で、横に歩くのは困難です。はい、道路を建設する必要はありません。したがって、ソリューション全体を見ると、UI レイヤーの実装はテンプレートとコントロールを共存させる必要があります。ほとんどの部分はテンプレートであり、いくつかの場所は別個の時間を必要とする道路と橋です。

    2 番目に、構成が複雑すぎます。カプセル化に適さないものも多くありますが、カプセル化のコストだけでなく、使用コストも高くなります。場合によっては、呼び出しコードのほとんどがさまざまな構成を記述していることに気づくことがあります。

    先ほどの従業員フォームと同様に、ラベルの名前と区別しないので、コンポーネントに設定を追加することになります。たとえば、最初にこれを実行したかった場合:

    rrree

    次に、コンポーネント内でヘッダーが設定されているかどうかを確認します。設定されていない場合は表示されません。 2 日後、製品は見出し内の特定の単語を太字にしたり色を変更したりできるかどうかを尋ね、コーダーはこの見出し属性を HTML に渡すことを許可し始めました。その後間もなく、誰かがあなたのコンポーネントを使用し、何も言わずにヘッダーの折りたたみボタンの HTML を渡し、セレクターを使用して折りたたみボタンをクリックした後にイベントを追加していることに気づくでしょう。フォーム...

    それでは、折り畳むボタンの表示を簡単に制御できるように別の設定を追加する必要があると思いますが、このように書くのはあまりにも直感的ではありません。そこで、オブジェクト構造の設定を使用します。

    <div ng-include="'aaa/bbb/ccc.html'"></div>
    ログイン後にコピー

    そして、ある日、多くのパネルが折りたためることに気づき、特別に折りたたみ可能なパネル コンポーネントを作成し、他の通常のビジネス パネルが継承する継承メカニズムを作成しました。 、制御不能でした。

    この例を挙げて私が言いたいのは、大規模なプロジェクトでは、すべての一般的なビジネス インターフェイスを完全にラベル付けして構成した方法で記述しようとすると、2 倍の労力で確実に半分の結果が得られるということです。規模が大きくなるほど、落とし穴も多くなります。これが、UI 層を過剰にカプセル化する ExtJS のようなシステムの最大の問題です。

    この問題について説明したので、次は別の問題を見てみましょう。UI コンポーネントにビジネス ロジックがある場合、それをどのように処理する必要があるかです。

    たとえば、性別を選択するためのドロップダウン ボックスは非常に一般的な機能であるため、コンポーネントとして提供するのが論理的に適切です。しかし、それをどのようにカプセル化するかには、いくつかの困難があります。このコンポーネントにはインターフェースに加えてデータも含まれます。このデータをコンポーネントに組み込む必要がありますか?理論的には、コンポーネントのカプセル化の観点からは、すべてが内部にあるはずなので、次のようなコンポーネントを作成しました:

    <GenderSelect></GenderSelect>
    ログイン後にコピー

    这个组件非常美好,只需直接放在任意的界面中,就能显示带有性别数据的下拉框了。性别的数据很自然地是放在组件的实现内部,一个写死的数组中。这个太简单了,我们改一下,改成商品销售的国家下拉框。

    表面上看,这个没什么区别,但我们有个要求,本公司商品销售的国家的信息是统一配置的,也就是说,这个数据来源于服务端。这时候,你是不是想把一个http请求封装到这组件里?

    这样做也不是不可以,但存在至少两个问题:

  • 如果这类组件在同一个界面中出现多次,就可能存在请求的浪费,因为有一个组件实例就会产生一个请求。
  • 如果国家信息的配置界面与这个组件同时存在,当我们在配置界面中新增一个国家了,下拉框组件中的数据并不会实时刷新。
  • 第一个问题只是资源的浪费,第二个就是数据的不一致了。曾经在很多系统中,大家都是手动刷新当前页面来解决这问题的,但到了这个时代,人们都是追求体验的,在一个全组件化的解决方案中,不应再出现此类问题。

    如何解决这样的问题呢?那就是引入一层Store的概念,每个组件不直接去到服务端请求数据,而是到对应的前端数据缓存中去获取数据,让这个缓存自己去跟服务端保持同步。

    所以,在实际做方案的过程中,不管是基于Angular,React,Polymer,最后肯定都做出一层Store了,不然会有很多问题。

    5. 为什么MVVM是一种很好的选择

    我们回顾一下刚才那个下拉框的组件,发现存在几个问题:

  • 界面不好调整。刚才的那个例子相对简单,如果我们是一个省市县三级联动的组件,就比较麻烦了。比如说,我们想要把水平布局改成垂直的,又或者,想要把中间的label的字改改,都会非常麻烦。按照传统的做组件的方式,就要加若干配置项,然后组件里面去分别判断,修改DOM结构。
  • 如果数据的来源不是静态json,而是某个动态的服务接口,那用起来就很麻烦。
  • 我们更多地需要业务逻辑的复用和纯“控件”的复用,至于那些绑定业务的界面组件,复用性其实很弱。
  • 所以,从这些角度,会尽量期望在HTML界面层与JavaScript业务逻辑之间,存在一种分离。

    这时候,再看看绝大多数界面组件存在什么问题:

    有时候我们考虑一下DOM操作的类型,会发现其实是很容易枚举的:

  • 创建并插入节点
  • 移除节点
  • 节点的交换
  • 属性的设置
  • 多数界面组件封装的绝大部分内容不过是这些东西的重复。这些东西,其实是可以通过某些配置描述出来的,比如说,某个数组以什么形式渲染成一个select或者无序列表之类,当数组变动,这些东西也跟着变动,这些都应当被自动处理,如果某个方案在现在这个时代还手动操作这些,那真的是一种落伍。

    所以我们可以看到,以Angular,Knockout,Vue,Avalon为代表的框架们在这方面做了很多事,尽管理念有所差异,但大方向都非常一致,也就是把大多数命令式的DOM操作过程简化为一些配置。

    有了这种方式之后,我们可以追求不同层级的复用:

  • 业务模型因为是纯逻辑,所以非常容易复用
  • 视图模型基本上也是纯逻辑,界面层多数是纯字符串模板,同一个视图模型搭配不同的界面模板,可以实现视图模型的复用
  • 同一个界面模板与不同的视图模型组合,也能直接组合出完全不同的东西
  • 所以这么一来,我们的复用粒度就非常灵活了。正因为这样,我一直认为Angular这样的框架战略方向是很正确的,虽然有很多战术失误。我们在很多场景下,都是需要这样的高效生产手段的。

    6. 组件的长期积累

    我们做组件化这件事,一定是一种长期打算,为了使得当前的很多东西可以作为一种积累,在将来还能继续使用,或者仅仅作较小的修改就能使用,所以必须考虑对未来标准的兼容。主要需要考虑的方面有这几点:

  • 尽可能中立于语言和框架,使用浏览器的原生特性
  • 逻辑层的模块化(ECMAScript module)
  • 界面层的元素化(Web Components)
  • 之前有很多人对Angular 2.0的激进变更很不认同,但它的变更很大程度上是对标准的全面迎合。这不仅仅是它的问题,其实是所有前端框架的问题。不面对这些问题,不管现在多么好,将来都是死路一条。这个问题的根源是,这几个已有的规范约束了模块化和元素化的推荐方式,并且,如果要对当前和未来两边做适配的话,基本就没法干了,导致以前的都不得不做一定的迁移。

    模块化的迁移成本还比较小,无论是之前AMD还是CMD的,都可以根据一些规则转换过来,但组件化的迁移成本太大了,几乎每种框架都会提出自己的理念,然后有不同的组件化理念。

    还是从三个典型的东西来说:Polymer,React,Angular。

    Polymer中的组件化,其实就是标签化。这里的标签,并不只是界面元素,甚至逻辑组件也可以这样,比如这个代码:

    <my-panel>    <core-ajax id="ajax" url="http://url" params="{{formdata}}" method="post"></core-ajax></my-panel>
    ログイン後にコピー

    注意到这里的core-ajax标签,很明显这已经是纯逻辑的了,在大多数前端框架或者库中,调用ajax肯定不是这样的,但在浏览器端这么干也不是它独创,比如flash里面的WebService,比如早期IE中基于htc实现的webservice.htc等等,都是这么干的。在Polymer中,这类东西称为非可见元素(non-visual-element)。

    React的组件化,跟Polymer略有不同,它的界面部分是标签化,但如果有单纯的逻辑,还是纯JavaScript模块。

    既然大家的实现方式都那么不一致,那我们怎么搞出尽量可复用的组件呢?问题到最后还是要绕到Web Components上。

    在Web Components与前端组件化框架的关系上,我觉得是这么个样子:

    各种前端组件化框架应当尽可能以Web Components为基石,它致力于组织这些Components与数据模型之间的关系,而不去关注某个具体Component的内部实现,比如说,一个列表组件,它究竟内部使用什么实现,组件化框架其实是不必关心的,它只应当关注这个组件的数据存取接口。

    然后,这些组件化框架再去根据自己的理念,进一步对这些标准Web Components进行封装。换句话说,业务开发人员使用某个组件的时候,他是应当感知不到这个组件内部究竟使用了Web Components,还是直接使用传统方式。(这一点有些理想化,可能并不是那么容易做到,因为我们还要管理像import之类的事情)。

    7. 我们需要关注什么

    目前来看,前端框架/库仍然处于混战期,可比中国历史上的春秋战国,百家齐放,作为跟随者来说,这是很痛苦的,因为无所适从,很可能你作为一个企业的前端架构师或者技术经理,需要做一些选型工作,但选哪个能保证几年后不被淘汰呢?基本没有。

    虽然我们不知道将来什么框架会流行,但我们可以从一些细节方面去关注,某个具体的方面,将来会有什么,也可以了解一下在某个具体领域存在什么样的方案。一个完整的框架方案,无非是以下多个方面的综合。

    7.1 模块化

    这块还是不讲了,支付宝seajs还有百度ecomfe这两个团队的人应该都能比我讲得好得多。

    7.2 Web Components

    本文前面讨论过一些,也不深入了。

    7.3 变更检测

    我们知道,现代框架的一个特点是自动化,也就是把原有的一些手动操作提取。在前端编程中,最常见的代码是在干什么呢?读写数据和操作DOM。不少现代的框架/库都对这方面作了处理,比如说通过某种配置的方式,由框架自动添加一些关联,当数据变更的时候,把DOM进行相应修改,又比如,当DOM发生变动的时候,也更新对应的数据。

    这个关联过程可能会用到几种技术。首先我们看怎么知道数据在变化,这里面有三种途径:

    一、存取器的封装。这个的意思也就是对数据进行一层包装,比如:

    var data = {    name: "aaa",    getName: function() {        return this.name;    },    setName: function(value) {        this.name = value;    }}
    ログイン後にコピー

    这样,不允许用户直接调用data.name,而是调用对应的两个函数。Backbone就是通过这样的机制实现数据变动观测的,这种方式适用于几乎所有浏览器,缺点就是比较麻烦,要对每个数据进行包装。

    这个机制在稍微新一点的浏览器中,也有另外一种实现方式,那就是defineProperty相关的一些方法,使用更优雅的存取器,这样外界可以不用调用函数,而是直接用data.name这样进行属性的读写。

    国産フレームワークのavalonはこの仕組みを利用しています。IEの下位バージョンにはdefinePropertyがありませんが、IEの下位バージョンにはJavaScriptだけでなくアクセサを持つVBScriptも存在するため、VBSを巧みに利用してこのような仕組みを作っています。互換性のあるパッケージ。

    アクセサーベースのメカニズムに関するもう 1 つの問題は、属性が動的に追加されるたびに、対応するアクセサーを追加する必要があり、追加しないと、この属性への変更を取得できないことです。

    2. ダーティ検出。

    Angular 1.x に代表されるフレームワークは、ダーティ検出を使用してデータの変更を学習します。 このメカニズムの一般原則は次のとおりです。

    DOM またはネットワーク、タイマー、および他のイベントでは、このイベントの後のデータを使用して、以前に保存されたデータと比較します。同じであれば、インターフェイスの更新はトリガーされません。そうでない場合は、更新されます。

    このメソッドの考え方は、データ変更を引き起こす可能性のあるすべてのソース (つまり、さまざまなイベント) を制御し、それらがデータを操作した後、すべてを無視して、古いデータと新しいデータに変更があるかどうかを判断することです。中間の変更、つまり、同じイベント内で特定のデータを任意に何度も変更し、最終的に元に戻した場合、フレームワークは何も行っていないと判断し、インターフェイスに更新を通知しません。

    主にデータ変更の影響を正確に把握できないため、ダーティ検出の効率が比較的低いことは否定できません。そのため、データ量が増えると無駄が大きくなり、手動による最適化が必要になります。たとえば、大きな配列では、項目が選択されると、インターフェイス上にリストが生成されます。このメカニズムでは、このアイテムのデータステータスが変更されるたびに、すべてのアイテムを元のアイテムと比較する必要があり、その後、すべてのアイテムを再度比較して、関連する変更がないことを確認してから、インターフェイスを構築することができます。それに応じてリフレッシュされました。

    3. 観察メカニズム。

    ES7 では、オブジェクトまたは配列の変更を監視するために使用できる Object の観察メソッドが導入されました。

    これは、これまでのところ最も合理的な観察計画です。この仕組みは非常に正確かつ効率的です。たとえば、中隊長は兵士たちに向かいの地下壕で何が起こっているかを観察するように指示します。この意味は非常に複雑ですが、どのような意味が含まれているのでしょうか?

  • 誰かが誰かを追加しましたか
  • 誰かが辞めましたか
  • 誰が誰と転職しましたか
  • 上の旗が日の丸から青空と白日の旗に変わりました
  • いわゆる観測機構は属性の変更です観測対象の配列要素の追加、削除、位置変更など。まず、インターフェースとデータのバインディングについて考えてみましょう。これは、あなたがデータであり、私がインターフェースであるということです。この種の束縛は、束縛によって本来のものを破壊してはいけないので、明らかにより合理的です。

    観察できるデータの変更に加えて、DOM も観察できます。ただし、現在の双方向同期フレームワークのほとんどは、イベントを通じて DOM の変更をデータに同期します。たとえば、テキスト ボックスがオブジェクトのプロパティにバインドされている場合、フレームワークはこのテキスト ボックスのキーボード入力、貼り付け、およびその他の関連イベントを内部的に監視し、値を取得してオブジェクトに書き込みます。 。

    これを行うことでほとんどの問題は解決できますが、myInput.value="111" を直接実行した場合、この変更は利用できません。双方向バインディング フレームワークでは、監視と手動割り当ての両方が行われること自体が非常に奇妙であるため、これは大きな問題ではありません。ただし、一部のフレームワークは、HTMLInputELement のプロトタイプからの値の割り当てをオーバーライドしようとします。この枠組みの管轄内にあるものも含まれます。

    もう 1 つの質問は、特定の要素の特定の属性のみを考慮し、イベントを通じて変更を取得できるということですが、より広い意味での DOM の変更を取得するにはどうすればよいでしょうか。たとえば、一般的な属性の変更、あるいは子ノードの追加や削除などでしょうか?

    DOM4 では、そのような変化の観察を実装するために MutationObserver を導入しています。 DOM とデータの間にこのような複雑な監視および同期メカニズムが必要かどうかはまだ決定されていませんが、フロントエンド開発全体が段階的に自動化される一般的な傾向の下では、これも試してみる価値があります。

    複雑な相関関係の監視は予期せぬ結果を容易にもたらす可能性があります:

  • Murong Fu さんは国に帰りたかったので、毎日本を読んで武術を練習し、さまざまな計画を立てました
  • Wang Yuyan さんはこの現象を観察し、いとこがそうしたのだと思いましたもう彼女を愛していません
  • 妖精の妹が落ち込んでいて、毎日食べ物や食べ物のことを考えていなかったとき、端宇は見ました
  • ジェンナン公主は息子を気の毒に思ったので、至る所ですべての話を調査し、予期せず段正春を発見しました彼の昔の恋人とまだ連絡を取っていました
  • ...
  • 要するに、結局のところ、彼がどこにいるのかわかりません、誰が邱中治に牛家村を通り過ぎるように頼んだのですか?

    したがって、関連する変更の監視は、特に閉ループが発生した場合には非常に複雑なシステムになります。このような一連のものを構築するには、非常に正確な設計が必要です。そうしないと、一連のメカニズムに精通している人でも、特定のシナリオを使用してわずかに押すだけで転倒してしまいます。霊志先生は武術に優れていましたが、欧陽鋒、周伯通、黄耀士と次々に遭遇し、たちまち全員首の後ろを掴まれました、というのがおおよその意味です。

    Polymer は、配列、オブジェクト、パスの変更を観察するために使用される Observ-js を実装しています。興味がある場合は、注目してください。

    在有些框架,比如aurelia中,是混合使用了存取器和观察模式,把存取器作为观察模式的降级方案,在浏览器不支持observe的情况下使用。值得一提的是,在脏检测方式中,变更是合并后批量提交的,这一点常常被另外两种方案的使用者忽视。其实,即使用另外两种方式,也还是需要一个合并与批量提交过程。

    怎么理解这个事情呢?数据的绑定,最终都是要体现到界面上的,对于界面来说,其实只关注你每一次操作所带来的数据变更的始终,并不需要关心中间过程。比如说,你写了这么一个循环,放在某个按钮的点击中:

    for (var i=0; i<10000; i++) {    obj.a += 1;}
    ログイン後にコピー

    界面有一个东西绑定到这个a,对框架来说,绝对不应当把中间过程直接应用到界面上,以刚才这个例子来说,合理的情况只应当存在一次对界面DOM的赋值,这个值就是对obj.a进行了10000次赋值之后的值。尽管用存取器或者观察模式,发现了对obj上a属性的这10000次赋值过程,这些赋值还是都必须被舍弃,否则就是很可怕的浪费。

    React使用虚拟DOM来减少中间的DOM操作浪费,本质跟这个是一样的,界面只应当响应逻辑变更的结束状态,不应当响应中间状态。这样,如果有一个ul,其中的li绑定到一个1000元素的数组,当首次把这个数组绑定到这个ul上的时候,框架内部也是可以优化成一次DOM写入的,类似之前常用的那种DocumentFragment,或者是innerHTML一次写入整个字符串。在这个方面,所有优化良好的框架,内部实现机制都应当类似,在这种方案下,是否使用虚拟DOM,对性能的影响都是很小的。

    7.4 Immutable Data

    Immutable Data是函数式编程中的一个概念,在前端组件化框架中能起到一些很独特的作用。

    它的大致理念是,任何一种赋值,都应当被转化成复制,不存在指向同一个地方的引用。比如说:

    var a = 1;var b = a;b = 2;console.log(a==b);
    ログイン後にコピー

    这个我们都知道,b跟a的内存地址是不一致的,简单类型的赋值会进行复制,所以a跟b不相等。但是:

    var a = {    counter : 1};var b = a;b.counter++;console.log(a.counter==b.counter);
    ログイン後にコピー

    这时候因为a和b指向相同的内存地址,所以只要修改了b的counter,a里面的counter也会跟着变。

    Immutable Data的理念是,我能不能在这种赋值情况下,直接把原来的a完全复制一份给b,然后以后大家各自变各自的,互相不影响。光凭这么一句话,看不出它的用处,看例子:

    对于全组件化的体系,不可避免会出现很多嵌套的组件。嵌套组件是一个很棘手的问题,在很多时候,是不太好处理的。嵌套组件所存在的问题主要在于生命周期的管理和数据的共享,很多已有方案的上下级组件之间都是存在数据共享的,但如果内外层存在共享数据,那么就会破坏组件的独立性,比如下面的一个列表控件:

    <my-list list-data="{arr}">    <my-listitem></my-listitem>    <my-listitem></my-listitem>    <my-listitem></my-listitem></my-list>
    ログイン後にコピー

    我们在赋值的时候,一般是在外层整体赋值一个类似数组的数据,而不是自己挨个在每个列表项上赋值,不然就很麻烦。但是如果内外层持有相同的引用,对组件的封装性很不利。

    比如在刚才这个例子里,假设数据源如下:

    var arr = [    {name: "Item1"},     {name: "Item2"},     {name: "Item3"}];
    ログイン後にコピー

    通过类似这样的方式赋值给界面组件,并且由它在内部给每个子组件分别进行数据项的赋值:

    list.data = arr;
    ログイン後にコピー

    赋值之后会有怎样的结果呢?

    console.log(list.data == arr);console.log(listitem0.data == arr[0]);console.log(listitem1.data == arr[1]);console.log(listitem2.data == arr[2]);
    ログイン後にコピー

    这种方案里面,后面那几个log输出的结果都会是true,意思就是内层组件与外层共享数据,一旦内层组件对数据进行改变,外层中的也就改变了,这明显是违背组件的封装性的。

    所以,有一些方案会引入Immutable Data的概念。在这些方案里,内外层组件的数据是不共享的,它们的引用不同,每个组件实际上是持有了自己的数据,然后引入了自动的赋值机制。

    这时候再看看刚才那个例子,就会发现两层的职责很清晰:

  • 外层持有一个类似数组的东西arr,用于形成整个列表,但并不关注每条记录的细节
  • 内层持有某条记录,用于渲染列表项的界面
  • 在整个列表的形成过程中,list组件根据arr的数据长度,实例化若干个listitem,并且把arr中的各条数据赋值给对应的listitem,而这个赋值,就是immutable data起作用的地方,其实是把这条数据复制了一份给里面,而不是把外层这条记录的引用赋值进去。内层组件发现自己的数据改变之后,就去进行对应的渲染
  • 如果arr的条数变更了,外层监控这个数据,并且根据变更类型,添加或者删除某个列表项
  • 如果从外界改变了arr中某一条记录的内容,外层组件并不直接处理,而是给对应的内层进行了一次赋值
  • 如果列表项中的某个操作,改变了自身的值,它首先是把自己持有的数据进行改变,然后,再通过immutable data把数据往外同步一份,这样,外层组件中的数据也就更新了。
  • 所以我们再看这个过程,真是非常清晰明了,而且内外层各司其职,互不干涉。这是非常有利于我们打造一个全组件化的大型Web应用的。各级组件之间存在比较松散的联系,而每个组件的内部则是封闭的,这正是我们所需要的结果。

    说到这里,需要再提一个容易混淆的东西,比如下面这个例子:

    <outer-component>    <inner-component></inner-component></outer-component>
    ログイン後にコピー

    如果我们为了给inner-component做一些样式定位之类的事情,很可能在内外层组件之间再加一些额外的布局元素,比如变成这样:

    <outer-component>    <div>        <inner-component></inner-component>    </div></outer-component>
    ログイン後にコピー

    这里中间多了一级div,也可能是若干级元素。如果有用过Angular 1.x的,可能会知道,假如这里面硬造一级作用域,搞个ng-if之类,就可能存在多级作用域的赋值问题。在上面这个例子里,如果在最外层赋值,数据就会是outer -> div -> inner这样,那么,从框架设计的角度,这两次赋值都应当是immutable的吗?

    不是,第一次赋值是非immutable,第二次才需要是,immutable赋值应当仅存在于组件边界上,在组件内部不是特别有必要使用。刚才的例子里,依附于div的那层变量应当还是跟outer组件在同一层面,都属于outer组件的人民内部矛盾。

    这里是facebook实现的immutable-js库

    7.6 Promise与异步

    前端一般都习惯于用事件的方式处理异步,但很多时候纯逻辑的“串行化”场景下,这种方式会让逻辑很难阅读。在新的ES规范里,也有yield为代表的各种原生异步处理方案,但是这些方案仍然有很大的理解障碍,流行度有限,很大程度上会一直停留在基础较好的开发人员手中。尤其是在浏览器端,它的受众应该会比node里面还要狭窄。

    前端里面,处理连续异步消息的最能被广泛接受的方案是promise,我这里并不讨论它的原理,也不讨论它在业务中的使用,而是要提一下它在组件化框架内部所能起到的作用。

    现在已经没有哪个前端组件化框架可以不考虑异步加载问题了,因为,在前端这个领域,加载就是一个绕不过去的坎,必须有了加载,才能有执行过程。每个组件化框架都不能阻止自己的使用者规模膨胀,因此也应当在框架层面提出解决方案。

    我们可能会动态配置路由,也可能在动态加载的路由中又引入新的组件,如何控制这些东西的生命周期,值得仔细斟酌,如果在框架层面全异步化,对于编程体验的一致性是有好处的。将各类接口都promise化,能够在可维护性和可扩展性上提供较多便利。

    我们之前可能熟知XMLHTTP这样的通信接口,这个东西虽然被广为使用,但是在优雅性等方面,存在一些问题,所以最近出来了替代方案,那就是fetch。

    细节可以参见月影翻译的这篇【翻译】这个API很“迷人”??(新的Fetch API)

    在不支持的浏览器上,也有github实现的一个polyfill,虽然不全,但可以凑合用window.fetch polyfill

    大家可以看到,fetch的接口就是基于promise的,这应当是前端开发人员最容易接受的方案了。

    7.7 Isomorphic JavaScript

    这个东西的意思是前后端同构的JavaScript,也就是说,比如一块界面,可以选择在前端渲染,也可以选择在后端渲染,值得关注,可以解决像seo之类的问题,但现在还不能处理很复杂的状况,持续关注吧。

    8. まとめ

    以上、この1年間の私の考えをまとめさせていただきました。技術選択の観点から見ると、大規模な Web アプリケーションを作成する人々にとっては非常に苦痛です。なぜなら、今は開発が停滞している時代であり、既存のフレームワーク/ライブラリはすべて程度の差こそあれ欠陥を抱えているからです。未来に目を向けると、それらすべてを放棄するか変革する必要があることがわかります。人々にとって最も苦痛なことは、多くのものが悪いと知りながら、その中から 1 つを選択しなければならないことです。 @yanqing はこの問題について @inchzhi @Tiye と議論しましたが、彼らは現段階でのテクノロジーの選択は難しいと考えているため、しばらく待った方がよいと考えています。私も彼らの意見に完全に同意します。

    選ぶのは難しいですが、学ぶという観点から見ると、本当に良い時代です。毎日、見るべきものを見ようと頑張っていますが、まだ終わりません。私たちにできるのは、時代についていくことだけです。

    次の段落はあなたの励ましのためです:

    それは最高の時代でした、それは最悪の時代でした、それは知恵の時代でした、それは愚かさの時代でした、それは信仰の時代でした、それは信じられない時代、それは光の季節、それは闇の季節、それは希望の春、それは絶望の冬、私たちの前にはすべてがあった、私たちの前には何もなかった、私たちは皆、まっすぐに向かっていた天国よ、私たちは皆、逆の方向に進んでいた――要するに、この時代は現代とあまりにも似ていて、最も騒々しい権威者の一部が、良くも悪くも比較の最上級の程度でのみこの時代が受け入れられると主張していたのだ。

    関連ラベル:
    ソース:php.cn
    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
    人気のチュートリアル
    詳細>
    最新のダウンロード
    詳細>
    ウェブエフェクト
    公式サイト
    サイト素材
    フロントエンドテンプレート