MobX与TypeScript #

MobX 对 TypeScript 有很好的支持,本文档介绍如何在 TypeScript 项目中使用 MobX。

基本类型定义 #

Store类型 #

typescript
import { makeAutoObservable, observable, action, computed } from 'mobx';

// 方式1:使用 makeAutoObservable
class CounterStore {
  count: number = 0;
  name: string = '';

  constructor() {
    makeAutoObservable(this);
  }

  increment(): void {
    this.count++;
  }

  decrement(): void {
    this.count--;
  }

  get double(): number {
    return this.count * 2;
  }
}

// 方式2:使用 makeObservable
class TodoStore {
  todos: Todo[] = [];
  filter: FilterType = 'all';

  constructor() {
    makeObservable(this, {
      todos: observable,
      filter: observable,
      addTodo: action,
      setFilter: action,
      filteredTodos: computed
    });
  }

  addTodo(title: string): void {
    this.todos.push({
      id: Date.now(),
      title,
      completed: false
    });
  }

  setFilter(filter: FilterType): void {
    this.filter = filter;
  }

  get filteredTodos(): Todo[] {
    switch (this.filter) {
      case 'completed':
        return this.todos.filter(t => t.completed);
      case 'active':
        return this.todos.filter(t => !t.completed);
      default:
        return this.todos;
    }
  }
}

// 类型定义
interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

type FilterType = 'all' | 'active' | 'completed';

Observable类型 #

typescript
import { observable, ObservableMap, ObservableSet } from 'mobx';

// 基本类型
const count = observable.box<number>(0);
const name = observable.box<string>('');

// 对象
interface User {
  name: string;
  age: number;
}
const user = observable.object<User>({ name: 'John', age: 30 });

// 数组
const items = observable.array<number>([1, 2, 3]);

// Map
const userMap = observable.map<string, User>();

// Set
const tags = observable.set<string>();

泛型Store #

基础泛型Store #

typescript
import { makeAutoObservable, runInAction } from 'mobx';

interface AsyncState<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
}

class AsyncStore<T> implements AsyncState<T> {
  data: T | null = null;
  loading: boolean = false;
  error: string | null = null;

  constructor() {
    makeAutoObservable(this);
  }

  async fetch(url: string): Promise<T> {
    this.loading = true;
    this.error = null;

    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data: T = await response.json();

      runInAction(() => {
        this.data = data;
        this.loading = false;
      });

      return data;
    } catch (error) {
      runInAction(() => {
        this.error = error instanceof Error ? error.message : 'Unknown error';
        this.loading = false;
      });
      throw error;
    }
  }

  setData(data: T): void {
    this.data = data;
  }

  clear(): void {
    this.data = null;
    this.error = null;
  }
}

// 使用
interface User {
  id: number;
  name: string;
  email: string;
}

const userStore = new AsyncStore<User>();

分页Store #

typescript
import { makeAutoObservable, runInAction } from 'mobx';

interface PageResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
}

class PaginationStore<T> {
  items: T[] = [];
  page: number = 1;
  pageSize: number = 10;
  total: number = 0;
  loading: boolean = false;
  hasMore: boolean = true;

  constructor(private fetchFn: (page: number, pageSize: number) => Promise<PageResponse<T>>) {
    makeAutoObservable(this);
  }

  async loadMore(): Promise<void> {
    if (this.loading || !this.hasMore) return;

    this.loading = true;

    try {
      const response = await this.fetchFn(this.page, this.pageSize);

      runInAction(() => {
        this.items = [...this.items, ...response.items];
        this.total = response.total;
        this.page++;
        this.hasMore = this.items.length < this.total;
        this.loading = false;
      });
    } catch (error) {
      runInAction(() => {
        this.loading = false;
      });
      throw error;
    }
  }

  async refresh(): Promise<void> {
    this.loading = true;

    try {
      const response = await this.fetchFn(1, this.pageSize);

      runInAction(() => {
        this.items = response.items;
        this.total = response.total;
        this.page = 2;
        this.hasMore = this.items.length < this.total;
        this.loading = false;
      });
    } catch (error) {
      runInAction(() => {
        this.loading = false;
      });
      throw error;
    }
  }

  reset(): void {
    this.items = [];
    this.page = 1;
    this.total = 0;
    this.hasMore = true;
  }
}

React集成 #

组件类型 #

typescript
import React from 'react';
import { observer } from 'mobx-react-lite';

interface TodoItemProps {
  todo: Todo;
  onToggle: (id: number) => void;
  onDelete: (id: number) => void;
}

const TodoItem: React.FC<TodoItemProps> = observer(({ todo, onToggle, onDelete }) => (
  <li>
    <span
      style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
      onClick={() => onToggle(todo.id)}
    >
      {todo.title}
    </span>
    <button onClick={() => onDelete(todo.id)}>Delete</button>
  </li>
));

interface TodoListProps {
  store: TodoStore;
}

const TodoList: React.FC<TodoListProps> = observer(({ store }) => (
  <ul>
    {store.filteredTodos.map(todo => (
      <TodoItem
        key={todo.id}
        todo={todo}
        onToggle={store.toggleTodo}
        onDelete={store.removeTodo}
      />
    ))}
  </ul>
));

useLocalObservable类型 #

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

interface FormState {
  values: {
    username: string;
    email: string;
  };
  errors: Record<string, string>;
  setValue: (field: string, value: string) => void;
  validate: () => boolean;
  get isValid: boolean;
}

const Form: React.FC = observer(() => {
  const form = useLocalObservable<FormState>(() => ({
    values: {
      username: '',
      email: ''
    },
    errors: {},

    setValue(field: string, value: string) {
      this.values[field as keyof typeof this.values] = value;
    },

    validate() {
      this.errors = {};
      if (!this.values.username) {
        this.errors.username = 'Required';
      }
      if (!this.values.email) {
        this.errors.email = 'Required';
      }
      return Object.keys(this.errors).length === 0;
    },

    get isValid() {
      return Object.keys(this.errors).length === 0;
    }
  }));

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

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={form.values.username}
        onChange={(e) => form.setValue('username', e.target.value)}
      />
      <input
        value={form.values.email}
        onChange={(e) => form.setValue('email', e.target.value)}
      />
      <button type="submit">Submit</button>
    </form>
  );
}));

工具类型 #

IObservable类型 #

typescript
import {
  IObservable,
  IObservableArray,
  IObservableMap,
  IObservableSet,
  IComputedValue,
  IAction
} from 'mobx';

// 使用类型
const observableValue: IObservable<number> = observable.box(0);
const observableArray: IObservableArray<string> = observable.array(['a', 'b']);
const observableMap: IObservableMap<string, number> = observable.map();
const observableSet: IObservableSet<string> = observable.set();

类型守卫 #

typescript
import {
  isObservable,
  isObservableObject,
  isObservableArray,
  isObservableMap,
  isObservableSet,
  isAction,
  isComputed
} from 'mobx';

function processData(data: unknown) {
  if (isObservable(data)) {
    console.log('Observable data');
  }

  if (isObservableArray(data)) {
    console.log('Observable array');
  }

  if (isObservableObject(data)) {
    console.log('Observable object');
  }
}

完整示例 #

用户管理Store #

typescript
import { makeAutoObservable, runInAction, flow } from 'mobx';

interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
  createdAt: Date;
}

interface CreateUserData {
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

class UserStore {
  users: User[] = [];
  currentUser: User | null = null;
  loading: boolean = false;
  error: string | null = null;

  constructor() {
    makeAutoObservable(this);
  }

  // 使用 flow
  fetchUsers = flow(function* (this: UserStore) {
    this.loading = true;
    this.error = null;

    try {
      const response: Response = yield fetch('/api/users');
      const users: User[] = yield response.json();

      this.users = users;
    } catch (error) {
      this.error = error instanceof Error ? error.message : 'Unknown error';
    } finally {
      this.loading = false;
    }
  });

  // 使用 async/await
  async createUser(data: CreateUserData): Promise<User> {
    this.loading = true;
    this.error = null;

    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const user: User = await response.json();

      runInAction(() => {
        this.users.push(user);
        this.loading = false;
      });

      return user;
    } catch (error) {
      runInAction(() => {
        this.error = error instanceof Error ? error.message : 'Unknown error';
        this.loading = false;
      });
      throw error;
    }
  }

  // 计算属性
  get adminUsers(): User[] {
    return this.users.filter(u => u.role === 'admin');
  }

  get userCount(): number {
    return this.users.length;
  }

  get isLoggedIn(): boolean {
    return this.currentUser !== null;
  }

  // 方法
  setUser(user: User | null): void {
    this.currentUser = user;
  }

  clearError(): void {
    this.error = null;
  }
}

export default new UserStore();

最佳实践 #

1. 明确类型 #

typescript
// 好:明确类型
class Store {
  count: number = 0;
  name: string = '';
  items: Item[] = [];
  userMap: Map<string, User> = new Map();
}

// 不好:使用 any
class Store {
  count: any = 0;
  name: any = '';
}

2. 使用接口 #

typescript
// 好:使用接口
interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

class TodoStore {
  todos: Todo[] = [];
}

// 不好:内联类型
class TodoStore {
  todos: { id: number; title: string; completed: boolean }[] = [];
}

3. 泛型复用 #

typescript
// 好:使用泛型
class Store<T> {
  items: T[] = [];
}

// 使用
const userStore = new Store<User>();
const todoStore = new Store<Todo>();

总结 #

TypeScript 集成要点:

要点 说明
明确类型 为所有属性指定类型
使用接口 定义数据结构
泛型复用 创建通用 Store
类型守卫 运行时类型检查

继续学习 常见问题,了解 MobX 开发中的常见问题和解决方案。

最后更新:2026-03-28