AIが生成したコードで@observable.refが使われているのを見た
@observable.refを知らなかったので調べた
@observable.refとは
@observable.refは、MobXが提供する観測可能な値の一種で、参照の変更のみを監視する仕組み
通常の@observableとは異なり、オブジェクトや配列の内部プロパティの変更は監視せず、オブジェクト自体が別のオブジェクトに置き換えられた場合のみリアクションが発火
通常の@observableとの違い
@observable(深い観測)
import { observable, autorun } from 'mobx'; class UserStore { @observable user = { name: 'John', age: 30, address: { city: 'Tokyo', country: 'Japan' } }; } const store = new UserStore(); // 深い変更も監視される autorun(() => { console.log('User changed:', store.user.name); }); // これでもリアクションが発火する store.user.address.city = 'Osaka';
@observable.ref(参照のみ観測)
import { observable, autorun } from 'mobx'; class UserStore { @observable.ref user = { name: 'John', age: 30, address: { city: 'Tokyo', country: 'Japan' } }; } const store = new UserStore(); autorun(() => { console.log('User reference changed:', store.user.name); }); // これではリアクションが発火しない store.user.address.city = 'Osaka'; // これでリアクションが発火する store.user = { name: 'Jane', age: 25, address: { city: 'Kyoto', country: 'Japan' } };
具体的な使用例
1. 大きなデータセットの管理
import { observable, action, computed } from 'mobx'; class DataStore { // 大きなデータセットは参照のみ監視 @observable.ref dataset = []; @observable selectedId = null; @action setDataset(newDataset) { // データセット全体を置き換える場合のみリアクションが発火 this.dataset = newDataset; } @computed get selectedItem() { return this.dataset.find(item => item.id === this.selectedId); } @action selectItem(id) { this.selectedId = id; } }
2. 外部ライブラリのオブジェクト管理
import { observable, action } from 'mobx'; class MapStore { // Google Maps インスタンスなどの外部オブジェクト @observable.ref mapInstance = null; @observable.ref markers = []; @action setMapInstance(map) { this.mapInstance = map; } @action updateMarkers(newMarkers) { // マーカー配列全体を置き換え this.markers = newMarkers; } @action addMarker(marker) { // 新しい配列を作成して置き換え this.markers = [...this.markers, marker]; } }
3. Immutableなデータ構造との組み合わせ
import { observable, action } from 'mobx'; import { Map as ImmutableMap } from 'immutable'; class ImmutableStore { @observable.ref data = ImmutableMap({ name: 'John', age: 30 }); @action updateData(key, value) { // Immutableオブジェクトの更新は常に新しい参照を返す this.data = this.data.set(key, value); } @action mergeData(updates) { this.data = this.data.merge(updates); } }
パフォーマンス比較
メモリ使用量とCPU負荷
// パフォーマンステスト例 class PerformanceTest { @observable deepObservable = generateLargeObject(); @observable.ref refObservable = generateLargeObject(); testDeepObservable() { const start = performance.now(); // 深い変更を1000回実行 for (let i = 0; i < 1000; i++) { this.deepObservable.nested.array[i % 100] = Math.random(); } console.log('Deep observable:', performance.now() - start, 'ms'); } testRefObservable() { const start = performance.now(); // 参照の変更を1000回実行 for (let i = 0; i < 1000; i++) { this.refObservable = { ...this.refObservable, counter: i }; } console.log('Ref observable:', performance.now() - start, 'ms'); } }
使用する際の注意点
1. 内部プロパティの変更は検知されない
class WrongUsage { @observable.ref items = []; addItem(item) { // ❌ これはリアクションを発火させない this.items.push(item); } addItemCorrectly(item) { // ✅ 新しい配列を作成する this.items = [...this.items, item]; } }
2. 意図しない副作用
class SideEffectExample { @observable.ref config = { theme: 'light', language: 'ja' }; // ❌ 危険:オブジェクトを直接変更 changeTheme(theme) { this.config.theme = theme; // リアクションが発火しない } // ✅ 正しい:新しいオブジェクトを作成 changeThemeCorrectly(theme) { this.config = { ...this.config, theme }; } }
実用的な活用パターン
1. APIレスポンスの管理
class ApiStore { @observable.ref userData = null; @observable loading = false; @observable error = null; @action async fetchUser(userId) { this.loading = true; this.error = null; try { const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); // APIレスポンス全体を置き換え this.userData = userData; } catch (error) { this.error = error.message; } finally { this.loading = false; } } }
2. フォーム状態の管理
class FormStore { @observable.ref formData = { name: '', email: '', preferences: { newsletter: false, notifications: true } }; @action updateField(path, value) { // 深いマージを行い、新しいオブジェクトを作成 this.formData = this.deepMerge(this.formData, path, value); } @action resetForm() { this.formData = { name: '', email: '', preferences: { newsletter: false, notifications: true } }; } deepMerge(obj, path, value) { const keys = path.split('.'); const result = { ...obj }; let current = result; for (let i = 0; i < keys.length - 1; i++) { current[keys[i]] = { ...current[keys[i]] }; current = current[keys[i]]; } current[keys[keys.length - 1]] = value; return result; } }
まとめ
@observable.refが特に有効な場面: