VueのInput周りの用法
チェックボックス、ラジオボタン、複数セレクト、テキスト入力など。
inputs.vue
<template> <div id="app"> <h4>Check Boxes</h4> <input type="checkbox" id="checkbox" v-model="checked" /> <label for="checkbox">This box is {{ checked ? 'checked' : 'unchecked' }}</label> <hr /> <h4>Radio Buttons</h4> <div v-for="(dino, index) in dinos" :key="index"> <label> <input type="radio" :value="dino" v-model="chosenDino" /> {{ dino }} </label> <br /> </div> <span>Favorite: {{ chosenDino }}</span> <hr /> <h4>Multi Select:</h4> <select v-model="selected" multiple> <option v-for="(period, index) in periods" :value="period.value" :key="index" >{{ period.name }}</option> </select> <br /> <span>Selected IDs: {{ selected }}</span> <hr /> <h4>Text Input:</h4> <input type="text" v-model="single" /> <p>{{ single }}</p> <hr /> <h4>Multiline message:</h4> <textarea v-model="message" placeholder="add multiple lines"></textarea> <p style="white-space: pre">{{ message }}</p> </div> </template> <script> import "./dark.min.css"; export default { data() { return { checked: false, selected: [], chosenDino: "", single: "", message: "", dinos: ["Triceratops", "Velociraptor", "Tyrannosaurus"], periods: [ { name: "Triassic", value: 1 }, { name: "Jurassic", value: 2 }, { name: "Cretaceous", value: 3 } ] }; }, methods: { addDinos: function() { this.count += this.amount; } } }; </script>
Vueのdata, computed, methods, watch, filters全部入りでお買い物リスト
たまに見返したいので。dark.min.css
は下記のものを使用。
tobuy.vue
<template> <div> <h1>Vue tobuy list</h1> <form @submit.prevent="addItem" autocomplete="off"> <input type="text" v-model="itemToAdd" @keypress.enter="addItem" /> <button>{{ buttonText }}</button> </form> <ul class="todo" style="list-style: none; padding-left: 0;"> <li v-for="(item, index) in items" :key="index"> <label> <button @click="deleteItem(index)">×</button> <span style="margin-right: 10px;">{{ item.text | capitalize }}</span> <button @click="decrease(index)">−</button> <span style="margin-right: 6px;">{{ item.quantity }}</span> <button @click="increase(index)">+</button> </label> </li> </ul> <div v-if="itemTotalAmount > 0">Total items in cart: {{ itemTotalAmount }}</div> <div v-else>There is no items!</div> </div> </template> <script> import "./dark.min.css"; import _ from "lodash"; export default { data() { return { itemToAdd: "", buttonText: "Add item", items: [ { text: "apple", quantity: 1 }, { text: "banana", quantity: 1 }, { text: "clementine", quantity: 1 } ] }; }, computed: { itemTotalAmount() { return this.items.reduce((prev, curr) => prev + curr.quantity, 0); } }, methods: { addItem() { const itemToAdd = this.itemToAdd.toLowerCase(); const matchedItemIndex = this.items.findIndex( item => item.text.toLowerCase() === itemToAdd ); if (matchedItemIndex !== -1) { this.items[matchedItemIndex].quantity += 1; return; } this.items.push({ text: itemToAdd, quantity: 0 }); }, deleteItem(index) { this.items.splice(index, 1); }, increase(index) { this.items[index].quantity += 1; }, decrease(index) { if (this.items[index].quantity === 1) { this.deleteItem(index); return; } this.items[index].quantity -= 1; } }, watch: { itemToAdd: _.debounce(function() { this.buttonText = this.itemToAdd !== "" ? `Add ${this.itemToAdd}` : "Add item"; }, 100) }, filters: { capitalize(value) { if (!value) return ""; const newValue = value.toString(); return newValue.charAt(0).toUpperCase() + newValue.slice(1); } } }; </script>
Vueでform validation
フォームの初期状態
下記のようなファイル構成。
. ├── app.js └── index.html
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue form validation</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <script src="https://unpkg.com/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div class="container"> <h1>Vue form validation</h1> <form @submit.prevent="submitForm" autocomplete="off"> <div class="form-group"> <label for="name">Name(Only string):</label> <input v-model="form.name" class="form-control" id="name"> <p v-if="!nameIsValid" class="error-message">The name field is required</p> </div> <div class="form-group"> <label for="age">Age(Between 18 and 108):</label> <input v-model="form.age" class="form-control" id="age"> <p v-if="!ageIsValid" class="error-message">The age field is invalid</p> </div> <div class="form-group form-group-last"> <button class="btn btn-primary" :disabled="!formIsValid">Submit</button> <p v-show="form.sent" class="mt-2 text-info">Form has been successfully sent!</p> </div> </form> </div> </div> <script src="app.js"></script> </body> </html>
app.js
/* global Vue */ // eslint-disable-next-line no-unused-vars const app = new Vue({ el: '#app', data: { form: { name: null, age: null, sent: false } }, computed: { nameIsValid () { return !Number(this.form.name) && !!this.form.name }, ageIsValid () { return !!Number(this.form.age) && this.form.age >= 18 && this.form.age <= 108 }, formIsValid () { return this.nameIsValid && this.ageIsValid } }, methods: { submitForm () { if (!this.formIsValid) return this.form.sent = true } } })
内容としては、名前と年齢を入力してフォームを送信するというシンプルなもの。 名前には数字は入力できず、年齢には18-108間の数字のみが入力できる。
できるだけhtmlのinput側では制御せず、vueの機能を用いてリファクタリングしていってみる。
まずはinputへの型指定だが、v-model
に修飾子を付けることで行うことができる。
v-model.number
で数値型に変換してくれるので、数値になってるかどうかのチェックが不要になる。
Before
<input v-model.number="form" class="form-control" id="age">
ageIsValid () { return !!Number(this.form.age) && this.form.age >= 18 && this.form.age <= 108 },
After
<input v-model.number="form.age" class="form-control" id="age">
ageIsValid () { return this.form.age >= 18 && this.form.age <= 108 },
しかしながら、初期状態でエラー文言が掲出されたままであったりして、まだこのままでは使い勝手がよくない。 一つひとつのエラー文言のハンドリングをしようとすると手間がかかってしまう。
そこで、Vueでvalidateのできるライブラリを用いる。
vuelidateを用いてリファクタリング
appVuelidate.vue
<template> <div id="app"> <div class="container"> <h1>Vue form validation</h1> <form @submit.prevent="submitForm" autocomplete="off"> <!-- To validate on input synchronously, remove @blur and add v-model.trim="$v.form.name.$model" --> <!-- $error === $invalid && $dirty --> <div class="form-group"> <label for="name">Name(Only string):</label> <input v-model.trim="form.name" @blur="$v.form.name.$touch()" :class="{ 'input-error': $v.form.name.$error, 'input-valid': !$v.form.name.$invalid }" class="form-control" id="name" /> <template v-if="$v.form.name.$error"> <p v-if="!$v.form.name.alpha" class="text-danger" >The name field should be alphabet characters</p> <p v-else class="text-danger">The name field is required</p> </template> <p class="text-info" >Invalid: {{ $v.form.name.$invalid }} | Dirty: {{ $v.form.name.$dirty }} | Error: {{ $v.form.name.$error }}</p> </div> <div class="form-group"> <label for="age">Age(Between 18 and 108):</label> <input v-model.number.trim="form.age" @blur="$v.form.age.$touch()" :class="{ 'input-error': $v.form.age.$error, 'input-valid': !$v.form.age.$invalid }" class="form-control" id="age" /> <template v-if="$v.form.age.$error"> <p v-if="!$v.form.age.required" class="text-danger">The age field is required</p> <p v-else-if="!$v.form.age.integer" class="text-danger" >The age field should be an integer</p> <p v-else-if="!$v.form.age.between" class="text-danger" >You should be at least 18 and younger than 108 to coutinue</p> </template> <p class="text-info" >Invalid: {{ $v.form.age.$invalid }} | Dirty: {{ $v.form.age.$dirty }} | Error: {{ $v.form.age.$error }}</p> </div> <button :disabled="$v.form.$invalid" class="btn btn-primary">Submit</button> <p v-show="form.sent" class="mt-2 text-success">Form has been successfully sent!</p> </form> </div> </div> </template> <script> import "bootstrap/dist/css/bootstrap.min.css"; import { validationMixin } from "vuelidate"; import * as validators from "vuelidate/lib/validators"; export default { data() { return { form: { name: null, age: null, sent: false } }; }, mixins: [validationMixin], validations: { form: { name: { required: validators.required, alpha: validators.alpha }, age: { required: validators.required, integer: validators.integer, between: validators.between(18, 108) } } }, methods: { submitForm() { this.$v.form.$touch(); if (!this.$v.form.$invalid) { this.form.sent = true; return; } this.form.sent = false; } } }; </script> <style> #app .input-valid { border-color: rgba(126, 239, 104, 0.8); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(126, 239, 104, 0.6); } #app .input-error { border-color: tomato; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px tomato; } </style>
VeeValidateを用いてリファクタリング
appVeeValidate.vue
<template> <div class="container"> <h1>Vue form validation</h1> <form @submit.prevent="submitForm" autocomplete="off"> <div class="form-group"> <label for="name">Name(Only alphabetic characters):</label> <input v-model.trim="form.name" v-validate="'alpha_spaces'" name="name" class="form-control" id="name" /> <p class="text-danger">{{ errors.first('name') }}</p> </div> <div class="form-group"> <label for="age">Age(Between 18 and 108):</label> <input v-model.number.trim="form.age" v-validate="'between:18,108'" name="age" class="form-control" id="age" /> <p class="text-danger">{{ errors.first('age') }}</p> </div> <button class="btn btn-primary">Submit</button> </form> </div> </template> <script> import { ValidationProvider } from "vee-validate"; export default { data() { return { form: { name: null, age: null, sent: false } }; }, components: { ValidationProvider }, methods: { submitForm() {} } }; </script>
Vueでrapid prototyping
rapid prototyping setup
VueのSFC (Single File Component) で軽く何かを試したいとき、いちいちvue-cli
を使うのは面倒。
そこでvue-cli
の一部機能であるcli-service-global
を使うことで、rapid prototypingができて便利。
vue-cli
のインストール
まずはVue CLIを先にインストールする。
npm install -g @vue/cli
// or
yarn global add @vue/cli
cli-service-global
のインストール
npm install -g @vue/cli-service-global
// or
yarn global add @vue/cli-service-global
cli-service-global
の実行
vue serve app.vue
TypeScript学習5(Classes)
Classes
Blueprint to create an object with some fields (values) and methods (functions) to represent a 'thing'.
class Vehicle { drive(): void { console.log('chugga chugga'); } honk(): void { console.log('beep'); } } class Car extends Vehicle { drive(): void { console.log('vroom'); } } const car = new Car(); car.drive(); car.honk();
上記のようにES2015のClassと同様に働くが、TSならではのmodifierが存在する。
Modifiers
public
This method can be called anywhere, anytime.
private
This method can only be called by other methods in this
class.
privateは決してセキュリティ目的で使用されるものではなく、メソッドが使用されるスコープを制限するためだからである。
protected
This method can be called by other methods in this
class, or by other methods in child classes.
classのメソッドを継承先で使用したいときに使う。
使用例
class Vehicle { constructor(public name: string) { this.name = name; // 省略可 } protected honk(): void { console.log('beep'); } } class Car extends Vehicle { private drive(): void { console.log('vroom'); } startDriving(): void { this.drive(); this.honk(); } } const car = new Car('Beatle'); console.log(car.name); car.startDriving();
継承
class Vehicle { constructor(public name: string) { this.name = name; // 省略可 } protected honk(): void { console.log('beep'); } } class Car extends Vehicle { constructor(public wheels: number, name: string) { super(name); } private drive(): void { console.log('vroom'); } startDriving(): void { this.drive(); this.honk(); } } const car = new Car(4, 'Beatle'); console.log(car.name); car.startDriving();
まとめ
Interfaces + Classes = How we get really strong code reuse in TS
TypeScript学習4(Typed objects: Interfaces)
Interfaces
Creates a new type, describing the property names and value types of an object
例えば、今まで学んだことを利用して次のように型定義を行うと、コードが長くなってしまって可読性も低くなる。
const oldCivic = { name: 'civic', year: 200, broken: true }; const printVehicle = (vehicle: { name: string; year: number; broken: boolean; }): void => { console.log(`Name: ${vehicle.name}`); console.log(`Year: ${vehicle.year}`); console.log(`Broken? ${vehicle.broken}`); };
そこで、Interfaceを用いて別箇所での型定義を行う。
const oldCivic = { name: 'civic', year: 200, broken: true }; interface Vehicle { name: string; year: number; broken: boolean; } const printVehicle = (vehicle: Vehicle): void => { console.log(`Name: ${vehicle.name}`); console.log(`Year: ${vehicle.year}`); console.log(`Broken? ${vehicle.broken}`); };
プリミティブだけでなく、どんな型でもInterfacesに入れることができる。
const oldCivic = { name: 'civic', year: new Date(), broken: true, summary(): string { return `Name: ${this.name}`; } }; interface Vehicle { name: string; year: Date; broken: boolean; summary(): string; } const printVehicle = (vehicle: Vehicle): void => { console.log(vehicle.summary()); }; printVehicle(oldCivic);
Interfaceとは、ファンクションに引数を渡す際のゲートキーパである。
TypeScript学習3(Typed arrays)
TSでarrayを扱う上で重要なポイント
- TS can do type inference when extracting values from an array
- TS can prevent us from adding incompatible values to the array
- We can get help with
map
,forEach
,reduce
,functions
- Flexible - arrays can still contain multiple different types
配列(Array)の記述方法
const carMakers: string[] = ['ford', 'toyota', 'chevy']; const dates = [new Date(), new Date()]; const carsByMake: string[][] = []; // Help with inferences when extracting values const car = carMakers[0]; const myCar = carMakers.pop(); // Prevent incompatible values carMakers.push(100); // Throws error // Help with `map` carMakers.map( (car: string): string => { return car.toUpperCase(); } ); const importantDates: (Date | string)[] = [new Date(), '2030-10-10']; importantDates.push('2030-10-10'); importantDates.push(new dates());
Tuple
Array-like structure where each element represents some property of record.
配列の中でも、決まった順序である特定の型の値が格納されるというものに関しては、Tupleという型定義を行う。
const drink = { color: 'brown', carbonated: true, sugar: 40 }; const pepsi = ['brown', true, 40];
例えば上記のようなコードの書き方だと、配列の値はstring
, number
, boolean
のどれでも取れるということになってしまい、順番も自由にできてしまう。そこでTupleを用いてannotationを行う。
const drink = { color: 'brown', carbonated: true, sugar: 40 }; type Drink = [string, boolean, number]; const pepsi: Drink = ['brown', true, 40]; const sprite: Drink = ['clear', true, 40];
だがここでよくよく考えてみる。
そもそも配列の順序が固定されていて値の型も固定ならば、配列よりオブジェクトを書いたほうが有効なはずである。 なので、あまりTapleを使う出番はないであろう。