ホームページ >ウェブフロントエンド >CSSチュートリアル >動作する相互運用可能なWebコンポーネントを構築します

動作する相互運用可能なWebコンポーネントを構築します

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌オリジナル
2025-03-13 10:05:10472ブラウズ

動作する相互運用可能なWebコンポーネントを構築します

数年以上Web開発者である私たちは、おそらく複数のJavaScriptフレームワークを使用してコードを作成しているでしょう。すべての選択肢があります - 反応、ヴェルテ、vue、角度、固体 - それはほとんど避けられません。フレームワーク全体で作業する際に対処しなければならないよりイライラすることの1つは、これらすべての低レベルのUIコンポーネントを再作成することです。ボタン、タブ、ドロップダウンなどです。特にイライラするのは、通常、1つのフレームワークで定義され、反応するが、Svelteで何かを構築したい場合は書き直す必要があることです。またはvue。または固体。等々。

これらの低レベルのUIコンポーネントを1回、フレームワークに依存しない方法で定義してから、フレームワーク間でそれらを再利用できれば、それ以上の方が良いでしょうか?もちろんそうだ!そして、私たちはできます。 Webコンポーネントが方法です。この投稿はあなたにその方法を示します。

現在のところ、WebコンポーネントのSSRストーリーには少し欠けています。宣言的なシャドウDOM(DSD)は、Webコンポーネントのサーバー側のレンダリング方法ですが、この執筆時点では、Next、RemixやSveltekitなどのお気に入りのアプリケーションフレームワークと統合されていません。それがあなたの要件である場合は、DSDの最新のステータスを必ず確認してください。しかし、それ以外の場合、SSRが使用しているものでない場合は、読んでください。

まず、いくつかのコンテキスト

Webコンポーネントは、基本的になどのように自分自身を定義するHTML要素です。ここでCSS-Tricks(Caleb Williamsによる広範なシリーズ、John Rheaによるものを含む)でカバーされていますが、プロセスを簡単に説明します。基本的に、JavaScriptクラスを定義し、HTMLelementから継承し、Webコンポーネントが持っているプロパティ、属性、スタイル、そしてもちろん、最終的にユーザーにレンダリングするマークアップを定義します。

特定のコンポーネントにバインドされていないカスタムHTML要素を定義できることはエキサイティングです。しかし、この自由も制限です。 JavaScriptフレームワークとは独立して存在することは、これらのJavaScriptフレームワークと実際に対話できないことを意味します。いくつかのデータを取得し、他のReactコンポーネントをレンダリングしてデータを渡すReactコンポーネントを考えてください。 WebコンポーネントはReactコンポーネントをレンダリングする方法がわからないため、これは実際にはWebコンポーネントとして機能しません。

Webコンポーネントは、特にリーフコンポーネントとして優れています。葉のコンポーネントは、コンポーネントツリーでレンダリングされる最後のものです。これらは、いくつかの小道具を受け取り、いくつかのUIをレンダリングするコンポーネントです。これらは、コンポーネントツリーの中央に座っているコンポーネントではなく、データを渡す、コンテキストを設定するなどです。同じように見えるUIの純粋なピースだけでなく、どのJavaScriptフレームワークがアプリの残りの部分に電力を供給していますか。

私たちが構築しているWebコンポーネント

ボタンのように退屈な(そして一般的な)何かを構築するのではなく、少し違うものを作りましょう。私の最後の投稿では、ぼやけた画像プレビューを使用してコンテンツリフローを防ぎ、画像のロード中にユーザーに適切なUIを提供することを検討しました。画像のぼやけた劣化したバージョンをエンコードするBase64を見て、実際の画像がロードされている間、UIでそれを示しています。また、Blurhashというツールを使用して、信じられないほどコンパクトでぼやけたプレビューを生成することを検討しました。

その投稿は、これらのプレビューを生成し、Reactプロジェクトで使用する方法を示しました。この投稿では、これらのプレビューをWebコンポーネントから使用する方法を示し、JavaScriptフレームワークで使用できるようにします。

しかし、走る前に歩く必要があるので、最初に些細で愚かなことを歩いて、Webコンポーネントがどのように機能するかを正確に確認します。

この投稿のすべてが、ツールなしでバニラのWebコンポーネントを構築します。つまり、コードには少しのボイラープレートがありますが、比較的簡単に従うことができます。 LITやステンシルなどのツールは、Webコンポーネントを構築するために設計されており、このボイラープレートの多くを除去するために使用できます。チェックアウトすることをお勧めします!しかし、この投稿では、別の依存関係を導入して教える必要がないことと引き換えに、もう少し融合することを好みます。

シンプルなカウンターコンポーネント

JavaScriptコンポーネントの古典的な「Hello World」を作成しましょう:カウンター。値をレンダリングし、その値を増加させるボタンをレンダリングします。シンプルで退屈ですが、可能な限り単純なWebコンポーネントを見てみましょう。

Webコンポーネントを構築するための最初のステップは、htmlelementから継承するJavaScriptクラスを作成することです。

クラスカウンターはhtmlelementを拡張します{}

最後のステップは、Webコンポーネントを登録することですが、既に登録していない場合のみです。

 if(!customelements.get( "counter-wc")){
  customelements.define( "counter-wc"、counter);
}

そして、もちろん、それをレンダリングしてください:

 <counter-wc> </counter-wc>

そして、その間のすべては、Webコンポーネントに私たちが望むことを何でもするようにすることです。 1つの一般的なライフサイクル方法は、ConnectedCallbackです。これは、WebコンポーネントがDOMに追加されると発射されます。その方法を使用して、希望するコンテンツをレンダリングできます。これは、HTMLelementから継承するJSクラスであることを忘れないでください。つまり、この値はWebコンポーネント要素自体であり、すでに知っていて愛しているすべての通常のDOM操作方法を備えています。

最も簡単ですが、これを行うことができます。

クラスカウンターはhtmlelementを拡張します{
  connectedcallback(){
    this.innerhtml = "<div style="'color:green'"> hey </div>";
  }
}

if(!customelements.get( "counter-wc")){
  customelements.define( "counter-wc"、counter);
}

…これはうまく機能します。

実際のコンテンツを追加します

便利でインタラクティブなコンテンツを追加しましょう。カウンターをインクリメントするには、現在の数値を保持するにはとが必要です。とりあえず、コンストラクターにこのコンテンツを作成し、Webコンポーネントが実際にDOMにあるときに追加します。

 constructor(){
  素晴らしい();
  const container = document.createelement( 'div');

  this.valspan = document.createelement( 'span');

  const increment = document.createelement( 'button');
  increment.innertext = 'increment';
  increment.addeventlistener( 'click'、()=> {
    this。#value = this。#currentValue 1;
  });

  container.appendChild(this.valspan);
  container.appendChild(document.createElement( 'br'));
  container.appendChild(increment);

  this.container = container;
}

connectedcallback(){
  this.appendChild(this.container);
  this.update();
}

手動DOMの作成によって本当にグロスアウトされている場合は、innerHTMLを設定したり、Webコンポーネントクラスの静的プロパティとしてテンプレート要素を1回作成したり、新しいWebコンポーネントインスタンスのコンテンツを挿入したりすることもできます。おそらく私が考えていない他のいくつかのオプションがあるか、LitやStencilなどのWebコンポーネントフレームワークをいつでも使用できます。しかし、この投稿では、引き続きシンプルに保ちます。

次に進むには、valueという名前の設定可能なJavaScriptクラスプロパティが必要です

#currentValue = 0;

set #value(val){
  this。#currentValue = val;
  this.update();
}

これは、セッターを備えた標準クラスのプロパティと、値を保持するための2番目のプロパティです。楽しいひねりの1つは、これらの値にプライベートJavaScriptクラスプロパティ構文を使用していることです。つまり、私たちのWebコンポーネントの外の誰もこれらの値に触れることはできません。これは、すべての最新のブラウザでサポートされている標準のJavaScriptなので、使用することを恐れないでください。

または、必要に応じて_valueと呼んでください。そして、最後に、更新方法:

アップデート() {
  this.valspan.innertext = this。#currentValue;
}

それは動作します!

明らかに、これは大規模に維持したいコードではありません。よく見たい場合は、完全な作業例です。私が言ったように、LITやステンシルのようなツールは、これをより簡単にするように設計されています。

いくつかの機能を追加します

この投稿は、Webコンポーネントに深く掘り下げるものではありません。すべてのAPIとライフサイクルをカバーするわけではありません。影の根もスロットもカバーしません。これらのトピックには無限のコンテンツがあります。ここでの私の目標は、あなたがすでに知っていて愛している人気のあるJavaScriptフレームワークを使用して、実際にWebコンポーネントを使用するという有用なガイダンスとともに、いくつかの関心を刺激するための十分な紹介を提供することです。

そのために、カウンターWebコンポーネントを少し強化しましょう。表示されている値の色を制御するために、色の属性を受け入れましょう。また、このWebコンポーネントの消費者が一度に2、3、4を増やすことができるように、増分プロパティを受け入れさせてください。そして、これらの状態の変更を促進するために、私たちの新しいカウンターをSvelte Sandboxで使用しましょう。少し反応することができます。

以前と同じWebコンポーネントから始めて、Color属性を追加します。属性を受け入れて応答するようにWebコンポーネントを構成するために、Webコンポーネントが耳を傾ける属性を返す静的観測されたAttributesプロパティを追加します。

 static oversedattributes = ["color"];

それを導入すると、属性ChangedCallbackライフサイクルメソッドを追加できます。これは、観察された属性にリストされている属性が設定または更新されるたびに実行されます。

 astibuteChangedCallback(name、oldvalue、newValue){
  if(name === "color"){
    this.update();
  }
}

次に、実際に使用するように更新方法を更新します。

アップデート() {
  this.valspan.innertext = this._currentValue;
  this.valspan.style.color = this.getattribute( "color")|| "黒";
}

最後に、増分プロパティを追加しましょう。

 Increment = 1;

シンプルで謙虚です。

Svelteのカウンターコンポーネントを使用します

私たちが作ったものを使ってみましょう。 Svelteアプリコンポーネントに移動して、次のようなものを追加します。

 
  color = "red";


<style>
  主要 {
    テキストアライグ:センター;
  }
</style>

<main>
  <bind>を選択します
     red 
     green 
     blue 
  

  <counter-wc color="{color}"> </counter-wc>
</bind></main>

そして、それは機能します!カウンターレンダリング、増分、ドロップダウンが色を更新します。ご覧のとおり、Svelteテンプレートで色属性をレンダリングし、値が変化すると、Svelteが基礎となるWebコンポーネントインスタンスでSetAttributeを呼び出すレッグワークを処理します。ここには特別なことは何もありません。これは、HTML要素の属性に対してすでに行っているのと同じことです。

増分プロップで物事は少し面白くなります。これは、Webコンポーネントの属性ではありません。これは、Webコンポーネントのクラスの小道具です。つまり、Webコンポーネントのインスタンスで設定する必要があります。物事が少し単純になるので、私と一緒に我慢してください。

まず、いくつかの変数をSvelteコンポーネントに追加します。

 Increment = 1;
wcinstanceとしましょう。

カウンターコンポーネントの大国では、1または2の増加を可能にします。

  increment = 1}> increment 1 
> increment = 2}> increment 2 

しかし、理論的には、Webコンポーネントの実際のインスタンスを取得する必要があります。これは、Reactを使用してREFを追加するときはいつでも常に同じことです。 Svelteを使用すると、それは単純なバインドです:この指令:

 <counter-wc bind color="{color}"> </counter-wc>

これで、Svelteテンプレートでは、コンポーネントの増分変数の変更を聴き、基礎となるWebコンポーネントプロパティを設定します。

 $:{
  if(wcinstance){
    wcinstance.increment = increment;
  }
}

このライブデモでテストできます。

明らかに、管理する必要があるすべてのWebコンポーネントまたはプロップに対してこれを行いたくありません。通常、コンポーネントの小道具のために行うように、マークアップでWebコンポーネントに到達を正しく設定できればいいのはないでしょうか。言い換えれば、wcinstanceのすべての使用法を削除して、代わりにこのより単純なコードを使用することができれば、それは素晴らしいことです。

 <counter-wc increment="{increment}" color="{color}"> </counter-wc>

できることがわかりました。このコードは機能します。 Svelteは私たちのためにそのすべてのレッグワークを処理します。このデモでそれをチェックしてください。これは、ほとんどすべてのJavaScriptフレームワークの標準動作です。

では、なぜWebコンポーネントの小道具を設定する手動の方法を紹介したのですか? 2つの理由は、これらのことがどのように機能するかを理解することが有用であり、しばらく前に、これはすべてのJavaScriptフレームワークで機能すると言いました。しかし、私たちが見たように、狂ったように、Webコンポーネントのプロップ設定をサポートしていない1つのフレームワークがあります。

反応は別の獣です

反応します。地球上で最も人気のあるJavaScriptフレームワークは、Webコンポーネントとの基本的な相互作用をサポートしていません。これは、反応に固有の有名な問題です。興味深いことに、これは実際にはReactの実験分岐で固定されていますが、何らかの理由でバージョン18に統合されていませんでした。そして、あなたはこれをライブデモで自分で試すことができます。

もちろん、ソリューションはREFを使用し、Webコンポーネントインスタンスをつかみ、その値が変更されたときに手動で増分を設定することです。このように見えます:

 react、{usestate、useref、useefect} 'race';
Import './Counter-wc';

デフォルトのfunction app()をエクスポート{{
  const [increment、setincrement] = uesestate(1);
  const [color、setcolor] = uesestate( 'red');
  const wcref = useref(null);

  effect(()=> {
    wcref.current.increment = increment;
  }、[増分]);

  戻る (
    <div>
      <div classname="increment-container">
        <button onclick="{()="> setIncrement(1)}> 1 </button>の増分
        <button onclick="{()="> setIncrement(2)}>増分は2 </button>
      </div>

      <select value="{color}" onchange="{(e)="> setColor(e.Target.Value)}>
         red 
         green 
         blue 
      </select>

      <counter-wc ref="{wcref}" increment="{increment}" color="{color}"> </counter-wc>
    </div>
  );
}
ライブデモ

議論したように、すべてのWebコンポーネントプロパティに対してこれを手動でコーディングすることは、単にスケーラブルではありません。しかし、いくつかのオプションがあるため、すべてが失われていません。

オプション1:どこでも属性を使用します

属性があります。上記のReactデモをクリックした場合、増分プロップは機能しませんでしたが、色は正しく変わりました。すべてを属性でコーディングできませんか?悲しいことに、いいえ。属性値は文字列のみです。ここでは十分であり、このアプローチでやや遠くに行くことができます。インクリメントのような数値は、文字列に変換できます。 json stringify/parseオブジェクトもできます。しかし、最終的には関数をWebコンポーネントに渡す必要があり、その時点でオプションから外れます。

オプション2:ラップします

間接レベルのレベルを追加することでコンピューターサイエンスの問題を解決できるという古いことわざがあります(間接的なレベルが多すぎるという問題を除く)。これらの小道具を設定するコードは、かなり予測可能でシンプルです。ライブラリに隠した場合はどうなりますか? Litの背後にある賢い人々には、1つの解決策があります。このライブラリは、Webコンポーネントを提供し、必要なプロパティをリストした後、新しいReactコンポーネントを作成します。賢い間、私はこのアプローチのファンではありません。

手動で作成されたReactコンポーネントにWebコンポーネントを1対1のマッピングするのではなく、私が好むのは、すべての属性とプロパティとともに、Webコンポーネントのタグ名を(カウンターWC)に渡す1つのReactコンポーネントだけです。それが私の意見では理想的な解決策です。私はこれを行うライブラリを知りませんが、作成するのは簡単なはずです。試してみましょう!

これは私たちが探している使用法です:

 <wcwrapper wctag="counter-wc" increment="{increment}" color="{color}"></wcwrapper>

WCTAGは、Webコンポーネントのタグ名です。残りは、私たちが伝えたいプロパティと属性です。

これが私の実装がどのように見えるかです:

 react、{createElement、useref、uselayouteffect、memo} from 'React';

const _wcwrapper =(props)=> {
  const {wctag、children、... restprops} = props;
  const wcref = useref(null);

  uselayouteffect(()=> {
    const wc = wcref.current;

    for(const [key、value] of object.entries(restprops)){
      if(WCのキー){
        if(wc [key]!== value){
          wc [key] = value;
        }
      } それ以外 {
        if(wc.getattribute(key)!== value){
          wc.setattribute(key、value);
        }
      }
    }
  });

  return createElement(wctag、{ref:wcref});
};

const wcwrapper = memo(_wcwrapper);

最も興味深いラインは最後です:

 return createElement(wctag、{ref:wcref});

これが、動的名でReactの要素を作成する方法です。実際、これは通常、JSXに反応するものです。すべてのDIVは、CreateElement(「Div」)コールに変換されます。通常、このAPIを直接呼び出す必要はありませんが、必要なときにそこにあります。

それを超えて、レイアウト効果を実行し、コンポーネントに渡したすべてのプロップをループしたいと考えています。それらすべてをループし、Webコンポーネントインスタンスオブジェクトとそのプロトタイプチェーンをチェックするチェックがあるプロパティであるかどうかを確認します。そのようなプロパティが存在しない場合、それは属性であると想定されています。どちらの場合でも、値が実際に変更された場合にのみ設定します。

Effectの代わりにUselayouteffectを使用する理由を疑問に思っている場合は、コンテンツがレンダリングされる前にこれらの更新をすぐに実行したいからです。また、uselayouteffectに依存関係配列がないことに注意してください。これは、すべてのレンダリングでこの更新を実行したいことを意味します。 Reactは非常に多くを再レンダリングする傾向があるため、これは危険になります。 React.memoで全体を包むことでこれを改善します。これは本質的にReact.purecomponentの最新バージョンです。つまり、コンポーネントは実際の小道具のいずれかが変更された場合にのみ再レンダリングされます。また、単純な平等チェックを介して発生したかどうかを確認します。

ここでの唯一のリスクは、再割り当てなしで直接変異しているオブジェクトプロップを渡す場合、更新が表示されないことです。しかし、これは特にReactコミュニティでは非常に落胆しているので、私はそれについて心配しません。

先に進む前に、最後のことを呼びたいと思います。使用状況に満足していないかもしれません。繰り返しますが、このコンポーネントは次のように使用されます。

 <wcwrapper wctag="counter-wc" increment="{increment}" color="{color}"></wcwrapper>

具体的には、Webコンポーネントのタグ名をコンポーネントに渡すのが好きではなく、代わりに上記の @lit-labs/Reactパッケージを好むかもしれません。それは完全に公平であり、私はあなたがあなたが最も快適に何でも使用することをお勧めします。しかし、私にとっては、このアプローチの利点の1つは、削除が簡単であることです。いくつかの奇跡の反応により、適切なWebコンポーネントが実験的なブランチからメインの明日に取り付けてマージした場合、上記のコードをこれから変更できるでしょう。

 <wcwrapper wctag="counter-wc" increment="{increment}" color="{color}"></wcwrapper>

…これに:

 <counter-wc ref="{wcref}" increment="{increment}" color="{color}"></counter-wc>

おそらくどこでもそれを行うために単一のcodeModを書いてから、を完全に削除することもできます。実際、それをスクラッチしてください:グローバルな検索と正規表現に置き換えると、おそらく機能します。

実装

私は知っている、それはここに来るのに旅が必要だったようです。思い出した場合、私たちの元の目標は、前回の投稿で見た画像プレビューコードを取得し、JavaScriptフレームワークで使用できるようにWebコンポーネントに移動することでした。 Reactの適切な相互作用の欠如は、ミックスに多くの詳細を追加しました。しかし、Webコンポーネントを作成する方法について適切なハンドルがあり、それを使用しているため、実装はほぼ反クライマックスになります。

ここにWebコンポーネント全体をドロップして、興味深いビットのいくつかを呼び出します。動作しているのを見たい場合は、これが実用的なデモです。私の3つのお気に入りのプログラミング言語に関する3つのお気に入りの本を切り替えます。各本のURLは毎回ユニークになるので、プレビューを見ることができますが、DevToolsネットワークタブに物事をスロットルして、実際に物事が起こっているのを見ることができます。

コード全体を表示します
クラスBookCoverはhtmlelementを拡張します{
  静的観測されたaTtributes = ['url'];

  astibuteChangedCallback(name、oldvalue、newValue){
    if(name === 'url'){
      this.createmainimage(newValue);
    }
  }

  Preview(val)を設定{
    this.previewel = this.createpreview(val);
    this.render();
  }

  createpreview(val){
    if(typeof val === 'string'){
      base64preview(val)を返します。
    } それ以外 {
      return blurhashpreview(val);
    }
  }

  createmainimage(url){
    this.loaded = false;
    const img = document.createelement( 'img');
    img.alt = 'Book Cover';
    img.addeventlistener( 'load'、()=&gt; {
      if(img === this.imageel){
        this.loaded = true;
        this.render();
      }
    });
    img.src = url;
    this.imageel = img;
  }

  connectedcallback(){
    this.render();
  }

  与える() {
    const elementmaybe = this.loaded? this.imageel:this.previewel;
    syncsinglechild(this、elementmaybe);
  }
}

まず、関心のある属性を登録し、それが変更されたときに反応します。

静的観測されたaTtributes = ['url'];

astibuteChangedCallback(name、oldvalue、newValue){
  if(name === 'url'){
    this.createmainimage(newValue);
  }
}

これにより、画像コンポーネントが作成されます。これは、ロードされたときにのみ表示されます。

 createmainimage(url){
  this.loaded = false;
  const img = document.createelement( 'img');
  img.alt = 'Book Cover';
  img.addeventlistener( 'load'、()=> {
    if(img === this.imageel){
      this.loaded = true;
      this.render();
    }
  });
  img.src = url;
  this.imageel = img;
}

次に、プレビュープロパティがあります。これは、Base64プレビュー文字列またはBlurhashパケットのいずれかです。

 Preview(val)を設定{
  this.previewel = this.createpreview(val);
  this.render();
}

createpreview(val){
  if(typeof val === 'string'){
    base64preview(val)を返します。
  } それ以外 {
    return blurhashpreview(val);
  }
}

これは、私たちが必要とするヘルパー関数に次のことを担当します。

関数base64preview(val){
  const img = document.createelement( 'img');
  img.src = val;
  IMGを返します。
}

function blurhashpreview(プレビュー){
  const canvasel = document.createelement( 'canvas');
  const {w:width、h:height} = preview;

  canvasel.width = width;
  canvasel.height = height;

  const pixels = decode(preview.blurhash、width、height);
  const ctx = canvasel.getContext( '2d');
  constimagedata = ctx.createimagedata(幅、高さ);
  Imagedata.data.set(ピクセル);
  ctx.putimagedata(Imagedata、0、0);

  Canvaselを返します。
}

そして最後に、私たちのレンダリング方法:

 connectedcallback(){
  this.render();
}

与える() {
  const elementmaybe = this.loaded? this.imageel:this.previewel;
  syncsinglechild(this、elementmaybe);
}

そして、すべてを結び付けるためのいくつかのヘルパーの方法:

エクスポート関数SyncsingLechild(コンテナ、子){
  const currentchild = container.firstelementChild;
  if(currentChild!== child){
    ClearContainer(コンテナ);
    if(child){
      container.appendChild(子);
    }
  }
}

エクスポート関数ClearContainer(EL){
  子供をさせてください。

  while((child = el.firstelementChild)){
    el.RemoveChild(子);
  }
}

フレームワークでこれを構築する場合は、必要なものよりも少し多くのボイラープレートですが、利点は、私たちが望むあらゆるフレームワークでこれを再利用できることです。

オッズと終わり

私はすでにLit's Reactラッパーについて言及しました。しかし、ステンシルを使用していることに気付いた場合、実際には反応のためだけに個別の出力パイプラインをサポートします。また、Microsoftの善良な人々は、Fast Webコンポーネントライブラリに添付されたLitのラッパーに似たようなものを作成しました。

先ほど述べたように、racemが命名されていないすべてのフレームワークは、Webコンポーネントのプロパティの設定を処理します。いくつかの特別な味の構文があることに注意してください。たとえば、solid.jsでは、は、値がプロパティであると常に想定しています。これは、のような属性プレフィックスでオーバーライドできます。

まとめます

Webコンポーネントは、Web開発の状況の興味深い、しばしば使用されていない部分です。 UI、または「リーフ」コンポーネントを管理することにより、単一のJavaScriptフレームワークへの依存を減らすのに役立ちます。これらをWebコンポーネントとして作成することは、SvelteまたはReactコンポーネントとは対照的に、人間工学に基づいていませんが、利点は広く再利用可能であるということです。

以上が動作する相互運用可能なWebコンポーネントを構築しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。