Vueカスタム入力


私たちのほとんどは、カスタム入力コンポーネントを構築します.その背後には複数の理由がありますが、一般的に、カスタムスタイルを持ち、再利用することができます.
それは簡単に聞こえるかもしれませんが、いくつかのgotchasを持っていると我々はいくつかの詳細を確認するために、ドキュメントを介して行くまで時間が経つ.あなたがいくつかのVue概念に精通していないなら、それは少し複雑になります.
先月、2021年2月、再び起こった.可能なとき、私はVueスラックグループの人々を助けようとします、そして、この質問は再び現れました.この質問ではなく、カスタム入力コンポーネントの構築に問題がありました.その問題はいくつかの概念に関連していた.
自分自身のためにこの知識を統合し、他の人のためのドキュメントとしてそれを使用するために、私はカスタム入力を書くプロセスをラップすることを決めた.
目次
  • v-model and <input>
  • The wrong custom input component

  • The happy custom input component
  • Adding validation (or operation on data change)
  • Combining computed and v-model
  • Extra: the model property
  • So what?

  • Vモデル<input>一旦我々がVueでフォームを構築し始めるならば、我々は指令を学びますv-model . それは私たちのための多くのハードワークを行います:それは入力に値をバインドします.これは、入力の値を変更するたびに、変数も更新されることを意味します.
    公式ドキュメントは、どのように動作するかを説明する素晴らしい仕事です.https://vuejs.org/v2/guide/forms.html
    手短に言えば、以下のテンプレートがあります.
    <!-- UsernameInput.vue -->
    <template>
      <label>
        Username
        <input type="text" name="username" v-model="username">
      </label>
    </template>
    
    <script>
    export default {
      name: 'UsernameInput',
      data() {
        return {
          username: 'Initial value',
        };
      },
    }
    </script>
    
    私たちには、ある入力がありますInitial value 初期値とユーザー名のデータが自動的に入力された値を変更すると自動的に更新されます.
    上記のコンポーネントの問題は、再利用できないことです.私たちがユーザ名と電子メールを必要とするページを持っていると想像してください.上記のコンポーネントは、データがコンポーネント自体の中にあるので、電子メールケースを処理しません.それはカスタム入力コンポーネントが輝き、またその課題の一つですv-model 一貫性のある行動

    間違ったカスタム入力コンポーネント
    さて、なぜ私はこの例を示していますか?答えはです:これは私たちのほとんどは、しようとする最初のアプローチです.
    カスタム入力コンポーネントの使い方を見てみましょう.
    <!-- App.vue -->
    <template>
      <custom-input :label="label" v-model="model" />
    </template>
    
    <script>
    import CustomInput from './components/CustomInput.ue';
    
    export default {
      name: 'App',
      components: { CustomInput },
      data() {
        return {
          label: 'Username',
          model: '',
        };
      },
    }
    </script>
    
    カスタム入力はlabelv-model この場合、以下のコンポーネントのようになります.
    <!-- CustomInput.vue -->
    <template>
      <label>
        {{ label }}
        <input type="text" :name="name" v-model="value" />
      </label>
    </template>
    
    <script>
    export default {
      name: 'CustomInput',
      props: {
        label: {
          type: String,
          required: true,
        },
        value: {
          type: String,
          required: true,
        },
      },
      computed: {
        name() {
          return this.label.toLowerCase();
        },
      },
    }
    </script>
    
    まず、それはlabel プロパティとしてname その上に(また、プロパティである可能性があります).第二に、それはvalue プロパティを<input> 通しv-model . その理由はわかるin the docs しかし、短いとき、我々が使うときv-model カスタムコンポーネントでvalue からの値であるプロパティとしてv-model 変数.この例では、model で定義App.vue .
    上記のコードを試してみると、予想通りに動作しますが、なぜ間違っていますか?コンソールを開くと、次のようになります.
    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
    
    我々は財産を変異していると文句を言う.Vueが機能する方法:子コンポーネントは親コンポーネントから派生した子要素を持ち、子コンポーネントは親コンポーネントへの変更を表します.使用v-modelvalue 親コンポーネントから得たpropはそれに違反します.
    この問題を見るもう一つの方法はApp.vue このように:
    <!-- App.vue -->
    <template>
      <custom-input :label="label" :value="model" />
    </template>
    
    ...
    
    主な違いは:value の代わりにv-model . この場合、我々はちょうど合格しているmodelvalue プロパティ.この例はまだ動作し、コンソールで同じメッセージを取得します.
    次のステップは上記の例を再加工し、期待通りに動作するようにすることです.

    幸せなカスタム入力コンポーネント
    幸せなカスタム入力コンポーネントは、そのプロップを変異せず、親コンポーネントへの変更を発行します.
    The docs この正確な例がありますが、もう少しここに行きます.我々が医者に続くならばCustomInput 以下のようになります.
    <!-- CustomInput.vue -->
    <template>
      <label>
        {{ label }}
        <input type="text" :name="name" :value="value" @input="$emit('input', $event.target.value)" />
      </label>
    </template>
    
    <script>
    export default {
      name: 'CustomInput',
      props: {
        label: {
          type: String,
          required: true,
        },
        value: {
          type: String,
          required: true,
        },
      },
      computed: {
        name() {
          return this.label.toLowerCase();
        },
      },
    }
    </script>
    
    これで作業ができる.両方に対してもApp.vue , 使用するものv-model , すべてが期待通りに動作し、使用するもの:value 私たちがプロパティを突然変異するのを止めたので、それがもう働きません.

    検証の追加
    データが変化したとき、何かをする必要がある場合には、例えば、空で、エラーメッセージを表示するかどうかを確認してください.以下のコンポーネントを変更します.
    <!-- CustomInput.vue -->
    <template>
    ...
        <input type="text" :name="name" :value="value" @input="onInput" />
    ...
    </template>
    
    <script>
    ...
      methods: {
        onInput(event) {
          this.$emit('input', event.target.value);
        }
      }
    ...
    </script>
    
    次に空のチェックを追加します.
    <!-- CustomInput.vue -->
    <template>
    ...
        <p v-if="error">{{ error }}</p>
    ...
    </template>
    
    <script>
    ...
      data() {
        return {
          error: '',
        };
      },
    ...
        onInput(event) {
          const value = event.target.value;
    
          if (!value) {
            this.error = 'Value should not be empty';
          }
    
          this.$emit('input', event.target.value)
        }
    ...
    </script>
    
    これは、作品の種類は、最初のエラーが表示されませんし、入力すると、エラーメッセージが表示されます削除します.問題は、エラーメッセージが消えないことです.これを修正するには、値プロパティにWatcherを追加し、更新されるたびにエラーメッセージを消去する必要があります.
    <!-- CustomInput.vue -->
    ...
    <script>
    ...
      watch: {
        value: {
          handler(value) {
            if (value) {
              this.error = '';
            }
          },
        },
      },
    ...
    </script>
    
    我々は、類似した結果をelse 内部onInput . Watcherを使用すると、ユーザーが入力値を更新する前に有効にすることができます.
    私たちがより多くのものを加えるならば、我々は最も多分この構成要素を拡大していて、ものはすべての上に広げられます<script> ブロック.物事をグループ化するために、我々は異なるアプローチを試みることができますv-model .

    計算とv-modelその代わりにinput イベントとして再び放出すると、我々は力を活用することができますv-model and computed . それは我々が間違ったアプローチを得ることができる最も近いが、まだそれを右にする😅
    そのようなコンポーネントを書き直しましょう.
    <!-- CustomInput.vue -->
    <template>
    ...
        <input type="text" :name="name" v-model="model" />
    ...
    </template>
    
    <script>
    ...
      computed: {
        ...
        model: {
          get() {
            return this.value;
          },
          set(value) {
            this.$emit('input', value);
          },
        },
      },
    ...
    </script>
    
    我々は、取り除くことができますonInput メソッドとウォッチャーから我々はすべての内のすべてを扱うことができますget/set 計算されたプロパティからの関数.
    我々がそれを達成することができる1つの涼しいことは修飾子の使用です.trim/number これは手動で書かなければなりません.
    これは、簡単な入力コンポーネントの良いアプローチです.物事は、より複雑になることができますし、このアプローチでは、すべてのユースケースを満たすことはありません.良い例は、あなたが.lazy 親コンポーネントの修飾子を手動で聞く必要がありますinput and change .

    エキストラmodel プロパティ
    The model property ビットをカスタマイズできますv-model 動作.どのプロパティをマップするかを指定することができますvalue , そして、どのイベントが放出されるか、デフォルトはそうですinput or change.lazy を使用する.
    この場合は、value 何か他のもののために、それが特定の前後関係のためにより多くの感覚を作るかもしれないか、ちょうどより明白なものを作りたいです、そして、名前を変えてくださいvalue to model , 例えば.ほとんどの場合、入力をオブジェクトとして取得するときにチェックボックス/ラジオをカスタマイズするのに使うかもしれません.

    何?
    私のテイクは、カスタム入力がどの程度複雑になるかによるものです.
  • それは1つのコンポーネントのスタイルを集中化するためにつくられました、そして、APIはかなりVueのAPIの上にあります:computed + v-model . それは我々の例でかなり落ちます、それは単純な小道具と複雑な合法化を持ちません.
  • <!-- CustomInput.vue -->
    <template>
      <label>
        {{ label }}
        <input type="text" :name="name" v-model="model" />
      </label>
    </template>
    
    <script>
    export default {
      name: 'CustomInput',
      props: {
        label: {
          type: String,
          required: true,
        },
        value: {
          type: String,
          required: true,
        },
      },
      computed: {
        name() {
          return this.label.toLowerCase();
        },
        model: {
          get() {
            return this.value;
          },
          set(value) {
            this.$emit('input', value);
          },
        },
      },
    }
    </script>
    
  • 他のすべて(これはあなたが必要なものをサポートするために多くの前のセットアップを微調整する必要があることを意味します):リスナー、ウォッチャーと他の必要があります.複数の状態を持っているかもしれません(ロード状態が役に立つかもしれないasync検証を考えます)、あるいは、あなたは支持したいかもしれません.lazy 親コンポーネントからの修飾子は、最初のアプローチを避ける良い例です.
  • <!-- CustomInput.vue -->
    <template>
      <label>
        {{ label }}
        <input type="text" :name="name" :value="value" @input="onInput" @change="onChange" />
      </label>
    </template>
    
    <script>
    export default {
      name: 'CustomInput',
      props: {
        label: {
          type: String,
          required: true,
        },
        value: {
          type: String,
          required: true,
        },
      },
      /* Can add validation here
      watch: {
        value: {
          handler(newValue, oldValue) {
    
          },
        },
      }, */
      computed: {
        name() {
          return this.label.toLowerCase();
        },
      },
      methods: {
        onInput(event) {
          // Can add validation here
          this.$emit('input', event.target.value);
        },
        onChange(event) { // Supports .lazy
          // Can add validation here
          this.$emit('change', event.target.value);
        },
      },
    }
    </script>
    
    それを見直してくれてありがとう