初心者がVue.jsの公式ガイドを勉強するメモ　リストレンダリング編

# 初心者がVue.jsの公式ガイドを勉強するメモ リストレンダリング編
# リストレンダリング
## 今回のお題は、こちらです。
## https://jp.vuejs.org/v2/guide/list.html
* 実際試して見てください。　(ブラウザーでの結果は掲載していません)
* この章は個人的に、大変参考になりましたが長めの掲載になりました。

## v-for で配列に要素をマッピングする

* App.vue

``` html
<template lang="pug">
  #App
    ul: li(v-for="item in items") {{ item.message }}

</template>

<script>

export default {
  name: 'app',
  data () {
    return {
      items: [
        { message: 'Momo' },
        { message: 'Tofu' },
        { message: 'Foo' },
        { message: 'Bar' }
      ]
    }
  },
  computed: {
  },
  methods: {
  }
}

</script>
```
* 以前に、component を作った時にもv-forは触っているので、基本は大丈夫かと思います。

* App.vue

``` html
<template lang="pug">
  #App
    ul: li(v-for="(item, index) in items")
      span {{ parent }} - {{ index }} - {{ item.message }}
    ul: li(v-for="(item, index) of items")
      span {{ parent }} - {{ index }} - {{ item.message }}

</template>

<script>

export default {
  name: 'app',
  data () {
    return {
      parent: 'Parent',
      items: [
        { message: 'Momo' },
        { message: 'Tofu' },
        { message: 'Foo' },
        { message: 'Bar' }
      ]
    }
  },
  computed: {
  },
  methods: {
  }
}

</script>
```
* 説明道理ですね。

## オブジェクトの v-for

* App.vue

``` html

<template lang="pug">
  #App
    ul: li(v-for="(value, key, index) in obj")
      span INDEX: {{ index }} - {{ key }} - {{ value }}

</template>

<script>

export default {
  name: 'app',
  data () {
    return {
      obj: {
        firstName: 'tomo',
        lastName: 'Yasuda',
        age: 99
      }
    }
  },
  computed: {
  },
  methods: {
  }
}

</script>


```
* オブジェクトのkey, valueも抜き出せています。
* Objectの列挙の注意事項は、遠回しに順序の一貫性が必要ならオブジェクトを配列で包むなりしなさいと言ってそうです。

## key
* リストが見にくい場合, cssで調整してください。

* App.vue

``` html
<template lang="pug">
  #App
    ul: li(v-for="item in items" :key="item.id")
      span ID: {{ item.id }} - {{ item.message }}
    ul: li(v-for="(item, index) in items" :key="item.index")
      span ID: {{ index }} - {{ item.message }}

</template>

<script>

export default {
  name: 'app',
  data () {
    return {
      items: [
        { id: 1, message: 'Momo' },
        { id: 2, message: 'Tofu' },
        { id: 3, message: 'Foo' },
        { id: 4, message: 'Bar' }
      ]
    }
  },
  computed: {
  },
  methods: {
  }
}

</script>
```
* keyは一意とありますので、重ならない値をとってこないと駄目です。　ハッシュ値を引っ張ってこれるならハッシュ値をいれたほうがよりベターです。　練習では、これで十分一意だと思います。

## 変更メソッド

App.vue

``` html
<template lang="pug">
  #app
    ul
      li(v-for="(item, index) in items" :key="index")
        | {{ item }}

    button(@click="items.push({ id: items.length + 1, message: 'add item'})") add button
    button(@click="items.pop()") remove item

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      items: [
        { id: 1, message: 'Momo' },
        { id: 2, message: 'Tofu' },
        { id: 3, message: 'Foo' },
        { id: 4, message: 'Bar' }
      ]
    }
  },
  methods: {
  },
  computed: {
  }
}
</script>

```
* ここは、配列の操作とかわりないです。 Vueがいい感じにディレクティブさせます。

## 配列の置き換え

* App.vue

``` html
<template lang="pug">
  #app
    | {{ inputItem }}
    ul
      li(v-for="(item, index) in items" :key="index")
        | {{ item }}
    button(@click="serchItem(/Foo/)") serch Foo button

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      inputItem: '',
      items: [
        { id: 1, message: 'Momo' },
        { id: 2, message: 'Tofu' },
        { id: 3, message: 'Foo' },
        { id: 4, message: 'Bar' }
      ]
    }
  },
  methods: {
    serchItem (value) {
      this.items = this.items.filter( item => {
          return item.message.match(value)
      })
    }
  },
  computed: {
  }
}
</script>

```
* 配列の変更には、置き換えが推奨されています。
* 新しい配列を返すメソッドを使用していきましょう。


### 注意事項

* App.vue

``` html

<template lang="pug">
  #app
    |  items length {{ items.length }}
    button(@click="items.pop()") POP
    button(@click="items.push({ id: items.length + 1, message: 'PUSH!!' })") PUSH

    ul
      li(v-for="(item, index) in items")
        | {{ item }}
        button(@click="changeItem1(item, index)")  no derective
        button(@click="changeItem2(item, index)") Set derective
        button(@click="changeItem3(item, index)") Splice derective
        button(@click="changeItem4(index)") derective

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      items: [
        { id: 1, message: 'Momo' },
        { id: 2, message: 'Tofu' },
        { id: 3, message: 'Foo' },
        { id: 4, message: 'Bar' }
      ]
    }
  },
  methods: {
    changeItem1 (item, index) {
      this.items[index] = { id: item.id,  message : 'no derective'}
    },
    changeItem2 (item, index) {
      this.$set(this.items, index, { id: item.id, message: 'derective' })
    },
    changeItem3 (item, index) {
      this.items.splice(index, 1, { id: item.id,  message: 'derective' })
    },
    changeItem4 (index) {
      this.items[index].message = 'derective'
    }
  },
  computed: {
  }
}
</script>

```
* 配列を直接指定して、変更するときは 注意が必要みたいです。
* 配列を直接変更する場合よく使う、items[0] の形は、画面の書き換えができないので、使わないようにとのこと。
* すこしややこしいのですが、items[0]でオブジェクトを書き換えるときの事で、items[0].message = 'Yasu' 等の指定は書き換わります。




## オブジェクトの変更検出時の注意

* App.vue

``` html
<template lang="pug">
  #App
    ul(v-for="(value, key) in userProfile")
      li {{ key }} {{ value }}
    button(@click="addItem()") add property
    button(@click="addItem2()") add many property
    button(@click="addItem3()") add many property

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      userProfile: {
        name: 'totoro'
      }
    }
  },
  methods: {
    addItem () {
      //- Vue.set(this.userProfile, 'age', 27)
      this.$set(this.userProfile, 'age', 28)
    },
    addItem2 () {
      this.userProfile = Object.assign({}, this.userProfile, {
        age: 33,
        favoriteColor: 'green'
      })
    },
    addItem3 () {
      this.userProfile = {
        ...this.userProfile,
        age: 45,
        favoriteColor: 'green'
      }
    }
  },
}

</script>
```
* data にsetメソッドで追加したデータが入ってるのが確認出来ます。
* dataに作成した、オブジェクトに追加は、setメソッドで可能。
* 複数のプロパティを追加するときは、ObjectAsignを使いましょうとのこと。
* スプレッド演算子もわかりやすいですね.




## フィルタ/ソートされた結果の表示

* App.vue

``` html
<template lang="pug">
  #App
    ul(v-for="n in numbers")
      li Original: {{ n }}
        //- 元の配列は変更せず、表示条件のみ変更 入れ子
        ul(v-for="n in even(numbers)")
          li Methods: {{ n }}
    //- 元の配列は変更せず、表示条件のみ変更
    ul(v-for="n in evenNumbers")
      li Computed: {{ n }}

    ul(v-for="n in sort")
      li Sort: {{ n }}


</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      numbers: [ 1, 2, 3, 4, 5]
    }
  },
  methods: {
    even (numbers) {
      return numbers.filter( number => {
        return number % 2 === 0
      })
    }
  },
  computed: {
    evenNumbers () {
      return this.numbers.filter( number => {
        return number % 2 === 0
      })
    },
    sort () {
      //- return this.numbers.sort( (a, b) => {
      return this.numbers.slice().sort( (a, b) => {
        if ( a > b ) return -1
        if ( a < b ) return 1
        return 0
      })
    }
  }
}

</script>


```
* 返り値に、新しい配列を返すfilter, slice, mapなどは、computedと相性が良さそうですね。
* sortなどの破壊的メソッドは使うときは、注意しましょう。
* 入れ子など、computedが使えない状況では、同じようにmethodsで対応できるとのこと。
* 元の値を変えずに、条件にあったものだけ表示したいばあいに使えます。
* 例えば、消費税有無等で、表示だけ変えたい場合等にもつかえますね。
* 下にでてくる、v-if v-forの組み合わせでも同じようなことができます。

## 範囲付き v-for
### 整数値

* App.vue

``` html
<template lang="pug">
  #App
    span(v-for="n in 10") {{ n }}

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
    }
  },
  methods: {
  },
  computed: {
  }
}

</script>
```

#### `<template> での v-for`

``` html
<template lang="pug">
  #App
    ul
      template(v-for="item in items")
        li {{ item.message }}
        li.divider ---------- css処理

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      items: [
        { id: 1, completed: true, message: 'Momo' },
        { id: 2, completed: false, message: 'Tofu' },
        { id: 3, completed: true, message: 'Foo' },
        { id: 4, completed: false, message: 'Bar' }
      ]
    }
  },
  methods: {
  },
  computed: {
  }
}

</script>
```
## フィルタを使った条件表示

```html
<template lang="pug">
  #App
    //- ID が偶数のみ抜き出し
    ul
      li(v-for="(item, index) in evenItems" :key="index")
        | {{ item }}

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      items: [
        { id: 1, completed: true, message: 'Momo' },
        { id: 2, completed: false, message: 'Tofu' },
        { id: 3, completed: true, message: 'Foo' },
        { id: 4, completed: false, message: 'Bar' }
      ]
    }
  },
  methods: {
  },
  computed: {
    evenItems () {
      return this.items.filter( item => {
        return item.id % 2 === 0
      })
    }
  }
}

</script>
```
#### methodを使う場合(filter)

```html
<template lang="pug">
  #App
    //- id1 のcompleted と同じものを抽出
    button(@click="items[0].completed = !items[0].completed") Toggle
    ul
      li(v-for="(item, index) in completedItems(items[0].completed)" :key="index")
        | {{ item }}

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      items: [
        { id: 1, completed: true, message: 'Momo' },
        { id: 2, completed: false, message: 'Tofu' },
        { id: 3, completed: true, message: 'Foo' },
        { id: 4, completed: false, message: 'Bar' }
      ]
    }
  },
  methods: {
    completedItems (boolean) {
      return this.items.filter( item => {
        return item.completed === boolean
      })
    }
  },
  computed: {
  }
}

</script>
```
* computedが使えない場合は、metodsを使えばいいですね。

### v-for と v-if

``` html
<template lang="pug">
  #App
    //- completed falseのみ表示
    ul
      li(v-for="todo in items" v-if="!todo.completed")
        | {{ todo }}

    //- v-if での条件分岐
    ul(v-if="items.length")
      li(v-for="todo in items")
        | {{ todo }}
    p(v-else) No todos left !

    button(@click="items.pop()") remove item

</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data () {
    return {
      items: [
        { id: 1, completed: true, message: 'Momo' },
        { id: 2, completed: false, message: 'Tofu' },
        { id: 3, completed: true, message: 'Foo' },
        { id: 4, completed: false, message: 'Bar' }
      ]
    }
  },
  methods: {
  },
  computed: {
  }
}

</script>
```

* v-for　も `<template>` で使用出来るとのこと。
* v-for と v-if　の組み合わせ時は、v-for　-> v-if 。
* 簡単に条件の組み合わせができるのは、すばらしいですね。
* computedやmetodsで先に条件をつければ、さらに細かく制御できます。


## コンポーネントと v-for
* コンポーネントは、vueをある程度理解してからでいいと思います。
* なくても生きていけます。

* App.vue

``` html
<template lang="pug">
  #App
    my-component(v-for="item in items" :key="item.id" :value="item")

</template>

<script>
import Vue from 'vue'

const myComponent = Vue.extend({
  props: ["value"],
  template: `<li>{{ value }}</li>`
})
Vue.component('my-component', myComponent)

export default {
  name: 'app',
  data () {
    return {
      items: [
        { id: 1, completed: true, message: 'Momo' },
        { id: 2, completed: false, message: 'Tofu' },
        { id: 3, completed: true, message: 'Foo' },
        { id: 4, completed: false, message: 'Bar' }
      ]
    }
  },
  methods: {
  },
  computed: {
  }
}

</script>
```
* コンポーネントでの親から子へのデータの受け渡しは、 props でしたね。
* ちなみに、itemsをコンポーネント側のdataに置けば、渡す必要はないです。
* 最適と思われる場所に置きましょう。

### todo

* App.vue

``` html
<template lang="pug">
  #App
    p Input : {{ newTodoText }}
    input(
      v-model="newTodoText"
      @keyup.enter="addNewTodo"
      placeholder="Add a todo"
    )
    button(@click="addNewTodo") add button
    ul
      todo-item(
        v-for="(todo, index) in todos"
        :key="index"
        :title="todo.title"
        @remove="todos.splice(index, 1)"
      )

</template>

<script>
import Vue from 'vue'

const todoItem = Vue.extend({
  props: ["title"],
  template: `
    <li>
      {{ title }}
      <button @click="$emit('remove')">X</button>
    </li>
  `
})
Vue.component('todo-item', todoItem)

export default {
  name: 'app',
  data () {
    return {
      newTodoText: '',
      todos: [
        {
          id: 1,
          title: 'Do the dishes',
        },
        {
          id: 2,
          title: 'Take out the trash',
        },
        {
          id: 3,
          title: 'Mow the lawn'
        }
      ]
    }
  },
  methods: {
    addNewTodo () {
      this.todos.push({
        id: this.todos.length + 1,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  },
  computed: {
  }
}

</script>
```
* props で、titleを一つづつ受け取っています。
* emit で、クリックイベントを子から親へ引き渡しています。
* id は単純に+1しているだけなので、すぐ重なりますがサンプルということで（笑）
* 簡単なtodo アプリならすぐに実装できそうですね。