# mobxLearning
**Repository Path**: hinge/mobxlearning
## Basic Information
- **Project Name**: mobxLearning
- **Description**: 深入mobx
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2017-07-09
- **Last Updated**: 2026-02-05
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 深入 mobx
## 状态管理是什么?
首先状态是什么? 当我们做赋值 x = 1 时, 我们知道x被赋值. 如果某个时刻x被再次赋值, 比如 x = 2, 我们就可以说x是状态. 特别是在前端的场景下, 我们需要让x被显示成html元素, 我们需要和用户交互, 我们需要让用户来决定x的变化. 最常见的工作流程是这样的:
1. js 代码里写 x = 1
2. 让html显示我们的x: $('h1').html(x)
3. 有一个地方让用户去改x,
```
$('button').click(function() {
x = x + 1 // 新的x
$('h1').html(x) // 别忘了更新界面, 显示新的x
})
```
为什么说以上的方式很糟糕? 首先
```
$('h1').html(x)
```
需要在两个地方被维护. 第一个地方是显示x的, 第二个地方是更改x后我们要让界面同步, 再一次写代码让界面显示新的x. 而且这只是复杂度的开始, 随着应用的发展, 我们会有第三个地方, 第四个地方, 第n个地方去修改x. 会散落在代码的任意地方, 被你, 或者其他的程序员添加. 想象一下过几个月让你来维护这些代码? 自己体会.
React(或者vue)它们的制造者就认为, 为什么我每次去改x就要手工写代码去更新界面? 能不能弄个东西让它自动化? 也就是说, 我可以这么写代码:
1. js 代码里写 x = 1
2. ```ReactComponent(
{x})```
3. 只要我们去改 x, 那么界面就自动同步. 也就是2会被自动渲染(render)
然后mobx的制造者又认为, 这不就是观察者模式吗, 我把它再泛化一下, 假设有个状态x, 我通过```extendObserver(x)```来包裹它, 就表示这个x是可以被观察的. 然后我在任意的地方```observer()```包裹一个react组件, 那么x只要变化, mobx就会自动再次渲染这个react组件. mobx还有```autorun()```, 是用来在x变化后触发任意在autorun里包裹的代码, 比如可以```autorun(console.log(x))```.
mobx的目标是把状态集中在一个地方管理. 通过useStrict(true), 保证程序员只能在状态管理相关的模块去改动代码(set action). 这样状态的更改就不会散落在工程的每个角落.
## demo代码运行说明
本demo有几个部分来分别演示mobx的用法. 分别在app.js, app1.js, app2.js中. 在index.js中, 改变这一行: ```import App from './App'``` 成 ```import App from './App1'``` 或者app2就可以看到不同的演示结果.
### App.js ###
本demo中, App.js/appStore.js 主要演示一个嵌套的状态结构如何管理和渲染. 在实际场景中, 我们经常会遇到嵌套的状态结构. 比如, 订单列表包括订单项目. 而本项目的结构是待办任务列表和待办项目:
```
[{task: xxx, completed: xxx}, {task: xxx, completed: xxx} ...]
```
重点是观察如何改变列表项目(item)里的状态. 比如App.js:
```
const App = (props) => {
...
todoStore.setComplete(index)} />
...
todoStore.setComplete(index)} />
```
对应appStore.js:
```
setComplete: action((index) => {
let prev = this.todos[index].completed
this.todos[index].completed = !prev
}),
```
需要向setComplete函数传递一个数组下标使得正确的数组元素被更改.
另外注意观察appStore里的computed(计算状态). 任何可以由其他状态派生出来的状态都可以是计算状态. 比如, 订单数量*单价=金额, 已完成的待办任务数量等等. 这里金额可以通过单价和数量派生, 已完成的待办任务可以通过过滤函数(filter)从数组中过滤得到. 注意计算状态一般为只读状态. 如果你去手工设置这样的状态, 那就违背了你的派生原则, 反而增加bug的可能性.
```
class TodoStore {
...
// 返回已完成待办事项的数目(get方法)
completedTodosCount: computed(() => {
return this.todos.filter(
todo => todo.completed === true
).length
}),
```
### App1.js ###
与App.js不同的地方是把todo的渲染单独做成一个组件, 这里演示了组件分拆的的技术.
### App2.js ###
这里重点是appStore的变化. App2用到了appStore1.js. 这里其实做了状态分拆. 把项目列表中的项目单独分拆出来:
```
// 待办事项项目结构
class Todo {
constructor(task, assignee) {
extendObservable(this, {
task: task, // 任务内容
completed: false, // 是否完成
assignee: assignee, // 任务负责人
setCompleted: action(() => this.completed = !this.completed),
setTodoTask: action((task) => this.task= task)
})
}
}
```
然后在他的容器状态TodoStore里添加一个项目变成:
```
// 添加一个待办事项(set方法)
addTodo: action((task, assignee) => {
this.todos.push(
new Todo(task, false, assignee)
)
})
```
这样做的好处是:
- store里的代码会更加清晰, 列表项目结构Todo不会像appStore.js里那样, 放在TodoStore里的一个方法里
```
class TodoStore {
...
addTodo: action((task, assignee) => {
this.todos.push({
// 待办事项结构
task: task, // 任务内容
completed: false, // 是否完成
assignee: assignee // 任务负责人
})
}),
```
- 处理列表项目状态不用数组下标, 而是直接写
```
class Todo {
...
setCompleted: action(() => this.completed = !this.completed),
setTodoTask: action((task) => this.task= task)
```
- 同样在渲染代码里也简化了
```
const App = (props) => {
...
item.setTodoTask(e.target.value)}
/>
...
item.setCompleted()} />
```