---
# System prepended metadata

title: Vue-cli
tags: [Vue]

---

# Vue-cli

###### tags: `Vue`

>1. 基於 Webpack 所建置的開發工具
>2. 便於使用各種第三方套件 (BS4、Vue Router)
>3. 可運行 Sass、Babel 等編譯工具
>4. 便於開發 SPA
>5. 簡單設定就能搭建開發時常用環境

## 安裝

:::info
需先安裝 Node.js
:::

### 基礎指令
```cmd
<!-- 安裝 Vue Cli -->
npm install -g vue-cli

<!-- 版本 -->
vue --version

<!-- 查看可使用指令 -->
vue

<!-- 樣板，主要使用 Webpack -->
vue list

<!-- 建立專案樣板 -->
vue init <template-name> <project-name>

<!-- 安裝套件 -->
npm install

<!-- 運行 Vue 開發環境 -->
npm run dev

<!-- 運行正式環境 -->
<!-- 此時會出現 dist 資料夾，所有東西打包壓縮 -->
<!-- build 的檔案只能運行在 HTTP 伺服器下，無法直接打開 -->
npm run build
```

## Vue Cli 所產生的資料夾結構

![file](https://i.imgur.com/7ZaLXdh.png)

* build: Webpack 設定檔
* config: Vue 應用程式設定檔
* dist: npm run build 產生，只能在 HTTP Server 下運行
* src: 最重要資料夾，檔案將會被編譯
  * main.js: Webpack 設定 main.js 作為進入點
  * assets 會針對特定大小的圖片編譯成 base 64
* static: 放入不會被編譯的檔案
* .vue: 元件，包含 x/template、js、style
* .babelrc: 替 ES6 編譯的設定檔
* .postcssrc.js: 替 CSS 編譯加入前啜詞的設定

> 詳細資料設定看 [Vue 課程 69](https://www.udemy.com/course/vue-hexschool/learn/lecture/10415930#questions)


## 運行解說

![Webpack](https://i.imgur.com/yJsBAXg.png)


Webpack 會使用 loader 工具，將 .sass、.vue、.jpg 檔呈現在 main.js。

Webpack 會一直監控 main.js 並且輸出。

## 安裝 Bootstrap

因為 Vue Cli 沒有完整的 sass loader，所以需要加上後面這段

```cmd
npm install bootstrap node-sass sass-loader --save
```

sass-loader 建議安裝舊版本，才能順利運行

```cmd
npm install --save-d sass-loader@7.1.0
```

### 載入 BS4

```htmlmixed
<style lang="scss">
@import "~bootstrap/scss/bootstrap";
</style>


<!-- scoped 只在當下元件有作用，不會影響其他元件 -->
<style scoped>
  
</style>
```

## Vue-axios

[https://www.npmjs.com/package/vue-axios](https://www.npmjs.com/package/vue-axios)

```cmd
npm install --save axios vue-axios
```

### 將下列加入到 main.js

```javascript
import Vue from 'vue'
import axios from 'axios'  // 主要 AJAX 套件
import VueAxios from 'vue-axios'  // 將它轉為 Vue 套件
 
Vue.use(VueAxios, axios)
```

### 取得遠端資料

> [Random User API](https://randomuser.me/)
> 
```javascript
created() {
  this.$http.get("https://randomuser.me/api/").then(response => {
    console.log(response.data);
  });
}
```

## Vue Router

[https://router.vuejs.org/zh/installation.html](https://router.vuejs.org/zh/installation.html)

在建立專案時，應該就可以直接裝 Vue router 了

```cmd
npm install vue-router --save
```
```
<!-- 進入點 -->
main.js

<!-- Router 配置檔案 (前端路由) -->
router/index.js

<!-- 分頁內容 -->
Vue.components (**.vue)
```

src/router/index.js

```javascript=
// 官方的元件
import Vue from 'vue';
import VueRouter from 'vue-router';

// 自訂的元件
import Home from '@/components/HelloWorld';
import Page from '@/components/pages/page';
import child from '@/components/pages/child';
import child2 from '@/components/pages/child2';
import child3 from '@/components/pages/child3';
import Menu from '@/components/pages/menu'

Vue.use(VueRouter); // 啟用

export default new VueRouter({
// routes 的 name 可加可不加
  routes: [{
      name: '首頁', // 元件呈現名稱
      path: '/index', // 對應的虛擬路徑
      component: Home, // 對應的元件
    },
    {
      name: '分頁', // 元件呈現名稱
      path: '/page', // 對應的虛擬路徑
      // component: Page,
      components: {
        default: Page,
        menu: Menu
      }, // 對應的元件
      children: [{
          name: '卡片1',
          path: '',
          component: child,
        },
        {
          name: '卡片2',
          path: 'child2',
          component: child2,
        },
        {
          name: '卡片3',
          path: 'child3',
          component: child3,
        }
      ]
    }
  ]
})
```

main.js

```javascript=
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
import App from './App';

// 將 router 的 index.js 掛進來
import router from './router';

import axios from 'axios';  // 主要 AJAX 套件
import VueAxios from 'vue-axios';  // 將它轉為 Vue 套件

Vue.use(VueAxios, axios);

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>',
  router
})
```

### router-view、router-link

router-view: 元件切換的地方

router-link: 
:::success
- 默認渲染成 a 標籤
- tag: 可指定渲染成別的標籤，例如 tag="li"
- replace: 改用 history 的 replaceState，切換路由時，則無法使用上下頁。
- router-link-active: 當前路由是 router-link 路由時，會對 router-link 加入 router-link-active 的 class。可以在 router-link 標籤內加入 active-calss = "active"，這樣就能替換掉預設的 router-link-active 改為自定義的。也能在 router 資料裡的 index.js 設置 linkActiveClass = 'active'，這樣全局都會更改
:::

```htmlmixed
<!-- 兩種切換方式 -->
<router-link :to="{name: '首頁'}" >Home</router-link>

<router-link to="/Page">Page</router-link>
```

### 巢狀路由頁面

```javascript
// 在 index.js

{
  name: '分頁2', 
  path: '/page2', 
  component: Page2,
  // 巢狀
  children:[
    {
      name: '卡片1', 
      path: '',  // 沒有寫就是預設 
      component: child
    },
    {
      name: '卡片2', 
      path: 'child2',  // 這裡 path 不能加斜線
      component: child2
    },
    {
      name: '卡片3', 
      path: 'child3',  
      component: child3
    }
  ]
}
```

### 動態路由

index.js
```javascript
{
  name: '卡片3', 
  // 補上:id 成為動態路由
  path: 'child/:id',  
  component: child3
}
```

child3.vue
```javascript
export default {
  data() {
    return {};
  },
  created() {
    const id = this.$route.params.id;
    // ?seed= 是 random user 提供的方法
    // $route 是路由對象，params 是取得參數，id 則對應到路由配置頁面 
    this.$http.get(`https://randomuser.me/api/?seed=${id}`).then(response => {
      console.log(response);
    });
  }
};
```

### 命名路由、載入兩個元件

[Vue 課程 76](https://www.udemy.com/course/vue-hexschool/learn/lecture/10416064#questions/8140341)

```htmlmixed
<router-view name="menu"></router-view>
```

index.js
```javascript
import Menu from '@/components/pages/menu'
;

{
  // name: '分頁', 如果 child 已有預設，則不能設置 name
  path: 'page',
  // components 載入多個元件
  components: {
    // default 會對應到沒有使用 name 的 router-view
    default: Page,
    // 會對應到有使用 name 的 router-view
    menu: Menu
  },
  children: 
  [
    {
      name: '卡片 1',
      path: '',
      component: child
    }
  ]
}
```




### Q&A 
>[https://www.udemy.com/course/vue-hexschool/learn/lecture/10416064#questions/8204684](https://www.udemy.com/course/vue-hexschool/learn/lecture/10416064#questions/8204684)
>
>[https://www.udemy.com/course/vue-hexschool/learn/lecture/10415924#questions/6833718](https://www.udemy.com/course/vue-hexschool/learn/lecture/10415924#questions/6833718)
>

### 切換路由的方法

[Vue 課程 78](https://www.udemy.com/course/vue-hexschool/learn/lecture/10416084#questions/8140341)

# API
>[六角學院 Vue 課程練習 API 申請](https://vue-course-api.hexschool.io/)
>
>[六角學院 Vue 一個電商網頁 課程的 API 說明文檔
](https://github.com/hexschool/vue-course-api-wiki/wiki)

## 環境設定

dev.env.js 會跟 prod.env.js 有不一樣的 API 路徑，一個是測試一個是正式環境

/config/dev.env.js

```javascript
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  // 自己設定的 API 路徑
  APIPATH:'"https://vue-course-api.hexschool.io"',
  CUSTOMPATH:'"kenapi"'
})
```

## 取得 API 資料

使用 Vue 的 [axios](https://www.npmjs.com/package/vue-axios) 方法

:sunny: 注意取得 環境變數的寫法 process.env.{名稱}

```javascript=
created() {
    const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/products`;
    console.log(process.env.APIPATH, process.env.CUSTOMPATH);

    this.$http.get(api).then(response => {
      console.log(response.data);
    });
  }
```

## 載入 BS4 並客製化

在 /assetes 下新增 `all.scss`
在 /src 下，新增 helpers/_variables.scss

:sunny: 使用 helpers 資料夾主要是避免原始路徑衝突

all.scss 需要 import
```sass
@import "~bootstrap/scss/functions";
@import "./helpers/_variables";
@import "~bootstrap/scss/bootstrap";
```

## 儲存 cookies

需在 main.js 加入這段，才能正確儲存 cookies

```javascript
axios.defaults.withCredentials = true;
```

```javascript
// 需加上 admin
methods: {
  signin() {
    const api = `${process.env.APIPATH}/admin/signin`;
    const vm = this;
    this.$http.post(api, vm.user).then(response => {
      console.log(response);

      console.log(response.data);

      if(response.data.success){
        vm.$router.push('/index');
      }
    });
  }
}
```

## 導航守衛

>[導航守衛](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%85%A8%E5%B1%80%E5%89%8D%E7%BD%AE%E5%AE%88%E5%8D%AB)
>[路由元信息](https://router.vuejs.org/zh/guide/advanced/meta.html)


main.js
```javascript
// to: 即將要進入的頁面
// from: 從哪個頁面過來
// next: 到達下一個頁面
router.beforeEach((to, from, next) => {
  // ...
})
```

## 避免使用者未驗證就跳頁

```javascript
// 放在需驗證的元件 routes 內
// meta: 路由訊息
meta: { requiresAuth: true }

// 例如
{
  name: '登入',
  path: '/login',
  component: Login,
  meta: { requiresAuth: true }
}
```

實作

```javascript
// main.js

router.beforeEach((to, from, next) => {
  // ...
  console.log('to:', to, 'from:', from, 'next', next);

  // 如果進入的畫面需要驗證，則執行 check 這個 API 程式
  if (to.meta.requiresAuth) {
    console.log('需要驗證');

    const api = `${process.env.APIPATH}/api/user/check`;
    // 需改成 axios。this.$http 是元件才有的方法
    axios.post(api).then(response => {
      console.log(response.data);

      if (response.data.success) {
        // 如果成功則進入下一頁
        next();
      } else {
        // 否則轉回 login
        next({
          path: '/login'
        })
      }

    });
  } else {
    next()
  }

})
```

## 避免使用者亂輸入進入不存在的頁面

```javascript
// 在 index.js 的 routes 下加入
routes: [
  {
    path: '*',
    redirect: 'login'
  },
  ...
]
```

## 引入套件注意

```javascript
// 引入 BS4 可以直接在 index.js 裡 import
// 但可能會要額外安裝 BS4 的 JS 檔案
import 'bootstrap';
```

```javascript
// 引入 jQuery 時，可以直接在 index.js 裡
// 直接將 jQuery 掛載到全域
// 如果全域沒有掛載，則要在每個元件上都要 import
import $ from 'jquery'
window.$ = $;

// 如果有啟用 ESLint 則要加入下面這段
/* global $ */
```

## 串接上傳 API

使用 change
```htmlmixed
<input type="file" id="customFile" @change="uploadFile" class="form-control" ref="files" / >
```

>參考 [MDN new FormData()](https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/FormData)

```javascript
 uploadFile() {  
    const vm = this;
    // 取得檔案位置
    const uploadedFile = this.$refs.files.files[0];
    // 使用 Web API，模擬表單送出
    const formData = new FormData();
    // 將欄位新增進去，會對應到你要 post 的 六角API 所設計的欄位
    formData.append('file-to-upload', uploadedFile);
    // 對應路徑
    const url = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/upload`;
    // 傳送，但要設定格式
    this.$http.post(url, formData, {
      headers: {
        // 取自 六角API
        'Content-Type': 'multipart/form-data'
      }
    }).then((response)=>{
      console.log(response.data);

      if(response.data.success){
        // 這樣子會沒有 getter、setter，所以不會雙向綁定
        // vm.tempProduct.imageUrl = response.data.imageUrl;

        // 使用 $set() 強制寫進去才能雙向綁定
        // 寫入位置、屬性、值
        vm.$set(vm.tempProduct, 'imageUrl', response.data.imageUrl);
      }
    })
  }
```

## 讀取效果

>[vue-loading-overlay](https://github.com/ankurk91/vue-loading-overlay)
>

### 安裝
```cmd
npm install vue-loading-overlay --save
```

### 使用

main.js 加入
```javascript
import Loading from 'vue-loading-overlay';
// Import stylesheet
import 'vue-loading-overlay/dist/vue-loading.css';

// 在全域啟用
Vue.component('Loading', Loading);
```

在元件中
```htmlmixed
<!-- 雖然有很多設定，但留這個就能用了，詳細看文件 -->
<!-- 把這段放在上層即可 -->
<!-- 主要是要使用 isLoading 來決定是否開啟關閉 -->
<loading :active.sync="isLoading"></loading>
```

### Fontawesome

>[動態 icon](https://fontawesome.com/how-to-use/on-the-web/styling/animating-icons)

直接以 CDN 較為方便，npm 需要安裝字體等等較為複雜

使用方式可用 v-if 來決定是否開啟
```htmlmixed
<link rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" 
integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt"
crossorigin="anonymous">
```

## 回饋錯誤訊息

>設定複雜，參考[課程 96](https://www.udemy.com/course/vue-hexschool/learn/lecture/10896750#overview)

## VeeValidate

表單驗證套件

>參考:
>[VeeValidate](http://vee-validate.logaretm.com/v2/)
>[驗證事件](http://vee-validate.logaretm.com/v2/guide/events.html#changing-default-events)


### 安裝
```cmd
<!-- 官方最新，但可能出錯 -->
npm install vee-validate --save

<!-- 先用這個 -->
npm install vee-validate@2.2.15 --save
```

main.js
```javascript
import VeeValidate from 'vee-validate';
// 啟用
Vue.use(VeeValidate);
```

### 使用

驗證姓名
```htmlmixed=
<label for="username">收件人姓名</label>
<!-- 使用 required ，所以是不得為空-->
<!-- 可以給予 :class 當 errors.has('name') 為 true 被觸發後
讓 input 框框也變為紅色'-->
<input v-validate="'required'" :class="{'is-invalid':errors.has('name')}" name="name" type="text">
<!-- span 是底下紅字 -->
<!-- errors 是套件提供的變數 -->
<!-- has('') 裡的名稱會對應到 input 的 name 裡的名稱 -->
<!-- 觸發是當 input 被觸發後，如果 name 的名稱不存在，
則會出現 errors 錯誤 (此時 errors.has('name') 會是 true)-->
<span class="text-danger" v-if="errors.has('name')">姓名必須輸入</span>

```

### 驗證 Email (特殊)
```htmlmixed=+
<label for="useremail">Email</label>
<input v-validate="'required|email'" name="email" type="text">

<!-- errors.first('email') 會告知錯誤在哪裡(英文) -->
<span class="text-danger" v-if="errors.has('email')">{{errors.first('email')}}</span>

```

#### 將 Eail 錯誤訊息中文化

1. 安裝 vue-i18n
```cmd
npm install vue-i18n --save
```
2. 在 node_mudules/vee-validate/dist/locale 是語系檔案
   將它 import 進來
```javascript
import zhTW from 'vee-validate/dist/locale/zh_TW';
```

3. 在 main.js 中將 vue-i18n import 進來
```javascript
import VueI18n from 'vue-i18n'
Vue.use(VueI18n);
```

4. 將 VeeValidate.Validator.localize('zh_TW', zhTWValidate) 及 Vue.use(VeeValidate) 刪除，並加入下列程式碼
```javascript
const i18n = new VueI18n({
  locale: 'zhTW'
});
Vue.use(VeeValidate, {
  i18n,
  dictionary: {
    zhTW
  }
});
```

5. 在 Vue 物件中新增 i18n
```cmd
new Vue({
  i18n,
  el: '#app',
  components: { App },
  template: '<App/>',
  router,
})
```

如果不想使用 chrome 的 required，可以使用套件的 API
在送出表單的 API 之前加入下段
```javascript
this.$validator.validate().then(valid => {
      if (!valid) {
        // do stuff if not valid.
      }
})
```

所以可以寫成下面這樣
```javascript
createOrder() {
  const vm = this;
  const url = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/order`;
  const order = vm.form;
  this.$validator.validate().then(valid => {
    // 如果欄位正確，則送出表單執行送出訂單 API
    if (valid) {
      this.$http.post(url, { data: order }).then(response => {
        console.log("訂單已建立", response);
        vm.isLoading = false;
      });
    } else {
      // 否則跳出錯誤訊息
      console.log("欄位輸入不正確");
    }
  });
}
```

# Vue cli 3.0

>參考 [Vue cli 3.0](https://cli.vuejs.org/zh/)


## 安裝

如果已安裝 2.0，必須先移除舊版再安裝
```cmd
npm uninstall vue-cli -g

npm install -g @vue/cli

<!-- 查看版本 -->
vue --version
```

## 建立專案

建立方式有 command line 與 GUI 建立
```cmd
vue create hello-world
```

## 運行專案
```cmd
npm run serve
```

## 資料夾結構

* 在 2.0 版本中會出現很多 Webpack 的設定檔，但在 3.0 全部放在 node_mudules/@vue 中，而且基本上不太會動到

* 在 3.0 中 .vue 預設不省略，例如: `import App from './App.vue'`，即使省略還是可以編譯，但 ESlint 會跳提示。

* public: 這裡檔案不會被編譯，但其實 index.html 還是會被壓縮並且插入 src 內容。

* src: 這裡所有檔案都會被編譯。

* views: 分頁的元件。

* components: 內部的元件。

## 打包

此時會出現 dist 資料夾，交檔案主要是要交這個

```cmd
npm run build
```

打開 dist 的 index.html 會看到預設注入的 `<link>` 標籤

最主要是下面的 `<script>`:

```htmlmixed
<!-- vendors 就是外部載入的資源，如 node_mudules 去 import -->
<script src=/js/chunk-vendors.a411fc52.js> </script>
<!-- 自己撰寫的部分 -->
<script src=/js/app.ed52e9ed.js> </script>
```

dist 的 index.html 無法直接開啟，但可以使用 vscode 的 live server 開啟。

## 環境變數

開發環境與正式環境不同

### 新增 .env 檔

[官方文件](https://cli.vuejs.org/zh/guide/mode-and-env.html#%E6%A8%A1%E5%BC%8F)

新增一個 .env 檔(不需要名稱)

### 設定變數
```.env
<!-- 前面的 VUE_APP 是固定的，為了讓 src 讀到這變數 -->
<!-- API 這部分可以自定義名稱 -->
<!-- 注意這裡等號右邊不需要引號，跟 2.0 config 不同 -->
VUE_APP_API=http://localhost:8080/
```

可以在 App.vue 中加入下段查看一下變數
```javascript
export default {
  name: "app",
  components: {
    HelloWorld
  },
  created() {
    console.log(process.env.VUE_APP_API);
  }
};
```

### 自訂 .env 的 mode

新增一個 .env.Ken 檔

可以在裡面加入這段
```.env
VUE_APP_APIPATH=https://vue-course-api.hexschool.io
VUE_APP_CUSTOMPATH=kenapi
```

在 package.json 中，修改下列這段

並執行 npm run serve
```json=
scripts": {
    "serve": "vue-cli-service serve --mode Ken",
    "build": "vue-cli-service build"
  }
```

### 預設的 .env

基本上用這兩個預設的就好了

新增檔案:
* .env.development
* .env.production

各放入開發與正式的環境變數即可，注意權重會大於沒有名稱的 .env

:sunny: 為什麼要分環境變數

![](https://i.imgur.com/e7Jo7Ym.png)

## 圖形化介面

>參考[課程 113](https://www.udemy.com/course/vue-hexschool/learn/lecture/11627486)

設定介紹較多，可以直接複習課程即可。

啟用
```cmd
vue ui
```

## 快速原型開發

適合用在小型專案

>參考 [快速原型開發](https://cli.vuejs.org/zh/guide/prototyping.html)
>
>設定複雜，參考[課程 117](https://www.udemy.com/course/vue-hexschool/learn/lecture/11627524)

## Vuex

適合大型專案用的工具並

僅適合用於簡單、資料量小的情境。

>[Vuex](https://vuex.vuejs.org/zh/guide/)

![](https://i.imgur.com/QKhJ7BV.png)

![](https://i.imgur.com/i0j98LY.png)
![](https://i.imgur.com/2SQ6YKt.png)

![](https://i.imgur.com/V8MtQ9F.png)

### 安裝
```cmd
npm install vuex --save
```

### 啟用
```javascript
import Vuex from 'vuex'

Vue.use(Vuex)
```

### 新增 store 資料夾

在 src 下新增 store 資料夾，並在裡面新增 index.js

index.js
```javascript
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({

});
```

main.js 就必須 import
```javascript
import store from './store';

// 然後在 new Vue 下就必須掛載 store
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>',
});
```

設定超級複雜，但創建專案時會自動建立。

### state

放資料的地方

跟元件不同的地方在於元件中是用 `data() {}`

state 則是 `state: {}`

:sunny: 可以在 state 上面加入 strict: true 進入嚴謹模式

### actions

actions 只處理 AJAX 行為，==不可以去改變資料==

例如:
```javascript
actions: {
    // context 是固定的參數，參考官網，還有許多其他的屬性
    // payload 是外部傳來的參數，是自定義的，名稱為'載荷'
    updateLoading(context, payload) {
      // context.commit() 去呼叫 mutations 並帶入參數
      context.commit('LOADING',payload)
    }
  }
```

### mutations

只改變資料，==不能處理 AJAX 或其他非同步行為==，如 setTimeout
```javascript
mutations: {
    // 建議在 mutations 以常數(大寫)撰寫
    // 第一個參數是固定的，代表上面資料狀態的 state
    LOADING(state, payload){
      state.isLoading = payload
    }
  }
```

### dispatch()

在元件中使用 dispatch 呼叫 actions 的方法

:sunny: 參數只能傳 1 個，若要傳多個參數，可以使用物件方式傳遞

接收參數的 actions 那邊可以使用物件形式來接收，就可以直接解構
```javascript
this.$store.dispatch('actions裡函式', 參數)
```

### getters

>寫法複雜，複習[課程 125](https://www.udemy.com/course/vue-hexschool/learn/lecture/11240780)

==類似 computed==

需先在 /store/index.js 中加入下段

使用解構，只取出 Vuex 的 mapGetters、mapActions 方法
```javascript
import { mapGetters } from "vuex";
```

接著在下面新增
```javascript
// state 為固定
getters:{
  categories(state){
    return state.categories;
  },
  products(state){
    return state.products;
  }
}
```

然後在元件中的 computed 改為
```javascript
...mapGetters(['categories','products'])
```

也能使用 mapActions，在元件中的 methods 改為
```javascript
...mapActions(['getProducts']),
```

### 模組化

大型專案中，index.js 裡也會非常多行程式碼，所以會繼續拆分進行模組化

新增一個檔案 /store/product.js
```javascript

```

超級複雜，直接複習[課程126](https://www.udemy.com/course/vue-hexschool/learn/lecture/11240786)
