mobx基础
React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而MobX提供机制来存储和更新应用状态供 React 使用。
对于应用开发中的常见问题,React 和 MobX 都提供了最优和独特的解决方案。React 提供了优化UI渲染的机制, 这种机制就是通过使用虚拟DOM来减少昂贵的DOM变化的数量。MobX 提供了优化应用状态与 React 组件同步的机制,这种机制就是使用响应式虚拟依赖状态图表,它只有在真正需要的时候才更新并且永远保持是最新的。
1.@observable 装饰器 使用observable可以吧对象的属性变成单据格或者引用值
import { observable } from "mobx"; class Todo { id = Math.random(); @observable title = ""; }
2.@computed
装饰器. 计算值
相关数据发生变化时自动更新的值
class TodoList { @observable todos = []; @computed get unfinishedTodoCount() { return this.todos.filter(todo => !todo.finished).length; } }
当添加了一个新的todo或者某个todo的 finished
属性发生变化时,MobX 会确保 unfinishedTodoCount
自动更新。
3.reactions. 反应
reactions是在响应式编程和命令式编程之间建立沟通的桥梁
和@computed相似,但是不是产生新的值,而是一些操作,例如 网络请求,打印控制台,更新react组件树
4.react 响应式组件
在组件上添加observer装饰器/函数 就可以把无状态组件变成响应式组件
MobX 会确保组件总是在需要的时重新渲染
import React, {Component} from 'react'; import ReactDOM from 'react-dom'; import {observer} from 'mobx-react'; @observer class TodoListView extends Component { render() { return} } const TodoView = observer(({todo}) =>{this.props.todoList.todos.map(todo =>
Tasks left: {this.props.todoList.unfinishedTodoCount})}
3.1 自定义reactions
使用autorun,reaction,when函数可以创建自定义reactions
例如,每当 unfinishedTodoCount
的数量发生变化时,下面的 autorun
会打印日志消息:
autorun(() => { console.log("Tasks left: " + todos.unfinishedTodoCount) })mobx的基本实现 1.定义可观察的状态 (
observable)
2.创建视图用来响应状态的变化
3.更改状态
mobx会对什么作出反应?
mobx会对追踪函数执行过程中读取现存的可观察属性作出反应
- “读取” 是对象属性的间接引用,可以用过
.
(例如user.name
) 或者[]
(例如user['name']
) 的形式完成。 - “追踪函数” 是
computed
表达式、observer 组件的render()
方法和when
、reaction
和autorun
的第一个入参函数。 - “过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数直接或间接使用并不重要。
换句话说,MobX 不会对其作出反应:
- 从 observable 获取的值,但是在追踪函数之外
- 在异步调用的代码块中读取的 observable
默认情况下 observable
会递归应用
所有字段都可观察 let message = observable({ title: "Foo", author: { name: "Michel" }, likes: [ "John", "Sara" ] })
mobx只追踪同步地访问数据
function upperCaseAuthorName(author) { const baseName = author.name; return baseName.toUpperCase(); } autorun(() => { console.log(upperCaseAuthorName(message.author)) }) message.author.name = "Chesterton"
这将会作出反应。尽管 author.name
不是在 autorun
本身的代码块中进行直接引用的。 MobX 会追踪发生在 upperCaseAuthorName
函数里的间接引用,因为它是在 autorun 执行期间发生的。
autorun(() => { setTimeout( () => console.log(message.likes.join(", ")), 10 ) }) message.likes.push("Jennifer");
这将不会作出反应。在 autorun
执行期间没有访问到任何 observable,而只在 setTimeout
执行期间访问了。 通常来说,这是相当明显的,很少会导致问题。
mobx只会为数据是直接通过render存取的observer组件进行数据追踪
一个使用 observer
的常见错误是它不会追踪语法上看起来像 observer
父组件的数据,但实际上是由不同的组件渲染的。当组件的 render 回调函数在第一个类中传递给另一个组件时,经常会发生这种情况。
const MyComponent = observer(({ message }) => <SomeContainer title = {() =>{message.title}} /> ) message.title = "Bar"
这种情况下,SomeContainer也应该是一个observer,才可以对message.title作出反应
如果 SomeContainer
来源于外部库的话,这通常不在你的掌控之中。在这种场景下,你可以用自己的无状态 observer
组件来包裹 div
解决此问题,或通过利用
组件:
const MyComponent = observer(({ message }) => <SomeContainer title = {() =>} /> ) const TitleRenderer = observer(({ message }) => {message.title}} ) message.title = "Bar"
或者
const MyComponent = ({ message }) => <SomeContainer title = {() =>{() => } /> message.title = "Bar"{message.title}}
避免在本地字段中缓存observer
组件会对 author.name
的变化作出反应,但不会对 message
本身的 .author
的变化作出反应!因为这个间接引用发生在 render()
之外,而render()
是 observer
组件的唯一追踪函数。 注意,即便把组件的 author
字段标记为 @observable
字段也不能解决这个问题,author
仍然是只分配一次。
@observer class MyComponent extends React.component { author; constructor(props) { super(props) this.author = props.message.author; } render() { return{this.author.name}} }
这个问题可以简单地解决,方法是在 render()
中进行间接引用或者在组件实例上引入一个计算属性:
@observer class MyComponent extends React.component { @computed get author() { return this.props.message.author } // ...
多个组件将如何渲染
const Message = observer(({ message }) =>{message.title}) const Author = observer(({ author }) => {author.name} ) const Likes = observer(({ likes }) =>
-
{likes.map(like =>
- {like} )}
变化 | 重新渲染组件 |
---|---|
message.title = "Bar" |
Message |
message.author.name = "Susan" |
Author (.author 在 Message 中进行间接引用, 但没有改变)* |
message.author = { name: "Susan"} |
Message , Author |
message.likes[0] = "Michel" |
Likes |
注意:
- * 如果
Author
组件是像这样调用的:
。Message
会是进行间接引用的组件并对message.author.name
的改变作出反应。尽管如此,
同样会重新渲染,因为它接收到了一个新的值。所以从性能上考虑,越晚进行间接引用越好。 - ** 如果 likes 数组里面的是对象而不是字符串,并且它们在它们自己的
Like
组件中渲染,那么对于发生在某个具体的 like 中发生的变化,Likes
组件将不会重新渲染。
mobx的优点
1,使用@observer的组件真正实现按需更新,只有监听的数据发生变化,它才会re-render,尽管父组件发生更新,但是子组件只要有@observer,则不会触发更新,类似于实现了shouldComponentUpdate的效果,而同样的场景,如果是redux,子组件通过connect绑定了store里部分数据,但是如果父组件发生更新,子组件绑定的数据源并未发生变化,因此子组件不应该更新,然而却强制更新。
mobx耦合性更低。
mobx的缺点
1,store过多导致无法统一数据源,在多人开发的情况下会比较混乱,如果需要公用某个store,唯有在特定组件注入该store,那如果有多个store里有多个数据源需要公用呢,那就要同时注入多个store,项目复杂了,store的管理又是一个问题。
2,mobx对于数组的处理,会将它转换成observableArray,它不是一个数组类型,需要进行数组转换(如slice)。
3,mobx在forceupdate时,并没有像setState那样处理多个更新合并的操作,这就带来个问题就是,在同一个event loop中,如果多次通过action去引起同一个观察者的更新,那么它就会连续更新多次,但是如果是setState,它会进行一次合并,不会有这种负担。(这里的解决方案就是人为的去控制,避免这种多次同时更新,可以将多个action手动合并为1个action);
4,对于某个公共组件,可能多个页面需要用到,但是组件内状态较多,并且不同页面,状态差异较多,此时如果用mobx的store来管理,会存在一个问题,组件虽然unmount了,但是store并未消失,导致需要手动reset store里所有的状态,否则会混乱
mobx的思想
将响应式编程和部分函数式编程相结合,取缔传统React的命令式编程,分而治之,将组件都变成可响应的,React提供了渲染的最优方式,通过对比虚拟DOM的diff算法来减少不必要的渲染耗费,而mobx则给React提供了响应式的状态管理。
关于mobx细粒度拆分
是否所有的state我都交给mobx来管理,其实类似于redux,只有当某个状态需要多个组件(这里默认为2个及以上)公用,则将该状态放到store里去监听,然后需要引用该状态的组件通过mobx-react的inject来进行监听。
关于mobx的优化
这里我参考redux的优化,将容器组件通过mobx-react来连接需要监听的store,然后展示组件就还是通过PureComponent来进行优化,避免重复re-render。这里其实有一点考虑是,我们可以利用@observer来对展示组件需要更新的状态进行一个监听,免去shouldComponentUpdate的步骤,但是如果多挂一层监听,性能未必更优,相当于要多处理一个监听器,所以还是减少监听的个数会好些。
为什么选择mobx而不是redux
这个标题不是为了贬低redux,而是解释为什么在某些项目环境中,我不使用redux,而是使用mobx,能否使用redux?可以,只不过mobx会更让开发者舒服一些。