Mobx 6
概述
Mobx 是一个简单的可扩展的状态管理库,无样板代码,风格简约。
目前最新版本是 6,版本 4 和版本 5 已不再支持。
Mobx 6 中不推荐使用装饰器语法,因为它不是 ES 标准,并且标准化过程要花费很长时间,但是通过配置仍然可以启用装饰器语法。
Mobx 可以运行在任何支持 ES5 的环境中,包含浏览器和 Node。
Mobx 通过和 React 配合使用,但是在 Angular 和 Vue 中也可以使用。
核心概念
observable:被 Mobx 跟踪的状态
action:允许修改状态的方法,在严格模式下只有 action 方法被允许修改状态
computed:根据现有状态衍生出来的状态
flow:执行副作用,它是 generator 函数。可以更改状态值。
工作流程

- mobx:Mobx 核心库
- mobx-react-lite:仅支持函数组件
- mobx-react:既支持函数组件也支持类组件
yarn add mobx mobx-react-lite
yarn add mobx mobx-react-lite
计数器案例
创建存储状态
创建用于存储状态的 store,创建用于修改状态的方法
export default class CounterStore {
constructor () {
this.count = 0;
}
increment () {
this.count += 1;
}
decrement () {
this.count -= 1;
}
}
export default class CounterStore {
constructor () {
this.count = 0;
}
increment () {
this.count += 1;
}
decrement () {
this.count -= 1;
}
}
追踪状态变化
让 Mobx 可以追踪状态变化
- 通过 observable 标识状态,使状态可观察
- 通过 action 标识修改状态的方法,状态只有通过 action 方法修改后才会通知视图更新
import { action, makeObservable, observable } from "mobx";
export default class CounterStore {
constructor () {
this.count = 0;
makeObservable(this, {
count: observable,
increment: action,
decrement: action
});
}
increment () {
this.count += 1;
}
decrement () {
this.count -= 1;
}
}
import { action, makeObservable, observable } from "mobx";
export default class CounterStore {
constructor () {
this.count = 0;
makeObservable(this, {
count: observable,
increment: action,
decrement: action
});
}
increment () {
this.count += 1;
}
decrement () {
this.count -= 1;
}
}
传递状态
创建 Store 类的实例对象并将实例对象传递给组件
import React from "react";
import Counter from './components/Counter';
import CounterStore from './store/CounterStore';
const counterStore = new CounterStore();
function App () {
return (
<Counter store={ counterStore } />
);
}
export default App;
import React from "react";
import Counter from './components/Counter';
import CounterStore from './store/CounterStore';
const counterStore = new CounterStore();
function App () {
return (
<Counter store={ counterStore } />
);
}
export default App;
获取、操作状态
组件中通过 Store 实例对象获取状态以及操作状态的方法
function Counter ({ store }) {
return (
<div>
<button onClick={ () => store.increment() }>+</button>
<span>{ store.count }</span>
<button onClick={ () => store.decrement() }>-</button>
</div>
)
}
export default Counter;
function Counter ({ store }) {
return (
<div>
<button onClick={ () => store.increment() }>+</button>
<span>{ store.count }</span>
<button onClick={ () => store.decrement() }>-</button>
</div>
)
}
export default Counter;
更新视图
当组件中使用到的 Mobx 管理的状态发生变化后,使视图更新。通过 observer 方法包裹组件
import { observer } from "mobx-react-lite";
function Counter ({ store }) {
return (
<div>
<button onClick={ () => store.increment() }>+</button>
<span>{ store.count }</span>
<button onClick={ () => store.decrement() }>-</button>
</div>
)
}
export default observer(Counter);
import { observer } from "mobx-react-lite";
function Counter ({ store }) {
return (
<div>
<button onClick={ () => store.increment() }>+</button>
<span>{ store.count }</span>
<button onClick={ () => store.decrement() }>-</button>
</div>
)
}
export default observer(Counter);
修改 action this 指向
修改 action this 指向
import { action, makeObservable, observable } from "mobx";
export default class CounterStore {
constructor () {
this.count = 0;
makeObservable(this, {
count: observable,
increment: action.bound,
decrement: action.bound
});
}
increment () {
this.count += 1;
}
decrement () {
this.count -= 1;
}
}
import { action, makeObservable, observable } from "mobx";
export default class CounterStore {
constructor () {
this.count = 0;
makeObservable(this, {
count: observable,
increment: action.bound,
decrement: action.bound
});
}
increment () {
this.count += 1;
}
decrement () {
this.count -= 1;
}
}
import { observer } from "mobx-react-lite";
function Counter ({ store }) {
const { count, increment, decrement } = store;
return (
<div>
<button onClick={ increment }>+</button>
<span>{ count }</span>
<button onClick={ decrement }>-</button>
</div>
)
}
export default observer(Counter);
import { observer } from "mobx-react-lite";
function Counter ({ store }) {
const { count, increment, decrement } = store;
return (
<div>
<button onClick={ increment }>+</button>
<span>{ count }</span>
<button onClick={ decrement }>-</button>
</div>
)
}
export default observer(Counter);
使用总结
状态变化更新视图的必要条件
- 状态必须被标记为 observable
- 更改状态的方法必须被标记为 action
- 组件必须通过 observer 方法包裹
创建 RootStore
在应用中可以存在多个 Store,多个 Store 最终要通过 RootState 管理,在每个组件都需要获取到 RootState(状态共享)。
// store/idnex.js
import CounterStore from "./CounterStore";
import { createContext, useContext } from "react";
class RootStore {
constructor () {
this.counterStore = new CounterStore();
}
}
const rootStore = new RootStore();
const RootStoreContext = createContext();
export const RootStoreProvider = ({ children }) => {
return (
<RootStoreContext.Provider value={ rootStore }>{ children }</RootStoreContext.Provider>
)
};
export const useRootStore = () => {
return useContext(RootStoreContext);
}
// store/idnex.js
import CounterStore from "./CounterStore";
import { createContext, useContext } from "react";
class RootStore {
constructor () {
this.counterStore = new CounterStore();
}
}
const rootStore = new RootStore();
const RootStoreContext = createContext();
export const RootStoreProvider = ({ children }) => {
return (
<RootStoreContext.Provider value={ rootStore }>{ children }</RootStoreContext.Provider>
)
};
export const useRootStore = () => {
return useContext(RootStoreContext);
}
// App.js
import React from "react";
import Counter from './components/Counter';
import { RootStoreProvider } from './store/index';
function App () {
return (
<RootStoreProvider>
<Counter />
</RootStoreProvider>
);
}
export default App;
// App.js
import React from "react";
import Counter from './components/Counter';
import { RootStoreProvider } from './store/index';
function App () {
return (
<RootStoreProvider>
<Counter />
</RootStoreProvider>
);
}
export default App;
// components/Counter.js
import { observer } from "mobx-react-lite";
import { useRootStore } from "../store";
function Counter () {
const { counterStore } = useRootStore();
const { count, increment, decrement } = counterStore;
return (
<div>
<button onClick={ increment }>+</button>
<span>{ count }</span>
<button onClick={ decrement }>-</button>
</div>
)
}
export default observer(Counter);
// components/Counter.js
import { observer } from "mobx-react-lite";
import { useRootStore } from "../store";
function Counter () {
const { counterStore } = useRootStore();
const { count, increment, decrement } = counterStore;
return (
<div>
<button onClick={ increment }>+</button>
<span>{ count }</span>
<button onClick={ decrement }>-</button>
</div>
)
}
export default observer(Counter);
todo 案例
创建 todo 状态
store/Todo.js
import { makeObservable, observable } from "mobx";
export default class Todo {
constructor (todo) {
this.id = todo.id;
this.title = todo.title;
this.isCompleted = todo.isCompleted || false;
this.isEditing = false;
makeObservable(this, {
title: observable,
isCompleted: observable,
isEditing: observable
});
}
}
import { makeObservable, observable } from "mobx";
export default class Todo {
constructor (todo) {
this.id = todo.id;
this.title = todo.title;
this.isCompleted = todo.isCompleted || false;
this.isEditing = false;
makeObservable(this, {
title: observable,
isCompleted: observable,
isEditing: observable
});
}
}
src/TodoStore.js
import { makeObservable, observable } from "mobx";
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable
})
}
}
import { makeObservable, observable } from "mobx";
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable
})
}
}
store/index.js
import CounterStore from "./CounterStore";
import TodoStore from "./TodoStore";
import { createContext, useContext } from "react";
class RootStore {
constructor () {
this.counterStore = new CounterStore();
this.todoStore = new TodoStore();
}
}
const rootStore = new RootStore();
const RootStoreContext = createContext();
export const RootStoreProvider = ({ children }) => {
return (
<RootStoreContext.Provider value={ rootStore }>{ children }</RootStoreContext.Provider>
)
};
export const useRootStore = () => {
return useContext(RootStoreContext);
}
import CounterStore from "./CounterStore";
import TodoStore from "./TodoStore";
import { createContext, useContext } from "react";
class RootStore {
constructor () {
this.counterStore = new CounterStore();
this.todoStore = new TodoStore();
}
}
const rootStore = new RootStore();
const RootStoreContext = createContext();
export const RootStoreProvider = ({ children }) => {
return (
<RootStoreContext.Provider value={ rootStore }>{ children }</RootStoreContext.Provider>
)
};
export const useRootStore = () => {
return useContext(RootStoreContext);
}
添加任务
components/Todo/Header.js
import { useState } from "react"
import { useRootStore } from "../../store";
function Header () {
const [title, setTitle] = useState('');
const { todoStore } = useRootStore();
const { addTodo } = todoStore;
return (
<header className="header">
<h1>todos</h1>
<input
className="new-todo"
placeholder="what needs to be done?"
autoFocus
value={title}
onChange={e => {
setTitle(e.target.value);
}}
onKeyUp={e => {
if (e.key !== 'Enter') return;
addTodo(title);
setTitle('');
}}
/>
</header>
)
}
export default Header;
import { useState } from "react"
import { useRootStore } from "../../store";
function Header () {
const [title, setTitle] = useState('');
const { todoStore } = useRootStore();
const { addTodo } = todoStore;
return (
<header className="header">
<h1>todos</h1>
<input
className="new-todo"
placeholder="what needs to be done?"
autoFocus
value={title}
onChange={e => {
setTitle(e.target.value);
}}
onKeyUp={e => {
if (e.key !== 'Enter') return;
addTodo(title);
setTitle('');
}}
/>
</header>
)
}
export default Header;
components/Todo/index.js
import Header from "./Header";
const Todo = () => {
return (
<Header />
)
}
export default Todo;
import Header from "./Header";
const Todo = () => {
return (
<Header />
)
}
export default Todo;
store/TodoStore.js
import { action, makeObservable, observable } from "mobx";
import Todo from './Todo';
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable,
addTodo: action.bound
});
}
addTodo (title) {
this.todos.push(new Todo({
title,
id: this.createId()
}));
console.log(this.todos);
}
createId () {
if (!this.todos.length) return 1;
return this.todos.reduce((id, todo) => (id < todo.id ? todo.id : id), 0) + 1;
}
}
import { action, makeObservable, observable } from "mobx";
import Todo from './Todo';
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable,
addTodo: action.bound
});
}
addTodo (title) {
this.todos.push(new Todo({
title,
id: this.createId()
}));
console.log(this.todos);
}
createId () {
if (!this.todos.length) return 1;
return this.todos.reduce((id, todo) => (id < todo.id ? todo.id : id), 0) + 1;
}
}
App.js
import React from "react";
import Counter from './components/Counter';
import Todo from "./components/Todo";
import { RootStoreProvider } from './store/index';
function App () {
return (
<RootStoreProvider>
<Counter />
<Todo />
</RootStoreProvider>
);
}
export default App;
import React from "react";
import Counter from './components/Counter';
import Todo from "./components/Todo";
import { RootStoreProvider } from './store/index';
function App () {
return (
<RootStoreProvider>
<Counter />
<Todo />
</RootStoreProvider>
);
}
export default App;
展示任务列表
components/Todos/Main.js
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
function Todo ({ todo }) {
return (
<li>
<label>{ todo.title }</label>
</li>
)
}
function Main () {
const { todoStore } = useRootStore();
const { todos } = todoStore;
return (
<section>
<ul>
{
todos.map(todo => <Todo todo={ todo } key={ todo.id } />)
}
</ul>
</section>
)
}
export default observer(Main);
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
function Todo ({ todo }) {
return (
<li>
<label>{ todo.title }</label>
</li>
)
}
function Main () {
const { todoStore } = useRootStore();
const { todos } = todoStore;
return (
<section>
<ul>
{
todos.map(todo => <Todo todo={ todo } key={ todo.id } />)
}
</ul>
</section>
)
}
export default observer(Main);
components/Todos/index.js
import Header from "./Header";
import Main from "./Main";
const Todo = () => {
return (
<>
<Header />
<Main />
</>
)
}
export default Todo;
import Header from "./Header";
import Main from "./Main";
const Todo = () => {
return (
<>
<Header />
<Main />
</>
)
}
export default Todo;
加载远端任务
mock 数据
db.json
{
"todos": [
{
"id": 1,
"title": "吃饭",
"isCompleted": false
},
{
"id": 2,
"title": "睡觉",
"isCompleted": false
},
{
"id": 3,
"title": "打豆豆",
"isCompleted": false
}
]
}
{
"todos": [
{
"id": 1,
"title": "吃饭",
"isCompleted": false
},
{
"id": 2,
"title": "睡觉",
"isCompleted": false
},
{
"id": 3,
"title": "打豆豆",
"isCompleted": false
}
]
}
package.json
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"server": "json-server --watch ./db.json --port 3001"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"server": "json-server --watch ./db.json --port 3001"
},
src/store/TodoStore.js
import { action, flow, makeObservable, observable } from "mobx";
import Todo from './Todo';
import axios from 'axios';
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable,
addTodo: action.bound,
loadTodos: flow.bound
});
this.loadTodos();
}
*loadTodos () {
const response = yield axios.get('http://localhost:3001/todos');
response.data.forEach(todo => this.todos.push(new Todo(todo)));
}
addTodo (title) {
this.todos.push(new Todo({
title,
id: this.createId()
}));
console.log(this.todos);
}
createId () {
if (!this.todos.length) return 1;
return this.todos.reduce((id, todo) => (id < todo.id ? todo.id : id), 0) + 1;
}
}
import { action, flow, makeObservable, observable } from "mobx";
import Todo from './Todo';
import axios from 'axios';
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable,
addTodo: action.bound,
loadTodos: flow.bound
});
this.loadTodos();
}
*loadTodos () {
const response = yield axios.get('http://localhost:3001/todos');
response.data.forEach(todo => this.todos.push(new Todo(todo)));
}
addTodo (title) {
this.todos.push(new Todo({
title,
id: this.createId()
}));
console.log(this.todos);
}
createId () {
if (!this.todos.length) return 1;
return this.todos.reduce((id, todo) => (id < todo.id ? todo.id : id), 0) + 1;
}
}
更改任务完成状态
components/Todo/Main.js
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
const TodoCompleted = observer(({ todo }) => {
const { isCompleted, modifyTodoIsCompleted } = todo;
return (
<input
type="checkbox"
checked={ isCompleted }
onChange={ modifyTodoIsCompleted }
/>
)
});
function Todo ({ todo }) {
return (
<li>
<TodoCompleted todo={ todo } />
<label>{ todo.title }</label>
</li>
)
}
function Main () {
const { todoStore } = useRootStore();
const { todos } = todoStore;
return (
<section>
<ul>
{
todos.map(todo => <Todo todo={ todo } key={ todo.id } />)
}
</ul>
</section>
)
}
export default observer(Main);
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
const TodoCompleted = observer(({ todo }) => {
const { isCompleted, modifyTodoIsCompleted } = todo;
return (
<input
type="checkbox"
checked={ isCompleted }
onChange={ modifyTodoIsCompleted }
/>
)
});
function Todo ({ todo }) {
return (
<li>
<TodoCompleted todo={ todo } />
<label>{ todo.title }</label>
</li>
)
}
function Main () {
const { todoStore } = useRootStore();
const { todos } = todoStore;
return (
<section>
<ul>
{
todos.map(todo => <Todo todo={ todo } key={ todo.id } />)
}
</ul>
</section>
)
}
export default observer(Main);
store/Todo.js
import { action, makeObservable, observable } from "mobx";
export default class Todo {
constructor (todo) {
this.id = todo.id;
this.title = todo.title;
this.isCompleted = todo.isCompleted || false;
this.isEditing = false;
makeObservable(this, {
title: observable,
isCompleted: observable,
isEditing: observable,
modifyTodoIsCompleted: action.bound
});
}
modifyTodoIsCompleted () {
this.isCompleted = !this.isCompleted;
}
}
import { action, makeObservable, observable } from "mobx";
export default class Todo {
constructor (todo) {
this.id = todo.id;
this.title = todo.title;
this.isCompleted = todo.isCompleted || false;
this.isEditing = false;
makeObservable(this, {
title: observable,
isCompleted: observable,
isEditing: observable,
modifyTodoIsCompleted: action.bound
});
}
modifyTodoIsCompleted () {
this.isCompleted = !this.isCompleted;
}
}
删除任务
components/Todo/Main.js
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
const TodoCompleted = observer(({ todo }) => {
const { isCompleted, modifyTodoIsCompleted } = todo;
return (
<input
type="checkbox"
checked={ isCompleted }
onChange={ modifyTodoIsCompleted }
/>
)
});
const TodoRemove = observer(({ id }) => {
const { todoStore } = useRootStore();
const { removeTodo } = todoStore;
return (
<button onClick={ () => removeTodo(id) } >Delete</button>
)
});
function Todo ({ todo }) {
return (
<li>
<TodoCompleted todo={ todo } />
<label>{ todo.title }</label>
<TodoRemove id={ todo.id } />
</li>
)
}
function Main () {
const { todoStore } = useRootStore();
const { todos } = todoStore;
return (
<section>
<ul>
{
todos.map(todo => <Todo todo={ todo } key={ todo.id } />)
}
</ul>
</section>
)
}
export default observer(Main);
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
const TodoCompleted = observer(({ todo }) => {
const { isCompleted, modifyTodoIsCompleted } = todo;
return (
<input
type="checkbox"
checked={ isCompleted }
onChange={ modifyTodoIsCompleted }
/>
)
});
const TodoRemove = observer(({ id }) => {
const { todoStore } = useRootStore();
const { removeTodo } = todoStore;
return (
<button onClick={ () => removeTodo(id) } >Delete</button>
)
});
function Todo ({ todo }) {
return (
<li>
<TodoCompleted todo={ todo } />
<label>{ todo.title }</label>
<TodoRemove id={ todo.id } />
</li>
)
}
function Main () {
const { todoStore } = useRootStore();
const { todos } = todoStore;
return (
<section>
<ul>
{
todos.map(todo => <Todo todo={ todo } key={ todo.id } />)
}
</ul>
</section>
)
}
export default observer(Main);
store/TodoStore.js
import { action, flow, makeObservable, observable } from "mobx";
import Todo from './Todo';
import axios from 'axios';
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable,
loadTodos: flow.bound,
addTodo: action.bound,
removeTodo: action.bound
});
this.loadTodos();
}
*loadTodos () {
const response = yield axios.get('http://localhost:3001/todos');
response.data.forEach(todo => this.todos.push(new Todo(todo)));
}
addTodo (title) {
this.todos.push(new Todo({
title,
id: this.createId()
}));
console.log(this.todos);
}
removeTodo (id) {
this.todos = this.todos.filter(todo => todo.id !== id);
}
createId () {
if (!this.todos.length) return 1;
return this.todos.reduce((id, todo) => (id < todo.id ? todo.id : id), 0) + 1;
}
}
import { action, flow, makeObservable, observable } from "mobx";
import Todo from './Todo';
import axios from 'axios';
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable,
loadTodos: flow.bound,
addTodo: action.bound,
removeTodo: action.bound
});
this.loadTodos();
}
*loadTodos () {
const response = yield axios.get('http://localhost:3001/todos');
response.data.forEach(todo => this.todos.push(new Todo(todo)));
}
addTodo (title) {
this.todos.push(new Todo({
title,
id: this.createId()
}));
console.log(this.todos);
}
removeTodo (id) {
this.todos = this.todos.filter(todo => todo.id !== id);
}
createId () {
if (!this.todos.length) return 1;
return this.todos.reduce((id, todo) => (id < todo.id ? todo.id : id), 0) + 1;
}
}
编辑任务名称
components/Main.js
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
import classnames from 'classnames';
import { useEffect, useRef } from 'react';
const TodoCompleted = observer(({ todo }) => {
const { isCompleted, modifyTodoIsCompleted } = todo;
return (
<input
type="checkbox"
checked={ isCompleted }
onChange={ modifyTodoIsCompleted }
/>
)
});
const TodoRemove = observer(({ id }) => {
const { todoStore } = useRootStore();
const { removeTodo } = todoStore;
return (
<button onClick={ () => removeTodo(id) } >Delete</button>
)
});
const TodoEditing = observer(({ todo }) => {
const { modifyTodoIsEditing, title } = todo;
return (
<label onDoubleClick={ modifyTodoIsEditing }>{ title }</label>
)
});
const Editing = observer(({ todo }) => {
const ref = useRef(null);
const { isEditing, modifyTodoTitle } = todo;
useEffect(() => {
if (isEditing) {
ref.current.focus();
}
}, [ isEditing ])
return (
<input
ref={ref}
className='edit'
defaultValue={ todo.title }
onBlur={ () => modifyTodoTitle(ref.current.value)}
/>
)
});
const Todo = observer(({ todo }) => {
const classname = classnames({
"completed": todo.isCompleted,
"editing": todo.isEditing
});
return (
<li
className={classname}
>
<div>
<TodoCompleted todo={ todo } />
<TodoEditing todo={ todo } />
<TodoRemove id={ todo.id } />
</div>
<Editing todo={ todo } />
</li>
)
});
function Main () {
const { todoStore } = useRootStore();
const { todos } = todoStore;
return (
<section>
<ul>
{
todos.map(todo => <Todo todo={ todo } key={ todo.id } />)
}
</ul>
</section>
)
}
export default observer(Main);
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
import classnames from 'classnames';
import { useEffect, useRef } from 'react';
const TodoCompleted = observer(({ todo }) => {
const { isCompleted, modifyTodoIsCompleted } = todo;
return (
<input
type="checkbox"
checked={ isCompleted }
onChange={ modifyTodoIsCompleted }
/>
)
});
const TodoRemove = observer(({ id }) => {
const { todoStore } = useRootStore();
const { removeTodo } = todoStore;
return (
<button onClick={ () => removeTodo(id) } >Delete</button>
)
});
const TodoEditing = observer(({ todo }) => {
const { modifyTodoIsEditing, title } = todo;
return (
<label onDoubleClick={ modifyTodoIsEditing }>{ title }</label>
)
});
const Editing = observer(({ todo }) => {
const ref = useRef(null);
const { isEditing, modifyTodoTitle } = todo;
useEffect(() => {
if (isEditing) {
ref.current.focus();
}
}, [ isEditing ])
return (
<input
ref={ref}
className='edit'
defaultValue={ todo.title }
onBlur={ () => modifyTodoTitle(ref.current.value)}
/>
)
});
const Todo = observer(({ todo }) => {
const classname = classnames({
"completed": todo.isCompleted,
"editing": todo.isEditing
});
return (
<li
className={classname}
>
<div>
<TodoCompleted todo={ todo } />
<TodoEditing todo={ todo } />
<TodoRemove id={ todo.id } />
</div>
<Editing todo={ todo } />
</li>
)
});
function Main () {
const { todoStore } = useRootStore();
const { todos } = todoStore;
return (
<section>
<ul>
{
todos.map(todo => <Todo todo={ todo } key={ todo.id } />)
}
</ul>
</section>
)
}
export default observer(Main);
store/todo.js
import { action, makeObservable, observable } from "mobx";
export default class Todo {
constructor (todo) {
this.id = todo.id;
this.title = todo.title;
this.isCompleted = todo.isCompleted || false;
this.isEditing = false;
makeObservable(this, {
title: observable,
isCompleted: observable,
isEditing: observable,
modifyTodoIsCompleted: action.bound,
modifyTodoIsEditing: action.bound,
modifyTodoTitle: action.bound
});
}
modifyTodoIsCompleted () {
this.isCompleted = !this.isCompleted;
}
modifyTodoIsEditing () {
this.isEditing = !this.isEditing;
}
modifyTodoTitle (title) {
this.title = title;
this.isEditing = false;
}
}
import { action, makeObservable, observable } from "mobx";
export default class Todo {
constructor (todo) {
this.id = todo.id;
this.title = todo.title;
this.isCompleted = todo.isCompleted || false;
this.isEditing = false;
makeObservable(this, {
title: observable,
isCompleted: observable,
isEditing: observable,
modifyTodoIsCompleted: action.bound,
modifyTodoIsEditing: action.bound,
modifyTodoTitle: action.bound
});
}
modifyTodoIsCompleted () {
this.isCompleted = !this.isCompleted;
}
modifyTodoIsEditing () {
this.isEditing = !this.isEditing;
}
modifyTodoTitle (title) {
this.title = title;
this.isEditing = false;
}
}
计算未完成任务数量
components/Todo/Footer.js
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
const UnCompletedTodoCount = observer(() => {
const { todoStore } = useRootStore();
const { unCompletedTodosCount } = todoStore;
return (
<strong>{ unCompletedTodosCount }</strong>
)
});
function Footer () {
return (
<UnCompletedTodoCount />
)
}
export default Footer;
import { useRootStore } from '../../store';
import { observer } from 'mobx-react-lite';
const UnCompletedTodoCount = observer(() => {
const { todoStore } = useRootStore();
const { unCompletedTodosCount } = todoStore;
return (
<strong>{ unCompletedTodosCount }</strong>
)
});
function Footer () {
return (
<UnCompletedTodoCount />
)
}
export default Footer;
components/Todo/Index.js
import Header from "./Header";
import Main from "./Main";
import Footer from "./Footer";
const Todo = () => {
return (
<>
<Header />
<Main />
<Footer />
</>
)
}
export default Todo;
import Header from "./Header";
import Main from "./Main";
import Footer from "./Footer";
const Todo = () => {
return (
<>
<Header />
<Main />
<Footer />
</>
)
}
export default Todo;
store/TodoStore.js
import { action, computed, flow, makeObservable, observable } from "mobx";
import Todo from './Todo';
import axios from 'axios';
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable,
loadTodos: flow.bound,
addTodo: action.bound,
removeTodo: action.bound,
unCompletedTodosCount: computed
});
this.loadTodos();
}
*loadTodos () {
const response = yield axios.get('http://localhost:3001/todos');
response.data.forEach(todo => this.todos.push(new Todo(todo)));
}
addTodo (title) {
this.todos.push(new Todo({
title,
id: this.createId()
}));
console.log(this.todos);
}
removeTodo (id) {
this.todos = this.todos.filter(todo => todo.id !== id);
}
get unCompletedTodosCount () {
return this.todos.filter(todo => !todo.isCompleted).length;
}
createId () {
if (!this.todos.length) return 1;
return this.todos.reduce((id, todo) => (id < todo.id ? todo.id : id), 0) + 1;
}
}
import { action, computed, flow, makeObservable, observable } from "mobx";
import Todo from './Todo';
import axios from 'axios';
export default class TodoStore {
constructor () {
this.todos = []
makeObservable(this, {
todos: observable,
loadTodos: flow.bound,
addTodo: action.bound,
removeTodo: action.bound,
unCompletedTodosCount: computed
});
this.loadTodos();
}
*loadTodos () {
const response = yield axios.get('http://localhost:3001/todos');
response.data.forEach(todo => this.todos.push(new Todo(todo)));
}
addTodo (title) {
this.todos.push(new Todo({
title,
id: this.createId()
}));
console.log(this.todos);
}
removeTodo (id) {
this.todos = this.todos.filter(todo => todo.id !== id);
}
get unCompletedTodosCount () {
return this.todos.filter(todo => !todo.isCompleted).length;
}
createId () {
if (!this.todos.length) return 1;
return this.todos.reduce((id, todo) => (id < todo.id ? todo.id : id), 0) + 1;
}
}