67
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vue Function API で簡単に view と logic を分離しよう

Last updated at Posted at 2019-08-09

書いた日: 19/8/9

19/8/29追記

function api は composition api にとって変わられる可能性が高そうです。
考え方自体はほぼ同じですがapiが変わっているため、
適宜読み替えてもらう必要があります。
https://www.npmjs.com/package/vue-function-api
https://vue-composition-api-rfc.netlify.com/

TL;DR

・だいたい custom hooks だ
・view と logic の分離がすごい簡単
・さよなら render props、scoped slots、HoC、function as children、mixin!

何が嬉しいのか分からなかったので使って調べました。
vue-function-api、view と logic の分けやすさ、custom hooks が vue でもできる事を喜ぶものなんだなーという印象です。ありがたい。

インストール

Vue2.6以降の環境を用意して

$ yarn add vue-function-api

で、ルートとなる js ファイルに

import { plugin } from 'vue-function-api'

Vue.use(plugin)

を追記すれば準備完了です。

改変元のソースを用意する

新しい書き方を学ぶには
既存コードの改変がとっつきやすいです。

お決まりの Todo アプリで行きます。まずはテンプレートを用意しましょう。

きみは今まで作った Todo アプリの数をおぼえているか。

<script>
export default {
  data:() => ({
    todoList: [],
    tempTodoName: '',
    taskId: 0
  }),
  computed: {
    itemCount() {
      return this.todoList.length
    }
  },
  methods: {
    updateTaskName(name) {
      this.tempTodoName = name
    },
    addTodoItem() {
      this.todoList = [
        ...this.todoList,
        {
          id: this.taskId,
          name: this.tempTodoName,
          done: false
        }
      ];
      this.tempTodoName = ''
      this.taskId = this.taskId + 1
    },
    removeTodoItem(id) {
      this.todoList = this.todoList.filter(item => item.id !== id);
    },
    toggleDoneStatus(id){
      this.todoList = this.todoList.map(item =>
        item.id === id ? { ...item, done: !item.done } : item
      );
    }
  }
}
</script>

動作確認が取れたら vue-function-api で書き換えてみましょう。

vue-function-api での書き換え

vue-function-api で提供される API は
vue に新しく追加された setup() の中で使います。

data(){} の書き換え

vue-function-api から value()をインポートして使います。今までのdata() { return {} }に代わって、値ごとに value を使って定義します。
value()でラップした値にアクセスする時は 変数名.value です。

// before

export default {
  data:() => ({
    todoList: [],
    tempTodoName: '',
    taskId: 0
  }),
  ...
}
// after
import { value } from 'vue-function-api'

const useTodo = () => {
  const todoList = value([]); // todoList.value = [];
  const tempTodoName = value("");
  const taskId = value(0);

  return {
    todoList,
    tempTodoName,
    taskId
  }
}

export default {
  setup() {
    ...useTodo()
  }
}

すでに嬉しいですね。
何が嬉しいって data 部分がただの関数としてコンポーネントの外に切り出せています。

computed の書き換え

computed も vue-function-api からインポートして使います。

// before

computed: {
  itemCount() {
    return this.todoList.length
  }
}
// after

const useTodo = () => {
  ...
  const itemCount = computed(() => todoList.value.length);

  return {
    ...
    itemCount
  }
}

export default {
  setup() {
    ...useTodo()
  }
}

前述の通り、value()の値にアクセスする時は 変数名.value です。

methods を書き換える

methods は特にラッパーが提供されていません。
変数名.value プロパティを更新する至ってふつうの関数として書けます。

const useTodo = () => {
  ...
  const updateTaskName = name => { tempTodoName.value = name };

  const incrementTaskId = () => { taskId.value = taskId.value + 1 };

  const addTodoItem = () => {
    todoList.value = [
      ...todoList.value,
      {
        id: taskId.value,
        name: tempTodoName.value,
        done: false
      }
    ];
    updateTaskName("");
    incrementTaskId();
  };

  const removeTodoItem = id => {
    todoList.value = todoList.value.filter(item => item.id !== id);
  };

  const toggleDoneStatus = id => {
    todoList.value = todoList.value.map(item =>
      item.id === id ? { ...item, done: !item.done } : item
    );
  };

  return {
    ...
    addTodoItem,
    removeTodoItem,
    updateTaskName,
    toggleDoneStatus
  };
}

export default {
  setup() {
    ...useTodo()
  }
}

完成形

はい。処理をvueインスタンスの外にまるっと切り出す事が出来ました。
viewとlogicをそれぞれ別コンポーネントに切り出して、scoped slotsで渡して…といった手間がこれだけで収まってしまっています。

import { value, computed } from "vue-function-api";

const useTodo = () => {
  const todoList = value([]);

  const tempTodoName = value("");

  const taskId = value(0);

  const itemCount = computed(() => todoList.value.length);

  const updateTaskName = name => {
    tempTodoName.value = name;
  };

  const incrementTaskId = () => {
    taskId.value = taskId.value + 1;
  };

  const addTodoItem = () => {
    todoList.value = [
      ...todoList.value,
      {
        id: taskId.value,
        name: tempTodoName.value,
        done: false
      }
    ];
    updateTaskName("");
    incrementTaskId();
  };

  const removeTodoItem = id => {
    todoList.value = todoList.value.filter(item => item.id !== id);
  };

  const toggleDoneStatus = id => {
    todoList.value = todoList.value.map(item =>
      item.id === id ? { ...item, done: !item.done } : item
    );
  };

  return {
    todoList,
    itemCount,
    tempTodoName,
    addTodoItem,
    removeTodoItem,
    updateTaskName,
    toggleDoneStatus
  };
};

export default {
  setup() {
    return {
      ...useTodo()
    }
  }
}

useTodo() を別ファイルに切り出してコンポーネントでimportしてもよし。
useTodo() をさらに細かい関数に切り出してもよし。自由度が高まりますね。

完走した感想

意外にとっつきやすく、scoped slots に breaking change が入って大ショックだった自分には嬉しい API でした。
Todo アプリ程度では覚える事も少なく、可読性も担保されています。

react hooks と違い、更新用の関数を提供せず代入メインでやって行くのは vue らしいと感じました。
設計のベストプラクティスは掴めてませんが、vue3.0 が楽しみです

参考資料

https://github.com/vuejs/vue-function-api#readme
https://bezkoder.com/vue-function-api-example/

67
52
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?