以下の内容はhttps://uga-box.hatenablog.com/entry/2025/06/24/000000より取得しました。


【MobX】@observable.refの使い方

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が特に有効な場面:

  • 大きなオブジェクトや配列を扱う際のパフォーマンス最適化
  • 外部ライブラリのオブジェクト(DOM要素、地図インスタンスなど)の管理
  • Immutableなデータ構造との組み合わせ
  • APIレスポンスの一括更新



以上の内容はhttps://uga-box.hatenablog.com/entry/2025/06/24/000000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14