レンダリング関数を使用して、拡張性の高いコンポーネントをカプセル化する

レンダリング関数を使用して、拡張性の高いコンポーネントをカプセル化する

必要:

バックグラウンド管理では、次のようなレイアウトでデータを表示する必要があることがよくあります。

これはテーブルのように見えますがテーブルではなく、フォームのように見えますがフォームではありません。実際、これはテーブルのように見え、表示されるデータはオブジェクトであり、フォームのバインドされた値と同じです。私はこれをフォームスタイルのテーブルと呼んでいます。

深いスタイルの列はタイトルで、浅いスタイルの列はタイトルに対応する値です。データはサーバーから返されることが多く、タイトルは固定幅であることが多いです。値は異なる場合があります。たとえば、画像を表示する場合、値は01で、はいまたはいいえを表示する必要があります。ユーザーが特定の値を変更できるように変更ボタンを追加する必要があり、列を複数の列にまたがるように設定する必要もあります。

まず、 element uiに基づく実装を見てみましょう。

不適切な実装:

引き継いだプロジェクトで実装を見ました。まずは使い方を見てみましょう。

<FormTable :data="lessonPackageArr" :fleldsInfo="lessonPackageInfo" :maxColumn="3" label-width="120px">
  <テンプレート #presentedHours="{データ}">
    <div class="flex-box between">
      <span>
        {{ data.presentedHours }}
      </span>
      <span class="column-btn" @click="editPresentedHours(data)">編集</span>
    </div>
  </テンプレート>
  <テンプレート #gifts="{データ}">
    <div class="flex-box between">
      <span>
        {{データ.ギフト}}
      </span>
      <span class="column-btn" @click="editPresentedHours(data)">編集</span>
    </div>
  </テンプレート>
</フォームテーブル>


lessonPackageInfo オブジェクトの構造は次のとおりです。

// タイトル列とタイトル列に対応するフィールドを構成するために使用されるオブジェクト // type は値のタイプを指定します。これで、コンポーネントは内部的にどのタイプの値を表示できるかを設定します // サービスの場合、1 0 を返します。数値を表示する必要がある場合は、列をまたいでマップする map_data を提供します // 列属性設定を列間でマッピングします // スロットを提供するには、表示内容をカスタマイズする必要があります
レッスンパッケージ情報: {
    orderType: { type: 'option', desc: 'コースパッケージカテゴリ', map_data: { 1: '初回注文', 2: '更新', 5: '無料コース' } },
    combo: { type: 'text', desc: 'パッケージ名' },
    presentationHours: { type: 'text', desc: '無料授業時間', slot: true },
    価格: { タイプ: 'テキスト'、説明: '標準価格' },
    ギフト: { タイプ: 'text'、説明: 'ギフト'、列: 3、スロット: true },
  }


  • Propsは直感的ではなく、設定項目が多い
  • 完全にデータ駆動型ではない

コンポーネントの構成項目が多すぎるとなぜ悪いのでしょうか?

この要件は非常に固定的です。コンポーネントの入力、つまりpropsは最小限に抑え、コンポーネントの機能は最大化し、 props には可能な限りデフォルト値を提供して、チームの開発効率を向上させる必要があります。

完全にデータ駆動型でないことがなぜ悪いのでしょうか?

このコンポーネントは完全にデータ駆動型ではありません。表示列をカスタマイズする必要がある場合は、テンプレートを作成する必要があります。

カスタマイズが必要な列が多数ある場合、テンプレートコードをたくさん記述する必要があります。再度抽出する場合は、コンポーネントを再度カプセル化するしかありません。抽出しない場合は、テンプレートコードが拡張される可能性があります。1行あたり500行のtemplateをよく見かけるのではないでしょうか。拡張されたテンプレート コードによりコンポーネントのメンテナンスが困難になり、 templateと js コードを何度も切り替える必要が生じます。さらに、カスタム データの列を追加するには、少なくとも 2 か所を変更する必要があります。

なぜ完全にデータ駆動型になる必要があるのでしょうか?

コンポーネントを拡張するためのスロットはありますが、ビジネス コンポーネントを作成するときはそれらをあまり使用せず、代わりにデータ駆動型テンプレートを使用するようにしてください。データは js コードなので、コンポーネント コードが展開された場合、js コードを別のファイルに抽出するのは簡単です。スロット コードを抽出したい場合は、コンポーネントを再度カプセル化するだけです。

3 つの主要なフロントエンド フレームワークの設計コンセプトはデータ駆動型テンプレートです。これは、jQuery との違いを示す重要な機能であり、ビジネス コンポーネントをカプセル化するときに最初に従う原則でもあります。

コンポーネントの使用に関する問題を確認した後、コンポーネント コードを確認しましょう。

<テンプレート>
  <div v-if="tableData.length" class="form-table">
    <div v-for="(data, _) in tableData" :key="_" class="table-border">
      <el-row v-for="(row, index) 行内" :key="index">
        <el-col v-for="(フィールド、キー) 行内" :key="キー" :span="getSpan(field.column)">
          <div v-if="(field.disabled && data[key]) || !field.disabled" class="column-content flex-box between">
            <div class="label" :style="'width:' + labelWidth">
              <span v-if="field.required" class="required">*</span>
              {{フィールド.desc}}
            </div>
            <div class="text flex-item" :title="データ[キー]">
              <テンプレート v-if="key === 'minAge'">
                <span>{{ データ[キー] }}</span>
                -
                <span>{{ データ['maxAge'] }}</span>
              </テンプレート>
              <テンプレート v-else-if="キー === 'ステータス'">
                <テンプレート v-if="フィールド.ステータスリスト">
                  <span v-if="data[key] == 0" :class="field.statusList[2]">{{ field.map_data[data[key]] }}</span>
                  <span v-else-if="データ[キー] == 10 || データ[キー] == 34" :class="field.statusList[1]">
                    {{ field.map_data[データ[キー]] }}
                  </span>
                  <span v-else :class="field.statusList[0]">{{ field.map_data[data[key]] }}</span>
                </テンプレート>
                <span v-else>{{ field.map_data[データ[キー]] }}</span>
              </テンプレート>

              <スロット v-else :name="キー" v-bind:data="データ">
                <テーブル列コンテンツ
                  :dataType="フィールドタイプ"
                  :metaData="データ[キー]"
                  :mapData="フィールド.map_data"
                  :text="フィールドテキスト"
                />
              </スロット>
            </div>
          </div>
        </el-col>
      </el-row>
    </div>
  </div>
  <div v-else class="form-table empty">データがありません</div>
</テンプレート>

<スクリプト>
  '@/components/TableColContent' から TableColContent をインポートします。
  エクスポートデフォルト{
    名前: 'FormTable',
    コンポーネント:
      表列コンテンツ、
    },
    小道具: {
      // データ: {
        必須: true、
        タイプ: [オブジェクト、配列、null],
      },
      // フィールド情報 fledsInfo: {
        必須: true、
        タイプ: オブジェクト、
        // className: { type: "text", desc: "クラス名", column: 3 },
      },
      // 表示する列の最大数 maxColumn: {
        必須: false、
        タイプ: 数値、
        デフォルト: 2,
      },
      ラベル幅: {
        必須: false、
        タイプ: 文字列、
        デフォルト: '90px'、
      },
    },
    データ() {
      戻る {}
    },
    計算: {
      テーブルデータ() {
        if (!this.data) {
          戻る []
        }
        if (this.data インスタンスの配列) {
          this.dataを返す
        } それ以外 {
          [this.data] を返す
        }
      },
      行() {
        定数戻り配列 = []
        合計を0にする
        アイテムを{}とします
        (this.fleldsInfo の定数キー) {
          const nextTotal = 合計 + this.fleldsInfo[キー].column || 1
          if (nextTotal > this.maxColumn) {
            returnArray.push(アイテム)
            アイテム = {}
            合計 = 0
          }
          合計 += this.fleldsInfo[キー].column || 1
          item[キー] = this.fleldsInfo[キー]
          合計 === this.maxColumn の場合 {
            returnArray.push(アイテム)
            アイテム = {}
            合計 = 0
          }
        }
        if (合計) {
          returnArray.push(アイテム)
        }
        戻り値 returnArray
      },
    },
    メソッド: {
      getSpan(列) {
        if (!列) {
          列 = 1
        }
        列を返す * (24 / this.maxColumn)
      },
    },
  }
</スクリプト>

質問は何ですか?

  • テンプレートには条件判断が多すぎて、エレガントではありません
  • 表示列をカスタマイズするには、 TableColContentも導入する必要があり、これによりコンポーネントの複雑さが増します。

TableColContent依然として構成項目の種類に応じて条件判断を実行します

コードの一部:

<span v-else-if="dataType === 'image' || dataType === 'cropper'" :class="className">
  <el-popover placement="right" title="" trigger="hover">
    <img :src="metaData" style="max-width: 600px;" />
    <img slot="reference" :src="metaData" :alt="metaData" width="44" class="column-pic" />
  </el-popover>
</span>


上記の実装上の問題を分析した後、適切な実装を見てみましょう。

良い実装:

まずは使い方を見てみましょう:

<テンプレート>
  <ZmFormTable :titleList="タイトルリスト" :data="データ" />
</テンプレート>
<スクリプト>
  エクスポートデフォルト{
    名前: 'テスト'、
    データ() {
      戻る {
        data: {}, // サーバーからtitleListを取得します: [
          { タイトル: '名前'、プロパティ: '名前'、スパン: 3 },
          {
            タイトル: 「教室作品」
            プロパティ: (h, データ) => {
              定数画像 =
                (データ.workPic && (
                  <エルイメージ
                    スタイル='幅: 100px; 高さ: 100px;'
                    src={data.workPic}
                    プレビューソースリスト={[data.workPic]}
                  </ElImage>
                )) ||
                ''
              画像を返す
            },
            スパン: 3,
          },
          { title: '仕事のコメント', prop: 'workComment', span: 3 },
        ]、
      }
    },
  }
</スクリプト>


コンポーネントの説明: titleListは、コンポーネントの列構成、配列です。要素の title 属性はタイトル、prop はデータから取得するフィールドを指定し、span はこの列値が及ぶ行数を指定します。

prop はstringと関数をサポートしており、カスタム表示を実現できます。この関数が非常に大きい場合は、別の js ファイルに抽出することも、 titleList全体を別の js ファイルに抽出することもできます。

パラメータ h と data はどのように渡されるのでしょうか?あるいは、この関数はどこで呼び出されるのでしょうか?

h はcreateElement関数、 dataコンポーネント内部のデータであり、親コンポーネントから渡されたデータと同じ値です。

通常の関数の最初の引数が h の場合、それはrender関数です。

この方法の方がはるかに簡単に使用できます。

内部実装を見てみましょう:

 <テンプレート>
  <div class="フォームテーブル">
    <ul v-if="タイトルリスト.長さ">
      <!-- titleInfo は変換された titleList です-->
      <li
        v-for="(item, index) in titleInfo"
        :key="インデックス"
        :style="{ 幅: ((item.span || 1) / titleNumPreRow) * 100 + '%' }"
      >
        <div class="form-table-title" :style="`width: ${titleWidth}px;`">
          <Container v-if="typeof item.title === 'function'" :renderContainer="item.title" :data="data" />
          <span v-else>
            {{ item.title }}
          </span>
        </div>
        <div class="form-table-key" :style="`width:calc(100% - ${titleWidth}px);`">
          <Container v-if="typeof item.prop === 'function'" :renderContainer="item.prop" :data="data" />
          <span v-else>
            {{ ![null, void 0].includes(data[item.prop] && data[item.prop]) || '' }}
          </span>
        </div>
      </li>
    </ul>
    <div v-else class="form-table-no-data">データなし</div>
  </div>
</テンプレート>

<スクリプト>
  './container.js' からコンテナをインポートします。
  エクスポートデフォルト{
    名前: 'FormTable',
    コンポーネント:
      容器、
    },
    小道具: {
      タイトル幅: {
        タイプ: 数値、
        デフォルト: 120、
      },
      タイトル番号前行: {
        タイプ: 数値、
        デフォルト: 3,
        バリデータ: 値 => {
          const 検証 = [1, 2, 3, 4, 5, 6].includes(値)
          検証する場合
            console.error('titleNumPreRow は、行にタイトル フィールド ペアがあることを示します。これは 1 から 6 までの偶数のみで、デフォルトは 3 です')
          }
          検証を返す
        },
      },
      タイトルリスト: {
        タイプ: 配列、
        デフォルト: () => {
          戻る []
        },
        バリデータ: 値 => {
          const 検証 = value.every(item => {
            const { タイトル、プロパティ } = アイテム
            タイトルとプロパティを返す
          })
          検証する場合{
            console.log('渡された titleList 属性の要素には title 属性と prop 属性が含まれている必要があります')
          }
          検証を返す
        },
      },
      データ: {
        タイプ: オブジェクト、
        デフォルト: () => {
          戻る {}
        },
      },
    },
  }
</スクリプト>
<!-- スタイルは重要ではないので省略-->

カスタム ディスプレイを実装する方法は、動的スロットを使用するのではなく、 render関数をpropとして受け取る機能コンポーネントContainerを使用することです。

エクスポートデフォルト{
  名前: 'コンテナ'、
  機能的: 真、
  レンダリング(h, {props}) {
    props.renderContainer(h, props.data) を返します。
  },
}

Container内のtitleListによって渡された関数を呼び出します。

要約:

  • コンポーネントをパッケージ化する際にデータ駆動を優先する
  • 通常の関数の最初のパラメータはレンダリング関数である h です。
  • JSX の書き方に慣れていない人もいるかもしれませんが、どちらの書き方も互換性があります。

レンダリング関数を使用して、拡張性の高いコンポーネントをカプセル化する方法についての記事はこれで終わりです。レンダリング関数を使用して、拡張性の高いコンポーネントをカプセル化する方法についての関連コンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • VUE レンダリング機能の使い方と詳細な説明
  • VueのRender関数
  • Vue.jsのレンダリング関数の使い方の詳しい説明
  • Vue のレンダリング関数を使用してサブコンポーネントの参照操作を設定する
  • Vue レンダリング関数は img の src パス操作を動的に読み込みます
  • Vue Render関数の原理とコード例の分析
  • Vueのレンダリング機能の詳細な説明

<<:  MySQL マルチバージョン同時実行制御 MVCC の基本原理の分析

>>:  LinuxシステムにDockerをインストールするプロセス

推薦する

mysql 8.0.18 mgr のインストールと切り替え機能

1. システムインストールパッケージ yum -y インストール make gcc-c++ cmak...

メンテナンス可能なJSコードの書き方を教えます

目次保守可能なコードとは何ですか?コード規約1. 読みやすさ2. 変数と関数の命名3. 透過的な変数...

CentOS6.8 は cmake を使用して MySQL5.7.18 をインストールします。

オンライン情報を参考に、cmakeを使用してCentOS6.8サーバーにMySQL5.7.18をイン...

MySQL 5.5.27 インストール グラフィック チュートリアル

1. MYSQLのインストール1. ダウンロードしたMySQLインストールファイルmysql-5.5...

シンプルなドラッグ効果を実現するJavaScript

この記事では、ドラッグ効果を実現するためのJavaScriptの具体的なコードを参考までに紹介します...

Ubuntu 18.04 は pyenv、pyenv-virtualenv、virtualenv、Numpy、SciPy、Pillow、Matplotlib をインストールします

1. 現在、Pythonのバージョン管理ツールは数多く存在します。その中でも比較的使いやすいのがPy...

MySQL で複数の主キーが定義されているエラーの解決方法

主キーを作成するには 2 つの方法があります。 テーブルテーブル名を作成( フィールド名タイプ、 フ...

MySQL で大量のデータ (数千万) を素早く削除するためのいくつかの実用的なソリューションの詳細な説明

著者は最近、仕事でパフォーマンスのボトルネックの問題に遭遇しました。MySQL テーブルには毎日約 ...

nginxで静的リソースを公開する方法

ステップ準備した静的リソースファイルを指定されたフォルダに配置しますnginx 設定ファイルを変更す...

MySQL で '%' を含むフィールドをクエリする方法の詳細な説明 (ESCAPE の使用法)

SQLのlike文では、例えば SELECT * FROM user WHERE username...

IE6のバグと修正は予防戦略です

元記事:究極の IE6 チートシート: 25 以上の Internet Explorer 6 のバグ...

MySQL インデックス プッシュダウンの詳細

目次1. 左端接頭辞原則2. 表に戻る3. インデックスプッシュダウン序文:インデックス プッシュダ...

JavaScriptのURLオブジェクトとは何かについて話しましょう

目次概要ハッシュプロパティホストプロパティホスト名属性Href属性起源のプロパティユーザー名とパスワ...

JavaScript で支払いの 10 秒カウントダウンを実現

この記事では、支払いの10秒カウントダウンを実現するためのJavaScriptの具体的なコードを参考...

img 画像タグに alt 属性を付与する必要がありますか?

img 画像タグに alt 属性を追加しますか?画像 img タグの alt 属性を見落とすことはよ...