TL;DR
-
@vue/composition-apiv1.0.0-beta.21 からreadonly(),shallowReadonly()が提供されるようになった -
readonly()は引数で受け取ったオブジェクトの型を DeepReadonly 化して返しているだけである -
shallowReadonly()は Vue 3 のものとほぼ同じように使える
readonly() と shallowReadonly() とは?
readonly() は Vue 3 ではリリース時から提供されているリアクティビティ API で、リアクティブな状態を受け取ってリードオンリーなリアクティブな状態を返すことを目的とする関数です。
`readonly()` のコード例[^1]
// Vue 3
import { reactive, readonly } from 'vue';
const original = reactive({
countA: 0,
foo: {
countB: 0,
},
});
const copy = readonly(original);
original.countA++;
copy.countA++; // Set operation on key "countA" failed: target is readonly.
original.foo.countB++;
copy.foo.countB++; // Set operation on key "countB" failed: target is readonly.
console.log(original.countA); // 1
console.log(copy.countA); // 1
console.log(original.foo.countB); // 1
console.log(copy.foo.countB); // 1
shallowReadonly() も Vue 3 ではリリース時から提供されているリアクティビティ API で、readonly() との違いはリードオンリーになる範囲がオブジェクトの第1階層の値に限定される点です。
`shallowReadonly()` のコード例[^1]
// Vue 3
import { reactive, shallowReadonly } from 'vue';
const original = reactive({
countA: 0,
foo: {
countB: 0,
},
});
const copy = shallowReadonly(original);
original.countA++;
copy.countA++; // Set operation on key "countA" failed: target is readonly.
original.foo.countB++;
copy.foo.countB++; // ブラウザのコンソール上に警告が出ない
console.log(original.countA); // 1
console.log(copy.countA); // 1
console.log(original.foo.countB); // 2
console.log(copy.foo.countB); // 2
ユースケース
readonly() を用いることで provide() や props などによって子コンポーネントに状態を渡す際、子コンポーネント側からの状態の変更を防ぐことができます。
このことは Vue 3 ドキュメントのリアクティブの基礎 - readonly でリアクティブオブジェクトの変更を防ぐにも記載があります:
例えば、provide されたリアクティブオブジェクトがある場合、それが注入された場所からの変更は防ぎたいことがあります。そうするために、元のオブジェクトに対する読み取り専用のプロキシを作成します
Vue 2 の readonly() と shallowReadonly() について
Vue 2 では Composition API は @vue/composition-api プラグインとして提供されていますが、@vue/composition-api v1.0.0-beta.21 から Vue 2 向けに readonly(), shallowReadonly() が提供されるようになりました。
readonly() は Vue 3 のものとは全く異なる
@vue/composition-api での readonly() の実装1を確認すると、引数で受け取ったオブジェクトの型を DeepReadonly 化して返しているだけであることがわかります。
そのため TypeScript による型チェックをすり抜けてしまった場合2、Vue 3 の readonly() とは異なり状態の変更ができてしまいます:
// Vue 2
import { reactive, readonly } from '@vue/composition-api';
const original = reactive({
countA: 0,
foo: {
countB: 0,
},
});
const copy = readonly(original);
original.countA++;
copy.countA++; // ブラウザのコンソール上に警告が出ない
original.foo.countB++;
copy.foo.countB++; // ブラウザのコンソール上に警告が出ない
console.log(original.countA); // 2
console.log(copy.countA); // 2
console.log(original.foo.countB); // 2
console.log(copy.foo.countB); // 2
shallowReadonly() は Vue 3 のものとほぼ同じように使える
shallowReadonly() は Vue 3 のものと同様にリードオンリーになる範囲がオブジェクトの第1階層の値に限定されますが、Vue 3 のものとほぼ同じように使うことができます。
Vue 2 では場合によっては readonly() よりも shallowReadonly() を使った方が良いことがあります。
例えば、各プロパティの値がすべてプリミティブ型であるようなオブジェクトには shallowReadonly() を用いることで readonly() よりも確実に状態の変更を防ぐことができます:
// Vue 2
import { reactive, readonly, shallowReadonly } from '@vue/composition-api';
// 各プロパティの値がすべてプリミティブ型(string | number | boolean)である
const original = reactive({
text1: '',
text2: '',
text3: '',
number1: 0,
number2: 0,
});
// Not good
const copy1 = readonly(original);
// Better
const copy2 = shallowReadonly(original);
Vue 2 の `shallowReadonly()` にはできないことについて
そのため `shallowReadonly(obj)` 実行時に `obj` が持っているプロパティの値に限ってリードオンリーとすることができます。
// Vue 2
import { reactive, shallowReadonly } from '@vue/composition-api';
const original = reactive({
countA: 100,
});
const copy = shallowReadonly(original);
original.countB = 200;
console.log(copy.countA); // 100
console.log(copy.countB); // undefined
// Vue 3
import { reactive, shallowReadonly } from 'vue';
const original = reactive({
countA: 100,
});
const copy = shallowReadonly(original);
original.countB = 200;
console.log(copy.countA); // 100
console.log(copy.countB); // 200
-
@vue/composition-apiv1.0.0-beta.22 でのreadonly()の実装: https://github.com/vuejs/composition-api/blob/v1.0.0-beta.22/src/reactivity/readonly.ts#L42-L46 ↩ -
特に Vue では
<template>上のv-modelなど型チェックが効きにくい場所があったりします ↩