ブラウザーの JavaScript について
学習の目的
この単元を完了すると、次のことができるようになります。
- JavaScript とブラウザーの相互作用について説明する。
- Web API の役割を説明する。
- ドキュメントオブジェクトモデルの基本的な API を使用する。
- Lightning コンポーネントで DOM を抽象化する方法について説明する。
ブラウザーでの JavaScript の機能
JavaScript のランタイムエンジンと言語について簡単に学習しました。また、JavaScript を記述する人なら誰もが知っておくべき事項もいくつか確認しました。このモジュールでは、JavaScript とブラウザーの関係、特に Web ページが JavaScript でどのように表示されるのかを中心に説明します。
JavaScript ランタイムはブラウザーではなく、ブラウザーは JavaScript ではない
JavaScript ランタイムエンジンはさまざまな場所で実行されますが、通常はブラウザーでホストされます。では、JavaScript とブラウザーは何が違うのでしょうか?
ブラウザーの主な役割は、Web サーバーのクライアントとして機能することです。数種類のプロトコルのいずれかを使用して (通常は HTTP/HTTPS)、インターネット経由でリソースを要求します。サーバーから一定のリソースが届いたら、ブラウザーはそれを利用して何かを実行する必要があります。少なくとも HTML や CSS はページに変換されます。リソースに JavaScript が含まれる場合は、ブラウザーが JavaScript ランタイムエンジンにアクセスしてそのコードを解析して評価し、実行します。
同様に、スクリプトが実行されている間に、JavaScript からブラウザーにアクセスして、Web ページを変更したり、ローカル環境と対話したり、他の Web リソースと対話したりすることもあります。
こうした相互作用を機能させるために、ブラウザーは API を表示します。実のところ、人々がクライアント側の JavaScript と思っているものの大半は、実際には以下のような API です。
Web API
標準
ブラウザーと対話する API があっても驚くことではありません。他のすべてのプログラミング言語やプラットフォームが、それを実行している環境と対話する API を表示するのと同様に、ブラウザーも同じことを行います。
ブラウザーのインターネットトラフィックの半分以上は Google Chrome から来ていますが、残りの約 30 パーセントは他の 5 つのブラウザーからのトラフィックです。こうしたことから API に対する Web 標準の重要性が強調され、そのお陰で JavaScript も一度作成すればどこでも実行できるようになっています。
図式への Web API の追加
以前に JavaScript ランタイムの図式を作成しました。
- シングルスレッドを使用して要求がスタック上で実行されます。
- 要求に対するすべての同期ロジックの実行が完了するまで、その要求がスレッドを保有します。
- 新しい要求はスレッドの準備ができるまでキューに入れられます。
- スレッドを使用可能になった時点で、イベントループがキューに入った次の要求をスタックに移します。
ここで、JavaScript の図式に Web API を追加します。これらの API によって JavaScript のコア言語が拡張されます。API は、昨今の Web のユーザーエクスペリエンスに欠かせない作業の大半を実行するインターフェースを表示します。たとえば、ブラウザーの API は次のことを実行できます。
- ブラウザーに表示される現在のページの構造と対話する (ドキュメントオブジェクトモデル (DOM) API)。
- 現在のページを開いたまま、サーバーに非同期要求を実行する (Fetch API)。
- 音声、動画、グラフィックと対話する。
- ブラウザーに表示されたデバイス機能 (地理位置、デバイスの方向、クライアント側のデータストレージ) と対話する。
ほとんどの場合、キューに新しい要求を追加するのはこれらの API です。
例を挙げてみましょう。フォームが設定された Web ページ上にメモのリストがあるとします。フォームのいくつかの項目に記入し、ボタンをクリックしてそのデータを保存すると、リストに新しい項目が追加されます。試しに、この新しいレコードをローカルにキャッシュして、このレコードに関する今後の要求が加速されるようにします。
このシンプルなユースケースでも、ブラウザーの次の API との対話があります。
- Fetch API (レコードを保存)
- DOM API (新しい項目を HTML リストに追加)
- クライアント側のデータストレージ (データをローカルにキャッシュ)
ではここで、JavaScript 内でユーザーに示される内容を表す DOM API を詳しく見ていきましょう。
ドキュメントオブジェクトモデル
DOM: JavaScript 上の各自のページ
ブラウザーがページを要求して受信すると、ブラウザーは HTML を解析して、そのページの説明 (モデル) を作成します。このモデルを使用して、ブラウザーのビューポートにページが描画されます。このページは DOM を介して JavaScript にも表示されます。
DOM を 1 本の木と考えます。出発点の根となるのがブラウザーの表示機能、つまりウィンドウです。そこから、ページが window.document
にカプセル化され、そのページの本文が window.document.body
にカプセル化されます。そして、ページ上に表されたコンテンツの各部分が枝として広がります。Web ページの大半は極めて複雑な木で、この階層の末端にあるのが葉であるリーフノードです。
API である DOM は膨大ですが、この大木の各部に触れることができます。DOM はまた、その相互作用を最適化する多数の手段を備えています。jsbin.com の次のシンプルな例を見てみましょう。次のような機能があります。
- 入力項目
- [Add Item (項目を追加)] ボタン
- 順序なしリスト
このボタンをクリックするたびに、コードが入力項目にアクセスしてその値を読み取り、テキストノードに変換してから、新しい li
要素を作成し、li
ノードにこのテキストノードを挿入して、最後に新しい li
-and-text ノードを ul
に貼り付けます。
ご覧のとおり、これほどシンプルな例でも、開発者がかなりの数の手動手順を実行する必要があります。そして、まだデータをサーバーに保存もしていなければ、他の方法でサーバーと対話することもしていません。このため、対話型ページについては JavaScript のライブラリ (reactjs、jQuery) やフレームワーク (Angular、vuejs) が標準になっています。こうしたフレームワークは、DOM の相互作用を抽象化して単純化し、欠落している機能に自動的にポリフィルを適用します。Lightning コンポーネントフレームワークも同様です。
Shadow DOM を使用したカプセル化
DOM API は柔軟で、機能が充実しています。比較的シンプルな JavaScript を使用して、UI の外観や動作、UI で呼び出されるアクションを簡単に変更できます。
けれども、落とし穴があります。DOM モデルでは、UI 要素をカプセル化して誤った変更 (または意図的や悪意のある変更) から守ることが難しくなります。
このため、Shadow DOM 標準が開発されました。Shadow DOM は UI 機能の特定の部分の周囲に境界を設けるものです。この境界によって親が子の要素や CSS を変更できなくなります。また、境界を越えて伝播したイベントのターゲット範囲を強制的に変更して、親が Shadow DOM の境界を越えて到達することを防止します。
Lightning Web コンポーネントと DOM
DOM の自動更新
Lightning コンポーネントの基本的な価値は、Salesforce データに基づいてカスタム UI を構築することです。この点は DOM 操作に関係しています。DOM 自体が Salesforce データによって決定されるためです。
以下のコンポーネントは、この原則を最もシンプルな形で示しています。
以下は、このコンポーネントのコードです。
// JavaScript Module: demo.js import { LightningElement } from 'lwc'; export default class Demo extends LightningElement { text = 'This text came from a JS prop'; } <!-- HTML Template: demo.html --> <template> <lightning-card title="Basic DOM Example" icon-name="utility:hierarchy"> <div class="slds-card__body slds-card__body-inner"> <p> <lightning-formatted-text value={text}></lightning-formatted-text> </p> </div> </lightning-card> </template>
最初の一定のコンテキスト: どの Lightning Web コンポーネントも JavaScript モジュールが必要です。コンポーネントにユーザーに表示される要素がある場合は、HTML テンプレートも必要です。大半のコンポーネントは、JavaScript と HTML ファイルをバンドルとしてまとめています。
Lightning Web コンポーネントでは、データを容易に UI に表示できます。たとえば、上記のコードは JavaScript の text
プロパティを使用してハードコード化された文字列を保存します。<lightning-formatted-text>
タグでは、value
属性が JavaScript の text
プロパティにバインドされ、UI にデータが表示されます。
ところで、ユーザーが UI を操作したときに変化させるにはどうすればよいのでしょうか? この例を拡張して、変化させることができます。
以下はそのコードです。
// JavaScript Module demo2.js import { LightningElement } from 'lwc'; export default class Demo2 extends LightningElement { text = ''; handleChange(event){ this.text = event.target.value; } } <!-- HTML Template demo2.html --> <template> <lightning-card title="Basic DOM Example" icon-name="utility:hierarchy"> <div class="slds-card__body slds-card__body-inner"> <p> <lightning-formatted-text value={text}></lightning-formatted-text> </p> <p> <lightning-input onchange={handleChange} label="Input Text" value={text}></lightning-input> </p> </div> </lightning-card> </template>
Lightning Web コンポーネントでは、JavaScript クラスのプロパティはリアクティブです。つまり、プロパティの値が割り当てられ (または再割り当てされ)、プロパティがテンプレートで使用されると、コンポーネントの DOM によって新しい値が再レンダリングされ、表示されます。
このテンプレートに <lightning-input>
が追加されています。データの表示で大切なのは text
プロパティのバインドですが、onchange
属性にも注意します。JavaScript クラスに追加されていた handleChange
関数が、ここで change
イベントによってトリガーされるようになっています。ユーザーによって値が変更されるたびに text
の値が更新されます。テキストが更新されると、DOM も更新されます。
これは架空の例ですが、暗黙の DOM の変更を開発者が簡単に実装できるようにする機能が Lightning Web コンポーネントにどのように表示されるかを示しています。
明示的な DOM 操作
Lightning Web コンポーネントを構築するときに手動の DOM 操作を最初の手段にすべきではありませんが、高度なコンポーネントではこの手法が必要になることがあります。これはより高度なトピックになりますが、DOM の説明の締めくくりとして、手動の DOM 操作がどのように機能するかを多少なりとも知っておく価値があります。
条件付き表示
Lightning Web コンポーネントを記述するときに、特定の条件が満たされたときにのみ表示される UI のセクションを明示的に定義できます。具体的には、ネストされた template タグと一緒に lwc:if
、lwc:elseif
、lwc:else
ディレクティブを使用します。
//conditionalButton.js import { LightningElement } from 'lwc'; export default class ConditionalButton extends LightningElement { show = true; handleClick(){ this.show = !this.show; } } //conditionalButton.html <template> <lightning-card title="Conditional Rendering with Button" icon-name="utility:hierarchy"> <div class="slds-card__body slds-card__body-inner"> <p> <template lwc:if={show}> Peek-a-boo! </template> <template lwc:else> I'm hiding! </template> </p> <p> <lightning-button onclick={handleClick} variant="neutral" label="Switch"></lightning-button> </p> </div> </lightning-card> </template>
このサンプルコードでは、show
プロパティの値が true
の場合に、Peek-a-boo! (いないいないばー!) というテキストが表示されます。show が false
に設定されている場合は、I'm hiding! (隠れてますよ!) というテキストが表示されます。ボタンをクリックすると、show
プロパティの値が切り替わります。
DOM を手動で操作する他の方法
一定の状況では、表示 CSS スタイルを使用して DOM 要素の表示を操作しても構いません。この方法は Lightning Web コンポーネントで通常どおりに機能しますが、この特定のモジュールの範疇外です。
リソース
- Trailhead モジュール: Lightning Web Components Basics (Lightning Web コンポーネントの基本)
- Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): HTML Templates (HTML テンプレート)
- Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): HTML Template Directives (HTML テンプレートのディレクティブ)
- Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): Render DOM Elements Conditionally (DOM 要素の条件付き表示)
- Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): DOM Access Containment (DOM のアクセスコンテインメント)
- Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): Shadow DOM
- ReactJS
- Angular
- VueJS
- Third party Web API (サードパーティの Web API)