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