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