お問い合わせ

ブログ

これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。

Vue3 compositon api よく使うところ一覧

okuda Okuda 2 years

compositon api

参考サイト

https://v3.ja.vuejs.org/guide/composition-api-setup.html https://qiita.com/azukiazusa/items/1a7e5849a04c22951e97
https://v3.ja.vuejs.org/api/basic-reactivity.html#reactive
https://v3.ja.vuejs.org/api/refs-api.html

モジュールのインポート

import {ref, reactive, toRefs, computed, watch, watchEffect, onMounted } from 'vue'

beforeCreateとcreated

setup(prips, context)

beforeCreatecreated はこの中に書く

第一引数はプロパティ props

props を分割代入する必要がある場合は、setup 関数内で toRefs を使う reftoRef については後で説明

<template>
  <div>{{ title }}: {{ sub }}</div>
</template>
import { toRef, toRefs } from "vue";
export default {
  props: {
    title: String,
    sub: String
  },
  setup(props, context) {

    console.log(props.title)
    // refに変換して分割代入
    const { title } = toRefs(props)
    // ref変換後はvalueプロパティに値が入る
    console.log(title.value)

    // 一つだけrefに変換する場合
    const sub = toRef(props, 'sub')

    // テンプレート内で使用するものはreturnする
    return {
      title,
      sub
    }
  }
}

第2引数は context

context は今までの this と同じ感じ
prips がない場合はわかりやすくするために setup(_, context) と書くようにする
propsattrsslots、 emit にはアクセスできる
datacomputedmethodsrefs (template refs) はコンポーネントインスタンスがまだ作成されてないのでアクセスできない

dataはreactiveとrefになる

ref(値)

  • ref(reference)はrefオブジェクトを作成して引数で受け取っ他値をvalueプロパティに持つ
  • setup内で値を取得する場合はxxx.valueのようにvalueにアクセスする
  • テンプレート内で使用するときは .value をつけない
  • Number、Stringのようなプリミティブな型(オブジェクトないもの)のリアクティブ化に使う (今のところこれで使い分ける)
<template>
  <div>name: {{ name }}, age: {{ age }}</div>
</template>
import { ref } from 'vue'
export default {
  setup(props, context) {

    // プリミティブな型(オブジェクトないもの)のリアクティブ化に使う
    const name = ref('test!')
    const age = ref(0)

    console.log(name)
    console.log(name.value)

    // テンプレート内で使用するものはreturnする
    return {
        name,
        age
    }
  }
}

reactive(オブジェクト)

  • 分割代入すると、リアクティブにならない
  • reactive分割代入のしたいときは、toRefs()を使う
  • toRef, toRefしたものはrefとなるのでsetup内で値にアクセスする場合はvalueを見る
  • reactiveに含まれる一部のプロパティの値をリアクティブにしたい場合、toRef()を使う
  • プリミティブ以外のもの(オブジェクト)のリアクティブ化に使う (今のところこれで使い分ける)
<template>
  <div>state: {{ name }}, age: {{ age }}</div>
</template>
import { reactive, toRef, toRefs } from 'vue'
export default {
  setup(props, context) {

    // プリミティブ以外のもの(オブジェクト)のリアクティブ化に使う
    const state = reactive({
        aaa: '11111',
        bbb: '22222',
    });

    // 分割代入はtoRefsを使用する
    const {aaa, bbb} = toRefs(state);

    // reactiveに含まれる一部のプロパティの値をリアクティブにしたい場合、toRef()を使う
    const aaa1 = toRef(state, 'aaa');

    // テンプレート内で使用するものはreturnする
    return {
      state,
      aaa,
      bbb,
    }
  }
}

methods

通常のJavaScriptの関数を宣言してリターンする

<template>
  <div>
    <ul v-if="todo.items.length">
      <li v-for="(item, index) in todo.items" :key="index">{{item.title}} {{item.created_at.toLocaleString()}}</li>
    </ul>
    <input type="text" v-model="todo.title" />
    <button @click="add">add</button>
  </div>
</template>
<script>
export default {
  setup(props, context) {

    // 通常のJavaScriptの関数を宣言する
    const add = () => {
      todo.items = [
        ...todo.items,
        {
          title: todo.title,
          created_at: new Date(),
        },
      ];
    };

    // テンプレート内で使用するものはreturnする
    return {
      todo,
      add,
    }
  }
}
</script>

computed

computed関数を使用する

<template>
  <div>
    <h2>{{ title_sub }}</h2>
  </div>
</template>
<script>
import { computed } from "vue";
export default {
  setup(props, context) {

    // computed関数を使用する
    const title_sub = computed(() => {
      return `${title.value} - ${sub.value}`
    });

    // テンプレート内で使用するものはreturnする
    return {
      title_sub,
    }
  }
}
</script>

watch

watch関数を使用する 第1引数:監視する対象のリアクティブな値(ref、reactive、computed)
第2引数:関数

<template>
  <div>
    <p>
      Ask a yes/no question:
      <input v-model="question" />
    </p>
    <p>{{ answer }}</p>
  </div>
</template>
<script>
import { watch } from "vue";
export default {
  setup(props, context) {

    const question = ref("");
    const answer = ref("Questions usually contain a question mark. ;-)");

    const getAnswer = async (value) => {
      const response = await axios.get("test").catch((error) => {
        console.log(error);
      });

      return response.data;
    };

    // question が変わるたびに、この関数が実行される
    watch(question, async (new_question, old_question) => {
      if (new_question.indexOf("?") > -1) {
        answer.value = "Thinking...";
        answer.value = await getAnswer(answer.value);
      }
    });

    // 複数監視する場合
    const ccc = ref("");
    const ddd = ref("");
    const eee = ref("ccc or ddd change and add ?");

    watch([ccc, ddd], async ([new_ccc, new_ddd], [old_ccc, old_ddd]) => {
      if (new_ccc.indexOf("?") > -1 || new_ddd.indexOf("?") > -1) {
        eee.value = "Thinking...";
       eee.value = await getAnswer(answer.value);
      }
    });

    // テンプレート内で使用するものはreturnする
    return {
      question,
      answer,
      ccc,
      ddd,
      eee,
    }
  }
}
</script>

watchEffect

watchEffect関数を使用する 以下の点に注意

  • watchより機能が劣る
  • 関数内にあるレアクティブ値を監視するので監視対象が明確でない
  • 以前の値と現在の値にアクセスできない
<template>
  <div>
    <p>
      Ask a yes/no question:
      <input v-model="question" />
    </p>
    <p>{{ answer }}</p>
  </div>
</template>
<script>
import { watch } from "vue";
export default {
  setup(props, context) {

    const fff = ref("");
    watchEffect(() => console.log(`fff: ${val1.value}`));

    // テンプレート内で使用するものはreturnする
    return {
      fff,
    }
  }
}
</script>

lifecycle

今まで Conposition API
beforeCreate setup内
created setup内
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured

onMountedとエレメントの取得

this.$refs で取得していたものは以下のようにして取得
setup内で使う場合も return に含めないと取得できない

<template>
  <div ref="root">
    ...
  </div>
</template>
<script>
import { ref, onUnmounted } from "vue";
export default {
  setup(props, context) {

    const root = ref(null);
    onMounted(() => {
      console.log(root.value);
    });

    // destroyedはonUnmountedに変更されている
    onUnmounted(() => {
      console.log('unmounted!');
    })

    // テンプレート内で使用するものはreturnする
    // setup内で使う場合も`return`に含めないと取得できない  
    return {
      root,
    }
  }
}
</script>

emit

setup関数の第2引数である context をつかって context.emit で呼び出す コンポーネントが発行するイベントは emits オプションで定義できる

<template>
  <div>
     <input v-model="ggg" />
    <button @click="onClick">test emit</button>
  </div>
</template>
<script>
import { ref, onUnmounted } from "vue";
export default {
  // コンポーネントが発行するイベントは emits オプションで定義できる
  emits: ['test_action'],
  setup(props, context) {

    const ggg = ref("");
    const onClick = () => {
      context.emit("test_action", ggg.value);
      ggg.value = "";
    };

    // テンプレート内で使用するものはreturnする
    return {
      ggg,
    }
  }
}
</script>

// パレント側では以下のように使用する
// <Test @test_action="testFunc" />

その他

isRef

値が ref オブジェクトであるかどうかをチェック

unref

val = isRef(val) ? val.value : val のシュガー関数

readonly

オブジェクトや ref を受け取り、オリジナルへの読み取り専用プロキシを返す
読み取り専用となる
ディープで、ネストされたプロパティへのアクセスも同様に読み取り専用となる

isProxy

オブジェクトが reactive または readonly で作成されたプロキシかどうかをチェック

isReactive

オブジェクトが reactive で作成されたリアクティブプロキシかどうかをチェック
readonly で作成されたプロキシが、 reactive で作成された別のプロキシをラップしている場合も true

isReadonly

オブジェクトが readonly で作成された読み取り専用プロキシかどうかをチェック

composables

モジュールに分割

src/composables/UseTodo.js を作成

export default (todo) => {
    const add = () => {
        const created_at = new Date();
        todo.items = [
            ...todo.items,
            {
                id: created_at.getTime(),
                subject: todo.title,
                done: false,
                created_at: created_at.toLocaleString(),
            },
        ];
        todo.subject = null;
    };

    return {
        add,
    }
}
<template>
  <div>
    <ul v-if="todo.items.length">
      <li
        v-for="(item, index) in todo.items"
        :key="index"
        @click="toggle(item.id)"
      >
        {{ item.id }} : {{ item.subject }} : {{ item.created_at }}
        <span v-show="item.done">⚪</span>
        <span v-show="!item.done">⚫</span>
      </li>
    </ul>
    <input type="text" v-model="todo.subject" />
    <button @click="add">add</button>
  </div>
</template>
<script>
// 作成したモジュールをインポート
import useTodo from "@/composables/UseTodo";
export default {
  // コンポーネントが発行するイベントは emits オプションで定義できる
  emits: ['test_action'],
  setup(props, context) {

    // この部分をモジュールとして分ける
    // const add = () => {
    //   const created_at = new Date();
    //   todo.items = [
    //     ...todo.items,
    //     {
    //       id : created_at.getTime(),
    //       subject: todo.title,
    //       done: false,
    //       created_at: created_at.toLocaleString(),
    //     },
    //   ];
    //   todo.subject = null;
    // };

    // インポートしたuseTodoからaddファンクションを使用する
    const { add } = useTodo(todo);
    // テンプレート内で使用するものはreturnする
    return {
      add,
    }
  }
}
</script>

今回使ったファイル

test.vue

<template>
  <div ref="root">
    <div>title:{{ title }}, sub:{{ sub }}</div>
    <div>name:{{ name }}, age:{{ age }}</div>
    <div>state.aaa:{{ state.aaa }}, state.bbb:{{ state.bbb }}</div>
    <div>aaa:{{ aaa }}, bbb:{{ bbb }}</div>
    <div>aaa1:{{ aaa1 }}</div>
    <ul v-if="todo.items.length">
      <li
        v-for="(item, index) in todo.items"
        :key="index"
        @click="toggle(item.id)"
      >
        {{ item.id }} : {{ item.subject }} : {{ item.created_at }}
        <span v-show="item.done">⚪</span>
        <span v-show="!item.done">⚫</span>
      </li>
    </ul>
    <input type="text" v-model="todo.subject" />
    <button @click="add">add</button>
    <h2>{{ title_sub }}</h2>

    <p>
      Ask a yes/no question:
      <input v-model="question" />
    </p>
    <p>{{ answer }}</p>

    <p>ccc: <input v-model="ccc" /></p>
    <p>ddd: <input v-model="ddd" /></p>
    <p>{{ eee }}</p>
    <p>fff: <input v-model="fff" /></p>
    <input v-model="ggg" />
    <button @click="onClick">test emit</button>
  </div>
</template>

<script>
import {
  ref,
  reactive,
  toRef,
  toRefs,
  computed,
  watch,
  watchEffect,
  onMounted,
  onUnmounted,
} from "vue";
import useTodo from "@/composables/UseTodo";
export default {
  name: "Test",
  props: {
    title: {
      type: String,
      default: null,
    },
    sub: {
      type: String,
      default: null,
    },
  },
  emits: ["test_action"],
  setup(props, context) {
    /*
     * props
     */
    console.log(props.title);
    // refに変換して分割代入
    const { title } = toRefs(props);
    // ref変換後はvalueプロパティに値が入る
    console.log(title);
    console.log(title.value);

    // 一つだけrefに変換する場合
    const sub = toRef(props, "sub");

    /*
     * ref
     */
    // プリミティブな型(オブジェクトないもの)のリアクティブ化に使う
    const name = ref("test");
    const age = ref(20);

    // setupの中で値にアクセスする場合はvalueを使う
    console.log(name);
    console.log(name.value);

    /*
     * reactive
     */
    // プリミティブ以外のもの(オブジェクト)のリアクティブ化に使う
    const state = reactive({
      aaa: "11111",
      bbb: "22222",
    });

    // 分割代入はtoRefsを使用する
    const { aaa, bbb } = toRefs(state);
    console.log(aaa);
    console.log(aaa.value);

    // reactiveに含まれる一部のプロパティの値をリアクティブにしたい場合、toRef()を使う
    const aaa1 = toRef(state, "aaa");

    /*
     * methods
     */
    const todo = reactive({
      subject: null,
      items: [],
    });

    // 通常のJavaScriptの関数を宣言してリターンする
    // const add = () => {
    //   const created_at = new Date();
    //   todo.items = [
    //     ...todo.items,
    //     {
    //       id : created_at.getTime(),
    //       subject: todo.title,
    //       done: false,
    //       created_at: created_at.toLocaleString(),
    //     },
    //   ];
    //   todo.subject = null;
    // };

    // インポートしたuseTodoからaddファンクションを使用する
    const { add } = useTodo(todo);

    const toggle = (id) => {
      const item = todo.items.find((item) => item.id === id);
      item.done = !item.done;
    };

    /*
     * computed
     */
    // computed関数を使用する
    const title_sub = computed(() => {
      return `${title.value} - ${sub.value}`;
    });

    /*
     * watch
     */
    const question = ref("");
    const answer = ref("Questions usually contain a question mark. ;-)");

    const getAnswer = async (value) => {
      const response = await axios.get("test").catch((error) => {
        console.log(error);
      });

      return response.data;
    };

    // question が変わるたびに、この関数が実行される
    watch(question, async (new_question, old_question) => {
      if (new_question.indexOf("?") > -1) {
        answer.value = "Thinking...";
        answer.value = await getAnswer(answer.value);
      }
    });

    // 複数監視する場合
    const ccc = ref("");
    const ddd = ref("");
    const eee = ref("ccc or ddd change and add ?");

    watch([ccc, ddd], async ([new_ccc, new_ddd], [old_ccc, old_ddd]) => {
      if (new_ccc.indexOf("?") > -1 || new_ddd.indexOf("?") > -1) {
        eee.value = "Thinking...";
        eee.value = await getAnswer(answer.value);
      }
    });

    /*
     * watchEffect
     */
    const fff = ref("");
    watchEffect(() => console.log(`fff: ${fff.value}`));

    /*
     * lifecycle
     */
    const root = ref(null);
    onMounted(() => {
      console.log(root.value);
    });

    onUnmounted(() => {
      console.log("unmounted!");
    });

    /*
     * emit
     */
    const ggg = ref("");
    const onClick = () => {
      context.emit("test_action", ggg.value);
      ggg.value = "";
    };

    // テンプレート内で使用するものはreturnする
    return {
      // props
      title,
      sub,
      // ref
      name,
      age,
      // reactive
      state,
      aaa,
      aaa1,
      bbb,
      // methods
      todo,
      add,
      toggle,
      // computed
      title_sub,
      // watch
      question,
      answer,
      ccc,
      ddd,
      eee,
      // watchEffect
      fff,
      // lifecycle
      root,
      onClick,
      // emit
      ggg,
    };
  },
};
</script>
Vue3 compositon api よく使うところ一覧 2021-11-30 17:37:03

コメントはありません。

4324

お気軽に
お問い合わせください。

お問い合わせ
gomibako@aska-ltd.jp