Vueでv-modelをつかってフォームコントロールのコンポーネントをつくる

## はじめに

おしゃれなフォームコントロールつくりたいですよね？

2.2.0から追加されたmodelオプション([API — Vue.js](https://jp.vuejs.org/v2/api/#model))を使えば、ビルトインのフォームコントロールを使う場合と同じようにv-modelディレクティブを利用できるようになります。

主要なフォームコントロールについて、簡単にカスタムコンポーネントをつくってみました。

## \<input type="text">

```vue
<template>
  <input type="text" v-model="internalValue">
</template>

<script>
export default {
  model: {
    prop: "value",
    event: "input"
  },
  props: {
    value: {
      type: String,
      default: ""
    }
  },
  computed: {
    internalValue: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit("input", val);
      }
    }
  }
};
</script>
```

## \<input type="checkbox">

```vue
<template>
  <label>
    <input type="checkbox" v-model="internalValue">
    {{ text }}
  </label>
</template>

<script>
export default {
  model: {
    prop: "value",
    event: "change"
  },
  props: {
    value: {
      type: Boolean,
      default: false,
    },
    text: {
      type: String,
      default: ""
    }
  },
  computed: {
    internalValue: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit("change", val);
      }
    }
  }
};
</script>

```

## \<input type="radio">


```vue
<template>
  <label>
    <input type="radio" v-model="internalValue" :value="value">
    {{ text }}
  </label>
</template>

<script>
export default {
  model: {
    prop: "checked",
    event: "change"
  },
  props: {
    checked: {
      type: String,
      default: ""
    },
    text: {
      type: String,
      default: ""
    },
    value: {
      type: String,
      default: ""
    }
  },
  computed: {
    internalValue: {
      get() {
        return this.checked;
      },
      set(val) {
        this.$emit("change", val);
      }
    }
  }
};
</script>
```

## \<select>

select要素の場合、カスタムはul,liなどを利用するかもしれませんね。

```vue
<template>
  <select v-model="selected">
    <option v-for="option in options" :value="option.value" :key="option.value">{{ option.text }}</option>
  </select>
</template>

<script>
export default {
  model: {
    prop: "value",
    event: "change"
  },
  props: {
    value: {
      type: String,
      default: ""
    },
    options: {
      type: Array,
      default() {
        return [];
      }
    }
  },
  computed: {
    selected: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit("change", val);
      }
    }
  }
};
</script>
```

## デモ

簡単に動作確認ができるページを用意しました。

https://codesandbox.io/s/vue-template-vqzt2

```vuejs
<template>
  <div class="wrapper">
    <div>
      <custom-input v-model="valueA"/>
      <p>result: {{ valueA }}</p>
    </div>
    <div>
      <custom-checkbox v-model="valueB" text="checkbox"/>
      <p>result: {{ valueB }}</p>
    </div>
    <div>
      <custom-radio v-model="valueC" value="1" text="[1]"/>
      <custom-radio v-model="valueC" value="2" text="[2]"/>
      <p>result: {{valueC}}</p>
    </div>
    <div>
      <custom-select
        v-model="valueD"
        :options="[{value: 'a', text: 'A'}, {value: 'b', text: 'B'}]"
      />
      <p>result: {{ valueD }}</p>
    </div>
  </div>
</template>

<script>
import CustomInput from "./components/CustomInput";
import CustomSelect from "./components/CustomSelect";
import CustomRadio from "./components/CustomRadio";
import CustomCheckbox from "./components/CustomCheckbox";

export default {
  data() {
    return {
      valueA: "",
      valueB: false,
      valueC: "1",
      valueD: "a"
    };
  },
  components: {
    CustomInput,
    CustomSelect,
    CustomRadio,
    CustomCheckbox
  }
};
</script>
```