应该如何理解mobx_mobx使用及最佳实践

mobx是一个简单、可扩展的状态管理库

什么是状态管理

首先我们来想一下这些问题,什么是状态管理,为什么要使用状态管理?

当我们构建一个前端应用时,组件内部都会有一些状态,使用this.state和setState就可以解决发部分问题。

当应用越来越复杂,出现了兄弟组件之间的通信,跨级组件通信,就需要把所有的状态存储到共同的祖先,通过props层层传递,状态发生变化时,在通过层层的传递,通知祖先组件。在一个大型应用中,状态的管理就会变得异常的复杂混乱。

因此我们需要状态管理库。所有组件的公共状态统一存在一个store中,修改和更新状态也在store中。每个组件中都可以直接获取store中状态,并且修改它。

mobx 原理

通俗一点的解释就是,事件触发actions,更改了store中的数据,然后计算值和UI都会更新。

基本概念

Actions:任何可以改变状态的代码,都是actions。最容易理解的是用户事件(点击按钮)

State:存储的全局状态,数据。

Observable:可以被观察的值,不仅原始值可以被观察,引用值也可以被观察,比如对象,数组。当数据被观察后,使用可观察值的组件,会在观察值变化后,自动重新渲染。

Computed values:相关数据变化可以计算出的值,比如一个可观察数组的长度,就是一个计算值。计算值不是实时更新的,只有使用时,才会更新,不再使用时,会自动被垃圾回收。

Reactions:数据变化是的一些变化,比如发起请求,更新React组件等

observer:react组件增加observer装饰,就会从无状态的组件变成响应式组件,当组件使用了可观察的值,可观察的值变化时组件会自动重新渲染。

自定义 reactions:当unfinishedTodoCount变化时,自动打印日志

1

2

3autorun(() => {

console.log("Tasks left: " + todos.unfinishedTodoCount)

})

mobx使用定义一个可观察的状态

定义了一个可观察状态timer,初始值为0

1

2

3

4

5import {observable} from 'mobx';

var appState = observable({

timer: 0

});

定义响应式组件,触发action改变state

定义了一个响应式组件TimerView,button点击调用resetTimer,页面上 Seconds passed显示当前的timer的值

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18import {observer} from 'mobx-react';

@observer

class TimerView extends React.Component {

render() {

return (

Seconds passed: {this.props.appState.timer}

);

}

onReset() {

this.props.appState.resetTimer();

}

};

ReactDOM.render(, document.body);

更改状态

resetTimer函数将timer置为0,定时函数令timer每秒加1.

1

2

3

4

5

6

7appState.resetTimer = action(function reset() {

appState.timer = 0;

});

setInterval(action(function tick() {

appState.timer += 1;

}), 1000);

这就是mobx工作的过程。先定义一些全局共享的状态,比如timer,并且使其可观察。然后定义响应式的组件,组件中使用了timer,当timer更新时,组件会自动重新渲染。组件中的按钮点击事件,又会触发action,造成state中的timer更新。

这就是mobx的工作流程,action改变state,state改变会更新所有受影响的view。当state更新时,所有依赖这些状态的组件,都会开始细颗粒度的更新。

1actions -------> state --------> views

mobx-react 渲染性能优化原理

我们可以通过使用@observer,将react组件转换成一个监听者(Reactions),这样在被监听的应用状态变量(Observable)有更新时,react组件就会重新渲染。如下代码中,TodoItemModel中的应用状态变量有更新时,TodoItem UI会重新渲染:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24class TodoItemModel {

id

@observable title;

@observable completed;

......

}

@observer

class TodoItem extends React.Component {

.....

render() {

//UI logic code ...

let todo = this.props.todo;

let title = todo.title;

let complete = todo.complete;

return (

....

);

}

}

var todoItem = new TodoItemModel();

todoItem.title = '';

在使用react的过程中,我们绕不开渲染性能优化问题,因为默认情况下react组件的shouldComponentUpdate函数会一直返回true,这回导致所有的组件都会进行耗时的虚拟DOM比较。在使用redux作为react的逻辑层框架时,我们可以使用经典的PureComponent+ShallowCompare的方式进行渲染性能优化,那么在使用mobx作为react的store时,我们该如何进行渲染性能优化呢?

(1). 默认的基本渲染性能优化

通过分析源代码发现,在使用@observer将react组件转换成一个监听者(Reactions)后,mobx会为react组件提供一个精确的、细粒度的shouldComponentUpdate函数:

1

2

3

4

5

6

7

8

9shouldComponentUpdate: function(nextProps, nextState) {

......

// update on any state changes (as is the default)

if (this.state !== nextState) {

return true;

}

// update if props are shallowly not equal

return isObjectShallowModified(this.props, nextProps);

}

借助于mobx框架对Observable变量引用的跟踪和依赖收集,mobx能够精确地得到react组件对Observable变量的依赖图谱,然后再用经典的ShallowCompare实现细粒度的shouldComponentUpdate函数,以达到100%无浪费render。这一切都是自动完成地,fantastic!使用mobx后,我们再也无需手动写shouldComponentUpdate函数了。

(2). 使用transaction进行高级渲染性能优化

mobx在带来便利性的同时,也可能引入新的问题。上文反复提到:mobx会记录监听者(Reactions)中对Observable变量的引用,通过引用在运行时动态地构建依赖图谱,从而实现精确地、细粒度地更新。

如果一个React组件依赖于多个应用状态(Observable)变量,而一次操作需要更新多个应用状态(Observable)变量时,React组件就会进行多次渲染。在有些场景下,更新地粒度过细,也不是我们希望看到的场景。比如如下代码,调用TodoItemModel的reset方法设置了两个应用状态变量,会导致对应的UI组件重新渲染两次:

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//todoItem UI组件,依赖于应用状态变量completed和title

@observer

class TodoItem extends React.Component {

...

render() {

//UI logic code ...

let todo = this.props.todo;

let title = todo.title;

let completed = todo.complete;

return (

......

);

}

}

class TodoItemModel {

id;

@observable title;

@observable completed;

......

reset() {

/*

分别设置title和completed值,

会触发两次todoItem UI组件的重新渲染

*/

this.completed = false;

this.title= '';

}

......

}

var todoItem = new TodoItemModel();

//调用reset将触发两次TodoItem UI组件的重新渲染

todoItem.reset();

在语义层面,有时我们需要将多个应用状态(Observable)的更新,视为一次操作,并希望只触发一次监听者(Reactions)的动作(UI更新、网络请求等)。为此mobx提供了transaction功能,可以将对多个应用状态(Observable)的更新封装成一个事务,只有在事务执行完成后,才会触发一次对应的监听者(Reactions)。这就使得我们对组件的渲染有更精细化的控制。比如如下代码,使用transaction后,对应用状态(Observable)的两次更新只会触发一次UI的更新。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21class TodoItemModel {

id;

@observable title;

@observable completed;

......

reset() {

/*

使用transaction设置title和completed值,

只会在函数调用结束后触发一次todoItem UI组件的重新渲染

*/

transaction(()=>{

this.completed = false;

this.title= '';

})

}

......

}

var todoItem = new TodoItemModel();

//调用reset只触发一次TodoItem UI组件的重新渲染

todoItem.reset();

最佳实践

1. 所有更改state都用action

使用action后,可以清楚的看出,哪些代码可以更改可观察的变量。

使用action,方便调试工具给出更多的信息

使用transaction可以将多个应用状态(Observable)的更新视为一次操作,并只触发一次监听者(Reactions)的动作(UI更新、网络请求等),避免多次重复渲染。action中封装了transaction,多次改变@observable变量时,只会重新渲染一次,提高了性能。

1

2

3

4

5

6

7

8

9

10

11

12

13@autobind

class TodoItemModel {

id;

@observable title;

@observable completed;

//使用action后,reset函数执行完成后,才会触发一次其监听者

@action

reset() {

this.completed = false;

this.title= '';

}

}

假设某个组件使用了reset函数,虽然对2个可观察对象completed和title都改变了,但是只会触发一下。

2. 所有的action都放在store中

把所有的状态改变都放在store中,不要散落在各个组件中。在排查问题时,各个组件都可以修改状态,很难定位是哪儿的问题。数据的变化都放在store中,看起来更清晰,也更好维护。

3. 使用compute属性

compute的值是延迟更新的,只有在使用的时候,才会自动计算。不再使用时,它会自动被垃圾回收。因此使用compute的值可以提高性能。

4. 组件使用@observer

如果一个组件需要使用@observable变量(应用状态),就应该使用@observer修饰符。

@observer可以将 React 组件转变成响应式组件,确保任何组件渲染中使用的数据变化时都可以强制刷新组件。

借助于精确的依赖分析,mobx可以得出组件对@observable变量(应用状态)的依赖图谱,对使用@observer进行标记的组件,实现精准的shouldComponentUpdate函数,保证组件100%无浪费渲染。如果我们不对子组件使用@observer,我们就放弃了mobx对React组件的默认性能优化。

5. 延迟对象属性地解引用

本质上,mobx使用ES5的新特性Object.defineProperty对属性设置set和get来实现对对象属性变动的监听和依赖跟踪。只有在真正需要使用Observable变量的组件中,再对其解引用,才能使得mobx构建出正确的依赖图谱。从而使得mobx能够精确的更新对特定属性有依赖的Reactions。

比如对于todolist,正确的写法是在子组件中才对需要使用的属性进行解引用:

1

2

3

4

5

6class TodoItemModel {

id;

@observable title;

@observable completed;

}

let todoItem = new TodoItemModel();

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@observer

class MainSection extends React.Component {

.......

render() {

......

return (

......

{todos.map(

(todo) =>

)}

......

);

}

}

@observer

class TodoItem extends React.Component {

......

render() {

let todo = this.props.todo;

let title = todo.title;

let complete = todo.complete;

return(

......

)

}

}

如果在父组件中对属性过早的解引用,而向子组件传递原始类型的变量,则导致mobx无法搜集到子组件对应用状态的依赖,如下代码是错误的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25class MainSection extends React.Component {

.......

render() {

......

return (

......

{todos.map(

(todo) =>

)}

......

);

}

}

@observer

class TodoItem extends React.Component {

......

render() {

let title = this.props.title;

let complete = this.props.complete;

return(

......

)

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值