序文フロントエンド開発者であり、Java コードを何度か読んだことがある場合、ES6 構文の矢印関数に似た書き方を見たことがあるかもしれません。 (文字列 a, 文字列 b) -> a.toLowerCase() + b.toLowerCase(); Java 8以降に登場したこのラムダ式は、C++/Pythonでも登場しています。従来のOOPスタイルのコードよりもコンパクトです。Javaにおけるこの式は、本質的にはクラスインスタンスを生成する関数型インターフェースの糖衣構文ですが、その簡潔な書き方と、不変の値を加工して別の値にマッピングする動作は、関数型プログラミング(FP)の典型的な特徴です。 1992 年のチューリング賞受賞者であるバトラー・ランプソンは、次のような有名な発言を残しました。
この文の「間接レベル」は「抽象レベル」と訳されることが多く、その厳密さについては議論もありますが、翻訳に関係なく意味は通じます。いずれにせよ、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 と タイプ 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 を書いて初めてこのドキュメントを読んだ開発者は、無意識のうちに「ああ、これは…」と叫ぶかもしれません。単に 実際、 Vue 3.x では、 React と同様に純粋なレンダリング関数である「関数コンポーネント」を記述できます。これについては後で説明します。 より一般的な Vue 2.x では、ドキュメントに記載されているように、機能コンポーネント (FC)はインスタンスのないコンポーネント (this コンテキストなし、ライフサイクル メソッドなし、プロパティのリッスンなし、状態の管理なし)を意味します。外部から見ると、これはおそらく、いくつかの props を受け入れて、期待どおりに何らかのレンダリング結果を返す さらに、実際の FP 関数は不変の状態に基づいており、Vue の「機能的」コンポーネントはそれほど理想的ではありません。後者は可変データに基づいており、通常のコンポーネントと比較すると、インスタンスの概念がありません。しかし、その利点は依然として明らかです。 機能コンポーネントはライフサイクルや監視などの実装ロジックを無視するため、レンダリングのオーバーヘッドが非常に少なく、実行速度が速い 通常のコンポーネントの いくつかのロジックをカプセル化し、条件に応じてパラメトリックな子コンポーネントをレンダリングするコンテナコンポーネントであるHOC(高階コンポーネント)パターンの実装が容易になります。 複数のルートノードを配列で返すことができます 🌰 例: el-table のカスタム列を最適化するまず、FC が適用できる典型的なシナリオを直感的に体験してみましょう。 これは、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> </テンプレート> コンテナ ページにインポートした後、コンポーネントで宣言して使用します。 基本的には前と同じですが、唯一の問題は、単一のルート要素に制限され、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 }`]) ]) } } 効果は驚くべきもので、配列の使用により単一のルート要素の制限を回避できます。さらに重要なのは、この抽象化されたウィジェットが実際の js モジュールであることです。 h 関数は多少の精神的負担をもたらすかもしれませんが、JSX サポートが設定されていれば、元のバージョンとほぼ同じになります。 さらに、ここで関係する scopedSlots と、後の 3 番目の列で説明するイベント処理についても説明します。 レンダリングコンテキスト上記のドキュメント セクションで説明したように、レンダリング関数は次の形式になります。 レンダリング: 関数 (createElement, コンテキスト) {} 実際のコーディングでは、createElement は h と記述されることが多く、jsx の使用時に h が呼び出されない場合でも記述する必要がありますが、Vue3 では、
公式ウェブサイトの文書は次のように続きます。
このコンテキストは、RenderContext のインターフェース型として定義されます。Vue 内でコンポーネントを初期化または更新する場合、次のように形成されます。 RenderContext インターフェイスによって定義されたさまざまなプロパティを習得することが、機能コンポーネントを操作するための基礎となります。 テンプレート前の例では、機能属性を持つテンプレートを使用して、テーブル内の日付列のロジックを独立したモジュールに抽象化しました。 これも、上の概略図で部分的に説明されています。Vueのテンプレートは実際にはレンダリング関数にコンパイルされているか、テンプレートと明示的なレンダリング関数が同じ内部処理ロジックに従い、 言い換えれば、複雑なロジックを扱う場合でも、テンプレート内でメソッドを習慣的に呼び出すなど、js のパワーを活用できます。もちろん、これは実際の Vue コンポーネント メソッドではありません。 放出する関数コンポーネントには ただし、イベント コールバックは引き続き通常どおり処理できます。使用する必要があるのは 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 テンプレートの 幸いなことに、元々定義されたフィルター関数も通常の関数なので、同等の記述方法は次のようになります。 '@/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, ['読み込みコンテキスト']) } }, ヌル) } } 公式ウェブサイトのドキュメントには <私の機能コンポーネント> <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 よりも覚えやすいです。 スタイル純粋な
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 をサポートするその他の開発ツールでよりわかりやすい自動プロンプトが表示されます。 外部入力プロパティの インターフェース 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" /> TypeScript と組み合わせて使用する場合、変更点は次のとおりです。
ユニットテストTypeScript の強力な型指定サポートを使用すると、コンポーネントの内外のパラメータの型がより適切に保護されます。 コンポーネントロジックに関しては、セキュリティ スキャフォールディングの構築を完了するには、ユニット テストがまだ必要です。同時に、機能コンポーネントは一般的に比較的単純なので、テストを書くことは難しくありません。 実際には、FC と通常のコンポーネントの違いにより、注意を払う必要があるいくつかの小さな問題がまだあります。 再レンダリング関数コンポーネントは、渡されるプロパティが変更された場合にのみレンダリングをトリガーするため、テスト ケースで 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/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 では、すべての機能コンポーネントは通常の関数を使用して作成されます。つまり、 これらは、 さらに、 h は 'vue' から { h } をインポートします const DynamicHeading = (props, context) => { h(`h${props.level}`, context.attrs, context.slots) を返します } DynamicHeading.props = ['レベル'] デフォルトのDynamicHeadingをエクスポートする 単一ファイルコンポーネント3.x では、ステートフル コンポーネントと機能コンポーネント間のパフォーマンスの違いが大幅に減少し、ほとんどのユース ケースで無視できるほどになりました。したがって、単一ファイル コンポーネントで <テンプレート> <コンポーネント v-bind:is="`h${$props.level}`" v-bind="$attrs" /> </テンプレート> <スクリプト> エクスポートデフォルト{ 小道具: ['レベル'] } </スクリプト> 主な違いは次のとおりです。
要約するVue.js 関数コンポーネントに関するこの記事はこれで終わりです。Vue.js 関数コンポーネントに関するより詳しい情報は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続きご覧ください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
1. MySQLインストールパッケージをダウンロードするまず、https://dev.mysql.c...
IntelliJ IDEA が Tomcat を使用して Javaweb プロジェクトをデプロイし...
Scrcpyのインストールsnap install scrcpy adbサービスのインストールsu...
関連記事:初心者が学ぶ HTML タグ (3)導入された HTML タグは、必ずしも XHTML 仕...
目次1. docker-maven-pluginの紹介2. 環境とソフトウェアの準備3. デモ例3....
1. 日付のサイズを比較するには、XML に渡される日付形式は 'yyyy-MM-dd...
この記事では、参考までに、Vue の具体的なコードで簡単な計算機を実装する方法を紹介します。具体的な...
序文MySQL の権限テーブルは、データベースの起動時にメモリにロードされます。ユーザーが ID 認...
1. ファイルを現在のディレクトリに解凍しますコマンド: tar -zxvf mysql....ta...
この記事を読む前に、Volumes について予備知識を身に付けておいてください。詳細については、こち...
1 背景最近、Shimo Document のオンライン ビジネスでパフォーマンスの問題が発生しまし...
この記事では、クリック時にサブメニューを表示するためのJavaScriptの具体的なコードを参考まで...
序文開発プロセスでは、10 進データ型がよく使用されます。 MySQL では、小数点は正確なデータ型...
以前、グループの友人が質問しました。つまり、ミニプログラムでユーザーがオンラインになったときに、ライ...
序文この記事はかなり詳細で、少し面倒です。他のチュートリアル ドキュメントでは多くの手順が省略されて...