【Vue】メソッドを外部のjsファイルに移動する方法。外部ファイルの処理結果をvueに戻す。

メソッドを外部のjsファイルに移動する方法について。

変数を外部ファイル（.js）に切り出してexport defaultでインポートする方法は[こちら](https://qiita.com/yuta-38/items/6b3307f70a17d63a5cd2)

今回は応用編として、.vue内のメソッドを外部ファイルに切り出す方法。

##流れと考え方
操作の流れは、vueのデータをjsに渡して、処理後に戻す作業になる。

**(1).vue　->　(2).js　-> (3).vue**

<br>
**(1) .vueの処理**
・vueのメソッドを実行
・メソッド内で変数を定義し、jsファイルにプロパティ名でデータを渡す。

**(2) .jsの処理**
・vueのデータを引数としてプロパティで受け取り処理を実行後、戻り値で返す。


**(3) .vueの処理**
.jsから戻ってきた値を、vueの値(this.xxx)に代入する。


<br>
**▼考え方**
Inputする（渡す）データとOutputする（戻す）データの受け渡しを意識することが大切。

```js:.vueの処理（概念）
methods:{
    vueのメソッド名(){
      const {受け取るデータ} = jsファイルのコマンド名({
        渡すデータ
      })
      
      vueのデータ = 受け取るデータ
    }
  } 
```

```js:.jsの処理（概念）
const メソッド名 = ({受け取るデータ}) => {
    処理
  return {
    渡すデータ
  }
}

export default 渡すメソッド
```

jsファイルに引数としてデータを渡し、処理後にreturnでvue側の変数名と一致するデータを返す。

最後に、受け取ったデータをvueのデータに入れて受け取り完了となる。


<br>

##実例
実例で見るとよりわかりやすい。

```js:.vue（scriptの中身）
//外部ファイル(transformationJs.js)からメソッドをjsMultiとしてインポート
import jsMulti from "./transformationJs"

export default {
  data(){
    return{
      A: 1,
      B: 2,
      x: 100,
      y: 200,
    }
  },

  //ここが重要！jsファイルのメソッドとデータをやりとりする記述
  methods:{
    btnClicked(){
      //処理結果をいれるプロパティを用意
      const {A, B} = jsMulti({
        //引数内でプロパティ名を指定してデータを渡す
        A: this.A,   
        B: this.B,
        x: this.x,
        y: this.y
      })
      
      //vueのデータにjsファイルの処理結果を代入
      this.A = A
      this.B = B
    }
  }
}
```

読み込んだのはテンプレートではなくメソッドなので、`components:{}`は不要

```js:.js
//メソッドを定義
const jsMulti = ({A, B, x, y}) => {
  //処理結果を代入する変数を定義
  const _A = A * x
  const _B = B * y

  return {  //ここが重要！
    //vue側で設定したプロパティにデータを入れる
    A: _A,
    B: _B
  }
}

//外部ファイルに渡すメソッド
export default jsMulti
```

・.js側で処理済みの値はわかりやすいように`_`を付けたが、これは別に何でもいい。（Aのままでもいいし、他の新しい変数名でも可）

・.jsのメソッドは最後にreturnをプロパティとして返す。

・`A = _A`はエラーになる。（※プロパティの形になっていない）

・`A`のみで値を入れない場合は、空のデータが返る。 (※AはA：Aの省略表記となるため、変数Aを定義していない場合は空のプロパティを返したことになる。)

>・A:Aの省略表記Aを[short property](https://qiita.com/yuta
38/items/560f8f80ffa5e3b231c0)と呼ぶ



<br>
##処理結果事例とフルコード

上記の実例は、ボタンをクリックする毎に、A × 100、B × 200をする処理となる。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/e33621bb-ab44-e972-2177-fdc67760626b.png)|
|:--|

↓　クリック

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/78971ebb-0c1a-8cbc-f3c9-a999a3a9a6f0.png)|
|:--|


↓　クリック

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/84215fac-68ef-5daf-84ea-643ed09c558d.png)|
|:--|


###▼フルコード

```vue:.vue
<template>
  <div>
　　 <!-- クリックイベントでbtnClickedを発火 -->
    <button
      @click="btnClicked"
    >【Click Me】</button>

    <p>・A:　{{A}}</p>
    <p>・B:　{{B}}</p>

  </div>
</template>

<script>
import jsMulti from "./transformationJs"

export default {
  data(){
    return{
      A: 1,
      B: 2,
      x: 100,
      y: 200,
    }
  },
  methods:{
    btnClicked(){
      const {A, B} = jsMulti({
        A: this.A,
        B: this.B,
        x: this.x,
        y: this.y
      })
      
      this.A = A
      this.B = B
    }
  }
}
</script>
```


```js:transformationJs.js
const jsMulti = ({A, B, x, y}) => {
  const _A = A * x
  const _B = B * y

  return {
    A: _A,
    B: _B
  }
}

export default jsMulti
```



<br>
##配列の中のコマンドを実行する例
v-forで表示した配列として格納されているボタンに対し、外部ファイルの処理を実行する場合の例。(＊流れのみ)


(1)ボタンタグをクリックするとメソッドが発火する。
このとき、引数でクリックされたボタンの要素を渡す。 

`@click="onClickButton(command)`


(2)ボタンクリックで発火させる処理
`command.onClick`でJSファイル内のプロパティonClickを実行している。


```vue:.vue
<script>
import cellCommands from "./commands"

export default {
  methods:{
    onClickButton(command){

      const {
        //js内で処理後の値を格納する変数を用意（output用）
        rows,
        selectedCells,
      } = command.onClick({  //jsファイルに渡すデータ(input用)。プロパティ名: 値　。returnで返す。
        selectedCells: this.selectedCells,
        rows: this.innerValue,
        maxColNum: this.maxColNum,
        thPosition: this.thPosition,
      })

      //jsの出力をvueに渡す（output）
      this.innerValue = rows
      this.selectedCells = selectedCells
    },
  }
}
</script>
```

各要素のプロパティonClickの中に記述された処理が実行される。

```js:commands.js
const commands = [
  {
    cmd: 'clearSelects',
    text: "選択解除",
    icon: 'mdi-cancel',
    isActive: anySelectedCells,
    onClick: ({
      rows,
    }) => {
      return {
        selectedCells:[],
        rows,
      }
    }
  },
  {
    cmd: 'addColumnBefore',
    text:"左に列追加",
    icon: 'mdi-table-column-plus-before',
    isActive: (params) => notThColandSelectedFirstCol(params) && singleSelectedCell(params),
    onClick: ({
      selectedCells,
      rows,
      thPosition
    }) => {

      if(selectedCells.length != 1) {
        return　{
          selectedCells,
          rows,
        }
      }

      //変換後の出力の値は別の変数に代入。一時的として、_変数名とする。
      const _rows = rows.map((tr, index) => {
        let newCell
        if(thPosition == 'row' && index === 0){
          newCell = initTh()
        }else{
          newCell = initTd()
        }
        tr.article_items = [
          ...tr.article_items.slice(0, selectedCells[0].cellIndex),
          ...[newCell],
          ...tr.article_items.slice(selectedCells[0].cellIndex),
        ]
        return tr
      })

      const _selectedCells = selectedCells.map((cell)=>{
        const {cellIndex: cCellIndex} = cell
        if(cCellIndex >= selectedCells[0].cellIndex){
          return {
            ...cell,
            cellIndex: cCellIndex + 1
          }
        } else {
          return cell
        }
      })

      //vueに戻すoutput用
      return {
        selectedCells: _selectedCells,
        rows: _rows,
      }
    }
  },
  {
    cmd: 'addColumnAfter',
    text:"右に列追加",
    icon: 'mdi-table-column-plus-after',
    isActive: singleSelectedCell,
    onClick: ({
      selectedCells,
      rows,
      thPosition,
    }) => {
      if(selectedCells.length != 1) {
        return　{
          selectedCells,
          rows,
        }
      }

      const _rows = rows.map((tr, index) => {
        let newCell
        if (thPosition == 'row' && index === 0) {
          newCell = initTh()
        } else {
          newCell = initTd()
        }

        return {
          ...tr,
          article_items: [
            ...tr.article_items.slice(0, selectedCells[0].cellIndex + 1),
            ...[newCell],
            ...tr.article_items.slice(selectedCells[0].cellIndex + 1),
          ]
        }
      })

      const _selectedCells = selectedCells.map((cell)=>{
        const {cellIndex: cCellIndex} = cell
        if(cCellIndex > selectedCells[0].cellIndex){
          return {
            ...cell,
            cellIndex: cCellIndex + 1
          }
        }else {
          return cell
        }
      })

      return {
        rows: _rows,
        selectedCells: _selectedCells
      }
    }
  },
  {
    cmd: 'removeColumn',
    text:"列削除",
    icon: 'mdi-table-column-remove',
    color: 'error',
    isActive: (params) => anySelectedCells(params) && notAllColumnsSelected(params),
    onClick: ({
      selectedCells,
      rows,
      maxColNum,
    }) => {
      const cellIndicesToRemove = new Set(
        selectedCells.map((cell) => cell.cellIndex)
      )

      if(cellIndicesToRemove.size == maxColNum) {
        return
      }

      const _rows = rows.map((row) => {
        return {
          ...row,
          article_items: row.article_items.filter((cells, cellIndex)=> 
            !cellIndicesToRemove.has(cellIndex)
          )
        }
      })

      return {
        rows: _rows,
        selectedCells: []
      }
    },
  },
  {
    cmd: 'addRowAfter',
    text:"下に行追加",
    icon: 'mdi-table-row-plus-after',
    isActive: singleSelectedCell,
    onClick: ({
      selectedCells,
      rows,
      maxColNum,
      thPosition,
    }) => {
      if(selectedCells.length != 1) {
        return　{
          selectedCells,
          rows,
        }
      }
      
      const _rows = [
        ...rows.slice(0, selectedCells[0].rowIndex + 1),
        ...[getTr( {maxColNum, thPosition} )],
        ...rows.slice(selectedCells[0].rowIndex + 1),
      ]

      const _selectedCells = selectedCells.map((cell) => {
        const { rowIndex: cRowIndex } = cell
        if (cRowIndex > selectedCells[0].rowIndex){
          return {
            ...cell,
            rowIndex:  cRowIndex + 1,
          }
        } else {
          return cell
        }
      })

      return {
        rows: _rows,
        selectedCells: _selectedCells
      }
    },
  },
  {
    cmd: 'addRowBefore',
    text:"上に行追加",
    icon: 'mdi-table-row-plus-before',
    isActive: (params) => singleSelectedCell(params) && notThColandSelectedFirstRow(params),
    onClick: ({
      selectedCells,
      rows,
      maxColNum,
      thPosition,
    }) => {

      if(selectedCells.length != 1) {
        return　{
          selectedCells,
          rows,
        }
      }

      const _rows = [
        ...rows.slice(0, selectedCells[0].rowIndex),
        ...[getTr( {maxColNum, thPosition} )],
        ...rows.slice(selectedCells[0].rowIndex),
      ]
      
      const _selectedCells = selectedCells.map((cell) => {
        const { rowIndex: cRowIndex } = cell
        if (cRowIndex >= selectedCells[0].rowIndex){
          return {
            ...cell,
            rowIndex:  cRowIndex + 1,
          }
        } else {
          return cell
        }
      })

      return {
        rows: _rows,
        selectedCells: _selectedCells
      }
    }
  },
  {
    cmd: 'removeRow',
    text:"行削除",
    icon: 'mdi-table-row-remove',
    color: 'error',
    isActive: (params) => anySelectedCells(params) && notAllRowsSelected(params),
    onClick: ({
      selectedCells,
      rows,
    }) => {
      const rowIndicesToRemove = new Set(
        selectedCells.map((cell)=> cell.rowIndex)
      )
  
      if(rowIndicesToRemove.size == rows.length) return

      const _rows = rows.filter((row, rowIndex)=> 
        !rowIndicesToRemove.has(rowIndex)
      )

      return {
        rows: _rows,
        selectedCells: []
      }
    },
  }
]

export default commands
```

commands.jsの中身はかなり長いので、vueファイルから切り出すことでコードを見やすくすることができる。



<br>
##参考リンク
＞[export defaultとimport fromの使い方](#https://qiita.com/yuta-38/items/54df030f02afa5de7347)
＞[複数の変数やメソッドを渡す方法](#https://qiita.com/yuta-38/items/54df030f02afa5de7347#%E8%A4%87%E6%95%B0%E3%81%AE%E5%A4%89%E6%95%B0%E3%82%92%E5%A4%96%E9%83%A8%E3%81%AB%E6%B8%A1%E3%81%99%E5%A0%B4%E5%90%88)
＞[MDN公式 export default](#https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/export)