投身烈火
Keep Walking

React全家桶又填新成员 MobX入坑指南(1)

话说,吐槽被react全家桶坑的文章已经算是各大论坛上的月经贴了吧……行内样式、jsx里html和js混排、依赖太多、学习曲线陡峭……当然其中不乏抱怨redux反人类的……

当然,这篇文章并不是来黑react不好的,毕竟以后涨工资还得靠它呢哈哈哈( ̄▽ ̄)……

今天咱们要说的是众多槽点之一,”反人类的redux”和其替代方案。

redux有什么不好?

首先,redux绝对是个优秀的库。它体小精悍,api简单优雅,扩展能力强,足以衍生出丰富的工具集和生态系统。在它出现之前,各种flux实现貌似都不能让人如意,以至于有人宁可用 backbone 甚至是 angular 和 react搭配,也不用flux……后来redux出现,解决了flux操作繁琐的问题,开始受到人们的关注,再后来作者也加入了 facebook从事react的开发,redux也顺理成章的成为了react官方推荐状态管理库。

但是,在真正去开发的时候,我发现状态的维护还是有些繁琐,如果你在设计阶段没有考虑周全,开发时就要不停的在action,container,reducer之前修改,穿梭,让人眼花缭乱……嗯,也没准儿是我项目不够大……

另外,由于redux大量使用函数式编程的思想,门槛有点高呢。当时为了理解redux里面的概念,我大概读了一周的文档……

按你胃(Anywhere),如果你更熟悉面向对象,羡慕mvvm框架的简单,那你真的应该体验一下mobx

mobx是啥?

mobx是个新的状态管理库,响应式的,我是看了阮一峰的微博知道的,后来查了资料发现,redux的作者在twitter推荐了这个库,还是作为redex的替代品……

那么废话少说,让我们现在看看mobx怎么用吧

api简单介绍

和redux一样,mobx是一个独立的库,不依赖于react也能自己用,它只有三个概念:观测状态,计算值和反应。这三个概念分别对应三个mobx的api:observable,computed和autorun。

咱们先来个最简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { observable, computed, autorun } from 'mobx';
let numbers = observable([1,2,3]);
let sum = computed(() => numbers.reduce((a, b) => a + b, 0));
let disposer1 = autorun(() => console.log(`sum:${sum.get()}`));
let disposer2 = autorun(() => console.log(`length:${numbers.length}`));
// sum:6
// length:3
numbers.push(4);
// sum:10
// length:4
disposer2();
numbers.push(5);
// sum:15

这个例子中,observable用来绑定数据;computed用来绑定计算方法;autorun用来注册数据变化时响应的方法,返回的函数用来取消响应。

下面我们试着来写一个TodoList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { observable, computed, autorun } from 'mobx';
class Todo {
id = Math.random();
@observable content;
@observable finished = false;
constructor(content) {
this.content = content;
}
}
class TodoList {
@observable todos = [];
@computed get todoListString() {
return this.todos.filter(todo => !todo.finished).map((todo, i) => `${i+1}. ${todo.content}`).join('\n');
}
}
const store = new TodoList();
store.todos.push(
new Todo("task1"),
new Todo("task2"),
new Todo("task3")
);
autorun(() => console.log(store.todoListString))
store.todos[2].finished = true;

从上面的例子可以看到,针对类的属性,可以使用修饰器@observable、@computed来进行绑定,修饰器是ES7的一个提案,目前Babel已经支持。详细的介绍可以看ECMAScript 6 入门中修饰器一章

如果不想用修饰器,也可以使用extendObservable函数,跟修饰器的功能是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { extendObservable, autorun } from 'mobx';
class Todo {
id = Math.random();
constructor(content) {
extendObservable(this, {
content: content,
finished: false
})
}
}
class TodoList {
constructor() {
extendObservable(this, {
todos: [],
todoListString: function() {
return this.todos.filter(todo => !todo.finished).map((todo, i) => `${i+1}. ${todo.content}`).join('\n');
}
})
}
}
const store = new TodoList();
store.todos.push(
new Todo("task1"),
new Todo("task2"),
new Todo("task3")
);
autorun(() => console.log(store.todoListString))
store.todos[2].finished = true;

mobx-react

在跟react配合时,mobx提供了mobx-react包,使用@observer装饰器或者observer函数,可以自动将react组建的render方法包装到mobx.autorun里面。还是以前面的TodoList为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import { observable, computed } from 'mobx';
import { observer } from mobxReact;
import { Component } from React;
class Todo {
id = Math.random();
@observable content;
@observable finished = false;
constructor(content) {
this.content = content;
}
finish() {
this.finished = true
}
}
class TodoList {
@observable todos = [];
@computed get unFinishedList() {
return this.todos.filter(todo => !todo.finished);
}
@computed get finishedList() {
return this.todos.filter(todo => todo.finished);
}
addTodo(content) {
if (content){
this.todos.push(new Todo(content))
}
}
}
@observer
class TodoListView extends Component {
render() {
const { todoList } = this.props;
let onChange = (event) => {
this.value = event.target.value
}
let onClick = () => { todoList.addTodo(this.value) }
return (<div>
<h2>添加任务</h2>
<input type="text" value={ this.value } onChange={ onChange }/>
<button type="button" onClick={ onClick }>添加</button>
<h2>未完成任务</h2>
<ol>
{todoList.unFinishedList.slice(0).map((todo,index) =>
<TodoView todo={todo} key={todo.id} />
)}
</ol>
Tasks left: {todoList.unFinishedList.length}
<h2>已完成任务</h2>
<ol>
{todoList.finishedList.map((todo) =>
<FinishedView todo={todo} key={todo.id} />
)}
</ol>
</div>)
}
}
const TodoView = observer((props) =>
{
let { todo } = props;
return (<li>
<input
type="checkbox"
checked={todo.finished}
onClick={todo.finish.bind(todo)}
/>{todo.content}
</li>)
}
);
@observer
class FinishedView extends Component{
render() {
let { todo } = this.props;
return (<li>
<del>{todo.content}</del>
</li>)
}
}
const store = new TodoList();
React.render(<TodoListView todoList={store} />, document.body);
store.todos.push(
new Todo("Get Coffee"),
new Todo("Write simpler code")
);

在上面的例子里可以看到,使用mobx-react的不同点只是在编写组件类时observer包装了一下,其他的跟不使用mobx-react没什么区别。另外,如果在stroe里加入方法,就可以做到类似action一样的动作,使用上比redux要简单很多。当然这也引出一个问题,就是当子组件状态变化影响到父组件的状态,如何通知父组件。redux用类似dbus的设计解决了这个问题,mobx怎么解决,我还没有看到,后续看到了再做补充。

简单的总结

怎么说呢,总感觉mobx的功能和vue好像…… ̄ω ̄=……特别是computed……这种设计对于写惯了mvc的人来说会感觉非常亲切,stroe其实就是相当于平时常写的model嘛,概念很容易理解。难度上,个人感觉不高,除了一些绑定后的数据需要调用set、get方法,api也不多。

其实mobx还有很多的功能没有介绍到,而且在网上资料也很少,如果有时间的话,打算试着翻译mobx的文档。更多的例子,后续再做详细的补充,今天就先到这里吧,我实在是写不动了……_(:3 」∠)_

相关文章