4
3

More than 3 years have passed since last update.

【Vue 2.x】Composition API の readonly と shallowReadonly で気を付けること

Posted at

TL;DR

  • @vue/composition-api v1.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() の実装2を確認すると、引数で受け取ったオブジェクトの型を DeepReadonly 化して返しているだけであることがわかります。

そのため TypeScript による型チェックをすり抜けてしまった場合3、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() にはできないことについて

@vue/composition-api での shallowReadonly() の実装4を確認すると、引数で受け取ったオブジェクトの(第1階層の)キーでループを回し5、別途定義したオブジェクトに対して同じキーを持つプロパティを定義する6実装となっていることがわかります。


そのため 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



  1. 2020/12/27 時点でリリースされていない Vue 3 の IE11 互換ビルドを利用すると Vue 3 の readonly(), shallowReadonly() の挙動が本記事で想定しているものと異なるものとなる可能性があります 

  2. @vue/composition-api v1.0.0-beta.22 での readonly() の実装: https://github.com/vuejs/composition-api/blob/v1.0.0-beta.22/src/reactivity/readonly.ts#L42-L46 

  3. 特に Vue では <template> 上の v-model など型チェックが効きにくい場所があったりします 

  4. @vue/composition-api v1.0.0-beta.22 での shallowReadonly() の実装: https://github.com/vuejs/composition-api/blob/v1.0.0-beta.22/src/reactivity/readonly.ts#L48-L97 

  5. for (const key of Object.keys(obj)) { ... } の部分 

  6. Object.defineProperty(readonlyObj, key, { ... }) の部分 

4
3
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
4
3