Vue warn: Computed property ‘X’ was assigned to but it has no setter の解決方法

Vue warn: Computed property ‘X’ was assigned to but it has no setter の解決方法

「Vue warn: Computed property ‘X’ was assigned to but it has no setter」という警告は、Vue.jsの計算プロパティに直接代入しようとした際に発生します。この警告の原因と解決方法を解説します。

1. 警告が発生する条件

  • computedプロパティがgetterのみ定義されている場合
  • getter-onlyの計算プロパティに値を代入しようとした場合
  • 誤って双方向データバインディングで使用した場合

2. 警告の具体例

以下のコードで、警告が発生します。

export default {
  data() {
    return {
      message: 'Hello World'
    };
  },
  computed: {
    upperCaseMessage() {
      return this.message.toUpperCase();
    }
  },
  methods: {
    updateMessage() {
      this.upperCaseMessage = 'NEW MESSAGE'; // 警告が発生する
    }
  }
};

3. computedプロパティの仕組み

computedプロパティはデフォルトでgetterのみを提供します。そのため、代入操作は無効です。

4. setterを追加する方法

getterとsetterの両方を定義することで、代入操作を可能にします。

export default {
  data() {
    return {
      message: 'Hello World'
    };
  },
  computed: {
    upperCaseMessage: {
      get() {
        return this.message.toUpperCase();
      },
      set(newValue) {
        this.message = newValue.toLowerCase();
      }
    }
  },
  methods: {
    updateMessage() {
      this.upperCaseMessage = 'NEW MESSAGE'; // setterが呼び出される
    }
  }
};

5. computedプロパティで双方向バインディングを使用するケース

フォーム要素とcomputedプロパティを双方向にバインディングする場合、setterが必要です。

export default {
  data() {
    return {
      firstName: '',
      lastName: ''
    };
  },
  computed: {
    fullName: {
      get() {
        return `${this.firstName} ${this.lastName}`;
      },
      set(value) {
        const names = value.split(' ');
        this.firstName = names[0];
        this.lastName = names[1] || '';
      }
    }
  }
};

6. setterがない場合の解決策

computedプロパティが必要な場合でもsetterを追加したくない場合は、代入操作を避けます。

export default {
  data() {
    return {
      message: 'Hello World'
    };
  },
  computed: {
    upperCaseMessage() {
      return this.message.toUpperCase();
    }
  },
  methods: {
    updateMessage() {
      this.message = 'new message'; // messageに直接代入
    }
  }
};

7. 双方向データバインディングを避ける

computedプロパティを一方向データフローで使用し、フォーム要素に直接バインディングするのが適切な場合もあります。

<input v-model="message" />
<p>{{ message.toUpperCase() }}</p>

8. コンポーネント間での注意点

子コンポーネントにpropsとして渡される値にsetterは追加できません。子コンポーネントで変更が必要な場合、イベントを発火して親コンポーネントで更新します。

// 子コンポーネント
export default {
  props: ['value'],
  methods: {
    updateValue(newValue) {
      this.$emit('input', newValue);
    }
  }
};

9. Vue 3でのreactiveの利用

Vue 3では、reactiveを活用して双方向データバインディングを実現する方法があります。

import { reactive, computed } from 'vue';

export default {
  setup() {
    const state = reactive({
      message: 'Hello World'
    });

    const upperCaseMessage = computed({
      get: () => state.message.toUpperCase(),
      set: (value) => {
        state.message = value.toLowerCase();
      }
    });

    return { state, upperCaseMessage };
  }
};

10. readonlyでの利用

計算プロパティが読み取り専用の場合、readonlyを使用して明示的に宣言することもできます。

import { readonly, computed } from 'vue';

export default {
  setup() {
    const message = 'Hello World';
    const upperCaseMessage = readonly(computed(() => message.toUpperCase()));

    return { upperCaseMessage };
  }
};

11. 結論

「Vue warn: Computed property ‘X’ was assigned to but it has no setter」は、computedプロパティに値を代入しようとした場合に発生します。getterとsetterの両方を適切に設定するか、代入を避けて設計を見直すことで解決できます。