JS で単一ファイルコンポーネントを実装する方法

JS で単一ファイルコンポーネントを実装する方法

概要

vue.js フレームワークについて学習したフロントエンド開発者は、単一ファイル コンポーネントについて知っているかもしれません。 Vue.js の単一ファイル コンポーネントを使用すると、コンポーネントのすべてのコンテンツを 1 つのファイルで定義できます。これは非常に便利なソリューションであり、ブラウザの Web ページではすでに推奨されています。しかし残念なことに、このコンセプトは2017年8月に提案されて以来、これまで進展がなく、消滅しつつあるようです。ただし、このトピックをさらに深く掘り下げて、既存のテクノロジを使用して単一ファイル コンポーネントを実装してみることは興味深く、価値があります。

単一ファイルコンポーネント

「プログレッシブエンハンスメント」の概念を知っているフロントエンド開発者は、「レイヤリング」の概念も聞いたことがあるはずです。コンポーネントにもそのような概念があります。実際、すべてのコンポーネントには、コンテンツ/テンプレート、プレゼンテーション、動作という少なくとも 3 つのレイヤー、あるいはそれ以上のレイヤーがあります。あるいは、保守的に考えると、各コンポーネントは少なくとも 3 つのファイルに分割されます。たとえば、ボタン コンポーネントのファイル構造は次のようになります。

ボタン/
|--ボタン.html
|--ボタン.css
|--ボタン.js

このように階層化することは、テクノロジの分離(コンテンツ/テンプレート: HTML を使用、プレゼンテーション: CSS を使用、動作: JavaScript を使用)と同等です。バンドルにビルド ツールが使用されない場合は、ブラウザーがこれらの 3 つのファイルを取得する必要があることを意味します。したがって、この問題を解決するには、テクノロジ(ファイル)を分離せずにコンポーネントコードを分離するテクノロジが緊急に必要です。この記事で取り上げるのは、単一ファイル コンポーネントです。

一般的に、私は「テクノロジーの階層化」には懐疑的です。これは、完全に独立した「テクノロジーの階層化」を回避する方法として、コンポーネントの階層化が放棄されることが多いという事実に由来しています。

話題に戻ると、単一ファイル コンポーネントを使用してボタンを実装すると、次のようになります。

<テンプレート>
  <!-- Button.html の内容をここに記述します。 -->
</テンプレート>

<スタイル>
  /* Button.css の内容をここに記述します。 */
</スタイル>

<スクリプト>
  // Button.js の内容をここに記述します。
</スクリプト>

この単一ファイル コンポーネントは、初期のフロントエンド開発の HTML ドキュメントと非常によく似ていることがわかります。独自のスタイル タグとスクリプト タグがありますが、プレゼンテーション層ではテンプレート タグが使用されます。シンプルなアプローチのおかげで、3 つの個別のファイルを使用しなくても、強力な階層化コンポーネント (コンテンツ/テンプレート: <template>、プレゼンテーション: <style>、動作: <script>) を取得できます。

基本概念

まず、コンポーネントをロードするためのグローバル関数 loadComponent() を作成します。

window.loadComponent = (関数() {
  関数 loadComponent( URL ) {}
  loadComponent を返します。
}());

ここでは JavaScript モジュール パターンが使用されます。必要なヘルパー関数をすべて定義できますが、外部に公開されるのは loadComponent() 関数のみです。もちろん、この関数は現時点では空です。

後で、次のコンテンツを表示する <hello-world> コンポーネントを作成します。

こんにちは、世界!私の名前は<名>です。

さらに、このコンポーネントをクリックすると、次のメッセージがポップアップ表示されます。

触らないでください!

コンポーネント コードは、HelloWorld.wc ファイルとして保存されます (ここで、.wc は WebComponent を表します)。初期コードは次のとおりです。

<テンプレート>
  <div class="hello">
    <p>こんにちは、世界!私の名前は <slot></slot> です。</p>
  </div>
</テンプレート>
<スタイル>
  div {
    背景: 赤;
    境界線の半径: 30px;
    パディング: 20px;
    フォントサイズ: 20px;
    テキスト配置: 中央;
    幅: 300ピクセル;
    マージン: 0 自動;
  }
</スタイル>
<スクリプト></スクリプト>

現在、コンポーネントには動作は追加されておらず、テンプレートとスタイルのみが定義されています。テンプレートでは、<div> などの一般的な HTML タグを使用できます。また、テンプレートには、コンポーネントが Shadow DOM を実装することを示す <slot> 要素が表示されます。また、デフォルトでは、この DOM 自体のすべてのスタイルとテンプレートはこの DOM 内に隠されています。

Web ページでコンポーネントを使用する方法は非常に簡単です。

<hello-world>コマンダー</hello-world>
<script src="loader.js"></script>
<スクリプト>
  コンポーネントをロードします( 'HelloWorld.wc' );
</スクリプト>

コンポーネントは標準のカスタム要素のように使用できます。唯一の違いは、loadComponent() メソッドを使用する前にロードする必要があることです (このメソッドは loader.js に配置されます)。 loadComponent() メソッドは、コンポーネントを取得して customElements.define() に登録するなど、面倒な処理をすべて実行します。

すべての概念を理解したので、実際にやってみることにしましょう。

シンプルなローダー

外部ファイルからファイルを読み込む場合は、ユニバーサル Ajax を使用する必要があります。しかし、今は 2020 年であり、ほとんどのブラウザでは、Fetch API を心配することなく使用できます。

関数loadComponent(URL){
  fetch(URL)を返します。
}

ただし、これはファイルを取得するだけで、処理は何も行いません。次に行うことは、次のように、Ajax の戻りコンテンツをテキストに変換することです。

関数loadComponent(URL){
  return fetch( URL ).then( ( レスポンス ) => {
    応答.text() を返します。
  } );
}

loadComponent() 関数は fetch 関数の実行結果を返すため、Promise オブジェクトになります。 then メソッドで、ファイル (HelloWorld.wc) が実際に読み込まれ、テキストに変換されているかどうかを確認できます。

loadComponent('HelloWorld.wc').then((コンポーネント) => {
    console.log(コンポーネント);
});

結果は次のとおりです。

Chrome ブラウザで console() メソッドを使用すると、HelloWorld.wc の内容がテキストに変換されて出力されていることがわかります。つまり、動作しているようです。

コンポーネントコンテンツの解析

しかし、単にテキストを出力するだけでは目的は達成されません。最終的には、表示のために DOM に変換され、実際にユーザーと対話できるようになります。

ブラウザ環境には、DOM パーサーを作成するために使用できる非常に実用的なクラス DOMParser があります。 DOMParser クラスをインスタンス化してオブジェクトを取得します。このオブジェクトを使用して、コンポーネント テキストを DOM に変換できます。

window.loadComponent = (関数() {
    関数loadComponent(URL) {
        戻り値 fetch(URL).then((レスポンス) => {
            応答.text() を返します。
        }).then((html) => {
            const パーサー = new DOMParser(); // 1
            parser.parseFromString(html, 'text/html'); を返します。 // 2
        });
    }
    loadComponent を返します。
}());

まず、DOMParserインスタンスパーサーが作成され(1)、次にこのインスタンスを使用してコンポーネントのコンテンツをDOMに変換します(2)。ここでは HTML モード ('text/html') が使用されていることに注意してください。コードを JSX 標準または元の Vue.js コンポーネントにより準拠させたい場合は、XML モード ('text/XML') を使用できます。ただし、この場合、コンポーネント自体の構造を変更する必要があります (たとえば、他の要素を保持できるメイン要素を追加するなど)。

これは loadComponent() 関数の戻り結果であり、DOM ツリーです。

Chrome ブラウザでは、console.log() は解析された HelloWorld.wc ファイル (DOM ツリー) を出力します。

parser.parseFromString メソッドは、<html>、<head>、<body> タグ要素をコンポーネントに自動的に追加することに注意してください。これは HTML パーサーの動作によるものです。 DOM ツリーを構築するためのアルゴリズムは、HTML LS 仕様で詳しく説明されています。これは長い記事なので、読むのに時間がかかりますが、簡単に理解すると、パーサーは、<body> タグ内にのみ配置できる DOM 要素に遭遇するまで、デフォルトですべてのコンテンツを <head> 要素に配置するということです。したがって、コンポーネント コード内のすべての要素 (<element>、<style>、<script>) を <head> 内に配置できます。 <p> 要素を <template> 内にラップすると、パーサーはそれを <body> 内に配置します。

もう一つ問題があります。コンポーネントを解析した後、<!DOCTYPE html> 宣言がないので、これは異常な HTML ドキュメントとなり、ブラウザは Quirks モードと呼ばれる方法を使用してこの HTML ドキュメントをレンダリングします。幸いなことに、DOM パーサーはコンポーネントを適切な部分に分割するためにのみ使用されるため、ここでは悪影響はありません。

DOM ツリーを取得したら、必要な部分だけを抽出できます。

戻り値 fetch(URL).then((レスポンス) => {
    応答.text() を返します。
}).then((html) => {
    定数パーサー = 新しい DOMParser();
    定数ドキュメント = parser.parseFromString(html, 'text/html');
    ドキュメントのhead要素をコピーします。
    const テンプレート = head.querySelector('テンプレート');
    定数スタイル = head.querySelector('style');
    定数script = head.querySelector('script');

    戻る {
        テンプレート、
        スタイル、
        スクリプト
    };
});

最後にコードを整理してみましょう。loadComponent メソッドは以下のとおりです。

window.loadComponent = (関数() {
    関数 fetchAndParse(URL) {
        戻り値 fetch(URL).then((レスポンス) => {
            応答.text() を返します。
        }).then((html) => {
            定数パーサー = 新しい DOMParser();
            定数ドキュメント = parser.parseFromString(html, 'text/html');
            ドキュメントのhead要素をコピーします。
            const テンプレート = head.querySelector('テンプレート');
            定数スタイル = head.querySelector('style');
            定数script = head.querySelector('script');

            戻る {
                テンプレート、
                スタイル、
                スクリプト
            };
        });
    }

    関数loadComponent(URL) {
        fetchAndParse(URL)を返します。
    }

    loadComponent を返します。
}());

FetchAPI は、外部ファイルからコンポーネント コードを取得する唯一の方法ではありません。XMLHttpRequest には、解析手順全体を省略できる専用のドキュメント モードがあります。しかし、XMLHttpRequest は Promise を返さないので、自分でラップする必要があります。

コンポーネントの登録

コンポーネント レイヤーができたので、新しいカスタム コンポーネントを登録するための registerComponent() メソッドを作成できます。

window.loadComponent = (関数() {
    関数 fetchAndParse(URL) {
        […]
    }
    関数registerComponent() {
    }
    関数loadComponent(URL) {
        fetchAndParse(URL) を返します。次に、registerComponent を返します。
    }
    loadComponent を返します。
}());

カスタム コンポーネントは HTMLElement から継承するクラスである必要があることに注意してください。さらに、各コンポーネントはスタイルとテンプレート コンテンツを保存するために Shadow DOM を使用します。したがって、このコンポーネントが参照されるたびに、同じスタイルになります。方法は次のとおりです。

関数registerComponent({テンプレート、スタイル、スクリプト}) {
    クラスUnityComponentはHTMLElementを拡張します{
        接続されたコールバック() {
            this._upcast();
        }

        _アップキャスト() {
            const shadow = this.attachShadow({mode: 'open'});
            shadow.appendChild(style.cloneNode(true));
            shadow.appendChild(document.importNode(template.content, true));
        }
    }
}

UnityComponent クラスは、registerComponent() に渡されたパラメータを使用するため、registerComponent() メソッド内に作成する必要があります。このクラスは、Shadow DOM に関するこの記事 (ポーランド語) で詳しく説明している、わずかに変更されたメカニズムを使用して Shadow DOM を実装します。

これで、コンポーネントを登録するために残っているのは、単一ファイル コンポーネントに名前を付けて、現在のページの DOM に追加するだけです。

関数registerComponent({テンプレート、スタイル、スクリプト}) {
  クラスUnityComponentはHTMLElementを拡張します{
    [...]
  }

  customElements.define( 'hello-world', UnityComponent ) を返します。
}

次のように開いて確認してみましょう。

Chrome では、このボタン コンポーネントには、「Hello, world! My name is Comandeer」というテキストが入った赤い四角形が表示されます。

スクリプトコンテンツを取得する

これで、シンプルなボタン コンポーネントが実装されました。ここで、動作レイヤーを追加し、ボタン内のコンテンツをカスタマイズするという難しい部分がやってきます。上記の手順では、コンポーネント コード内のボタンにテキスト コンテンツをハードコーディングするのではなく、ボタンに渡されたコンテンツを使用する必要があります。同様に、コンポーネント内にバインドされたイベント リスナーも処理する必要があります。ここでは、次のように Vue.js に似た規則を使用します。

<テンプレート>
  […]
</テンプレート>

<スタイル>
  […]
</スタイル>

<スクリプト>
  エクスポートデフォルト{ // 1
    名前: 'hello-world', // 2
    onClick() { // 3
      警告(`私に触れないでください!`);
    }
  }
</スクリプト>

コンポーネント内の <script> タグ内のコンテンツは、コンテンツをエクスポートする JavaScript モジュールであると想定できます (1)。モジュールによってエクスポートされたオブジェクトには、コンポーネントの名前 (2) と「on..」で始まるイベント リスナー メソッド (3) が含まれています。

これは見た目がすっきりしており、モジュールの外部には何も公開されません (モジュールは JavaScript のグローバル スコープ内にないため)。ここで問題があります。内部モジュールからエクスポートされたオブジェクト (HTML ドキュメントで直接定義されたオブジェクト) を処理するための標準が存在しないのです。 import ステートメントは、モジュール識別子が取得され、この識別子に従ってインポートされることを前提としています。最も一般的なのは、コードを含むファイルへの URL パスです。コンポーネントは js ファイルではないため、このような識別子はありません。内部モジュールにはこのような識別子はありません。

降伏する前に、使える超汚いハックがあります。ブラウザがテキストをファイルのように扱うには、少なくとも 2 つの方法があります。それは、DataURI と ObjectURI です。 ServiceWorker を使用するという提案もいくつかあります。しかし、ここでは少しやり過ぎのようです。

DataURI と ObjectURI

DataURI は古くて原始的な方法です。これは、ファイルの内容を URL に変換し、不要なスペースを削除し、すべてを Base64 を使用してエンコードすることに基づいています。次の内容の JavaScript ファイルがあるとします。

エクスポートのデフォルトは true です。

次のように DataURI に変換されます。

データ:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQgdHJ1ZTs=

ファイルをインポートするのと同じように、この URI をインポートできます。

'data:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQgdHJ1ZTs=' からテストをインポートします。
console.log( テスト );

DataURI アプローチの明らかな欠点は、JavaScript ファイルのコンテンツが増えるにつれて、URL の長さが非常に長くなることです。また、バイナリ データを DataURI に入れるのは非常に困難です。

つまり、新しい種類の ObjectURI が誕生したのです。これは、File API や HTML5 の <video> タグおよび <audio> タグなど、いくつかの標準から派生したものです。 ObjectURI の目的は単純で、現在のコンテキストで一意の URI を指定して、指定されたバイナリ データから「疑似ファイル」を作成することです。簡単に言えば、メモリ内に一意の名前を持つファイルを作成します。 ObjectURI には、DataURI (「ファイル」を作成する方法) の利点がすべてありますが、欠点はありません (ファイルが 100 MB でも問題ありません)。

オブジェクト URI は通常、マルチメディア ストリーム (<video> または <audio> コンテキストなど) から、または入力 [type=file] およびドラッグ アンド ドロップ メカニズムを介して送信されたファイルから作成されます。 File クラスと Blob クラスを使用して手動で作成することもできます。この例では、Bolb を使用して最初にコンテンツをモジュールに配置し、次にそれを ObjectURI に変換します。

const myJSFile = new Blob( [ 'export default true;' ], { type: 'application/javascript' } );
const myJSURL = URL.createObjectURL( myJSFile );

console.log( myJSURL ); // ブロブ:https://blog.comandeer.pl/8e8fbd73-5505-470d-a797-dfb06ca71333

動的インポート

ただし、問題が 1 つあります。import ステートメントは、モジュール識別子として変数を受け入れません。つまり、この方法を使用してモジュールを「ファイル」に変換する以外に、それをインポートする方法はありません。まだ解決策はないのでしょうか?

必ずしもそうではありません。この問題はずっと以前から提起されており、動的インポート メカニズムを使用することで解決できます。これは ES2020 標準の一部であり、Firefox、Safari、Node.js 13.x に実装されています。動的にインポートされるモジュールの識別子として変数を使用することは、もはや問題ではありません。

const myJSFile = new Blob( [ 'export default true;' ], { type: 'application/javascript' } );
const myJSURL = URL.createObjectURL( myJSFile );

import( myJSURL ).then( ( モジュール ) => {
  console.log( module.default ); // true
});

上記のコードからわかるように、import() コマンドはメソッドのように使用できます。Promise オブジェクトを返し、then メソッドでモジュール オブジェクトが取得されます。デフォルト属性には、モジュールで定義されているすべてのエクスポート オブジェクトが含まれます。

成し遂げる

アイデアができたので、それを実装し始めることができます。ツール メソッド getSetting() を追加します。スクリプト コードからすべての情報を取得するには、registerComponents() メソッドの前に呼び出します。

関数 getSettings({ テンプレート、スタイル、スクリプト }) {
  戻る {
    テンプレート、
    スタイル、
    スクリプト
  };
}
[...]
関数loadComponent(URL){
  fetchAndParse( URL ).then( getSettings ).then( registerComponent ) を返します。
}

このメソッドは渡されたすべてのパラメータを返します。上記のロジックに従って、スクリプト コードを ObjectURI に変換します。

const jsFile = new Blob( [ script.textContent ], { type: 'application/javascript' } );
const jsURL = URL.createObjectURL( jsFile );

次に、import を使用してモジュールをロードし、テンプレート、スタイル、コンポーネントの名前を返します。

import(jsURL).then((モジュール) => { を返します。
  戻る {
    名前: module.default.name、
    テンプレート、
    スタイル
  }
} );

このため、registerComponent() は依然として 3 つの引数を取得しますが、スクリプトの代わりに名前を取得するようになりました。正しいコードは次のとおりです。

関数registerComponent({テンプレート、スタイル、名前}) {
  クラスUnityComponentはHTMLElementを拡張します{
    [...]
  }

  customElements.define( 名前、 UnityComponent ) を返します。
}

行動層

コンポーネントには、イベントを処理するために使用される動作レイヤーという最後のレイヤーが残っています。ここで、getSettings() メソッドでコンポーネントの名前を取得するだけでなく、イベント リスナーも取得する必要があります。 Object.entrie() メソッドを使用して取得できます。 getSettings() メソッドに適切なコードを追加します。

関数 getSettings({ テンプレート、スタイル、スクリプト }) {
  [...]

  関数 getListeners( 設定 ) { // 1
    定数リスナー = {};

    Object.entries( 設定 ).forEach( ( [ 設定, 値 ] ) => { // 3
      if (setting.startsWith('on')) { // 4
        リスナー[setting[2].toLowerCase() + setting.substr(3)] = 値; // 5
      }
    } );

    リスナーを返します。
  }

  import(jsURL).then((モジュール) => { を返します。
    const listeners = getListeners( module.default ); // 2

    戻る {
      名前: module.default.name、
      リスナー、// 6
      テンプレート、
      スタイル
    }
  } );
}

ここで、方法は少し複雑になります。モジュールの出力をこのパラメータに渡す新しい関数 getListeners() (1) が追加されました。

次に、Object.entries() (3) メソッドを使用して、エクスポートされたモジュールを反復処理します。現在の属性が「on」で始まる場合(4)、それはリスニング関数であることを意味します。このノード(リスニング関数)の値をリスナーオブジェクトに追加し、setting[2].toLowerCase()+setting.substr(3)(5)を使用してキー値を取得します。

キー値は、先頭の「on」を削除し、次の「Click」の最初の文字を小文字に変換することによって形成されます (つまり、onClick からキー値として click を取得します)。次に、istenersオブジェクト(6)を渡します。

次のように、[].forEach() メソッドの代わりに [].reduce() メソッドを使用すると、リスナー変数を省略できます。

関数 getListeners(設定) {
  Object.entries(設定).reduce((リスナー、[設定、値])を返す=> {
    if ( 設定が 'on' の場合 ) {
      リスナー[setting[2].toLowerCase() + setting.substr(3)] = 値;
    }

    リスナーを返します。
  }, {} );
}

これで、コンポーネント内のクラスにリスナーをバインドできます。

関数registerComponent({テンプレート、スタイル、名前、リスナー}) { // 1
  クラスUnityComponentはHTMLElementを拡張します{
    接続されたコールバック() {
      this._upcast();
      this._attachListeners(); // 2
    }
    [...]
    _attachListeners() {
      Object.entries( リスナー ).forEach( ( [ イベント, リスナー ] ) => { // 3
        this.addEventListener( イベント、リスナー、false ); // 4
      } );
    }
  }
  customElements.define( 名前、 UnityComponent ) を返します。
}

リスナーメソッド(1)にパラメータが追加され、クラス(2)に新しいメソッド_attachListeners()が追加されます。ここで、Object.entries() を再度使用してリスナー (3) を反復処理し、それらを要素 (4) にバインドできます。

最後に、コンポーネントをクリックすると、以下に示すように「Don't touch me!」というポップアップが表示されます。

互換性の問題やその他の問題

ご覧のとおり、この単一ファイル コンポーネントを実装するには、基本的なフォームをサポートする方法を中心に作業が行われます。多くの部分でダーティ ハックが使用されています (ObjectURI を使用して ES でモジュールをロードしますが、これはブラウザーのサポートがなければ意味がありません)。幸いなことに、これらのテクニックはすべて、Chrome、Firefox、Safari などの主要なブラウザで問題なく動作します。

それでも、多くのブラウザ テクノロジと最新の Web 標準を扱えるこのようなプロジェクトを作成するのは楽しかったです。

上記は、JS が単一ファイル コンポーネントを実装する方法の詳細です。JS が単一ファイル コンポーネントを実装する方法の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • vue.js の単一ファイルコンポーネントで非親コンポーネントと子コンポーネント間で値を渡す例
  • Vuejs 単一ファイルコンポーネントの例の詳細な説明
  • WebStorm の Vue.js 単一ファイル コンポーネントにハイライトと構文サポートを追加する方法の詳細な説明
  • vuejs 単一ファイルコンポーネント.vue ファイルの使用
  • Vuejs でスタイル付きの単一ファイルコンポーネントを実装する新しい方法
  • Vue.js デスクトップ カスタム スクロール バー コンポーネント 美化 スクロール バー VScroll
  • ドラッグエフェクトコンポーネント機能を実装するJavaScript(モバイル端末対応)
  • Vue.js 3.0 コンポーネントが DOM にレンダリングされる方法の詳細な説明
  • Vue.jsはスイッチコンポーネントの操作をカプセル化します

<<:  Linux でユーザー アカウントをロックおよびロック解除する 3 つの方法

>>:  MySql で SQL 実行プランをクエリするために explain を使用する方法

推薦する

MySQL データベースにおける高同時実行性の問題を解決する方法

序文スタートアップ企業が最初はモノリシック アプリケーションを主要なアーキテクチャとして使用し、通常...

MySQL データベース操作 (作成、選択、削除)

MySQL データベースの作成MySQL サービスにログインしたら、create コマンドを使用し...

Vueを使い始める際に習得する必要がある知識について簡単に説明します

最も人気のあるフロントエンド フレームワークの 1 つとして、Vue は多くのフロントエンド開発エン...

Linux コマンドライン操作 Baidu クラウドのファイルのアップロードとダウンロード

目次0. 背景1. インストール2. Baidu Cloudアカウントにログインする3. ファイルを...

エラー 1045 (28000): ユーザー ''root''@''localhost'' のアクセスが拒否されました (パスワード使用: YES) 実用的な解決策

昨日はデータベースへの接続に問題はありませんでしたが、今日はデータベースへの接続時にこのエラーが報告...

ユーザー エクスペリエンス デザイナーとは誰ですか?

怖いですね! 写真の翻訳: (内側から外側へ)最初のレイヤー:ユーザーエクスペリエンス第2層:コンテ...

React Router 5.1.0 はページジャンプナビゲーションを実装するために useHistory を使用します

目次1. withRouterコンポーネントを使用する2. ルートタグを使用するReactRoute...

HTML で margin:0 auto を使用するとページ全体が中央に配置されない問題の解決方法

今日、jsp ページを書きました。<div style="margin:0 auto...

Kali Linux インストール VMware ツールのインストール プロセスと VM インストール vmtools ボタン グレー

Xiaobai は vmtools のインストールを記録します。 1. 意義と機能: VMWARE ...

ウェブページの読み込み速度を上げる25の方法とヒント

はじめに<br />誰もが高速インターネット接続にアクセスできるわけではありません。たと...

ウェブ開発で遭遇した問題と経験

<br />以下は開発中に遭遇した問題と、そこから得た経験です。デバッグに時間がかかりま...

ウェブサイトでページコンテンツや情報を直接コピーできない問題を解決する方法

最近では、多くのウェブサイトでは、ページ上の特定のコンテンツや情報を直接コピーすることは許可されてお...

SQL文の最適化の一般的な手順の詳細な説明

序文この記事では主に、SQL ステートメントの最適化の一般的な手順について説明します。これは、参考と...

IE8 開発者ツール メニューの説明

<br />この記事では、開発者ツールのさまざまなメニューについて簡単に説明しました。こ...

sqlite3 から mysql に移行するときに起こりうる問題のコレクション

簡単な説明適切な読者: モバイル開発sqlite3 データを mysql に移行する場合、多くの構文...