# 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()} /> ```