ブログ
これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。
Vue3 compositon api よく使うところ一覧
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)
beforeCreate
と created
はこの中に書く
第一引数はプロパティ props
props を分割代入する必要がある場合は、setup 関数内で toRefs を使う
ref
、 toRef
については後で説明
<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)
と書くようにする
props
、 attrs
、 slots、
emit
にはアクセスできる
data
、 computed
、 methods
、 refs (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>
コメントはありません。