Vue3でいろいろ試してるけど、若干混乱してきたので、
ガイドを再度読み直してみた。慣れるまでむずい。。
まとめ
- リアクティブの基本
- リアクティブにしてくれるのは
ref()
。オブジェクトもdeepで追跡 reactive()
はリンクを保ったまま、ラップ/アンラップをしてくれる便利関数/オブジェクト- reactiveオブジェクトから一部を取り出すときは、
toRefs/toRef
を使って、リンクを保つ - Refから値を取り出す便利関数
unref()
もある
- リアクティブにしてくれるのは
- 基本要素
- computed ... Refを返す
- useState ... Refを返す
- props ... Refを返す
- その他
- composition関数からreactiveオブジェクトを返すとき ...
toRefs()
- propのrefをcomposition関数に渡したいときは ...
toRef()
- toRefsでは省略可能なpropsを取得できない
- composition関数からreactiveオブジェクトを返すとき ...
宣言
リアクティブな状態を作る方法は2つ
reactive()
... オブジェクトref()
... プリミティブ or オブジェクト
reactive()
もref()
もディープにリアクティブになる。
reactive()
はアンラップした状態で利用できるため、通常のオブジェクトのように利用できる。
// reactive() import { reactive } from 'vue' const state = reactive({ count: 0 }) // ----------- // ref() import { ref } from 'vue' const count = ref(0)
リアクティブオブジェクトへのアクセス
アンラップ
const countRef = ref(0) const state = reactive({ count: countRef }); console.log(state.count) // 0 state.count = 1 console.log(countRef.value) // 1
reactive
を経由すると、内部のrefにアクセスできる。
refの再代入
const otherCount = ref(2) state.count = otherCount console.log(state.count) // 2 console.log(countRef.value) // 1
しかし、再代入すると、refが置き換わるため、リンクが切れる。
ArrayやMapはアンラップされない
const books = reactive([ref('Vue 3 Guide')]) // ここでは .value が必要です console.log(books[0].value) const map = reactive(new Map([['count', ref(0)]])) // ここでは .value が必要です console.log(map.get('count').value)
refのアンラップはリアクティブな Object の中の入れ子となっている場合にのみ発生します。
refがArrayやMapのようなネイティブのコレクション型からアクセスされた場合、アンラップは行われません
分割代入はリアクティブを失う
import { reactive } from 'vue' const book = reactive({ author: 'Vue Team', title: 'Vue 3 Guide', }) // リアクティブではない let { author, title } = book // リアクティブを保持 let { author, title } = toRefs(book)
reactive()
オブジェクトのリアクティブなコピーを返します。
リアクティブの変換は「ディープ」で、ネストされたすべてのプロパティに影響します。
const obj = reactive({ count: 0 })
reactive
は、refのリアクティビティを維持しながら、全ての深さのrefをアンラップします。
const count = ref(1) const obj = reactive({ count }) // ref はアンラップされる console.log(obj.count === count.value) // true // `obj.count` が更新される count.value++ console.log(`${count.value}, ${obj.count}`) // 2, 2 // `count` の ref も更新される obj.count++ console.log(`${count.value}, ${obj.count}`) // 3, 3
reactive
のプロパティにrefを代入すると、そのrefは自動的にアンラップされます。
const count = ref(1) const obj = reactive({}) obj.count = count console.log(obj.count) // 1 console.log(obj.count === count.value) // true
ref()
内部の値を受け取り、リアクティブでミュータブルなrefオブジェクトを返します。
const count = ref(0) console.log(count.value) // 0
refの値としてオブジェクトが割り当てられている場合、
そのオブジェクトはreactive関数によってディープなリアクティブになります。
unref()
val = isRef(val) ? val.value : val
のシュガー(簡易)関数です。
const count = ref(0); console.log(count.value); // 0 console.log(unref(count)); // 0
toRef()
reactiveオブジェクトのプロパティに対するrefを作成するために使用できます。 このrefは、リアクティブな接続を維持したまま、引き渡すことができます。
const state = reactive({ foo: 1, bar: 2 }) const fooRef = toRef(state, 'foo') fooRef.value++ console.log(state.foo) // 2 state.foo++ console.log(fooRef.value) // 3
toRefは、propのrefをcomposition関数に渡したいときに便利です
export default { setup(props) { useSomeFeature(toRef(props, 'foo')) } }
toRefは、ソースとなるプロパティが現在存在しない場合でも、使用可能なrefを返します。
これはtoRefsで取得されない省略可能なpropsを扱うときに特に便利です。
toRefs()
reactiveオブジェクトをプレーンオブジェクトに変換します。
変換後のオブジェクトの各プロパティは、元のオブジェクトの対応するプロパティを指すrefとなります。
const state = reactive({ foo: 1, bar: 2 }) const stateAsRefs = toRefs(state) /* stateAsRefs の型: { foo: Ref<number>, bar: Ref<number> } */ // ref と元のプロパティは「リンク」している state.foo++ console.log(stateAsRefs.foo.value) // 2 stateAsRefs.foo.value++ console.log(state.foo) // 3
toRefsは、composition関数からreactiveオブジェクトを返すときに便利で、
利用する側のコンポーネントはリアクティビティを失うことなく、返されたオブジェクトを分割代入できます
function useFeatureX() { const state = reactive({ foo: 1, bar: 2 }) // 状態で動作するロジック // 返すときに ref に変換する return toRefs(state) } export default { setup() { // リアクティビティを失うことなく分割代入できる const { foo, bar } = useFeatureX() return { foo, bar } } }
toRefsはソースオブジェクトに含まれるプロパティのrefを生成するだけです。
特定のプロパティのリファレンスを作成するには、代わりにtoRefを使用してください。
useState
useState<T>(init?: () => T | Ref<T>): Ref<T> useState<T>(key: string, init?: () => T | Ref<T>): Ref<T>
おまけ: 型定義
- core/ref.ts at 3.2 · vuejs/core
- core/reactive.ts at 3.2 · vuejs/core
- core/apiSetupHelpers.ts at v3.2.37 · vuejs/core
// ref function ref<T>(value: T): Ref<UnwrapRef<T>>; // reactive function reactive<T extends object>(target: T): UnwrapNestedRefs<T>; // toRefs function toRefs<T extends object>(object: T): ToRefs<T>; // toRef function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>; // runref function unref<T>(ref: T | Ref<T>): T; // useState function useState<T>(key: string, init?: () => T | Ref<T>): Ref<T> // computed function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>; function computed<T>(options: WritableComputedOptions<T>): WritableComputedRef<T>; // props function defineProps<TypeProps>(): Readonly<TypeProps>; // ************************************************************************ // types type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T> type UnwrapRef<T> = T extends ShallowRef<infer V> ? V : T extends Ref<infer V> ? UnwrapRefSimple<V> : UnwrapRefSimple<T>; type ShallowUnwrapRef<T> = { [K in keyof T]: T[K] extends Ref<infer V> ? V : T[K] extends Ref<infer V> | undefined ? unknown extends V ? undefined : V | undefined : T[K] }; type UnwrapRefSimple<T> = T extends | Function | CollectionTypes | BaseTypes | Ref | RefUnwrapBailTypes[keyof RefUnwrapBailTypes] | { [RawSymbol]?: true } ? T : T extends Array<any> ? { [K in keyof T]: UnwrapRefSimple<T[K]> } : T extends object & { [ShallowReactiveMarker]?: never } ? { [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]> } : T type ToRefs<T = any> = { [K in keyof T]: ToRef<T[K]> }; type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>; interface ComputedRef<T = any> extends WritableComputedRef<T> { readonly value: T [ComputedRefSymbol]: true }; interface WritableComputedRef<T> extends Ref<T> { readonly effect: ReactiveEffect<T> } type ComputedGetter<T> = (...args: any[]) => T; type ComputedSetter<T> = (v: T) => void; interface WritableComputedOptions<T> { get: ComputedGetter<T> set: ComputedSetter<T> };