web初心者が1週間でVueを使ってみた. part2-vuecli編

#part2では
part1ではjsFiddleで学んで今回はその続きで,vue-cliによるプロジェクト作成方法,単一コンポーネント,vue-vuexで設定画面を作る.その際に親子関係などの考え方を学ぶ.
https://qiita.com/binary2/items/c3b216e0ab02baaaa154

今までインデント変になるな-思っていた..
自動整形するようにしました.快適になった.
https://qiita.com/maron8676/items/017cd830ab0c5fb8bcac

#環境構築
##node.js
公式(nodejs.org)からインストール,インストーラを展開しても設定は特になし.
完了したら確認.

![image.png](https://qiita-image-store.s3.amazonaws.com/0/283053/f4daf999-78ae-7801-fe85-60f09ec00a9c.png)

以下でインストール成功確認.

```
node --version
>v8.12.0(ココはインストールしたバージョン)
```

#プロジェクト作成
これで一緒にnpmもインストールされているので,次はこれを使ってvueインストール,プロジェクト作成をする.

```
npm init
npm install -g vue-cli
vue init webpack my-project
```

オプションは全てEnterで(プロジェクト名,説明,作者,ESLint,プリセット,UnitTest,E2ETest)

フォルダ構成
>
/my-project
 ├ /build
 ├ /config
 ├ /node_modules
 ├ /src
 │ ├ /assets
 │ │ └ logo.png
 │ ├ /components *ココに部品
 │ │ └ HelloWorld.vue 
 │ ├ /router
 │ │ └ index.js *ココにルート設定
 │ ├ App.vue *エントリポイント
 │ └ main.js
 ├ /static
 └ /test

起動テスト(コマンドラインに To get started と記載有り)

```
cd my-project
npm run dev
```

build終わったら,localhostを叩く.
[http://localhost:8080](http://localhost:8080)

Welcome to Your Vue.js App が表示されたらプロジェクト作成完了.
![image.png](https://qiita-image-store.s3.amazonaws.com/0/283053/1f5550a2-b9a9-4050-7929-76239cdfb92f.png)

##vuex and element-ui
プロジェクトで使うのでインストールしておく.

```
cd my-project
npm install vuex --save
npm install element-ui --save
```

##Vetur(visual studio code用)
vueファイルはシンタックスハイライトできない.その拡張機能.
手順は,Extension>Vetur>install(イメージはインストール済みなので,Uninstallになっている..)
![無題.png](https://qiita-image-store.s3.amazonaws.com/0/283053/bea3ff73-14c8-6368-0500-d38bb0fc12e3.png)

##Vue.js devtools(Chrome用)
ブラウザでvue作成物を見るときに役に立つツール.
拡張機能>Chromeウェブストアを開く>Vueで検索>一番上のはず>Chromeに追加
![image.png](https://qiita-image-store.s3.amazonaws.com/0/283053/22a24bc2-6c0e-9f3f-61c4-cf91cc463416.png)
開発ツールでvueタブができる.
![image.png](https://qiita-image-store.s3.amazonaws.com/0/283053/0f5a95a6-d6aa-ee87-694c-ce495466b837.png)



#単一コンポーネントを作る
これでプロジェクトの準備完了したので単一コンポーネントを作ってみる.

##現状理解
```javascript:main.js
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})
```
App読み込んでVueインスタンスのコンポーネントにAppを設定している.くらいの認識?

```javascript:router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]
})
```
ここでルーティングやってるくらいの認識.
メインパス?(localhost:8080/)だとHelloWorldコンポーネントがルーティングされている.

```vue:componentsHelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      msg: "Welcome to Your Vue.js App"
    };
  }
};
</script>
<style scoped>
h1 {
  font-weight: normal;
}
</style>

```

邪魔な箇所を削除してスリムにした.
templateがhtml,javascriptが処理部分,styleが..
なるほど,vueファイル1つで1コンポーネントとなっている.
なんとなく雰囲気はつかめた(と勝手に思っている.)
javascript部分はpart1でもやったけど,store(データ部分)に分ける方がいいけど後にやる.

##コンポーネントを作ってみる
今回は設定画面(文字サイズ:big,regular,small)みたいなものを作ってみようと思います.
settings.vueをcomponentフォルダに追加する.

```vue:components/Settings.vue
<template>
  <div class="settings">
    <h1>{{ msg }}</h1>
  </div>  
</template>
<script>
export default {
  name: "Settings",
  data() {
    return {
      msg: "Settings"
    };
  }
};

</script>
```
HelloWorldコピペしてmessage部分を変えてcss消しただけ.
次はルーティングにsettingsを追加する.

```javascript:router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Settings from '@/components/Settings'//読み込み追加

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    },
    //ココ追加
    {
      path: '/settings',
      name: 'Settings',
      component: Settings
    }
  ]
})
```
追加したら(本当はApp.vueにglobal navigation追加するべきだけど..)

http://localhost:8080/#/settings
![image.png](https://qiita-image-store.s3.amazonaws.com/0/283053/1bc53565-f510-abff-ff23-7cd89040a62c.png)

Settingsと表示されていればルーティングできているので,中身を追加していく.
まず,cssライブラリを読み込むようにする.

```javascript:main.js
import Vue from 'vue'
import App from './App'
import router from './router'

//ココを追加
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/ja'
Vue.use(ElementUI, { locale });

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})
```
SettingComp.vueファイルを追加する.(良い名前が思いつかない..)

```vue:components/SettingComp.vue
<template>
  <div>
      <h1> {{option}} </h1>
    　 <!-- keyは設定した方がいいらしい,indexでもいいけど,今回はid持っているてい -->
      <el-radio-group v-for="item in items" :key="item.id" v-model="selectId">
        <el-radio-button :label=item.id>{{item.name}}</el-radio-button>
      </el-radio-group>
  </div>
</template>

<script>
export default {
  name: "SettingComp",
  data() {
    return {
      option: "font-size", //設定項目
      selectId: null, //選択id
      items: [
        //選択項目
        {
          id: 1,
          name: "big"
        },
        {
          id: 2,
          name: "regular"
        },
        {
          id: 3,
          name: "small"
        }
      ]
    };
  }
};
</script>

<style scoped>
</style>

```

SettingComp.vueを作り終わったので,次にこれをSettings.vueに読み込む

```vue:components/Settings.vue
<template>
  <div class="settings">
    <h1>{{ msg }}</h1>
    <!-- ここでオプションごとに追加していく -->
    <setting-comp></setting-comp>
  </div>  
</template>

<script>
import SettingComp from "@/components/SettingComp"; //読み込み

export default {
  name: "Settings",
  data() {
    return {
      msg: "Settings"
    };
  },
  components: {
    //コンポーネント追加
    SettingComp
  }
};

</script>
```
![radioButton3.gif](https://qiita-image-store.s3.amazonaws.com/0/283053/7e9ab2dc-de9a-aaf2-4a33-7ac0820f0237.gif)

font-sizeのオプションが表示されれば成功.

#親子間でデータのやり取りをやってみる

そして次の設定を追加したい場合は,settingComp2ファイルを作るコピペラッシュをやると取り返しのつかないことになる.
のでsettingsでデータを定義してsettingCompに渡し変更を受け取るように変更する必要がある.

```vue:components/Settings.vue
<template>
  <div class="settings">
    <h1>{{ msg }}</h1>
    <p> {{ selectId }}:{{ items[selectId-1].name}} </p>
    <!-- ここでオプションごとに追加していく -->
    <setting-comp :option="option" :items="items" :selectId.sync="selectId"></setting-comp>
  </div>  
</template>

<script>
import SettingComp from "@/components/SettingComp";

export default {
  name: "Settings",
  data() {
    return {
      msg: "Settings",
      option: "font-size",
      selectId: 1,
      items: [//省略
      ]
    };
  },
  components: {
    SettingComp
  }
};
</script>
```
やったことは

* ベタで持っていたものをdataへ
* 子(settingComp)のデータ部分を親へ移行
* <setting-comp>内に子コンポーネントへ渡す値を記載した

注意点は,子コンポーネントで選択しているラジオボタンを変更されても子の値を変更するだけなので,そこは工夫が必要となる.

```vue:components/SettingsComp.vue
<template>
  <div>
      <h1> {{option}} </h1>
      <el-radio-group v-for="item in items" :key="item.id" v-model="changeId">
        <el-radio-button :label=item.id>{{item.name}}</el-radio-button>
      </el-radio-group>
  </div>
</template>

<script>
export default {
  name: "SettingComp",
  props: {
    //親からデータを受け取る
    option: {
      type: String
    },
    selectId: {
      type: Number,
      default: 0
    },
    items: {
      type: Array
    }
  },
  computed: {
    //ココでいろいろやる
    changeId: {
      get: function() {
        return this.selectId;
      },
      set: function(val) {
        //変更されたら親へ渡す
        this.$emit("update:selectId", val);
      }
    }
  }
};
</script>

<style scoped>
</style>
```
![radioButton4.gif](https://qiita-image-store.s3.amazonaws.com/0/283053/140aa393-df70-423c-d1c1-d825016eb56c.gif)
親からデータを渡して,選択変更したら親へデータを渡すことが確認できました.

##肉付け(汎化)
親で追加したいときにdataにインスタンス変数を追加しまくるのはあれなので,まとめる.

```vue:components/Settings.vue
<template>
  <div class="settings">
    <h1>{{ msg }}</h1>
    <!-- datasetをループしてるだけ -->
    <li v-for="data in dataset" :key="data.key">
        <setting-comp :option="data.option" :items="data.items" :selectId.sync="data.selectId"></setting-comp>
    </li>
  </div>  
</template>

<script>
import SettingComp from "@/components/SettingComp";

export default {
  name: "Settings",
  data() {
    return {
      msg: "Settings",
      //データセットにしただけ
      dataset: {
        1: {
          option: "font-size",
          selectId: 1,
          items: [
            {
              id: 1,
              name: "big"
            },
            {
              id: 2,
              name: "regular"
            },
            {
              id: 3,
              name: "small"
            }
          ]
        },
        2: {//省略
        },
        3: {//省略
        }
      }
    };
  },
  components: {
    SettingComp
  }
};
</script>
<style scoped>
h1 {
  font-weight: normal;
}
li {
  display: inline;
}
</style>
```
![radioButton5.gif](https://qiita-image-store.s3.amazonaws.com/0/283053/c0297481-ece6-1f2f-fc76-41e3234d024f.gif)

#vuex
なんとかできたので,vuexで分離を行う.
基本的にやることはjavascript内のデータ操作をstoreへ移動する.

##storeを作成
src直下にstoreフォルダを作成してindex.jsを作成します.
![image.png](https://qiita-image-store.s3.amazonaws.com/0/283053/5558da6d-f109-825a-e41b-5b7bcca087f0.png)

まず,読み込みしてしまう.

```javascript:main.js
import Vue from 'vue'
import App from './App'
import store from './store'//追加
import router from './router'

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/ja'

Vue.use(ElementUI, { locale });

new Vue({
  el: '#app',
  store,//追加
  router,
  components: { App },
  template: '<App/>'
})
```

```javascript:store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const Settings = {
  namespaced: true,
  state: {
    msg: "Settings",
    dataset: {
      1: {
        option: "font-size",
        selectId: 1,
        items: [
          {
            id: 1,
            name: "big"
          },
          {
            id: 2,
            name: "regular"
          },
          {
            id: 3,
            name: "small"
          }
        ]
      },
      2: {
        option: "color",
        selectId: 1,
        items: [
          {
            id: 1,
            name: "red"
          },
          {
            id: 2,
            name: "blue"
          },
          {
            id: 3,
            name: "green"
          }
        ]
      },
      3: {
        option: "timeZone",
        selectId: 1,
        items: [
          {
            id: 1,
            name: "japan"
          },
          {
            id: 2,
            name: "america"
          },
          {
            id: 3,
            name: "china"
          }
        ]
      }
    }
  },
  getters: {
    getMsg(state) {
      return state.msg
    },
    getDataset(state) {
      return state.dataset
    }
  }
}

export default new Vuex.Store({
  modules: {
    Settings
  }
})
```
```vue:components/Settings.vue
<template>
  <div class="settings">
    <h1>{{ msg }}</h1>
    <li v-for="data in dataset" :key="data.key">
        <setting-comp :option="data.option" :items="data.items" :selectId.sync="data.selectId"></setting-comp>
    </li>
  </div>  
</template>

<script>
import SettingComp from "@/components/SettingComp";
import { mapState, mapGetters, mapMutations } from "vuex";

export default {
  name: "Settings",
  computed: mapGetters("Settings", {
    msg: "getMsg",
    dataset: "getDataset"
  }),
  components: {
    SettingComp
  }
};
</script>
<style scoped>
h1 {
  font-weight: normal;
}
li {
  display: inline;
}
</style>

```

これで同様に動きます.
疑問点は,datasetをコミットしてないのにログ見ると変更されている..(調査必須)
後は,commit and restoreボタンも欲しい.storeもしくはcreateのタイミングでtmp変数に格納すればできそう.

今回も疑問点はまだあるけど,いろいろ学んだ.

未学習項目は,

- classなどの要素付け
- watchやdispatchの利用方法

part1,2は,1週間ドキュメントなどを見て学んだ部分を自分で調べてまとめてみました.
資料は,

* vue.js入門(book)
* vue.js公式
* vuex.js公式
* qiita記事たくさん

まだまだvue入門もいいところだし(そもそもweb初心者)なので,学習楽しくできています.
もっといろいろと学んだらメモがてらまとめていきたいと思います.

#改版履歴
vue.js devtoolsの設定方法忘れていた.