useLocalObservable #

useLocalObservable 是 mobx-react-lite 提供的一个 Hook,用于在 React 组件内创建本地可观察状态。它结合了 MobX 的响应式特性和 React Hooks 的便利性。

基本用法 #

简单示例 #

javascript
import { useLocalObservable } from 'mobx-react-lite';
import { observer } from 'mobx-react-lite';

const Counter = observer(() => {
  // 创建本地可观察状态
  const store = useLocalObservable(() => ({
    count: 0,
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
    get double() {
      return this.count * 2;
    }
  }));

  return (
    <div>
      <p>Count: {store.count}</p>
      <p>Double: {store.double}</p>
      <button onClick={store.increment}>+1</button>
      <button onClick={store.decrement}>-1</button>
    </div>
  );
});

工作原理 #

text
┌─────────────────────────────────────────────────────────────┐
│              useLocalObservable 工作原理                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. 组件首次渲染时,创建可观察状态对象                       │
│                                                             │
│   2. 后续渲染时,返回同一个状态对象(类似 useMemo)           │
│                                                             │
│   3. 状态变化时,触发组件重新渲染                            │
│                                                             │
│   ┌──────────────────────────────────────────────┐         │
│   │  useLocalObservable(() => ({                 │         │
│   │    count: 0,           // 可观察状态          │         │
│   │    increment() {...},  // action              │         │
│   │    get double() {...}  // computed            │         │
│   │  }))                                          │         │
│   └──────────────────────────────────────────────┘         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

与useState的区别 #

javascript
// useState:每次更新都是独立的
const [count, setCount] = useState(0);
const [name, setName] = useState('');

// useLocalObservable:统一管理
const store = useLocalObservable(() => ({
  count: 0,
  name: '',
  increment() { this.count++; },
  setName(name) { this.name = name; }
}));

对比表 #

特性 useLocalObservable useState
状态管理 统一对象 分散变量
计算属性 支持(getter) 需要useMemo
方法 内置 需要useCallback
响应式 自动 手动触发
批量更新 自动 需要手动处理

使用场景 #

1. 复杂表单状态 #

javascript
import { useLocalObservable, observer } from 'mobx-react-lite';

const ComplexForm = observer(() => {
  const form = useLocalObservable(() => ({
    values: {
      username: '',
      email: '',
      password: ''
    },
    errors: {},
    touched: {},

    setField(field, value) {
      this.values[field] = value;
      this.validateField(field);
    },

    setTouched(field) {
      this.touched[field] = true;
    },

    validateField(field) {
      const value = this.values[field];
      switch (field) {
        case 'username':
          this.errors.username = value.length < 3 ? 'Too short' : '';
          break;
        case 'email':
          this.errors.email = !/^[^\s@]+@[^\s@]+$/.test(value) ? 'Invalid' : '';
          break;
        case 'password':
          this.errors.password = value.length < 8 ? 'Too short' : '';
          break;
      }
    },

    get isValid() {
      return Object.values(this.errors).every(e => !e);
    },

    get isDirty() {
      return Object.keys(this.touched).length > 0;
    },

    reset() {
      this.values = { username: '', email: '', password: '' };
      this.errors = {};
      this.touched = {};
    }
  }));

  const handleSubmit = (e) => {
    e.preventDefault();
    if (form.isValid) {
      console.log('Submit:', form.values);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          value={form.values.username}
          onChange={(e) => form.setField('username', e.target.value)}
          onBlur={() => form.setTouched('username')}
        />
        {form.errors.username && <span>{form.errors.username}</span>}
      </div>

      <div>
        <input
          value={form.values.email}
          onChange={(e) => form.setField('email', e.target.value)}
          onBlur={() => form.setTouched('email')}
        />
        {form.errors.email && <span>{form.errors.email}</span>}
      </div>

      <div>
        <input
          type="password"
          value={form.values.password}
          onChange={(e) => form.setField('password', e.target.value)}
          onBlur={() => form.setTouched('password')}
        />
        {form.errors.password && <span>{form.errors.password}</span>}
      </div>

      <button type="submit" disabled={!form.isValid}>
        Submit
      </button>
      <button type="button" onClick={form.reset}>
        Reset
      </button>
    </form>
  );
});

2. 交互状态 #

javascript
import { useLocalObservable, observer } from 'mobx-react-lite';

const Dropdown = observer(({ options, onSelect }) => {
  const state = useLocalObservable(() => ({
    isOpen: false,
    selectedIndex: -1,
    searchTerm: '',

    toggle() {
      this.isOpen = !this.isOpen;
    },

    close() {
      this.isOpen = false;
    },

    select(index) {
      this.selectedIndex = index;
      this.close();
      onSelect(options[index]);
    },

    setSearchTerm(term) {
      this.searchTerm = term;
    },

    get filteredOptions() {
      if (!this.searchTerm) return options;
      return options.filter(opt =>
        opt.label.toLowerCase().includes(this.searchTerm.toLowerCase())
      );
    },

    get selectedOption() {
      return this.selectedIndex >= 0 ? options[this.selectedIndex] : null;
    }
  }));

  return (
    <div className="dropdown">
      <div className="dropdown-trigger" onClick={state.toggle}>
        {state.selectedOption?.label || 'Select...'}
      </div>

      {state.isOpen && (
        <div className="dropdown-menu">
          <input
            value={state.searchTerm}
            onChange={(e) => state.setSearchTerm(e.target.value)}
            placeholder="Search..."
          />
          <ul>
            {state.filteredOptions.map((option, index) => (
              <li key={option.value} onClick={() => state.select(index)}>
                {option.label}
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
});

3. 动画状态 #

javascript
import { useLocalObservable, observer } from 'mobx-react-lite';

const AnimatedModal = observer(({ children, isOpen, onClose }) => {
  const state = useLocalObservable(() => ({
    visible: false,
    animating: false,

    show() {
      this.visible = true;
      this.animating = true;
    },

    hide() {
      this.animating = false;
      setTimeout(() => {
        this.visible = false;
        onClose();
      }, 300);
    },

    get className() {
      return `modal ${this.visible ? 'visible' : ''} ${this.animating ? 'animating' : ''}`;
    }
  }));

  React.useEffect(() => {
    if (isOpen) {
      state.show();
    } else if (state.visible) {
      state.hide();
    }
  }, [isOpen]);

  if (!state.visible) return null;

  return (
    <div className={state.className}>
      <div className="modal-backdrop" onClick={state.hide} />
      <div className="modal-content">
        {children}
      </div>
    </div>
  );
});

结合全局Store #

javascript
import { useLocalObservable, observer } from 'mobx-react-lite';
import { todoStore } from '../stores/TodoStore';

const TodoInput = observer(() => {
  const local = useLocalObservable(() => ({
    text: '',
    setText(text) {
      this.text = text;
    },
    get canAdd() {
      return this.text.trim().length > 0;
    },
    add() {
      if (this.canAdd) {
        todoStore.addTodo(this.text);
        this.text = '';
      }
    }
  }));

  return (
    <div>
      <input
        value={local.text}
        onChange={(e) => local.setText(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && local.add()}
      />
      <button onClick={local.add} disabled={!local.canAdd}>
        Add
      </button>
    </div>
  );
});

完整示例 #

分页列表 #

javascript
import { useLocalObservable, observer } from 'mobx-react-lite';

const PaginatedList = observer(({ fetchData }) => {
  const state = useLocalObservable(() => ({
    items: [],
    page: 1,
    pageSize: 10,
    total: 0,
    loading: false,
    error: null,

    async load() {
      this.loading = true;
      this.error = null;
      try {
        const result = await fetchData(this.page, this.pageSize);
        this.items = result.items;
        this.total = result.total;
      } catch (error) {
        this.error = error.message;
      } finally {
        this.loading = false;
      }
    },

    setPage(page) {
      this.page = page;
      this.load();
    },

    setPageSize(size) {
      this.pageSize = size;
      this.page = 1;
      this.load();
    },

    get totalPages() {
      return Math.ceil(this.total / this.pageSize);
    },

    get hasNext() {
      return this.page < this.totalPages;
    },

    get hasPrev() {
      return this.page > 1;
    }
  }));

  React.useEffect(() => {
    state.load();
  }, []);

  if (state.loading) return <div>Loading...</div>;
  if (state.error) return <div>Error: {state.error}</div>;

  return (
    <div>
      <ul>
        {state.items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>

      <div className="pagination">
        <button
          onClick={() => state.setPage(state.page - 1)}
          disabled={!state.hasPrev}
        >
          Previous
        </button>

        <span>Page {state.page} of {state.totalPages}</span>

        <button
          onClick={() => state.setPage(state.page + 1)}
          disabled={!state.hasNext}
        >
          Next
        </button>
      </div>
    </div>
  );
});

注意事项 #

1. 必须配合observer使用 #

javascript
// 错误:没有 observer,状态变化不会触发更新
const Component = () => {
  const store = useLocalObservable(() => ({ count: 0 }));
  return <div>{store.count}</div>;
};

// 正确:配合 observer 使用
const Component = observer(() => {
  const store = useLocalObservable(() => ({ count: 0 }));
  return <div>{store.count}</div>;
});

2. 不要在回调中访问过期的状态 #

javascript
// 注意闭包问题
const Component = observer(() => {
  const store = useLocalObservable(() => ({ count: 0 }));

  React.useEffect(() => {
    const timer = setInterval(() => {
      // 正确:直接访问 store
      store.count++;
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  return <div>{store.count}</div>;
});

3. 初始化函数只执行一次 #

javascript
const Component = observer(() => {
  // 初始化函数只在首次渲染时执行
  const store = useLocalObservable(() => ({
    count: 0,
    // props 变化不会重新执行
    initialName: props.name
  }));

  // 如果需要响应 props 变化,使用 useEffect
  React.useEffect(() => {
    store.name = props.name;
  }, [props.name]);
});

总结 #

useLocalObservable 的核心要点:

  • 组件内状态:在组件内创建可观察状态
  • 统一管理:状态、计算值、方法统一管理
  • 自动响应:配合 observer 实现自动更新
  • 持久化:组件生命周期内状态持久化

使用建议:

  • 复杂本地状态:使用 useLocalObservable
  • 简单状态:使用 useState
  • 需要计算属性:使用 useLocalObservable
  • 需要方法:使用 useLocalObservable

继续学习 异步Action,了解 MobX 中的异步操作处理。

最后更新:2026-03-28