Vueの制御および非制御コンポーネント


Vueの制御および非制御コンポーネント
Reactに詳しい開発者は「制御されたコンポーネント」の概念をよく知らないはずだが、実際にはいかなるコンポーネント化開発フレームワークにとっても、いわゆる制御されたものと非制御されたものを実現することができ、Vueも例外ではない.また、制御された非制御に対応する需要シーンを理解することで、いくつかの基礎コンポーネントを設計する際の考え方をより明確にし、露出したコンポーネントAPIもより合理的で統一することができます.
需要
多くのUIコンポーネントにはステータス(stateful)がありますが、このステータスはコンポーネントの外部制御によって維持されるか、コンポーネントの内部メンテナンスによって維持されるか、制御されているモードと非制御の2つのモードに対応しています.
例えば、Tabsコンポーネントは、現在のactiveのTabを記録し、ユーザが切り替えることを可能にするUIコンポーネントとして一般的である.
多くの場合、Tabsがactiveのコンテンツを正しく表示し、ユーザーの操作時に正常に切り替えることを望んでいます.介入する必要はありません.では、すべてのTabコンテンツを転送するだけで、追加の構成は必要ありません.
しかし、いくつかの関連するTabs、子Tabsの内容は親Tabsのactive Tabに基づいて動的に切り替える必要があり、この場合、Tabsコンポーネントが十分なAPIを露出してビジネスのニーズを実現することを望んでいる.
従って、任意のコンポーネントの任意の状態を制御されたモードと非制御の2つのモードとを同時に互換化させ、異なる需要シーンで最も合理的なAPIを使用することができる汎用的なモードを使用することができる.
簡略化の例
このような一般的なコンポーネントAPI設計モードを簡単なTabs実装で実証し,簡略化された部分は次のようなものを含む.
  • は、indexを用いてTabの一意の識別として使用する
  • .
  • Tab contentは文字列
  • のみをサポートする
    オンラインDEMOを開いて読むことができます
    API設計
    Vueコンポーネントの場合、API設計は主に内部のdata、computed、methodsおよび対外のprops、eventsを指す.この例では、activeIdxをコアステータスとして使用し、すべてのAPIもこのステータスの周りに名前を付けます.
    非制御モード
    上述したように、非制御モードとは、使用者が制御コンポーネントの形状体に関心を持つ必要がなく、コンポーネント内部で完全に維持されることを意味する.
    したがって、APIには次のものが含まれます.
    {
      props: {
        defaultActiveIdx: {
          type: Number,
          default: 0
        }
      },
      data() {
        return {
          localActiveIdx: this.defaultActiveIdx
        }
      },
      methods: {
        handleActiveIdxChange(idx) {
          this.localActiveIdx = idx;
          this.$emit("active-idx-change", idx);
        }
      }
    }
    localActiveIdxはactive indexを格納するためのコンポーネント内dataであり、非制御モードでは外部で状態を維持することは望ましくないが、外部で初期状態を決定することが望ましい可能性があるため、defaultActiveIdxというpropsでlocalActiveIdxの初期値を決定する.
    その後、v-for="(tab, idx) in tabs"命令ですべてのTabを生成すると、idx === localActiveIdxによって現在のTabがactiveであるか否かを判断し、@click="handleActiveIdxChange(idx)"によってlocalActiveIdxの更新を実現することができる.
    同様に、active Tabの内容を{{ tabs[localActiveIdx].content }}で示すこともできます.handleActiveIdxChangeのイベント処理では、active-idx-changeというイベントもemitされており、コンポーネントの状態を管理する必要がなく、コンポーネントの状態と同期を保つことができる外部を容易にすることができます.例えば、active TabをURLに反映したい場合、active-idx-changeというイベントを外部で傍受し、現在のindexをルーティングに同期し、ルーティングで取得したindexをdefaultActiveIdxとして転送すれば、URLとTabsの同期を実現することができる.
    制御モード
    制御モードの場合,active indexは外部から伝達されたpropsであり,外部が自らその状態を維持していると理解できる.
    したがって、次のpropsを追加するだけです.
    props: {
      activeIdx: Number
    }

    外部emitに対するイベントactive-idx-changeがすでに存在するため、外部は以下の方法で1つのdata属性externalActiveIdxで対応状態を維持することができる.

    もちろん、このようなモードでは外部から状態を完全に制御することができるので、active-idx-changeのイベント処理においても、例えばターゲットTabのアクティブ化が許可されるかどうかなどのより複雑な判断を行うことができる.
    Tabsコンポーネントの内部では、いくつかの小さな修正が必要です.制御モードでは、私たちのすべての状態に関連する処理はlocalActiveIdxを直接使用していますが、現在、私たちの論理は「activeIdx propsが存在する場合は、localActiveIdxを使用し、そうでない場合はidx === _activeIdxを使用する」に変わります.
    上記のロジックがコンポーネントの内部実装を複雑にし、誤りやすいように、computedプロパティを導入します.
    computed: {
      _activeIdx() {
        return this.activeIdx || this.localActiveIdx;
      }
    }

    これにより,状態に関する判断を{{ tabs[_activeIdx].content }}により1つのTabがアクティブであるか否かを判断し,handleActiveIdxChangeによりactive Tabの内容を示すことができる.
    同様に、props aciveIdxが存在する場合、localActiveIdxは更新されません.
    handleActiveIdxChange(idx) {
      if (this.activeIdx === undefined) {
        this.localActiveIdx = idx;
      }
      this.$emit("active-idx-change", idx);
    }

    より複雑なコンポーネントでは、制御モードであるか否かを頻繁に判断し、異なる処理を行う可能性があるが、this.activeIdxのようなコア状態propsが伝達されるか否かによって、制御モードであるか否かを判断することは良い実践である.
    まとめ
    最終的にactive indexのために設計された完全なAPIは以下の通りです.
    {
      props: {
        activeIdx: Number,
        defaultActiveIdx: {
          type: Number,
          default: 0
        }
      },
      data() {
        return {
          localActiveIdx: this.defaultActiveIdx
        };
      },
      computed: {
        _activeIdx() {
          return this.activeIdx || this.localActiveIdx;
        }
      },
      methods: {
        handleActiveIdxChange(idx) {
          if (this.activeIdx === undefined) {
            this.localActiveIdx = idx;
          }
          this.$emit("active-idx-change", idx);
        }
      }
    }

    このようなAPI設計方式により、我々が設計した基礎コンポーネントの使用方式をより一致させ、拡張性をより強くすることができ、開発時でも使用時でも構想がより簡潔で明確になる.