Vue.jsの機能コンポーネントに関する包括的な理解

Vue.jsの機能コンポーネントに関する包括的な理解

序文

フロントエンド開発者であり、Java コードを何度か読んだことがある場合、ES6 構文の矢印関数に似た書き方を見たことがあるかもしれません。

(文字列 a, 文字列 b) -> a.toLowerCase() + b.toLowerCase();

Java 8以降に登場したこのラムダ式は、C++/Pythonでも登場しています。従来のOOPスタイルのコードよりもコンパクトです。Javaにおけるこの式は、本質的にはクラスインスタンスを生成する関数型インターフェースの糖衣構文ですが、その簡潔な書き方と、不変の値を加工して別の値にマッピングする動作は、関数型プログラミング(FP)の典型的な特徴です。

1992 年のチューリング賞受賞者であるバトラー・ランプソンは、次のような有名な発言を残しました。

コンピュータサイエンスにおけるすべての問題は、別のレベルの間接化によって解決できる。
コンピュータサイエンスのあらゆる問題は、間接レベルを追加することで解決できる。

この文の「間接レベル」は「抽象レベル」と訳されることが多く、その厳密さについては議論もありますが、翻訳に関係なく意味は通じます。いずれにせよ、OOP 言語による FP の採用は、プログラミング分野における関数型プログラミングの統合と重視の増加を直接反映しており、さらに、別のレベルの間接性を導入することで実際の問題を解決できるという「ソフトウェア エンジニアリングの基本定理」を裏付けています。

それほど厳密ではないもう一つの有名な格言があります。

OOPはデータの抽象化であり、FPは動作を抽象化するために使用される

さまざまなオブジェクトを抽象化し、それらの間の分離の問題に注意を払うオブジェクト指向プログラミングとは異なり、関数型プログラミングは最小の単一の操作に焦点を当て、複雑なタスクを f(x) = y 型の関数操作の繰り返しの重ね合わせに変換します。関数は FP のファーストクラス オブジェクトであり、関数パラメータとして使用したり、関数によって返したりできます。

同時に、FP では、関数は外部の状態に依存したり、外部の状態に影響を与えたりしてはなりません。つまり、特定の入力に対して、同じ出力が生成されます。これが、FP で「不変」や「純粋」などの言葉がよく使用される理由です。前述の「ラムダ計算」や「curring」についても言及すると、FP 愛好家のように聞こえます。

上記の概念とそれに関連する理論は、20 世紀前半に誕生しました。多くの科学者が数理論理学の研究で実りある成果を上げています。人気の ML や AI も、これらの成果の恩恵を受けています。たとえば、当時のポーランド系アメリカ人の数学者の巨匠、ハスケル・カリーは、Haskell 言語やカリー化などの典型的な関数型の実践にその名を残しました。

React 関数コンポーネント

jQuery/RxJS の「チェーン構文」を使ったことがあるなら、それは実は FP におけるモナドの実践とみなすことができます。そして近年、ほとんどのフロントエンド開発者が実際に FP に触れるようになったのは、1 つは ES6 で導入された map/reduce などの関数型配列インスタンス メソッドからであり、もう 1 つは React の関数型コンポーネント (FC - functional component) からです。

React の機能コンポーネントは、ステートレス コンポーネントと呼ばれることがよくあります。より直感的な名前はレンダリング関数です。これは、レンダリングに使用される関数にすぎないためです。

const Welcome = (props) => { 
  <h1>Hello, {props.name}</h1> を返します。 
}

TypeScript と組み合わせて、type とFC<propsType>を使用して、jsx を返すこの関数の入力パラメータを制限することもできます。

タイプ GreetingProps = {
 名前: 文字列;
}
 
const Greeting:React.FC<GreetingProps> = ({名前}) => {
 戻り値 <h1>こんにちは {name}</h1>
};

インターフェースとパラダイムを使用して、プロパティ タイプをより柔軟に定義することもできます。

インターフェースIGreeting<T = 'm' | 'f'> {
 名前: 文字列;
 性別: T
}
エクスポートconst Greeting = ({ name, gender }: IGreeting<0 | 1>): JSX.Element => {
 戻り値 <h1>こんにちは { 性別 === 0 ? 'Ms.' : 'Mr.' } {名前}</h1>
};

Vue (2.x) の機能コンポーネント

Vue公式サイトのドキュメントの[Functional Components]セクションでは、次のように説明されています。

...コンポーネントを機能的としてマークすることができます。これは、コンポーネントがステートレス (リアクティブ データなし) であり、インスタンスがない (this コンテキストがない) ことを意味します。機能コンポーネントは次のようになります。
 
Vue.component('my-component', {
  機能的: 真、
  // Propsはオプションです props: {
    // ...
  },
  // インスタンスの不足を補うために // コンテキストレンダリングとして2番目の引数を提供します: function (createElement, context) {
    // ...
  }
})
 
...
 
バージョン 2.5.0 以降では、[単一ファイル コンポーネント] を使用する場合、テンプレートベースの機能コンポーネントを次のように宣言できます。
 
<テンプレート機能>
</テンプレート>

React を書いて初めてこのドキュメントを読んだ開発者は、無意識のうちに「ああ、これは…」と叫ぶかもしれません。単にfunctionalと書くだけで機能的と呼ばれるのですか? ? ?

実際、 Vue 3.x では、 React と同様に純粋なレンダリング関数である「関数コンポーネント」を記述できます。これについては後で説明します。

より一般的な Vue 2.x では、ドキュメントに記載されているように、機能コンポーネント (FC)はインスタンスのないコンポーネント (this コンテキストなし、ライフサイクル メソッドなし、プロパティのリッスンなし、状態の管理なし)を意味します。外部から見ると、これはおそらく、いくつかの props を受け入れて、期待どおりに何らかのレンダリング結果を返すfc(props) => VNode関数として見ることができます。

さらに、実際の FP 関数は不変の状態に基づいており、Vue の「機能的」コンポーネントはそれほど理想的ではありません。後者は可変データに基づいており、通常のコンポーネントと比較すると、インスタンスの概念がありません。しかし、その利点は依然として明らかです。

機能コンポーネントはライフサイクルや監視などの実装ロジックを無視するため、レンダリングのオーバーヘッドが非常に少なく、実行速度が速い

通常のコンポーネントのv-ifやその他の命令と比較すると、h関数を使用したり、jsxロジックを組み合わせたりする方が明確です。

いくつかのロジックをカプセル化し、条件に応じてパラメトリックな子コンポーネントをレンダリングするコンテナコンポーネントであるHOC(高階コンポーネント)パターンの実装が容易になります。

複数のルートノードを配列で返すことができます

🌰 例: el-table のカスタム列を最適化する

まず、FC が適用できる典型的なシナリオを直感的に体験してみましょう。

551072df36b6159d4aa452536c412f12.png

これは、ElementUI の公式 Web サイトで提供されているカスタム テーブル列の例です。対応するテンプレート コードは次のとおりです。

<テンプレート>
  <el-テーブル
    :data="テーブルデータ"
    スタイル="幅: 100%">
    <el-テーブル列
      label="日付"
      幅="180">
      <テンプレート スロット スコープ="スコープ">
        <i class="el-icon-time"></i>
        <span style="margin-left: 10px">{{ scope.row.date }}</span>
      </テンプレート>
    </el-table-column>
    <el-テーブル列
      label="名前"
      幅="180">
      <テンプレート スロット スコープ="スコープ">
        <el-popover トリガー="ホバー" 配置="上">
          <p>名前: {{ scope.row.name }}</p>
          <p>アドレス: {{ scope.row.address }}</p>
          <div スロット="参照" クラス="名前ラッパー">
            <el-tag size="medium">{{ scope.row.name }}</el-tag>
          </div>
        </el-popover>
      </テンプレート>
    </el-table-column>
    <el-table-column label="操作">
      <テンプレート スロット スコープ="スコープ">
        <el-ボタン
          サイズ="ミニ"
          @click="handleEdit(scope.$index, scope.row)">編集</el-button>
        <el-ボタン
          サイズ="ミニ"
          タイプ="危険"
          @click="handleDelete(scope.$index, scope.row)">削除</el-button>
      </テンプレート>
    </el-table-column>
  </el-table>
</テンプレート>

実際のビジネスニーズでは、ドキュメントの例にあるような小さなテーブルは確かに存在しますが、それらは私たちの注目の的ではありません。ElementUI のカスタム テーブル列は、多数のフィールドと複雑なインタラクションを持つ大規模なレポートのレンダリング ロジックで広く使用されています。通常、20 を超える列から始まり、各列には、画像のリスト、ビデオ プレビュー ポップアップ、結合して書式設定する必要がある段落、権限やステータスに応じた不特定多数の操作ボタンが含まれます。関連するテンプレート部分は、数百行、さらにはそれ以上になることもよくあります。長くなるだけでなく、異なる列で同様のロジックを再利用することも困難です。

テレビシリーズ「フレンズ」ではこう言っています。

現実世界へようこそ!残念ですが、きっと気に入るはずです!

Vue の単一ファイル コンポーネントは、include などのテンプレートを分割するためのソリューションを提供していません。結局のところ、構文糖は十分にあり、構文糖がないのが最善です。

潔癖症の開発者は、この問題を解決するために、複雑な列テンプレートを独立したコンポーネントにカプセル化しようとします。これはすでに非常に優れていますが、元の記述方法と比較するとパフォーマンス上のリスクが生じます。

面接中に、マルチレイヤー ノード レンダリングを最適化する方法についての質問に答えたときのあなたの圧倒的な自信を思い出すと、懸念を分割してパフォーマンスの問題を回避するために、この実践をさらに一歩進める必要があることは明らかです。このシナリオでは、機能コンポーネントが適切なソリューションです。

最初に試したのは、元のテンプレートの日付列を関数コンポーネント DateCol.vue に「変換」することでした。

<テンプレート機能>
  <div>
    <i class="el-icon-time"></i>
    <span style="margin-left: 10px; color: blue;">{{ props.row.date }}</span>
  </div>
</テンプレート> 

0107d1f090b1cfde7c5c3a6312acd6c4.png

コンテナ ページにインポートした後、コンポーネントで宣言して使用します。

cddd2a771a92e3966399c2366293df6c.png

基本的には前と同じですが、唯一の問題は、単一のルート要素に制限され、div の追加レイヤーがあることです。これは、vue-fragment などで解決することもできます。

次に、名前列を NameCol.js にリファクタリングします。

エクスポートデフォルト{
  機能的: 真、
  レンダリング(h, {props}) {
    定数{行} = プロパティ;
    h('el-popover', { を返す
        プロパティ: {トリガー: "hover"、配置: "top"}、
        スコープスロット: {
          参照: () => h('div', {class: "name-wrapper"}, [
            h('el-tag', {props: {size: 'medium'}}, [row.name + '~'])
          ])
        }
      }, [
          h('p', null, [`名前: ${ row.name }`]),
          h('p', null, [`アドレス: ${ row.address }`])
      ])
  }
} 

翻訳:

b93923ff0aa5cb73d08a003bb644ea65.png

効果は驚くべきもので、配列の使用により単一のルート要素の制限を回避できます。さらに重要なのは、この抽象化されたウィジェットが実際の js モジュールであることです。 <script>でラップする代わりに.jsファイルに配置することができ、自由に好きなことを実行できます。

h 関数は多少の精神的負担をもたらすかもしれませんが、JSX サポートが設定されていれば、元のバージョンとほぼ同じになります。

さらに、ここで関係する scopedSlots と、後の 3 番目の列で説明するイベント処理についても説明します。

レンダリングコンテキスト

上記のドキュメント セクションで説明したように、レンダリング関数は次の形式になります。

レンダリング: 関数 (createElement, コンテキスト) {}

実際のコーディングでは、createElement は h と記述されることが多く、jsx の使用時に h が呼び出されない場合でも記述する必要がありますが、Vue3 では、 import { h } from 'vue'を使用してグローバルにインポートできます。

これは、多くの仮想 DOM 実装でよく使用される「ハイパースクリプト」という用語に由来しています。「ハイパースクリプト」自体は「HTML 構造を生成するスクリプト」を意味します。HTML は「ハイパーテキスト マークアップ言語」の頭字語だからです。-- Evan You

公式ウェブサイトの文書は次のように続きます。

コンポーネントに必要なものはすべて、次のフィールドを持つオブジェクトであるコンテキスト パラメータとして渡されます。

props: すべてのpropsを提供するオブジェクト
children: VNode の子ノードの配列
スロット: すべてのスロットを含むオブジェクトを返す関数
scopedSlots: (2.6.0+) 渡されたスコープ スロットを公開するオブジェクト。通常のスロットも関数として公開します。
data: コンポーネントに渡されるデータ オブジェクト全体。createElement の 2 番目のパラメータとしてコンポーネントに渡されます。
親: 親コンポーネントへの参照
listeners: (2.3.0+) このコンポーネントの親コンポーネントによって登録されたすべてのイベント リスナーを含むオブジェクト。これは data.on のエイリアスです。
injections: (2.3.0+) inject オプションが使用される場合、このオブジェクトには注入されるプロパティが含まれます。

このコンテキストは、RenderContext のインターフェース型として定義されます。Vue 内でコンポーネントを初期化または更新する場合、次のように形成されます。

9e29f71bf512349a61441dd4d13a4535.png

RenderContext インターフェイスによって定義されたさまざまなプロパティを習得することが、機能コンポーネントを操作するための基礎となります。

テンプレート

前の例では、機能属性を持つテンプレートを使用して、テーブル内の日付列のロジックを独立したモジュールに抽象化しました。

これも、上の概略図で部分的に説明されています。Vueのテンプレートは実際にはレンダリング関数にコンパイルされているか、テンプレートと明示的なレンダリング関数が同じ内部処理ロジックに従い、 $optionsなどのプロパティが添付されています。

言い換えれば、複雑なロジックを扱う場合でも、テンプレート内でメソッドを習慣的に呼び出すなど、js のパワーを活用できます。もちろん、これは実際の Vue コンポーネント メソッドではありません。

b31e059aa65f9ab946da51dbb2831634.png

放出する

関数コンポーネントにはthis.$emit()のようなメソッドはありません。

ただし、イベント コールバックは引き続き通常どおり処理できます。使用する必要があるのは context.listeners プロパティです。ドキュメントに記載されているように、これは data.on のエイリアスです。たとえば、前の例では、コンテナ ページの日付列のアイコンのクリックをリッスンします。

<date-col v-bind="scope" @icon-click="onDateClick" />

DateCol.vue で、次のようにイベントをトリガーします。

<i class="el-icon-time" 
      @click="() => リスナー['icon-click'](props.row.date)">
    </i>

唯一注意すべき点は、上記の方法はほとんどの状況では十分ですが、同じ名前の複数のイベントが外部でリッスンされている場合、リスナーは配列になるため、比較的完全なカプセル化方法は次のとおりです。

/**
 * 機能コンポーネントのイベントトリガーメソッド * @param {object} listeners - コンテキスト内のリスナーオブジェクト * @param {string} eventName - イベント名 * @param {...any} args - 複数のパラメーター * @returns {void} - なし */
エクスポート const fEmit = (リスナー、イベント名、...引数) => {
    const cbk = リスナー[イベント名]
    _.isFunction(cbk) の場合、cbk.apply(null、引数)
    そうでない場合、(_.isArray(cbk)) cbk.forEach(f => f.apply(null, args))
}

フィルター

従来の Vue テンプレートの<label>{ title | withColon }</label>フィルター構文は、h 関数または jsx の戻り構造では機能しなくなりました。

幸いなことに、元々定義されたフィルター関数も通常の関数なので、同等の記​​述方法は次のようになります。

'@/filters' からフィルターをインポートします。
 
const { withColon } = フィルター;
 
//...
 
// レンダリングは jsx を返します <label>{ withColon(title) }</label>

スロット

通常のコンポーネントのテンプレート部分でスロットを使用する方法は、jsx モードを含め、機能コンポーネントのレンダリング機能では使用できません。

前の例では、名前列を NameCol.js にリファクタリングするときに、対応する記述方法が示されました。ElementUI のスケルトン画面コンポーネントの例を見てみましょう。たとえば、一般的なテンプレートの使用方法は次のようになります。

<el-skeleton :loading="skeLoading">
    実際のテキスト
    <テンプレートスロット="テンプレート">
      <p>コンテンツを読み込んでいます</p>
    </テンプレート>
</el-skeleton>

これには実際には、default と template の 2 つのスロットが含まれます。関数コンポーネントの render 関数に切り替えると、対応する記述方法は次のようになります。

エクスポートデフォルト{
  機能的: 真、
  プロップ: ['ok'],
  レンダリング(h, {props}) {
    h('el-skeleton' ,{ を返す
      props: {読み込み中: props.ok},
      スコープスロット: {
        デフォルト: () => '実際のテキスト'、
        テンプレート: () => h('p', null, ['読み込みコンテキスト'])
      }
    }, ヌル)
  }
}

v-bind:user="user"ような属性を渡すスコープ付きスロットに遭遇した場合は、スロット関数の入力パラメータとしてuser を使用できます。

公式ウェブサイトのドキュメントにはslots()childrenの比較についても記載されています。

<私の機能コンポーネント>
  <p v-スロット:foo>
    初め
  </p>
  <p>2番目</p>
</my-function-component> 関数コンポーネント
 
このコンポーネントの場合、 children は 2 つの段落タグを提供しますが、 slots().default は 2 番目の匿名段落タグのみを渡し、 slots().foo は最初の名前付き段落タグを渡します。 children と slots() の両方を備えているため、コンポーネントにスロット メカニズムを認識させるか、children を渡すことによって他のコンポーネントに渡すかを選択できます。

提供する/注入する

ドキュメントに記載されているインジェクションの使用法に加えて、Vue 2 の provide / inject は最終的に非応答性であることにも注意してください。

評価後にこのメソッドを使用する必要がある場合は、vue-reactive-provideを試すことができます。

HTMLコンテンツ

Vue の jsx は、通常のコンポーネント テンプレートでの v-html の記述をサポートできません。対応する要素属性は、次の domPropsInnerHTML です。

<strong class={type} domPropsInnerHTML={formatValue(item, type)} />

render メソッドでは、単語が再度分割され、次のように記述されます。

h('p', {
      domProps: {
            内部HTML: '<h1>こんにちは</h1>'
      }
})

いずれにしても書くのは少し面倒ですが、ありがたいことに、React の dangerouslySetInnerHTML よりも覚えやすいです。

スタイル

純粋な.js / .tsコンポーネントを使用する場合、唯一の問題は、 .vueコンポーネントでスコープ指定されたスタイルを利用できなくなることです。React の状況を参照すると、解決方法はいくつかしかありません。

  • 外部スタイルをインポートし、BEMなどの命名規則を使用する
  • vue-loaderオプションでCSSモジュールを有効にし、コンポーネントにstyleMod.fooを適用します。
  • モジュール内のスタイル配列またはオブジェクトを動的に構築し、属性に割り当てる
  • ツールメソッドを使用してスタイル クラスを動的に構築します。
const _insertCSS = css => {
    $head = document.head || document.getElementsByTagName('head')[0] とします。
    定数 style = document.createElement('style');
    style.setAttribute('type', 'text/css');
    スタイルシートの場合
        style.styleSheet.cssText = css;
    } それ以外 {
        style.appendChild(document.createTextNode(css));
    }
    $head.appendChild(スタイル);
    $head = null;
};

タイプスクリプト

React と Vue はどちらも、props タイプを検証する手段をいくつか提供しています。ただし、これらの方法は設定が少し面倒であり、軽量の機能コンポーネントには少し重すぎます。

TypeScript は JavaScript の厳密に型付けされたスーパーセットであるため、プロパティの型をより正確に定義およびチェックでき、使いやすく、VSCode や Vetur をサポートするその他の開発ツールでよりわかりやすい自動プロンプトが表示されます。

外部入力プロパティのinterface RenderContext<Props>で定義されているように、Vue 機能コンポーネントを TS と組み合わせるには、次のようにカスタム TypeScript インターフェイスを使用してその構造を宣言します。

インターフェース IProps {
 年: 文字列;
 四半期: 配列<'Q1' | 'Q2' | 'Q3' | 'Q4'>;
 注記:
  コンテンツ: 文字列;
  著者:かき混ぜる;
 }
}

次に、RenderContext の最初のジェネリック型としてインターフェイスを指定します。

'vue' から Vue, { CreateElement, RenderContext } をインポートします。
 
...
 
デフォルトのエクスポート Vue.extend({
  機能的: 真、
  レンダリング: (h: CreateElement, コンテキスト: RenderContext<IProps>) => {
     コンソールにログ出力します。
   //...
  }
});

合成APIを組み合わせる

React Hooks の設計目的と同様に、Vue Composition API も、レスポンシブ機能、onMounted などのライフサイクルの概念、および副作用を管理するメソッドを機能コンポーネントにある程度もたらします。

ここでは、composition-api での独自の記述方法、つまり setup() エントリ関数でレンダリング関数を返す方法についてのみ説明します。

たとえば、counter.js を定義します。

"@vue/composition-api" から { h, ref } をインポートします。
 
エクスポートデフォルト{
  モデル: {
    プロパティ: "値",
    イベント:「雑煮」
  },
  小道具: {
    価値: {
      タイプ: 数値、
      デフォルト: 0
    }
  },
  セットアップ(props, { 出力 }) {
    const カウンター = ref(props.value);
    定数増分 = () => {
      出力("zouni", ++counter.value);
    };
 
    戻り値 () =>
      h("div", null, [h("button", { on: { click: increment } }, ["plus"])]);
  }
};

コンテナ ページ内:

<el-input v-model="cValue" />
<カウンタ v-model="cValue" /> 

fc3645b0441ee1865f2c5e0581aed9f6.gif

TypeScript と組み合わせて使用​​する場合、変更点は次のとおりです。

  • "@vue/composition-api" から defineComponent をインポートします。
  • デフォルトのdefineComponent<IProps>({component})をエクスポートします。

ユニットテスト

TypeScript の強力な型指定サポートを使用すると、コンポーネントの内外のパラメータの型がより適切に保護されます。

コンポーネントロジックに関しては、セキュリティ スキャフォールディングの構築を完了するには、ユニット テストがまだ必要です。同時に、機能コンポーネントは一般的に比較的単純なので、テストを書くことは難しくありません。

実際には、FC と通常のコンポーネントの違いにより、注意を払う必要があるいくつかの小さな問題がまだあります。

再レンダリング

関数コンポーネントは、渡されるプロパティが変更された場合にのみレンダリングをトリガーするため、テスト ケースでnextTick()を呼び出すだけでは更新された状態を取得できません。手動で再レンダリングをトリガーする必要があります。

it("一括選択", async () => {
    結果を mockData とします。
    // これは実際には、外部からプロパティが渡されるたびにコンポーネントを更新するプロセスをシミュレートします // wrapper.setProps() は関数コンポーネントでは呼び出せません
    定数更新 = 非同期() => {
      ラッパーの作成(
        {
          値: 結果
        },
        {
          リスナー: {
            変更: m => (結果 = m)
          }
        }
      );
      localVue.nextTick() を待機します。
    };
    更新を待機します();
    wrapper.findAll("input").toHaveLength(6) を期待します。
 
    wrapper.find("tr.whole label").trigger("click");
    更新を待機します();
    expect(wrapper.findAll("input:checked")).toHaveLength(6);
 
    wrapper.find("tr.whole label").trigger("click");
    更新を待機します();
    expect(wrapper.findAll("input:checked")).toHaveLength(0);
 
    wrapper.find("tr.whole label").trigger("click");
    更新を待機します();
    wrapper.find("tbody>tr:nth-child(3)>td:nth-child(2)>ul>li:nth-child(4)>label").trigger("クリック");
    更新を待機します();
    expect(wrapper.find("tr.whole label input:checked").exists()).toBeFalsy();
  });

複数のルートノード

関数コンポーネントの利点の 1 つは、要素の配列を返すことができることです。これは、render() で複数のルート ノードを返すことと同じです。

このとき、shallowMount などのメソッドを使用してテスト内のコンポーネントを直接ロードすると、エラーが発生します。

[Vue 警告]: レンダリング関数から複数のルート ノードが返されました。レンダリング関数は単一のルート ノードを返す必要があります。

解決策は、パッケージング コンポーネントをカプセル化することです。

'@vue/test-utils' から { mount } をインポートします。
'@/components/Cell' から Cell をインポートします。
 
定数ラップセル = {
  コンポーネント: { セル },
  テンプレート: `
    <div>
      <セル v-bind="$attrs" v-on="$listeners" />
    </div>
  `
}
 
const wrapper = mount(WrappedCell, {
  プロパティデータ: {
    セルデータ: {
      カテゴリー: 'foo',
      説明: 'バー'
    }
  }
});
 
記述('Cell.vue', () => {
  it('カテゴリと説明を含む2つのtdを出力する必要があります', () => {
    期待(wrapper.findAll('td')).toHaveLength(2);
    wrapper.findAll('td').at(0).text()).toBe('foo') を期待します。
    期待(wrapper.findAll('td').at(1).text()).toBe('bar');
  });
});

フラグメントコンポーネント

FC で使用できるもう 1 つのトリックは、vue-fragment (通常はマルチノードの問題を解決するために使用) を参照する一般的なコンポーネントに対して、機能コンポーネントをカプセル化して、ユニット テストでフラグメント コンポーネントをスタブ化することで、依存関係を減らし、テストを容易にできることです。

wrapper を null にします。
const makeWrapper = (props = null、opts = null) => {
  ラッパー = マウント(Comp, {
    ローカルビュー、
    プロパティデータ: {
      ...小道具
    },
    スタブ:
      フラグメント: {
        機能的: 真、
        レンダリング(h, {スロット}) {
          h("div", slots().default); を返します。
        }
      }
    },
    添付文書: true、
    同期: false、
    ...オプション
  });
};

Vue 3 の機能コンポーネント

この部分は、基本的に、composition-api での以前の慣行と一致しています。新しい公式 Web サイトのドキュメントから、ステートメントを大まかに抜粋してみましょう。

真の機能コンポーネント

Vue 3 では、すべての機能コンポーネントは通常の関数を使用して作成されます。つまり、 { functional: true }コンポーネント オプションを定義する必要はありません

これらは、 propscontext 2 つのパラメーターを受け取ります。 contextパラメータは、コンポーネントのattrsslots 、およびemitプロパティを含むオブジェクトです。

さらに、 h はrender関数で暗黙的に提供されるのではなく、 hにインポートされるようになりました

'vue' から { h } をインポートします
 
const DynamicHeading = (props, context) => {
  h(`h${props.level}`, context.attrs, context.slots) を返します
}
 
DynamicHeading.props = ['レベル']
 
デフォルトのDynamicHeadingをエクスポートする

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

3.x では、ステートフル コンポーネントと機能コンポーネント間のパフォーマンスの違いが大幅に減少し、ほとんどのユース ケースで無視できるほどになりました。したがって、単一ファイル コンポーネントでfunctionalを使用する開発者の移行パスは、属性を削除しpropsへのすべての参照を$propsに、 attrsへのすべての参照を$attrsに名前変更することです。

<テンプレート>
  <コンポーネント
    v-bind:is="`h${$props.level}`"
    v-bind="$attrs"
  />
</テンプレート>
 
<スクリプト>
エクスポートデフォルト{
  小道具: ['レベル']
}
</スクリプト>

主な違いは次のとおりです。

  1. <template>からfunctional属性を削除します。
  2. listeners$attrsの一部として渡され、削除できるようになりまし

要約する

Vue.js 関数コンポーネントに関するこの記事はこれで終わりです。Vue.js 関数コンポーネントに関するより詳しい情報は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続きご覧ください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • VUE の手ぶれ補正とスロットリングの最適なソリューションに関する簡単な説明 (機能コンポーネント)
  • Vue機能コンポーネントの詳細な応用例
  • Vue 関数コンポーネント - あなたにふさわしい
  • Vue関数コンポーネントの使用に関する簡単な説明

<<:  MySQLアラームの詳細な分析と処理

>>:  Docker のポート解放失敗の解決策

推薦する

MySQL方言の簡単な紹介

データベースはさておき、人生における方言とは何でしょうか?方言とは、ある場所特有の言語です。他の場所...

Navicat for MySQL 15 登録とアクティベーションの詳細なチュートリアル

1. Navicat for MySQL 15をダウンロードするhttps://www.navica...

HTMLのフォントがline-heightを指定しても垂直方向に中央揃えできない問題の解決方法を詳しく説明します

による写真に示されている効果を例に挙げてみましょう。明らかに、「次へ」というテキストを水平方向だけで...

HTML テーブルタグチュートリアル (23): 行の境界線の色属性 BORDERCOLORDARK

行ごとに、暗い境界線の色を個別に定義できます。基本的な構文<TR 境界線の色を暗くする=col...

ユーザーエクスペリエンスの要素またはWebデザインの要素

システムとユーザー環境の設計<br />Apple システムの成功は、そのシステム アー...

ノード スキャフォールディングを使用してトークン検証を実装するサーバーを構築する方法

コンテンツスキャフォールディングを使用してノードプロジェクトを素早く構築するデータベースとやり取りす...

ウェブサイトアイコンを追加するにはどうすればいいですか?

最初のステップは、アイコン作成ソフトウェアを準備することです。まず、いわゆるアイコンは拡張子 .ic...

DockerにFastDFSをインストールする方法

画像をプルする docker pull season/fastdfs:1.2トラッカーを開始 doc...

アーティストの自己啓発におけるいくつかの経験

会社の影響力が拡大し、製品が改良され続けるにつれて、関連するイメージデザインもそれに追いつき、徐々に...

IDEA は MySQL への接続時にエラーを報告します。サーバーが無効なタイムゾーンを返します。タブに移動して serverTimezone プロパティを設定してください。

これからの道は常に困難で、棘だらけです。歯を食いしばって、乗り越えられると信じてください。さあ、さあ...

ウェブテーブルフレームを作成するためのヒント

<br />Web テーブル フレームを作成するためのヒント。 ------------...

CSS で子 div の高さを親コンテナの残りのスペースに合わせる方法

1. フローティング方式を使用する効果画像: コードは次のとおりです: (.content の高さは...

Vue を通じて QR コードスキャン機能を実装する

ヒントこのプラグインは https プロトコルでのみアクセスできます。http プロトコルはうまく機...

opensslを使用して無料の証明書を生成する方法

1: openssl とは何ですか? その機能は何ですか?適用シナリオは何ですか? Baidu 百科...

MySQLがトランザクション分離を実装する方法の簡単な分析

目次1. はじめに2. RC および RR 分離レベル2.1. RRトランザクション分離レベルでのク...